├── .gitignore ├── MANIFEST.in ├── README.md ├── examples ├── bootswatch_example │ ├── README.md │ ├── bootswatch_example │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ ├── views.py │ │ └── wsgi.py │ ├── manage.py │ └── templates │ │ └── home.html └── simple_example │ ├── README.md │ ├── manage.py │ ├── simple_example │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py │ ├── static │ └── simple_example │ │ └── css │ │ ├── green.css │ │ └── red.css │ └── templates │ └── home.html ├── requirements.txt ├── setup.py └── themeswitch ├── __init__.py ├── context_processors.py ├── models.py ├── runtests ├── __init__.py ├── runcoverage.py ├── runtests.py ├── settings.py └── urls.py ├── settings.py ├── templatetags ├── __init__.py └── themeswitch_tags.py ├── tests.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /.idea/ 3 | 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | MANIFEST -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-themeswitch 2 | 3 | _django-themeswitch_ is a django app that allows users to easily switch between a set of predefined stylings. 4 | 5 | ## Usage 6 | 7 | To use _django-themeswitch_ update your `settings.py`: 8 | 9 | - add `themeswitch` to your `INSTALLED_APPS` 10 | - add 'themeswitch.context_processors.selected_theme' to your `TEMPLATE_CONTEXT_PROCESSORS` 11 | 12 | Then add an entry in your `urls.py` to include `themeswitch.urls`. 13 | 14 | In your templates after you `{% load themeswitch_tags %}` you have access to the template tags `{% get_available_themes as VARIABLENAME %}` 15 | and `{% render_selected_theme_css %}`. Place the `{% render_selected_theme_css %}` somewhere in your template's ``. 16 | 17 | Now add an entry `THEMESWITCHER_THEMES` to your `settings.py`. `THEMESWITCHER_THEMES` should be a dictionary that contains mappings of `: `. 18 | The URLs must be absolute, but can exclude the host. Example: 19 | 20 | ```python 21 | THEMESWITCHER_THEMES = { 22 | 'green': '/static/green.css' 23 | } 24 | ``` 25 | 26 | This would make `get_available_themes` return just one theme, named 'green'. 27 | 28 | Check out the [examples subdirectory](https://github.com/nschlemm/django-themeswitch/tree/master/examples). 29 | 30 | # License 31 | 32 | Copyright (c) 2013-2014, Nikolaus Schlemm 33 | All rights reserved. 34 | 35 | Redistribution and use in source and binary forms, with or without 36 | modification, are permitted provided that the following conditions are met: 37 | 38 | Redistributions of source code must retain the above copyright notice, this 39 | list of conditions and the following disclaimer. 40 | Redistributions in binary form must reproduce the above copyright notice, this 41 | list of conditions and the following disclaimer in the documentation and/or 42 | other materials provided with the distribution. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 45 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 46 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 47 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 48 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 49 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 50 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 51 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 52 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | -------------------------------------------------------------------------------- /examples/bootswatch_example/README.md: -------------------------------------------------------------------------------- 1 | # Simple Example 2 | 3 | A very basic django app that includes _django-themeswitch_ and shows how to switch between bootswatch themes. -------------------------------------------------------------------------------- /examples/bootswatch_example/bootswatch_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nschlemm/django-themeswitch/371495b05abe14fddc2cabc9468f4fb2aedf77f7/examples/bootswatch_example/bootswatch_example/__init__.py -------------------------------------------------------------------------------- /examples/bootswatch_example/bootswatch_example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for bootswatch_example project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '=fbvn45kes+#g1y4=&)ks$1ftfu_5tnu!ed8s4e__)nu*2*6cc' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 40 | 'themeswitch' 41 | ) 42 | 43 | TEMPLATE_DIRS = ( 44 | os.path.join(BASE_DIR, 'templates'), 45 | ) 46 | 47 | MIDDLEWARE_CLASSES = ( 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | ) 55 | 56 | TEMPLATE_CONTEXT_PROCESSORS = ( 57 | 'django.contrib.auth.context_processors.auth', 58 | 'django.core.context_processors.debug', 59 | 'django.core.context_processors.i18n', 60 | 'django.core.context_processors.media', 61 | 'django.core.context_processors.request', 62 | 'django.core.context_processors.static', 63 | 'django.core.context_processors.tz', 64 | 'django.contrib.messages.context_processors.messages', 65 | 'themeswitch.context_processors.selected_theme' 66 | ) 67 | 68 | ROOT_URLCONF = 'bootswatch_example.urls' 69 | 70 | WSGI_APPLICATION = 'bootswatch_example.wsgi.application' 71 | 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 75 | 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.sqlite3', 79 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 80 | } 81 | } 82 | 83 | # Internationalization 84 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 85 | 86 | LANGUAGE_CODE = 'en-us' 87 | 88 | TIME_ZONE = 'UTC' 89 | 90 | USE_I18N = True 91 | 92 | USE_L10N = True 93 | 94 | USE_TZ = True 95 | 96 | 97 | # Static files (CSS, JavaScript, Images) 98 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 99 | 100 | STATIC_URL = '/static/' 101 | 102 | 103 | def _get_bootswatch_css_url(theme, bootstrap_version='3.1.1'): 104 | """ 105 | A helper function to generate CDN URLs for bootswatch themes. 106 | """ 107 | css_url = '//netdna.bootstrapcdn.com/bootswatch/%s/%s/bootstrap.min.css' 108 | return css_url % (bootstrap_version, theme) 109 | 110 | 111 | BOOTSWATCHES = ( 112 | 'amelia', 113 | 'cerulean', 114 | 'cosmo', 115 | 'cyborg', 116 | 'darkly', 117 | 'flatly', 118 | 'journal', 119 | 'lumen', 120 | 'readable', 121 | 'shamrock', 122 | 'simplex', 123 | 'slate', 124 | 'spacelab', 125 | 'superhero', 126 | 'united', 127 | 'yeti', 128 | ) 129 | 130 | BOOTSWATCH_THEMES = dict( 131 | (theme, _get_bootswatch_css_url(theme)) 132 | for theme in BOOTSWATCHES 133 | ) 134 | 135 | THEMESWITCHER_THEMES = dict( 136 | BOOTSWATCH_THEMES, 137 | ) 138 | -------------------------------------------------------------------------------- /examples/bootswatch_example/bootswatch_example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | urlpatterns = patterns( 4 | '', 5 | url(r'', include('themeswitch.urls')), 6 | url(r'^$', 'bootswatch_example.views.home', name='home'), 7 | ) 8 | -------------------------------------------------------------------------------- /examples/bootswatch_example/bootswatch_example/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def home(request): 5 | return render(request, 'home.html') 6 | -------------------------------------------------------------------------------- /examples/bootswatch_example/bootswatch_example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for simple_example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bootswatch_example.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /examples/bootswatch_example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", 7 | "bootswatch_example.settings") 8 | 9 | from django.core.management import execute_from_command_line 10 | 11 | execute_from_command_line(sys.argv) 12 | -------------------------------------------------------------------------------- /examples/bootswatch_example/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | {% load themeswitch_tags %} 3 | {% get_available_themes as themes %} 4 | 5 | 6 | A bootswatch example 7 | {% render_selected_theme_css %} 8 | 17 | 18 | 19 |
20 |

