codeforamerica/intake

View on GitHub
formation/fields.py

Summary

Maintainability
D
2 days
Test Coverage
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.core.validators import (
    EmailValidator, URLValidator, RegexValidator, MinValueValidator,
    MaxValueValidator)
from formation.validators import mailgun_email_validator, is_a_valid_date
from intake.models import County
from formation.field_types import (
    CharField, MultilineCharField, IntegerField, WholeDollarField, ChoiceField,
    YesNoField, YesNoIDontKnowField, MultipleChoiceField, MultiValueField,
    PhoneField, FormNote, DateTimeField, ConsentCheckbox,
    YES_NO_CHOICES, NOT_APPLICABLE, YES_NO_IDK_CHOICES
)
from intake.constants import (
    CONTACT_PREFERENCE_CHOICES, REASON_FOR_APPLYING_CHOICES,
    GENDER_PRONOUN_CHOICES, DECLARATION_LETTER_REVIEW_CHOICES,
    APPLICATION_REVIEW_CHOICES,
    CITIZENSHIP_STATUS_CHOICES)
from project.jinja2 import namify, oxford_comma

###
# Meta fields about the application
###


class DateReceived(DateTimeField):
    context_key = "date_received"
    default_display_format = "%b %-d, %Y"
    display_label = "Applied on"

    def get_display_value(self, strftime_format=None):
        value = super().get_display_value(strftime_format=strftime_format)
        if value:
            local_now = timezone.now()
            time_delta = local_now - self.get_current_value()
            n_days = abs(time_delta.days)
            if n_days == 0:
                note = " (today)"
            else:
                note = " ({} day{} ago)".format(
                    n_days,
                    's' if n_days != 1 else '')
            value = value + note
        return mark_safe(value)


class Counties(MultipleChoiceField):
    template_name = "formation/counties_input.jinja"
    display_template_name = "formation/counties_display.jinja"
    context_key = "counties"
    label = _('Where were you arrested or convicted?')
    help_text = _(
        "We will send your Clear My Record application to agencies in these "
        "counties.")
    display_label = "Wants help with record in"

    def __init__(self, *args, **kwargs):
        # prevents the choices query from being called during import
        self.valid_choices = County.objects.get_county_choices()
        self.possible_choices = \
            County.objects.get_all_counties_as_choice_list()
        self.choices = self.valid_choices
        super().__init__(*args, **kwargs)

    def get_ordered_selected_counties(self):
        selected_counties = self.get_current_value()
        return [
            county for county_slug, county in self.possible_choices
            if county_slug in selected_counties]

    def get_display_for_county(self, county, unlisted_counties=None):
        if (county.slug == 'not_listed') and unlisted_counties:
            return '“{}”'.format(unlisted_counties)
        return county.name

    def get_display_value(self, unlisted_counties=None):
        return oxford_comma([
            self.get_display_for_county(county, unlisted_counties)
            for county in self.get_ordered_selected_counties()])


class UnlistedCountyNote(FormNote):
    context_key = "unlisted_county_note"
    content = mark_safe("""
    <p>
        In counties where we don't have official partners, we will send you
        information on how to get started.
    </p>
    """)


class UnlistedCounties(CharField):
    context_key = "unlisted_counties"
    label = _(
        "Which counties were not listed that you need to clear your record in?"
    )
    display_label = "Needs help in unlisted counties"


class ConsentNote(FormNote):
    context_key = "consent_note"
    content = mark_safe("""
    <p>
      By clicking "Apply",  you are:
    </p>
    <ol>
      <li>
        Giving attorneys in the county you are applying in the permission to
        request your criminal record (RAP sheet).
      </li>
      <li>
        Acknowledging that filling out this form is not a guarantee that an
        attorney will represent you.
      </li>
    </ol>""")


class ConsentToRepresent(ConsentCheckbox):
    context_key = "consent_to_represent"
    is_required_error_message = (
        "The attorneys need your permission in order to help you")
    label = _(
        "Is it okay for attorneys in each county you've selected to access "
        "your criminal record, file petitions for you, and attend court on "
        "your behalf, even if you aren't there?")
    agreement_text = _("Yes, I give them permission to do that")
    display_label = str(
        "Consents to record access, filing, and court representation")


