示例#1
0
 class Meta:
     model = Sponsorship
     fields = '__all__'
     widgets = {
         'organization': ModelSelect2(url='organization-lookup'),
         'event': ModelSelect2(url='event-lookup'),
         'contact': ModelSelect2(url='person-lookup'),
     }
示例#2
0
 class Meta:
     model = Task
     fields = '__all__'
     widgets = {
         'person': ModelSelect2(url='person-lookup',
                                attrs=SIDEBAR_DAL_WIDTH),
         'event': ModelSelect2(url='event-lookup', attrs=SIDEBAR_DAL_WIDTH),
     }
示例#3
0
class TrainingProgressForm(forms.ModelForm):
    trainee = forms.ModelChoiceField(label='Trainee',
                                     required=True,
                                     queryset=Person.objects.all(),
                                     widget=ModelSelect2(url='person-lookup'))
    evaluated_by = forms.ModelChoiceField(
        label='Evaluated by',
        required=False,
        queryset=Person.objects.all(),
        widget=ModelSelect2(url='admin-lookup'))
    event = forms.ModelChoiceField(label='Event',
                                   required=False,
                                   queryset=Event.objects.all(),
                                   widget=ModelSelect2(url='event-lookup'))

    # helper used in edit view
    helper = BootstrapHelper(duplicate_buttons_on_top=True,
                             submit_label='Update',
                             add_delete_button=True,
                             additional_form_class='training-progress',
                             add_cancel_button=False)

    # helper used in create view
    create_helper = BootstrapHelper(duplicate_buttons_on_top=True,
                                    submit_label='Add',
                                    additional_form_class='training-progress',
                                    add_cancel_button=False)

    class Meta:
        model = TrainingProgress
        fields = [
            'trainee',
            'evaluated_by',
            'requirement',
            'state',
            'discarded',
            'event',
            'url',
            'notes',
        ]
        widgets = {
            'state': RadioSelect,
        }

    def clean(self):
        cleaned_data = super().clean()

        trainee = cleaned_data.get('trainee')

        # check if trainee has at least one training task
        training_tasks = trainee.get_training_tasks()

        if not training_tasks:
            raise ValidationError("It's not possible to add training progress "
                                  "to a trainee without any training task.")
示例#4
0
 class Meta:
     model = Award
     fields = '__all__'
     widgets = {
         'person':
         ModelSelect2(url='person-lookup', attrs=SIDEBAR_DAL_WIDTH),
         'event':
         ModelSelect2(url='event-lookup', attrs=SIDEBAR_DAL_WIDTH),
         'awarded_by':
         ModelSelect2(url='admin-lookup', attrs=SIDEBAR_DAL_WIDTH),
     }
示例#5
0
class EventsSelectionForm(forms.Form):
    event_a = forms.ModelChoiceField(label='Event A',
                                     required=True,
                                     queryset=Event.objects.all(),
                                     widget=ModelSelect2(url='event-lookup'))

    event_b = forms.ModelChoiceField(label='Event B',
                                     required=True,
                                     queryset=Event.objects.all(),
                                     widget=ModelSelect2(url='event-lookup'))

    helper = BootstrapHelper(use_get_method=True, add_cancel_button=False)
示例#6
0
class PersonsSelectionForm(forms.Form):
    person_a = forms.ModelChoiceField(label='Person From',
                                      required=True,
                                      queryset=Person.objects.all(),
                                      widget=ModelSelect2(url='person-lookup'))

    person_b = forms.ModelChoiceField(label='Person To',
                                      required=True,
                                      queryset=Person.objects.all(),
                                      widget=ModelSelect2(url='person-lookup'))

    helper = BootstrapHelper(use_get_method=True, add_cancel_button=False)
示例#7
0
class TrainingRequestsSelectionForm(forms.Form):
    trainingrequest_a = forms.ModelChoiceField(
        label='Training request A',
        required=True,
        queryset=TrainingRequest.objects.all(),
        widget=ModelSelect2(url='trainingrequest-lookup'))

    trainingrequest_b = forms.ModelChoiceField(
        label='Training request B',
        required=True,
        queryset=TrainingRequest.objects.all(),
        widget=ModelSelect2(url='trainingrequest-lookup'))

    helper = BootstrapHelper(use_get_method=True, add_cancel_button=False)
