diff --git a/activities/models/post_attachment.py b/activities/models/post_attachment.py index ee77d29..6ccea08 100644 --- a/activities/models/post_attachment.py +++ b/activities/models/post_attachment.py @@ -1,5 +1,8 @@ +from functools import partial + from django.db import models +from core.uploads import upload_namer from stator.models import State, StateField, StateGraph, StatorModel @@ -31,7 +34,9 @@ class PostAttachment(StatorModel): mimetype = models.CharField(max_length=200) # File may not be populated if it's remote and not cached on our side yet - file = models.FileField(upload_to="attachments/%Y/%m/%d/", null=True, blank=True) + file = models.FileField( + upload_to=partial(upload_namer, "attachments"), null=True, blank=True + ) remote_url = models.CharField(max_length=500, null=True, blank=True) diff --git a/activities/views/timelines.py b/activities/views/timelines.py index 38f9331..65b6c49 100644 --- a/activities/views/timelines.py +++ b/activities/views/timelines.py @@ -57,7 +57,6 @@ class Home(FormView): return redirect(".") -@method_decorator(identity_required, name="dispatch") class Local(TemplateView): template_name = "activities/local.html" diff --git a/core/models/config.py b/core/models/config.py index 19ac85d..021bf67 100644 --- a/core/models/config.py +++ b/core/models/config.py @@ -1,9 +1,21 @@ +from functools import partial from typing import ClassVar import pydantic +from django.core.files import File from django.db import models +from django.templatetags.static import static from django.utils.functional import classproperty +from core.uploads import upload_namer +from takahe import __version__ + + +class UploadedImage(str): + """ + Type used to indicate a setting is an image + """ + class Config(models.Model): """ @@ -31,7 +43,11 @@ class Config(models.Model): ) json = models.JSONField(blank=True, null=True) - image = models.ImageField(blank=True, null=True, upload_to="config/%Y/%m/%d/") + image = models.ImageField( + blank=True, + null=True, + upload_to=partial(upload_namer, "config"), + ) class Meta: unique_together = [ @@ -46,60 +62,110 @@ class Config(models.Model): system: ClassVar["Config.ConfigOptions"] # type: ignore @classmethod - def load_system(cls): + def load_values(cls, options_class, filters): """ - Load all of the system config options and return an object with them + Loads config options and returns 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) + for config in cls.objects.filter(**filters): + values[config.key] = config.image.url if config.image else config.json + if values[config.key] is None: + del values[config.key] + values["version"] = __version__ + return options_class(**values) + + @classmethod + def load_system(cls): + """ + Loads the system config options object + """ + return cls.load_values( + cls.SystemOptions, + {"identity__isnull": True, "user__isnull": True}, + ) @classmethod def load_user(cls, user): """ - Load all of the user config options and return an object with them + Loads a user config options object """ - values = {} - for config in cls.objects.filter(user=user, identity__isnull=True): - values[config.key] = config.image or config.json - return cls.UserOptions(**values) + return cls.load_values( + cls.SystemOptions, + {"identity__isnull": True, "user": user}, + ) @classmethod def load_identity(cls, identity): """ - Load all of the identity config options and return an object with them + Loads a user config options object """ - values = {} - for config in cls.objects.filter(user__isnull=True, identity=identity): - values[config.key] = config.image or config.json - return cls.IdentityOptions(**values) + return cls.load_values( + cls.IdentityOptions, + {"identity": identity, "user__isnull": True}, + ) + + @classmethod + def set_value(cls, key, value, options_class, filters): + config_field = options_class.__fields__[key] + if isinstance(value, File): + if config_field.type_ is not UploadedImage: + raise ValueError(f"Cannot save file to {key} of type: {type(value)}") + cls.objects.update_or_create( + key=key, + defaults={"json": None, "image": value}, + **filters, + ) + elif value is None: + cls.objects.filter(key=key, **filters).delete() + else: + if not isinstance(value, config_field.type_): + raise ValueError(f"Invalid type for {key}: {type(value)}") + if value == config_field.default: + cls.objects.filter(key=key, **filters).delete() + else: + cls.objects.update_or_create( + key=key, + defaults={"json": value}, + **filters, + ) @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}, + cls.set_value( + key, + value, + cls.SystemOptions, + {"identity__isnull": True, "user__isnull": True}, + ) + + @classmethod + def set_user(cls, user, key, value): + cls.set_value( + key, + value, + cls.UserOptions, + {"identity__isnull": True, "user": user}, ) @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}, + cls.set_value( + key, + value, + cls.IdentityOptions, + {"identity": identity, "user__isnull": True}, ) class SystemOptions(pydantic.BaseModel): + version: str = __version__ + site_name: str = "takahē" highlight_color: str = "#449c8c" + site_about: str = "