Skip to content

Commit

Permalink
Merge pull request #963 from maykinmedia/feature/2030-fonts-override
Browse files Browse the repository at this point in the history
[#2030] Add option to upload and use custom fonts
  • Loading branch information
alextreme authored Feb 13, 2024
2 parents 7f74686 + 735c79d commit 24228e0
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 17 deletions.
14 changes: 12 additions & 2 deletions src/open_inwoner/configurations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from ..utils.css import ALLOWED_PROPERTIES
from ..utils.fields import CSSEditorWidget
from ..utils.iteration import split
from .models import SiteConfiguration, SiteConfigurationPage
from .models import CustomFontSet, SiteConfiguration, SiteConfigurationPage


@admin.action(description=_("Delete selected websites"))
Expand Down Expand Up @@ -60,6 +60,9 @@ def delete_model(self, request, obj):
else:
super().delete_model(request, obj)

class Media:
css = {"all": ("css/admin/admin_overrides.css",)}


# re-register `Site` with our CustomSiteAdmin
admin.site.unregister(Site)
Expand All @@ -82,6 +85,13 @@ class SiteConfigurationPageInline(OrderedTabularInline):
autocomplete_fields = ("flatpage",)


class FontConfigurationInline(admin.StackedInline):
model = CustomFontSet
verbose_name = "Fonts"
min_num = 1
can_delete = False


class SiteConfigurationAdminForm(forms.ModelForm):
class Meta:
model = SiteConfiguration
Expand Down Expand Up @@ -284,7 +294,7 @@ class SiteConfigurationAdmin(OrderedInlineModelAdminMixin, SingletonModelAdmin):
),
(_("Social media"), {"fields": ("display_social",)}),
)
inlines = [SiteConfigurationPageInline]
inlines = [SiteConfigurationPageInline, FontConfigurationInline]
form = SiteConfigurationAdminForm

readonly_fields = [
Expand Down
8 changes: 8 additions & 0 deletions src/open_inwoner/configurations/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ class ColorTypeChoices(models.TextChoices):
class OpenIDDisplayChoices(models.TextChoices):
admin = "admin", _("Admin")
regular = "regular", _("Regular user")


class CustomFontName(models.TextChoices):
body = _("body_font_regular"), _("Text body font")
body_italic = _("body_font_italic"), _("Text body font italic")
body_bold = _("body_font_bold"), _("Text body font bold")
body_bold_italic = _("body_font_bold_italic"), _("Text body font bold italic")
heading = _("heading_font"), _("Heading font")
69 changes: 69 additions & 0 deletions src/open_inwoner/configurations/migrations/0058_customfontset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Generated by Django 3.2.23 on 2024-01-29 10:45

import django.core.validators
import django.db.models.deletion
from django.db import migrations, models

import open_inwoner.configurations.models
import open_inwoner.utils.files


class Migration(migrations.Migration):

dependencies = [
("configurations", "0057_siteconfiguration_theme_stylesheet"),
]

operations = [
migrations.CreateModel(
name="CustomFontSet",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"text_body_font",
open_inwoner.configurations.models.CustomFontField(
blank=True,
help_text="Upload text body font. TTF font types only.",
null=True,
storage=open_inwoner.utils.files.OverwriteStorage(),
upload_to=open_inwoner.configurations.models.CustomFontSet.update_filename_body,
validators=[
django.core.validators.FileExtensionValidator(["ttf"])
],
verbose_name="Text body font",
),
),
(
"heading_font",
open_inwoner.configurations.models.CustomFontField(
blank=True,
help_text="Upload heading font. TTF font types only.",
null=True,
storage=open_inwoner.utils.files.OverwriteStorage(),
upload_to=open_inwoner.configurations.models.CustomFontSet.update_filename_heading,
validators=[
django.core.validators.FileExtensionValidator(["ttf"])
],
verbose_name="Heading font",
),
),
(
"site_configuration",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="custom_fonts",
to="configurations.siteconfiguration",
verbose_name="Configuration",
),
),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generated by Django 3.2.23 on 2024-01-29 15:45

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("configurations", "0058_customfontset"),
("configurations", "0058_siteconfiguration_recipients_email_digest"),
]