示例#8
0
class AdminLookupForm(forms.Form):
    person = forms.ModelChoiceField(label='Administrator',
                                    required=True,
                                    queryset=Person.objects.all(),
                                    widget=ModelSelect2(url='admin-lookup'))

    helper = BootstrapHelper(add_cancel_button=False)
示例#9
0
class EventLookupForm(forms.Form):
    event = forms.ModelChoiceField(label='Event',
                                   required=True,
                                   queryset=Event.objects.all(),
                                   widget=ModelSelect2(url='event-lookup'))

    helper = BootstrapHelper(add_cancel_button=False)
示例#10
0
class TaskForm(WidgetOverrideMixin, forms.ModelForm):

    helper = BootstrapHelper(add_cancel_button=False)

    SEAT_MEMBERSHIP_HELP_TEXT = (
        '{}<br><b>Hint:</b> you can use input format YYYY-MM-DD to display '
        'memberships available on that date.'.format(
            Task._meta.get_field('seat_membership').help_text))
    seat_membership = forms.ModelChoiceField(
        label=Task._meta.get_field('seat_membership').verbose_name,
        help_text=SEAT_MEMBERSHIP_HELP_TEXT,
        required=False,
        queryset=Membership.objects.all(),
        widget=ModelSelect2(
            url='membership-lookup',
            attrs=SIDEBAR_DAL_WIDTH,
        ))

    class Meta:
        model = Task
        fields = '__all__'
        widgets = {
            'person': ModelSelect2(url='person-lookup',
                                   attrs=SIDEBAR_DAL_WIDTH),
            'event': ModelSelect2(url='event-lookup', attrs=SIDEBAR_DAL_WIDTH),
        }
示例#11
0
class TrainingRequestUpdateForm(forms.ModelForm):
    person = forms.ModelChoiceField(label='Matched Trainee',
                                    required=False,
                                    queryset=Person.objects.all(),
                                    widget=ModelSelect2(url='person-lookup'))

    score_auto = forms.IntegerField(
        disabled=True,
        label=TrainingRequest._meta.get_field('score_auto').verbose_name,
        help_text=TrainingRequest._meta.get_field('score_auto').help_text,
    )

    helper = BootstrapHelper(duplicate_buttons_on_top=True,
                             submit_label='Update')

    class Meta:
        model = TrainingRequest
        exclude = ()
        widgets = {
            'occupation': forms.RadioSelect(),
            'domains': forms.CheckboxSelectMultiple(),
            'gender': forms.RadioSelect(),
            'previous_involvement': forms.CheckboxSelectMultiple(),
            'previous_training': forms.RadioSelect(),
            'previous_experience': forms.RadioSelect(),
            'programming_language_usage_frequency': forms.RadioSelect(),
            'teaching_frequency_expectation': forms.RadioSelect(),
            'max_travelling_frequency': forms.RadioSelect(),
            'state': forms.RadioSelect()
        }
示例#12
0
class TaskFilter(AMYFilterSet):
    event = django_filters.ModelChoiceFilter(
        queryset=Event.objects.all(),
        label='Event',
        widget=ModelSelect2(
            url='event-lookup',
            attrs=SIDEBAR_DAL_WIDTH,
        ),
    )

    order_by = django_filters.OrderingFilter(
        fields=(
            ('event__slug', 'event'),
            ('person__family', 'person'),
            ('role', 'role'),
        ),
        field_labels={
            'event__slug': 'Event',
            'person__family': 'Person',
            'role': 'Role',
        }
    )

    class Meta:
        model = Task
        fields = [
            'event',
            # can't filter on person because person's name contains 3 fields:
            # person.personal, person.middle, person.family
            # 'person',
            'role',
        ]
