Compare commits

...

2 commits

Author SHA1 Message Date
Víðir Valberg Guðmundsson e38bd56549 Pre-commit autoupdate and run on --all-files.
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-03 21:39:40 +01:00
Víðir Valberg Guðmundsson 704b196128 Implement a generic way to adding actions to rows in a list. 2023-01-03 21:36:34 +01:00
11 changed files with 154 additions and 28 deletions

View file

@ -2,11 +2,11 @@ default_language_version:
python: python3
repos:
- repo: https://github.com/psf/black
rev: 22.6.0
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-ast
- id: check-merge-conflict
@ -19,7 +19,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pycqa/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies:
@ -28,7 +28,7 @@ repos:
- flake8-tidy-imports
args: [--max-line-length=88, --extend-exclude=src/project/settings/deployments/*]
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.8.2
rev: v3.9.0
hooks:
- id: reorder-python-imports
args:
@ -36,15 +36,15 @@ repos:
- --application-directories=.:src
exclude: migrations/
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
rev: v3.3.1
hooks:
- id: pyupgrade
args:
- --py39-plus
- --py310-plus
exclude: migrations/
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.7.0
rev: 1.12.0
hooks:
- id: django-upgrade
args:
- --target-version=3.2
- --target-version=4.1

View file

@ -9,23 +9,20 @@ class OrderAdmin(admin.ModelAdmin):
list_display = ("who", "description", "created", "is_paid")
@admin.display(description=_("Customer"))
def who(self, instance):
return instance.user.get_full_name()
who.short_description = _("Customer")
@admin.register(models.Payment)
class PaymentAdmin(admin.ModelAdmin):
list_display = ("who", "description", "order_id", "created")
@admin.display(description=_("Customer"))
def who(self, instance):
return instance.order.user.get_full_name()
who.short_description = _("Customer")
@admin.display(description=_("Order ID"))
def order_id(self, instance):
return instance.order.id
order_id.short_description = _("Order ID")

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

@ -89,7 +89,6 @@ TIME_ZONE = "Europe/Copenhagen"
USE_I18N = True
USE_L10N = True
USE_TZ = True

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(