From 44af0d4c59eed1c3715e9044e75c159cfddf54cc Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Wed, 16 Nov 2022 17:23:46 -0700 Subject: Add start of a settings (config) system --- core/admin.py | 8 +++ core/config.py | 20 -------- core/context.py | 7 ++- core/migrations/0001_initial.py | 63 +++++++++++++++++++++++ core/migrations/__init__.py | 0 core/models/__init__.py | 1 + core/models/config.py | 111 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 188 insertions(+), 22 deletions(-) create mode 100644 core/admin.py delete mode 100644 core/config.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/__init__.py create mode 100644 core/models/__init__.py create mode 100644 core/models/config.py (limited to 'core') diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..e4a6ad0 --- /dev/null +++ b/core/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from core.models import Config + + +@admin.register(Config) +class ConfigAdmin(admin.ModelAdmin): + list_display = ["id", "key", "user", "identity"] diff --git a/core/config.py b/core/config.py deleted file mode 100644 index b9f6878..0000000 --- a/core/config.py +++ /dev/null @@ -1,20 +0,0 @@ -import pydantic - - -class Config(pydantic.BaseModel): - - # Basic configuration options - site_name: str = "takahē" - identity_max_age: int = 24 * 60 * 60 - - # Cached ORM object storage - __singleton__ = None - - class Config: - env_prefix = "takahe_" - - @classmethod - def load(cls) -> "Config": - if cls.__singleton__ is None: - cls.__singleton__ = cls() - return cls.__singleton__ diff --git a/core/context.py b/core/context.py index 17617b9..4346cbb 100644 --- a/core/context.py +++ b/core/context.py @@ -1,7 +1,10 @@ -from core.config import Config +from core.models import Config def config_context(request): return { - "config": Config.load(), + "config": Config.load_system(), + "config_identity": ( + Config.load_identity(request.identity) if request.identity else None + ), } diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..2c4731f --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 4.1.3 on 2022-11-16 21:23 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("users", "0002_identity_public_key_id"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Config", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("key", models.CharField(max_length=500)), + ("json", models.JSONField(blank=True, null=True)), + ( + "image", + models.ImageField( + blank=True, null=True, upload_to="config/%Y/%m/%d/" + ), + ), + ( + "identity", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="configs", + to="users.identity", + ), + ), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="configs", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "unique_together": {("key", "user", "identity")}, + }, + ), + ] diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models/__init__.py b/core/models/__init__.py new file mode 100644 index 0000000..87bfe4e --- /dev/null +++ b/core/models/__init__.py @@ -0,0 +1 @@ +from .config import Config # noqa diff --git a/core/models/config.py b/core/models/config.py new file mode 100644 index 0000000..8a2e40b --- /dev/null +++ b/core/models/config.py @@ -0,0 +1,111 @@ +from typing import ClassVar + +import pydantic +from django.db import models +from django.utils.functional import classproperty + + +class Config(models.Model): + """ + A configuration setting for either the server or a specific user or identity. + + The possible options and their defaults are defined at the bottom of the file. + """ + + key = models.CharField(max_length=500) + + user = models.ForeignKey( + "users.user", + blank=True, + null=True, + related_name="configs", + on_delete=models.CASCADE, + ) + + identity = models.ForeignKey( + "users.identity", + blank=True, + null=True, + related_name="configs", + on_delete=models.CASCADE, + ) + + json = models.JSONField(blank=True, null=True) + image = models.ImageField(blank=True, null=True, upload_to="config/%Y/%m/%d/") + + class Meta: + unique_together = [ + ("key", "user", "identity"), + ] + + @classproperty + def system(cls): + cls.system = cls.load_system() + return cls.system + + system: ClassVar["Config.ConfigOptions"] # type: ignore + + @classmethod + def load_system(cls): + """ + Load all of the system config options and return an object with them + """ + values = {} + for config in cls.objects.filter(user__isnull=True, identity__isnull=True): + values[config.key] = config.image or config.json + return cls.SystemOptions(**values) + + @classmethod + def load_user(cls, user): + """ + Load all of the user config options and return an object with them + """ + values = {} + for config in cls.objects.filter(user=user, identity__isnull=True): + values[config.key] = config.image or config.json + return cls.UserOptions(**values) + + @classmethod + def load_identity(cls, identity): + """ + Load all of the identity config options and return an object with them + """ + values = {} + for config in cls.objects.filter(user__isnull=True, identity=identity): + values[config.key] = config.image or config.json + return cls.IdentityOptions(**values) + + @classmethod + def set_system(cls, key, value): + config_field = cls.SystemOptions.__fields__[key] + if not isinstance(value, config_field.type_): + raise ValueError(f"Invalid type for {key}: {type(value)}") + cls.objects.update_or_create( + key=key, + defaults={"json": value}, + ) + + @classmethod + def set_identity(cls, identity, key, value): + config_field = cls.IdentityOptions.__fields__[key] + if not isinstance(value, config_field.type_): + raise ValueError(f"Invalid type for {key}: {type(value)}") + cls.objects.update_or_create( + identity=identity, + key=key, + defaults={"json": value}, + ) + + class SystemOptions(pydantic.BaseModel): + + site_name: str = "takahē" + highlight_color: str = "#449c8c" + identity_max_age: int = 24 * 60 * 60 + + class UserOptions(pydantic.BaseModel): + + pass + + class IdentityOptions(pydantic.BaseModel): + + toot_mode: bool = False -- cgit v1.2.3