Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional logic - Voting #209

Merged
merged 8 commits into from
Dec 20, 2019
27 changes: 16 additions & 11 deletions crt_portal/crt_portal/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,26 @@
from django.conf.urls.static import static
from django.views.generic import RedirectView

from cts_forms.forms import Contact, Details, PrimaryReason, LocationForm, ProtectedClassForm
from cts_forms.views import CRTReportWizard
from cts_forms.forms import Contact, Details, PrimaryReason, LocationForm, ProtectedClassForm, ElectionLocation
from cts_forms.views import CRTReportWizard, show_election_form_condition, show_location_form_condition

urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
path('form/', include('cts_forms.urls')),
path('report/', CRTReportWizard.as_view([
Contact,
PrimaryReason,
LocationForm,
ProtectedClassForm,
Details,
# WhatHappened,
# Who,
]), name='crt_report_form'),
path('report/', CRTReportWizard.as_view(
[
Contact,
PrimaryReason,
ElectionLocation,
LocationForm,
ProtectedClassForm,
Details,
],
condition_dict={
'2': show_election_form_condition,
'3': show_location_form_condition,
},
), name='crt_report_form'),
path('', RedirectView.as_view(pattern_name='crt_report_form', permanent=False)),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
52 changes: 36 additions & 16 deletions crt_portal/cts_forms/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .widgets import UsaRadioSelect, UsaCheckboxSelectMultiple, CrtRadioArea, CrtDropdown, CrtMultiSelect
from .models import Report, ProtectedClass, HateCrimesandTrafficking
from .model_variables import (
ELECTION_CHOICES,
RESPONDENT_TYPE_CHOICES,
PROTECTED_CLASS_CHOICES,
PROTECTED_CLASS_ERROR,
Expand Down Expand Up @@ -127,6 +128,16 @@ class Meta:


class LocationForm(ModelForm):
class Meta:
model = Report
fields = [
'location_name',
'location_address_line_1',
'location_address_line_2',
'location_city_town',
'location_state',
]

def __init__(self, *args, **kwargs):
super(ModelForm, self).__init__(*args, **kwargs)

Expand Down Expand Up @@ -158,29 +169,38 @@ def __init__(self, *args, **kwargs):
QuestionGroup(
self,
('location_name', 'location_address_line_1', 'location_address_line_2'),
group_name='Where did this happen?',
help_text='Please be as specific as possible. We will handle this information with sensitivity.',
group_name=_('Where did this happen?'),
help_text=_('Please be as specific as possible. We will handle this information with sensitivity.'),
optional=False
)
]


class ElectionLocation(LocationForm):
class Meta:
model = Report
fields = [
'location_name',
'location_address_line_1',
'location_address_line_2',
'location_city_town',
'location_state',
]
fields = LocationForm.Meta.fields + ['election_details']

widgets = {
'location_name': TextInput(attrs={'class': 'usa-input'}),
'location_address_line_1': TextInput(attrs={'class': 'usa-input'}),
'location_address_line_2': TextInput(attrs={'class': 'usa-input'}),
'location_city_town': TextInput(attrs={'class': 'usa-input'}),
'location_state': CrtDropdown,
}
def __init__(self, *args, **kwargs):
LocationForm.__init__(self, *args, **kwargs)
self.question_groups = [
QuestionGroup(
self,
('election_details',),
group_name=_('What kind of election or voting activity was this related to?'),
optional=False
)
] + self.question_groups

self.fields['election_details'] = TypedChoiceField(
choices=ELECTION_CHOICES,
empty_value=None,
widget=UsaRadioSelect,
required=True,
error_messages={
'required': _('Please select the type of election or voting activity.')
}
)


class ProtectedClassForm(ModelForm):
Expand Down
18 changes: 18 additions & 0 deletions crt_portal/cts_forms/migrations/0028_add_election_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2019-12-16 19:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cts_forms', '0027_merge_20191218_1749'),
]

