from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Q
from django_countries.fields import CountryField

from workshops.models import (
    Language,
    GenderMixin,
    Person,
    TrainingProgress,
    TrainingRequirement,
)

from workshops.forms import BootstrapHelper
# this is used instead of Django Autocomplete Light widgets
# see issue #1330: https://github.com/swcarpentry/amy/issues/1330
from workshops.fields import (
    Select2Widget,
    ModelSelect2MultipleWidget,
    RadioSelectWithOther,
)


class AssignmentForm(forms.Form):
    assigned_to = forms.ModelChoiceField(
        label="Assigned to:",
        required=False,
        queryset=Person.objects.filter(
            Q(is_superuser=True) | Q(groups__name="administrators")
        ),
        widget=Select2Widget(),
    )
    helper = BootstrapHelper(
        add_submit_button=False,
        add_cancel_button=False,
        wider_labels=True,
        use_get_method=True,
        form_id="assignment-form"
    )


class AutoUpdateProfileForm(forms.ModelForm):
    username = forms.CharField(disabled=True, required=False)
    email = forms.CharField(
        disabled=True, required=False,
        label=Person._meta.get_field('email').verbose_name,
        help_text=Person._meta.get_field('email').help_text,
    )
    github = forms.CharField(
        disabled=True, required=False,
        help_text='If you want to change your github username, please email '
                  'us at <a href="mailto:team@carpentries.org">'
                  'team@carpentries.org</a>.')

    country = CountryField().formfield(
        required=False,
        help_text='Your country of residence.',
        widget=Select2Widget,
    )

    languages = forms.ModelMultipleChoiceField(
        label='Languages',
        required=False,
        queryset=Language.objects.all(),
        widget=ModelSelect2MultipleWidget(data_view='language-lookup')
    )

    helper = BootstrapHelper(add_cancel_button=False)

    class Meta:
        model = Person
        fields = [
            'personal',
            'middle',
            'family',
            'email',
            'secondary_email',
            'gender',
            'gender_other',
            'may_contact',
            'publish_profile',
            'lesson_publication_consent',
            'country',
            'airport',
            'github',
            'twitter',
            'url',
            'username',
            'affiliation',
            'domains',
            'lessons',
            'languages',
            'occupation',
            'orcid',
        ]
        readonly_fields = (
            'username',
            'github',
        )
        widgets = {
            'gender': RadioSelectWithOther('gender_other'),
            'domains': forms.CheckboxSelectMultiple(),
            'lessons': forms.CheckboxSelectMultiple(),
            'airport': Select2Widget,
        }

    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 `*WithOther` widgets so that they can display additional
        # fields inline
        self['gender'].field.widget.other_field = self['gender_other']

        # remove additional fields
        self.helper.layout.fields.remove('gender_other')

    def clean(self):
        super().clean()
        errors = dict()

        # 1: require "other gender" field if "other" was selected in
        # "gender" field
        gender = self.cleaned_data.get('gender', '')
        gender_other = self.cleaned_data.get('gender_other', '')
        if gender == GenderMixin.OTHER and not gender_other:
            errors['gender'] = ValidationError("This field is required.")
        elif gender != GenderMixin.OTHER and gender_other:
            errors['gender'] = ValidationError(
                'If you entered data in "Other" field, please select that '
                "option.")

        # raise errors if any present
        if errors:
            raise ValidationError(errors)


class SendHomeworkForm(forms.ModelForm):
    url = forms.URLField(label='URL')
    requirement = forms.ModelChoiceField(
        queryset=TrainingRequirement.objects.filter(name__endswith="Homework"),
        label="Type", required=True,
    )

    helper = BootstrapHelper(add_cancel_button=False)

    class Meta:
        model = TrainingProgress
        fields = [
            'requirement',
            'url',
        ]


class SearchForm(forms.Form):
    """Represent general searching form."""

    term = forms.CharField(label="Term", max_length=100)
    no_redirect = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
    helper = BootstrapHelper(add_cancel_button=False, use_get_method=True)

amy/dashboard/forms.py
from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Q
from django_countries.fields import CountryField

from workshops.models import (
    Language,
    GenderMixin,
    Person,
    TrainingProgress,
    TrainingRequirement,
)

from workshops.forms import BootstrapHelper
# this is used instead of Django Autocomplete Light widgets
# see issue #1330: https://github.com/swcarpentry/amy/issues/1330
from workshops.fields import (
    Select2Widget,
    ModelSelect2MultipleWidget,
    RadioSelectWithOther,
)


class AssignmentForm(forms.Form):
    assigned_to = forms.ModelChoiceField(
        label="Assigned to:",
        required=False,
        queryset=Person.objects.filter(
            Q(is_superuser=True) | Q(groups__name="administrators")
        ).distinct(),
        widget=Select2Widget(),
    )
    helper = BootstrapHelper(
        add_submit_button=False,
        add_cancel_button=False,
        wider_labels=True,
        use_get_method=True,
        form_id="assignment-form"
    )


