diff --git a/ucamlookup/forms.py b/ucamlookup/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..39ca9e03b10489f1e14d33330f9d7f7efd62a414 --- /dev/null +++ b/ucamlookup/forms.py @@ -0,0 +1,59 @@ +import re + +from django import forms +from django.core.exceptions import ValidationError +from django.http import QueryDict + +from ucamlookup import get_or_create_user_by_crsid + + +class UserListField(forms.Field): + """ + A custom `forms.Field` that validates either is single crsid or a list of crsids and replaces + them with a list of `User` objects. If a `User` doesn't exist, it is created. + """ + def clean(self, crsids): + users = () + + if crsids is None: + return users + + if isinstance(crsids, str): + crsids = [crsids] + + crsid_re = re.compile(r'^[a-z][a-z0-9]{3,7}$') + for crsid in crsids: + if crsid_re.match(crsid): + users += (get_or_create_user_by_crsid(crsid),) + else: + raise ValidationError("The list of users contains an invalid user") + + return users + + +def convert_query_dict(query_dict): + """ + This function takes a QueryDict converts it to a normal dict, where single parameters are + converted to strings and parameter arrays remain as lists. For example a QueryDict representing + `?a=1&a=2&b=3` will be converted to `{a: ['1', '2'], b: '3'}`. + + This works around a problem with django forms where the `clean()` method of a `form.Field` + expects to receive a list of data but only receives the first element of that list + (See: https://code.djangoproject.com/ticket/25347). + + It is used is conjunction with `UserListField` as follows: + + ```python + class MyForm(forms.Form): + a_field = UserListField() + + def a_handler(request): + form = MyForm(convert_query_dict(request.POST)) + ``` + """ + assert isinstance(query_dict, QueryDict) + + return { + item[0]: item[1][0] if len(item[1]) == 1 else item[1] + for item in dict(query_dict).items() + }