Pick a Theme!

21 | 27 |

django-themeswitch is a simple way to allow users to style your content to their liking!

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/simple_example/README.md: -------------------------------------------------------------------------------- 1 | # Simple Example 2 | 3 | A very basic django app that includes _django-themeswitch_ and shows how to switch between local themes. -------------------------------------------------------------------------------- /examples/simple_example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simple_example.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /examples/simple_example/simple_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nschlemm/django-themeswitch/371495b05abe14fddc2cabc9468f4fb2aedf77f7/examples/simple_example/simple_example/__init__.py -------------------------------------------------------------------------------- /examples/simple_example/simple_example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for simple_example project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '=fbvn45kes+#g1y4=&)ks$1ftfu_5tnu!ed8s4e__)nu*2*6cc' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.admin', 34 | 'django.contrib.auth', 35 | 'django.contrib.contenttypes', 36 | 'django.contrib.sessions', 37 | 'django.contrib.messages', 38 | 'django.contrib.staticfiles', 39 | 40 | 'themeswitch' 41 | ) 42 | 43 | TEMPLATE_DIRS = ( 44 | os.path.join(BASE_DIR, 'templates'), 45 | ) 46 | 47 | MIDDLEWARE_CLASSES = ( 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | ) 55 | 56 | TEMPLATE_CONTEXT_PROCESSORS = ( 57 | 'django.contrib.auth.context_processors.auth', 58 | 'django.core.context_processors.debug', 59 | 'django.core.context_processors.i18n', 60 | 'django.core.context_processors.media', 61 | 'django.core.context_processors.request', 62 | 'django.core.context_processors.static', 63 | 'django.core.context_processors.tz', 64 | 'django.contrib.messages.context_processors.messages', 65 | 66 | 'themeswitch.context_processors.selected_theme' 67 | ) 68 | 69 | ROOT_URLCONF = 'simple_example.urls' 70 | 71 | WSGI_APPLICATION = 'simple_example.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | # Internationalization 85 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 86 | 87 | LANGUAGE_CODE = 'en-us' 88 | 89 | TIME_ZONE = 'UTC' 90 | 91 | USE_I18N = True 92 | 93 | USE_L10N = True 94 | 95 | USE_TZ = True 96 | 97 | 98 | # Static files (CSS, JavaScript, Images) 99 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 100 | 101 | STATIC_URL = '/static/' 102 | 103 | STATICFILES_DIRS = ( 104 | os.path.join(BASE_DIR, 'static'), 105 | ) 106 | 107 | THEMESWITCHER_THEMES = { 108 | 'red': '/static/simple_example/css/red.css', 109 | 'green': '/static/simple_example/css/green.css' 110 | } 111 | -------------------------------------------------------------------------------- /examples/simple_example/simple_example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.conf.urls.static import static 4 | 5 | urlpatterns = patterns( 6 | '', 7 | url(r'', include('themeswitch.urls')), 8 | url(r'^$', 'simple_example.views.home', name='home'), 9 | ) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 10 | -------------------------------------------------------------------------------- /examples/simple_example/simple_example/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | def home(request): 5 | return render(request, 'home.html') 6 | -------------------------------------------------------------------------------- /examples/simple_example/simple_example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for simple_example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "simple_example.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /examples/simple_example/static/simple_example/css/green.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: green; 3 | } -------------------------------------------------------------------------------- /examples/simple_example/static/simple_example/css/red.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /examples/simple_example/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | {% load themeswitch_tags %} 3 | {% get_available_themes as themes %} 4 | 5 | 6 | A very basic example 7 | {% render_selected_theme_css %} 8 | 17 | 18 | 19 |
20 |