class ConsentSelfRepresent(ConsentCheckbox):
    context_key = "consent_self_represent"
    is_required_error_message = (
        "We need your understanding before we can help you")
    label = _(
        "Do you understand that you will not be represented by the Fresno "
        "County Public Defender’s Office or the Clean Slate Program? The "
        "attorneys will help you prepare your paperwork, but you will have "
        "to represent yourself in court in Fresno County.")
    agreement_text = _("Yes, I understand")
    display_label = str(
        "Consents to self representation")


class UnderstandsLimits(ConsentCheckbox):
    context_key = "understands_limits"
    is_required_error_message = (
        "We need your understanding before we can help you")
    label = _(
        "Do you understand that not everyone qualifies for help, submitting "
        "this form does not guarantee that you will be represented by one of "
        "our partners, and it might take a few months to finish?")
    agreement_text = _("Yes, I understand")
    display_label = str(
        "Understands might not qualify and could take a few months")


class IdentityConfirmation(ConsentCheckbox):
    context_key = "identity_confirmation"
    is_required_error_message = (
        "We need your understanding before we can help you")
    label = _(
        "Do you understand that this application should only be submitted for "
        "yourself or someone who has given you permission to apply on their "
        "behalf, and that it is illegal to use this application to gain "
        "access to someone's private information?")
    agreement_text = _("Yes, I understand")
    display_label = str(
        "Confirms application is for self or with permission")


class UnderstandsMaybeFee(ConsentCheckbox):
    context_key = "understands_maybe_fee"
    is_required_error_message = (
        "We need your understanding before we can help you")
    label = _(
        "Do you understand that if you are eligible, you might have to pay a "
        "fee in Yolo County? If you are low income, you may be eligible for "
        "a fee waiver.")
    agreement_text = _("Yes, I understand")
    display_label = str(
        "Understands that there may be a fee in Yolo County")


class ReasonsForApplying(MultipleChoiceField):
    context_key = "reasons_for_applying"
    label = _("Why are you applying to clear your record?")
    choices = REASON_FOR_APPLYING_CHOICES


class HowDidYouHear(CharField):
    context_key = "how_did_you_hear"
    label = _("How did you hear about this program or website?")
    display_label = "How they found out about this"


class AdditionalInformation(CharField):
    context_key = "additional_information"
    label = _("Is there anything else you would like us to know?")


class DeclarationLetterReviewActions(ChoiceField):
    context_key = 'submit_action'
    choices = DECLARATION_LETTER_REVIEW_CHOICES


class ApplicationReviewActions(ChoiceField):
    context_key = 'submit_action'
    choices = APPLICATION_REVIEW_CHOICES

###
# Identification Questions
###


class NameField(CharField):

    def get_display_value(self):
        return namify(self.get_current_value())


class FirstName(NameField):
    context_key = "first_name"
    label = _('What is your first name?')


class MiddleName(NameField):
    context_key = "middle_name"
    label = _('What is your middle name?')


class LastName(NameField):
    context_key = "last_name"
    label = _('What is your last name?')


class Aliases(NameField):
    context_key = "aliases"
    label = _('Any other names that might be used on your record?')


number_validation_template = "Please enter a {field} between {min} and {max}"
month_validation_message = number_validation_template.format(
    field='month', min=1, max=12)
day_validation_message = number_validation_template.format(
    field='day', min=1, max=31)
year_validation_message = number_validation_template.format(
    field='year', min=1900, max=timezone.now().year)


class DisplayRawIfFalsyMixin:
    def get_display_value(self):
        return self.get_current_value() or self.raw_input_value


class Month(DisplayRawIfFalsyMixin, IntegerField):
    context_key = "month"
    label = _("Month")
    validators = [
        MinValueValidator(1, message=month_validation_message),
        MaxValueValidator(12, message=month_validation_message)
    ]


class Day(DisplayRawIfFalsyMixin, IntegerField):
    context_key = "day"
    label = _("Day")
    validators = [
        MinValueValidator(1, message=day_validation_message),
        MaxValueValidator(31, message=day_validation_message)
    ]


