diff --git a/membership_file/admin.py b/membership_file/admin.py
index 72a40145..610ea117 100644
--- a/membership_file/admin.py
+++ b/membership_file/admin.py
@@ -1,10 +1,12 @@
from datetime import datetime
+from typing import List
from django.contrib import admin, messages
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
-from django.urls import reverse
from django_object_actions import DjangoObjectActions, action as object_action
+from django.urls import reverse, path
+from django.urls.resolvers import URLPattern
from import_export.admin import ExportActionMixin
from import_export.formats.base_formats import CSV, TSV, ODS, XLSX
@@ -58,7 +60,9 @@ class MemberLogReadOnlyInline(DisableModificationsAdminMixin, URLLinkInlineAdmin
@admin.register(Member)
-class MemberWithLog(RequestUserToFormModelAdminMixin, DjangoObjectActions, ExportActionMixin, HideRelatedNameAdmin):
+class MemberWithLog(
+ # RequestUserToFormModelAdminMixin,
+ DjangoObjectActions, ExportActionMixin, HideRelatedNameAdmin):
##############################
# Export functionality
resource_class = MemberResource
@@ -71,9 +75,10 @@ class MemberWithLog(RequestUserToFormModelAdminMixin, DjangoObjectActions, Expor
@object_action(attrs={"class": "addlink"})
def register_new_member(modeladmin, request, queryset):
- view = modeladmin.admin_site.admin_view(RegisterNewMemberAdminView.as_view())
+ view = modeladmin.admin_site.admin_view(RegisterNewMemberAdminView.as_view(model_admin=modeladmin))
return view(request)
+ # Note: get_urls is extended by
changelist_actions = ("register_new_member",)
def get_changelist_actions(self, request):
@@ -227,7 +232,7 @@ def has_delete_permission(self, request, obj=None):
return True
-# Prevents MemberLogField creation, edting, or deletion in the Django Admin Panel
+# Prevents MemberLogField creation, editing, or deletion in the Django Admin Panel
class MemberLogFieldReadOnlyInline(DisableModificationsAdminMixin, admin.TabularInline):
model = MemberLogField
extra = 0
diff --git a/membership_file/forms.py b/membership_file/forms.py
index 8f4d4484..21c0e1e0 100644
--- a/membership_file/forms.py
+++ b/membership_file/forms.py
@@ -5,6 +5,7 @@
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.mail import EmailMultiAlternatives, send_mail
+from django.forms.models import ModelFormMetaclass
from django.template import loader
from django.utils.translation import gettext_lazy as _
@@ -162,31 +163,31 @@ def formfield_for_dbfield(db_field, **kwargs):
# # passed to formfield_for_dbfield override the defaults.
for klass in db_field.__class__.mro():
if klass in FORMFIELD_FOR_DBFIELD_DEFAULTS:
- print(klass)
+ # print(klass)
kwargs = {**copy.deepcopy(FORMFIELD_FOR_DBFIELD_DEFAULTS[klass]), **kwargs}
- print("", kwargs)
+ # print("", kwargs)
return db_field.formfield(**kwargs)
# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)
class Foo:
+ # Add this as a superclass to RegisterMemberForm to use the admin form overrides
class Meta:
formfield_callback = formfield_for_dbfield
-
-class RegisterMemberForm(UpdatingUserFormMixin, Foo, forms.ModelForm):
- """
- Registers a member in the membership file, and optionally sends them an email to link or register a Squire account.
- Also contains some useful presets.
- """
- # Required for ModelAdmin.formfield_overrides functionality
- # See BaseModelAdmin.formfield_for_dbfield for other uses (e.g. foreign key/m2m/radio)
- # This class variable is used by ModelFormMetaclass
- # formfield_callback = "foooo" # partial(self.formfield_for_dbfield, request=request)
-
+class FieldsetModelFormMetaclass(ModelFormMetaclass):
+ def __new__(mcs, name, bases, attrs):
+ new_class = super().__new__(mcs, name, bases, attrs)
+ new_class._meta.fieldsets = None
+ meta_class = getattr(new_class, 'Meta', None)
+ if meta_class is not None:
+ new_class._meta.fieldsets = getattr(meta_class, "fieldsets", None)
+ return new_class
+
+class FieldsetAdminFormMixin(metaclass=FieldsetModelFormMetaclass):
+ """ TODO """
required_css_class = "required"
- # field_order = ()
# ModelAdmin media
@property
@@ -204,6 +205,29 @@ def media(self):
]
return forms.Media(js=['admin/js/%s' % url for url in js]) + super().media
+ def get_fieldsets(self, request, obj=None):
+ """
+ Hook for specifying fieldsets.
+ """
+ print(self._meta.__dict__)
+ if self._meta.fieldsets:
+ return self._meta.fieldsets
+ return [(None, {'fields': self.fields})]
+
+class RegisterMemberForm(UpdatingUserFormMixin, FieldsetAdminFormMixin, forms.ModelForm):
+ """
+ Registers a member in the membership file, and optionally sends them an email to link or register a Squire account.
+ Also contains some useful presets.
+ """
+ # Required for ModelAdmin.formfield_overrides functionality
+ # See BaseModelAdmin.formfield_for_dbfield for other uses (e.g. foreign key/m2m/radio)
+ # This class variable is used by ModelFormMetaclass
+ # formfield_callback = "foooo" # partial(self.formfield_for_dbfield, request=request)
+
+
+ # field_order = ()
+
+
class Meta:
model = Member
fields = (
@@ -226,10 +250,26 @@ class Meta:
"notes",
)
+ fieldsets = [
+ (None, {'fields':
+ [('first_name', 'tussenvoegsel', 'last_name'),
+ 'legal_name', 'date_of_birth',
+ ('educational_institution', 'student_number'),
+ 'tue_card_number',
+ ]}),
+
+ ('Contact Details', {'fields':
+ [('email', "send_registration_email"), 'phone_number',
+ ('street', 'house_number', 'house_number_addition'), ('postal_code', 'city'), 'country']}),
+ ('Notes', {'fields':
+ ['notes']}),
+ ]
+
widgets = {
"educational_institution": OtherRadioSelect(
choices=[
(Member.EDUCATIONAL_INSTITUTION_TUE, "Eindhoven University of Technology"),
+ (Member.EDUCATIONAL_INSTITUTION_TUE + "PhD", "TU/e (PhD)"),
("Fontys Eindhoven", "Fontys Eindhoven"),
("Summa College", "Summa College"),
("", "None (not a student)"),
@@ -252,9 +292,7 @@ class Meta:
)
-
def __init__(self, *args, **kwargs):
- print(args)
super().__init__(*args, **kwargs)
# Make more fields required
@@ -306,13 +344,15 @@ def clean(self) -> Dict[str, Any]:
)
if self.cleaned_data["educational_institution"] and not self.cleaned_data["student_number"]:
- self.add_error(
- "student_number",
- ValidationError(
- "A student number is required when an educational institution is set.",
- code="student_number_required",
- ),
- )
+ # PhD'ers do not have student numbers
+ if "(PhD)" not in self.cleaned_data["educational_institution"]:
+ self.add_error(
+ "student_number",
+ ValidationError(
+ "A student number is required when an educational institution is set.",
+ code="student_number_required",
+ ),
+ )
return res
diff --git a/membership_file/templates/membership_file/register_member.html b/membership_file/templates/membership_file/register_member.html
index b2e66083..2bc1c637 100644
--- a/membership_file/templates/membership_file/register_member.html
+++ b/membership_file/templates/membership_file/register_member.html
@@ -4,10 +4,7 @@
{% block extrahead %}{{ block.super }}
-{{ media }}
-
-{{ form.media }}
-
+{{ adminform.media }}
{% endblock %}
{% block extrastyle %}{{ block.super }}{% endblock %}
diff --git a/membership_file/views.py b/membership_file/views.py
index b0b8d2d4..5dce7b29 100644
--- a/membership_file/views.py
+++ b/membership_file/views.py
@@ -1,6 +1,8 @@
-from typing import Any, Dict
+from functools import partial
+from typing import Any, Dict, Optional, Type
from django.contrib import messages
-from django.contrib.admin import helpers
+from django.contrib.admin import helpers, ModelAdmin
+from django.contrib.admin.utils import flatten_fieldsets
from django.core.exceptions import PermissionDenied
from django.forms.models import BaseModelForm
from django.http import HttpResponse
@@ -84,7 +86,63 @@ class ExtendMembershipSuccessView(MemberMixin, UpdateMemberYearMixin, TemplateVi
template_name = "membership_file/extend_membership_successpage.html"
-class RegisterNewMemberAdminView(CreateView):
+class ModelAdminFormViewMixin:
+ """ TODO """
+ model_admin: ModelAdmin = None
+
+ def __init__(self, *args, model_admin: ModelAdmin=None, **kwargs) -> None:
+ assert model_admin is not None
+ self.model_admin = model_admin
+ super().__init__(*args, **kwargs)
+
+ def get_form(self, form_class: Optional[type[BaseModelForm]]=None) -> BaseModelForm:
+ # This should return a form instance
+ # NB: More defaults can be passed into the **kwargs of ModelAdmin.get_form
+ if form_class is None:
+ form_class = self.get_form_class()
+
+ # Use this form_class's excludes instead of those from the ModelAdmin's form_class
+ exclude = form_class._meta.exclude or ()
+
+ # fields = flatten_fieldsets(self.get_fieldsets(request, obj))
+
+ # print(form_class)
+
+ # This constructs a form class
+ form_class = self.model_admin.get_form(
+ self.request, None, change=False,
+ # Fields are defined in the form
+ fields=None,
+ # Override standard ModelAdmin form and ignore its exclude list
+ form=form_class,
+ exclude=exclude,
+ )
+
+
+ return super().get_form(form_class)
+
+ def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
+ context = super().get_context_data(**kwargs)
+ form: RegisterMemberForm = context.pop("form")
+ adminForm = helpers.AdminForm(form, list(form.get_fieldsets(self.request, self.object)), {}, model_admin=self.model_admin)
+ # FORMFIELD_FOR_DBFIELD_DEFAULTS
+
+ context.update(
+ {
+ "adminform": adminForm,
+ # 'form_url': form_url,
+ "is_nav_sidebar_enabled": True,
+ "opts": Member._meta,
+ "title": "Register new member",
+ # 'content_type_id': get_content_type_for_model(self.model).pk,
+ # 'app_label': app_label,
+ }
+ )
+
+ return context
+
+
+class RegisterNewMemberAdminView(ModelAdminFormViewMixin, CreateView):
"""placeholder"""
form_class = RegisterMemberForm
@@ -111,39 +169,4 @@ def get_success_url(self) -> str:
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
- fields = ["first_name"]
- fields = context["form"].fields
-
- fieldsets = [(None, {"fields": fields})]
-
- fieldsets = [
- (None, {'fields':
- [('first_name', 'tussenvoegsel', 'last_name'),
- 'legal_name', 'date_of_birth',
- ('educational_institution', 'student_number'),
- 'tue_card_number',
- ]}),
-
- ('Contact Details', {'fields':
- ['email', 'phone_number',
- ('street', 'house_number', 'house_number_addition'), ('postal_code', 'city'), 'country']}),
- ('Notes', {'fields':
- ['notes']}),
- ]
- # fieldsets = [(None, {"fields": ["date_of_birth"]})]
-
- adminForm = helpers.AdminForm(context["form"], list(fieldsets), {})
- # FORMFIELD_FOR_DBFIELD_DEFAULTS
-
- context.update(
- {
- "adminform": adminForm,
- # 'form_url': form_url,
- "is_nav_sidebar_enabled": True,
- "opts": Member._meta,
- "title": "Register new member",
- # 'content_type_id': get_content_type_for_model(self.model).pk,
- # 'app_label': app_label,
- }
- )
return context
diff --git a/utils/widgets.py b/utils/widgets.py
index a4e433c7..44afb7ec 100644
--- a/utils/widgets.py
+++ b/utils/widgets.py
@@ -24,6 +24,12 @@ class OtherRadioSelect(RadioSelect):
class Media:
js = ("js/other_option_widget.js",)
+ def __init__(self, attrs=None, choices=None) -> None:
+ if attrs is None or attrs.get("class", None):
+ attrs = attrs or {}
+ attrs["class"] = "radiolist"
+ super().__init__(attrs, choices)
+
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
# If an initial value was provided that does not occur in the list,