示例#13
0
class BadgeAwardsFilter(AMYFilterSet):
    awarded_after = django_filters.DateFilter(field_name='awarded',
                                              lookup_expr='gte')
    awarded_before = django_filters.DateFilter(field_name='awarded',
                                               lookup_expr='lte')
    event = django_filters.ModelChoiceFilter(
        queryset=Event.objects.all(),
        label='Event',
        widget=ModelSelect2(
            url='event-lookup',
            attrs=SIDEBAR_DAL_WIDTH,
        ),
    )

    order_by = django_filters.OrderingFilter(
        fields=(
            'awarded',
            'person__family',
        ),
        field_labels={
            'awarded': 'Awarded date',
            'person__family': 'Person',
        }
    )

    class Meta:
        model = Award
        fields = (
            'awarded_after', 'awarded_before', 'event',
        )
示例#14
0
class BulkAddTrainingProgressForm(forms.ModelForm):
    event = forms.ModelChoiceField(
        label='Training',
        required=False,
        queryset=Event.objects.filter(tags__name='TTT'),
        widget=ModelSelect2(url='ttt-event-lookup'))

    trainees = forms.ModelMultipleChoiceField(queryset=Person.objects.all())
    # TODO: add trainees lookup?
    # trainees = forms.ModelMultipleChoiceField(
    #     label='Trainees',
    #     required=False,
    #     queryset=Person.objects.all(),
    #     widget=ModelSelect2(url='person-lookup'),
    # )

    helper = BootstrapHelper(additional_form_class='training-progress',
                             submit_label='Add',
                             form_tag=False,
                             add_cancel_button=False)
    helper.layout = Layout(
        # no 'trainees' -- you should take care of generating it manually in
        # the template where this form is used
        'requirement',
        'state',
        'event',
        'url',
        'notes',
    )

    class Meta:
        model = TrainingProgress
        fields = [
            # no 'trainees'
            'requirement',
            'state',
            'event',
            'url',
            'notes',
        ]
        widgets = {
            'state': RadioSelect,
            'notes': TextInput,
        }

    def clean(self):
        cleaned_data = super().clean()

        trainees = cleaned_data.get('trainees')

        # check if all trainees have at least one training task
        for trainee in trainees:
            training_tasks = trainee.get_training_tasks()

            if not training_tasks:
                raise ValidationError("It's not possible to add training "
                                      "progress to a trainee without any "
                                      "training task.")
示例#15
0
class TraineeFilter(AMYFilterSet):
    search = django_filters.CharFilter(
        method=filter_trainees_by_trainee_name_or_email, label='Name or Email')

    all_persons = django_filters.BooleanFilter(
        label='Include all people, not only trainees',
        method=filter_all_persons,
        widget=widgets.CheckboxInput)

    homework = django_filters.BooleanFilter(
        label='Only trainees with unevaluated homework',
        widget=widgets.CheckboxInput,
        method=filter_trainees_by_unevaluated_homework_presence,
    )

    training_request = django_filters.BooleanFilter(
        label='Is training request present?',
        method=filter_trainees_by_training_request_presence,
    )

    is_instructor = django_filters.ChoiceFilter(
        label='Is SWC/DC instructor?',
        method=filter_trainees_by_instructor_status,
        choices=[
            ('', 'Unknown'),
            ('swc-and-dc', 'Both SWC and DC'),
            ('swc-or-dc', 'SWC or DC '),
            ('swc', 'SWC instructor'),
            ('dc', 'DC instructor'),
            ('eligible', 'No, but eligible to be certified'),
            ('no', 'No'),
        ])

    training = django_filters.ModelChoiceFilter(
        queryset=Event.objects.ttt(),
        method=filter_trainees_by_training,
        label='Training',
        widget=ModelSelect2(
            url='ttt-event-lookup',
            attrs=SIDEBAR_DAL_WIDTH,
        ),
    )

    order_by = NamesOrderingFilter(fields=(
        'last_login',
        'email',
    ), )

    class Meta:
        model = Person
        fields = [
            'search',
            'all_persons',
            'homework',
            'is_instructor',
            'training',
        ]
