Compare commits
No commits in common. "535543f82f5326174f99c69df6fa89806c5fcd29" and "c48cc3b3475743a3df562e5b9c2bd1d59e67f930" have entirely different histories.
535543f82f
...
c48cc3b347
|
@ -1,5 +1,5 @@
|
||||||
default_language_version:
|
default_language_version:
|
||||||
python: python3.12
|
python: python3.11
|
||||||
exclude: ^.*\b(migrations)\b.*$
|
exclude: ^.*\b(migrations)\b.*$
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
@ -16,22 +16,45 @@ repos:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
rev: 'v0.3.0'
|
rev: 'v0.1.11'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args:
|
args:
|
||||||
- --fix
|
- --fix
|
||||||
- id: ruff-format
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
|
rev: v3.12.0
|
||||||
|
hooks:
|
||||||
|
- id: reorder-python-imports
|
||||||
|
args:
|
||||||
|
- --py310-plus
|
||||||
|
- --application-directories=.:src
|
||||||
|
exclude: migrations/
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.15.1
|
rev: v3.15.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args:
|
args:
|
||||||
- --py311-plus
|
- --py311-plus
|
||||||
exclude: migrations/
|
exclude: migrations/
|
||||||
- repo: https://github.com/adamchainz/django-upgrade
|
- repo: https://github.com/adamchainz/django-upgrade
|
||||||
rev: 1.16.0
|
rev: 1.15.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: django-upgrade
|
- id: django-upgrade
|
||||||
args:
|
args:
|
||||||
- --target-version=5.0
|
- --target-version=4.1
|
||||||
|
- repo: https://github.com/asottile/yesqa
|
||||||
|
rev: v1.5.0
|
||||||
|
hooks:
|
||||||
|
- id: yesqa
|
||||||
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
|
rev: v3.1.0
|
||||||
|
hooks:
|
||||||
|
- id: add-trailing-comma
|
||||||
|
- repo: https://github.com/hadialqattan/pycln
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: pycln
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 23.12.1
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.12-slim-bullseye
|
FROM python:3.11-slim-bullseye
|
||||||
|
|
||||||
ENV PYTHONFAULTHANDLER=1 \
|
ENV PYTHONFAULTHANDLER=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
|
|
241
devenv.lock
241
devenv.lock
|
@ -1,241 +0,0 @@
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
19
devenv.nix
19
devenv.nix
|
@ -1,19 +0,0 @@
|
||||||
{ 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";
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
inputs:
|
|
||||||
nixpkgs:
|
|
||||||
url: github:NixOS/nixpkgs/nixpkgs-unstable
|
|
||||||
nixpkgs-python:
|
|
||||||
url: github:cachix/nixpkgs-python
|
|
|
@ -104,26 +104,3 @@ follow_imports = "normal"
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
module = "tests.*"
|
module = "tests.*"
|
||||||
allow_untyped_defs = true
|
allow_untyped_defs = true
|
||||||
|
|
||||||
[tool.ruff]
|
|
||||||
target-version = "py312"
|
|
||||||
extend-exclude = [
|
|
||||||
".git",
|
|
||||||
"__pycache__",
|
|
||||||
]
|
|
||||||
line-length = 120
|
|
||||||
|
|
||||||
[tool.ruff.lint]
|
|
||||||
select = ["ALL"]
|
|
||||||
ignore = [
|
|
||||||
"G004", # Logging statement uses f-string
|
|
||||||
"ANN101", # Missing type annotation for `self` in method
|
|
||||||
"ANN102", # Missing type annotation for `cls` in classmethod
|
|
||||||
"EM101", # Exception must not use a string literal, assign to variable first
|
|
||||||
"EM102", # Exception must not use a f-string literal, assign to variable first
|
|
||||||
"COM812", # missing-trailing-comma (https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules)
|
|
||||||
"ISC001", # single-line-implicit-string-concatenation (https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules)
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff.lint.isort]
|
|
||||||
force-single-line = true
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -98,7 +99,9 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"vat",
|
"vat",
|
||||||
djmoney.models.fields.MoneyField(decimal_places=2, max_digits=16, verbose_name="VAT"),
|
djmoney.models.fields.MoneyField(
|
||||||
|
decimal_places=2, max_digits=16, verbose_name="VAT"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
("is_paid", models.BooleanField(default=False, verbose_name="is paid")),
|
||||||
(
|
(
|
||||||
|
|
|
@ -17,7 +17,8 @@ class CreatedModifiedAbstract(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Account(CreatedModifiedAbstract):
|
class Account(CreatedModifiedAbstract):
|
||||||
"""This is the model where we can give access to several users, such that they
|
"""
|
||||||
|
This is the model where we can give access to several users, such that they
|
||||||
can decide which account to use to pay for something.
|
can decide which account to use to pay for something.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -29,7 +30,8 @@ class Account(CreatedModifiedAbstract):
|
||||||
|
|
||||||
|
|
||||||
class Transaction(CreatedModifiedAbstract):
|
class Transaction(CreatedModifiedAbstract):
|
||||||
"""Tracks in and outgoing events of an account. When an order is received, an
|
"""
|
||||||
|
Tracks in and outgoing events of an account. When an order is received, an
|
||||||
amount is subtracted, when a payment is received, an amount is added.
|
amount is subtracted, when a payment is received, an amount is added.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -48,7 +50,8 @@ class Transaction(CreatedModifiedAbstract):
|
||||||
|
|
||||||
|
|
||||||
class Order(CreatedModifiedAbstract):
|
class Order(CreatedModifiedAbstract):
|
||||||
"""Scoped out: Contents of invoices will have to be tracked either here or in
|
"""
|
||||||
|
Scoped out: Contents of invoices will have to be tracked either here or in
|
||||||
a separate Invoice model. This is undecided because we are not generating
|
a separate Invoice model. This is undecided because we are not generating
|
||||||
invoices at the moment.
|
invoices at the moment.
|
||||||
"""
|
"""
|
||||||
|
@ -88,7 +91,7 @@ class Order(CreatedModifiedAbstract):
|
||||||
verbose_name = pgettext_lazy("accounting term", "Order")
|
verbose_name = pgettext_lazy("accounting term", "Order")
|
||||||
verbose_name_plural = pgettext_lazy("accounting term", "Orders")
|
verbose_name_plural = pgettext_lazy("accounting term", "Orders")
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
return f"Order ID {self.display_id}"
|
return f"Order ID {self.display_id}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,7 +116,7 @@ class Payment(CreatedModifiedAbstract):
|
||||||
description=order.description,
|
description=order.description,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
return f"Payment ID {self.display_id}"
|
return f"Payment ID {self.display_id}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -8,8 +8,8 @@ from . import models
|
||||||
# do stuff
|
# do stuff
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db()
|
@pytest.mark.django_db
|
||||||
def test_balance() -> None:
|
def test_balance():
|
||||||
user = User.objects.create_user("test", "lala@adas.com", "1234")
|
user = User.objects.create_user("test", "lala@adas.com", "1234")
|
||||||
account = models.Account.objects.create(owner=user)
|
account = models.Account.objects.create(owner=user)
|
||||||
assert account.balance == 0
|
assert account.balance == 0
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Membership application.
|
"""
|
||||||
|
Membership application
|
||||||
======================
|
======================
|
||||||
|
|
||||||
This application's domain relate to organizational structures and
|
This application's domain relate to organizational structures and
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.db.models.signals import post_migrate
|
||||||
class MembershipConfig(AppConfig):
|
class MembershipConfig(AppConfig):
|
||||||
name = "membership"
|
name = "membership"
|
||||||
|
|
||||||
def ready(self) -> None:
|
def ready(self):
|
||||||
from .permissions import persist_permissions
|
from .permissions import persist_permissions
|
||||||
|
|
||||||
post_migrate.connect(persist_permissions, sender=self)
|
post_migrate.connect(persist_permissions, sender=self)
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
import django.contrib.postgres.constraints
|
import django.contrib.postgres.constraints
|
||||||
import django.contrib.postgres.fields.ranges
|
import django.contrib.postgres.fields.ranges
|
||||||
from django.db import migrations
|
from django.db import migrations, models
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("membership", "0001_initial"),
|
("membership", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
@ -34,7 +34,9 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"period",
|
"period",
|
||||||
django.contrib.postgres.fields.ranges.DateRangeField(verbose_name="period"),
|
django.contrib.postgres.fields.ranges.DateRangeField(
|
||||||
|
verbose_name="period"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Generated by Django 4.1 on 2023-01-02 21:05
|
# Generated by Django 4.1 on 2023-01-02 21:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("membership", "0002_subscriptionperiod_remove_membership_period_and_more"),
|
("membership", "0002_subscriptionperiod_remove_membership_period_and_more"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Generated by Django 4.1 on 2023-01-02 21:06
|
# Generated by Django 4.1 on 2023-01-02 21:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("membership", "0003_membership_period"),
|
("membership", "0003_membership_period"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,20 +4,22 @@ from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
("membership", "0004_alter_membership_period"),
|
('membership', '0004_alter_membership_period'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Member",
|
name='Member',
|
||||||
fields=[],
|
fields=[
|
||||||
|
],
|
||||||
options={
|
options={
|
||||||
"proxy": True,
|
'proxy': True,
|
||||||
"indexes": [],
|
'indexes': [],
|
||||||
"constraints": [],
|
'constraints': [],
|
||||||
},
|
},
|
||||||
bases=("auth.user",),
|
bases=('auth.user',),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -36,7 +36,9 @@ class Member(User):
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
"""
|
||||||
|
|
||||||
period = DateRangeField(verbose_name=_("period"))
|
period = DateRangeField(verbose_name=_("period"))
|
||||||
|
|
||||||
|
@ -50,12 +52,16 @@ class SubscriptionPeriod(CreatedModifiedAbstract):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
return f"{self.period.lower} - {self.period.upper or _('next general assembly')}"
|
return (
|
||||||
|
f"{self.period.lower} - {self.period.upper or _('next general assembly')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Membership(CreatedModifiedAbstract):
|
class Membership(CreatedModifiedAbstract):
|
||||||
"""Tracks that a user has membership of a given type for a given period."""
|
"""
|
||||||
|
Tracks that a user has membership of a given type for a given period.
|
||||||
|
"""
|
||||||
|
|
||||||
class QuerySet(models.QuerySet):
|
class QuerySet(models.QuerySet):
|
||||||
def for_member(self, member: Member):
|
def for_member(self, member: Member):
|
||||||
|
@ -96,12 +102,13 @@ class Membership(CreatedModifiedAbstract):
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
return f"{self.user} - {self.period}"
|
return f"{self.user} - {self.period}"
|
||||||
|
|
||||||
|
|
||||||
class MembershipType(CreatedModifiedAbstract):
|
class MembershipType(CreatedModifiedAbstract):
|
||||||
"""Models membership types. Currently only a name, but will in the future
|
"""
|
||||||
|
Models membership types. Currently only a name, but will in the future
|
||||||
possibly contain more information like fees.
|
possibly contain more information like fees.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -111,7 +118,7 @@ class MembershipType(CreatedModifiedAbstract):
|
||||||
|
|
||||||
name = models.CharField(verbose_name=_("name"), max_length=64)
|
name = models.CharField(verbose_name=_("name"), max_length=64)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
PERMISSIONS = []
|
PERMISSIONS = []
|
||||||
|
|
||||||
|
|
||||||
def persist_permissions(sender, **kwargs) -> None:
|
def persist_permissions(sender, **kwargs):
|
||||||
for permission in PERMISSIONS:
|
for permission in PERMISSIONS:
|
||||||
permission.persist_permission()
|
permission.persist_permission()
|
||||||
|
|
||||||
|
@ -23,10 +23,10 @@ class Permission:
|
||||||
PERMISSIONS.append(self)
|
PERMISSIONS.append(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> str:
|
def path(self):
|
||||||
return f"{self.app_label}.{self.codename}"
|
return f"{self.app_label}.{self.codename}"
|
||||||
|
|
||||||
def persist_permission(self) -> None:
|
def persist_permission(self):
|
||||||
content_type, _ = ContentType.objects.get_or_create(
|
content_type, _ = ContentType.objects.get_or_create(
|
||||||
app_label=self.app_label,
|
app_label=self.app_label,
|
||||||
model=self.model,
|
model=self.model,
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_view_decorator import namespaced_decorator_factory
|
from django_view_decorator import namespaced_decorator_factory
|
||||||
from utils.view_utils import RowAction
|
|
||||||
from utils.view_utils import render
|
|
||||||
from utils.view_utils import render_list
|
|
||||||
|
|
||||||
from .permissions import ADMINISTRATE_MEMBERS
|
from .permissions import ADMINISTRATE_MEMBERS
|
||||||
from .selectors import get_member
|
from .selectors import get_member
|
||||||
from .selectors import get_members
|
from .selectors import get_members
|
||||||
from .selectors import get_memberships
|
from .selectors import get_memberships
|
||||||
from .selectors import get_subscription_periods
|
from .selectors import get_subscription_periods
|
||||||
|
from utils.view_utils import render
|
||||||
|
from utils.view_utils import render_list
|
||||||
|
from utils.view_utils import RowAction
|
||||||
|
|
||||||
|
|
||||||
member_view = namespaced_decorator_factory(namespace="member", base_path="membership")
|
member_view = namespaced_decorator_factory(namespace="member", base_path="membership")
|
||||||
|
|
||||||
|
|
|
@ -156,26 +156,6 @@ ACCOUNT_EMAIL_REQUIRED = True
|
||||||
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
||||||
ACCOUNT_USERNAME_REQUIRED = 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:
|
if DEBUG:
|
||||||
INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"]
|
INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"]
|
||||||
MIDDLEWARE += [
|
MIDDLEWARE += [
|
||||||
|
|
2018
src/project/static/css/bootstrap-icons.css
vendored
Normal file
2018
src/project/static/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
7
src/project/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/css/bootstrap.min.css.map
Normal file
1
src/project/static/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,56 +0,0 @@
|
||||||
html.dark body {
|
|
||||||
--splash: #5b47e0;
|
|
||||||
background: var(--dark-dark);
|
|
||||||
color: var(--medium-dust)
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark h1,
|
|
||||||
html.dark h2,
|
|
||||||
html.dark h3,
|
|
||||||
html.dark h4,
|
|
||||||
html.dark h5,
|
|
||||||
html.dark h6,
|
|
||||||
html.dark footer,
|
|
||||||
html.dark nav ol li a {
|
|
||||||
color: var(--medium-dust);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark nav ol li a:not(.current):hover {
|
|
||||||
border-color: var(--medium-dust);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark header,
|
|
||||||
html.dark main aside,
|
|
||||||
html.dark nav {
|
|
||||||
background: #1d1d1d;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark nav {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark hr {
|
|
||||||
border-color: var(--twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark main aside div,
|
|
||||||
html.dark article div.content-view {
|
|
||||||
background: var(--dark-twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark article table tbody {
|
|
||||||
background: var(--dark-twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark article table tbody tr:nth-child(2n+1) {
|
|
||||||
background: var(--dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark article table tbody tr:nth-child(2n+1) td {
|
|
||||||
border-top: 1px solid var(--dark-dark);
|
|
||||||
border-bottom: 1px solid var(--dark-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark article table tbody tr:last-child td {
|
|
||||||
border-bottom: var(--half-space) solid var(--twilight);
|
|
||||||
}
|
|
|
@ -1,65 +1,39 @@
|
||||||
/* Reset */
|
/* Reset */
|
||||||
*,
|
*, *::before, *::after {
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
img, picture, video, canvas, svg {
|
||||||
img,
|
|
||||||
picture,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
svg {
|
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
input, button, textarea, select {
|
||||||
input,
|
|
||||||
button,
|
|
||||||
textarea,
|
|
||||||
select {
|
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
p, h1, h2, h3, h4, h5, h6 {
|
||||||
p,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
#root, #__next {
|
||||||
#root,
|
|
||||||
#__next {
|
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
:root {
|
:root {
|
||||||
/* Colors */
|
/* Colors */
|
||||||
--light: #ffffff;
|
--light : #fff;
|
||||||
--light-dust: #fefef9;
|
--light-dust : #f6f6f6;
|
||||||
--dust: #f4f1ef;
|
--dust : #f1f1f1;
|
||||||
--medium-dust : #dadada;
|
--medium-dust : #dadada;
|
||||||
--dark-dust : #bfbfbf;
|
--dark-dust : #bfbfbf;
|
||||||
--fade : #878787;
|
--fade : #878787;
|
||||||
--twilight : #4a4a4a;
|
--twilight : #4a4a4a;
|
||||||
--dark-twilight: #2f2f2f;
|
|
||||||
--dark : #2a2a2a;
|
--dark : #2a2a2a;
|
||||||
--dark-dark: #121212;
|
|
||||||
--light-custard: #eee7d5;
|
|
||||||
--custard : #f0dcac;
|
--custard : #f0dcac;
|
||||||
--dark-custard: #d4c7a9;
|
|
||||||
--water : #a8f3f4;
|
--water : #a8f3f4;
|
||||||
--splash : #4b3aba;
|
--splash : #4b3aba;
|
||||||
|
|
||||||
|
@ -77,38 +51,19 @@ h6 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html, body {
|
||||||
body {
|
|
||||||
height : 100%;
|
height : 100%;
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1, h2, h3, h4, h5, h6 {
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-weight : 600;
|
font-weight : 600;
|
||||||
color : var(--twilight);
|
color : var(--twilight);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
<<<<<<< HEAD font-weight: 500;
|
font-weight : 500;
|
||||||
color : var(--splash);
|
color : var(--splash);
|
||||||
text-decoration : none;
|
text-decoration : none;
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: var(--double-space) 0;
|
|
||||||
height: 0;
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 1px solid var(--dark-custard);
|
|
||||||
=======font-weight: 500;
|
|
||||||
color: var(--splash);
|
|
||||||
text-decoration: none;
|
|
||||||
>>>>>>>bdc2d8717cbcab1795b1b2dc4f08f83242e4a4ca
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -133,36 +88,21 @@ header>h1 {
|
||||||
font-size : 1.44em;
|
font-size : 1.44em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#switch-icon {
|
header > a.logout {
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin: 0 var(--space);
|
|
||||||
top: -2px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#switch-icon #layer1 path {
|
|
||||||
fill: var(--twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
header>div>a#logout {
|
|
||||||
padding : 6px 12px;
|
padding : 6px 12px;
|
||||||
border-radius : 6px;
|
border-radius : 6px;
|
||||||
background : var(--twilight);
|
background : var(--twilight);
|
||||||
text-decoration : none;
|
text-decoration : none;
|
||||||
color : var(--dust);
|
color : var(--dust);
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header>div>a#logout:hover {
|
header > a.logout:hover {
|
||||||
background : var(--splash);
|
background : var(--splash);
|
||||||
color : var(--light);
|
color : var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
padding: 0 var(--outer-space) var(--double-space) var(--outer-space);
|
padding : var(--double-space) var(--outer-space);
|
||||||
background : var(--light);
|
background : var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +141,7 @@ aside>div>dl>dt {
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display : block;
|
display : block;
|
||||||
border-bottom: 1px solid var(--dark-custard);
|
border-bottom : 1px solid var(--dark-dust);
|
||||||
background : var(--light);
|
background : var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,31 +188,26 @@ article {
|
||||||
padding : var(--double-space) var(--outer-space);
|
padding : var(--double-space) var(--outer-space);
|
||||||
}
|
}
|
||||||
|
|
||||||
article>div.content-view {
|
article > div {
|
||||||
background : var(--dust);
|
background : var(--dust);
|
||||||
padding : var(--double-space);
|
padding : var(--double-space);
|
||||||
margin-bottom: var(--space);
|
margin-bottom : var(--double-space);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.content-view > h2 {
|
div.content-view > h2 {
|
||||||
margin: 0 0 var(--space) 0;
|
margin : 0 0 var(--double-space) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.services {
|
div.services {
|
||||||
display : flex;
|
display : flex;
|
||||||
justify-content: space-between;
|
justify-content : start;
|
||||||
gap : var(--double-space);
|
gap : var(--double-space);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.services > div,
|
div.services > div,
|
||||||
div.infobox {
|
div.infobox {
|
||||||
<<<<<<< HEAD background: var(--light);
|
background : var(--light);
|
||||||
padding: var(--double-space);
|
|
||||||
border-radius: 6px;
|
|
||||||
flex: 240px;
|
|
||||||
max-width: 420px;
|
|
||||||
=======background: var(--light);
|
|
||||||
padding : var(--double-space);
|
padding : var(--double-space);
|
||||||
border-radius : 6px;
|
border-radius : 6px;
|
||||||
flex : 240px;
|
flex : 240px;
|
||||||
|
@ -380,166 +315,6 @@ form>div>input[type="password"] {
|
||||||
color : var(--twilight);
|
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 {
|
|
||||||
display: block;
|
|
||||||
color: var(--light);
|
|
||||||
background: var(--splash);
|
|
||||||
padding: var(--space) var(--double-space);
|
|
||||||
border-radius: var(--quarter-space);
|
|
||||||
opacity: 0.85;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
border: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
transition: opacity 0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.small {
|
|
||||||
font-size: 0.78em;
|
|
||||||
padding: var(--half-space) var(--space);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.services>div>a:hover,
|
|
||||||
a.button:hover,
|
|
||||||
button:hover {
|
|
||||||
opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
background: var(--twilight);
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
article table {
|
|
||||||
width: 100%;
|
|
||||||
border-spacing: 0;
|
|
||||||
margin: var(--space) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article table thead th {
|
|
||||||
background: var(--twilight);
|
|
||||||
color: var(--medium-dust);
|
|
||||||
}
|
|
||||||
|
|
||||||
article table thead th a {
|
|
||||||
color: var(--light);
|
|
||||||
}
|
|
||||||
|
|
||||||
article table thead th:first-child {
|
|
||||||
border-radius: var(--half-space) 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article table thead th:last-child {
|
|
||||||
border-radius: 0 var(--half-space) 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
article table tbody {
|
|
||||||
background: var(--light-dust);
|
|
||||||
}
|
|
||||||
|
|
||||||
article table tbody tr:nth-child(odd) {
|
|
||||||
background: var(--light-custard);
|
|
||||||
}
|
|
||||||
|
|
||||||
article table tbody tr:nth-child(odd) td {
|
|
||||||
border-top: 1px solid var(--dark-custard);
|
|
||||||
border-bottom: 1px solid var(--dark-custard);
|
|
||||||
}
|
|
||||||
|
|
||||||
article table tbody tr:last-child td {
|
|
||||||
border-bottom: var(--half-space) solid var(--twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
article table thead th,
|
|
||||||
article table tbody td {
|
|
||||||
padding: var(--space);
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
article table#user_email_table tbody tr td:first-child {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
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"],
|
|
||||||
input[type="email"] {
|
|
||||||
border: 2px solid var(--twilight);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 8px;
|
|
||||||
background: var(--light-dust);
|
|
||||||
width: 100%;
|
|
||||||
color: var(--dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
form fieldset {
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.buttonHolder button {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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 {
|
#loginbox > div.login {
|
||||||
background : var(--light-dust);
|
background : var(--light-dust);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -600,48 +375,10 @@ span.time_remaining {
|
||||||
display : flex;
|
display : flex;
|
||||||
justify-content : center;
|
justify-content : center;
|
||||||
list-style : none;
|
list-style : none;
|
||||||
padding: var(--half-space) 0;
|
padding : 0;
|
||||||
margin : 0;
|
margin : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination > li {
|
.pagination > li {
|
||||||
margin: 0 var(--half-space);
|
margin : 0 6px;
|
||||||
}
|
|
||||||
|
|
||||||
.pagination>li:first-child {
|
|
||||||
margin-right: var(--double-space);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination>li:last-child {
|
|
||||||
margin-left: var(--double-space);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination .page-item {
|
|
||||||
border: 1px solid var(--fade);
|
|
||||||
padding: var(--quarter-space) var(--half-space);
|
|
||||||
border-radius: var(--half-space);
|
|
||||||
background: var(--light-dust);
|
|
||||||
font-size: 0.78em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination .page-link {
|
|
||||||
padding: var(--half-space);
|
|
||||||
color: var(--twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination .page-item.active {
|
|
||||||
background: var(--twilight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination .page-item.active .page-link {
|
|
||||||
color: var(--light-dust);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination .page-item.disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination .page-item.disabled .page-link {
|
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
7
src/project/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
src/project/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/project/static/js/bootstrap.bundle.min.js.map
Normal file
1
src/project/static/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -2,26 +2,29 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block head_title %}{% trans "Email Addresses" %}{% endblock %}
|
{% block head_title %}{% trans "E-mail Addresses" %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="content-view">
|
|
||||||
<h2>{% trans "Email Addresses" %}</h2>
|
|
||||||
<p>{% trans 'The following email addresses are associated with your account:' %}</p>
|
|
||||||
|
|
||||||
<hr />
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4>{% trans "E-mail Addresses" %}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
|
||||||
{% if user.emailaddress_set.all %}
|
{% if user.emailaddress_set.all %}
|
||||||
<form action="{% url 'account_email' %}" class="email_list" method="post">
|
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
||||||
|
<form action="{% url 'account_email' %}" class="email_list"
|
||||||
|
method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset class="blockLabels">
|
<fieldset class="blockLabels">
|
||||||
<div class="buttonHolder">
|
|
||||||
<button class="small" name="action_add" style="float:right">Add Email …</button>
|
<table class="table">
|
||||||
<button class="small" disabled type="submit" id="action_primary" name="action_primary">Make Primary</button>
|
|
||||||
<button class="small" type="submit" name="action_send">Re-send Verification</button>
|
|
||||||
<button class="small" type="submit" name="action_remove">Remove</button>
|
|
||||||
</div>
|
|
||||||
<table class="table" id="user_email_table">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
@ -31,8 +34,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
{% for emailaddress in user.emailaddress_set.all %}
|
{% for emailaddress in user.emailaddress_set.all %}
|
||||||
<tr>
|
<tr class="ctrlHolder">
|
||||||
<label for="email_radio_{{ forloop.counter }}"
|
<label for="email_radio_{{ forloop.counter }}"
|
||||||
class="{% if emailaddress.primary %}primary_email{% endif %}">
|
class="{% if emailaddress.primary %}primary_email{% endif %}">
|
||||||
<td>
|
<td>
|
||||||
|
@ -44,7 +48,6 @@
|
||||||
{% if emailaddress.primary or user.emailaddress_set.count == 1 %}
|
{% if emailaddress.primary or user.emailaddress_set.count == 1 %}
|
||||||
checked="checked"
|
checked="checked"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
class="{% if emailaddress.primary %}primary_email{% endif %}"
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -52,15 +55,14 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if emailaddress.verified %}
|
{% if emailaddress.verified %}
|
||||||
<span class="label label-success">{% trans "Verified" %}</span>
|
<span class="label label-success">Verified</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-danger">{% trans "Unverified" %}</span>
|
<span class="label label-danger">Unverified</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if emailaddress.primary %}
|
{% if emailaddress.primary %}
|
||||||
<span class="label label-primary">{% trans "Primary" %}</span>
|
<span class="label label-primary">Primary</span>{% endif %}
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</label>
|
</label>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -68,6 +70,19 @@
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="buttonHolder">
|
||||||
|
<button class="btn btn-success" type="submit"
|
||||||
|
name="action_primary">Make Primary
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" type="submit"
|
||||||
|
name="action_send">Re-send Verification
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" type="submit"
|
||||||
|
name="action_remove">Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -76,10 +91,13 @@
|
||||||
{% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
|
{% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!--
|
</div>
|
||||||
<hr />
|
</div>
|
||||||
|
|
||||||
<h3>{% trans "Add E-mail" %}</h3>
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4>{% trans "Add E-mail" %}</h4>
|
||||||
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form method="post" action="{% url 'account_email' %}"
|
<form method="post" action="{% url 'account_email' %}"
|
||||||
class="add_email">
|
class="add_email">
|
||||||
|
@ -90,7 +108,9 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
-->
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -104,15 +124,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let radio_actions = document.getElementsByName('email');
|
|
||||||
if (radio_actions.length) {
|
|
||||||
for (radio of radio_actions) {
|
|
||||||
radio.addEventListener("change", function (e) {
|
|
||||||
document.getElementById('action_primary').disabled = e.target.classList.contains('primary_email')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,13 @@
|
||||||
<div>
|
<div>
|
||||||
<label for="id_username"
|
<label for="id_username"
|
||||||
class="visually-hidden">
|
class="visually-hidden">
|
||||||
{% trans "Email" %}
|
{% trans "E-mail" %}
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="id_username"
|
id="id_username"
|
||||||
name="login"
|
name="login"
|
||||||
class="form-control mb-lg-2"
|
class="form-control mb-lg-2"
|
||||||
placeholder="{% trans "Email" %}"
|
placeholder="{% trans "E-mail" %}"
|
||||||
required
|
required
|
||||||
autofocus>
|
autofocus>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
required>
|
required>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button type="submit">{% trans "Login" %}</button>
|
<button type="submit">{% trans "Sign in" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
<div class="signup">
|
<div class="signup">
|
||||||
<img src="https://data.coop/static/img/logo_da.svg" alt="data.coop logo">
|
<img src="https://data.coop/static/img/logo_da.svg" alt="data.coop logo">
|
||||||
<div class="new_here">
|
<div class="new_here">
|
||||||
<h2>{% trans "Are you new here?" %}</h2>
|
<h2> Are you new here? </h2>
|
||||||
<a class="button" href="{% url "account_signup" %}">{% trans "Become a member" %}</a>
|
<a class="button" href="{% url "account_signup" %}">{% trans "Become a member" %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,39 +10,11 @@
|
||||||
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
<title>{% block head_title %}{% endblock %} – {{ site.name }}</title>
|
||||||
<link rel="stylesheet" href="{% static "fonts/inter.css" %}">
|
<link rel="stylesheet" href="{% static "fonts/inter.css" %}">
|
||||||
<link rel="stylesheet" href="{% static "css/style.css" %}">
|
<link rel="stylesheet" href="{% static "css/style.css" %}">
|
||||||
<link rel="stylesheet" href="{% static "css/dark-style.css" %}">
|
|
||||||
<script>
|
|
||||||
const savedTheme = localStorage.getItem('theme');
|
|
||||||
|
|
||||||
if (savedTheme === "dark" || (savedTheme == null && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
|
|
||||||
document.querySelector('html').classList.add('dark');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1> data.coop membersystem </h1>
|
<h1> data.coop membersystem </h1>
|
||||||
<div>
|
<a class="logout" href="{% url "account_logout" %}">Log out</a>
|
||||||
<a id="theme-switcher" title="Switch theme">
|
|
||||||
<svg id="switch-icon"
|
|
||||||
width="20.00033mm"
|
|
||||||
height="20.000334mm"
|
|
||||||
viewBox="0 0 20.00033 20.000334"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<g id="layer1">
|
|
||||||
<path id="path1"
|
|
||||||
style="fill-opacity:1;stroke:none;stroke-width:0.697782;stroke-linecap:round"
|
|
||||||
d="M 9.999906,20.000334 A 10,10 0 0 0 20.000329,9.999911 10,10 0 0 0 9.999906,0 10,10 0 0 0 0,9.999911 10,10 0 0 0 9.999906,20.000334 Z m 0,-2.00039 V 1.99988 a 8,8 0 0 1 8.000023,8.000031 8,8 0 0 1 -8.000023,8.000033 z" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a id="logout" href="{% url "account_logout" %}">Log out</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<aside>
|
<aside>
|
||||||
|
@ -106,14 +78,5 @@
|
||||||
<footer>
|
<footer>
|
||||||
data.coop membersystem version 0.0.1
|
data.coop membersystem version 0.0.1
|
||||||
</footer>
|
</footer>
|
||||||
<script>
|
|
||||||
const themeSwitcher = document.getElementById('theme-switcher');
|
|
||||||
themeSwitcher.addEventListener('click', function() {
|
|
||||||
themeSwitcher.classList.toggle('active')
|
|
||||||
let isDark = document.querySelector('html').classList.toggle('dark');
|
|
||||||
|
|
||||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""URLs for the membersystem."""
|
"""URLs for the membersystem"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include
|
from django.urls import include
|
||||||
|
@ -15,5 +15,4 @@ if settings.DEBUG:
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("__debug__/", include("debug_toolbar.urls")),
|
path("__debug__/", include("debug_toolbar.urls")),
|
||||||
path("__reload__/", include("django_browser_reload.urls")),
|
path("__reload__/", include("django_browser_reload.urls")),
|
||||||
*urlpatterns,
|
] + urlpatterns
|
||||||
]
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django_view_decorator import view
|
from django_view_decorator import view
|
||||||
|
|
||||||
from utils.view_utils import render
|
from utils.view_utils import render
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""WSGI config for membersystem project.
|
"""
|
||||||
|
WSGI config for membersystem project.
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ register = template.Library()
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def active_path(context, path_name, class_name) -> str | None:
|
def active_path(context, path_name, class_name) -> str | None:
|
||||||
"""Return the given class name if the current path matches the given path name."""
|
"""Return the given class name if the current path matches the given path name."""
|
||||||
|
|
||||||
path = reverse(path_name)
|
path = reverse(path_name)
|
||||||
request_path = context.get("request").path
|
request_path = context.get("request").path
|
||||||
|
|
||||||
|
@ -18,4 +19,3 @@ def active_path(context, path_name, class_name) -> str | None:
|
||||||
|
|
||||||
if is_path or is_base_path:
|
if is_path or is_base_path:
|
||||||
return class_name
|
return class_name
|
||||||
return None
|
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
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 django.urls import reverse
|
||||||
from zen_queries import queries_disabled
|
from zen_queries import queries_disabled
|
||||||
from zen_queries import render as zen_queries_render
|
from zen_queries import render as zen_queries_render
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from django.db.models import Model
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Row:
|
class Row:
|
||||||
"""A row in a table."""
|
"""
|
||||||
|
A row in a table.
|
||||||
|
"""
|
||||||
|
|
||||||
data: dict[str, str]
|
data: dict[str, str]
|
||||||
actions: list[dict[str, str]]
|
actions: list[dict[str, str]]
|
||||||
|
@ -26,14 +25,18 @@ class Row:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RowAction:
|
class RowAction:
|
||||||
"""An action that can be performed on a row in a table."""
|
"""
|
||||||
|
An action that can be performed on a row in a table.
|
||||||
|
"""
|
||||||
|
|
||||||
label: str
|
label: str
|
||||||
url_name: str
|
url_name: str
|
||||||
url_kwargs: dict[str, str]
|
url_kwargs: dict[str, str]
|
||||||
|
|
||||||
def render(self, obj) -> dict[str, str]:
|
def render(self, obj) -> dict[str, str]:
|
||||||
"""Render the action as a dictionary for the given object."""
|
"""
|
||||||
|
Render the action as a dictionary for the given object.
|
||||||
|
"""
|
||||||
url = reverse(
|
url = reverse(
|
||||||
self.url_name,
|
self.url_name,
|
||||||
kwargs={key: getattr(obj, value) for key, value in self.url_kwargs.items()},
|
kwargs={key: getattr(obj, value) for key, value in self.url_kwargs.items()},
|
||||||
|
@ -47,11 +50,14 @@ def render_list(
|
||||||
entity_name_plural: str,
|
entity_name_plural: str,
|
||||||
objects: list["Model"],
|
objects: list["Model"],
|
||||||
columns: list[tuple[str, str]],
|
columns: list[tuple[str, str]],
|
||||||
row_actions: list[RowAction] | None = None,
|
row_actions: list[RowAction] = None,
|
||||||
list_actions: list[tuple[str, str]] | None = None,
|
list_actions: list[tuple[str, str]] = None,
|
||||||
paginate_by: int | None = None,
|
paginate_by: int = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
"""Render a list of objects with a table."""
|
"""
|
||||||
|
Render a list of objects with a table.
|
||||||
|
"""
|
||||||
|
|
||||||
# TODO: List actions
|
# TODO: List actions
|
||||||
|
|
||||||
total_count = len(objects)
|
total_count = len(objects)
|
||||||
|
@ -101,18 +107,17 @@ def render_list(
|
||||||
|
|
||||||
|
|
||||||
def base_context(request: HttpRequest) -> dict[str, Any]:
|
def base_context(request: HttpRequest) -> dict[str, Any]:
|
||||||
"""Return a base context for all views."""
|
"""
|
||||||
|
Return a base context for all views.
|
||||||
|
"""
|
||||||
return {"site": get_current_site(request)}
|
return {"site": get_current_site(request)}
|
||||||
|
|
||||||
|
|
||||||
def render(request, template_name, context=None):
|
def render(request, template_name, context=None):
|
||||||
"""Render a template with a base context."""
|
"""
|
||||||
|
Render a template with a base context.
|
||||||
|
"""
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
context = base_context(request) | 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)
|
return zen_queries_render(request, template_name, context)
|
||||||
|
|
Loading…
Reference in a new issue