class Year(DisplayRawIfFalsyMixin, IntegerField):
    context_key = "year"
    label = _("Year")
    validators = [
        MinValueValidator(1900, message=year_validation_message),
        MaxValueValidator(timezone.now().year, message=year_validation_message)
    ]


class DateOfBirthField(MultiValueField):
    context_key = "dob"
    label = _("What is your date of birth?")
    help_text = _("For example: 4/28/1986")
    is_required_error_message = _("The public defender may not be able to "
                                  "check your record without a full date "
                                  "of birth.")
    is_recommended_error_message = is_required_error_message
    subfields = [
        Month,
        Day,
        Year
    ]
    display_label = "Date of birth"
    validators = [is_a_valid_date]

    def get_display_value(self):
        return "{month}/{day}/{year}".format(
            month=self.month.get_display_value(),
            day=self.day.get_display_value(),
            year=self.year.get_display_value()
        )


class SocialSecurityNumberField(CharField):
    context_key = "ssn"
    label = _('What is your Social Security Number? (if you have one)')
    help_text = _("The public defender's office will use this to "
                  "get your record and find any "
                  "convictions that can be reduced or dismissed.")
    is_required_error_message = _("The public defender may not be able to "
                                  "check your record without a social "
                                  "security number.")
    is_recommended_error_message = is_required_error_message
    display_label = "SSN"


class LastFourOfSocial(CharField):
    context_key = "last_four"
    label = _(
        'What are the last 4 digits of your Social Security Number? '
        '(if you have one)')
    help_text = _(
        "This helps identify your case from people who have a "
        "similar name.")
    display_label = "SSN (Last 4)"


class DriverLicenseOrIDNumber(CharField):
    context_key = "driver_license_or_id"
    label = _("What is your Driver License or ID number? (if you have one)")
    help_text = _(
        "This helps identify your case from people who have a "
        "similar name.")
    display_label = "Driver License/ID"


class CaseNumber(CharField):
    context_key = "case_number"
    label = _("What is your case number, if you know it?")


class PFNNumber(CharField):
    context_key = "pfn_number"
    display_label = "PFN Number"
    label = _("What is your personal file number (PFN), if you know it?")
    help_text = _(
        "This is a number that is given to people who have been arrested in "
        "Santa Clara County that helps attorneys find your case. ")


###
# Contact Info Questions
###


class ContactPreferences(MultipleChoiceField):
    context_key = "contact_preferences"
    choices = CONTACT_PREFERENCE_CHOICES
    label = _(
        'How would you like Clear My Record to update you about your '
        'application?')
    help_text = _(
        'An attorney may need to send you official documents in the mail '
        'or call you to help with your case.')
    display_label = "Preferred contact methods"

    def get_display_value(self):
        return super().get_display_value(use_or=True)


class PreferredPronouns(ChoiceField):
    context_key = "preferred_pronouns"
    choices = GENDER_PRONOUN_CHOICES
    label = _('How would you like us to address you?')
    display_label = "Preferred pronouns"


class PhoneNumberField(PhoneField):
    context_key = "phone_number"
    help_text = _('For example, (555) 555-5555')
    label = _('What is your phone number?')
    autocomplete = "tel"


class AlternatePhoneNumberField(PhoneNumberField):
    context_key = "alternate_phone_number"
    label = _('What is another phone number we can reach you at?')


class FaxNumberField(PhoneNumberField):
    context_key = "fax_number"


class EmailField(CharField):
    context_key = "email"
    label = _('What is your email?')
    help_text = _('For example "yourname@example.com"')
    validators = [
        EmailValidator(_("Please enter a valid email")),
        mailgun_email_validator
    ]
    display_template_name = "formation/email_display.jinja"


class WebsiteField(CharField):
    context_key = "website"
    label = _("What is your website?")
    help_text = _('Like "example.com"')
    validators = [
        URLValidator(_("Please enter a valid URL")),
    ]
    display_template_name = "formation/url_display.jinja"


class Street(CharField):
    context_key = "street"
    autocomplete = "street-address"


class City(CharField):
    context_key = "city"
    label = _("City")
    autocomplete = "locality"


