From bcb5bdd7f302ab278d9f3cb3ffa46dc083ef8e78 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Fri, 2 Feb 2018 16:22:30 +1100 Subject: [PATCH 01/10] add get_response to ThemingMiddleware --- theming/middleware.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/theming/middleware.py b/theming/middleware.py index 2d4f61d..d11bc63 100644 --- a/theming/middleware.py +++ b/theming/middleware.py @@ -21,6 +21,12 @@ class ThemingMiddleware(object): ''' + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + return self.get_response(request) + def process_request(self, request): try: host = request.get_host() From f57a20edb60fccd16d886c284a9f5b71b49cb724 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Wed, 7 Mar 2018 12:21:58 +1100 Subject: [PATCH 02/10] set version manually in setup.py, remove import theming from setup.py and set the version manually to allow this package to be pip installed into an empty repo via requirements.txt when installed at the same time as Django - removes ModuleNotFoundError on pip install --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index f51b33c..1292d07 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,6 @@ from setuptools import setup, find_packages -import theming - BASE = path.abspath(path.dirname(__file__)) @@ -14,7 +12,7 @@ setup( name='django-template-theming', - version=theming.__version__, + version='0.8.2', url='https://github.com/wtayyeb/django-template-theming', author='w.Tayyeb', author_email='tayyeb@tsaze.com', From 09fa5aed15d87967ce3771106444a943822fa589 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Fri, 9 Mar 2018 17:07:09 +1100 Subject: [PATCH 03/10] Allow for installable themes, This change adds a method `get_theming_root` to `Theme` and calls that passing the theming root if necessary. It returns either a list of full paths to themes within virtual environments (allowing them to be used) or a single full path if `theme_slug` is passed in. Update docs to explain usage. --- README.rst | 18 +++++++++++++++- theming/__init__.py | 30 +++++++++++++++------------ theming/models.py | 50 ++++++++++++++++++++++++++++++++++++++------- theming/template.py | 2 +- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/README.rst b/README.rst index 47833a6..75b4292 100644 --- a/README.rst +++ b/README.rst @@ -46,10 +46,26 @@ It should create a folder ``themes`` at the project with the following structure | | -- images/ | -- templatefiles and folders + +Installable Themes + +To install a theme add it to ``INSTALLED_APPS``, then define ``THEMING_APPS`` dict with the theme slug as +the key and the app label as the value e.g. :: + + THEMING_ROOT = 'themes' + THEMING_APPS = { + 'default': 'my_default_theme_app', # could be the project app or an installed theme. + 'mytheme': 'my_theme', + 'slug': 'app_label', # example format + } + + + + Contributing ============ -Development of **django-template-theming** happens at github and any idea and contribution is wellcome. +Development of **django-template-theming** happens at github and any idea and contribution is welcome. https://github.com/wtayyeb/django-template-theming Credits diff --git a/theming/__init__.py b/theming/__init__.py index 3a82344..980e138 100644 --- a/theming/__init__.py +++ b/theming/__init__.py @@ -40,20 +40,24 @@ def configure(self): def patch_settings_staticfiles_dirs(self): staticfiles_dirs = [] - for theme_slug in os.listdir(settings.THEMING_ROOT): - if theme_slug.startswith('~'): - continue + from .models import Theme + root_list = Theme.get_theming_root(settings.THEMING_ROOT) # Theme.get_theming_root + # make root a list + for root in root_list: + for theme_slug in os.listdir(root): + if theme_slug.startswith('~'): + continue - real_path = os.path.join(settings.THEMING_ROOT, theme_slug, 'static').replace('\\', '/') - if os.path.isdir(real_path): - # here we need its path under static so using THEMING_URL - key = os.path.join(settings.THEMING_URL, theme_slug).replace('\\', '/') - row = (key, real_path) - if os.name == 'nt': # fix for windows - row = [r.replace('/', '\\') for r in row] - staticfiles_dirs.append(row) - else: - logger.debug('theme `%s` not found.' % theme_slug) + real_path = os.path.join(settings.THEMING_ROOT, theme_slug, 'static').replace('\\', '/') + if os.path.isdir(real_path): + # here we need its path under static so using THEMING_URL + key = os.path.join(settings.THEMING_URL, theme_slug).replace('\\', '/') + row = (key, real_path) + if os.name == 'nt': # fix for windows + row = [r.replace('/', '\\') for r in row] + staticfiles_dirs.append(row) + else: + logger.debug('theme `%s` not found.' % theme_slug) PRE_STATICFILES_DIRS = getattr(settings, 'PRE_STATICFILES_DIRS', None) if PRE_STATICFILES_DIRS is None: diff --git a/theming/models.py b/theming/models.py index ea9c7f4..ff61227 100644 --- a/theming/models.py +++ b/theming/models.py @@ -29,8 +29,39 @@ def __init__(self, slug, *args, **kwargs): self._metadata = {} self.metadata_ready = None + def get_theming_root(self, theme_slug=None): + root = settings.THEMING_ROOT if hasattr(settings, 'THEMING_ROOT') else 'themes' + if theme_slug: + theme_app = settings.THEMING_APPS[theme_slug] + theme_app = __import__(theme_app) + root = os.path.join(theme_app.__file__).replace( + '__init__.py', settings.THEMING_ROOT + ) + else: + if hasattr(settings, 'THEMING_APPS') and settings.THEMING_APPS is not None: + root_list = [] + for theme, app in settings.THEMING_APPS.items(): + theme_app = __import__(app) + + # Get the THEMING_ROOT for a loaded theme app. + # Theme apps should use the same construction i.e. + # the THEMING_ROOT and folder layout should be standardised. + root = os.path.join(theme_app.__file__).replace( + '__init__.py', settings.THEMING_ROOT + ) + root_list.append(root) + return root_list + return [root] + # used by read_metadata or by passing a theme_slug to return a + # a single root rather than a list. + return root + def read_metadata(self): - filename = os.path.join(settings.THEMING_ROOT, self.slug, self._metadata_filename) + # filename = os.path.join(settings.THEMING_ROOT, self.slug, self._metadata_filename) + filename = os.path.join( + self.get_theming_root(thememanager.get_current_theme().slug), + self.slug, self._metadata_filename + ) try: with open(filename, 'r') as f: self._metadata = json.load(f) @@ -70,10 +101,12 @@ def __init__(self, *args, **kwargs): def find_themes(self, force=False): if self._themes is None or force: self._themes = {} - root = settings.THEMING_ROOT - for dirname in os.listdir(root): - if not dirname.startswith('~'): - self._themes[dirname] = Theme(dirname) + root_list = Theme.get_theming_root(settings.THEMING_ROOT) # Theme.get_theming_root + # make root a list + for root in root_list: + for dirname in os.listdir(root): + if not dirname.startswith('~'): + self._themes[dirname] = Theme(dirname) return self._themes def get_themes_choice(self): @@ -88,10 +121,13 @@ def get_current_theme(self): if sitetheme: theme = sitetheme.theme else: - theme = self.get_theme(settings.THEMING_DEFAULT_THEME) + if not hasattr(settings, 'THEMING_DEFAULT_THEME'): + theme = self.get_theme('default') + else: + theme = self.get_theme(settings.THEMING_DEFAULT_THEME) return theme - def get_theme(self, theme_slug): + def get_theme(self, theme_slug='default'): self.find_themes() return self._themes[theme_slug] diff --git a/theming/template.py b/theming/template.py index 09aefba..7266041 100644 --- a/theming/template.py +++ b/theming/template.py @@ -37,7 +37,7 @@ def get_template_sources(self, template_name, template_dirs=None): theme = thememanager.get_current_theme() if not template_dirs: - template_dirs = [safe_join(settings.THEMING_ROOT, theme.slug), ] + template_dirs = [safe_join(theme.get_theming_root(theme.slug), theme.slug), ] for template_dir in template_dirs: try: From 5a6c34cd19e8c7d87f66d4abd2acb83c4be7a7d9 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Fri, 9 Mar 2018 17:10:25 +1100 Subject: [PATCH 04/10] Fixes middleware compatibility by extending MiddlewareMixin --- theming/middleware.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/theming/middleware.py b/theming/middleware.py index d11bc63..5d05e7b 100644 --- a/theming/middleware.py +++ b/theming/middleware.py @@ -5,11 +5,12 @@ ''' from django.contrib.sites.models import Site, SITE_CACHE +from django.utils.deprecation import MiddlewareMixin from .threadlocals import set_thread_variable -class ThemingMiddleware(object): +class ThemingMiddleware(MiddlewareMixin): ''' Middleware that puts the request object in thread local storage. add this middleware to MIDDLEWARE_CLASSES to make theming work. @@ -21,12 +22,6 @@ class ThemingMiddleware(object): ''' - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request): - return self.get_response(request) - def process_request(self, request): try: host = request.get_host() @@ -44,4 +39,3 @@ def process_request(self, request): sitetheme = None set_thread_variable('sitetheme', sitetheme) - From 29a6e643466606cee178c09dd408f4c6b2e39e16 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Fri, 9 Mar 2018 17:19:36 +1100 Subject: [PATCH 05/10] fix incompatibility with admin_tools.theming --- theming/__init__.py | 2 ++ theming/migrations/0002_auto_20170508_0505.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/theming/__init__.py b/theming/__init__.py index 980e138..8e325a5 100644 --- a/theming/__init__.py +++ b/theming/__init__.py @@ -18,6 +18,8 @@ class App(AppConfig): name = 'theming' + label = 'template_theming' + verbose_name = "Template Theming" class Defaults: THEMING_DEFAULT_THEME = 'default' diff --git a/theming/migrations/0002_auto_20170508_0505.py b/theming/migrations/0002_auto_20170508_0505.py index f6cb209..facf9fd 100644 --- a/theming/migrations/0002_auto_20170508_0505.py +++ b/theming/migrations/0002_auto_20170508_0505.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('theming', '0001_initial'), + ('template_theming', '0001_initial'), ] operations = [ From 2dc2cb827c15a8b80419d0a218cfe2a25d902c5d Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Fri, 9 Mar 2018 20:01:40 +1100 Subject: [PATCH 06/10] make get_theming_root a classmethod --- theming/models.py | 5 +++-- theming/template.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/theming/models.py b/theming/models.py index ff61227..76869ff 100644 --- a/theming/models.py +++ b/theming/models.py @@ -29,6 +29,7 @@ def __init__(self, slug, *args, **kwargs): self._metadata = {} self.metadata_ready = None + @classmethod def get_theming_root(self, theme_slug=None): root = settings.THEMING_ROOT if hasattr(settings, 'THEMING_ROOT') else 'themes' if theme_slug: @@ -59,7 +60,7 @@ def get_theming_root(self, theme_slug=None): def read_metadata(self): # filename = os.path.join(settings.THEMING_ROOT, self.slug, self._metadata_filename) filename = os.path.join( - self.get_theming_root(thememanager.get_current_theme().slug), + Theme.get_theming_root(thememanager.get_current_theme().slug), self.slug, self._metadata_filename ) try: @@ -101,7 +102,7 @@ def __init__(self, *args, **kwargs): def find_themes(self, force=False): if self._themes is None or force: self._themes = {} - root_list = Theme.get_theming_root(settings.THEMING_ROOT) # Theme.get_theming_root + root_list = Theme.get_theming_root(settings.THEMING_ROOT) # make root a list for root in root_list: for dirname in os.listdir(root): diff --git a/theming/template.py b/theming/template.py index 7266041..e3344e4 100644 --- a/theming/template.py +++ b/theming/template.py @@ -37,7 +37,8 @@ def get_template_sources(self, template_name, template_dirs=None): theme = thememanager.get_current_theme() if not template_dirs: - template_dirs = [safe_join(theme.get_theming_root(theme.slug), theme.slug), ] + from .models import Theme + template_dirs = [safe_join(Theme.get_theming_root(theme.slug), theme.slug), ] for template_dir in template_dirs: try: From 7cb22d773f794971f2fa322f770216f1b19af625 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Sat, 10 Mar 2018 12:42:05 +1100 Subject: [PATCH 07/10] make get_theming_root a classmethod --- theming/__init__.py | 2 +- theming/models.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/theming/__init__.py b/theming/__init__.py index 8e325a5..6e6ed97 100644 --- a/theming/__init__.py +++ b/theming/__init__.py @@ -43,7 +43,7 @@ def configure(self): def patch_settings_staticfiles_dirs(self): staticfiles_dirs = [] from .models import Theme - root_list = Theme.get_theming_root(settings.THEMING_ROOT) # Theme.get_theming_root + root_list = Theme.get_theming_root() # make root a list for root in root_list: for theme_slug in os.listdir(root): diff --git a/theming/models.py b/theming/models.py index 76869ff..d773304 100644 --- a/theming/models.py +++ b/theming/models.py @@ -30,7 +30,7 @@ def __init__(self, slug, *args, **kwargs): self.metadata_ready = None @classmethod - def get_theming_root(self, theme_slug=None): + def get_theming_root(cls, theme_slug=None): root = settings.THEMING_ROOT if hasattr(settings, 'THEMING_ROOT') else 'themes' if theme_slug: theme_app = settings.THEMING_APPS[theme_slug] @@ -58,7 +58,6 @@ def get_theming_root(self, theme_slug=None): return root def read_metadata(self): - # filename = os.path.join(settings.THEMING_ROOT, self.slug, self._metadata_filename) filename = os.path.join( Theme.get_theming_root(thememanager.get_current_theme().slug), self.slug, self._metadata_filename @@ -102,8 +101,7 @@ def __init__(self, *args, **kwargs): def find_themes(self, force=False): if self._themes is None or force: self._themes = {} - root_list = Theme.get_theming_root(settings.THEMING_ROOT) - # make root a list + root_list = Theme.get_theming_root() for root in root_list: for dirname in os.listdir(root): if not dirname.startswith('~'): From 6fe1b323d1fa01849a931eb3acb231ca47b3c900 Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Sat, 10 Mar 2018 13:01:34 +1100 Subject: [PATCH 08/10] extra settings checks --- theming/models.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/theming/models.py b/theming/models.py index d773304..04c1117 100644 --- a/theming/models.py +++ b/theming/models.py @@ -33,11 +33,13 @@ def __init__(self, slug, *args, **kwargs): def get_theming_root(cls, theme_slug=None): root = settings.THEMING_ROOT if hasattr(settings, 'THEMING_ROOT') else 'themes' if theme_slug: - theme_app = settings.THEMING_APPS[theme_slug] - theme_app = __import__(theme_app) - root = os.path.join(theme_app.__file__).replace( - '__init__.py', settings.THEMING_ROOT - ) + if hasattr(settings, 'THEMING_APPS') and settings.THEMING_APPS is not None: + theme_app = settings.THEMING_APPS[theme_slug] if \ + hasattr(settings, 'THEMING_APPS') else 'themes' + theme_app = __import__(theme_app) + root = os.path.join(theme_app.__file__).replace( + '__init__.py', settings.THEMING_ROOT + ) else: if hasattr(settings, 'THEMING_APPS') and settings.THEMING_APPS is not None: root_list = [] From 961adaee9c4ef6ccf78b0e8868e89aae1a0776af Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Sat, 10 Mar 2018 13:41:51 +1100 Subject: [PATCH 09/10] compatibility and tox updates --- theming/middleware.py | 15 +++++++++++++-- theming/migrations/0001_initial.py | 2 +- theming/models.py | 2 +- tox.ini | 9 ++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/theming/middleware.py b/theming/middleware.py index 5d05e7b..8706bb0 100644 --- a/theming/middleware.py +++ b/theming/middleware.py @@ -5,12 +5,11 @@ ''' from django.contrib.sites.models import Site, SITE_CACHE -from django.utils.deprecation import MiddlewareMixin from .threadlocals import set_thread_variable -class ThemingMiddleware(MiddlewareMixin): +class ThemingMiddleware(object): ''' Middleware that puts the request object in thread local storage. add this middleware to MIDDLEWARE_CLASSES to make theming work. @@ -21,6 +20,18 @@ class ThemingMiddleware(MiddlewareMixin): ) ''' + def __init__(self, get_response=None): + self.get_response = get_response + + def __call__(self, request): + response = None + if hasattr(self, 'process_request'): + response = self.process_request(request) + if not response: + response = self.get_response(request) + if hasattr(self, 'process_response'): + response = self.process_response(request, response) + return response def process_request(self, request): try: diff --git a/theming/migrations/0001_initial.py b/theming/migrations/0001_initial.py index e433750..799afe9 100644 --- a/theming/migrations/0001_initial.py +++ b/theming/migrations/0001_initial.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('theme_slug', models.CharField(max_length=100, choices=thememanager.get_themes_choice())), - ('site', models.OneToOneField(to='sites.Site')), + ('site', models.OneToOneField(to='sites.Site',on_delete=models.CASCADE)), ], ), ] diff --git a/theming/models.py b/theming/models.py index 04c1117..8897488 100644 --- a/theming/models.py +++ b/theming/models.py @@ -138,7 +138,7 @@ def get_theme(self, theme_slug='default'): @python_2_unicode_compatible class SiteTheme(models.Model): - site = models.OneToOneField(Site) + site = models.OneToOneField(Site, on_delete=models.CASCADE) theme_slug = models.CharField(max_length=100, choices=thememanager.get_themes_choice()) site_title = models.CharField(max_length=255, default='', blank=True) site_description = models.CharField(max_length=255, default='', blank=True) diff --git a/tox.ini b/tox.ini index 3be5ae1..dd3038e 100644 --- a/tox.ini +++ b/tox.ini @@ -6,9 +6,10 @@ minversion = 1.8 skip_missing_interpreters=True envlist = - py27-dj{1.8,1.9,.dev}, - py34-dj{1.8,1.9,.dev}, - py35-dj{1.9,.dev}, + py27-dj{1.8,1.9,1.10,1.11}, + py34-dj{1.8,1.9,1.10,1.11,2.0}, + py35-dj{1.9,1.10,1.11,2.0,.dev}, + py36-dj{1.9,1.10,1.11,2.0,.dev}, docs, @@ -17,6 +18,8 @@ deps = dj1.8: Django >= 1.8, < 1.9 dj1.9: Django >= 1.9a1, < 1.10 dj1.10: Django >= 1.10, < 1.11 + dj1.11: Django >= 1.11, < 2.0 + dj2.0: Django >= 2.0, < 2.1 dj.dev: https://github.com/django/django/tarball/master coverage From a152cfdf59c77b344463309c15b5d490f6e94e7c Mon Sep 17 00:00:00 2001 From: Sam Minton Date: Sat, 10 Mar 2018 19:39:52 +1100 Subject: [PATCH 10/10] tox python version --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dd3038e..91c5794 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ skip_missing_interpreters=True envlist = py27-dj{1.8,1.9,1.10,1.11}, - py34-dj{1.8,1.9,1.10,1.11,2.0}, + py34-dj{1.8,1.9,1.10,1.11}, py35-dj{1.9,1.10,1.11,2.0,.dev}, py36-dj{1.9,1.10,1.11,2.0,.dev},