Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: selectable theme file #28

Merged
merged 14 commits into from
Mar 28, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,4 @@ cython_debug/
/static/CACHE/
license_report.txt
requirements.txt
static/
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,27 @@ STATICFILES_FINDERS = [
"compressor.finders.CompressorFinder",
]
COMPRESS_PRECOMPILERS = (("text/x-scss", "django_libsass.SassCompiler"),)
```
2. Assuming you are using [Bootstrap 5](https://github.com/zostera/django-bootstrap5) you also need to add:
```python
INSTALLED_APPS = [
....
"django_bootstrap5",

BOOSTRAP5 = {
"theme_url": "static/theme.css",
TEMPLATES = {
"OPTIONS": {
"context_processors": [
...,
"django_dynamic_theme-context_processor.theme",
]
}
}
```
2. Run `python manage.py migrate`

3. Assuming you have `base.html` add this to it before the `<body>` tag:
```html
<html>
...
{% load compress %}
{% load static %}
{% compress css %}
<link type="text/x-scss" rel="stylesheet" href="{% static 'theme.scss' %}" />
...
</html>
```

3. Visit `http://127.0.0.1:8000/admin/django_dynamic_theme/` to start customizing your theme.
17 changes: 17 additions & 0 deletions django_dynamic_theme/context_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Defines extra contexts for this application globally."""

from django_dynamic_theme.models import Theme


# pylint: disable=no-member
def theme(_) -> dict[str, str]:
"""
Defines the url to the theme to be used.
Returns: url to theme in a dict.
"""

try:
admin_default: Theme = Theme.objects.get(default=True)
return {"theme_file": f"{admin_default.name}.scss"}
except Theme.DoesNotExist:
return {"theme_file": "theme.scss"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0.3 on 2024-03-28 19:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('django_dynamic_theme', '0006_alter_theme_background'),
]

operations = [
migrations.AddField(
model_name='theme',
name='default',
field=models.BooleanField(default=False),
preserve_default=False,
),
migrations.AddConstraint(
model_name='theme',
constraint=models.UniqueConstraint(condition=models.Q(('default', True)), fields=('default',), name='Only one theme can be default.'),
),
]
18 changes: 18 additions & 0 deletions django_dynamic_theme/migrations/0008_alter_theme_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-03-28 19:09

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('django_dynamic_theme', '0007_theme_default_theme_only_one_theme_can_be_default_'),
]

operations = [
migrations.AlterField(
model_name='theme',
name='default',
field=models.BooleanField(default=False),
),
]
13 changes: 13 additions & 0 deletions django_dynamic_theme/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,21 @@ class Theme(models.Model):
"""

name = models.CharField(max_length=50)
default = models.BooleanField(default=False)
background: Background = models.ForeignKey(Background, on_delete=models.CASCADE)

# pylint: disable=too-few-public-methods
class Meta:
"""Meta class of the Theme."""

constraints = [
models.UniqueConstraint(
fields=["default"],
condition=models.Q(default=True),
name="Only one theme can be default.",
)
]

@property
def path(self) -> str:
"""
Expand Down
20 changes: 20 additions & 0 deletions tests/test_context_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Tests the context processor(s)"""

from django.test import TestCase

from django_dynamic_theme.context_processor import theme
from django_dynamic_theme.models import Background, Theme


class ThemeTest(TestCase):
"""Tests the theme context processor."""

def test_theme_file(self):
self.assertDictEqual({"theme_file": "theme.scss"}, theme(""))

def test_theme_file_defined_by_admin(self):
background = Background.objects.create(name="black", primary_bg="000000")
admin_theme = Theme.objects.create(
name="Admin", default=True, background=background
)
self.assertDictEqual({"theme_file": f"{admin_theme.name}.scss"}, theme(""))
22 changes: 15 additions & 7 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from os import mkdir, path, remove
from django_dynamic_theme.models import Background, Theme

from django.db import IntegrityError
from django_dynamic_theme.models import Background, Theme

from django.test import TestCase

Expand All @@ -11,6 +12,8 @@ def setUp(self) -> None:
self.theme_name = "test"
self.folder = "static/"
self.file_path = f"{self.folder}/{self.theme_name}.scss"
color: str = "F0F0F0"
self.background = Background.objects.create(primary_bg=color)
if not path.exists(self.folder):
mkdir(self.folder)
_ = open(self.file_path, mode="w+")
Expand All @@ -21,21 +24,26 @@ def tearDown(self) -> None:
remove(self.file_path)

def test_export(self):
color: str = "F0F0F0"
background = Background.objects.create(primary_bg=color)
theme = Theme.objects.create(name=self.theme_name, background=background)
theme = Theme.objects.create(name=self.theme_name, background=self.background)
expected_string = "body {background: F0F0F0;}"
self.assertEqual(expected_string, theme.export())

def test_export_without_pre_existing_file(self):
if path.exists(self.file_path):
remove(self.file_path)
color: str = "F0F0F0"
background = Background.objects.create(primary_bg=color)
theme = Theme.objects.create(name=self.theme_name, background=background)
theme = Theme.objects.create(name=self.theme_name, background=self.background)
expected_string = "body {background: F0F0F0;}"
self.assertEqual(expected_string, theme.export())

def test_theme_default_uniqueness(self):
_ = Theme.objects.create(
name="Original", default=True, background=self.background
)
with self.assertRaises(IntegrityError):
_ = Theme.objects.create(
name="Second", default=True, background=self.background
)


class BackgroundModelTest(TestCase):
def test_export(self):
Expand Down
Loading