class AutoUpdateProfileForm(forms.ModelForm):
    username = forms.CharField(disabled=True, required=False)
    email = forms.CharField(
        disabled=True, required=False,
        label=Person._meta.get_field('email').verbose_name,
        help_text=Person._meta.get_field('email').help_text,
    )
    github = forms.CharField(
        disabled=True, required=False,
        help_text='If you want to change your github username, please email '
                  'us at <a href="mailto:team@carpentries.org">'
                  'team@carpentries.org</a>.')

    country = CountryField().formfield(
        required=False,
        help_text='Your country of residence.',
        widget=Select2Widget,
    )

    languages = forms.ModelMultipleChoiceField(
        label='Languages',
        required=False,
        queryset=Language.objects.all(),
        widget=ModelSelect2MultipleWidget(data_view='language-lookup')
    )

    helper = BootstrapHelper(add_cancel_button=False)

    class Meta:
        model = Person
        fields = [
            'personal',
            'middle',
            'family',
            'email',
            'secondary_email',
            'gender',
            'gender_other',
            'may_contact',
            'publish_profile',
            'lesson_publication_consent',
            'country',
            'airport',
            'github',
            'twitter',
            'url',
            'username',
            'affiliation',
            'domains',
            'lessons',
            'languages',
            'occupation',
            'orcid',
        ]
        readonly_fields = (
            'username',
            'github',
        )
        widgets = {
            'gender': RadioSelectWithOther('gender_other'),
            'domains': forms.CheckboxSelectMultiple(),
            'lessons': forms.CheckboxSelectMultiple(),
            'airport': Select2Widget,
        }

    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 `*WithOther` widgets so that they can display additional
        # fields inline
        self['gender'].field.widget.other_field = self['gender_other']

        # remove additional fields
        self.helper.layout.fields.remove('gender_other')

    def clean(self):
        super().clean()
        errors = dict()

        # 1: require "other gender" field if "other" was selected in
        # "gender" field
        gender = self.cleaned_data.get('gender', '')
        gender_other = self.cleaned_data.get('gender_other', '')
        if gender == GenderMixin.OTHER and not gender_other:
            errors['gender'] = ValidationError("This field is required.")
        elif gender != GenderMixin.OTHER and gender_other:
            errors['gender'] = ValidationError(
                'If you entered data in "Other" field, please select that '
                "option.")

        # raise errors if any present
        if errors:
            raise ValidationError(errors)


class SendHomeworkForm(forms.ModelForm):
    url = forms.URLField(label='URL')
    requirement = forms.ModelChoiceField(
        queryset=TrainingRequirement.objects.filter(name__endswith="Homework"),
        label="Type", required=True,
    )

    helper = BootstrapHelper(add_cancel_button=False)

    class Meta:
        model = TrainingProgress
        fields = [
            'requirement',
            'url',
        ]


class SearchForm(forms.Form):
    """Represent general searching form."""

    term = forms.CharField(label="Term", max_length=100)
    no_redirect = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
    helper = BootstrapHelper(add_cancel_button=False, use_get_method=True)

amy/dashboard/forms.py
diff --git a/amy/dashboard/forms.py b/amy/dashboard/forms.py
index e04d5d474..d3aba18e9 100644
--- a/amy/dashboard/forms.py
+++ b/amy/dashboard/forms.py
@@ -27,7 +27,7 @@ class AssignmentForm(forms.Form):
         required=False,
         queryset=Person.objects.filter(
             Q(is_superuser=True) | Q(groups__name="administrators")
-        ),
+        ).distinct(),
         widget=Select2Widget(),
     )
     helper = BootstrapHelper(
diff --git a/amy/dashboard/tests/test_assignment_form.py b/amy/dashboard/tests/test_assignment_form.py
new file mode 100644
index 000000000..3148ac654
--- /dev/null
+++ b/amy/dashboard/tests/test_assignment_form.py
@@ -0,0 +1,47 @@
+from django.contrib.auth.models import Group
+from django.test import TestCase
+
+from dashboard.forms import AssignmentForm
+from workshops.models import Person
+
+
+class TestAssignmentForm(TestCase):
+    def setUp(self):
+        self.superuser = Person.objects.create(
+            personal="Harry",
+            family="Potter",
+            email="hp@magic.uk",
+            username="potter_harry",
+            is_superuser=True,
+        )
+        self.administrator = Person.objects.create(
+            personal="Ron",
+            family="Weasley",
+            email="rw@magic.uk",
+            username="weasley_ron",
+        )
+        self.both = Person.objects.create(
+            personal="Hermione",
+            family="Granger",
+            email="hg@magic.uk",
+            username="granger_hermione",
+            is_superuser=True,
+        )
+
+    def test_assigned_to_queryset(self):
+        """Ensure duplicate results don't appear in the queryset.
+
+        This is a regression test for https://github.com/carpentries/amy/issues/1792.
+        """
+        admins = Group.objects.get(name='administrators')
+        committee = Group.objects.get(name='steering committee')
+        self.administrator.groups.add(admins)
+        # user has to be in 2 groups to yield duplicate
+        # results in the queryset
+        self.both.groups.add(committee)
+        self.both.groups.add(admins)
+
+        field = AssignmentForm().fields["assigned_to"]
+        self.assertEqual(list(field.queryset), [
+            self.both, self.superuser, self.administrator
+        ])

Bug: assignment form queryset may return duplicate results
Introduced in v2.16, AssignmentForm contains a queryset that may yield duplicate results due to the filtering used.
