Enable member editing functionality: implement MemberEditView, update routes, modify templates for dynamic filtering and superuser badges, and standardize contact info handling.

This commit is contained in:
2026-04-12 11:06:28 +02:00
parent 3d94b9b2d8
commit 7aa4a4816c
4 changed files with 73 additions and 61 deletions

View File

@@ -6,7 +6,7 @@ app_name = "members"
urlpatterns = [ urlpatterns = [
path("", MemberListView.as_view(), name="list"), path("", MemberListView.as_view(), name="list"),
path("add/", MemberAddView.as_view(), name="add"), path("add/", MemberAddView.as_view(), name="add"),
# path("<int:pk>/edit/", MemberEditView.as_view(), name="edit"), path("<int:pk>/edit/", MemberEditView.as_view(), name="edit"),
path("<int:pk>/delete/", MemberDeleteView.as_view(), name="delete"), path("<int:pk>/delete/", MemberDeleteView.as_view(), name="delete"),
# path("load/", MemberLoadView.as_view(), name="load"), # path("load/", MemberLoadView.as_view(), name="load"),
] ]

View File

@@ -5,15 +5,17 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DeleteView, UpdateView, CreateView from django.views.generic import CreateView, DeleteView, UpdateView
from django_filters.views import FilterView from django_filters.views import FilterView
from rules.contrib.views import PermissionRequiredMixin from rules.contrib.views import PermissionRequiredMixin
from members.filters import MemberFilter from members.filters import MemberFilter
from members.models import Member
from members.forms import MemberForm from members.forms import MemberForm
from members.models import Member
from ..mixins import HTMXViewMixin from ..mixins import HTMXViewMixin
class MemberListView(HTMXViewMixin, PermissionRequiredMixin, FilterView): class MemberListView(HTMXViewMixin, PermissionRequiredMixin, FilterView):
filterset_class = MemberFilter filterset_class = MemberFilter
paginate_by = 50 paginate_by = 50
@@ -73,6 +75,14 @@ class MemberEditView(HTMXViewMixin, PermissionRequiredMixin, SuccessMessageMixin
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):
return self.success_message % dict(cleaned_data, name=self.object.user.get_full_name()) return self.success_message % dict(cleaned_data, name=self.object.user.get_full_name())
def get_initial(self):
initial = super().get_initial()
user = self.get_object().user
initial.update({"first_name": user.first_name, "last_name": user.last_name, "email": user.email, "admin": user.is_superuser})
return initial
class MemberDeleteView(HTMXViewMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView): class MemberDeleteView(HTMXViewMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView):
model = Member model = Member

View File

@@ -14,7 +14,7 @@
<h1 class="page-title">{% translate "Members" %}</h1> <h1 class="page-title">{% translate "Members" %}</h1>
<div class="lg:hidden collapse collapse-plus bg-base-100 border-neutral border"> <div class="lg:hidden collapse collapse-plus bg-base-100 border-neutral border">
<input type="checkbox" /> <input type="checkbox"/>
<div class="collapse-title text-sm font-semibold"><i class="fa-solid fa-filter mr-2"></i>{% translate "Filter" %}{% if filter.is_bound %}<span class="ml-2 badge badge-sm badge-neutral">active</span>{% endif %}</div> <div class="collapse-title text-sm font-semibold"><i class="fa-solid fa-filter mr-2"></i>{% translate "Filter" %}{% if filter.is_bound %}<span class="ml-2 badge badge-sm badge-neutral">active</span>{% endif %}</div>
<div class="collapse-content"> <div class="collapse-content">
<form class="flex flex-col gap-2" hx-get="{% url "backend:members:list" %}" hx-target="#content"> <form class="flex flex-col gap-2" hx-get="{% url "backend:members:list" %}" hx-target="#content">
@@ -89,7 +89,7 @@
{% for member in object_list %} {% for member in object_list %}
<tr class="hover:bg-base-300"> <tr class="hover:bg-base-300">
<td> <td>
<a href=""> <a href="{% url "backend:members:edit" member.pk %}" hx-get="{% url "backend:members:edit" member.pk %}" hx-target="#content">
<div class="flex flex-row items-center gap-3"> <div class="flex flex-row items-center gap-3">
<div> <div>
{% avatar first_name=member.user.first_name last_name=member.user.last_name %} {% avatar first_name=member.user.first_name last_name=member.user.last_name %}
@@ -105,7 +105,7 @@
{% endif %} {% endif %}
{% if not member.user.is_active %} {% if not member.user.is_active %}
<div class="badge badge-neutral badge-sm">{% translate "Inactive"%}</div> <div class="badge badge-neutral badge-sm">{% translate "Inactive" %}</div>
{% endif %} {% endif %}
</div> </div>
</a> </a>
@@ -129,7 +129,7 @@
</td> </td>
<td> <td>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<a class="btn btn-outline btn-sm" href=""> <a class="btn btn-outline btn-sm" href="{% url "backend:members:edit" member.pk %}" hx-get="{% url "backend:members:edit" member.pk %}" hx-target="#content">
<i class="fa-solid fa-eye"></i>{% translate "Details" %} <i class="fa-solid fa-eye"></i>{% translate "Details" %}
</a> </a>
@@ -151,13 +151,23 @@
{% else %} {% else %}
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
{% for member in object_list %} {% for member in object_list %}
<a class="border border-base-300 rounded-lg p-2 flex flex-row gap-2 items-center" href=""> <a class="border border-base-300 rounded-lg p-2 flex flex-row gap-2 items-center" href="{% url "backend:members:edit" member.pk %}" hx-get="{% url "backend:members:edit" member.pk %}" hx-target="#content">
<div> <div>
{% avatar first_name=member.user.first_name last_name=member.user.last_name width="sm" %} {% avatar first_name=member.user.first_name last_name=member.user.last_name width="sm" %}
</div> </div>
<div class="grow"> <div class="grow">
<div class="font-semibold text-sm">{{ member.user.get_full_name }} {% if member.license %}#{{ member.license }}{% endif %}</div> <div class="font-semibold text-sm">
{{ member.user.get_full_name }}
{% if member.license %}
#{{ member.license }}
{% endif %}
{% if member.user.is_superuser %}
<div class="badge badge-xs badge-accent">
<i class="fa-solid fa-user-shield"></i>
</div>
{% endif %}
</div>
<div class="opacity-50 text-xs">{{ member.birthday|date:"d M Y"|default:"" }}</div> <div class="opacity-50 text-xs">{{ member.birthday|date:"d M Y"|default:"" }}</div>
</div> </div>