示例#16
0
class PersonForm(forms.ModelForm):
    airport = forms.ModelChoiceField(label='Airport',
                                     required=False,
                                     queryset=Airport.objects.all(),
                                     widget=ModelSelect2(url='airport-lookup'))
    languages = forms.ModelMultipleChoiceField(
        label='Languages',
        required=False,
        queryset=Language.objects.all(),
        widget=ModelSelect2Multiple(url='language-lookup'))

    helper = BootstrapHelper(add_cancel_button=False,
                             duplicate_buttons_on_top=True)

    class Meta:
        model = Person
        # don't display the 'password', 'user_permissions',
        # 'groups' or 'is_superuser' fields
        # + reorder fields
        fields = [
            'username',
            'personal',
            'middle',
            'family',
            'may_contact',
            'publish_profile',
            'lesson_publication_consent',
            'data_privacy_agreement',
            'email',
            'gender',
            'country',
            'airport',
            'affiliation',
            'github',
            'twitter',
            'url',
            'occupation',
            'orcid',
            'user_notes',
            'lessons',
            'domains',
            'languages',
        ]

        widgets = {
            'country': ListSelect2(),
        }
示例#17
0
class MembershipForm(forms.ModelForm):
    helper = BootstrapHelper(add_cancel_button=False)

    organization = forms.ModelChoiceField(
        label='Organization',
        required=True,
        queryset=Organization.objects.all(),
        widget=ModelSelect2(url='organization-lookup')
    )

    class Meta:
        model = Membership
        fields = [
            'organization', 'variant', 'agreement_start', 'agreement_end',
            'contribution_type', 'workshops_without_admin_fee_per_agreement',
            'self_organized_workshops_per_agreement',
            'seats_instructor_training',
            'additional_instructor_training_seats',
        ]
示例#18
0
class MatchTrainingRequestForm(forms.Form):
    """Form used to match a training request to a Person."""
    person = forms.ModelChoiceField(
        label='Trainee Account',
        required=False,
        queryset=Person.objects.all(),
        widget=ModelSelect2(url='person-lookup'),
    )

    helper = BootstrapHelper(add_submit_button=False, add_cancel_button=False)
    helper.layout = Layout(
        'person',
        FormActions(
            Submit('match-selected-person',
                   'Match to selected trainee account'),
            HTML('&nbsp;<strong>OR</strong>&nbsp;&nbsp;'),
            Submit('create-new-person', 'Create new trainee account'),
        ))

    def clean(self):
        super().clean()

        if 'match-selected-person' in self.data:
            self.person_required = True
            self.action = 'match'
        elif 'create-new-person' in self.data:
            self.person_required = False
            self.action = 'create'
        else:
            raise ValidationError('Unknown action.')

        if self.person_required and self.cleaned_data['person'] is None:
            raise ValidationError({'person': 'No person was selected.'})

    class Meta:
        fields = [
            'person',
        ]
示例#19
0
class SWCEventRequestNoCaptchaForm(PrivacyConsentMixin, forms.ModelForm):
    workshop_type = forms.CharField(initial='swc', widget=forms.HiddenInput())
    understand_admin_fee = forms.BooleanField(
        required=True,
        initial=False,
        label='I understand the Software Carpentry Foundation\'s '
        'administration fee.',
        help_text='<a href="http://software-carpentry.org/blog/2015/07/changes'
        '-to-admin-fee.html" target="_blank">Look up administration '
        'fees</a>.',
    )
    language = forms.ModelChoiceField(
        label='Language',
        required=False,
        queryset=Language.objects.all(),
        widget=ModelSelect2(url='language-lookup'))

    helper = BootstrapHelper(wider_labels=True,
                             add_cancel_button=False,
                             duplicate_buttons_on_top=True)

    class Meta:
        model = EventRequest
        exclude = (
            'created_at',
            'last_updated_at',
            'assigned_to',
            'data_types',
            'data_types_other',
            'attendee_data_analysis_level',
            'fee_waiver_request',
        )
        widgets = {
            'event':
            Select2(),
            'approx_attendees':
            forms.RadioSelect(),
            'attendee_domains':
            CheckboxSelectMultipleWithOthers('attendee_domains_other'),
            'attendee_academic_levels':
            forms.CheckboxSelectMultiple(),
            'attendee_computing_levels':
            forms.CheckboxSelectMultiple(),
            'travel_reimbursement':
            RadioSelectWithOther('travel_reimbursement_other'),
            'admin_fee_payment':
            forms.RadioSelect(),
            'country':
            ListSelect2(),
        }

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

        # set up a layout object for the helper
        self.helper.layout = self.helper.build_default_layout(self)

        # set up RadioSelectWithOther widget so that it can display additional
        # field inline
        self['attendee_domains'].field.widget.other_field = \
            self['attendee_domains_other']
        self['travel_reimbursement'].field.widget.other_field = \
            self['travel_reimbursement_other']

        # remove that additional field
        self.helper.layout.fields.remove('attendee_domains_other')
        self.helper.layout.fields.remove('travel_reimbursement_other')
