Compare commits

..

2 Commits

7 changed files with 72 additions and 7 deletions

View File

@@ -7,6 +7,6 @@ urlpatterns = [
path("", MemberListView.as_view(), name="list"),
# path("add/", MemberAddView.as_view(), name="add"),
# 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"),
]

View File

@@ -1,9 +1,11 @@
from typing import Any
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import DeleteView
from django_filters.views import FilterView
from rules.contrib.views import PermissionRequiredMixin
@@ -21,6 +23,17 @@ class MemberListView(HTMXViewMixin, PermissionRequiredMixin, FilterView):
def handle_no_permission(self) -> HttpResponseRedirect:
messages.error(self.request, self.get_permission_denied_message())
return HttpResponseRedirect(reverse_lazy("backend:index"))
def get_filterset_kwargs(self, filterset_class) -> dict[str, Any]:
kwargs = super().get_filterset_kwargs(filterset_class)
filter_values = {} if kwargs["data"] is None else kwargs["data"].dict()
if not filter_values:
filter_values.update({"user__is_active": "true"})
kwargs["data"] = filter_values
return kwargs
class MemberAddView: ...
@@ -29,7 +42,30 @@ class MemberAddView: ...
class MemberEditView: ...
class MemberDeleteView: ...
class MemberDeleteView(HTMXViewMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView):
model = Member
success_message = _("Member <strong>%(name)s</strong> has been deleted successfully.")
permission_required = "members.delete_member"
permission_denied_message = _("You do not have permission to view this page.")
success_url = reverse_lazy("backend:members:list")
def handle_no_permission(self) -> HttpResponseRedirect:
messages.error(self.request, self.get_permission_denied_message())
return HttpResponseRedirect(reverse_lazy("backend:index"))
def get_success_message(self, cleaned_data):
return self.success_message % dict(cleaned_data, name=self.object.user.get_full_name())
def post(self, request, *args, **kwargs):
self.object = self.get_object()
# Soft delete user
self.object.user.is_active = False
self.object.user.save()
# Do not delete the member object
messages.success(self.request, self.get_success_message({"name": self.object.user.get_full_name()}))
return HttpResponseRedirect(self.get_success_url())
class MemberLoadView: ...

View File

@@ -17,14 +17,14 @@ class HTMXPartialMixin:
class HTMXViewMixin:
"""
A full featured HTMX integration mixin for Django CBVs.
A full-featured HTMX integration mixin for Django CBVs.
Supports:
- partial rendering via django-partials
- HX-Redirect
- HX-Push-URL
- HX-Trigger events
- HX-Refresh
- Graceful fallback to normal DJango rendering
- Graceful fallback to normal Django rendering
"""
# Name of the partial block: template.html#partial_name

View File

@@ -8,9 +8,10 @@ class MemberFilter(django_filters.FilterSet):
user__first_name = django_filters.CharFilter(field_name="user__first_name", label=_("First name"), lookup_expr="icontains")
user__last_name = django_filters.CharFilter(field_name="user__last_name", label=_("Last name"), lookup_expr="icontains")
license = django_filters.CharFilter(label=_("License"), lookup_expr="icontains")
user__is_active = django_filters.TypedChoiceFilter( field_name='user__is_active', label=_("Active?"), initial="true", choices=( ('', 'All'), ('true', 'Yes'), ('false', 'No'), ), coerce=lambda x: x.lower() == 'true' )
class Meta:
model = Member
fields = ["user__first_name", "user__last_name", "license"]
fields = ["user__first_name", "user__last_name", "license", "user__is_active"]

View File

@@ -0,0 +1,27 @@
{% extends "backend/base.html" %}
{% load i18n %}
{% load form_field %}
{% load avatar %}
{% load pagination %}
{% block content %}
{% partialdef content inline %}
<h1 class="page-title">{% translate "Members" %}</h1>
<div>
{% blocktranslate with name=object.user.get_full_name %}
Are you sure you want to delete member <span class="font-bold">{{ name }}</span>?
{% endblocktranslate %}
</div>
<form method="post">
{% csrf_token %}
<div class="flex flex-row gap-2 mt-8">
<a href="{% url "backend:members:list" %}" class="btn btn-neutral btn-outline grow lg:grow-0">{% translate "Cancel" %}</a>
<button type="submit" class="btn btn-error grow lg:grow-0"><i class="fa-solid fa-trash"></i>{% translate "Delete" %}</button>
</div>
</form>
{% endpartialdef content %}
{% endblock content %}

View File

@@ -7,7 +7,7 @@
{% block content %}
{% partialdef content inline %}
<h1 class="page-title">Members</h1>
<h1 class="page-title">{% translate "Members" %}</h1>
<div class="lg:hidden collapse collapse-plus bg-base-100 border-neutral border">
<input type="checkbox" />
@@ -129,7 +129,7 @@
<i class="fa-solid fa-eye"></i>{% translate "Details" %}
</a>
<a class="btn btn-outline btn-error btn-sm" href="">
<a class="btn btn-outline btn-error btn-sm" href="{% url "backend:members:delete" member.pk %}">
<i class="fa-solid fa-trash"></i>{% translate "Delete" %}
</a>
</div>

View File

@@ -57,6 +57,7 @@
}
@source inline("input-{xs,sm,md,lg,xl}");
@source inline("select-{xs,sm,md,lg,xl}");
@layer components {
h1.page-title {