We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
List of Superusers
<!-- Do you want to ask a question? Are you looking for support? The system administrator can help you: admin@utn.se -->

### Description

Making it possible to sort after lvel under /admin/users/ so admins shows up and you get a proper overview. 

Or: Make a list of users with superuser access to the website

### Steps to Reproduce

1. [First Step]
2. [Second Step]
3. [and so on...]

<!-- Please select the appropriate "topic category"/blue and "issue type"/yellow label -->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/members/wagtail_hooks.py`
Content:
```
1 from django.forms import CheckboxSelectMultiple
2 from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, \
3     modeladmin_register
4 from wagtail.contrib.modeladmin.views import EditView, CreateView
5 from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList, \
6     FieldPanel
7 
8 from members.models import StudyProgram, Section
9 from django.utils.translation import ugettext_lazy as _
10 
11 
12 class StudyProgramEditHandler:
13     def get_edit_handler_class(self):
14         edit_handler = TabbedInterface([
15             ObjectList([
16                 FieldPanel('name_en'),
17                 FieldPanel('name_sv'),
18                 FieldPanel('degree'),
19             ], heading=_('General'),
20             ),
21             # TODO: http://stackoverflow.com/questions/43188124/
22             # ObjectList([
23             #     FieldPanel('sections', widget=CheckboxSelectMultiple),
24             # ], heading=_('Sections'),
25             # ),
26         ])
27         return edit_handler.bind_to_model(self.model)
28 
29 
30 class StudyProgramEditView(StudyProgramEditHandler, EditView):
31     pass
32 
33 
34 class StudyProgramCreateView(StudyProgramEditHandler, CreateView):
35     pass
36 
37 
38 class StudyProgramAdmin(ModelAdmin):
39     model = StudyProgram
40     menu_label = _('Study Program')
41     menu_icon = 'fa-graduation-cap'
42     menu_order = 510
43     add_to_settings_menu = False
44     create_view_class = StudyProgramCreateView
45     edit_view_class = StudyProgramEditView
46     list_display = ('degree', 'name_en', 'name_sv')
47     search_fields = ('name_en', 'name_sv')
48 
49 
50 class SectionEditHandler:
51     def get_edit_handler_class(self):
52         edit_handler = TabbedInterface([
53             ObjectList([
54                 FieldPanel('name_en'),
55                 FieldPanel('name_sv'),
56                 FieldPanel('abbreviation'),
57             ], heading=_('General'), ),
58             ObjectList([
59                 FieldPanel('studies', widget=CheckboxSelectMultiple),
60             ], heading=_('Studies'), ),
61         ])
62         return edit_handler.bind_to_model(self.model)
63 
64 
65 class SectionEditView(SectionEditHandler, EditView):
66     pass
67 
68 
69 class SectionCreateView(SectionEditHandler, CreateView):
70     pass
71 
72 
73 class SectionAdmin(ModelAdmin):
74     model = Section
75     menu_label = _('Sections')
76     menu_icon = 'fa-eye'
77     menu_order = 520
78     add_to_settings_menu = False
79     create_view_class = SectionCreateView
80     edit_view_class = SectionEditView
81     list_display = ('abbreviation', 'name_en', 'name_sv')
82     search_fields = ('name_en', 'name_sv', 'abbreviation')
83 
84 
85 class EducationAdminGroup(ModelAdminGroup):
86     menu_label = _('Education')
87     menu_icon = 'fa-university'
88     menu_order = 450
89     items = (StudyProgramAdmin, SectionAdmin)
90 
91 
92 modeladmin_register(EducationAdminGroup)
93
```

Path: `src/members/models.py`
Content:
```
1 import requests
2 from django.conf import settings
3 from django.contrib.auth.models import AbstractUser
4 from django.core import validators
5 from django.db import models
6 from django.db.models import ManyToManyField
7 from django.utils import timezone
8 from django.utils.translation import ugettext_lazy as _
9 from requests.auth import HTTPDigestAuth
10 from simple_email_confirmation.models import SimpleEmailConfirmationUserMixin
11 
12 from utils.translation import TranslatedField
13 
14 
15 class StudyProgram(models.Model):
16     """This class describes a university study program"""
17 
18     class Meta:
19         verbose_name = _('study program')
20         verbose_name_plural = _('study programs')
21 
22     DEGREE_CHOICES = (
23         ('bsc', _('Bachelor of Science')),
24         ('msc', _('Master of Science')),
25         ('be', _('Bachelor of Engineering')),
26         ('msceng', _('Master of Science in Engineering')),
27     )
28 
29     name_en = models.CharField(
30         max_length=255,
31         verbose_name=_('English program name'),
32         help_text=_('Enter the name of the study program'),
33         blank=False,
34     )
35 
36     name_sv = models.CharField(
37         max_length=255,
38         verbose_name=_('Swedish program name'),
39         help_text=_('Enter the name of the study program'),
40         blank=False,
41     )
42 
43     name = TranslatedField('name_en', 'name_sv')
44 
45     degree = models.CharField(
46         max_length=20,
47         choices=DEGREE_CHOICES,
48         verbose_name=_('Degree type'),
49         blank=True,
50     )
51 
52     def __str__(self) -> str:
53         if self.degree:
54             return _('%(degree_type)s in %(study_program)s') % {
55                 'degree_type': self.get_degree_display(),
56                 'study_program': self.name,
57             }
58         else:
59             return self.name.__str__()
60 
61 
62 class Section(models.Model):
63     """This class represent a study section"""
64 
65     class Meta:
66         verbose_name = _('section')
67         verbose_name_plural = _('sections')
68 
69     name_en = models.CharField(
70         max_length=255,
71         verbose_name=_('English section name'),
72         help_text=_('Enter the name of the section'),
73         blank=False,
74     )
75 
76     name_sv = models.CharField(
77         max_length=255,
78         verbose_name=_('Swedish section name'),
79         help_text=_('Enter the name of the section'),
80         blank=False,
81     )
82 
83     name = TranslatedField('name_en', 'name_sv')
84 
85     abbreviation = models.CharField(
86         max_length=130,
87         verbose_name=_('Section abbreviation'),
88         help_text=_('Enter the abbreviation for the section'),
89         blank=True,
90     )
91 
92     studies = ManyToManyField(
93         StudyProgram,
94         related_name='sections',
95         blank=True,
96     )
97 
98     def __str__(self) -> str:
99         if self.abbreviation:
100             return '%s - %s' % (self.abbreviation, self.name)
101         else:
102             return self.name.__str__()
103 
104 
105 class Member(SimpleEmailConfirmationUserMixin, AbstractUser):
106     """This class describes a member"""
107 
108     # ---- Personal information ------
109 
110     birthday = models.DateField(
111         verbose_name=_('Birthday'),
112         null=True
113     )
114 
115     person_number_ext = models.CharField(
116         max_length=4,
117         verbose_name=_('Person number extension'),
118         help_text=_('Enter the last four digits of your Swedish person '
119                     'number, given by the Swedish tax authority'),
120         validators=[validators.RegexValidator(
121             regex=r'^\d{4}$',
122             message=_('The person number extension consists of four numbers'),
123         )],
124         unique_for_date="birthday",
125         blank=True,
126     )
127 
128     # ---- Membership information ------
129 
130     MEMBERSHIP_CHOICES = (
131         ('unknown', _('Unknown')),
132         ('nonmember', _('Nonmember')),
133         ('member', _('Member')),
134         ('alumnus', _('Alumnus')),
135     )
136 
137     status = models.CharField(
138         max_length=20,
139         choices=MEMBERSHIP_CHOICES,
140         verbose_name=_('Membership status'),
141         blank=False,
142         default='unknown'
143     )
144     status_changed = models.DateTimeField(
145         default=timezone.now,
146         null=False,
147     )
148 
149     # ---- Contact information ------
150 
151     phone_number = models.CharField(
152         max_length=20,
153         verbose_name=_('Phone number'),
154         help_text=_('Enter a phone number so UTN may reach you'),
155         validators=[validators.RegexValidator(
156             regex=r'^\+?\d+$',
157             message=_('Please enter a valid phone number'),
158         )],
159         blank=True,
160     )
161 
162     # ---- University information ------
163 
164     registration_year = models.CharField(
165         max_length=4,
166         verbose_name=_('Registration year'),
167         help_text=_('Enter the year you started studying at the TakNat '
168                     'faculty'),
169         validators=[validators.RegexValidator(
170             regex=r'^\d{4}$',
171             message=_('Please enter a valid year')
172         )],
173         blank=True,
174     )
175 
176     study = models.ForeignKey(
177         StudyProgram,
178         verbose_name=_('Study program'),
179         on_delete=models.SET_NULL,
180         null=True,
181         blank=True,
182     )
183 
184     section = models.ForeignKey(
185         Section,
186         verbose_name=_('Member of section'),
187         on_delete=models.SET_NULL,
188         null=True,
189         blank=True,
190     )
191 
192     def __str__(self) -> str:
193         if self.first_name and self.last_name:
194             return '%s %s' % (self.first_name, self.last_name)
195         else:
196             return self.username
197 
198     def person_number(self) -> str:
199         if self.birthday is None or self.person_number_ext is None:
200             return ''
201         else:
202             return '%s-%s' % (self.birthday.strftime('%Y%m%d'),
203                               self.person_number_ext)
204 
205     def update_status(self, data=None):
206         if data is None:
207             if self.person_number() == '':
208                 return
209             try:
210                 r = requests.get(
211                     'https://register.utn.se/api.php',
212                     auth=HTTPDigestAuth(settings.MEMBERSHIP_API_USER,
213                                         settings.MEMBERSHIP_API_PASSWORD),
214                     params={
215                         'action': 'check',
216                         'person_number': self.person_number().replace('-', '')
217                     },
218                 )
219                 data = r.json().get('status')
220             except requests.exceptions.ConnectionError:
221                 data = 'unknown'
222             except ValueError:
223                 return
224 
225         if data == 'member':
226             self.status = 'member'
227         elif data == 'nonmember':
228             if self.status in ['unknown', 'nonmember']:
229                 self.status = 'nonmember'
230             else:
231                 self.status = 'alumnus'
232 
233         self.status_changed = timezone.now()
234 
235     def remove_old_email(self):
236         for email in self.get_unconfirmed_emails() or []:
237             self.remove_email(email)
238         for email in self.get_confirmed_emails():
239             if email != self.email:
240                 self.remove_email(email)
241
```

Path: `src/members/signals.py`
Content:
```
1 import datetime
2 from django.conf import settings
3 from django.core.mail import EmailMultiAlternatives
4 from django.db.models.signals import pre_save
5 from django.dispatch import receiver
6 from django.template import loader
7 from django.utils import timezone
8 from simple_email_confirmation import unconfirmed_email_created
9 
10 from members.models import Member
11 
12 
13 @receiver(unconfirmed_email_created, dispatch_uid='send_email_confirmation')
14 def send_confirmation_email(sender, email, user=None, **kwargs):
15     user = user or sender
16     if user is not None:
17         context = {
18             'email': email,
19             'domain': settings.BASE_URL,
20             'site_name': settings.WAGTAIL_SITE_NAME,
21             'token': user.get_confirmation_key(email),
22             'new_user': user.get_confirmed_emails() == []
23         }
24 
25         subject = loader.render_to_string(
26             'members/email_change_subject.txt', context)
27         # Email subject *must not* contain newlines
28         subject = ''.join(subject.splitlines())
29         body = loader.render_to_string('members/email_change_email.html',
30                                        context)
31 
32         email_message = EmailMultiAlternatives(subject, body, None, [email])
33         email_message.send()
34 
35 
36 @receiver(pre_save, sender=Member, dispatch_uid='member_check_membership')
37 def check_membership(sender, instance, **kwargs):
38     if timezone.now() - instance.status_changed > datetime.timedelta(1)\
39             or instance.status == 'unknown':
40         instance.update_status()
41
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/members/models.py b/src/members/models.py
--- a/src/members/models.py
+++ b/src/members/models.py
@@ -191,7 +191,7 @@
 
     def __str__(self) -> str:
         if self.first_name and self.last_name:
-            return '%s %s' % (self.first_name, self.last_name)
+            return self.get_full_name()
         else:
             return self.username
 
diff --git a/src/members/signals.py b/src/members/signals.py
--- a/src/members/signals.py
+++ b/src/members/signals.py
@@ -12,7 +12,7 @@
 
 @receiver(unconfirmed_email_created, dispatch_uid='send_email_confirmation')
 def send_confirmation_email(sender, email, user=None, **kwargs):
-    user = user or sender
+    user = user or sender  # TODO: use user.send_email
     if user is not None:
         context = {
             'email': email,
diff --git a/src/members/wagtail_hooks.py b/src/members/wagtail_hooks.py
--- a/src/members/wagtail_hooks.py
+++ b/src/members/wagtail_hooks.py
@@ -1,12 +1,14 @@
 from django.forms import CheckboxSelectMultiple
+from django.template import loader
+from django.utils.translation import ugettext_lazy as _
 from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, \
     modeladmin_register
 from wagtail.contrib.modeladmin.views import EditView, CreateView
 from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList, \
     FieldPanel
+from wagtail.wagtailcore import hooks
 
-from members.models import StudyProgram, Section
-from django.utils.translation import ugettext_lazy as _
+from members.models import StudyProgram, Section, Member
 
 
 class StudyProgramEditHandler:
@@ -90,3 +92,22 @@
 
 
 modeladmin_register(EducationAdminGroup)
+
+
+class SuperUserPanel(object):
+    order = 1000
+
+    def __init__(self, request):
+        self.request = request
+
+    def render(self):
+        c = {
+            'supers': Member.objects.filter(is_superuser=True),
+            'user': self.request.user
+        }
+        return loader.get_template('members/admin_panel.html').render(c)
+
+
+@hooks.register('construct_homepage_panels')
+def add_super_user_panel(request, panels):
+    return panels.append(SuperUserPanel(request))

{"golden_diff": "diff --git a/src/members/models.py b/src/members/models.py\n--- a/src/members/models.py\n+++ b/src/members/models.py\n@@ -191,7 +191,7 @@\n \n     def __str__(self) -> str:\n         if self.first_name and self.last_name:\n-            return '%s %s' % (self.first_name, self.last_name)\n+            return self.get_full_name()\n         else:\n             return self.username\n \ndiff --git a/src/members/signals.py b/src/members/signals.py\n--- a/src/members/signals.py\n+++ b/src/members/signals.py\n@@ -12,7 +12,7 @@\n \n @receiver(unconfirmed_email_created, dispatch_uid='send_email_confirmation')\n def send_confirmation_email(sender, email, user=None, **kwargs):\n-    user = user or sender\n+    user = user or sender  # TODO: use user.send_email\n     if user is not None:\n         context = {\n             'email': email,\ndiff --git a/src/members/wagtail_hooks.py b/src/members/wagtail_hooks.py\n--- a/src/members/wagtail_hooks.py\n+++ b/src/members/wagtail_hooks.py\n@@ -1,12 +1,14 @@\n from django.forms import CheckboxSelectMultiple\n+from django.template import loader\n+from django.utils.translation import ugettext_lazy as _\n from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, \\\n     modeladmin_register\n from wagtail.contrib.modeladmin.views import EditView, CreateView\n from wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList, \\\n     FieldPanel\n+from wagtail.wagtailcore import hooks\n \n-from members.models import StudyProgram, Section\n-from django.utils.translation import ugettext_lazy as _\n+from members.models import StudyProgram, Section, Member\n \n \n class StudyProgramEditHandler:\n@@ -90,3 +92,22 @@\n \n \n modeladmin_register(EducationAdminGroup)\n+\n+\n+class SuperUserPanel(object):\n+    order = 1000\n+\n+    def __init__(self, request):\n+        self.request = request\n+\n+    def render(self):\n+        c = {\n+            'supers': Member.objects.filter(is_superuser=True),\n+            'user': self.request.user\n+        }\n+        return loader.get_template('members/admin_panel.html').render(c)\n+\n+\n+@hooks.register('construct_homepage_panels')\n+def add_super_user_panel(request, panels):\n+    return panels.append(SuperUserPanel(request))\n", "issue": "List of Superusers\n<!-- Do you want to ask a question? Are you looking for support? The system administrator can help you: admin@utn.se -->\r\n\r\n### Description\r\n\r\nMaking it possible to sort after lvel under /admin/users/ so admins shows up and you get a proper overview. \r\n\r\nOr: Make a list of users with superuser access to the website\r\n\r\n### Steps to Reproduce\r\n\r\n1. [First Step]\r\n2. [Second Step]\r\n3. [and so on...]\r\n\r\n<!-- Please select the appropriate \"topic category\"/blue and \"issue type\"/yellow label -->\r\n\n", "before_files": [{"content": "from django.forms import CheckboxSelectMultiple\nfrom wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, \\\n    modeladmin_register\nfrom wagtail.contrib.modeladmin.views import EditView, CreateView\nfrom wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList, \\\n    FieldPanel\n\nfrom members.models import StudyProgram, Section\nfrom django.utils.translation import ugettext_lazy as _\n\n\nclass StudyProgramEditHandler:\n    def get_edit_handler_class(self):\n        edit_handler = TabbedInterface([\n            ObjectList([\n                FieldPanel('name_en'),\n                FieldPanel('name_sv'),\n                FieldPanel('degree'),\n            ], heading=_('General'),\n            ),\n            # TODO: http://stackoverflow.com/questions/43188124/\n            # ObjectList([\n            #     FieldPanel('sections', widget=CheckboxSelectMultiple),\n            # ], heading=_('Sections'),\n            # ),\n        ])\n        return edit_handler.bind_to_model(self.model)\n\n\nclass StudyProgramEditView(StudyProgramEditHandler, EditView):\n    pass\n\n\nclass StudyProgramCreateView(StudyProgramEditHandler, CreateView):\n    pass\n\n\nclass StudyProgramAdmin(ModelAdmin):\n    model = StudyProgram\n    menu_label = _('Study Program')\n    menu_icon = 'fa-graduation-cap'\n    menu_order = 510\n    add_to_settings_menu = False\n    create_view_class = StudyProgramCreateView\n    edit_view_class = StudyProgramEditView\n    list_display = ('degree', 'name_en', 'name_sv')\n    search_fields = ('name_en', 'name_sv')\n\n\nclass SectionEditHandler:\n    def get_edit_handler_class(self):\n        edit_handler = TabbedInterface([\n            ObjectList([\n                FieldPanel('name_en'),\n                FieldPanel('name_sv'),\n                FieldPanel('abbreviation'),\n            ], heading=_('General'), ),\n            ObjectList([\n                FieldPanel('studies', widget=CheckboxSelectMultiple),\n            ], heading=_('Studies'), ),\n        ])\n        return edit_handler.bind_to_model(self.model)\n\n\nclass SectionEditView(SectionEditHandler, EditView):\n    pass\n\n\nclass SectionCreateView(SectionEditHandler, CreateView):\n    pass\n\n\nclass SectionAdmin(ModelAdmin):\n    model = Section\n    menu_label = _('Sections')\n    menu_icon = 'fa-eye'\n    menu_order = 520\n    add_to_settings_menu = False\n    create_view_class = SectionCreateView\n    edit_view_class = SectionEditView\n    list_display = ('abbreviation', 'name_en', 'name_sv')\n    search_fields = ('name_en', 'name_sv', 'abbreviation')\n\n\nclass EducationAdminGroup(ModelAdminGroup):\n    menu_label = _('Education')\n    menu_icon = 'fa-university'\n    menu_order = 450\n    items = (StudyProgramAdmin, SectionAdmin)\n\n\nmodeladmin_register(EducationAdminGroup)\n", "path": "src/members/wagtail_hooks.py"}, {"content": "import requests\nfrom django.conf import settings\nfrom django.contrib.auth.models import AbstractUser\nfrom django.core import validators\nfrom django.db import models\nfrom django.db.models import ManyToManyField\nfrom django.utils import timezone\nfrom django.utils.translation import ugettext_lazy as _\nfrom requests.auth import HTTPDigestAuth\nfrom simple_email_confirmation.models import SimpleEmailConfirmationUserMixin\n\nfrom utils.translation import TranslatedField\n\n\nclass StudyProgram(models.Model):\n    \"\"\"This class describes a university study program\"\"\"\n\n    class Meta:\n        verbose_name = _('study program')\n        verbose_name_plural = _('study programs')\n\n    DEGREE_CHOICES = (\n        ('bsc', _('Bachelor of Science')),\n        ('msc', _('Master of Science')),\n        ('be', _('Bachelor of Engineering')),\n        ('msceng', _('Master of Science in Engineering')),\n    )\n\n    name_en = models.CharField(\n        max_length=255,\n        verbose_name=_('English program name'),\n        help_text=_('Enter the name of the study program'),\n        blank=False,\n    )\n\n    name_sv = models.CharField(\n        max_length=255,\n        verbose_name=_('Swedish program name'),\n        help_text=_('Enter the name of the study program'),\n        blank=False,\n    )\n\n    name = TranslatedField('name_en', 'name_sv')\n\n    degree = models.CharField(\n        max_length=20,\n        choices=DEGREE_CHOICES,\n        verbose_name=_('Degree type'),\n        blank=True,\n    )\n\n    def __str__(self) -> str:\n        if self.degree:\n            return _('%(degree_type)s in %(study_program)s') % {\n                'degree_type': self.get_degree_display(),\n                'study_program': self.name,\n            }\n        else:\n            return self.name.__str__()\n\n\nclass Section(models.Model):\n    \"\"\"This class represent a study section\"\"\"\n\n    class Meta:\n        verbose_name = _('section')\n        verbose_name_plural = _('sections')\n\n    name_en = models.CharField(\n        max_length=255,\n        verbose_name=_('English section name'),\n        help_text=_('Enter the name of the section'),\n        blank=False,\n    )\n\n    name_sv = models.CharField(\n        max_length=255,\n        verbose_name=_('Swedish section name'),\n        help_text=_('Enter the name of the section'),\n        blank=False,\n    )\n\n    name = TranslatedField('name_en', 'name_sv')\n\n    abbreviation = models.CharField(\n        max_length=130,\n        verbose_name=_('Section abbreviation'),\n        help_text=_('Enter the abbreviation for the section'),\n        blank=True,\n    )\n\n    studies = ManyToManyField(\n        StudyProgram,\n        related_name='sections',\n        blank=True,\n    )\n\n    def __str__(self) -> str:\n        if self.abbreviation:\n            return '%s - %s' % (self.abbreviation, self.name)\n        else:\n            return self.name.__str__()\n\n\nclass Member(SimpleEmailConfirmationUserMixin, AbstractUser):\n    \"\"\"This class describes a member\"\"\"\n\n    # ---- Personal information ------\n\n    birthday = models.DateField(\n        verbose_name=_('Birthday'),\n        null=True\n    )\n\n    person_number_ext = models.CharField(\n        max_length=4,\n        verbose_name=_('Person number extension'),\n        help_text=_('Enter the last four digits of your Swedish person '\n                    'number, given by the Swedish tax authority'),\n        validators=[validators.RegexValidator(\n            regex=r'^\\d{4}$',\n            message=_('The person number extension consists of four numbers'),\n        )],\n        unique_for_date=\"birthday\",\n        blank=True,\n    )\n\n    # ---- Membership information ------\n\n    MEMBERSHIP_CHOICES = (\n        ('unknown', _('Unknown')),\n        ('nonmember', _('Nonmember')),\n        ('member', _('Member')),\n        ('alumnus', _('Alumnus')),\n    )\n\n    status = models.CharField(\n        max_length=20,\n        choices=MEMBERSHIP_CHOICES,\n        verbose_name=_('Membership status'),\n        blank=False,\n        default='unknown'\n    )\n    status_changed = models.DateTimeField(\n        default=timezone.now,\n        null=False,\n    )\n\n    # ---- Contact information ------\n\n    phone_number = models.CharField(\n        max_length=20,\n        verbose_name=_('Phone number'),\n        help_text=_('Enter a phone number so UTN may reach you'),\n        validators=[validators.RegexValidator(\n            regex=r'^\\+?\\d+$',\n            message=_('Please enter a valid phone number'),\n        )],\n        blank=True,\n    )\n\n    # ---- University information ------\n\n    registration_year = models.CharField(\n        max_length=4,\n        verbose_name=_('Registration year'),\n        help_text=_('Enter the year you started studying at the TakNat '\n                    'faculty'),\n        validators=[validators.RegexValidator(\n            regex=r'^\\d{4}$',\n            message=_('Please enter a valid year')\n        )],\n        blank=True,\n    )\n\n    study = models.ForeignKey(\n        StudyProgram,\n        verbose_name=_('Study program'),\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n    )\n\n    section = models.ForeignKey(\n        Section,\n        verbose_name=_('Member of section'),\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n    )\n\n    def __str__(self) -> str:\n        if self.first_name and self.last_name:\n            return '%s %s' % (self.first_name, self.last_name)\n        else:\n            return self.username\n\n    def person_number(self) -> str:\n        if self.birthday is None or self.person_number_ext is None:\n            return ''\n        else:\n            return '%s-%s' % (self.birthday.strftime('%Y%m%d'),\n                              self.person_number_ext)\n\n    def update_status(self, data=None):\n        if data is None:\n            if self.person_number() == '':\n                return\n            try:\n                r = requests.get(\n                    'https://register.utn.se/api.php',\n                    auth=HTTPDigestAuth(settings.MEMBERSHIP_API_USER,\n                                        settings.MEMBERSHIP_API_PASSWORD),\n                    params={\n                        'action': 'check',\n                        'person_number': self.person_number().replace('-', '')\n                    },\n                )\n                data = r.json().get('status')\n            except requests.exceptions.ConnectionError:\n                data = 'unknown'\n            except ValueError:\n                return\n\n        if data == 'member':\n            self.status = 'member'\n        elif data == 'nonmember':\n            if self.status in ['unknown', 'nonmember']:\n                self.status = 'nonmember'\n            else:\n                self.status = 'alumnus'\n\n        self.status_changed = timezone.now()\n\n    def remove_old_email(self):\n        for email in self.get_unconfirmed_emails() or []:\n            self.remove_email(email)\n        for email in self.get_confirmed_emails():\n            if email != self.email:\n                self.remove_email(email)\n", "path": "src/members/models.py"}, {"content": "import datetime\nfrom django.conf import settings\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.db.models.signals import pre_save\nfrom django.dispatch import receiver\nfrom django.template import loader\nfrom django.utils import timezone\nfrom simple_email_confirmation import unconfirmed_email_created\n\nfrom members.models import Member\n\n\n@receiver(unconfirmed_email_created, dispatch_uid='send_email_confirmation')\ndef send_confirmation_email(sender, email, user=None, **kwargs):\n    user = user or sender\n    if user is not None:\n        context = {\n            'email': email,\n            'domain': settings.BASE_URL,\n            'site_name': settings.WAGTAIL_SITE_NAME,\n            'token': user.get_confirmation_key(email),\n            'new_user': user.get_confirmed_emails() == []\n        }\n\n        subject = loader.render_to_string(\n            'members/email_change_subject.txt', context)\n        # Email subject *must not* contain newlines\n        subject = ''.join(subject.splitlines())\n        body = loader.render_to_string('members/email_change_email.html',\n                                       context)\n\n        email_message = EmailMultiAlternatives(subject, body, None, [email])\n        email_message.send()\n\n\n@receiver(pre_save, sender=Member, dispatch_uid='member_check_membership')\ndef check_membership(sender, instance, **kwargs):\n    if timezone.now() - instance.status_changed > datetime.timedelta(1)\\\n            or instance.status == 'unknown':\n        instance.update_status()\n", "path": "src/members/signals.py"}], "after_files": [{"content": "from django.forms import CheckboxSelectMultiple\nfrom django.template import loader\nfrom django.utils.translation import ugettext_lazy as _\nfrom wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, \\\n    modeladmin_register\nfrom wagtail.contrib.modeladmin.views import EditView, CreateView\nfrom wagtail.wagtailadmin.edit_handlers import TabbedInterface, ObjectList, \\\n    FieldPanel\nfrom wagtail.wagtailcore import hooks\n\nfrom members.models import StudyProgram, Section, Member\n\n\nclass StudyProgramEditHandler:\n    def get_edit_handler_class(self):\n        edit_handler = TabbedInterface([\n            ObjectList([\n                FieldPanel('name_en'),\n                FieldPanel('name_sv'),\n                FieldPanel('degree'),\n            ], heading=_('General'),\n            ),\n            # TODO: http://stackoverflow.com/questions/43188124/\n            # ObjectList([\n            #     FieldPanel('sections', widget=CheckboxSelectMultiple),\n            # ], heading=_('Sections'),\n            # ),\n        ])\n        return edit_handler.bind_to_model(self.model)\n\n\nclass StudyProgramEditView(StudyProgramEditHandler, EditView):\n    pass\n\n\nclass StudyProgramCreateView(StudyProgramEditHandler, CreateView):\n    pass\n\n\nclass StudyProgramAdmin(ModelAdmin):\n    model = StudyProgram\n    menu_label = _('Study Program')\n    menu_icon = 'fa-graduation-cap'\n    menu_order = 510\n    add_to_settings_menu = False\n    create_view_class = StudyProgramCreateView\n    edit_view_class = StudyProgramEditView\n    list_display = ('degree', 'name_en', 'name_sv')\n    search_fields = ('name_en', 'name_sv')\n\n\nclass SectionEditHandler:\n    def get_edit_handler_class(self):\n        edit_handler = TabbedInterface([\n            ObjectList([\n                FieldPanel('name_en'),\n                FieldPanel('name_sv'),\n                FieldPanel('abbreviation'),\n            ], heading=_('General'), ),\n            ObjectList([\n                FieldPanel('studies', widget=CheckboxSelectMultiple),\n            ], heading=_('Studies'), ),\n        ])\n        return edit_handler.bind_to_model(self.model)\n\n\nclass SectionEditView(SectionEditHandler, EditView):\n    pass\n\n\nclass SectionCreateView(SectionEditHandler, CreateView):\n    pass\n\n\nclass SectionAdmin(ModelAdmin):\n    model = Section\n    menu_label = _('Sections')\n    menu_icon = 'fa-eye'\n    menu_order = 520\n    add_to_settings_menu = False\n    create_view_class = SectionCreateView\n    edit_view_class = SectionEditView\n    list_display = ('abbreviation', 'name_en', 'name_sv')\n    search_fields = ('name_en', 'name_sv', 'abbreviation')\n\n\nclass EducationAdminGroup(ModelAdminGroup):\n    menu_label = _('Education')\n    menu_icon = 'fa-university'\n    menu_order = 450\n    items = (StudyProgramAdmin, SectionAdmin)\n\n\nmodeladmin_register(EducationAdminGroup)\n\n\nclass SuperUserPanel(object):\n    order = 1000\n\n    def __init__(self, request):\n        self.request = request\n\n    def render(self):\n        c = {\n            'supers': Member.objects.filter(is_superuser=True),\n            'user': self.request.user\n        }\n        return loader.get_template('members/admin_panel.html').render(c)\n\n\n@hooks.register('construct_homepage_panels')\ndef add_super_user_panel(request, panels):\n    return panels.append(SuperUserPanel(request))\n", "path": "src/members/wagtail_hooks.py"}, {"content": "import requests\nfrom django.conf import settings\nfrom django.contrib.auth.models import AbstractUser\nfrom django.core import validators\nfrom django.db import models\nfrom django.db.models import ManyToManyField\nfrom django.utils import timezone\nfrom django.utils.translation import ugettext_lazy as _\nfrom requests.auth import HTTPDigestAuth\nfrom simple_email_confirmation.models import SimpleEmailConfirmationUserMixin\n\nfrom utils.translation import TranslatedField\n\n\nclass StudyProgram(models.Model):\n    \"\"\"This class describes a university study program\"\"\"\n\n    class Meta:\n        verbose_name = _('study program')\n        verbose_name_plural = _('study programs')\n\n    DEGREE_CHOICES = (\n        ('bsc', _('Bachelor of Science')),\n        ('msc', _('Master of Science')),\n        ('be', _('Bachelor of Engineering')),\n        ('msceng', _('Master of Science in Engineering')),\n    )\n\n    name_en = models.CharField(\n        max_length=255,\n        verbose_name=_('English program name'),\n        help_text=_('Enter the name of the study program'),\n        blank=False,\n    )\n\n    name_sv = models.CharField(\n        max_length=255,\n        verbose_name=_('Swedish program name'),\n        help_text=_('Enter the name of the study program'),\n        blank=False,\n    )\n\n    name = TranslatedField('name_en', 'name_sv')\n\n    degree = models.CharField(\n        max_length=20,\n        choices=DEGREE_CHOICES,\n        verbose_name=_('Degree type'),\n        blank=True,\n    )\n\n    def __str__(self) -> str:\n        if self.degree:\n            return _('%(degree_type)s in %(study_program)s') % {\n                'degree_type': self.get_degree_display(),\n                'study_program': self.name,\n            }\n        else:\n            return self.name.__str__()\n\n\nclass Section(models.Model):\n    \"\"\"This class represent a study section\"\"\"\n\n    class Meta:\n        verbose_name = _('section')\n        verbose_name_plural = _('sections')\n\n    name_en = models.CharField(\n        max_length=255,\n        verbose_name=_('English section name'),\n        help_text=_('Enter the name of the section'),\n        blank=False,\n    )\n\n    name_sv = models.CharField(\n        max_length=255,\n        verbose_name=_('Swedish section name'),\n        help_text=_('Enter the name of the section'),\n        blank=False,\n    )\n\n    name = TranslatedField('name_en', 'name_sv')\n\n    abbreviation = models.CharField(\n        max_length=130,\n        verbose_name=_('Section abbreviation'),\n        help_text=_('Enter the abbreviation for the section'),\n        blank=True,\n    )\n\n    studies = ManyToManyField(\n        StudyProgram,\n        related_name='sections',\n        blank=True,\n    )\n\n    def __str__(self) -> str:\n        if self.abbreviation:\n            return '%s - %s' % (self.abbreviation, self.name)\n        else:\n            return self.name.__str__()\n\n\nclass Member(SimpleEmailConfirmationUserMixin, AbstractUser):\n    \"\"\"This class describes a member\"\"\"\n\n    # ---- Personal information ------\n\n    birthday = models.DateField(\n        verbose_name=_('Birthday'),\n        null=True\n    )\n\n    person_number_ext = models.CharField(\n        max_length=4,\n        verbose_name=_('Person number extension'),\n        help_text=_('Enter the last four digits of your Swedish person '\n                    'number, given by the Swedish tax authority'),\n        validators=[validators.RegexValidator(\n            regex=r'^\\d{4}$',\n            message=_('The person number extension consists of four numbers'),\n        )],\n        unique_for_date=\"birthday\",\n        blank=True,\n    )\n\n    # ---- Membership information ------\n\n    MEMBERSHIP_CHOICES = (\n        ('unknown', _('Unknown')),\n        ('nonmember', _('Nonmember')),\n        ('member', _('Member')),\n        ('alumnus', _('Alumnus')),\n    )\n\n    status = models.CharField(\n        max_length=20,\n        choices=MEMBERSHIP_CHOICES,\n        verbose_name=_('Membership status'),\n        blank=False,\n        default='unknown'\n    )\n    status_changed = models.DateTimeField(\n        default=timezone.now,\n        null=False,\n    )\n\n    # ---- Contact information ------\n\n    phone_number = models.CharField(\n        max_length=20,\n        verbose_name=_('Phone number'),\n        help_text=_('Enter a phone number so UTN may reach you'),\n        validators=[validators.RegexValidator(\n            regex=r'^\\+?\\d+$',\n            message=_('Please enter a valid phone number'),\n        )],\n        blank=True,\n    )\n\n    # ---- University information ------\n\n    registration_year = models.CharField(\n        max_length=4,\n        verbose_name=_('Registration year'),\n        help_text=_('Enter the year you started studying at the TakNat '\n                    'faculty'),\n        validators=[validators.RegexValidator(\n            regex=r'^\\d{4}$',\n            message=_('Please enter a valid year')\n        )],\n        blank=True,\n    )\n\n    study = models.ForeignKey(\n        StudyProgram,\n        verbose_name=_('Study program'),\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n    )\n\n    section = models.ForeignKey(\n        Section,\n        verbose_name=_('Member of section'),\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n    )\n\n    def __str__(self) -> str:\n        if self.first_name and self.last_name:\n            return self.get_full_name()\n        else:\n            return self.username\n\n    def person_number(self) -> str:\n        if self.birthday is None or self.person_number_ext is None:\n            return ''\n        else:\n            return '%s-%s' % (self.birthday.strftime('%Y%m%d'),\n                              self.person_number_ext)\n\n    def update_status(self, data=None):\n        if data is None:\n            if self.person_number() == '':\n                return\n            try:\n                r = requests.get(\n                    'https://register.utn.se/api.php',\n                    auth=HTTPDigestAuth(settings.MEMBERSHIP_API_USER,\n                                        settings.MEMBERSHIP_API_PASSWORD),\n                    params={\n                        'action': 'check',\n                        'person_number': self.person_number().replace('-', '')\n                    },\n                )\n                data = r.json().get('status')\n            except requests.exceptions.ConnectionError:\n                data = 'unknown'\n            except ValueError:\n                return\n\n        if data == 'member':\n            self.status = 'member'\n        elif data == 'nonmember':\n            if self.status in ['unknown', 'nonmember']:\n                self.status = 'nonmember'\n            else:\n                self.status = 'alumnus'\n\n        self.status_changed = timezone.now()\n\n    def remove_old_email(self):\n        for email in self.get_unconfirmed_emails() or []:\n            self.remove_email(email)\n        for email in self.get_confirmed_emails():\n            if email != self.email:\n                self.remove_email(email)\n", "path": "src/members/models.py"}, {"content": "import datetime\nfrom django.conf import settings\nfrom django.core.mail import EmailMultiAlternatives\nfrom django.db.models.signals import pre_save\nfrom django.dispatch import receiver\nfrom django.template import loader\nfrom django.utils import timezone\nfrom simple_email_confirmation import unconfirmed_email_created\n\nfrom members.models import Member\n\n\n@receiver(unconfirmed_email_created, dispatch_uid='send_email_confirmation')\ndef send_confirmation_email(sender, email, user=None, **kwargs):\n    user = user or sender  # TODO: use user.send_email\n    if user is not None:\n        context = {\n            'email': email,\n            'domain': settings.BASE_URL,\n            'site_name': settings.WAGTAIL_SITE_NAME,\n            'token': user.get_confirmation_key(email),\n            'new_user': user.get_confirmed_emails() == []\n        }\n\n        subject = loader.render_to_string(\n            'members/email_change_subject.txt', context)\n        # Email subject *must not* contain newlines\n        subject = ''.join(subject.splitlines())\n        body = loader.render_to_string('members/email_change_email.html',\n                                       context)\n\n        email_message = EmailMultiAlternatives(subject, body, None, [email])\n        email_message.send()\n\n\n@receiver(pre_save, sender=Member, dispatch_uid='member_check_membership')\ndef check_membership(sender, instance, **kwargs):\n    if timezone.now() - instance.status_changed > datetime.timedelta(1)\\\n            or instance.status == 'unknown':\n        instance.update_status()\n", "path": "src/members/signals.py"}]}