operations = [
migrations.AddField(
model_name='report',
name='election_details',
field=models.CharField(blank=True, choices=[('federal', 'Federal- presidential, or congressional'), ('state_local', 'State or local- Governor, state legislation, city position (mayor, council, local board)'), ('both', 'Both Federal & State/local'), ('unknown', 'I don’t know')], max_length=225, null=True),
),
]
8 changes: 7 additions & 1 deletion crt_portal/cts_forms/model_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
'something_else': []
}

ELECTION_CHOICES = (
('federal', _('Federal- presidential, or congressional')),
('state_local', _('State or local- Governor, state legislation, city position (mayor, council, local board)')),
('both', _('Both Federal & State/local')),
('unknown', _('I don’t know')),
)

HATE_CRIMES_TRAFFICKING_MODEL_CHOICES = (
('physical_harm', _('Physical harm or threats of violence based on race, color, national origin, religion, gender, sexual orientation, gender identity, or disability')),
('trafficking', _('Coerced or forced to do work or perform a commercial sex act')),
Expand All @@ -62,7 +69,6 @@
_('Coerced or forced to do work or perform a commercial sex act'),
)


# PROTECTED_CLASS_CHOICES means "PROTECTED_CLASS_FORM_CHOICES" and refers to the choices that will be displayed on the form front-end.
# See protected maintenance docs: https://github.com/usdoj-crt/crt-portal/blob/develop/docs/maintenance_or_infrequent_tasks.md#change-protected-class-options
# This tuple will create the initial order, the form_order data can be directly adjusted after the initial load.
Expand Down
2 changes: 2 additions & 0 deletions crt_portal/cts_forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
PROTECTED_MODEL_CHOICES,
STATUS_CHOICES,
SECTION_CHOICES,
ELECTION_CHOICES,
HATE_CRIMES_TRAFFICKING_MODEL_CHOICES,
)

Expand Down Expand Up @@ -84,6 +85,7 @@ class Report(models.Model):
location_city_town = models.CharField(max_length=700, blank=False)
location_state = models.CharField(max_length=100, blank=False, choices=STATES_AND_TERRITORIES)
create_date = models.DateTimeField(auto_now_add=True)
election_details = models.CharField(choices=ELECTION_CHOICES, max_length=225, null=True, blank=True)
###############################################################
# These fields have not been implemented in the form yet: #
###############################################################
Expand Down
1 change: 0 additions & 1 deletion crt_portal/cts_forms/templates/forms/report_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ <h1 class="padding-top-5 margin-0">
<span class="usa-sr-only">
Current step: {{ current_step_name }}. Step {{ wizard.steps.step1 }} of 6.
</span>

<div class="page-header-background">
<div class="grid-container">
<div class="grid-row grid-gap">
Expand Down
31 changes: 30 additions & 1 deletion crt_portal/cts_forms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ def ShowView(request, id):
'forms/report_grouped_questions.html',
# Primary reason
'forms/report_multiple_questions.html',
# Election + location
'forms/report_location.html',
# Location
'forms/report_location.html',
# Protected Class
Expand All @@ -126,6 +128,22 @@ def ShowView(request, id):
]


def show_election_form_condition(wizard):
# try to get the cleaned data of step 1
cleaned_data = wizard.get_cleaned_data_for_step('1') or {'primary_complaint': 'not yet completed'}
if cleaned_data['primary_complaint'] == 'voting':
return True
return False


def show_location_form_condition(wizard):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that the default location question set was always displayed during the 'where' step. If that is the case, do we need to check the negative condition here?

Based on the mocks I see both appearing, so it feels like just the check to explicitly show the election section of the form is needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Django form wizard, I think of the forms as pages and the fields as questions.

We wanted the election information to be on the location pages, so the approach is to have two versions of the location page. Based on your primary issue response, you will either will get the Location form page which only has the location questions or the ElectionLocation form page, which has the location questions and the election question.

                        | (voting)  -->  election location --|