示例#20
0
class EventForm(forms.ModelForm):
    host = forms.ModelChoiceField(
        label='Host',
        required=True,
        help_text=Event._meta.get_field('host').help_text,
        queryset=Organization.objects.all(),
        widget=ModelSelect2(url='organization-lookup'))

    administrator = forms.ModelChoiceField(
        label='Administrator',
        required=False,
        help_text=Event._meta.get_field('administrator').help_text,
        queryset=Organization.objects.all(),
        widget=ModelSelect2(url='organization-lookup'))

    assigned_to = forms.ModelChoiceField(
        label='Assigned to',
        required=False,
        queryset=Person.objects.all(),
        widget=ModelSelect2(url='admin-lookup'))

    language = forms.ModelChoiceField(
        label='Language',
        required=False,
        queryset=Language.objects.all(),
        widget=ModelSelect2(url='language-lookup'))

    country = CountryField().formfield(
        required=False,
        help_text=Event._meta.get_field('country').help_text,
        widget=ListSelect2(),
    )

    comment = MarkdownxFormField(
        label='Comment',
        help_text='Any content in here will be added to comments after this '
        'event is saved.',
        widget=forms.Textarea,
        required=False,
    )

    helper = BootstrapHelper(add_cancel_button=False,
                             duplicate_buttons_on_top=True)

    class Meta:
        model = Event
        fields = [
            'slug',
            'completed',
            'start',
            'end',
            'host',
            'administrator',
            'assigned_to',
            'tags',
            'url',
            'language',
            'reg_key',
            'venue',
            'manual_attendance',
            'contact',
            'country',
            'address',
            'latitude',
            'longitude',
            'open_TTT_applications',
            'curricula',
            'comment',
        ]
        widgets = {
            'manual_attendance':
            TextInput,
            'latitude':
            TextInput,
            'longitude':
            TextInput,
            'invoice_status':
            RadioSelect,
            'tags':
            SelectMultiple(attrs={'size': Tag.ITEMS_VISIBLE_IN_SELECT_WIDGET}),
            'curricula':
            CheckboxSelectMultiple(),
        }

    class Media:
        # thanks to this, {{ form.media }} in the template will generate
        # a <link href=""> (for CSS files) or <script src=""> (for JS files)
        js = (
            'date_yyyymmdd.js',
            'edit_from_url.js',
            'online_country.js',
        )

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

        self.helper.layout = Layout(
            Field('slug', placeholder='YYYY-MM-DD-location'),
            'completed',
            Field('start', placeholder='YYYY-MM-DD'),
            Field('end', placeholder='YYYY-MM-DD'),
            'host',
            'administrator',
            'assigned_to',
            'tags',
            'open_TTT_applications',
            'curricula',
            'url',
            'language',
            'reg_key',
            'manual_attendance',
            'contact',
            Div(Div(HTML('Location details'), css_class='card-header'),
                Div('country',
                    'venue',
                    'address',
                    'latitude',
                    'longitude',
                    css_class='card-body'),
                css_class='card mb-2'),
            'comment',
        )

    def clean_slug(self):
        # Ensure slug is in "YYYY-MM-DD-location" format
        data = self.cleaned_data['slug']
        match = re.match(r'(\d{4}|x{4})-(\d{2}|x{2})-(\d{2}|x{2})-.+', data)
        if not match:
            raise forms.ValidationError('Slug must be in "YYYY-MM-DD-location"'
                                        ' format, where "YYYY", "MM", "DD" can'
                                        ' be unspecified (ie. "xx").')
        return data

    def clean_end(self):
        """Ensure end >= start."""
        start = self.cleaned_data['start']
        end = self.cleaned_data['end']

        if start and end and end < start:
            raise forms.ValidationError('Must not be earlier than start date.')
        return end

    def clean_open_TTT_applications(self):
        """Ensure there's a TTT tag applied to the event, if the
        `open_TTT_applications` is True."""
        open_TTT_applications = self.cleaned_data['open_TTT_applications']
        tags = self.cleaned_data.get('tags', None)
        error_msg = 'You cannot open applications on a non-TTT event.'

        if open_TTT_applications and tags:
            # find TTT tag
            TTT_tag = False
            for tag in tags:
                if tag.name == 'TTT':
                    TTT_tag = True
                    break

            if not TTT_tag:
                raise forms.ValidationError(error_msg)

        elif open_TTT_applications:
            raise forms.ValidationError(error_msg)

        return open_TTT_applications

    def clean_curricula(self):
        """Validate tags when some curricula are selected."""
        curricula = self.cleaned_data['curricula']
        tags = self.cleaned_data['tags']

        try:
            expected_tags = [
                c.slug.split("-")[0].upper() for c in curricula
                if c.active and not c.unknown
            ]
        except (ValueError, TypeError):
            expected_tags = []

        for tag in expected_tags:
            if not tags.filter(name=tag):
                raise forms.ValidationError(
                    "You must add tags corresponding to these curricula.")

        return curricula

    def save(self, *args, **kwargs):
        res = super().save(*args, **kwargs)

        create_comment_signal.send(sender=self.__class__,
                                   content_object=res,
                                   comment=self.cleaned_data['comment'],
                                   timestamp=None)

        return res