class State(CharField):
    context_key = "state"
    label = _("State")
    autocomplete = "region"


class Zip(CharField):
    context_key = "zip"
    label = _("Zip")
    autocomplete = "postal-code"


class AddressField(MultiValueField):
    context_key = "address"
    label = _("Where is a safe place you can receive mail?")
    help_text = _("Enter your mailing address")
    template_name = "formation/multivalue_address.jinja"
    is_required_error_message = _("The public defender needs a mailing "
                                  "address to send you a letter with the next "
                                  "steps.")
    is_recommended_error_message = is_required_error_message
    subfields = [
        Street,
        City,
        State,
        Zip
    ]
    display_template_name = "formation/address_display.jinja"

    def get_display_value(self):
        return "{street}\n{city}, {state}\n{zip}".format(
            **self.get_current_value())

    def get_inline_display_value(self):
        return "{street}, {city}, {state} {zip}".format(
            **self.get_current_value())


class IsCaliforniaResident(YesNoField):
    context_key = "is_california_resident"
    label = _("Are you a current resident of California?")
    display_label = "California resident?"


class HowLongCaliforniaResident(CharField):
    context_key = "how_long_california_resident"
    label = _(
        "If you live in California, how long have you continuously lived "
        "here?")
    help_text = _("For example: 5 years")
    display_label = "How long?"


###
# Case status and screening
###

class CitizenshipStatus(ChoiceField):
    context_key = "us_citizen"
    choices = CITIZENSHIP_STATUS_CHOICES
    label = _("What is your citizenship status?")
    help_text = _(
        "It is important for your attorney to know if you are a U.S citizen "
        "so they can find the best ways to help you. Your citizenship status "
        "will not be shared with any law enforcement agencies.")
    display_label = "Citizenship status"


class IsVeteran(YesNoField):
    context_key = "is_veteran"
    label = _("Are you a veteran?")


class IsStudent(YesNoField):
    context_key = "is_student"
    label = _("Are you a student?")


class BeingCharged(YesNoIDontKnowField):
    context_key = "being_charged"
    label = _("Are you currently being charged with a crime?")
    display_label = "Is currently being charged"
    flip_display_choice_order = True


class ServingSentence(YesNoIDontKnowField):
    context_key = "serving_sentence"
    label = _("Are you currently serving a sentence?")
    display_label = "Is serving a sentence"
    flip_display_choice_order = True


class OnProbationParole(YesNoIDontKnowField):
    context_key = "on_probation_parole"
    label = _("Are you on probation or parole (including MSR or PRCS)?")
    help_text = _(
        "MSR is mandatory supervised release and PRCS is post release "
        "community supervision")
    display_label = "Is on probation or parole"
    flip_display_choice_order = True


class WhereProbationParole(CharField):
    context_key = "where_probation_or_parole"
    label = _("If you are on probation or parole, what county is it in?")
    display_label = "Where"


class WhenProbationParole(CharField):
    context_key = "when_probation_or_parole"
    label = _("If you are on probation or parole, when does it end?")
    display_label = "Until"


class FinishedHalfProbation(YesNoIDontKnowField):
    context_key = "finished_half_probation"
    choices = YES_NO_IDK_CHOICES + ((NOT_APPLICABLE, _("Not on probation")),)
    label = _("If you're on probation, have you finished half of your "
              "probation time?")
    display_label = "Finished half probation"


class ReducedProbation(FinishedHalfProbation):
    context_key = "reduced_probation"
    label = _("If you're on probation, has the judge promised to reduce your "
              "probation time or end your probation early?")
    display_label = "Reduced probation"


class OtherCountyArrestsOrConvictions(YesNoIDontKnowField):
    context_key = "rap_outside_sf"
    label = _(
        "Have you ever been arrested or convicted in any other counties?")
    display_label = "RAP in other counties"
    flip_display_choice_order = True


class WhenWhereOtherCounties(CharField):
    context_key = "when_where_outside_sf"
    label = _(
        "If you were arrested or convicted in other counties, which ones and "
        "when?")
    display_label = "Where/when"


class HasSuspendedLicense(ChoiceField):
    context_key = "has_suspended_license"
    label = _("Is your driver's license suspended?")
    display_label = "Has suspended license"
    flip_display_choice_order = True
    choices = YES_NO_IDK_CHOICES

    def get_display_choices(self):
        return YES_NO_IDK_CHOICES


class OwesCourtFees(YesNoIDontKnowField):
    context_key = "owes_court_fees"
    label = _("Do you owe any court fines or fees?")
    display_label = "Owes court fines/fees"
    flip_display_choice_order = True


class BeenToPrison(YesNoIDontKnowField):
    context_key = "has_been_to_prison"
    label = _("Have you been to prison?")
    flip_display_choice_order = True


class HasBeenDeniedHousingOrEmployment(YesNoIDontKnowField):
    context_key = "has_been_denied_housing_or_employment"
    label = _("Have you been denied housing or employment because of your "
              "criminal record?")


class WhoWhenDeniedHousingOrEmployment(CharField):
    context_key = "who_denied_housing_or_employment"
    label = _("If you have been denied housing or employment when and by "
              "which organization/agency?")
    display_label = "Denied housing/employment by"


class SeekingEmploymentThatRequiresLiveScan(YesNoIDontKnowField):
    context_key = "seeking_employment_that_requires_livescan"
    label = _("Are you seeking employment that will require Live Scan "
              "fingerprinting?")
    display_label = "Seeking job that requires LiveScan"


class IsRegisteredUnderPc290(YesNoIDontKnowField):
    context_key = "is_registered_under_pc_290"
    label = _("Are you registered under PC section 290 "
              "(California sex offender registration)?")
    display_label = "Registered under PC 290"


###
# Financial Questions
###


class FinancialScreeningNote(FormNote):
    context_key = "financial_screening_note"
    content = _("Our partners use information about your income to "
                "give low income applicants special help and get help from "
                "government programs.")


class CurrentlyEmployed(YesNoField):
    context_key = "currently_employed"
    label = _("Are you currently employed?")
    display_label = "Is employed"


class IsReasonableMonthsWages:
    amount_warning = _("Are you sure {} is the right amount per month ?")

    def __init__(self, bottom, top, zero_okay=True):
        self.interval = (bottom, top)
        self.zero_okay = zero_okay

    def set_context(self, field):
        self.field = field

    def __call__(self, value):
        bottom, top = self.interval
        if self.zero_okay and value == 0:
            return
        if not (bottom < value < top):
            self.field.add_warning(
                self.amount_warning.format(
                    self.field.get_display_value())
            )


class MonthlyIncome(WholeDollarField):
    context_key = "monthly_income"
    additional_classes = WholeDollarField.additional_classes + ['monthly']
    label = _("What is your monthly household income?")
    help_text = _("Include your spouse or legal partner's income. "
                  "Your best estimate is okay.")
    validators = [
        IsReasonableMonthsWages(10, 10000),
    ]


class OtherIncome(WholeDollarField):
    context_key = "other_income"
    label = _("In the last 12 months, how much money have you received from "
              "other sources such as lawsuits, a tax refund, and/or pension "
              "fund?")
    help_text = _("Enter a dollar amount. Your best estimate is okay.")


class IncomeSource(CharField):
    context_key = "income_source"
    label = _("Where does your income come from?")
    help_text = _("For example: Job, Social Security, Food stamps")


class OnPublicBenefits(YesNoField):
    context_key = "on_public_benefits"
    label = _("Are you on any government benefits?")


class OwnsHome(YesNoField):
    context_key = "owns_home"
    label = _("Do you own a home or rental property?")


class MonthlyExpenses(WholeDollarField):
    context_key = "monthly_expenses"
    additional_classes = WholeDollarField.additional_classes + ['monthly']
    help_text = _("Your best estimate is okay.")
    label = _("How much do you spend each month on things like rent, "
              "groceries, utilities, medical expenses, or childcare expenses?")


class HowMuchSavings(WholeDollarField):
    context_key = "how_much_savings"
    help_text = _("Your best estimate is okay.")
    label = _("How much money do you have saved?")