Pick a Theme!

21 | 27 |

django-themeswitch is a simple way to allow users to style your content to their liking!

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup 4 | import sys 5 | 6 | 7 | def get_packages(package): 8 | """ 9 | Return root package and all sub-packages. 10 | """ 11 | return [dirpath 12 | for dirpath, dirnames, filenames in os.walk(package) 13 | if os.path.exists(os.path.join(dirpath, '__init__.py'))] 14 | 15 | 16 | def get_package_data(package): 17 | """ 18 | Return all files under the root package, that are not in a 19 | package themselves. 20 | """ 21 | walk = [(dirpath.replace(package + os.sep, '', 1), filenames) 22 | for dirpath, dirnames, filenames in os.walk(package) 23 | if not os.path.exists(os.path.join(dirpath, '__init__.py'))] 24 | 25 | filepaths = [] 26 | for base, filenames in walk: 27 | filepaths.extend([os.path.join(base, filename) 28 | for filename in filenames]) 29 | return {package: filepaths} 30 | 31 | 32 | version = '0.3.1' 33 | 34 | README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read() 35 | 36 | # allow setup.py to be run from any path 37 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 38 | 39 | 40 | if sys.argv[-1] == 'publish': 41 | os.system("python setup.py sdist upload") 42 | # os.system("python setup.py bdist_wheel upload") 43 | print("You probably want to also tag the version now:") 44 | print(" git tag -a %s -m 'version %s'" % (version, version)) 45 | print(" git push --tags") 46 | sys.exit() 47 | 48 | setup( 49 | name='django-themeswitch', 50 | version=version, 51 | packages=get_packages('themeswitch'), 52 | package_data=get_package_data('themeswitch'), 53 | test_suite='themeswitch.runtests.runtests.main', 54 | license='BSD License', 55 | description='a django app that allows for easy switch between themes', 56 | long_description=README, 57 | url='https://github.com/nschlemm/django-themeswitch', 58 | author='Nikolaus Schlemm', 59 | author_email='capo@coder-nostra.org', 60 | classifiers=[ 61 | 'Environment :: Web Environment', 62 | 'Framework :: Django', 63 | 'Intended Audience :: Developers', 64 | 'License :: OSI Approved :: BSD License', 65 | 'Operating System :: OS Independent', 66 | 'Programming Language :: Python', 67 | 'Programming Language :: Python :: 2.6', 68 | 'Programming Language :: Python :: 2.7', 69 | 'Topic :: Internet :: WWW/HTTP', 70 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 71 | ], 72 | ) 73 | -------------------------------------------------------------------------------- /themeswitch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nschlemm/django-themeswitch/371495b05abe14fddc2cabc9468f4fb2aedf77f7/themeswitch/__init__.py -------------------------------------------------------------------------------- /themeswitch/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ImproperlyConfigured 2 | 3 | from .settings import DEFAULT_THEME, THEMES 4 | 5 | 6 | def selected_theme(request): 7 | theme = request.COOKIES.get('selected_theme', DEFAULT_THEME) 8 | if theme not in THEMES: 9 | theme = DEFAULT_THEME 10 | 11 | return {'selected_theme': theme} 12 | -------------------------------------------------------------------------------- /themeswitch/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /themeswitch/runtests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'nschlemm' 2 | -------------------------------------------------------------------------------- /themeswitch/runtests/runcoverage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Useful tool to run the test suite for rest_framework and generate a coverage 4 | report. 5 | """ 6 | 7 | # http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/ 8 | # http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/ 9 | # http://code.djangoproject.com/svn/django/trunk/tests/runtests.py 10 | # https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/runtests/runcoverage.py 11 | import os 12 | import sys 13 | 14 | # fix sys path so we don't need to setup PYTHONPATH 15 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../..")) 16 | os.environ['DJANGO_SETTINGS_MODULE'] = 'themeswitch.runtests.settings' 17 | 18 | from coverage import coverage 19 | 20 | 21 | def main(): 22 | """Run the tests for themeswitch and generate a coverage report.""" 23 | 24 | cov = coverage() 25 | cov.erase() 26 | cov.start() 27 | 28 | from django.conf import settings 29 | from django.test.utils import get_runner 30 | TestRunner = get_runner(settings) 31 | 32 | if hasattr(TestRunner, 'func_name'): 33 | # Pre 1.2 test runners were just functions, 34 | # and did not support the 'failfast' option. 35 | import warnings 36 | warnings.warn( 37 | 'Function-based test runners are deprecated. Test runners should ' 38 | 'be classes with a run_tests() method.', 39 | DeprecationWarning 40 | ) 41 | failures = TestRunner(['tests']) 42 | else: 43 | test_runner = TestRunner() 44 | failures = test_runner.run_tests(['themeswitch.tests']) 45 | cov.stop() 46 | 47 | # Discover the list of all modules that we should test coverage for 48 | import themeswitch 49 | 50 | project_dir = os.path.dirname(themeswitch.__file__) 51 | cov_files = [] 52 | 53 | for (path, dirs, files) in os.walk(project_dir): 54 | # Drop tests and runtests directories from the test coverage report 55 | if os.path.basename(path) in ['tests', 'runtests', 'migrations']: 56 | continue 57 | 58 | # Drop the compat and six modules from coverage, since we're not 59 | # interested in the coverage of modules which are specifically for 60 | # resolving environment dependant imports. (Because we'll end up 61 | # getting different coverage reports for it for each environment) 62 | if 'compat.py' in files: 63 | files.remove('compat.py') 64 | 65 | if 'six.py' in files: 66 | files.remove('six.py') 67 | 68 | # Same applies to template tags module. 69 | # This module has to include branching on Django versions, 70 | # so it's never possible for it to have full coverage. 71 | if 'themeswitch_tags.py' in files: 72 | files.remove('themeswitch_tags.py') 73 | 74 | cov_files.extend([os.path.join(path, file) 75 | for file in files if file.endswith('.py')]) 76 | 77 | cov.report(cov_files) 78 | if '--html' in sys.argv: 79 | cov.html_report(cov_files, directory='coverage') 80 | sys.exit(failures) 81 | 82 | if __name__ == '__main__': 83 | main() 84 | -------------------------------------------------------------------------------- /themeswitch/runtests/runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/ 4 | # http://www.travisswicegood.com/2010/01/17/django-virtualenv-pip-and-fabric/ 5 | # http://code.djangoproject.com/svn/django/trunk/tests/runtests.py 6 | # https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/runtests/runtests.py 7 | import os 8 | import sys 9 | 10 | # fix sys path so we don't need to setup PYTHONPATH 11 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../..")) 12 | os.environ['DJANGO_SETTINGS_MODULE'] = 'themeswitch.runtests.settings' 13 | 14 | import django 15 | from django.conf import settings 16 | from django.test.utils import get_runner 17 | 18 | 19 | def usage(): 20 | return """ 21 | Usage: python runtests.py [UnitTestClass].[method] 22 | 23 | You can pass the Class name of the `UnitTestClass` you want to test. 24 | 25 | Append a method name if you only want to test a specific method of that 26 | class. 27 | """ 28 | 29 | 30 | def main(): 31 | try: 32 | django.setup() 33 | except AttributeError: 34 | pass 35 | TestRunner = get_runner(settings) 36 | 37 | test_runner = TestRunner() 38 | if len(sys.argv) == 2: 39 | test_case = '.' + sys.argv[1] 40 | elif len(sys.argv) == 1: 41 | test_case = '' 42 | else: 43 | print(usage()) 44 | sys.exit(1) 45 | test_module_name = 'themeswitch.tests' 46 | if django.VERSION[0] == 1 and django.VERSION[1] < 6: 47 | test_module_name = 'tests' 48 | 49 | failures = test_runner.run_tests([test_module_name + test_case]) 50 | 51 | sys.exit(failures) 52 | 53 | if __name__ == '__main__': 54 | main() 55 | -------------------------------------------------------------------------------- /themeswitch/runtests/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for runtests project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '@_sb=hk_qqburc))z!dbd59ia9_ev$7_qjz2fw4vp+xo6)=fud' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'django.contrib.auth', 34 | 'django.contrib.contenttypes', 35 | 'django.contrib.sessions', 36 | 'django.contrib.messages', 37 | 38 | 'themeswitch', 39 | ) 40 | 41 | MIDDLEWARE_CLASSES = ( 42 | 'django.contrib.sessions.middleware.SessionMiddleware', 43 | 'django.middleware.common.CommonMiddleware', 44 | 'django.middleware.csrf.CsrfViewMiddleware', 45 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 46 | 'django.contrib.messages.middleware.MessageMiddleware', 47 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 48 | ) 49 | 50 | ROOT_URLCONF = 'urls' 51 | 52 | # Database 53 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 54 | 55 | DATABASES = { 56 | 'default': { 57 | 'ENGINE': 'django.db.backends.sqlite3', 58 | 'NAME': ':memory:', # os.path.join(BASE_DIR, 'db.sqlite3'), 59 | } 60 | } 61 | 62 | # Internationalization 63 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 64 | 65 | LANGUAGE_CODE = 'en-us' 66 | 67 | TIME_ZONE = 'UTC' 68 | 69 | USE_I18N = True 70 | 71 | USE_L10N = True 72 | 73 | USE_TZ = True 74 | 75 | 76 | # Static files (CSS, JavaScript, Images) 77 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 78 | 79 | STATIC_URL = '/static/' 80 | 81 | THEMESWITCHER_DEFAULT_THEME = 'foo' 82 | THEMESWITCHER_THEMES = dict(foo='foo.css', bar='bar.css') 83 | 84 | 85 | # If we're running on the Jenkins server we want to archive the coverage 86 | # reports as XML. 87 | import os 88 | if os.environ.get('HUDSON_URL', None): 89 | TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' 90 | TEST_OUTPUT_VERBOSE = True 91 | TEST_OUTPUT_DESCRIPTIONS = True 92 | TEST_OUTPUT_DIR = 'xmlrunner' 93 | -------------------------------------------------------------------------------- /themeswitch/runtests/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Blank URLConf just to keep runtests.py happy. 3 | """ 4 | from rest_framework.compat import patterns 5 | 6 | urlpatterns = patterns('',) 7 | -------------------------------------------------------------------------------- /themeswitch/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | DEFAULT_THEME = getattr(settings, 'THEMESWITCHER_DEFAULT_THEME', None) 4 | THEMES = getattr(settings, 'THEMESWITCHER_THEMES', dict()) 5 | -------------------------------------------------------------------------------- /themeswitch/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nschlemm/django-themeswitch/371495b05abe14fddc2cabc9468f4fb2aedf77f7/themeswitch/templatetags/__init__.py -------------------------------------------------------------------------------- /themeswitch/templatetags/themeswitch_tags.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import ImproperlyConfigured 3 | from django.template.base import Library 4 | 5 | from themeswitch.settings import THEMES 6 | 7 | 8 | register = Library() 9 | 10 | 11 | @register.simple_tag(takes_context=True) 12 | def render_selected_theme_css(context): 13 | if 'selected_theme' not in context: 14 | context_processor = 'themeswitch.context_processors.selected_theme' 15 | if context_processor not in settings.TEMPLATE_CONTEXT_PROCESSORS: 16 | raise ImproperlyConfigured( 17 | 'Add %s to TEMPLATE_CONTEXT_PROCESSORS' % context_processor 18 | ) 19 | selected_theme = context['selected_theme'] 20 | if selected_theme and selected_theme in THEMES: 21 | return u'' % THEMES[selected_theme] 22 | 23 | return u'' 24 | 25 | 26 | @register.assignment_tag() 27 | def get_available_themes(): 28 | available_themes = THEMES.keys() 29 | available_themes.sort() 30 | return available_themes 31 | -------------------------------------------------------------------------------- /themeswitch/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | from django.test.client import RequestFactory 10 | from themeswitch.settings import THEMES 11 | from themeswitch.context_processors import selected_theme 12 | 13 | 14 | class ContextProcessorTest(TestCase): 15 | def setUp(self): 16 | self.factory = RequestFactory() 17 | 18 | def test_selected_theme(self): 19 | """ 20 | Tests that "selected_theme" is added to context 21 | """ 22 | request = self.factory.get('/') 23 | ctx = selected_theme(request) 24 | self.assertIn('selected_theme', ctx) 25 | 26 | self.assertNotIn('bogus', THEMES) 27 | self.factory.cookies['selected_theme'] = 'bogus' 28 | request = self.factory.get('/') 29 | ctx = selected_theme(request) 30 | self.assertIn('selected_theme', ctx) 31 | self.assertDictEqual(dict(selected_theme='foo'), ctx) 32 | 33 | self.assertIn('bar', THEMES) 34 | self.factory.cookies['selected_theme'] = 'bar' 35 | request = self.factory.get('/') 36 | ctx = selected_theme(request) 37 | self.assertDictEqual(dict(selected_theme='bar'), ctx) 38 | -------------------------------------------------------------------------------- /themeswitch/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | from .views import switch 4 | 5 | urlpatterns = patterns( 6 | '', 7 | url(r'^switch/$', switch, name='themeswitch-switch'), 8 | ) 9 | -------------------------------------------------------------------------------- /themeswitch/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseBadRequest, HttpResponseRedirect 2 | 3 | from .settings import THEMES 4 | 5 | 6 | def switch(request): 7 | if 'theme' not in request.GET: 8 | return HttpResponseBadRequest('No theme defined') 9 | 10 | theme = request.GET.get('theme') 11 | if theme and theme not in THEMES: 12 | return HttpResponseBadRequest('Unknown theme: "%s"' % theme) 13 | 14 | response = HttpResponseRedirect( 15 | request.GET.get('next', request.META.get('HTTP_REFERER', '/'), )) 16 | if theme: 17 | response.set_cookie('selected_theme', theme) 18 | else: 19 | response.delete_cookie('selected_theme') 20 | return response 21 | --------------------------------------------------------------------------------