operations = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Generated by Django 3.2.23 on 2024-02-08 13:26

import django.core.validators
from django.db import migrations
import open_inwoner.configurations.models
import open_inwoner.utils.files


class Migration(migrations.Migration):

dependencies = [
("configurations", "0059_merge_20240129_1645"),
]

operations = [
migrations.RemoveField(
model_name="customfontset",
name="text_body_font",
),
migrations.AddField(
model_name="customfontset",
name="body_font_bold",
field=open_inwoner.configurations.models.CustomFontField(
blank=True,
help_text="Upload bold text body font. TTF font types only.",
null=True,
storage=open_inwoner.utils.files.OverwriteStorage(),
upload_to=open_inwoner.configurations.models.CustomFontSet.update_filename_body_bold,
validators=[django.core.validators.FileExtensionValidator(["ttf"])],
verbose_name="Body font bold",
),
),
migrations.AddField(
model_name="customfontset",
name="body_font_bold_italic",
field=open_inwoner.configurations.models.CustomFontField(
blank=True,
help_text="Upload bold italic text body font. TTF font types only.",
null=True,
storage=open_inwoner.utils.files.OverwriteStorage(),
upload_to=open_inwoner.configurations.models.CustomFontSet.update_filename_body_bold_italic,
validators=[django.core.validators.FileExtensionValidator(["ttf"])],
verbose_name="Body font bold italic",
),
),
migrations.AddField(
model_name="customfontset",
name="body_font_italic",
field=open_inwoner.configurations.models.CustomFontField(
blank=True,
help_text="Upload italic text body font. TTF font types only.",
null=True,
storage=open_inwoner.utils.files.OverwriteStorage(),
upload_to=open_inwoner.configurations.models.CustomFontSet.update_filename_body_italic,
validators=[django.core.validators.FileExtensionValidator(["ttf"])],
verbose_name="Body font italic",
),
),
migrations.AddField(
model_name="customfontset",
name="body_font_regular",
field=open_inwoner.configurations.models.CustomFontField(
blank=True,
help_text="Upload regular text body font. TTF font types only.",
null=True,
storage=open_inwoner.utils.files.OverwriteStorage(),
upload_to=open_inwoner.configurations.models.CustomFontSet.update_filename_body,
validators=[django.core.validators.FileExtensionValidator(["ttf"])],
verbose_name="Body font regular",
),
),
]
133 changes: 131 additions & 2 deletions src/open_inwoner/configurations/models.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import os
from typing import Optional

from django.conf import settings
from django.contrib.flatpages.models import FlatPage
from django.core.validators import FileExtensionValidator
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _

from colorfield.fields import ColorField
from django_better_admin_arrayfield.models.fields import ArrayField
from filer.fields.file import FilerFileField
from filer.fields.image import FilerImageField
from ordered_model.models import OrderedModel, OrderedModelManager
from solo.models import SingletonModel

from ..utils.colors import hex_to_hsl
from ..utils.css import clean_stylesheet
from ..utils.fields import CSSField
from ..utils.files import OverwriteStorage
from ..utils.validators import FilerExactImageSizeValidator
from .choices import ColorTypeChoices, OpenIDDisplayChoices
from .choices import ColorTypeChoices, CustomFontName, OpenIDDisplayChoices
from .validators import validate_oidc_config


Expand Down Expand Up @@ -594,6 +598,131 @@ def get_help_text(self, request) -> Optional[str]:
return ""


class CustomFontField(models.FileField):
def __init__(self, file_name: str = "", **kwargs):
self.file_name = file_name
super().__init__(**kwargs)


