forked from data.coop/membersystem
Implement a generic way to adding actions to rows in a list.
This commit is contained in:
parent
cf99c3f40e
commit
704b196128
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ member.username }}
|
||||
|
||||
{% endblock %}
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue