Design Patterns Reference¶
Due to the size of the AMY codebase and the number of views available, it can be tricky to figure out if something has been done before or not.
This document acts as a reference for where certain design patterns can be found in the UI and code base.
Forms¶
Update choice field options dynamically when a different field is updated¶
Demo: Go to the edit view for any Person with an Instructor badge, and select the "Community Roles" tab. Notice how changing the "Role name" selection between "Maintainer" and "Instructor" updates the options available under "Associated award" and "Generic relation object."
Method: It's not possible to make these dynamic updates using Django without submitting the form. Instead, we must use JavaScript.
- Use JavaScript to change the request that is used to get the available choices for the dynamically changing field. Add the dependent field as a parameter in the request. (e.g.
$("#id_communityrole-award").select2({...})
in /static/communityrole_form.js) - In the relevant form, ensure that the JS file is included under the
Media
metaclass (e.g.CommunityRoleForm
in /communityroles/forms.py) - In the relevant lookup view, check for the presence of the extra parameters and use them to filter the results as needed (e.g.
AwardLookupView
in /workshops/lookups.py) - Write tests using different parameter settings (e.g.
TestAwardLookupView
in /workshops/tests/test_lookups.py)
Make a field required/not required according to another field's value¶
Demo: Go to the edit view for any Person with an Instructor badge, and select the "Community Roles" tab. Leave the rest of the form empty, and notice how changing the "Role name" selection between "Trainer" and "Instructor" changes which fields give a "Please fill in this field" error if you click Submit.
Method: It's not possible to make these dynamic updates using Django without submitting the form. Instead, we must use JavaScript.
- Use JavaScript to set the
required
property on the dynamically changing field when the dependent field is updated. (e.g. in /static/communityrole_form.js) - In the relevant form, ensure that the JS file is included under the
Media
metaclass (e.g.CommunityRoleForm
in /communityroles/forms.py) - Ensure that the relevant form/model has validation that matches the dynamic behaviour (e.g.
CommunityRoleForm.clean()
in /communityroles/forms.py)
Autofill some form fields when creating one object using data from another¶
Demo: Go to the "Workshop requests" page and open the detail view for any pending request. Select 'Accept and create a new event' at the bottom. Notice that some fields in the Event form are pre-filled with data from the request (e.g. Start and End, Curricula, Tags).
Method:
- Create a view that inherits the
AMYCreateAndFetchObjectView
- Set a URL for the view that includes the original object's ID, e.g.
workshop_request/<int:request_id>/accept_event/
- Set the
model
andform_class
variables in the view according to the model of the object that will be created - Set the
queryset_other
,context_other_object_name
, andpk_url_kwarg
variables in the view according to the original object. These are used byAMYCreateAndFetchObjectView
to select the correct object to use data from. The object will become available asself.other_object
in the view - Override
get_initial()
to set form fields based on data inself.other_object
Perform "soft validation" to allow possibly invalid data to be submitted with only a warning¶
Demo: On test-amy or a local AMY instance, go to the Instructor Training application form and select "Profile Creation for Pre-approved Trainees." Enter some random letters as the registration code (an invalid input), then fill out the rest of the required form fields. After clicking "Submit", if member code enforcement is enabled, you should see an error on the registration code starting with "This code is invalid." Underneath the field, a checkbox should be visible with the label "Continue with registration code marked as invalid" - if you check this box, you should now be able to submit the form.
Method:
- Add a field
<fieldname>-override
to the relevant model. This should be aBooleanField
defaulting toFalse
. - On
Form
s connected to the model, manipulate thehelper.layout
to show/hide the new field according to the value of the field you're implementing soft validation on (e.g. thevalidate_member_code
method ofTrainingRequestForm
in/extforms/forms.py
shows and hides themember_code_override
field according to the validity of themember_code
). - Build validation carefully for the override and the field it relates to. The override should only be required and
True
if the related field is invalid. In other cases, it should beFalse
- this may require updating the value during validation (e.g. thevalidate_member_code
method again). - Consider adding a filter to help admins find objects where the override was used. Beware that the default
django_filters.BooleanFilter
is not quite appropriate - typically you will want all results to be shown when the filter isFalse
, and only results that use the override to be shown when the filter isTrue
(e.g.invalid_member_code
filter inTrainingRequestFilter
in /extrequests/filters.py).
Reference files:
extrequests/views.py
- all the...AcceptEvent
classesextrequests/urls.py
extrequests/base_views.py
-WRFInitial
andAMYCreateAndFetchObjectView
Tests¶
Migration tests¶
Use the django_test_migrations
package, e.g. /workshops/tests/test_migrations.py