示例#21
0
class BulkMatchTrainingRequestForm(forms.Form):
    requests = forms.ModelMultipleChoiceField(
        queryset=TrainingRequest.objects.all())

    event = forms.ModelChoiceField(
        label='Training',
        required=True,
        queryset=Event.objects.filter(tags__name='TTT'),
        widget=ModelSelect2(url='ttt-event-lookup'))

    seat_membership = forms.ModelChoiceField(
        label='Membership seats',
        required=False,
        queryset=Membership.objects.all(),
        help_text='Assigned users will take instructor seats from selected '
        'member site.',
        widget=ModelSelect2(url='membership-lookup'),
    )

    seat_open_training = forms.BooleanField(
        label='Open training seat',
        required=False,
        help_text="Some TTT events allow for open training; check this field "
        "to count this person into open applications.",
    )

    helper = BootstrapHelper(add_submit_button=False,
                             form_tag=False,
                             add_cancel_button=False)
    helper.layout = Layout(
        'event',
        'seat_membership',
        'seat_open_training',
    )
    helper.add_input(
        Submit(
            'match', 'Accept & match selected trainees to chosen training', **{
                'data-toggle':
                'popover',
                'data-html':
                'true',
                'data-trigger':
                'hover',
                'data-content':
                'If you want to <strong>re</strong>match '
                'trainees to other training, first '
                '<strong>unmatch</strong> them!',
            }))

    def clean(self):
        super().clean()

        event = self.cleaned_data['event']
        member_site = self.cleaned_data['seat_membership']
        open_training = self.cleaned_data['seat_open_training']

        if any(r.person is None
               for r in self.cleaned_data.get('requests', [])):
            raise ValidationError('Some of the requests are not matched '
                                  'to a trainee yet. Before matching them to '
                                  'a training, you need to accept them '
                                  'and match with a trainee.')

        if member_site and open_training:
            raise ValidationError(
                "Cannot simultaneously match as open training and use "
                "a Membership instructor training seat.")

        if open_training and not event.open_TTT_applications:
            raise ValidationError({
                'seat_open_training':
                ValidationError(
                    'Selected TTT event does not allow for open training '
                    'seats.'),
            })
