Implement a generic way to adding actions to rows in a list.

This commit is contained in:
Víðir Valberg Guðmundsson 2023-01-03 21:36:34 +01:00
parent cf99c3f40e
commit 704b196128
8 changed files with 143 additions and 13 deletions

View file

@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateRangeField
from django.contrib.postgres.fields import RangeOperators
@ -8,6 +9,26 @@ from django.utils.translation import gettext as _
from utils.mixins import CreatedModifiedAbstract
class Member(User):
class QuerySet(models.QuerySet):
def annotate_membership(self):
from membership.selectors import get_current_subscription_period
return self.annotate(
active_membership=models.Exists(
Membership.objects.filter(
user=models.OuterRef("pk"),
period=get_current_subscription_period().id,
)
)
)
objects = QuerySet.as_manager()
class Meta:
proxy = True
class SubscriptionPeriod(CreatedModifiedAbstract):
"""
Denotes a period for which members should pay their membership fee for.

View file

@ -1,5 +1,9 @@
from django.contrib.auth.models import User
import contextlib
from django.contrib.auth.models import User
from django.utils import timezone
from membership.models import Member
from membership.models import Membership
from membership.models import SubscriptionPeriod
@ -12,6 +16,13 @@ def get_subscription_periods() -> list[SubscriptionPeriod]:
return list(subscription_periods)
def get_current_subscription_period() -> SubscriptionPeriod | None:
with contextlib.suppress(SubscriptionPeriod.DoesNotExist):
return SubscriptionPeriod.objects.prefetch_related(
"membership_set", "membership_set__user"
).get(period__contains=timezone.now())
def get_memberships(
*, user: User | None = None, period: SubscriptionPeriod | None = None
) -> Membership.QuerySet:
@ -26,5 +37,9 @@ def get_memberships(
return memberships
def get_users():
return User.objects.all()
def get_members():
return Member.objects.all().annotate_membership()
def get_member(*, member_id: int) -> Member:
return get_members().get(id=member_id)

View file

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{{ member.username }}
{% endblock %}

View file

@ -4,10 +4,12 @@ from django.utils.translation import gettext_lazy as _
from zen_queries import render
from .permissions import ADMINISTRATE_MEMBERS
from .selectors import get_member
from .selectors import get_members
from .selectors import get_memberships
from .selectors import get_users
from utils.view_utils import base_view_context
from utils.view_utils import render_list
from utils.view_utils import RowAction
@login_required
@ -34,7 +36,7 @@ def membership_overview(request):
@login_required
@permission_required(ADMINISTRATE_MEMBERS.path)
def members_admin(request):
users = get_users()
users = get_members()
return render_list(
request=request,
@ -44,5 +46,27 @@ def members_admin(request):
("first_name", _("First name")),
("last_name", _("Last name")),
("email", _("Email")),
("active_membership", _("Active membership")),
],
row_actions=[
RowAction(
label=_("View"),
url_name="admin-members-detail",
url_kwargs={"member_id": "id"},
)
],
)
@login_required
@permission_required(ADMINISTRATE_MEMBERS.path)
def members_admin_detail(request, member_id):
member = get_member(member_id=member_id)
context = base_view_context(request) | {"member": member}
return render(
request=request,
template_name="membership/members_admin_detail.html",
context=context,
)

View file

@ -203,7 +203,7 @@
</div>
</nav>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 p-4">
{% block content %}
{% endblock %}
</main>

View file

@ -8,6 +8,7 @@ from django.urls import path
from .views import index
from .views import services_overview
from membership.views import members_admin
from membership.views import members_admin_detail
from membership.views import membership_overview
urlpatterns = [
@ -15,8 +16,13 @@ urlpatterns = [
path("services/", login_required(services_overview), name="services-overview"),
path("membership/", membership_overview, name="membership-overview"),
path("admin/members/", members_admin, name="admin-members"),
path(
"admin/members/<int:member_id>/",
members_admin_detail,
name="admin-members-detail",
),
path("accounts/", include("allauth.urls")),
path("admin/", admin.site.urls),
path("_admin/", admin.site.urls),
]
if settings.DEBUG:

View file

@ -11,11 +11,20 @@
{% endfor %}
</tr>
</thead>
{% for row in object_rows %}
{% for row in rows %}
<tr>
{% for value in row %}
{% for value in row.data.values %}
<td>{{ value }}</td>
{% endfor %}
{% if row.actions %}
<td>
{% for action in row.actions %}
<a href="{{ action.url }}">
{{ action.label }}
</a>
{% endfor %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>

View file

@ -1,7 +1,10 @@
from dataclasses import dataclass
from django.contrib.sites.shortcuts import get_current_site as django_get_current_site
from django.db.models import Model
from django.http import HttpRequest
from django.http import HttpResponse
from django.urls import reverse
from zen_queries import render
@ -10,21 +13,65 @@ def base_view_context(request):
return {"site": django_get_current_site(request)}
@dataclass
class Row:
"""
A row in a table.
"""
data: dict[str, str]
actions: list[dict[str, str]]
@dataclass
class RowAction:
"""
An action that can be performed on a row in a table.
"""
label: str
url_name: str
url_kwargs: dict[str, str]
def render(self, obj) -> dict[str, str]:
"""
Render the action as a dictionary for the given object.
"""
url = reverse(
self.url_name,
kwargs={key: getattr(obj, value) for key, value in self.url_kwargs.items()},
)
return {"label": self.label, "url": url}
def render_list(
request: HttpRequest,
objects: list["Model"],
columns: list[tuple[str, str]],
row_actions: list[RowAction] = None,
list_actions: list[tuple[str, str]] = None,
) -> HttpResponse:
# TODO: Actions per object
# TODO: Listwide actions
"""
Render a list of objects with a table.
"""
object_rows = [[getattr(obj, column[0]) for column in columns] for obj in objects]
# TODO: List actions
rows = []
for obj in objects:
row = Row(
data={column: getattr(obj, column[0]) for column in columns},
actions=[action.render(obj) for action in row_actions],
)
rows.append(row)
column_labels = [column[1] for column in columns]
context = base_view_context(request) | {
"object_rows": object_rows,
"rows": rows,
"columns": column_labels,
"row_actions": row_actions,
"list_actions": list_actions,
}
return render(