From 9dda7670df99e62e307962731e705903fb72f6a0 Mon Sep 17 00:00:00 2001 From: Halfdan Mouritzen Date: Sun, 14 Jan 2024 11:10:55 +0000 Subject: [PATCH 1/6] Minimal CSS for tables (#26) Reviewed-on: https://git.data.coop/data.coop/membersystem/pulls/26 Co-authored-by: Halfdan Mouritzen Co-committed-by: Halfdan Mouritzen --- src/project/static/css/style.css | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/project/static/css/style.css b/src/project/static/css/style.css index 7a6f045..49bb56e 100644 --- a/src/project/static/css/style.css +++ b/src/project/static/css/style.css @@ -28,6 +28,7 @@ p, h1, h2, h3, h4, h5, h6 { --light : #fff; --light-dust : #f6f6f6; --dust : #f1f1f1; + --medium-dust : #dadada; --dark-dust : #bfbfbf; --fade : #878787; --twilight : #4a4a4a; @@ -61,6 +62,8 @@ h1, h2, h3, h4, h5, h6 { a { font-weight : 500; + color : var(--splash); + text-decoration : none; } body { @@ -249,6 +252,25 @@ button:hover { opacity : 1.0; } +article table { + width : 100%; + border-spacing : 0; + margin : var(--space) 0; +} + +article table thead th { + text-align : left; +} + +article table tbody tr:nth-child(odd) { + background : var(--medium-dust); +} + +article table thead th, +article table tbody td { + padding : var(--half-space); +} + form > div { margin : 0 0 var(--double-space); } From b8a970d5fe09bc1e16532be75d53351d07646580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sun, 14 Jan 2024 12:14:51 +0100 Subject: [PATCH 2/6] Add missing migration --- ...rency_alter_order_vat_currency_and_more.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/accounting/migrations/0002_alter_order_price_currency_alter_order_vat_currency_and_more.py diff --git a/src/accounting/migrations/0002_alter_order_price_currency_alter_order_vat_currency_and_more.py b/src/accounting/migrations/0002_alter_order_price_currency_alter_order_vat_currency_and_more.py new file mode 100644 index 0000000..7dcbdb1 --- /dev/null +++ b/src/accounting/migrations/0002_alter_order_price_currency_alter_order_vat_currency_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.0.1 on 2024-01-14 11:14 + +import djmoney.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("accounting", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="price_currency", + field=djmoney.models.fields.CurrencyField( + choices=[("DKK", "DKK")], default=None, editable=False, max_length=3 + ), + ), + migrations.AlterField( + model_name="order", + name="vat_currency", + field=djmoney.models.fields.CurrencyField( + choices=[("DKK", "DKK")], default=None, editable=False, max_length=3 + ), + ), + migrations.AlterField( + model_name="payment", + name="amount_currency", + field=djmoney.models.fields.CurrencyField( + choices=[("DKK", "DKK")], default=None, editable=False, max_length=3 + ), + ), + migrations.AlterField( + model_name="transaction", + name="amount_currency", + field=djmoney.models.fields.CurrencyField( + choices=[("DKK", "DKK")], default=None, editable=False, max_length=3 + ), + ), + ] From f31cd623516124d8bd2870f1004ea1dec19f3c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sun, 14 Jan 2024 12:27:36 +0100 Subject: [PATCH 3/6] Implement django-view-decorator --- pyproject.toml | 1 + requirements/base.txt | 3 +++ src/membership/views.py | 38 +++++++++++++++++++++++++-------- src/project/settings.py | 1 + src/project/templates/base.html | 2 +- src/project/urls.py | 18 ++-------------- src/project/views.py | 12 +++++++++++ 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e571243..2ff5219 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "whitenoise==6.6.0", "django-zen-queries==2.1.0", "django-registries==0.0.3", + "django-view-decorator==0.0.4", ] dynamic = ["version"] diff --git a/requirements/base.txt b/requirements/base.txt index 7213dee..4d4f8f1 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -30,6 +30,7 @@ django==5.0.1 # django-allauth # django-money # django-registries + # django-view-decorator # django-zen-queries # membersystem (pyproject.toml) django-allauth==0.60.0 @@ -40,6 +41,8 @@ django-money==3.4.1 # via membersystem (pyproject.toml) django-registries==0.0.3 # via membersystem (pyproject.toml) +django-view-decorator==0.0.4 + # via membersystem (pyproject.toml) django-zen-queries==2.1.0 # via membersystem (pyproject.toml) environs[django]==10.0.0 diff --git a/src/membership/views.py b/src/membership/views.py index a325e29..7655d12 100644 --- a/src/membership/views.py +++ b/src/membership/views.py @@ -1,6 +1,5 @@ -from django.contrib.auth.decorators import login_required -from django.contrib.auth.decorators import permission_required from django.utils.translation import gettext_lazy as _ +from django_view_decorator import namespaced_decorator_factory from .permissions import ADMINISTRATE_MEMBERS from .selectors import get_member @@ -12,7 +11,14 @@ from utils.view_utils import render_list from utils.view_utils import RowAction -@login_required +member_view = namespaced_decorator_factory(namespace="member", base_path="membership") + + +@member_view( + paths="", + name="membership-overview", + login_required=True, +) def membership_overview(request): memberships = get_memberships(member=request.user) current_membership = memberships.current() @@ -33,8 +39,18 @@ def membership_overview(request): ) -@login_required -@permission_required(ADMINISTRATE_MEMBERS.path) +admin_members_view = namespaced_decorator_factory( + namespace="admin-members", + base_path="admin", +) + + +@admin_members_view( + paths="members/", + name="list", + login_required=True, + permissions=[ADMINISTRATE_MEMBERS.path], +) def members_admin(request): users = get_members() @@ -54,15 +70,19 @@ def members_admin(request): row_actions=[ RowAction( label=_("View"), - url_name="admin-members-detail", + url_name="admin-members:detail", url_kwargs={"member_id": "id"}, ), ], ) -@login_required -@permission_required(ADMINISTRATE_MEMBERS.path) +@admin_members_view( + paths="/", + name="detail", + login_required=True, + permissions=[ADMINISTRATE_MEMBERS.path], +) def members_admin_detail(request, member_id): member = get_member(member_id=member_id) subscription_periods = get_subscription_periods(member=member) @@ -70,7 +90,7 @@ def members_admin_detail(request, member_id): context = { "member": member, "subscription_periods": subscription_periods, - "base_path": "admin-members", + "base_path": "admin-members:list", } return render( diff --git a/src/project/settings.py b/src/project/settings.py index 349ee9e..e16582b 100644 --- a/src/project/settings.py +++ b/src/project/settings.py @@ -40,6 +40,7 @@ DJANGO_APPS = [ THIRD_PARTY_APPS = [ "allauth", "allauth.account", + "django_view_decorator", ] LOCAL_APPS = [ diff --git a/src/project/templates/base.html b/src/project/templates/base.html index a822ae6..5e41e54 100644 --- a/src/project/templates/base.html +++ b/src/project/templates/base.html @@ -66,7 +66,7 @@ {% if perms.membership.administrate_memberships %}
  • - + Admin
  • diff --git a/src/project/urls.py b/src/project/urls.py index ecc691b..17e788b 100644 --- a/src/project/urls.py +++ b/src/project/urls.py @@ -1,26 +1,12 @@ """URLs for the membersystem""" from django.conf import settings from django.contrib import admin -from django.contrib.auth.decorators import login_required from django.urls import include 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 +from django_view_decorator import include_view_urls urlpatterns = [ - path("", login_required(index), name="index"), - path("services/", login_required(services_overview), name="services"), - path("membership/", membership_overview, name="membership-overview"), - path("admin/members/", members_admin, name="admin-members"), - path( - "admin/members//", - members_admin_detail, - name="admin-members-detail", - ), + path("", include_view_urls(extra_modules=["project.views"])), path("accounts/", include("allauth.urls")), path("_admin/", admin.site.urls), ] diff --git a/src/project/views.py b/src/project/views.py index 26db680..7882b02 100644 --- a/src/project/views.py +++ b/src/project/views.py @@ -1,9 +1,21 @@ +from django_view_decorator import view + from utils.view_utils import render +@view( + paths="", + name="index", + login_required=True, +) def index(request): return render(request, "index.html") +@view( + paths="services/", + name="services", + login_required=True, +) def services_overview(request): return render(request, "services_overview.html") From a098a0b03264916a565de9f90da99988c142ee96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Fri, 9 Feb 2024 22:04:30 +0100 Subject: [PATCH 4/6] Add logging. --- src/project/settings.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/project/settings.py b/src/project/settings.py index e16582b..a07226f 100644 --- a/src/project/settings.py +++ b/src/project/settings.py @@ -154,6 +154,26 @@ ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False ACCOUNT_USERNAME_REQUIRED = False +# Logging +# We want to log everything to stdout in docker +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + }, + }, + "loggers": { + "": { + "handlers": ["console"], + "level": "DEBUG", + }, + }, +} + + if DEBUG: INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"] MIDDLEWARE += [ From f99c7ee698145b5de261d267145097f87d4b4215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sat, 10 Feb 2024 10:50:05 +0100 Subject: [PATCH 5/6] Fetch permissions before rendering templates. --- src/utils/view_utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/view_utils.py b/src/utils/view_utils.py index 7b2e416..9b752a8 100644 --- a/src/utils/view_utils.py +++ b/src/utils/view_utils.py @@ -120,4 +120,9 @@ def render(request, template_name, context=None): if context is None: context = {} context = base_context(request) | context + + # Make sure to fetch all permissions before rendering the template + # otherwise django-zen-queries will complain about database queries. + request.user.get_all_permissions() + return zen_queries_render(request, template_name, context) From bdc2d8717cbcab1795b1b2dc4f08f83242e4a4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sat, 10 Feb 2024 10:55:38 +0100 Subject: [PATCH 6/6] Add devenv files. --- devenv.lock | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++ devenv.nix | 19 +++++ devenv.yaml | 5 ++ 3 files changed, 265 insertions(+) create mode 100644 devenv.lock create mode 100644 devenv.nix create mode 100644 devenv.yaml diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..0b577e6 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,241 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1707004164, + "narHash": "sha256-9Hr8onWtvLk5A8vCEkaE9kxA0D7PR62povFokM1oL5Q=", + "owner": "cachix", + "repo": "devenv", + "rev": "0e68853bb27981a4ffd7a7225b59ed84f7180fc7", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703887061, + "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1707451808, + "narHash": "sha256-UwDBUNHNRsYKFJzyTMVMTF5qS4xeJlWoeyJf+6vvamU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "442d407992384ed9c0e6d352de75b69079904e4e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-python": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1707114737, + "narHash": "sha256-ZXqv2epXAjDjfWbYn+yy4VOmW+C7SuUBoiZkkDoSqA4=", + "owner": "cachix", + "repo": "nixpkgs-python", + "rev": "f34ed02276bc08fe1c91c1bf0ef3589d68028878", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "nixpkgs-python", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1704874635, + "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1707347730, + "narHash": "sha256-0etC/exQIaqC9vliKhc3eZE2Mm2wgLa0tj93ZF/egvM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1707297608, + "narHash": "sha256-ADjo/5VySGlvtCW3qR+vdFF4xM9kJFlRDqcC9ZGI8EA=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "0db2e67ee49910adfa13010e7f012149660af7f0", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "nixpkgs-python": "nixpkgs-python", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..be720b2 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,19 @@ +{ pkgs, ... }: + +{ + languages.python.enable = true; + languages.python.version = "3.12"; + + services.postgres = { + enable = true; + package = pkgs.postgresql_15; + initialDatabases = [ {"name" = "postgres";} ]; + listen_addresses = "localhost"; + initialScript = "create user postgres with password 'postgres' superuser;"; + }; + + processes = { + app.exec = "while ! pg_isready -d postgres -h localhost -U postgres 2>/dev/null; do sleep 1; done; hatch run server"; + }; + +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..a32e623 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,5 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable + nixpkgs-python: + url: github:cachix/nixpkgs-python