We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ValidationError when saving completed membership registration
In GitLab by _thaliatechnicie on Jun 5, 2019, 13:13


### One-sentence description

<!-- What breaks -->

### Current behaviour / Reproducing the bug

Click save while observing a completed registration.

Sentry Issue: [CONCREXIT-4](https://sentry.io/organizations/thalia/issues/1057927495/?referrer=gitlab_integration)

```
ValueError: 'RegistrationForm' has no field named 'email'.
(14 additional frame(s) were not displayed)
...
  File "django/forms/forms.py", line 180, in errors
    self.full_clean()
  File "django/forms/forms.py", line 383, in full_clean
    self._post_clean()
  File "django/forms/models.py", line 405, in _post_clean
    self._update_errors(e)
  File "django/forms/models.py", line 379, in _update_errors
    self.add_error(None, errors)
  File "django/forms/forms.py", line 353, in add_error
    "'%s' has no field named '%s'." % (self.__class__.__name__, field))

ValidationError: {'email': ['A user with that email address already exists. Login using the existing account and renew the membership by visiting the account settings.'], 'student_number': ['A user with that student number already exists. Login using the existing account and renew the membership by visiting the account settings.'], 'username': ['A user with that username already exists.']}
  File "django/forms/models.py", line 403, in _post_clean
    self.instance.full_clean(exclude=exclude, validate_unique=False)
  File "django/db/models/base.py", line 1152, in full_clean
    raise ValidationError(errors)
```

### Expected behaviour

No crash, maybe even no save button available.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `website/registrations/admin.py`
Content:
```
1 """Registers admin interfaces for the registrations module."""
2 from functools import partial
3 
4 from django.contrib import admin, messages
5 from django.contrib.admin.utils import model_ngettext
6 from django.forms import Field
7 from django.utils.translation import gettext_lazy as _
8 
9 from payments.widgets import PaymentWidget
10 from . import services
11 from .forms import RegistrationAdminForm
12 from .models import Entry, Registration, Renewal, Reference
13 
14 
15 class ReferenceInline(admin.StackedInline):
16     model = Reference
17     extra = 0
18 
19 
20 def _show_message(model_admin, request, n, message, error):
21     """Show a message in the Django Admin."""
22     if n == 0:
23         model_admin.message_user(request, error, messages.ERROR)
24     else:
25         model_admin.message_user(
26             request,
27             message % {"count": n, "items": model_ngettext(model_admin.opts, n)},
28             messages.SUCCESS,
29         )
30 
31 
32 @admin.register(Registration)
33 class RegistrationAdmin(admin.ModelAdmin):
34     """Manage the registrations."""
35 
36     list_display = (
37         "name",
38         "email",
39         "status",
40         "membership_type",
41         "contribution",
42         "created_at",
43         "payment",
44         "no_references",
45         "reference_count",
46     )
47     list_filter = (
48         "status",
49         "programme",
50         "membership_type",
51         "no_references",
52         "payment__type",
53         "contribution",
54     )
55     inlines = (ReferenceInline,)
56     search_fields = (
57         "first_name",
58         "last_name",
59         "email",
60         "phone_number",
61         "student_number",
62     )
63     date_hierarchy = "created_at"
64     fieldsets = (
65         (
66             _("Application information"),
67             {
68                 "fields": (
69                     "created_at",
70                     "updated_at",
71                     "username",
72                     "length",
73                     "contribution",
74                     "membership_type",
75                     "status",
76                     "payment",
77                     "remarks",
78                 )
79             },
80         ),
81         (
82             _("Personal information"),
83             {
84                 "fields": (
85                     "first_name",
86                     "last_name",
87                     "birthday",
88                     "optin_birthday",
89                     "email",
90                     "optin_mailinglist",
91                     "phone_number",
92                 )
93             },
94         ),
95         (
96             _("Address"),
97             {
98                 "fields": (
99                     "address_street",
100                     "address_street2",
101                     "address_postal_code",
102                     "address_city",
103                     "address_country",
104                 )
105             },
106         ),
107         (
108             _("Financial"),
109             {"fields": ("direct_debit", "initials", "iban", "bic", "signature",)},
110         ),
111         (
112             _("University information"),
113             {"fields": ("student_number", "programme", "starting_year",)},
114         ),
115     )
116     actions = ["accept_selected", "reject_selected"]
117     form = RegistrationAdminForm
118 
119     def reference_count(self, obj):
120         return obj.reference_set.count()
121 
122     reference_count.short_description = _("references")
123 
124     def get_form(self, request, obj=None, **kwargs):
125         return super().get_form(
126             request,
127             obj,
128             formfield_callback=partial(
129                 self.formfield_for_dbfield, request=request, obj=obj
130             ),
131             **kwargs
132         )
133 
134     def formfield_for_dbfield(self, db_field, request, obj=None, **kwargs):
135         field = super().formfield_for_dbfield(db_field, request, **kwargs)
136         if db_field.name == "payment":
137             return Field(
138                 widget=PaymentWidget(obj=obj), initial=field.initial, required=False
139             )
140         return field
141 
142     def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
143         """Render the change formview.
144 
145         Only allow when the entry has not been processed yet
146         """
147         obj = None
148         can_review = False
149         can_resend = False
150         can_revert = False
151         if object_id is not None and request.user.has_perm(
152             "registrations.review_entries"
153         ):
154             obj = Entry.objects.get(id=object_id)
155             can_review = obj.status == Entry.STATUS_REVIEW
156             can_revert = obj.status in [Entry.STATUS_ACCEPTED, Entry.STATUS_REJECTED]
157             try:
158                 can_resend = obj.registration.status == Entry.STATUS_CONFIRM
159             except Registration.DoesNotExist:
160                 pass
161         return super().changeform_view(
162             request,
163             object_id,
164             form_url,
165             {
166                 "entry": obj,
167                 "can_review": can_review,
168                 "can_resend": can_resend,
169                 "can_revert": can_revert,
170             },
171         )
172 
173     def get_readonly_fields(self, request, obj=None):
174         if obj is None or not (
175             obj.status == Entry.STATUS_REJECTED
176             or obj.status == Entry.STATUS_ACCEPTED
177             or obj.status == Entry.STATUS_COMPLETED
178         ):
179             return ["status", "created_at", "updated_at", "payment", "contribution"]
180         return [
181             field.name
182             for field in self.model._meta.get_fields()
183             if field.name not in ["payment", "no_references"] and field.editable
184         ]
185 
186     @staticmethod
187     def name(obj):
188         return obj.get_full_name()
189 
190     def reject_selected(self, request, queryset):
191         """Reject the selected entries."""
192         if request.user.has_perm("registrations.review_entries"):
193             rows_updated = services.reject_entries(request.user.pk, queryset)
194             _show_message(
195                 self,
196                 request,
197                 rows_updated,
198                 message=_("Successfully rejected %(count)d %(items)s."),
199                 error=_("The selected registration(s) could not be rejected."),
200             )
201 
202     reject_selected.short_description = _("Reject selected registrations")
203     reject_selected.allowed_permissions = ("review",)
204 
205     def accept_selected(self, request, queryset):
206         """Accept the selected entries."""
207         if request.user.has_perm("registrations.review_entries"):
208             rows_updated = services.accept_entries(request.user.pk, queryset)
209             _show_message(
210                 self,
211                 request,
212                 rows_updated,
213                 message=_("Successfully accepted %(count)d %(items)s."),
214                 error=_("The selected registration(s) could not be accepted."),
215             )
216 
217     accept_selected.short_description = _("Accept selected registrations")
218     accept_selected.allowed_permissions = ("review",)
219 
220     def has_review_permission(self, request):
221         """Check if the user has the review permission."""
222         return request.user.has_perm("registrations.review_entries")
223 
224     def save_model(self, request, obj, form, change):
225         if not (
226             obj.status == Entry.STATUS_REJECTED
227             or obj.status == Entry.STATUS_ACCEPTED
228             or obj.status == Entry.STATUS_COMPLETED
229         ):
230             super().save_model(request, obj, form, change)
231 
232 
233 @admin.register(Renewal)
234 class RenewalAdmin(RegistrationAdmin):
235     """Manage the renewals."""
236 
237     list_display = (
238         "name",
239         "email",
240         "status",
241         "membership_type",
242         "contribution",
243         "created_at",
244         "payment",
245         "no_references",
246         "reference_count",
247     )
248     list_filter = (
249         "status",
250         "membership_type",
251         "no_references",
252         "payment__type",
253         "contribution",
254     )
255     search_fields = (
256         "member__first_name",
257         "member__last_name",
258         "member__email",
259         "member__profile__phone_number",
260         "member__profile__student_number",
261     )
262     date_hierarchy = "created_at"
263     fieldsets = (
264         (
265             _("Application information"),
266             {
267                 "fields": (
268                     "created_at",
269                     "updated_at",
270                     "length",
271                     "contribution",
272                     "membership_type",
273                     "status",
274                     "payment",
275                     "remarks",
276                     "member",
277                 )
278             },
279         ),
280     )
281     actions = RegistrationAdmin.actions
282 
283     def get_readonly_fields(self, request, obj=None):
284         """Make all fields read-only and add member if needed."""
285         fields = super().get_readonly_fields(request, obj)
286         if obj is None or not (
287             obj.status == Entry.STATUS_REJECTED
288             or obj.status == Entry.STATUS_ACCEPTED
289             or obj.status == Entry.STATUS_COMPLETED
290         ):
291             fields.remove("contribution")
292         if "member" not in fields and obj is not None:
293             return fields + ["member"]
294         return fields
295 
296     @staticmethod
297     def name(obj):
298         return obj.member.get_full_name()
299 
300     name.short_description = _("name")
301 
302     @staticmethod
303     def email(obj):
304         return obj.member.email
305
```
--- 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/website/registrations/admin.py b/website/registrations/admin.py
--- a/website/registrations/admin.py
+++ b/website/registrations/admin.py
@@ -221,6 +221,14 @@
         """Check if the user has the review permission."""
         return request.user.has_perm("registrations.review_entries")
 
+    def has_change_permission(self, request, obj=None):
+        """Completed registrations are read-only."""
+        return (
+            False
+            if obj and obj.status == Entry.STATUS_COMPLETED
+            else super().has_change_permission(request, obj)
+        )
+
     def save_model(self, request, obj, form, change):
         if not (
             obj.status == Entry.STATUS_REJECTED

{"golden_diff": "diff --git a/website/registrations/admin.py b/website/registrations/admin.py\n--- a/website/registrations/admin.py\n+++ b/website/registrations/admin.py\n@@ -221,6 +221,14 @@\n         \"\"\"Check if the user has the review permission.\"\"\"\n         return request.user.has_perm(\"registrations.review_entries\")\n \n+    def has_change_permission(self, request, obj=None):\n+        \"\"\"Completed registrations are read-only.\"\"\"\n+        return (\n+            False\n+            if obj and obj.status == Entry.STATUS_COMPLETED\n+            else super().has_change_permission(request, obj)\n+        )\n+\n     def save_model(self, request, obj, form, change):\n         if not (\n             obj.status == Entry.STATUS_REJECTED\n", "issue": "ValidationError when saving completed membership registration\nIn GitLab by _thaliatechnicie on Jun 5, 2019, 13:13\n\n\n### One-sentence description\n\n<!-- What breaks -->\n\n### Current behaviour / Reproducing the bug\n\nClick save while observing a completed registration.\n\nSentry Issue: [CONCREXIT-4](https://sentry.io/organizations/thalia/issues/1057927495/?referrer=gitlab_integration)\n\n```\nValueError: 'RegistrationForm' has no field named 'email'.\n(14 additional frame(s) were not displayed)\n...\n  File \"django/forms/forms.py\", line 180, in errors\n    self.full_clean()\n  File \"django/forms/forms.py\", line 383, in full_clean\n    self._post_clean()\n  File \"django/forms/models.py\", line 405, in _post_clean\n    self._update_errors(e)\n  File \"django/forms/models.py\", line 379, in _update_errors\n    self.add_error(None, errors)\n  File \"django/forms/forms.py\", line 353, in add_error\n    \"'%s' has no field named '%s'.\" % (self.__class__.__name__, field))\n\nValidationError: {'email': ['A user with that email address already exists. Login using the existing account and renew the membership by visiting the account settings.'], 'student_number': ['A user with that student number already exists. Login using the existing account and renew the membership by visiting the account settings.'], 'username': ['A user with that username already exists.']}\n  File \"django/forms/models.py\", line 403, in _post_clean\n    self.instance.full_clean(exclude=exclude, validate_unique=False)\n  File \"django/db/models/base.py\", line 1152, in full_clean\n    raise ValidationError(errors)\n```\n\n### Expected behaviour\n\nNo crash, maybe even no save button available.\n\n", "before_files": [{"content": "\"\"\"Registers admin interfaces for the registrations module.\"\"\"\nfrom functools import partial\n\nfrom django.contrib import admin, messages\nfrom django.contrib.admin.utils import model_ngettext\nfrom django.forms import Field\nfrom django.utils.translation import gettext_lazy as _\n\nfrom payments.widgets import PaymentWidget\nfrom . import services\nfrom .forms import RegistrationAdminForm\nfrom .models import Entry, Registration, Renewal, Reference\n\n\nclass ReferenceInline(admin.StackedInline):\n    model = Reference\n    extra = 0\n\n\ndef _show_message(model_admin, request, n, message, error):\n    \"\"\"Show a message in the Django Admin.\"\"\"\n    if n == 0:\n        model_admin.message_user(request, error, messages.ERROR)\n    else:\n        model_admin.message_user(\n            request,\n            message % {\"count\": n, \"items\": model_ngettext(model_admin.opts, n)},\n            messages.SUCCESS,\n        )\n\n\n@admin.register(Registration)\nclass RegistrationAdmin(admin.ModelAdmin):\n    \"\"\"Manage the registrations.\"\"\"\n\n    list_display = (\n        \"name\",\n        \"email\",\n        \"status\",\n        \"membership_type\",\n        \"contribution\",\n        \"created_at\",\n        \"payment\",\n        \"no_references\",\n        \"reference_count\",\n    )\n    list_filter = (\n        \"status\",\n        \"programme\",\n        \"membership_type\",\n        \"no_references\",\n        \"payment__type\",\n        \"contribution\",\n    )\n    inlines = (ReferenceInline,)\n    search_fields = (\n        \"first_name\",\n        \"last_name\",\n        \"email\",\n        \"phone_number\",\n        \"student_number\",\n    )\n    date_hierarchy = \"created_at\"\n    fieldsets = (\n        (\n            _(\"Application information\"),\n            {\n                \"fields\": (\n                    \"created_at\",\n                    \"updated_at\",\n                    \"username\",\n                    \"length\",\n                    \"contribution\",\n                    \"membership_type\",\n                    \"status\",\n                    \"payment\",\n                    \"remarks\",\n                )\n            },\n        ),\n        (\n            _(\"Personal information\"),\n            {\n                \"fields\": (\n                    \"first_name\",\n                    \"last_name\",\n                    \"birthday\",\n                    \"optin_birthday\",\n                    \"email\",\n                    \"optin_mailinglist\",\n                    \"phone_number\",\n                )\n            },\n        ),\n        (\n            _(\"Address\"),\n            {\n                \"fields\": (\n                    \"address_street\",\n                    \"address_street2\",\n                    \"address_postal_code\",\n                    \"address_city\",\n                    \"address_country\",\n                )\n            },\n        ),\n        (\n            _(\"Financial\"),\n            {\"fields\": (\"direct_debit\", \"initials\", \"iban\", \"bic\", \"signature\",)},\n        ),\n        (\n            _(\"University information\"),\n            {\"fields\": (\"student_number\", \"programme\", \"starting_year\",)},\n        ),\n    )\n    actions = [\"accept_selected\", \"reject_selected\"]\n    form = RegistrationAdminForm\n\n    def reference_count(self, obj):\n        return obj.reference_set.count()\n\n    reference_count.short_description = _(\"references\")\n\n    def get_form(self, request, obj=None, **kwargs):\n        return super().get_form(\n            request,\n            obj,\n            formfield_callback=partial(\n                self.formfield_for_dbfield, request=request, obj=obj\n            ),\n            **kwargs\n        )\n\n    def formfield_for_dbfield(self, db_field, request, obj=None, **kwargs):\n        field = super().formfield_for_dbfield(db_field, request, **kwargs)\n        if db_field.name == \"payment\":\n            return Field(\n                widget=PaymentWidget(obj=obj), initial=field.initial, required=False\n            )\n        return field\n\n    def changeform_view(self, request, object_id=None, form_url=\"\", extra_context=None):\n        \"\"\"Render the change formview.\n\n        Only allow when the entry has not been processed yet\n        \"\"\"\n        obj = None\n        can_review = False\n        can_resend = False\n        can_revert = False\n        if object_id is not None and request.user.has_perm(\n            \"registrations.review_entries\"\n        ):\n            obj = Entry.objects.get(id=object_id)\n            can_review = obj.status == Entry.STATUS_REVIEW\n            can_revert = obj.status in [Entry.STATUS_ACCEPTED, Entry.STATUS_REJECTED]\n            try:\n                can_resend = obj.registration.status == Entry.STATUS_CONFIRM\n            except Registration.DoesNotExist:\n                pass\n        return super().changeform_view(\n            request,\n            object_id,\n            form_url,\n            {\n                \"entry\": obj,\n                \"can_review\": can_review,\n                \"can_resend\": can_resend,\n                \"can_revert\": can_revert,\n            },\n        )\n\n    def get_readonly_fields(self, request, obj=None):\n        if obj is None or not (\n            obj.status == Entry.STATUS_REJECTED\n            or obj.status == Entry.STATUS_ACCEPTED\n            or obj.status == Entry.STATUS_COMPLETED\n        ):\n            return [\"status\", \"created_at\", \"updated_at\", \"payment\", \"contribution\"]\n        return [\n            field.name\n            for field in self.model._meta.get_fields()\n            if field.name not in [\"payment\", \"no_references\"] and field.editable\n        ]\n\n    @staticmethod\n    def name(obj):\n        return obj.get_full_name()\n\n    def reject_selected(self, request, queryset):\n        \"\"\"Reject the selected entries.\"\"\"\n        if request.user.has_perm(\"registrations.review_entries\"):\n            rows_updated = services.reject_entries(request.user.pk, queryset)\n            _show_message(\n                self,\n                request,\n                rows_updated,\n                message=_(\"Successfully rejected %(count)d %(items)s.\"),\n                error=_(\"The selected registration(s) could not be rejected.\"),\n            )\n\n    reject_selected.short_description = _(\"Reject selected registrations\")\n    reject_selected.allowed_permissions = (\"review\",)\n\n    def accept_selected(self, request, queryset):\n        \"\"\"Accept the selected entries.\"\"\"\n        if request.user.has_perm(\"registrations.review_entries\"):\n            rows_updated = services.accept_entries(request.user.pk, queryset)\n            _show_message(\n                self,\n                request,\n                rows_updated,\n                message=_(\"Successfully accepted %(count)d %(items)s.\"),\n                error=_(\"The selected registration(s) could not be accepted.\"),\n            )\n\n    accept_selected.short_description = _(\"Accept selected registrations\")\n    accept_selected.allowed_permissions = (\"review\",)\n\n    def has_review_permission(self, request):\n        \"\"\"Check if the user has the review permission.\"\"\"\n        return request.user.has_perm(\"registrations.review_entries\")\n\n    def save_model(self, request, obj, form, change):\n        if not (\n            obj.status == Entry.STATUS_REJECTED\n            or obj.status == Entry.STATUS_ACCEPTED\n            or obj.status == Entry.STATUS_COMPLETED\n        ):\n            super().save_model(request, obj, form, change)\n\n\n@admin.register(Renewal)\nclass RenewalAdmin(RegistrationAdmin):\n    \"\"\"Manage the renewals.\"\"\"\n\n    list_display = (\n        \"name\",\n        \"email\",\n        \"status\",\n        \"membership_type\",\n        \"contribution\",\n        \"created_at\",\n        \"payment\",\n        \"no_references\",\n        \"reference_count\",\n    )\n    list_filter = (\n        \"status\",\n        \"membership_type\",\n        \"no_references\",\n        \"payment__type\",\n        \"contribution\",\n    )\n    search_fields = (\n        \"member__first_name\",\n        \"member__last_name\",\n        \"member__email\",\n        \"member__profile__phone_number\",\n        \"member__profile__student_number\",\n    )\n    date_hierarchy = \"created_at\"\n    fieldsets = (\n        (\n            _(\"Application information\"),\n            {\n                \"fields\": (\n                    \"created_at\",\n                    \"updated_at\",\n                    \"length\",\n                    \"contribution\",\n                    \"membership_type\",\n                    \"status\",\n                    \"payment\",\n                    \"remarks\",\n                    \"member\",\n                )\n            },\n        ),\n    )\n    actions = RegistrationAdmin.actions\n\n    def get_readonly_fields(self, request, obj=None):\n        \"\"\"Make all fields read-only and add member if needed.\"\"\"\n        fields = super().get_readonly_fields(request, obj)\n        if obj is None or not (\n            obj.status == Entry.STATUS_REJECTED\n            or obj.status == Entry.STATUS_ACCEPTED\n            or obj.status == Entry.STATUS_COMPLETED\n        ):\n            fields.remove(\"contribution\")\n        if \"member\" not in fields and obj is not None:\n            return fields + [\"member\"]\n        return fields\n\n    @staticmethod\n    def name(obj):\n        return obj.member.get_full_name()\n\n    name.short_description = _(\"name\")\n\n    @staticmethod\n    def email(obj):\n        return obj.member.email\n", "path": "website/registrations/admin.py"}], "after_files": [{"content": "\"\"\"Registers admin interfaces for the registrations module.\"\"\"\nfrom functools import partial\n\nfrom django.contrib import admin, messages\nfrom django.contrib.admin.utils import model_ngettext\nfrom django.forms import Field\nfrom django.utils.translation import gettext_lazy as _\n\nfrom payments.widgets import PaymentWidget\nfrom . import services\nfrom .forms import RegistrationAdminForm\nfrom .models import Entry, Registration, Renewal, Reference\n\n\nclass ReferenceInline(admin.StackedInline):\n    model = Reference\n    extra = 0\n\n\ndef _show_message(model_admin, request, n, message, error):\n    \"\"\"Show a message in the Django Admin.\"\"\"\n    if n == 0:\n        model_admin.message_user(request, error, messages.ERROR)\n    else:\n        model_admin.message_user(\n            request,\n            message % {\"count\": n, \"items\": model_ngettext(model_admin.opts, n)},\n            messages.SUCCESS,\n        )\n\n\n@admin.register(Registration)\nclass RegistrationAdmin(admin.ModelAdmin):\n    \"\"\"Manage the registrations.\"\"\"\n\n    list_display = (\n        \"name\",\n        \"email\",\n        \"status\",\n        \"membership_type\",\n        \"contribution\",\n        \"created_at\",\n        \"payment\",\n        \"no_references\",\n        \"reference_count\",\n    )\n    list_filter = (\n        \"status\",\n        \"programme\",\n        \"membership_type\",\n        \"no_references\",\n        \"payment__type\",\n        \"contribution\",\n    )\n    inlines = (ReferenceInline,)\n    search_fields = (\n        \"first_name\",\n        \"last_name\",\n        \"email\",\n        \"phone_number\",\n        \"student_number\",\n    )\n    date_hierarchy = \"created_at\"\n    fieldsets = (\n        (\n            _(\"Application information\"),\n            {\n                \"fields\": (\n                    \"created_at\",\n                    \"updated_at\",\n                    \"username\",\n                    \"length\",\n                    \"contribution\",\n                    \"membership_type\",\n                    \"status\",\n                    \"payment\",\n                    \"remarks\",\n                )\n            },\n        ),\n        (\n            _(\"Personal information\"),\n            {\n                \"fields\": (\n                    \"first_name\",\n                    \"last_name\",\n                    \"birthday\",\n                    \"optin_birthday\",\n                    \"email\",\n                    \"optin_mailinglist\",\n                    \"phone_number\",\n                )\n            },\n        ),\n        (\n            _(\"Address\"),\n            {\n                \"fields\": (\n                    \"address_street\",\n                    \"address_street2\",\n                    \"address_postal_code\",\n                    \"address_city\",\n                    \"address_country\",\n                )\n            },\n        ),\n        (\n            _(\"Financial\"),\n            {\"fields\": (\"direct_debit\", \"initials\", \"iban\", \"bic\", \"signature\",)},\n        ),\n        (\n            _(\"University information\"),\n            {\"fields\": (\"student_number\", \"programme\", \"starting_year\",)},\n        ),\n    )\n    actions = [\"accept_selected\", \"reject_selected\"]\n    form = RegistrationAdminForm\n\n    def reference_count(self, obj):\n        return obj.reference_set.count()\n\n    reference_count.short_description = _(\"references\")\n\n    def get_form(self, request, obj=None, **kwargs):\n        return super().get_form(\n            request,\n            obj,\n            formfield_callback=partial(\n                self.formfield_for_dbfield, request=request, obj=obj\n            ),\n            **kwargs\n        )\n\n    def formfield_for_dbfield(self, db_field, request, obj=None, **kwargs):\n        field = super().formfield_for_dbfield(db_field, request, **kwargs)\n        if db_field.name == \"payment\":\n            return Field(\n                widget=PaymentWidget(obj=obj), initial=field.initial, required=False\n            )\n        return field\n\n    def changeform_view(self, request, object_id=None, form_url=\"\", extra_context=None):\n        \"\"\"Render the change formview.\n\n        Only allow when the entry has not been processed yet\n        \"\"\"\n        obj = None\n        can_review = False\n        can_resend = False\n        can_revert = False\n        if object_id is not None and request.user.has_perm(\n            \"registrations.review_entries\"\n        ):\n            obj = Entry.objects.get(id=object_id)\n            can_review = obj.status == Entry.STATUS_REVIEW\n            can_revert = obj.status in [Entry.STATUS_ACCEPTED, Entry.STATUS_REJECTED]\n            try:\n                can_resend = obj.registration.status == Entry.STATUS_CONFIRM\n            except Registration.DoesNotExist:\n                pass\n        return super().changeform_view(\n            request,\n            object_id,\n            form_url,\n            {\n                \"entry\": obj,\n                \"can_review\": can_review,\n                \"can_resend\": can_resend,\n                \"can_revert\": can_revert,\n            },\n        )\n\n    def get_readonly_fields(self, request, obj=None):\n        if obj is None or not (\n            obj.status == Entry.STATUS_REJECTED\n            or obj.status == Entry.STATUS_ACCEPTED\n            or obj.status == Entry.STATUS_COMPLETED\n        ):\n            return [\"status\", \"created_at\", \"updated_at\", \"payment\", \"contribution\"]\n        return [\n            field.name\n            for field in self.model._meta.get_fields()\n            if field.name not in [\"payment\", \"no_references\"] and field.editable\n        ]\n\n    @staticmethod\n    def name(obj):\n        return obj.get_full_name()\n\n    def reject_selected(self, request, queryset):\n        \"\"\"Reject the selected entries.\"\"\"\n        if request.user.has_perm(\"registrations.review_entries\"):\n            rows_updated = services.reject_entries(request.user.pk, queryset)\n            _show_message(\n                self,\n                request,\n                rows_updated,\n                message=_(\"Successfully rejected %(count)d %(items)s.\"),\n                error=_(\"The selected registration(s) could not be rejected.\"),\n            )\n\n    reject_selected.short_description = _(\"Reject selected registrations\")\n    reject_selected.allowed_permissions = (\"review\",)\n\n    def accept_selected(self, request, queryset):\n        \"\"\"Accept the selected entries.\"\"\"\n        if request.user.has_perm(\"registrations.review_entries\"):\n            rows_updated = services.accept_entries(request.user.pk, queryset)\n            _show_message(\n                self,\n                request,\n                rows_updated,\n                message=_(\"Successfully accepted %(count)d %(items)s.\"),\n                error=_(\"The selected registration(s) could not be accepted.\"),\n            )\n\n    accept_selected.short_description = _(\"Accept selected registrations\")\n    accept_selected.allowed_permissions = (\"review\",)\n\n    def has_review_permission(self, request):\n        \"\"\"Check if the user has the review permission.\"\"\"\n        return request.user.has_perm(\"registrations.review_entries\")\n\n    def has_change_permission(self, request, obj=None):\n        \"\"\"Completed registrations are read-only.\"\"\"\n        return (\n            False\n            if obj and obj.status == Entry.STATUS_COMPLETED\n            else super().has_change_permission(request, obj)\n        )\n\n    def save_model(self, request, obj, form, change):\n        if not (\n            obj.status == Entry.STATUS_REJECTED\n            or obj.status == Entry.STATUS_ACCEPTED\n            or obj.status == Entry.STATUS_COMPLETED\n        ):\n            super().save_model(request, obj, form, change)\n\n\n@admin.register(Renewal)\nclass RenewalAdmin(RegistrationAdmin):\n    \"\"\"Manage the renewals.\"\"\"\n\n    list_display = (\n        \"name\",\n        \"email\",\n        \"status\",\n        \"membership_type\",\n        \"contribution\",\n        \"created_at\",\n        \"payment\",\n        \"no_references\",\n        \"reference_count\",\n    )\n    list_filter = (\n        \"status\",\n        \"membership_type\",\n        \"no_references\",\n        \"payment__type\",\n        \"contribution\",\n    )\n    search_fields = (\n        \"member__first_name\",\n        \"member__last_name\",\n        \"member__email\",\n        \"member__profile__phone_number\",\n        \"member__profile__student_number\",\n    )\n    date_hierarchy = \"created_at\"\n    fieldsets = (\n        (\n            _(\"Application information\"),\n            {\n                \"fields\": (\n                    \"created_at\",\n                    \"updated_at\",\n                    \"length\",\n                    \"contribution\",\n                    \"membership_type\",\n                    \"status\",\n                    \"payment\",\n                    \"remarks\",\n                    \"member\",\n                )\n            },\n        ),\n    )\n    actions = RegistrationAdmin.actions\n\n    def get_readonly_fields(self, request, obj=None):\n        \"\"\"Make all fields read-only and add member if needed.\"\"\"\n        fields = super().get_readonly_fields(request, obj)\n        if obj is None or not (\n            obj.status == Entry.STATUS_REJECTED\n            or obj.status == Entry.STATUS_ACCEPTED\n            or obj.status == Entry.STATUS_COMPLETED\n        ):\n            fields.remove(\"contribution\")\n        if \"member\" not in fields and obj is not None:\n            return fields + [\"member\"]\n        return fields\n\n    @staticmethod\n    def name(obj):\n        return obj.member.get_full_name()\n\n    name.short_description = _(\"name\")\n\n    @staticmethod\n    def email(obj):\n        return obj.member.email\n", "path": "website/registrations/admin.py"}]}