Compare commits

..

No commits in common. "e38bd565499044b8c455c45867f542e7830a263b" and "cf99c3f40e9ef8d148c8eee2cde83051166521da" have entirely different histories.

11 changed files with 27 additions and 153 deletions

View file

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

View file

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

View file

@ -1,4 +1,3 @@
from django.contrib.auth.models import User
from django.contrib.postgres.constraints import ExclusionConstraint from django.contrib.postgres.constraints import ExclusionConstraint
from django.contrib.postgres.fields import DateRangeField from django.contrib.postgres.fields import DateRangeField
from django.contrib.postgres.fields import RangeOperators from django.contrib.postgres.fields import RangeOperators
@ -9,26 +8,6 @@ from django.utils.translation import gettext as _
from utils.mixins import CreatedModifiedAbstract 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): class SubscriptionPeriod(CreatedModifiedAbstract):
""" """
Denotes a period for which members should pay their membership fee for. Denotes a period for which members should pay their membership fee for.

View file

@ -1,9 +1,5 @@
import contextlib
from django.contrib.auth.models import User 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 Membership
from membership.models import SubscriptionPeriod from membership.models import SubscriptionPeriod
@ -16,13 +12,6 @@ def get_subscription_periods() -> list[SubscriptionPeriod]:
return list(subscription_periods) 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( def get_memberships(
*, user: User | None = None, period: SubscriptionPeriod | None = None *, user: User | None = None, period: SubscriptionPeriod | None = None
) -> Membership.QuerySet: ) -> Membership.QuerySet:
@ -37,9 +26,5 @@ def get_memberships(
return memberships return memberships
def get_members(): def get_users():
return Member.objects.all().annotate_membership() return User.objects.all()
def get_member(*, member_id: int) -> Member:
return get_members().get(id=member_id)

View file

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

View file

@ -4,12 +4,10 @@ from django.utils.translation import gettext_lazy as _
from zen_queries import render from zen_queries import render
from .permissions import ADMINISTRATE_MEMBERS from .permissions import ADMINISTRATE_MEMBERS
from .selectors import get_member
from .selectors import get_members
from .selectors import get_memberships from .selectors import get_memberships
from .selectors import get_users
from utils.view_utils import base_view_context from utils.view_utils import base_view_context
from utils.view_utils import render_list from utils.view_utils import render_list
from utils.view_utils import RowAction
@login_required @login_required
@ -36,7 +34,7 @@ def membership_overview(request):
@login_required @login_required
@permission_required(ADMINISTRATE_MEMBERS.path) @permission_required(ADMINISTRATE_MEMBERS.path)
def members_admin(request): def members_admin(request):
users = get_members() users = get_users()
return render_list( return render_list(
request=request, request=request,
@ -46,27 +44,5 @@ def members_admin(request):
("first_name", _("First name")), ("first_name", _("First name")),
("last_name", _("Last name")), ("last_name", _("Last name")),
("email", _("Email")), ("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,6 +89,7 @@ TIME_ZONE = "Europe/Copenhagen"
USE_I18N = True USE_I18N = True
USE_L10N = True
USE_TZ = True USE_TZ = True

View file

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

View file

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

View file

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

View file

@ -1,10 +1,7 @@
from dataclasses import dataclass
from django.contrib.sites.shortcuts import get_current_site as django_get_current_site from django.contrib.sites.shortcuts import get_current_site as django_get_current_site
from django.db.models import Model from django.db.models import Model
from django.http import HttpRequest from django.http import HttpRequest
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse
from zen_queries import render from zen_queries import render
@ -13,65 +10,21 @@ def base_view_context(request):
return {"site": django_get_current_site(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( def render_list(
request: HttpRequest, request: HttpRequest,
objects: list["Model"], objects: list["Model"],
columns: list[tuple[str, str]], columns: list[tuple[str, str]],
row_actions: list[RowAction] = None,
list_actions: list[tuple[str, str]] = None,
) -> HttpResponse: ) -> HttpResponse:
""" # TODO: Actions per object
Render a list of objects with a table. # TODO: Listwide actions
"""
# TODO: List actions object_rows = [[getattr(obj, column[0]) for column in columns] for obj in objects]
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] column_labels = [column[1] for column in columns]
context = base_view_context(request) | { context = base_view_context(request) | {
"rows": rows, "object_rows": object_rows,
"columns": column_labels, "columns": column_labels,
"row_actions": row_actions,
"list_actions": list_actions,
} }
return render( return render(