contact -> primary issue|                                    |--> protected class --> details
                        | (not voting) -->  location       --|    

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, I totally get that they are distinct pages. I guess I was just hoping that we could eliminate one of the choices in the dict in urls.py, e.g. just have a condition for 2, since 3 would be the default in all other cases. Seems that is not the case, or might not be useful even if it is. Thanks!

# try to get the cleaned data of step 1
cleaned_data = wizard.get_cleaned_data_for_step('1') or {'primary_complaint': 'not yet completed'}
if cleaned_data['primary_complaint'] != 'voting':
return True
return False


class CRTReportWizard(SessionWizardView):
"""Once all the sub-forms are submitted this class will clean data and save."""
def get_template_names(self):
Expand All @@ -145,13 +163,24 @@ def get_context_data(self, form, **kwargs):
_('Protected Class'),
_('Details'),
]
current_step_name = ordered_step_names[int(self.steps.current)]
# Name for all forms whether they are skipped or not
all_step_names = [
_('Contact'),
_('Primary Issue'),
_('Location'),
_('Location'),
_('Protected Class'),
_('Details'),
]

current_step_name = all_step_names[int(self.steps.current)]

# This title appears in large font above the question elements
ordered_step_titles = [
_('Contact'),
_('What is your primary reason for contacting the Civil Rights Division?'),
_('Location details'),
_('Location details'),
_('Please provide details'),
_('Details'),
]
Expand Down
17 changes: 17 additions & 0 deletions docs/maintenance_or_infrequent_tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,20 @@ If you change the the ProtectedClass model, you may need to squish the migration
To rename existing models, change the name in model_variables.py and create a data migration like: crt_portal/cts_forms/migrations/0016_rename_more_protected_class.py

You should be able to reorder the form by setting the value in the database or making a data migration to update the protected classes and form_order. Do NOT use the Django admin for this task, you can use the Django shell.


## Add a new optional form
See example code here: https://github.com/usdoj-crt/crt-portal/pull/209/files

1) Make any model changes in models.py, such as new fields. Create and then apply the migration

2) Make a form class in forms.py. You can inherit pieces of other forms if this form is similar to another.

3) make a function to determine when you want the form to show on the front end

4) Add the form class and the function to urls.py

5) In views.py
- add the new page name for the form page `to all_step_names`
- add the top-line title for the page to `ordered_step_titles`
- add an existing or template to the `TEMPLATES` list
2 changes: 1 addition & 1 deletion pa11y_scripts/.pa11y-ci-p3.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"wait for element #id_1-primary_complaint to be visible",
"click element #id_1-primary_complaint_0",
"click element #submit-next",
"wait for element #id_2-location_name to be visible"
"wait for element #id_3-location_name to be visible"
],
"screenCapture": "pa11y-screenshots/page3.png"
}
Expand Down
14 changes: 7 additions & 7 deletions pa11y_scripts/.pa11y-ci-p4.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
"wait for element #id_1-primary_complaint to be visible",
"click element #id_1-primary_complaint_0",
"click element #submit-next",
"wait for element #id_2-location_name to be visible",
"set field #id_2-location_name to Store",
"set field #id_2-location_city_town to Town",
"set field #id_2-location_state_0 to AL",
"wait for element #id_3-location_name to be visible",
"set field #id_3-location_name to Store",
"set field #id_3-location_city_town to Town",
"set field #id_3-location_state_0 to AL",
"click element #submit-next",
"wait for element #id_3-protected_class to be visible",
"click element #id_3-protected_class_0",
"wait for element #id_4-protected_class to be visible",
"click element #id_4-protected_class_0",
"click element #submit-next",
"wait for element #id_4-violation_summary to be visible"
"wait for element #id_5-violation_summary to be visible"
],
"screenCapture": "pa11y-screenshots/page3.png"
}
Expand Down