Set up members app with models, migrations, permissions, and rules integration; updated dependencies and settings

This commit is contained in:
2026-01-03 13:00:06 +01:00
parent 63ad906557
commit a38a813865
7 changed files with 129 additions and 31 deletions

View File

@@ -45,8 +45,9 @@ INSTALLED_APPS = [
"django.contrib.staticfiles", "django.contrib.staticfiles",
"constance", "constance",
"tailwind", "tailwind",
"theme.apps.ThemeConfig", "rules.apps.AutodiscoverRulesConfig",
"members.apps.MembersConfig", # Tailwind theme app "theme.apps.ThemeConfig", # Tailwind theme app
"members.apps.MembersConfig",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -83,9 +84,7 @@ WSGI_APPLICATION = "TeamForge.wsgi.application"
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases # https://docs.djangoproject.com/en/6.0/ref/settings/#databases
DATABASES = { DATABASES = {
"default": config( "default": config("DJANGO_DATABASE_URL", default="sqlite:///db.sqlite3", cast=db_url),
"DJANGO_DATABASE_URL", default="sqlite:///db.sqlite3", cast=db_url
),
} }
@@ -132,31 +131,11 @@ MEDIA_ROOT = BASE_DIR / "media"
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
CONSTANCE_CONFIG = { CONSTANCE_CONFIG = {
"TF_CLUB_NAME": ( "TF_CLUB_NAME": (config("TF_CLUB_NAME", default="TeamForge", cast=str), "Club Name", str),
config("TF_CLUB_NAME", default="TeamForge", cast=str), "TF_CLUB_HOME": (config("TF_CLUB_HOME", default="TeamForge", cast=str), "Club Location", str),
"Club Name", "TF_DEFAULT_SEASON_MONTH": (config("TF_DEFAULT_SEASON_MONTH", default=8, cast=int), "Default season start month", int),
str, "TF_DEFAULT_SEASON_DAY": (config("TF_DEFAULT_SEASON_DAY", default=1, cast=int), "Default season start day", int),
), "TF_DEFAULT_SEASON_DURATION": (config("TF_DEFAULT_SEASON_DURATION", default="1y", cast=str), "Default season duration", str),
"TF_CLUB_HOME": (
config("TF_CLUB_HOME", default="TeamForge", cast=str),
"Club Location",
str,
),
"TF_DEFAULT_SEASON_MONTH": (
config("TF_DEFAULT_SEASON_MONTH", default=8, cast=int),
"Default season start month",
int,
),
"TF_DEFAULT_SEASON_DAY": (
config("TF_DEFAULT_SEASON_DAY", default=1, cast=int),
"Default season start day",
int,
),
"TF_DEFAULT_SEASON_DURATION": (
config("TF_DEFAULT_SEASON_DURATION", default="1y", cast=str),
"Default season duration",
str,
),
} }
PHONENUMBER_DEFAULT_FORMAT = "INTERNATIONAL" PHONENUMBER_DEFAULT_FORMAT = "INTERNATIONAL"

View File

@@ -0,0 +1,48 @@
# Generated by Django 6.0 on 2026-01-03 11:53
import django.db.models.deletion
import rules.contrib.models
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Member",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="member",
to=settings.AUTH_USER_MODEL,
verbose_name="user",
),
),
],
options={
"verbose_name": "member",
"verbose_name_plural": "members",
"ordering": ["user__last_name", "user__first_name"],
"permissions": [("member_manager", "Can manage members")],
},
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 6.0 on 2026-01-03 11:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("members", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="member",
name="family",
field=models.ManyToManyField(
blank=True,
related_name="family",
to="members.member",
verbose_name="family",
),
),
]

View File

@@ -1,3 +1,27 @@
from django.conf import settings
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _
from rules import is_superuser
from rules.contrib.models import RulesModel
# Create your models here. from members.rules import is_member_manager
class Member(RulesModel):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="member", verbose_name=_("user"))
family = models.ManyToManyField("self", symmetrical=True, blank=True, verbose_name=_("family"))
class Meta:
verbose_name = _("member")
verbose_name_plural = _("members")
ordering = ["user__last_name", "user__first_name"]
permissions = [("member_manager", _("Can manage members"))]
rules_permissions = {
"add": is_superuser | is_member_manager,
"change": is_superuser | is_member_manager,
"delete": is_superuser | is_member_manager,
"view": is_superuser | is_member_manager,
}
def __str__(self):
return self.user.get_full_name()

9
members/rules.py Normal file
View File

@@ -0,0 +1,9 @@
from typing import Optional
import rules
from django.contrib.auth.models import AbstractUser
@rules.predicate
def is_member_manager(user: Optional[AbstractUser]) -> bool:
return user.has_perm('members.member_manager')

View File

@@ -12,6 +12,7 @@ dependencies = [
"django-tailwind[cookiecutter,honcho]>=4.4.2", "django-tailwind[cookiecutter,honcho]>=4.4.2",
"psycopg2-binary>=2.9.11", "psycopg2-binary>=2.9.11",
"python-decouple>=3.8", "python-decouple>=3.8",
"rules>=3.5",
] ]
[dependency-groups] [dependency-groups]
@@ -19,3 +20,6 @@ dev = [
"coverage>=7.13.1", "coverage>=7.13.1",
"ruff>=0.14.10", "ruff>=0.14.10",
] ]
[tool.ruff]
line-length = 250

11
uv.lock generated
View File

@@ -567,6 +567,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
] ]
[[package]]
name = "rules"
version = "3.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f7/36/918cf4cc9fd0e38bb9310b2d1a13ae6ebb2b5732d56e7de6feb4a992a6ed/rules-3.5.tar.gz", hash = "sha256:f01336218f4561bab95f53672d22418b4168baea271423d50d9e8490d64cb27a", size = 55504, upload-time = "2024-09-02T16:01:46.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ea/33/16213dd62ca8ce8749985318a966ac1300ab55c977b2d66632a45b405c99/rules-3.5-py2.py3-none-any.whl", hash = "sha256:0f00fc9ee448b3f82e9aff9334ab0c56c76dce4dfa14f1598f57969f1022acc0", size = 25658, upload-time = "2024-09-02T16:01:44.844Z" },
]
[[package]] [[package]]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"
@@ -598,6 +607,7 @@ dependencies = [
{ name = "django-tailwind", extra = ["cookiecutter", "honcho"] }, { name = "django-tailwind", extra = ["cookiecutter", "honcho"] },
{ name = "psycopg2-binary" }, { name = "psycopg2-binary" },
{ name = "python-decouple" }, { name = "python-decouple" },
{ name = "rules" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -616,6 +626,7 @@ requires-dist = [
{ name = "django-tailwind", extras = ["cookiecutter", "honcho"], specifier = ">=4.4.2" }, { name = "django-tailwind", extras = ["cookiecutter", "honcho"], specifier = ">=4.4.2" },
{ name = "psycopg2-binary", specifier = ">=2.9.11" }, { name = "psycopg2-binary", specifier = ">=2.9.11" },
{ name = "python-decouple", specifier = ">=3.8" }, { name = "python-decouple", specifier = ">=3.8" },
{ name = "rules", specifier = ">=3.5" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]