View File

@@ -18,14 +18,6 @@
<div class="flex flex-row gap-2 grow justify-center items-center"> <div class="flex flex-row gap-2 grow justify-center items-center">
{% if member %} {% if member %}
<div class="font-bold text-xl">{{ member.user.get_full_name }}</div> <div class="font-bold text-xl">{{ member.user.get_full_name }}</div>
{% if member.user.is_superuser %}
<div class="tooltip" data-tip="{% translate "This user is a site admin" %}">
<div class="badge badge-sm badge-accent">
<i class="fa-solid fa-user-shield"></i>
</div>
</div>
{% endif %}
{% else %} {% else %}
<div class="font-bold text-xl">{% translate "Create new member" %}</div> <div class="font-bold text-xl">{% translate "Create new member" %}</div>
{% endif %} {% endif %}
@@ -44,29 +36,28 @@
{% if member %} {% if member %}
<div class="mt-4 lg:hidden flex flex-row gap-2"> <div class="mt-4 lg:hidden flex flex-row gap-2">
<div class="mt-4 lg:hidden flex flex-row gap-2"> {% if member.phone_number %}
{% if member.phone_number %} <a href="{{ member.phone_number.as_rfc3966 }}" class="btn btn-info btn-outline btn-sm grow">
<a href="{{ member.phone_number.as_rfc3966 }}" class="btn btn-info btn-outline btn-sm grow"> <i class="fa-solid fa-phone"></i>
<i class="fa-solid fa-phone"></i> {{ member.phone_number }}
{{ member.phone }}
</a>
{% endif %}
{% if member.emergency_phone_number %}
<a href="{{ member.emergency_phone_number.as_rfc3966 }}" class="btn btn-error btn-outline btn-sm grow">
<i class="fa-solid fa-file-medical"></i>
{{ member.emergency_phone_number }}
</a>
{% endif %}
</div>
{% if config.TF_ENABLE_TEAMS %}
<a href="?member__user__first_name={{ member.user.first_name }}&member__user__last_name={{ member.user.last_name }}" class="btn btn-sm w-full mt-2 btn-outline btn-neutral lg:hidden">
<i class="fa-solid fa-ticket"></i>{% translate "View team memberships" %}
</a> </a>
{% endif %} {% endif %}
<div class="flex flex-row items-center mt-8 gap-x-3 hidden lg:flex"> {% if member.emergency_phone_number %}
<a href="{{ member.emergency_phone_number.as_rfc3966 }}" class="btn btn-error btn-outline btn-sm grow">
<i class="fa-solid fa-file-medical"></i>
{{ member.emergency_phone_number }}
</a>
{% endif %}
</div>
{% if config.TF_ENABLE_TEAMS %}
<a href="?member__user__first_name={{ member.user.first_name }}&member__user__last_name={{ member.user.last_name }}" class="btn btn-sm w-full mt-2 btn-outline btn-neutral lg:hidden">
<i class="fa-solid fa-ticket"></i>{% translate "View team memberships" %}
</a>
{% endif %}
<div class="hidden flex-row items-center mt-8 gap-x-3 hidden lg:flex">
{% avatar first_name=member.user.first_name last_name=member.user.last_name %} {% avatar first_name=member.user.first_name last_name=member.user.last_name %}
<h2 class="page-subtitle border-b-0! mt-0!">{{ member.user.get_full_name }}</h2> <h2 class="page-subtitle border-b-0! mt-0!">{{ member.user.get_full_name }}</h2>
@@ -76,12 +67,12 @@
{% endif %} {% endif %}
<div class="justify-end hidden gap-2 lg:flex lg:flex-row grow"> <div class="justify-end hidden gap-2 lg:flex lg:flex-row grow">
{% if member.phone %} {% if member.phone_number %}
<a href="{{ member.phone.as_rfc3966 }}" class="btn btn-outline btn-info"><i class="fa-solid fa-phone"></i>{{ member.phone }}</a> <a href="{{ member.phone_number.as_rfc3966 }}" class="btn btn-outline btn-info"><i class="fa-solid fa-phone"></i>{{ member.phone_number }}</a>
{% endif %} {% endif %}
{% if member.emergency_phone %} {% if member.emergency_phone_number %}
<a href="{{ member.emergency_phone.as_rfc3966 }}" class="btn btn-outline btn-error"><i class="fa-solid fa-file-medical"></i>{{ member.emergency_phone }}</a> <a href="{{ member.emergency_phone_number.as_rfc3966 }}" class="btn btn-outline btn-error"><i class="fa-solid fa-file-medical"></i>{{ member.emergency_phone_number }}</a>
{% endif %} {% endif %}
{% if config.TF_ENABLE_TEAMS %} {% if config.TF_ENABLE_TEAMS %}
@@ -90,7 +81,7 @@
</a> </a>
{% endif %} {% endif %}
<a href="{% url "backend:members:members_delete" member.id %}" class="btn btn-error btn-outline"> <a href="{% url "backend:members:delete" member.pk %}" class="btn btn-error btn-outline">
<i class="fa-solid fa-trash"></i>{% translate "Delete" %} <i class="fa-solid fa-trash"></i>{% translate "Delete" %}
</a> </a>
</div> </div>
@@ -114,33 +105,34 @@
{% csrf_token %} {% csrf_token %}
<h2 class="page-subtitle">{% translate "Personal information" %}</h2> <h2 class="page-subtitle">{% translate "Personal information" %}</h2>
<div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2"> <div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2 xl:grid-cols-3">
{% form_field form.first_name %} {% form_field form.first_name %}
{% form_field form.last_name %} {% form_field form.last_name %}
{% form_field form.birthday %} {% form_field form.birthday %}
</div> </div>
<h2 class="page-subtitle">{% translate "Contact information" %}</h2> <h2 class="page-subtitle">{% translate "Contact information" %}</h2>
<div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2"> <div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2 xl:grid-cols-3">
{% form_field form.email %} {% form_field form.email %}
{% form_field form.phone_number %} {% form_field form.phone_number %}
{% form_field form.emergency_phone_number %} {% form_field form.emergency_phone_number %}
</div> </div>
<h2 class="page-subtitle">{% translate "Family information" %}</h2> <h2 class="page-subtitle">{% translate "Family information" %}</h2>
<div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2"> <div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2 xl:grid-cols-3">
{% form_field form.family_members %} {% form_field form.family_members %}
</div> </div>
<h2 class="page-subtitle">{% translate "Club information" %}</h2> <h2 class="page-subtitle">{% translate "Club information" %}</h2>
<div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2"> <div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2 xl:grid-cols-3">
{% form_field form.license %} {% form_field form.license %}
{% form_field form.admin show_as_toggle=True %} {% form_field form.admin show_as_toggle=True %}
</div> </div>
<h2 class="page-subtitle">{% translate "Password" %}</h2> <h2 class="page-subtitle">{% translate "Password" %}</h2>
<div class="mt-2 text-sm text-justify">{% blocktranslate %}Setting the password here will overwrite the current password for this member, after changing the member will be prompted to set a new password at the next login.<br /><br />If both fields are empty the current password will not be changed.{% endblocktranslate %}</div> <div class="mt-2 text-sm text-justify">{% blocktranslate %}Setting the password here will overwrite the current password for this member, after changing the member will be prompted to set a new password at the next login.<br/><br/>If both
<div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2"> fields are empty the current password will not be changed.{% endblocktranslate %}</div>
<div class="grid grid-cols-1 gap-4 mt-2 lg:grid-cols-2 xl:grid-cols-3">
{% form_field form.password %} {% form_field form.password %}
{% form_field form.password_confirmation %} {% form_field form.password_confirmation %}
</div> </div>