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 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/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 + ), + ), + ] 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..a07226f 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 = [ @@ -153,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 += [ diff --git a/src/project/static/css/style.css b/src/project/static/css/style.css index 5017698..641b25b 100644 --- a/src/project/static/css/style.css +++ b/src/project/static/css/style.css @@ -94,7 +94,7 @@ h6 { } a { - font-weight: 500; + <<<<<<< HEAD font-weight: 500; color: var(--splash); text-decoration: none; cursor: pointer; @@ -105,6 +105,10 @@ hr { height: 0; border: 0; border-bottom: 1px solid var(--dark-custard); + =======font-weight: 500; + color: var(--splash); + text-decoration: none; + >>>>>>>bdc2d8717cbcab1795b1b2dc4f08f83242e4a4ca } body { @@ -263,7 +267,12 @@ div.services { div.services>div, div.infobox { - background: var(--light); + <<<<<<< HEAD background: var(--light); + padding: var(--double-space); + border-radius: 6px; + flex: 240px; + max-width: 420px; + =======background: var(--light); padding: var(--double-space); border-radius: 6px; flex: 240px; @@ -285,6 +294,111 @@ div.services>div>div.description>p { margin-top: var(--half-space); } +div.services>div>a, +a.button, +button { + display: block; + color: var(--light); + background: var(--splash); + padding: var(--space) var(--double-space); + border-radius: 3px; + opacity: 0.9; + cursor: pointer; + text-align: center; + border: 0; + font-weight: 600; + letter-spacing: 0.03em; + text-decoration: none; +} + +div.services>div>a:hover, +a.button:hover, +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); +} + +form>div>label { + display: block; + margin: 0 0 6px; +} + +form>div>input[type="text"], +form>div>input[type="password"] { + border: 2px solid var(--twilight); + border-radius: 6px; + padding: 8px; + background: var(--light-dust); + width: 100%; +} + +#login { + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +#loginbox { + border-radius: var(--space); + border: 6px solid white; + width: 800px; + height: 500px; + + display: flex; + flex-flow: row; +} + +#loginbox>div { + padding: var(--double-space); + flex: 1; +} + +#loginbox label { + color: var(--twilight); +} + +#loginbox>div.login { + background: var(--light-dust); + >>>>>>>bdc2d8717cbcab1795b1b2dc4f08f83242e4a4ca display: flex; + flex-flow: column; + justify-content: space-between; +} + +div.infobox button { + margin-top: var(--double-space); +} + +div.services>div>div.description { + margin-bottom: var(--double-space); +} + +div.services>div>div.description>p { + margin-top: var(--half-space); +} + div.services>div>a, a.button, button { @@ -387,7 +501,7 @@ input[type="email"] { padding: 8px; background: var(--light-dust); width: 100%; - color : var(--dark); + color: var(--dark); } form fieldset { @@ -530,4 +644,4 @@ span.time_remaining { .pagination .page-item.disabled .page-link { cursor: default; -} +} \ No newline at end of file diff --git a/src/project/templates/base.html b/src/project/templates/base.html index d489149..5f31cd9 100644 --- a/src/project/templates/base.html +++ b/src/project/templates/base.html @@ -94,7 +94,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") 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)