class CustomFontSet(models.Model):
def update_filename(self, filename: str, new_name: str, path: str) -> str:
ext = filename.split(".")[1]
filename = f"{new_name}.{ext}"
return "{path}/{filename}".format(path=path, filename=filename)

def update_filename_body(self, filename: str) -> str:
return CustomFontSet.update_filename(
self,
filename,
new_name=CustomFontName.body,
path="custom_fonts/",
)

def update_filename_body_italic(self, filename: str) -> str:
return CustomFontSet.update_filename(
self,
filename,
new_name=CustomFontName.body_italic,
path="custom_fonts/",
)

def update_filename_body_bold(self, filename: str) -> str:
return CustomFontSet.update_filename(
self,
filename,
new_name=CustomFontName.body_bold,
path="custom_fonts/",
)

def update_filename_body_bold_italic(self, filename: str) -> str:
return CustomFontSet.update_filename(
self,
filename,
new_name=CustomFontName.body_bold_italic,
path="custom_fonts/",
)

def update_filename_heading(self, filename: str) -> str:
return CustomFontSet.update_filename(
self,
filename,
new_name=CustomFontName.heading,
path="custom_fonts/",
)

heading_font = CustomFontField(
verbose_name=_("Heading font"),
upload_to=update_filename_heading,
file_name=CustomFontName.heading,
storage=OverwriteStorage(),
validators=[FileExtensionValidator(["ttf"])],
blank=True,
null=True,
help_text=_("Upload heading font. TTF font types only."),
)
site_configuration = models.OneToOneField(
SiteConfiguration,
verbose_name=_("Configuration"),
related_name="custom_fonts",
on_delete=models.CASCADE,
)
body_font_regular = CustomFontField(
verbose_name=_("Body font regular"),
upload_to=update_filename_body,
file_name=CustomFontName.body,
storage=OverwriteStorage(),
validators=[FileExtensionValidator(["ttf"])],
blank=True,
null=True,
help_text=_("Upload regular text body font. TTF font types only."),
)
body_font_italic = CustomFontField(
verbose_name=_("Body font italic"),
upload_to=update_filename_body_italic,
file_name=CustomFontName.body_italic,
storage=OverwriteStorage(),
validators=[FileExtensionValidator(["ttf"])],
blank=True,
null=True,
help_text=_("Upload italic text body font. TTF font types only."),
)
body_font_bold = CustomFontField(
verbose_name=_("Body font bold"),
upload_to=update_filename_body_bold,
file_name=CustomFontName.body_bold,
storage=OverwriteStorage(),
validators=[FileExtensionValidator(["ttf"])],
blank=True,
null=True,
help_text=_("Upload bold text body font. TTF font types only."),
)
body_font_bold_italic = CustomFontField(
verbose_name=_("Body font bold italic"),
upload_to=update_filename_body_bold_italic,
file_name=CustomFontName.body_bold_italic,
storage=OverwriteStorage(),
validators=[FileExtensionValidator(["ttf"])],
blank=True,
null=True,
help_text=_("Upload bold italic text body font. TTF font types only."),
)


@receiver(post_save, sender=CustomFontSet)
def remove_orphan_files(sender, instance, *args, **kwargs):
"""
Remove font files corresponding to `CustomFont` fields that have been cleared
"""
custom_fonts_dir = os.path.join(settings.MEDIA_ROOT, "custom_fonts")
font_names = os.listdir(custom_fonts_dir)

for field in sender._meta.concrete_fields:
if isinstance(field, models.FileField) and not getattr(instance, field.name):
for font_name in font_names:
if font_name.startswith(field.file_name):
os.remove(os.path.join(custom_fonts_dir, font_name))


class SiteConfigurationPage(OrderedModel):
configuration = models.ForeignKey(
SiteConfiguration,
Expand Down
Loading

0 comments on commit 24228e0

Please sign in to comment.