class HouseholdSize(IntegerField):
    context_key = "household_size"
    label = _("How many people live with you?")
    help_text = _('For example: "3" or "0"')

    def get_display_value(self):
        """The question asks for people in addition to the applicant but
        reviewers typically want to see the applicant included in the number.
        """
        value = self.get_current_value()
        if value is not None:
            value += 1
        return value


class HowManyDependents(IntegerField):
    context_key = "dependents"
    label = ("How many people depend on your financial support?")


class IsMarried(YesNoField):
    context_key = "is_married"
    label = _("Are you married or in a legal domestic partnership?")


class HasChildren(YesNoField):
    context_key = "has_children"
    label = _("Do you have children?")


###
# Declaration Letter
###


class DeclarationLetterNote(FormNote):
    context_key = "declaration_letter_note"
    content = _("Create your letter to the judges in the counties you "
                "applied to. This is required to complete your application.")


class DeclarationLetterIntro(MultilineCharField):
    context_key = "declaration_letter_intro"
    label = _("Introduce yourself. What has been going on in your life?")
    help_text = _("Write your answer in 3-5 sentences. The judge will read "
                  "this when deciding whether to clear your record.")


class DeclarationLetterLifeChanges(DeclarationLetterIntro):
    context_key = "declaration_letter_life_changes"
    label = _("How is your life different now, since your last conviction?")


class DeclarationLetterActivities(DeclarationLetterIntro):
    context_key = "declaration_letter_activities"
    label = _("Have you been involved in jobs, programs, activities, or "
              "community service? What were they and what did you do?")


class DeclarationLetterGoals(DeclarationLetterIntro):
    context_key = "declaration_letter_goals"
    label = _("What goals are you working on achieving in your life right "
              "now? How are you working on them?")


class DeclarationLetterWhy(DeclarationLetterIntro):
    context_key = "declaration_letter_why"
    label = _("Why do you want to clear your record? How will this change or "
              "help your life?")


INTAKE_FIELDS = [
    DateReceived,
    Counties,
    UnlistedCountyNote,
    UnlistedCounties,

    ContactPreferences,

    FirstName,
    MiddleName,
    LastName,
    Aliases,

    PreferredPronouns,

    PhoneNumberField,
    AlternatePhoneNumberField,
    EmailField,
    AddressField,
    IsCaliforniaResident,
    HowLongCaliforniaResident,
    DateOfBirthField,
    DriverLicenseOrIDNumber,
    LastFourOfSocial,
    SocialSecurityNumberField,
    CaseNumber,
    PFNNumber,

    CitizenshipStatus,
    IsVeteran,
    IsStudent,
    BeingCharged,
    ServingSentence,
    BeenToPrison,
    OnProbationParole,
    WhereProbationParole,
    WhenProbationParole,
    FinishedHalfProbation,
    ReducedProbation,
    OtherCountyArrestsOrConvictions,
    WhenWhereOtherCounties,
    HasSuspendedLicense,
    OwesCourtFees,
    HasBeenDeniedHousingOrEmployment,
    WhoWhenDeniedHousingOrEmployment,
    SeekingEmploymentThatRequiresLiveScan,
    IsRegisteredUnderPc290,

    FinancialScreeningNote,
    CurrentlyEmployed,
    MonthlyIncome,
    IncomeSource,
    HowMuchSavings,
    OnPublicBenefits,
    OtherIncome,
    MonthlyExpenses,
    OwnsHome,
    HouseholdSize,
    HasChildren,
    HowManyDependents,
    IsMarried,

    ReasonsForApplying,
    HowDidYouHear,
    AdditionalInformation,
    UnderstandsLimits,
    IdentityConfirmation,
    UnderstandsMaybeFee,
    ConsentToRepresent,
    ConsentSelfRepresent,
    ConsentNote,

    DeclarationLetterNote,
    DeclarationLetterIntro,
    DeclarationLetterLifeChanges,
    DeclarationLetterActivities,
    DeclarationLetterGoals,
    DeclarationLetterWhy,
]


FIELD_NAME_LOOKUP = {
    c.context_key: c for c in INTAKE_FIELDS
}


def get_field_index(field):
    return INTAKE_FIELDS.index(field)