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/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', diff --git a/theming/__init__.py b/theming/__init__.py index 3a82344..6e6ed97 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' @@ -40,20 +42,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() + # 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/middleware.py b/theming/middleware.py index 2d4f61d..8706bb0 100644 --- a/theming/middleware.py +++ b/theming/middleware.py @@ -20,6 +20,18 @@ class ThemingMiddleware(object): ) ''' + 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: @@ -38,4 +50,3 @@ def process_request(self, request): sitetheme = None set_thread_variable('sitetheme', sitetheme) - 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/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 = [ diff --git a/theming/models.py b/theming/models.py index ea9c7f4..8897488 100644 --- a/theming/models.py +++ b/theming/models.py @@ -29,8 +29,41 @@ def __init__(self, slug, *args, **kwargs): self._metadata = {} self.metadata_ready = None + @classmethod + def get_theming_root(cls, theme_slug=None): + root = settings.THEMING_ROOT if hasattr(settings, 'THEMING_ROOT') else 'themes' + if theme_slug: + 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 = [] + 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( + Theme.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 +103,11 @@ 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() + 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 +122,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] @@ -101,7 +138,7 @@ def get_theme(self, theme_slug): @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/theming/template.py b/theming/template.py index 09aefba..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(settings.THEMING_ROOT, 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: diff --git a/tox.ini b/tox.ini index 3be5ae1..91c5794 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}, + 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