示例#22
0
class WorkshopStaffForm(forms.Form):
    '''Represent instructor matching form.'''

    latitude = forms.FloatField(label='Latitude',
                                min_value=-90.0,
                                max_value=90.0,
                                required=False)
    longitude = forms.FloatField(label='Longitude',
                                 min_value=-180.0,
                                 max_value=180.0,
                                 required=False)
    airport = forms.ModelChoiceField(label='Airport',
                                     required=False,
                                     queryset=Airport.objects.all(),
                                     widget=ModelSelect2(
                                         url='airport-lookup',
                                         attrs=SIDEBAR_DAL_WIDTH,
                                     ))
    languages = forms.ModelMultipleChoiceField(label='Languages',
                                               required=False,
                                               queryset=Language.objects.all(),
                                               widget=ModelSelect2Multiple(
                                                   url='language-lookup',
                                                   attrs=SIDEBAR_DAL_WIDTH,
                                               ))

    country = forms.MultipleChoiceField(
        choices=list(Countries()),
        required=False,
        widget=Select2Multiple,
    )

    continent = forms.ChoiceField(
        choices=continent_list,
        required=False,
        widget=Select2,
    )

    lessons = forms.ModelMultipleChoiceField(
        queryset=Lesson.objects.all(),
        widget=SelectMultiple(),
        required=False,
    )

    badges = forms.ModelMultipleChoiceField(
        queryset=Badge.objects.instructor_badges(),
        widget=CheckboxSelectMultiple(),
        required=False,
    )

    is_trainer = forms.BooleanField(required=False, label='Has Trainer badge')

    GENDER_CHOICES = ((None, '---------'), ) + Person.GENDER_CHOICES
    gender = forms.ChoiceField(choices=GENDER_CHOICES, required=False)

    was_helper = forms.BooleanField(required=False,
                                    label='Was helper at least once before')
    was_organizer = forms.BooleanField(
        required=False, label='Was organizer at least once before')
    is_in_progress_trainee = forms.BooleanField(
        required=False, label='Is an in-progress instructor trainee')

    def __init__(self, *args, **kwargs):
        '''Build form layout dynamically.'''
        super().__init__(*args, **kwargs)

        self.helper = FormHelper(self)
        self.helper.form_method = 'get'
        self.helper.layout = Layout(
            Div(
                Div(HTML('<h5 class="card-title">Location</h5>'),
                    'airport',
                    HTML('<hr>'),
                    'country',
                    HTML('<hr>'),
                    'continent',
                    HTML('<hr>'),
                    'latitude',
                    'longitude',
                    css_class='card-body'),
                css_class='card',
            ),
            'badges',
            'is_trainer',
            HTML('<hr>'),
            'was_helper',
            'was_organizer',
            'is_in_progress_trainee',
            'languages',
            'gender',
            'lessons',
            Submit('', 'Submit'),
        )

    def clean(self):
        cleaned_data = super().clean()
        lat = bool(cleaned_data.get('latitude'))
        lng = bool(cleaned_data.get('longitude'))
        airport = bool(cleaned_data.get('airport'))
        country = bool(cleaned_data.get('country'))
        latlng = lat and lng

        # if searching by coordinates, then there must be both lat & lng
        # present
        if lat ^ lng:
            raise forms.ValidationError(
                'Must specify both latitude and longitude if searching by '
                'coordinates')

        # User must search by airport, or country, or coordinates, or none
        # of them. Sum of boolean elements must be equal 0 (if general search)
        # or 1 (if searching by airport OR country OR lat/lng).
        if sum([airport, country, latlng]) not in [0, 1]:
            raise forms.ValidationError(
                'Must specify an airport OR a country, OR use coordinates, OR '
                'none of them.')
        return cleaned_data