├── .coveragerc
├── .gitignore
├── .travis.yml
├── CHANGELOG.rst
├── CONTRIBUTING.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs
└── screenshots
│ ├── index1.png
│ └── node1.png
├── manage.py
├── ninecms
├── __init__.py
├── admin.py
├── apps.py
├── checks.py
├── forms.py
├── locale
│ └── el
│ │ └── LC_MESSAGES
│ │ └── django.po
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── cache_clear.py
│ │ └── check_updates.py
├── migrations
│ ├── 0001_squashed_0024_auto_20150416_1551.py
│ ├── 0002_auto_20150519_1102.py
│ ├── 0003_auto_20150623_1731.py
│ ├── 0004_auto_20150624_1131.py
│ ├── 0005_auto_20150624_1841.py
│ ├── 0006_auto_20150701_1401.py
│ ├── 0007_auto_20150727_1833.py
│ ├── 0008_auto_20150819_1516.py
│ ├── 0009_auto_20150924_1456.py
│ ├── 0010_auto_20150924_1850.py
│ ├── 0011_auto_20151202_1400.py
│ ├── 0012_auto_20151218_1637.py
│ ├── 0013_auto_20160117_1209.py
│ └── __init__.py
├── models.py
├── settings.py
├── signals.py
├── static
│ └── ninecms
│ │ ├── admin.js
│ │ ├── ckeditor
│ │ └── build-config.js
│ │ ├── images
│ │ ├── favicon.ico
│ │ ├── flags
│ │ │ ├── af.png
│ │ │ ├── ar.png
│ │ │ ├── be.png
│ │ │ ├── bg.png
│ │ │ ├── bo.png
│ │ │ ├── ca.png
│ │ │ ├── cs.png
│ │ │ ├── da.png
│ │ │ ├── de.png
│ │ │ ├── el.png
│ │ │ ├── en.png
│ │ │ ├── eo.png
│ │ │ ├── es.png
│ │ │ ├── et.png
│ │ │ ├── eu.png
│ │ │ ├── fa.png
│ │ │ ├── fi.png
│ │ │ ├── fil.png
│ │ │ ├── fo.png
│ │ │ ├── fr.png
│ │ │ ├── ga.png
│ │ │ ├── gl.png
│ │ │ ├── he.png
│ │ │ ├── hi.png
│ │ │ ├── hr.png
│ │ │ ├── hu.png
│ │ │ ├── id.png
│ │ │ ├── is.png
│ │ │ ├── it.png
│ │ │ ├── ja.png
│ │ │ ├── km.png
│ │ │ ├── ko.png
│ │ │ ├── lb.png
│ │ │ ├── lt.png
│ │ │ ├── lv.png
│ │ │ ├── mn.png
│ │ │ ├── ms.png
│ │ │ ├── nb.png
│ │ │ ├── nl.png
│ │ │ ├── nn.png
│ │ │ ├── pl.png
│ │ │ ├── pt-br.png
│ │ │ ├── pt-pt.png
│ │ │ ├── ro.png
│ │ │ ├── ru.png
│ │ │ ├── sco.png
│ │ │ ├── se.png
│ │ │ ├── sk.png
│ │ │ ├── sl.png
│ │ │ ├── so.png
│ │ │ ├── sq.png
│ │ │ ├── sr.png
│ │ │ ├── sv.png
│ │ │ ├── tg.png
│ │ │ ├── th.png
│ │ │ ├── tl.png
│ │ │ ├── tr.png
│ │ │ ├── uk.png
│ │ │ ├── vi.png
│ │ │ ├── zh-hans.png
│ │ │ └── zh-hant.png
│ │ └── toplink.png
│ │ ├── layout.js
│ │ ├── masonry.js
│ │ └── style.css
├── templates
│ ├── admin
│ │ └── ninecms
│ │ │ ├── image
│ │ │ └── stacked.html
│ │ │ ├── index.html
│ │ │ ├── node
│ │ │ ├── change_form.html
│ │ │ └── change_list.html
│ │ │ └── pagetype
│ │ │ ├── change_form.html
│ │ │ └── perms_form.html
│ └── ninecms
│ │ ├── base.html
│ │ ├── block_contact.html
│ │ ├── block_content.html
│ │ ├── block_language.html
│ │ ├── block_login.html
│ │ ├── block_menu.html
│ │ ├── block_menu_breadcrumbs.html
│ │ ├── block_menu_header.html
│ │ ├── block_search.html
│ │ ├── block_search_results.html
│ │ ├── block_signal.html
│ │ ├── block_signal_terms.html
│ │ ├── block_static.html
│ │ ├── block_user_menu.html
│ │ ├── ckeditor.html
│ │ ├── field.html
│ │ ├── fieldset.html
│ │ ├── form_confirm.html
│ │ ├── form_logout.html
│ │ ├── form_non_field_errors.html
│ │ ├── glyphicon.html
│ │ ├── index.html
│ │ ├── mail_contact.txt
│ │ ├── mail_updates.txt
│ │ ├── messages.html
│ │ ├── pagination.html
│ │ └── robots.txt
├── templatetags
│ ├── __init__.py
│ └── ninecms_extras.py
├── tests
│ ├── __init__.py
│ ├── setup.py
│ ├── tests_content.py
│ ├── tests_content_i18n.py
│ ├── tests_content_login.py
│ ├── tests_content_login_simple.py
│ └── tests_no_content.py
├── urls.py
├── utils
│ ├── __init__.py
│ ├── manytomany.py
│ ├── media.py
│ ├── nodes.py
│ ├── perms.py
│ ├── render.py
│ ├── sanitize.py
│ ├── status.py
│ └── transliterate.py
└── views.py
├── requirements.txt
├── setup.py
└── tests
├── __init__.py
├── media
└── ninecms
│ └── basic
│ └── image
│ ├── test_big.jpg
│ ├── test_big_portrait.jpg
│ └── test_small.png
├── settings_test.py
├── templates
└── ninecms
│ ├── page_basic.html
│ └── page_front.html
└── urls.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit = */settings*.py,setup.py,manage.py
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Python
2 | # Byte-compiled / optimized / DLL files
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 |
7 | # Distribution / packaging
8 | .Python
9 | env/
10 | build/
11 | develop-eggs/
12 | dist/
13 | downloads/
14 | eggs/
15 | .eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # Unit test / coverage reports
26 | htmlcov/
27 | .tox/
28 | .coverage
29 | .coverage.*
30 | .cache
31 | nosetests.xml
32 | coverage.xml
33 | *,cover
34 |
35 | ### IDE
36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
37 | .idea/
38 |
39 | ### Fish vf
40 | .venv
41 |
42 | ### Django
43 | # Exclude all static files such as front-end libraries
44 | ninecms/static/ninecms/*
45 |
46 | # except ckeditor recommended build config
47 | !ninecms/static/ninecms/ckeditor/
48 | ninecms/static/ninecms/ckeditor/*
49 | !ninecms/static/ninecms/ckeditor/build-config.js
50 |
51 | # except NineCMS own images, js and css
52 | !ninecms/static/ninecms/images/
53 | !ninecms/static/ninecms/admin.js
54 | !ninecms/static/ninecms/layout.js
55 | !ninecms/static/ninecms/masonry.js
56 | !ninecms/static/ninecms/style.css
57 | tests/media/ninecms/basic/image/*/
58 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # http://docs.travis-ci.com/user/languages/python/
2 |
3 | language: python
4 |
5 | python:
6 | - 3.4
7 |
8 | install:
9 | - pip install -r requirements.txt
10 | - pip install coveralls
11 |
12 | script:
13 | coverage run --source='.' manage.py test ninecms
14 |
15 | after_success:
16 | coveralls
17 |
18 | notifications:
19 | email:
20 | on_success: change
21 | on_failure: change
22 |
23 | sudo: false
24 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Contributing
3 | ============
4 |
5 | Any contribution to the project is highly appreciated and the best will be done to respond to it.
6 |
7 | Authors
8 | -------
9 |
10 | 1. George Karakostas (gckarakostas@gmail.com)
11 | 2. Panayiotis Broumis (pbroumis@gmail.com)
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) George Karakostas and individual contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of django-ninecms (Nine CMS, 9cms) nor the names of its contributors may be used
15 | to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | include CONTRIBUTING.rst
4 | include CHANGELOG.rst
5 | recursive-include ninecms *
6 | recursive-include docs *
7 | recursive-include tests *
8 |
--------------------------------------------------------------------------------
/docs/screenshots/index1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/docs/screenshots/index1.png
--------------------------------------------------------------------------------
/docs/screenshots/node1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/docs/screenshots/node1.png
--------------------------------------------------------------------------------
/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", "tests.settings_test")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/ninecms/__init__.py:
--------------------------------------------------------------------------------
1 | """ NineCMS package """
2 | __version__ = '0.6.0'
3 | __author__ = 'George Karakostas'
4 | __copyright__ = 'Copyright 2015, George Karakostas'
5 | __licence__ = 'BSD-3'
6 |
7 | default_app_config = 'ninecms.apps.NineCMSConfig'
8 |
--------------------------------------------------------------------------------
/ninecms/apps.py:
--------------------------------------------------------------------------------
1 | """ Application name of Nine CMS for Admin """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.apps import AppConfig
8 |
9 |
10 | # noinspection PyUnresolvedReferences
11 | class NineCMSConfig(AppConfig):
12 | name = 'ninecms'
13 | verbose_name = "Nine CMS"
14 |
15 | def ready(self):
16 | import ninecms.signals
17 | import ninecms.checks
18 |
--------------------------------------------------------------------------------
/ninecms/checks.py:
--------------------------------------------------------------------------------
1 | """ NineCMS system checks """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 |
6 | from django.core.checks import register, Tags, Info
7 | from django.conf import settings
8 |
9 |
10 | # noinspection PyUnusedLocal
11 | @register(Tags.compatibility)
12 | def check_settings(app_configs, **kwargs):
13 | """ Check that settings are implemented properly for 9cms
14 | :param app_configs: a list of apps to be checks or None for all
15 | :param kwargs: keyword arguments
16 | :return: a list of errors
17 | """
18 | checks = []
19 | if not settings.MEDIA_ROOT:
20 | msg = ("No media root is specified in settings. A media folder is necessary for the user to upload images. "
21 | "You can specify one in the settings file as eg: `MEDIA_ROOT = os.path.join(BASE_DIR, 'media')`.")
22 | checks.append(Info(msg, id='ninecms.I001'))
23 | if not settings.MEDIA_URL:
24 | msg = ("No media url is specified in settings. A media url is necessary for 9cms to display uploaded images. "
25 | "You can specify one in the settings file as eg: `MEDIA_URL = '/media/'`.")
26 | checks.append(Info(msg, id='ninecms.I002'))
27 | if not settings.ADMINS:
28 | msg = ("No administrator emails are specified in settings. These are necessary for 9cms to send information "
29 | "on updates if a cron is setup. You can specify them in the settings file as eg: "
30 | "`ADMINS = (('Webmaster', 'web@9-dev.com'),)`.")
31 | checks.append(Info(msg, id='ninecms.I003'))
32 | if not settings.MANAGERS:
33 | msg = ("No manager emails are specified in settings. These are necessary for 9cms to messages through the "
34 | "contact form. You can specify them in the settings file as eg: "
35 | "`MANAGERS = (('Webmaster', 'web@9-dev.com'),)`.")
36 | checks.append(Info(msg, id='ninecms.I004'))
37 | if settings.SESSION_COOKIE_NAME == 'sessionid':
38 | msg = ("It is advised that you specify a session cookie name other than the default, especially in shared "
39 | "hosting environments. You can specify this in the settings file as eg: "
40 | "`SESSION_COOKIE_NAME = 'myapp_sessionid'`.")
41 | checks.append(Info(msg, id='ninecms.I005'))
42 | if settings.CACHES['default']['BACKEND'] == 'django.core.cache.backends.memcached.MemcachedCache':
43 | if not settings.CACHES['default']['KEY_PREFIX']:
44 | msg = ("It is advised that you specify a cache key prefix for the default memcached, especially in shared "
45 | "hosting environments. You can specify this in the settings file with "
46 | "``settings.CACHES['default']['KEY_PREFIX']`.")
47 | checks.append(Info(msg, id='ninecms.I006'))
48 | return checks
49 |
--------------------------------------------------------------------------------
/ninecms/forms.py:
--------------------------------------------------------------------------------
1 | """ Form definition for Nine CMS """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django import forms
8 | from django.forms.models import inlineformset_factory
9 | from django.contrib.auth.models import Group
10 | from django.utils.translation import ugettext_lazy as _
11 | from ninecms.models import Node, Image, File, Video, ContentBlock, PageType, TaxonomyTerm
12 | from ninecms.utils.sanitize import sanitize, ModelSanitizeForm
13 | from ninecms.utils.manytomany import ManyToManyModelForm, ModelBiMultipleChoiceField
14 |
15 |
16 | class PageTypeForm(ManyToManyModelForm):
17 | """ Override default page type form to show related blocks """
18 | blocks = ModelBiMultipleChoiceField(ContentBlock.objects.all(), double_list="Blocks")
19 |
20 | class Meta:
21 | """ Meta class """
22 | model = PageType
23 | fields = ['name', 'description', 'guidelines', 'url_pattern']
24 |
25 |
26 | class ContentTypePermissionsForm(forms.Form):
27 | """ Content type permissions form """
28 | add_node = forms.ModelMultipleChoiceField(
29 | queryset=Group.objects.all(),
30 | widget=forms.CheckboxSelectMultiple,
31 | required=False
32 | )
33 | change_node = forms.ModelMultipleChoiceField(
34 | queryset=Group.objects.all(),
35 | widget=forms.CheckboxSelectMultiple,
36 | required=False
37 | )
38 | delete_node = forms.ModelMultipleChoiceField(
39 | queryset=Group.objects.all(),
40 | widget=forms.CheckboxSelectMultiple,
41 | required=False
42 | )
43 |
44 |
45 | class ContentNodeEditForm(ManyToManyModelForm):
46 | """ Node edit or create form """
47 | terms = ModelBiMultipleChoiceField(TaxonomyTerm.objects.all(), double_list="Terms")
48 |
49 | def __init__(self, *args, **kwargs):
50 | """ Get user object to check if has full_html permission
51 | Checks if current_user is already set (eg from ModelAdmin)
52 | :param args: default arguments
53 | :param kwargs: default keywords, expecting user
54 | :return: None
55 | """
56 | try:
57 | self.current_user = kwargs.pop('user', self.current_user)
58 | except AttributeError:
59 | self.current_user = kwargs.pop('user', None)
60 | super(ContentNodeEditForm, self).__init__(*args, **kwargs)
61 |
62 | def clean(self):
63 | """ Override clean form to bleach
64 | :return: cleaned data
65 | """
66 | cleaned_data = super(ContentNodeEditForm, self).clean()
67 | full_html = self.current_user and self.current_user.has_perm('ninecms.use_full_html')
68 | for field in ('title', 'highlight', 'alias'):
69 | if field in cleaned_data:
70 | cleaned_data[field] = sanitize(cleaned_data[field], allow_html=False)
71 | for field in ('summary', 'body'):
72 | if field in cleaned_data:
73 | cleaned_data[field] = sanitize(cleaned_data[field], full_html=full_html)
74 | return cleaned_data
75 |
76 | class Meta:
77 | """ Form model and fields """
78 | model = Node
79 | fields = ['page_type', 'language', 'title', 'user', 'status', 'promote', 'sticky', 'created',
80 | 'original_translation', 'summary', 'body', 'highlight', 'link', 'weight', 'alias', 'redirect']
81 |
82 |
83 | class ImageForm(ModelSanitizeForm):
84 | """ Explicitly define image form in order to sanitize input """
85 | class Meta:
86 | """ Form meta """
87 | model = Image
88 | fields = ['title', 'group', 'image']
89 | sanitize = ['title', 'group']
90 |
91 | ImageInlineFormset = inlineformset_factory(Node, Image, form=ImageForm, extra=0, min_num=0)
92 |
93 |
94 | class FileForm(ModelSanitizeForm):
95 | """ Explicitly define file form in order to sanitize input """
96 | class Meta:
97 | """ Form meta """
98 | model = File
99 | fields = ['title', 'group', 'file']
100 | sanitize = ['title', 'group']
101 |
102 | FileInlineFormset = inlineformset_factory(Node, File, form=FileForm, extra=0, min_num=0)
103 |
104 |
105 | class VideoForm(ModelSanitizeForm):
106 | """ Explicitly define video form in order to sanitize input """
107 | class Meta:
108 | """ Form meta """
109 | model = Video
110 | fields = ['title', 'group', 'video', 'type', 'media']
111 | sanitize = ['title', 'group', 'type', 'media']
112 |
113 | VideoInlineFormset = inlineformset_factory(Node, Video, form=VideoForm, extra=0, min_num=0)
114 |
115 |
116 | class RedirectForm(forms.Form):
117 | """ General purpose form with a hidden redirect field, used in contact form, login form and as a logout form """
118 | attr = {'class': 'form-control'}
119 | redirect = forms.CharField(widget=forms.HiddenInput())
120 |
121 |
122 | class ContactForm(RedirectForm):
123 | """ Contact form """
124 | sender_name = forms.CharField(
125 | max_length=100,
126 | label=_("Your name"),
127 | widget=forms.TextInput(attrs=RedirectForm.attr)
128 | )
129 | sender_email = forms.EmailField(
130 | max_length=100,
131 | label=_("Your email"),
132 | widget=forms.TextInput(attrs=RedirectForm.attr)
133 | )
134 | subject = forms.CharField(max_length=255, widget=forms.TextInput(attrs=RedirectForm.attr))
135 | message = forms.CharField(widget=forms.Textarea(attrs=RedirectForm.attr))
136 |
137 | def clean(self):
138 | """ Additionally to Django clean() (https://docs.djangoproject.com/en/1.7/ref/forms/validation/)
139 | Sanitize HTML from form data (http://stackoverflow.com/questions/5641901/sanitizing-html-in-submitted-form-data)
140 | Otherwise the template will escape without stripping if not so specified
141 | :return: cleaned data
142 | """
143 | cleaned_data = super(ContactForm, self).clean()
144 | for field in ('sender_name', 'sender_email', 'message', 'redirect'):
145 | if field in cleaned_data:
146 | cleaned_data[field] = sanitize(cleaned_data[field], allow_html=False)
147 | if 'subject' in cleaned_data:
148 | cleaned_data['subject'] = "[Website Feedback] " + sanitize(cleaned_data['subject'], allow_html=False)
149 | return cleaned_data
150 |
151 |
152 | class LoginForm(RedirectForm):
153 | """ Login form """
154 | username = forms.CharField(max_length=255, label=_("Username"), widget=forms.TextInput(attrs=RedirectForm.attr))
155 | password = forms.CharField(max_length=255, label=_("Password"), widget=forms.PasswordInput(attrs=RedirectForm.attr))
156 |
157 |
158 | class SearchForm(forms.Form):
159 | """ Search form """
160 | q = forms.CharField(max_length=255, label=_("Search"), widget=forms.TextInput(attrs={'class': 'form-control'}))
161 |
162 | def clean(self):
163 | """ Override clean function to sanitize data
164 | :return: cleaned data
165 | """
166 | cleaned_data = super(forms.Form, self).clean()
167 | if 'q' in cleaned_data:
168 | cleaned_data['q'] = sanitize(cleaned_data['q'], allow_html=False)
169 | return cleaned_data
170 |
--------------------------------------------------------------------------------
/ninecms/management/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'gkarak'
2 |
--------------------------------------------------------------------------------
/ninecms/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'gkarak'
2 |
--------------------------------------------------------------------------------
/ninecms/management/commands/cache_clear.py:
--------------------------------------------------------------------------------
1 | """ Management command for clearing cache """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.core.management import BaseCommand
8 | from ninecms.utils.status import cache_clear
9 |
10 |
11 | class Command(BaseCommand):
12 | help = "Clears the cache."
13 |
14 | def handle(self, *args, **options):
15 | """ Core function
16 | :param args: None
17 | :param options: None
18 | :return: None
19 | """
20 | cache_clear()
21 | self.stdout.write("Cache cleared.")
22 |
--------------------------------------------------------------------------------
/ninecms/management/commands/check_updates.py:
--------------------------------------------------------------------------------
1 | """ Management command for checking updates """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.core.management import BaseCommand
8 | from django.core.cache import caches
9 | from django.core.mail import mail_admins
10 | from django.template import loader
11 | from django.utils.translation import ugettext as _
12 | from ninecms.utils.status import Capturing
13 | # noinspection PyPackageRequirements
14 | import pip
15 |
16 |
17 | class Command(BaseCommand):
18 | help = "Check for updates, store the results to cache and send email to admins."
19 |
20 | def handle(self, *args, **options):
21 | """ Core function
22 | :param args: None
23 | :param options: None
24 | :return: None
25 | """
26 | with Capturing() as updates:
27 | pip.main(['list', '--outdated', '--retries', '1'])
28 | cache = caches['default']
29 | if not updates: # pragma: nocover
30 | cache.delete('updates')
31 | else: # pragma: nocover
32 | cache.set('updates', updates, 7 * 24 * 60 * 60) # 1wk
33 | t = loader.get_template('ninecms/mail_updates.txt')
34 | mail_admins(_("New updates available"), t.render({'updates': updates}))
35 |
--------------------------------------------------------------------------------
/ninecms/migrations/0002_auto_20150519_1102.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0001_squashed_0024_auto_20150416_1551'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='menuitem',
16 | name='title',
17 | field=models.CharField(max_length=255),
18 | ),
19 | migrations.AlterField(
20 | model_name='taxonomyterm',
21 | name='name',
22 | field=models.CharField(max_length=50),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/ninecms/migrations/0003_auto_20150623_1731.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0002_auto_20150519_1102'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='node',
16 | name='language',
17 | field=models.CharField(max_length=2, choices=[('el', 'Greek'), ('en', 'English')], blank=True),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/ninecms/migrations/0004_auto_20150624_1131.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0003_auto_20150623_1731'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterModelOptions(
15 | name='node',
16 | options={'permissions': (('access_toolbar', 'Can access the CMS toolbar'), ('use_full_html', 'Can use Full HTML in node body and summary'), ('list_nodes', 'Can list nodes'))},
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/ninecms/migrations/0005_auto_20150624_1841.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0004_auto_20150624_1131'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterModelOptions(
15 | name='pagetype',
16 | options={'permissions': (('list_nodes_pagetype', 'List nodes of a specific page type'), ('add_node_pagetype', 'Add node of a specific page type'), ('change_node_pagetype', 'Change node of a specific page type'), ('delete_node_pagetype', 'Delete node of a specific page type')), 'ordering': ['id']},
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/ninecms/migrations/0006_auto_20150701_1401.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0005_auto_20150624_1841'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='pagelayoutelement',
16 | name='hidden',
17 | field=models.BooleanField(db_index=True, default=False),
18 | ),
19 | migrations.AlterField(
20 | model_name='node',
21 | name='language',
22 | field=models.CharField(max_length=2, blank=True, choices=[('el', 'Greek')]),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/ninecms/migrations/0007_auto_20150727_1833.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0006_auto_20150701_1401'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='contentblock',
16 | name='type',
17 | field=models.CharField(default='static', choices=[('static', 'Static: link to node'), ('menu', 'Menu: render a menu or submenu'), ('signal', 'Signal: call site-specific custom render'), ('language', 'Language: switch menu'), ('user-menu', 'User menu: render a menu with login/register and logout links'), ('login', 'Login: render login form'), ('search', 'Search: render search form'), ('search-results', 'Search: render search results'), ('contact', 'Contact: render contact form')], max_length=50),
18 | ),
19 | migrations.AlterField(
20 | model_name='node',
21 | name='language',
22 | field=models.CharField(choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmal'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-cn', 'Simplified Chinese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese'), ('zh-tw', 'Traditional Chinese')], max_length=2, blank=True),
23 | ),
24 | migrations.AlterUniqueTogether(
25 | name='pagelayoutelement',
26 | unique_together=set([]),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/ninecms/migrations/0008_auto_20150819_1516.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0007_auto_20150727_1833'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='taxonomyterm',
16 | name='description_node',
17 | field=models.ForeignKey(related_name='term_described', blank=True, to='ninecms.Node', null=True),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/ninecms/migrations/0009_auto_20150924_1456.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Custom migration for fixing page type name issue
3 | from __future__ import unicode_literals
4 |
5 | # noinspection PyUnresolvedReferences
6 | from django.db import models, migrations
7 | from django.conf import settings
8 | from ninecms.utils.transliterate import transliterate
9 | import os
10 |
11 |
12 | def transliterate_folder(field): # pragma: nocover
13 | """ Utility function to transliterate a path_file_name
14 | :param field: string with path_file_name
15 | :return: transliterated path_file_name
16 | """
17 | return '/'.join(list(transliterate(folder, True, True) for folder in field.split('/')))
18 |
19 |
20 | # noinspection PyUnusedLocal
21 | # noinspection PyPep8Naming
22 | def migrate_path_file_name(apps, schema_editor): # pragma: nocover
23 | """ Transliterate the directories of all saved files
24 | Transliterate the corresponding field values
25 | :param app registry
26 | :param schema_editor
27 | :return: None
28 | """
29 | basedir = os.path.join(settings.MEDIA_ROOT, 'ninecms')
30 | for folder in os.listdir(basedir):
31 | os.rename(os.path.join(basedir, folder), os.path.join(basedir, transliterate(folder, True, True)))
32 |
33 | Image = apps.get_model('ninecms', 'Image')
34 | for image in Image.objects.all():
35 | image.image.name = transliterate_folder(image.image.name)
36 | image.save()
37 |
38 | File = apps.get_model('ninecms', 'File')
39 | for file in File.objects.all():
40 | file.file.name = transliterate_folder(file.file.name)
41 | file.save()
42 |
43 | Video = apps.get_model('ninecms', 'Video')
44 | for video in Video.objects.all():
45 | video.video.name = transliterate_folder(video.video.name)
46 | video.save()
47 |
48 |
49 | # noinspection PyUnusedLocal
50 | def reverse(apps, schema_editor): # pragma: nocover
51 | """
52 | Reverse the above operation
53 | Nothing to do here, transliterated folder names remain
54 | :param apps: app registry
55 | :param schema_editor
56 | :return: None
57 | """
58 | pass
59 |
60 |
61 | class Migration(migrations.Migration):
62 |
63 | dependencies = [
64 | ('ninecms', '0008_auto_20150819_1516'),
65 | ]
66 |
67 | operations = [
68 | migrations.RunPython(migrate_path_file_name, reverse),
69 | ]
70 |
--------------------------------------------------------------------------------
/ninecms/migrations/0010_auto_20150924_1850.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('ninecms', '0009_auto_20150924_1456'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterModelOptions(
15 | name='node',
16 | options={'permissions': (('access_toolbar', 'Can access the CMS toolbar'), ('use_full_html', 'Can use Full HTML in node body and summary'), ('list_nodes', 'Can list nodes'), ('view_unpublished', 'Can view unpublished content'))},
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/ninecms/migrations/0011_auto_20151202_1400.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations, models
5 | import mptt.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('ninecms', '0010_auto_20150924_1850'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterModelOptions(
16 | name='node',
17 | options={'permissions': (('use_full_html', 'Can use Full HTML in node body and summary'), ('view_unpublished', 'Can view unpublished content'))},
18 | ),
19 | migrations.AlterModelOptions(
20 | name='pagetype',
21 | options={'ordering': ['id'], 'permissions': (('add_node_pagetype', 'Add node of a specific page type'), ('change_node_pagetype', 'Change node of a specific page type'), ('delete_node_pagetype', 'Delete node of a specific page type'))},
22 | ),
23 | migrations.AlterField(
24 | model_name='contentblock',
25 | name='classes',
26 | field=models.CharField(max_length=255, blank=True, help_text='Additional CSS classes to append to block.'),
27 | ),
28 | migrations.AlterField(
29 | model_name='contentblock',
30 | name='menu_item',
31 | field=mptt.fields.TreeForeignKey(blank=True, null=True, to='ninecms.MenuItem', help_text='The related parent menu item related (block type: menu only).'),
32 | ),
33 | migrations.AlterField(
34 | model_name='contentblock',
35 | name='node',
36 | field=models.ForeignKey(blank=True, null=True, to='ninecms.Node', help_text='The related node with this block (block type: static only).'),
37 | ),
38 | migrations.AlterField(
39 | model_name='contentblock',
40 | name='signal',
41 | field=models.CharField(max_length=100, blank=True, help_text='The signal name to trigger for a custom view (block type: signal only).'),
42 | ),
43 | migrations.AlterField(
44 | model_name='contentblock',
45 | name='type',
46 | field=models.CharField(max_length=50, choices=[('static', 'Static: link to node'), ('menu', 'Menu: render a menu or submenu'), ('signal', 'Signal: call site-specific custom render'), ('language', 'Language: switch menu'), ('user-menu', 'User menu: render a menu with login/register and logout links'), ('login', 'Login: render login form'), ('search', 'Search: render search form'), ('search-results', 'Search: render search results'), ('contact', 'Contact: render contact form')], help_text='How to render the block.', default='static'),
47 | ),
48 | migrations.AlterField(
49 | model_name='menuitem',
50 | name='language',
51 | # field=models.CharField(max_length=2, blank=True, choices=[('el', 'Greek')]),
52 | field=models.CharField(choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmal'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-cn', 'Simplified Chinese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese'), ('zh-tw', 'Traditional Chinese')], max_length=2, blank=True),
53 | ),
54 | # migrations.AlterField(
55 | # model_name='node',
56 | # name='language',
57 | # field=models.CharField(max_length=2, blank=True, choices=[('el', 'Greek')]),
58 | # ),
59 | migrations.AlterField(
60 | model_name='pagelayoutelement',
61 | name='region',
62 | field=models.CharField(max_length=50, help_text='A hard coded region name that is rendered in template index and also used in theme suggestions .', db_index=True),
63 | ),
64 | migrations.AlterField(
65 | model_name='pagelayoutelement',
66 | name='weight',
67 | field=models.IntegerField(help_text='Elements with greater number in the same region sink to the bottom of the page.', default=0, db_index=True),
68 | ),
69 | migrations.AlterField(
70 | model_name='pagetype',
71 | name='description',
72 | field=models.CharField(max_length=255, help_text='Describe the page type.'),
73 | ),
74 | migrations.AlterField(
75 | model_name='pagetype',
76 | name='guidelines',
77 | field=models.CharField(max_length=255, blank=True, help_text='Provide content submission guidelines for this page type.'),
78 | ),
79 | migrations.AlterField(
80 | model_name='pagetype',
81 | name='name',
82 | field=models.CharField(max_length=100, unique=True, help_text='Specify a unique page type name. A machine name is recommended if to be used in code.'),
83 | ),
84 | migrations.AlterField(
85 | model_name='pagetype',
86 | name='template',
87 | field=models.CharField(max_length=255, blank=True, help_text='Custom template name (deprecated).'),
88 | ),
89 | migrations.AlterField(
90 | model_name='pagetype',
91 | name='url_pattern',
92 | field=models.CharField(max_length=255, blank=True, help_text='Default pattern for page type, if no alias is specified in node edit. More info .'),
93 | ),
94 | ]
95 |
--------------------------------------------------------------------------------
/ninecms/migrations/0013_auto_20160117_1209.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.1 on 2016-01-17 10:09
3 |
4 | # Overridden migration to prepare default value for block name
5 | # and to transfer elements to new m2m field
6 |
7 | # Several lines excluded from tests with pragma nocover
8 | # Haven't found a working way to test migrations, the following have been tested:
9 | # https://micknelson.wordpress.com/2013/03/01/testing-django-migrations/
10 | # https://www.caktusgroup.com/blog/2016/02/02/writing-unit-tests-django-migrations/
11 | # Other migrations include 0009
12 |
13 | from __future__ import unicode_literals
14 | from django.db import migrations, models
15 |
16 |
17 | def str_block(block): # pragma: nocover
18 | """ Get title based on block type
19 | The default `__str__` methods do not operate within migrations
20 | :return: model title
21 | """
22 | if block.type == 'static':
23 | return '-'.join((block.type, block.node.title))
24 | elif block.type == 'menu':
25 | return '-'.join((block.type, block.menu_item.title))
26 | elif block.type == 'signal':
27 | return '-'.join((block.type, block.signal))
28 | return block.type
29 |
30 |
31 | # noinspection PyUnusedLocal
32 | # noinspection PyPep8Naming
33 | def provide_block_name_default(apps, schema_editor):
34 | """ Provide a default block name in order for the next migration to establish field unique
35 | :param apps: app registry
36 | :param schema_editor
37 | :return: None
38 | """
39 | Block = apps.get_model('ninecms', 'ContentBlock')
40 | for block in Block.objects.all(): # pragma: nocover
41 | block.name = '%s-%d' % (str_block(block), block.pk)
42 | block.save()
43 |
44 |
45 | # noinspection PyUnusedLocal
46 | # noinspection PyPep8Naming
47 | def transfer_elements(apps, schema_editor):
48 | """ Transfer records from deprecated PageLayoutElements to new page_types block field
49 | :param apps
50 | :param schema_editor
51 | :return: None
52 | """
53 | PageLayoutElement = apps.get_model('ninecms', 'PageLayoutElement')
54 | for element in PageLayoutElement.objects.all(): # pragma: nocover
55 | element.block.page_types.add(element.page_type)
56 |
57 |
58 | # noinspection PyUnusedLocal
59 | def reverse(apps, schema_editor): # pragma: nocover
60 | """
61 | Reverse the above operations
62 | Nothing to do here, data in fields will be removed anyway
63 | :param apps: app registry
64 | :param schema_editor
65 | :return: None
66 | """
67 | pass
68 |
69 |
70 | class Migration(migrations.Migration):
71 | """ Migration class """
72 |
73 | dependencies = [
74 | ('ninecms', '0012_auto_20151218_1637'),
75 | ]
76 |
77 | operations = [
78 | migrations.AddField(
79 | model_name='contentblock',
80 | name='name',
81 | field=models.CharField(
82 | help_text='Specify a unique block machine name.',
83 | max_length=100,
84 | null=True,
85 | verbose_name='name'
86 | ),
87 | ),
88 | migrations.AddField(
89 | model_name='contentblock',
90 | name='page_types',
91 | field=models.ManyToManyField(
92 | blank=True,
93 | related_name='blocks',
94 | to='ninecms.PageType',
95 | verbose_name='page types'
96 | ),
97 | ),
98 | migrations.AlterField(
99 | model_name='menuitem',
100 | name='language',
101 | field=models.CharField(
102 | blank=True,
103 | choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-co', 'Colombian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gd', 'Scottish Gaelic'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmal'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese')], max_length=2, verbose_name='language'),
104 | ),
105 | migrations.AlterField(
106 | model_name='node',
107 | name='language',
108 | field=models.CharField(
109 | blank=True,
110 | choices=[('af', 'Afrikaans'), ('ar', 'Arabic'), ('ast', 'Asturian'), ('az', 'Azerbaijani'), ('bg', 'Bulgarian'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('br', 'Breton'), ('bs', 'Bosnian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('el', 'Greek'), ('en', 'English'), ('en-au', 'Australian English'), ('en-gb', 'British English'), ('eo', 'Esperanto'), ('es', 'Spanish'), ('es-ar', 'Argentinian Spanish'), ('es-co', 'Colombian Spanish'), ('es-mx', 'Mexican Spanish'), ('es-ni', 'Nicaraguan Spanish'), ('es-ve', 'Venezuelan Spanish'), ('et', 'Estonian'), ('eu', 'Basque'), ('fa', 'Persian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Frisian'), ('ga', 'Irish'), ('gd', 'Scottish Gaelic'), ('gl', 'Galician'), ('he', 'Hebrew'), ('hi', 'Hindi'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('ia', 'Interlingua'), ('id', 'Indonesian'), ('io', 'Ido'), ('is', 'Icelandic'), ('it', 'Italian'), ('ja', 'Japanese'), ('ka', 'Georgian'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('kn', 'Kannada'), ('ko', 'Korean'), ('lb', 'Luxembourgish'), ('lt', 'Lithuanian'), ('lv', 'Latvian'), ('mk', 'Macedonian'), ('ml', 'Malayalam'), ('mn', 'Mongolian'), ('mr', 'Marathi'), ('my', 'Burmese'), ('nb', 'Norwegian Bokmal'), ('ne', 'Nepali'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('os', 'Ossetic'), ('pa', 'Punjabi'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('pt-br', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('sq', 'Albanian'), ('sr', 'Serbian'), ('sr-latn', 'Serbian Latin'), ('sv', 'Swedish'), ('sw', 'Swahili'), ('ta', 'Tamil'), ('te', 'Telugu'), ('th', 'Thai'), ('tr', 'Turkish'), ('tt', 'Tatar'), ('udm', 'Udmurt'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese')], max_length=2, verbose_name='language'),
111 | ),
112 | migrations.RemoveField(
113 | model_name='pagetype',
114 | name='template',
115 | ),
116 | migrations.RunPython(provide_block_name_default, reverse),
117 | migrations.RunPython(transfer_elements, reverse),
118 | ]
119 |
--------------------------------------------------------------------------------
/ninecms/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/migrations/__init__.py
--------------------------------------------------------------------------------
/ninecms/settings.py:
--------------------------------------------------------------------------------
1 | """ Settings default definition for Nine CMS """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | """ Default recommended settings """
8 | # INSTALLED_APPS = (
9 | # 'admin_bootstrapped_plus',
10 | # # 'bootstrap3',
11 | # 'django_admin_bootstrapped',
12 | # 'django.contrib.admin',
13 | # 'django.contrib.auth',
14 | # 'django.contrib.contenttypes',
15 | # 'django.contrib.sessions',
16 | # 'django.contrib.messages',
17 | # 'django.contrib.staticfiles',
18 | # 'mptt',
19 | # 'debug_toolbar',
20 | # 'guardian',
21 | # 'ninecms',
22 | # 'myproject_core'
23 | # )
24 | #
25 | # MIDDLEWARE_CLASSES = (
26 | # 'django.middleware.cache.UpdateCacheMiddleware',
27 | # 'django.contrib.sessions.middleware.SessionMiddleware',
28 | # 'django.middleware.locale.LocaleMiddleware',
29 | # 'django.middleware.common.CommonMiddleware',
30 | # 'django.middleware.cache.FetchFromCacheMiddleware',
31 | # 'django.middleware.csrf.CsrfViewMiddleware',
32 | # 'django.contrib.auth.middleware.AuthenticationMiddleware',
33 | # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
34 | # 'django.contrib.messages.middleware.MessageMiddleware',
35 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
36 | # 'django.middleware.security.SecurityMiddleware',
37 | # )
38 |
39 | # ROOT_URLCONF = 'myproject.urls'
40 |
41 | # TEMPLATES = [
42 | # {
43 | # 'BACKEND': 'django.template.backends.django.DjangoTemplates',
44 | # 'DIRS': [
45 | # os.path.join(BASE_DIR, 'templates'),
46 | # ],
47 | # 'APP_DIRS': True,
48 | # 'OPTIONS': {
49 | # 'context_processors': [
50 | # 'django.template.context_processors.debug',
51 | # 'django.template.context_processors.request',
52 | # 'django.contrib.auth.context_processors.auth',
53 | # 'django.contrib.messages.context_processors.messages',
54 | # ],
55 | # 'debug': True,
56 | # },
57 | # },
58 | # ]
59 |
60 | # WSGI_APPLICATION = 'myproject.wsgi.application'
61 |
62 |
63 | # DATABASES = {
64 | # 'default': {
65 | # 'ENGINE': 'django.db.backends.sqlite3',
66 | # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
67 | # }
68 | # }
69 |
70 |
71 | # LANGUAGE_CODE = 'el'
72 | #
73 | # LANGUAGES = (
74 | # ('el', 'Greek'),
75 | # # ('en', 'English'),
76 | # )
77 | #
78 | # TIME_ZONE = 'Europe/Athens'
79 |
80 | # USE_I18N = True
81 | #
82 | # USE_L10N = True
83 | #
84 | # USE_TZ = True
85 |
86 | # STATIC_URL = '/static/'
87 | #
88 | # # Following remains for PyCharm code inspections in templates; deprecated in Django 1.8
89 | # TEMPLATE_DIRS = (
90 | # os.path.join(BASE_DIR, 'templates'),
91 | # )
92 |
93 | # STATICFILES_DIRS = (
94 | # os.path.join(BASE_DIR, "static"),
95 | # )
96 |
97 | # MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
98 | #
99 | # MEDIA_URL = '/media/'
100 |
101 | # LOGIN_URL = '/admin/login/'
102 |
103 | # # Error reporting
104 | #
105 | # ADMINS = (
106 | # ("Webmaster", "web@9-dev.com"),
107 | # )
108 | #
109 | # MANAGERS = (
110 | # ("Webmaster", "web@9-dev.com"),
111 | # )
112 | #
113 | # EMAIL_HOST = 'mail.9-dev.com'
114 | #
115 | # EMAIL_HOST_USER = 'ninecms@9-dev.com'
116 | #
117 | # EMAIL_HOST_PASSWORD = ''
118 | #
119 | # EMAIL_USE_SSL = True
120 | #
121 | # EMAIL_PORT = 465
122 | #
123 | # EMAIL_SUBJECT_PREFIX = '[9cms] '
124 | #
125 | # SERVER_EMAIL = 'ninecms@9-dev.com'
126 | #
127 | # DEFAULT_FROM_EMAIL = 'ninecms@9-dev.com'
128 | #
129 | #
130 | # # Security
131 | # # http://django-secure.readthedocs.org/en/latest/settings.html
132 | #
133 | # SECURE_CONTENT_TYPE_NOSNIFF = True
134 | #
135 | # SECURE_BROWSER_XSS_FILTER = True
136 | #
137 | # X_FRAME_OPTIONS = 'DENY'
138 | #
139 | # CSRF_COOKIE_HTTPONLY = True
140 | #
141 | # # SSL only
142 | # # SECURE_SSL_REDIRECT = True
143 | #
144 | # # SECURE_HSTS_SECONDS = 31536000
145 | #
146 | # # SECURE_HSTS_INCLUDE_SUBDOMAINS = True
147 | #
148 | # # SESSION_COOKIE_SECURE = True
149 | #
150 | # # CSRF_COOKIE_SECURE = True
151 |
152 | # # Add unique session cookie name for concurrent logins with other sites
153 | # SESSION_COOKIE_NAME = 'myapp_sessionid'
154 | #
155 | # # Caches
156 | # # https://docs.djangoproject.com/en/1.8/topics/cache/
157 | #
158 | # CACHES = {
159 | # 'default': {
160 | # 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
161 | # }
162 | # }
163 | #
164 | # CACHE_MIDDLEWARE_SECONDS = 3 * 60 * 60
165 |
166 | # # Guardian
167 | # # https://django-guardian.readthedocs.org/en/v1.2/configuration.html
168 | #
169 | # AUTHENTICATION_BACKENDS = (
170 | # 'django.contrib.auth.backends.ModelBackend', # this is default
171 | # 'guardian.backends.ObjectPermissionBackend',
172 | # )
173 | #
174 | # ANONYMOUS_USER_ID = -1
175 |
176 | # # Django admin
177 | # DAB_FIELD_RENDERER = 'django_admin_bootstrapped.renderers.BootstrapFieldRenderer'
178 | #
179 | # MESSAGE_TAGS = {
180 | # messages.SUCCESS: 'alert-success success',
181 | # messages.WARNING: 'alert-warning warning',
182 | # messages.ERROR: 'alert-danger error'
183 | # }
184 |
185 |
186 | """ Test """
187 | # from myapp.settings import *
188 | #
189 | # DEBUG = True
190 | #
191 | # PASSWORD_HASHERS = (
192 | # 'django.contrib.auth.hashers.MD5PasswordHasher',
193 | # )
194 | #
195 | # TEMPLATES = [
196 | # {
197 | # 'BACKEND': 'django.template.backends.django.DjangoTemplates',
198 | # 'DIRS': [ # disable overriden templates
199 | # ],
200 | # 'APP_DIRS': True,
201 | # 'OPTIONS': {
202 | # 'context_processors': [
203 | # 'django.template.context_processors.debug',
204 | # 'django.template.context_processors.request',
205 | # 'django.contrib.auth.context_processors.auth',
206 | # 'django.contrib.messages.context_processors.messages',
207 | # ],
208 | # 'debug': True,
209 | # },
210 | # },
211 | # ]
212 | #
213 | # DATABASES = {
214 | # 'default': {
215 | # 'ENGINE': 'django.db.backends.sqlite3',
216 | # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
217 | # }
218 | # }
219 | #
220 | # LANGUAGES = (
221 | # ('el', 'Greek'),
222 | # ('en', 'English'),
223 | # )
224 | #
225 | # IMAGE_STYLES.update({
226 | # 'thumbnail-upscale': {
227 | # 'type': 'thumbnail-upscale',
228 | # 'size': (150, 150)
229 | # },
230 | # })
231 |
232 | """ Live """
233 | # # noinspection PyUnresolvedReferences
234 | # from myapp.settings import *
235 | #
236 | # DEBUG = False
237 | #
238 | # ALLOWED_HOSTS = [
239 | # '',
240 | # ]
241 | #
242 | # TEMPLATE_DIRS = None
243 | #
244 | # TEMPLATES = [
245 | # {
246 | # 'BACKEND': 'django.template.backends.django.DjangoTemplates',
247 | # 'DIRS': [
248 | # os.path.join(BASE_DIR, 'templates'),
249 | # ],
250 | # 'APP_DIRS': True,
251 | # 'OPTIONS': {
252 | # 'context_processors': [
253 | # 'django.template.context_processors.debug',
254 | # 'django.template.context_processors.request',
255 | # 'django.contrib.auth.context_processors.auth',
256 | # 'django.contrib.messages.context_processors.messages',
257 | # ],
258 | # },
259 | # },
260 | # ]
261 | #
262 | # STATIC_ROOT = '/var/www'
263 | #
264 | # STATICFILES_DIRS = []
265 | #
266 | # CACHES = {
267 | # 'default': {
268 | # 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
269 | # 'LOCATION': '127.0.0.1:11211',
270 | # 'TIMEOUT': 3 * 60 * 60, # 3h
271 | # 'KEY_PREFIX': 'rabelvideo_',
272 | # 'VERSION': 1,
273 | # }
274 | # }
275 |
276 |
277 | """ CMS """
278 |
279 | # Site name to display in title etc.
280 | SITE_NAME = "9cms"
281 |
282 | # Meta author tag
283 | SITE_AUTHOR = "9cms"
284 |
285 | # Meta keywords
286 | SITE_KEYWORDS = ""
287 |
288 | # Define image styles
289 | IMAGE_STYLES = {
290 | 'thumbnail': {
291 | 'type': 'thumbnail',
292 | 'size': (150, 1000)
293 | },
294 | 'thumbnail_crop': {
295 | 'type': 'thumbnail-crop',
296 | 'size': (150, 150)
297 | },
298 | 'thumbnail_upscale': {
299 | 'type': 'thumbnail-upscale',
300 | 'size': (150, 150)
301 | },
302 | 'gallery_style': {
303 | 'type': 'thumbnail',
304 | 'size': (400, 1000)
305 | },
306 | 'blog_style': {
307 | 'type': 'thumbnail-crop',
308 | 'size': (350, 226)
309 | },
310 | 'large': {
311 | 'type': 'thumbnail',
312 | 'size': (1280, 1280)
313 | },
314 | }
315 |
316 | # Update image styles in project settings such as:
317 | # IMAGE_STYLES.update({})
318 |
319 | # Define characters to remove at transliteration
320 | TRANSLITERATE_REMOVE = '"\'`,:;|{[}]+=*&%^$#@!~()?<>'
321 |
322 | # Define characters to replace at transliteration
323 | TRANSLITERATE_REPLACE = (' .-_/', '-----')
324 |
325 | # Define language menu labels
326 | # Possible values: name, code, flag
327 | LANGUAGE_MENU_LABELS = 'name'
328 |
329 | # Enable i18n urls for 9cms
330 | I18N_URLS = True
331 |
--------------------------------------------------------------------------------
/ninecms/signals.py:
--------------------------------------------------------------------------------
1 | """ Signal definitions for Nine CMS """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django import dispatch
8 | from django.db.models.signals import pre_delete
9 | from django.contrib.contenttypes.models import ContentType
10 | # noinspection PyPackageRequirements
11 | from guardian.models import GroupObjectPermission
12 | from ninecms.models import TaxonomyTerm, Node, PageType, Video, Image, File
13 | from ninecms.utils.media import delete_all
14 |
15 |
16 | # noinspection PyUnusedLocal
17 | @dispatch.receiver(pre_delete)
18 | def pre_delete_tasks(sender, instance, **kwargs):
19 | """ Delete all relevant permissions from guardian as there is no foreign key to the object
20 | http://django-guardian.readthedocs.org/en/stable/userguide/caveats.html
21 | Delete the respective file when an image, a video or a file record is to be deleted
22 | :param sender: the model to be deleted
23 | :param instance: the page type object to be deleted
24 | :param kwargs: other arguments
25 | :return: None
26 | """
27 | if sender == PageType:
28 | content_type = ContentType.objects.get_for_model(instance)
29 | GroupObjectPermission.objects.filter(content_type=content_type, object_pk=instance.pk).delete()
30 | elif sender == Image:
31 | delete_all(instance.image.path)
32 | elif sender == Video:
33 | delete_all(instance.video.path)
34 | elif sender == File:
35 | delete_all(instance.file.path)
36 |
37 |
38 | block_signal = dispatch.Signal(providing_args=['view', 'request'])
39 |
40 |
41 | @dispatch.receiver(block_signal)
42 | def render_view(**kwargs):
43 | """ Example of custom views
44 | :param kwargs: 'view' contains the CMS view to render
45 | :return: None
46 | """
47 | if kwargs['view'] == 'terms':
48 | return TaxonomyTerm.objects.all()
49 |
--------------------------------------------------------------------------------
/ninecms/static/ninecms/admin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file admin.js
3 | * Provide ajax operations for 9cms admin
4 | *
5 | * @author
6 | * George Karakostas
7 | *
8 | * @copyright
9 | * (c) 2015 George Karakostas
10 | *
11 | * @license
12 | * BSD-3
13 | *
14 | * @email
15 | * gkarak@9-dev.com
16 | */
17 |
18 | (function($) {
19 | /**
20 | * Add a css class on element for a specified time
21 | * Combine with css transition for a nice effect
22 | * Alternatively use jQueryUI addClass
23 | *
24 | * @param css: the class to add
25 | * @param time: for how long
26 | */
27 | $.fn.addClassTemp = function(css, time) {
28 | return this
29 | .addClass(css)
30 | .delay(time)
31 | .queue(function() {
32 | $(this)
33 | .removeClass(css)
34 | .dequeue();
35 | });
36 | };
37 |
38 | $(document).ready(function() {
39 | /*******************
40 | * Ajax edit inline
41 | ******************/
42 | $(this).djangoAjaxSetup({csrf_cookie_httponly: true});
43 | $('.edit-inline').on('click', function(e) {
44 | var t = $(this);
45 | /**
46 | * Ajax success callback function (see below)
47 | * Parse the serialized data returned in form eg. id=1&status=True
48 | * then find the row, then the a element and set value and glyphicon
49 | *
50 | * @param json: data returned
51 | */
52 | var ajaxSuccess = function(json) {
53 | var data = this.data.match(/id=([-_\w\d]+)&([-_\w\d]+)=([-_\w\d]+)/);
54 | $('tr[data-id="' + data[1] + '"]')
55 | .find('a[data-field="' + data[2] + '"]')
56 | .each(function() {
57 | if (json.result) {
58 | var value = data[3];
59 | if ($(this).data('type') == 'bool') {
60 | value = (value == 'True')? 'ok': 'remove';
61 | value = ' '
62 | }
63 | $(this)
64 | .data('value', data[3]).html(value)
65 | .addClassTemp('text-success', 4000);
66 | }
67 | else $(this).addClassTemp('text-danger', 4000);
68 | console.log(json);
69 | });
70 | };
71 | // construct the post data
72 | var data = {id: t.parents('tr').data('id')};
73 | var value = t.data('value');
74 | if (t.data('type') == 'bool') {
75 | if (value == 'True') value = 'False';
76 | else value = 'True';
77 | }
78 | data[t.data('field')] = value;
79 | $.ajax({
80 | url: t.parents('tr').data('edit-inline-url'),
81 | type: 'POST',
82 | data: data,
83 | success: ajaxSuccess
84 | });
85 | e.preventDefault();
86 | });
87 | });
88 | })(jQuery);
89 |
--------------------------------------------------------------------------------
/ninecms/static/ninecms/ckeditor/build-config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license
4 | */
5 |
6 | /**
7 | * This file was added automatically by CKEditor builder.
8 | * You may re-use it at any time to build CKEditor again.
9 | *
10 | * If you would like to build CKEditor online again
11 | * (for example to upgrade), visit one the following links:
12 | *
13 | * (1) http://ckeditor.com/builder
14 | * Visit online builder to build CKEditor from scratch.
15 | *
16 | * (2) http://ckeditor.com/builder/0f20eddcb7463a55558fc4f293ca0dda
17 | * Visit online builder to build CKEditor, starting with the same setup as before.
18 | *
19 | * (3) http://ckeditor.com/builder/download/0f20eddcb7463a55558fc4f293ca0dda
20 | * Straight download link to the latest version of CKEditor (Optimized) with the same setup as before.
21 | *
22 | * NOTE:
23 | * This file is not used by CKEditor, you may remove it.
24 | * Changing this file will not change your CKEditor configuration.
25 | */
26 |
27 | var CKBUILDER_CONFIG = {
28 | skin: 'moono',
29 | preset: 'basic',
30 | ignore: [
31 | '.bender',
32 | 'bender.js',
33 | 'bender-err.log',
34 | 'bender-out.log',
35 | 'dev',
36 | '.DS_Store',
37 | '.editorconfig',
38 | '.gitattributes',
39 | '.gitignore',
40 | 'gruntfile.js',
41 | '.idea',
42 | '.jscsrc',
43 | '.jshintignore',
44 | '.jshintrc',
45 | 'less',
46 | '.mailmap',
47 | 'node_modules',
48 | 'package.json',
49 | 'README.md',
50 | 'tests'
51 | ],
52 | plugins : {
53 | 'basicstyles' : 1,
54 | 'blockquote' : 1,
55 | 'clipboard' : 1,
56 | 'codeTag' : 1,
57 | 'div' : 1,
58 | 'elementspath' : 1,
59 | 'enterkey' : 1,
60 | 'entities' : 1,
61 | 'filebrowser' : 1,
62 | 'find' : 1,
63 | 'floatingspace' : 1,
64 | 'format' : 1,
65 | 'horizontalrule' : 1,
66 | 'htmlwriter' : 1,
67 | 'image' : 1,
68 | 'indentblock' : 1,
69 | 'indentlist' : 1,
70 | 'justify' : 1,
71 | 'link' : 1,
72 | 'list' : 1,
73 | 'magicline' : 1,
74 | 'maximize' : 1,
75 | 'newpage' : 1,
76 | 'pagebreak' : 1,
77 | 'pastefromword' : 1,
78 | 'pastetext' : 1,
79 | 'pbckcode' : 1,
80 | 'quicktable' : 1,
81 | 'removeformat' : 1,
82 | 'resize' : 1,
83 | 'scayt' : 1,
84 | 'selectall' : 1,
85 | 'showblocks' : 1,
86 | 'showborders' : 1,
87 | 'sourcearea' : 1,
88 | 'specialchar' : 1,
89 | 'stylescombo' : 1,
90 | 'tab' : 1,
91 | 'tabletools' : 1,
92 | 'toolbar' : 1,
93 | 'undo' : 1,
94 | 'widget' : 1,
95 | 'wordcount' : 1,
96 | 'wsc' : 1,
97 | 'wysiwygarea' : 1,
98 | 'youtube' : 1
99 | },
100 | languages : {
101 | 'el' : 1,
102 | 'en' : 1
103 | }
104 | };
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/favicon.ico
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/af.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/af.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ar.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/be.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/be.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/bg.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/bo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/bo.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ca.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ca.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/cs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/cs.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/da.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/da.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/de.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/de.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/el.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/el.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/en.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/eo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/eo.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/es.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/es.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/et.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/et.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/eu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/eu.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/fa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/fa.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/fi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/fi.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/fil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/fil.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/fo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/fo.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/fr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/fr.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ga.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/gl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/gl.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/he.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/he.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/hi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/hi.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/hr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/hr.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/hu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/hu.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/id.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/is.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/is.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/it.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/it.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ja.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ja.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/km.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/km.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ko.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ko.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/lb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/lb.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/lt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/lt.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/lv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/lv.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/mn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/mn.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ms.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/nb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/nb.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/nl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/nl.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/nn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/nn.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/pl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/pl.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/pt-br.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/pt-br.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/pt-pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/pt-pt.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ro.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/ru.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/ru.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/sco.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/sco.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/se.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/se.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/sk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/sk.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/sl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/sl.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/so.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/so.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/sq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/sq.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/sr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/sr.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/sv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/sv.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/tg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/tg.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/th.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/th.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/tl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/tl.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/tr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/tr.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/uk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/uk.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/vi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/vi.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/zh-hans.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/zh-hans.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/flags/zh-hant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/flags/zh-hant.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/images/toplink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/ninecms/static/ninecms/images/toplink.png
--------------------------------------------------------------------------------
/ninecms/static/ninecms/layout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file layout.js
3 | * Provide js-level formatting
4 | *
5 | * @author
6 | * George Karakostas
7 | *
8 | * @copyright
9 | * (c) 2015 George Karakostas
10 | *
11 | * @license
12 | * BSD-3
13 | *
14 | * @email
15 | * gkarak@9-dev.com
16 | */
17 |
18 | (function ($) {
19 | jQuery.fn.topLink = function(settings) {
20 | settings = jQuery.extend({
21 | min: 1,
22 | fadeSpeed: 200
23 | }, settings);
24 | return this.each(function() {
25 | //listen for scroll
26 | var el = jQuery(this);
27 | el.hide(); //in case the user forgot
28 | jQuery(window).scroll(function() {
29 | if(jQuery(window).scrollTop() >= settings.min) {
30 | el.fadeIn(settings.fadeSpeed);
31 | }
32 | else {
33 | el.fadeOut(settings.fadeSpeed);
34 | }
35 | });
36 | });
37 | };
38 |
39 | $(document).ready(function () {
40 | // Loader
41 | // hide scroll and show on window.load
42 | var loader = $('#loader');
43 | if (loader.length && loader.css('display') != 'none') $('body').css({overflow: 'hidden'});
44 |
45 | // Page top
46 | $('#top-link')
47 | // set the link
48 | .topLink({
49 | min: 400,
50 | fadeSpeed: 500
51 | })
52 | // smooth scroll
53 | .click(function(e) {
54 | e.preventDefault();
55 | $.scrollTo(0,300);
56 | });
57 |
58 | // Show/hide toolbar
59 | $('.toolbar-handler').on('click', 'a', function(e) {
60 | $('body').toggleClass('toolbar').find('.toolbar').toggleClass('hide');
61 | e.preventDefault();
62 | });
63 |
64 | // Shrink
65 | if ($('.shrinkable').length) {
66 | $(window).scroll(function() {
67 | if ($(this).scrollTop() > 50)
68 | $('.shrinkable').addClass('shrink');
69 | else $('.shrinkable').removeClass('shrink');
70 | });
71 | }
72 |
73 | // Bookmark smooth scroll
74 | $('.nav a[href*="#"]').each(function() {
75 | var bookmark = $(this).attr('href').match(/(#.*)$/g)[0];
76 | if (bookmark == '#') return;
77 | if (!$(bookmark).length) return;
78 | $(this).on('click', function() {
79 | $.scrollTo(bookmark, 300, {offset: {top: -50, left: 0}});
80 | });
81 | });
82 |
83 | // bootstrap
84 | $('input[type="text"], input[type="number"], input[type="email"], input[type="url"], select')
85 | .addClass('form-control');
86 |
87 | });
88 |
89 | $(window).load(function () {
90 | // Loader
91 | // hide loader and show scrollbars (hidden in document.ready)
92 | $('#loader, #loader-container').fadeOut('slow');
93 | $('body').css({'overflow': 'visible'});
94 | });
95 |
96 | }(jQuery));
97 |
--------------------------------------------------------------------------------
/ninecms/static/ninecms/masonry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file layout.js
3 | * Provide js-level formatting
4 | *
5 | * @author
6 | * George Karakostas
7 | *
8 | * @copyright
9 | * (c) 2015 George Karakostas
10 | *
11 | * @license
12 | * BSD-3
13 | *
14 | * @email
15 | * gkarak@9-dev.com
16 | */
17 |
18 | (function($) {
19 |
20 | var $container = $('.masonry-container');
21 | $container.imagesLoaded( function () {
22 | $container.masonry({
23 | columnWidth: '.item',
24 | itemSelector: '.item'
25 | });
26 | });
27 |
28 | //Reinitialize masonry inside each panel after the relative tab link is clicked -
29 | $('a[data-toggle=tab]').each(function () {
30 | var $this = $(this);
31 |
32 | $this.on('shown.bs.tab', function () {
33 |
34 | $container.imagesLoaded( function () {
35 | $container.masonry({
36 | columnWidth: '.item',
37 | itemSelector: '.item'
38 | });
39 | });
40 |
41 | }); //end shown
42 | }); //end each
43 |
44 | })(jQuery);
--------------------------------------------------------------------------------
/ninecms/static/ninecms/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | NineCMS styling
3 |
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | */
9 |
10 | /*******
11 | * Base
12 | *******/
13 | .padding-80 { padding: 80px; }
14 | .padding-top-80 { padding-top: 80px; }
15 | .padding-bottom-80 { padding-bottom: 80px; }
16 | .padding-left-80 { padding-left: 80px; }
17 | .padding-right-80 { padding-right: 80px; }
18 | .padding-vertical-80 { padding-top: 80px; padding-bottom: 80px; }
19 | .padding-horizontal-80 { padding-left: 80px; padding-right: 80px; }
20 |
21 | .padding-40 { padding: 40px; }
22 | .padding-top-40 { padding-top: 40px; }
23 | .padding-bottom-40 { padding-bottom: 40px; }
24 | .padding-left-40 { padding-left: 40px; }
25 | .padding-right-40 { padding-right: 40px; }
26 | .padding-vertical-40 { padding-top: 40px; padding-bottom: 40px; }
27 | .padding-horizontal-40 { padding-left: 40px; padding-right: 40px; }
28 |
29 | .padding-20 { padding: 20px; }
30 | .padding-top-20 { padding-top: 20px; }
31 | .padding-bottom-20 { padding-bottom: 20px; }
32 | .padding-left-20 { padding-left: 20px; }
33 | .padding-right-20 { padding-right: 20px; }
34 | .padding-vertical-20 { padding-top: 20px; padding-bottom: 20px; }
35 | .padding-horizontal-20 { padding-left: 20px; padding-right: 20px; }
36 | .padding-0 { padding: 0; }
37 | .margin-0 { margin: 0 !important; }
38 |
39 | .font-300 { font-weight: 300; }
40 | .font-400 { font-weight: 400; }
41 | .font-500 { font-weight: 500; }
42 | .font-700 { font-weight: 700; }
43 | .font-italic { font-style: italic; }
44 |
45 | .font-size-12 { font-size: 12px !important; }
46 | .font-size-16 { font-size: 16px !important; }
47 | .font-size-18 { font-size: 18px !important; }
48 | .font-size-24 { font-size: 24px !important; }
49 |
50 | .text-upper { text-transform: uppercase !important; }
51 | .text-no-transform { text-transform: none !important; }
52 |
53 | .inline-block { display: inline-block !important; }
54 | .vertical-middle { vertical-align: middle; }
55 | .position-relative { position: relative; }
56 | .overflow-hidden { overflow: hidden; }
57 |
58 | /*********
59 | * Layout
60 | *********/
61 | /* Loader */
62 | #loader {
63 | overflow-x: hidden;
64 | overflow-y: hidden;
65 | vertical-align: middle;
66 | background: #fdfdfd; /* Old browsers */
67 | /* IE9 SVG, needs conditional override of 'filter' to 'none' */
68 | background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZkZmRmZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmN2Y3ZjciIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
69 | background: -moz-linear-gradient(top, #fdfdfd 0%, #f7f7f7 100%); /* FF3.6+ */
70 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdfdfd), color-stop(100%, #f7f7f7)); /* Chrome,Safari4+ */
71 | background: -webkit-linear-gradient(top, #fdfdfd 0%, #f7f7f7 100%); /* Chrome10+,Safari5.1+ */
72 | background: -o-linear-gradient(top, #fdfdfd 0%, #f7f7f7 100%); /* Opera 11.10+ */
73 | background: -ms-linear-gradient(top, #fdfdfd 0%, #f7f7f7 100%); /* IE10+ */
74 | background: linear-gradient(to bottom, #fdfdfd 0%, #f7f7f7 100%); /* W3C */
75 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdfdfd', endColorstr='#f7f7f7', GradientType=0); /* IE6-8 */
76 | position: fixed;
77 | display: table;
78 | width: 100%;
79 | top: 0;
80 | height: 100%;
81 | min-height: 100%;
82 | z-index: 100000;
83 | }
84 |
85 | .loader-container {
86 | position: relative;
87 | display: table-cell;
88 | vertical-align: middle;
89 | z-index: 12;
90 | text-align: center;
91 | }
92 |
93 | body { overflow: hidden; }
94 |
95 | /* Debug toolbar */
96 | #djDebug #djDebugToolbarHandle { top: 52px !important; }
97 |
98 | /* Navbars */
99 | .navbar.affix {
100 | width: 100%;
101 | top: 0;
102 | right: 0;
103 | padding: 0;
104 | margin: 0;
105 | position: fixed;
106 | z-index: 10000;
107 | -webkit-transition: all 0.8s;
108 | -moz-transition: all 0.8s;
109 | transition: all 0.8s;
110 | }
111 |
112 | .navbar.affix-top { margin: 0; }
113 | body.toolbar #header.navbar.affix { top: 50px; }
114 |
115 |
116 | /* Top link */
117 | #top-link {
118 | display: none;
119 | position: fixed;
120 | right: 20px;
121 | bottom: 60px;
122 | padding: 12px 12px 8px;
123 | background: url('images/toplink.png') no-repeat center;
124 | color: #fff;
125 | font-size: 18px;
126 | font-weight: 400;
127 | text-decoration: none;
128 | z-index: 100;
129 | }
130 |
131 | .page-unpublished #content { background: #e4b9b9; }
132 |
133 | /*******
134 | * Menu
135 | *******/
136 | .language.menu button {
137 | border: none;
138 | background: none;
139 | color: #fff;
140 | padding: 15px;
141 | }
142 |
143 | .language.menu.flag button { padding: 15px 5px; }
144 | .language.menu { padding-left: 15px; }
145 |
--------------------------------------------------------------------------------
/ninecms/templates/admin/ninecms/image/stacked.html:
--------------------------------------------------------------------------------
1 | {% load i18n admin_urls admin_static bootstrapped_goodies_tags ninecms_extras %}
2 | {% comment %}
3 |
4 | Template for inline form of Node images
5 | Most of the template is the same as /admin/edit_inline/stacked.html except for a row with two columns added
6 |
7 | Author: George Karakostas
8 | Copyright: Copyright 2015, George Karakostas
9 | Licence: BSD-3
10 | Email: gkarak@9-dev.com
11 |
12 | {% endcomment %}
13 |
79 |
80 |
--------------------------------------------------------------------------------
/ninecms/templates/admin/ninecms/node/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/change_form.html" %}
2 | {% load i18n admin_urls admin_static admin_modify %}
3 |
4 | {% comment %}
5 | Template for Node edit form: add CKEditor
6 | Author: George Karakostas
7 | Copyright: Copyright 2015, George Karakostas
8 | Licence: BSD-3
9 | Email: gkarak@9-dev.com
10 | {% endcomment %}
11 |
12 | {% block admin_change_form_document_ready %}
13 | {{ block.super }}
14 | {% include 'ninecms/ckeditor.html' %}
15 | {% endblock %}
--------------------------------------------------------------------------------
/ninecms/templates/admin/ninecms/node/change_list.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/change_list.html" %}
2 | {% load ninecms_extras %}
3 |
4 | {% comment %}
5 | Template for Node list: add clear cache action
6 | Author: George Karakostas
7 | Copyright: Copyright 2015, George Karakostas
8 | Licence: BSD-3
9 | Email: gkarak@9-dev.com
10 | {% endcomment %}
11 |
12 | {% block object-tools-items %}
13 | {{ block.super }}
14 | {% if user.is_superuser %}
15 |
16 |
17 |
23 |
24 | {% endif %}
25 | {% endblock %}
26 |
--------------------------------------------------------------------------------
/ninecms/templates/admin/ninecms/pagetype/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/change_form.html" %}
2 | {% load i18n admin_urls admin_static admin_modify %}
3 |
4 | {% comment %}
5 | Template for Page type form: add action link to edit permissions
6 | Author: George Karakostas
7 | Copyright: Copyright 2015, George Karakostas
8 | Licence: BSD-3
9 | Email: gkarak@9-dev.com
10 | {% endcomment %}
11 |
12 | {% block object-tools-items %}
13 | {% trans "Permissions" %}
14 | {{ block.super }}
15 | {% endblock %}
--------------------------------------------------------------------------------
/ninecms/templates/admin/ninecms/pagetype/perms_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/base_site.html' %}
2 | {% load i18n ninecms_extras %}
3 |
4 | {% comment %}
5 | Edit Page type permissions
6 | Author: George Karakostas
7 | Copyright: Copyright 2015, George Karakostas
8 | Licence: BSD-3
9 | Email: gkarak@9-dev.com
10 | {% endcomment %}
11 |
12 | {% block title %}{% trans "Edit permissions for page type" %} {{ page_type.name }}{% endblock %}
13 |
14 | {% if not is_popup %}
15 | {% block breadcrumbs %}
16 |
22 | {% endblock %}
23 | {% endif %}
24 |
25 | {% block object-tools %}
26 | {% if not is_popup %}
27 |
28 |
{% trans "Edit permissions for page type" %}
29 |
34 |
35 | {% endif %}
36 | {% endblock %}
37 |
38 | {% block content %}
39 |
60 | {% endblock content %}
61 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/base.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 | {% load ninecms_extras %}
3 | {% load i18n %}
4 | {% get_current_language as LANGUAGE_CODE %}
5 | {% comment %}
6 |
7 | Base template
8 | Author: George Karakostas
9 | Copyright: Copyright 2015, George Karakostas
10 | Licence: BSD-3
11 | Email: gkarak@9-dev.com
12 |
13 | {% endcomment %}
14 |
15 | {# #}
16 |
17 |
18 |
19 |
20 |
21 |
22 | {% block title %}{{ title }}{% endblock %}
23 | {% block meta %}
24 | {% if node.summary %} {% endif %}
25 |
26 |
27 |
28 | {% endblock %}
29 | {% block head %}
30 |
31 |
32 | {% endblock head %}
33 |
34 |
35 |
36 | {# #}
37 | {% block body_top %}^ {% endblock %}
38 |
39 | {% block body_loader %}
40 |
41 |
42 |
43 | {% block site_name %}{% endblock %}
44 |
45 |
46 |
47 | {% endblock %}
48 |
49 |
50 |
51 |
52 | {% block content %}{% endblock %}
53 |
54 |
55 | {# #}
56 | {% block body_bottom %} {% endblock %}
57 | {% block body_scripts %}
58 |
59 | {% endblock %}
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_contact.html:
--------------------------------------------------------------------------------
1 | {% load i18n ninecms_extras %}
2 | {% comment %}
3 | Block template for contact
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 |
10 |
13 |
23 |
24 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_content.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Block template for content
3 | Author: George Karakostas
4 | Copyright: Copyright 2015, George Karakostas
5 | Licence: BSD-3
6 | Email: gkarak@9-dev.com
7 | {% endcomment %}
8 |
9 | {% include 'ninecms/block_static.html' %}
10 |
11 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_language.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% load staticfiles %}
3 | {% comment %}
4 |
5 | Block template for language menu
6 | Author: George Karakostas
7 | Copyright: Copyright 2015, George Karakostas
8 | Licence: BSD-3
9 | Email: gkarak@9-dev.com
10 |
11 | http://stackoverflow.com/a/30779618/940098
12 | {% endcomment %}
13 |
47 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_login.html:
--------------------------------------------------------------------------------
1 | {% load i18n ninecms_extras %}
2 | {% comment %}
3 |
4 | Block template for login
5 | Author: George Karakostas
6 | Copyright: Copyright 2015, George Karakostas
7 | Licence: BSD-3
8 | Email: gkarak@9-dev.com
9 |
10 | {% endcomment %}
11 | {% if not user.is_authenticated %}
12 |
28 | {% else %}
29 |
30 | {% include 'ninecms/form_logout.html' %}
31 |
32 | {% endif %}
33 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_menu.html:
--------------------------------------------------------------------------------
1 | {% load mptt_tags %}
2 | {% load i18n %}
3 | {% get_current_language as LANGUAGE_CODE %}
4 | {% comment %}
5 | Block template for menu
6 | Author: George Karakostas
7 | Copyright: Copyright 2015, George Karakostas
8 | Licence: BSD-3
9 | Email: gkarak@9-dev.com
10 | {% endcomment %}
11 |
31 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_menu_breadcrumbs.html:
--------------------------------------------------------------------------------
1 | {% load mptt_tags %}
2 | {% load ninecms_extras %}
3 | {% comment %}
4 |
5 | Block template for menu as breadcrumbs
6 | The menu is expected to be the children of a menu item, of a menu block.
7 |
8 | Author: George Karakostas
9 | Copyright: Copyright 2015, George Karakostas
10 | Licence: BSD-3
11 | Email: gkarak@9-dev.com
12 |
13 | {% endcomment %}
14 |
15 | {# active_trail: expected to be in the form: [<'Site map'>, <'Front'>, <'Page 1'>], therefore slice the first #}
16 | {% with active_trail=menu|active_trail:request.path|slice:'1:' %}
17 | {% for node in active_trail %}
18 | {# if this item is THE active item #}
19 | {% if not node.path|check_path_active:request.path %}
20 |
21 | {% if not node.disabled and node.path %}
22 | {{ node.title }}
23 | {% else %}
24 | {{ node.title }}
25 | {% endif %}
26 |
27 | {% else %}
28 | {{ node.title }}
29 | {% endif %}
30 | {% endfor %}
31 | {% endwith %}
32 |
33 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_menu_header.html:
--------------------------------------------------------------------------------
1 | {% load mptt_tags %}
2 | {% load i18n %}
3 | {% load ninecms_extras %}
4 | {% get_current_language as LANGUAGE_CODE %}
5 | {% comment %}
6 |
7 | Block template for menu in header region
8 | The menu is expected to be the children of a menu item, of a menu block.
9 |
10 | Author: George Karakostas
11 | Copyright: Copyright 2015, George Karakostas
12 | Licence: BSD-3
13 | Email: gkarak@9-dev.com
14 |
15 | {% endcomment %}
16 |
50 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_search.html:
--------------------------------------------------------------------------------
1 | {% load ninecms_extras %}
2 | {% comment %}
3 |
4 | Block template for search
5 | Author: George Karakostas
6 | Copyright: Copyright 2015, George Karakostas
7 | Licence: BSD-3
8 | Email: gkarak@9-dev.com
9 |
10 | {% endcomment %}
11 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_search_results.html:
--------------------------------------------------------------------------------
1 | {% load ninecms_extras %}
2 | {% comment %}
3 |
4 | Block template for search results
5 | Author: George Karakostas
6 | Copyright: Copyright 2015, George Karakostas
7 | Licence: BSD-3
8 | Email: gkarak@9-dev.com
9 |
10 | {% endcomment %}
11 |
12 | {% if results.q %}
Search results for {{ results.q }} {% endif %}
13 | {% if results.nodes %}
14 | {% for result in results.nodes %}
15 |
16 | {% if result.summary %}{{ result.summary|safe }}{% else %}{{ result.body|safe }}{% endif %}
17 | {% endfor %}
18 | {% else %}
19 |
No results found.
20 | {% endif %}
21 |
22 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_signal.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Block template for signal
3 | If this is shown then a signal template is missing.
4 |
5 | Author: George Karakostas
6 | Copyright: Copyright 2015, George Karakostas
7 | Licence: BSD-3
8 | Email: gkarak@9-dev.com
9 | {% endcomment %}
10 |
11 | Signal template is missing.
12 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_signal_terms.html:
--------------------------------------------------------------------------------
1 | {% load mptt_tags %}
2 | {% comment %}
3 | Block template for view: terms
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 | Terms
10 |
11 | {% recursetree content %}
12 |
13 | {{ node.name }}
14 | {% if not node.is_leaf_node %}
15 |
18 | {% endif %}
19 |
20 | {% endrecursetree %}
21 |
22 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_static.html:
--------------------------------------------------------------------------------
1 | {% load ninecms_extras %}
2 | {% comment %}
3 | Block template for static content
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 |
10 |
{{ node.body|safe }}
11 | {% if node.image_set.all %}
12 |
13 | {% for img in node.image_set.all %}
14 |
15 |
16 |
17 | {% endfor %}
18 |
19 | {% endif %}
20 | {# Attention: it is recommended that this is disabled and added on particular templates only (causes db hit) #}
21 | {% if node.video_set.all %}
22 |
26 | {% for source in node.video_set.all %}
27 | {% if source.type != 'swf' and source.type != 'jpg' %}
28 |
29 | {% elif source.type == 'swf' %}
30 |
31 | {% elif source.type == 'jpg' %}
32 |
33 | {% endif %}
34 | {% endfor %}
35 | To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
36 |
37 | {% endif %}
38 |
39 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/block_user_menu.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% comment %}
3 | Block template for user menu
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 |
17 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/ckeditor.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 | {% comment %}
3 | CKEditor template
4 |
5 | For toolbar layout edit ckeditor/config.js
6 | http://docs.ckeditor.com/#!/guide/dev_toolbarconcepts
7 | http://docs.ckeditor.com/#!/guide/dev_howtos_styles
8 |
9 | Author: George Karakostas
10 | Copyright: Copyright 2015, George Karakostas
11 | Licence: BSD-3
12 | Email: gkarak@9-dev.com
13 |
14 | {% endcomment %}
15 |
16 |
45 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/field.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Field template
3 | Author: George Karakostas
4 | Copyright: Copyright 2015, George Karakostas
5 | Licence: BSD-3
6 | Email: gkarak@9-dev.com
7 | {% endcomment %}
8 |
15 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/fieldset.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Fieldset template
3 | Author: George Karakostas
4 | Copyright: Copyright 2015, George Karakostas
5 | Licence: BSD-3
6 | Email: gkarak@9-dev.com
7 | {% endcomment %}
8 |
9 |
{{ name }}
10 |
{{ var }}
11 |
12 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/form_confirm.html:
--------------------------------------------------------------------------------
1 | {% extends 'ninecms/base.html' %}
2 | {% comment %}
3 | Data manipulation confirm generic form
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 |
9 | {% endcomment %}
10 |
11 | {% block title %}{{ title|striptags }} | 9cms{% endblock %}
12 |
13 | {% block content %}
14 |
15 |
16 | {% include 'ninecms/messages.html' %}
17 |
{{ prompt }}
18 |
23 |
24 | {% endblock content %}
25 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/form_logout.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Form template for logout
3 | Author: George Karakostas
4 | Copyright: Copyright 2015, George Karakostas
5 | Licence: BSD-3
6 | Email: gkarak@9-dev.com
7 | {% endcomment %}
8 |
14 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/form_non_field_errors.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Messages template for non-field errors
3 |
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 |
10 | {% if errors %}
11 |
12 | {% for error in errors %}
13 |
{{ error }}
14 | {% endfor %}
15 |
16 | {% endif %}
17 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/glyphicon.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'ninecms/base.html' %}
2 |
3 | {% comment %}
4 | Content page
5 | Author: George Karakostas
6 | Copyright: Copyright 2015, George Karakostas
7 | Licence: BSD-3
8 | Email: gkarak@9-dev.com
9 | {% endcomment %}
10 |
11 | {% block content %}
12 |
31 |
32 | {% block featured %}{% endblock %}
33 |
34 |
35 |
38 | {% include 'ninecms/messages.html' %}
39 |
40 | {% block main %}{% endblock %}
41 |
42 |
43 |
44 | {% block content_below %}{% endblock %}
45 |
46 |
51 | {% endblock content %}
52 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/mail_contact.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% comment %}
3 | Mail template for contact
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 | {{ sender_name|striptags }} <{{ sender_email|striptags }}> {% trans "sent a message using the contact form" %}.
10 |
11 | {{ message|striptags }}
12 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/mail_updates.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% comment %}
3 | Mail template for contact
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 | {% endcomment %}
9 | {% trans "There are security updates available for your web site" %}.
10 | {% trans "To ensure the security of your server, you should update" %}.
11 | {% trans "The following updates are available" %}:
12 |
13 | {% for update in updates %}{{ update }}
14 | {% endfor %}
15 | {% trans "See the status page for more information" %}.
16 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/messages.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Messages template
3 |
4 | Author: George Karakostas
5 | Copyright: Copyright 2015, George Karakostas
6 | Licence: BSD-3
7 | Email: gkarak@9-dev.com
8 |
9 | {% endcomment %}
10 | {% if messages %}
11 |
12 | {% for message in messages %}
13 |
14 |
15 | ×
16 |
17 | {{ message }}
18 |
19 | {% endfor %}
20 |
21 | {% endif %}
22 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/pagination.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Paginator template
3 | Author: George Karakostas
4 | Copyright: Copyright 2015, George Karakostas
5 | Licence: BSD-3
6 | Email: gkarak@9-dev.com
7 | {% endcomment %}
8 | {% if is_paginated %}
9 |
10 |
41 |
42 | {% endif %}
43 |
--------------------------------------------------------------------------------
/ninecms/templates/ninecms/robots.txt:
--------------------------------------------------------------------------------
1 | #
2 | # robots.txt
3 | #
4 | # This file is to prevent the crawling and indexing of certain parts
5 | # of the site by web crawlers and spiders run by sites like Google.
6 | # By telling these "robots" where not to go on the site, bandwidth
7 | # and server resources are saved.
8 | #
9 | # This file will be ignored unless it is at the root of the host:
10 | # Used: http://example.com/robots.txt
11 | # Ignored: http://example.com/site/robots.txt
12 | #
13 | # For more information about the robots.txt standard, see:
14 | # http://www.robotstxt.org/robotstxt.html
15 |
16 | User-agent: *
17 | Crawl-delay: 10
18 | # Paths
19 | Disallow: /admin/
20 |
--------------------------------------------------------------------------------
/ninecms/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 | """ NineCMS custom template filters and tags """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
--------------------------------------------------------------------------------
/ninecms/templatetags/ninecms_extras.py:
--------------------------------------------------------------------------------
1 | """ NineCMS custom template filters and tags """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django import template
8 | from django.template.defaultfilters import stringfilter
9 | from django.template import Context
10 | from ninecms.utils.media import image_style as util_image
11 | from ninecms.utils.transliterate import upper_no_intonation as util_upper
12 | from ninecms.utils.nodes import get_clean_url
13 | from ninecms.utils import status
14 |
15 |
16 | register = template.Library()
17 |
18 |
19 | @register.filter
20 | def image_style(image, style):
21 | """ Return the url of different image style
22 | :param image: An image url
23 | :param style: Specify style to return image
24 | :return: image url of specified style
25 | """
26 | return util_image(image, style)
27 |
28 |
29 | class FieldsetNode(template.Node): # pragma: nocover
30 | """ Fieldset renderer for 'fieldset' tag (see below) """
31 | def __init__(self, nodelist, fieldset_name):
32 | """ Initialize renderer class
33 | https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-renderer
34 | https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#passing-template-variables-to-the-tag
35 | :param nodelist: a list of the template nodes inside a block of 'fieldset'
36 | :param fieldset_name: the name of the fieldset
37 | :return: None
38 | """
39 | self.nodelist = nodelist
40 | # if not in quotes, get variable from context
41 | if not (fieldset_name[0] == fieldset_name[-1] and fieldset_name[0] in ('"', "'")): # pragma: nocover
42 | self.fieldset_name = template.Variable(fieldset_name)
43 | self.fieldset_id = fieldset_name
44 | self.resolve = True
45 | else:
46 | self.fieldset_name = fieldset_name[1:-1]
47 | self.fieldset_id = self.fieldset_name
48 | self.resolve = False
49 |
50 | def render(self, context):
51 | """ Render the inside of a fieldset block based on template file
52 | https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#auto-escaping-considerations
53 | :param context: the previous template context
54 | :return: HTML string
55 | """
56 | t = context.template.engine.get_template('ninecms/fieldset.html')
57 | return t.render(Context({
58 | 'var': self.nodelist.render(context),
59 | 'name': self.fieldset_name.resolve(context) if self.resolve else self.fieldset_name,
60 | 'id': self.fieldset_id,
61 | }, autoescape=context.autoescape))
62 |
63 |
64 | @register.tag
65 | def fieldset(parser, token): # pragma: nocover
66 | """ Compilation function for fieldset block tag
67 | Render a form fieldset
68 | *This is an aux tag that is not used and excluded from coverage tests*
69 | https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#writing-the-compilation-function
70 | https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#parsing-until-another-block-tag
71 | http://stackoverflow.com/a/30097784/940098
72 | :param parser: template parser
73 | :param token: tag name and variables
74 | :return: HTML string
75 | """
76 | try:
77 | tag_name, fieldset_name = token.split_contents()
78 | except ValueError: # pragma: nocover
79 | raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0])
80 | nodelist = parser.parse(('endfieldset',))
81 | parser.delete_first_token()
82 | return FieldsetNode(nodelist, fieldset_name)
83 |
84 |
85 | @register.inclusion_tag('ninecms/field.html')
86 | def field(field_var):
87 | """ Render a field based on template
88 | https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/#inclusion-tags
89 | Possibly add parameter for custom classes
90 | :param field_var: the field variable
91 | :return: the template context
92 | """
93 | return {'field': field_var}
94 |
95 |
96 | @register.filter
97 | def upper_no_intonation(s):
98 | """ Convert a string to uppercase, removing any intonation
99 | :param s: the string to convert
100 | :return: the converted string
101 | """
102 | return util_upper(s)
103 |
104 |
105 | @register.filter
106 | def active_trail(menu, url):
107 | """ Get the active menu item based on url provided, and all of its ancestors
108 | To be used to check each individual node's path if in this list so to obtain the active trail
109 | Also remove language part from url if i18n urls are enabled
110 | :param menu: the parent menu
111 | :param url: the current url to check against for the active path (should be request.path)
112 | :return: a recordset of all active menu ancestors
113 | """
114 | return menu.filter(path=get_clean_url(url)).get_ancestors(include_self=True) if menu else []
115 |
116 |
117 | @register.filter
118 | def flatten(records, fld):
119 | """ Flatten a recordset to list of a particular field
120 | :param records: the recordset to flatten
121 | :param fld: the field from the records to include in list
122 | :return: a list
123 | """
124 | return [path for fields in records.values_list(fld) for path in fields] if records else []
125 |
126 |
127 | @register.filter
128 | @stringfilter
129 | def strip(string, char): # pragma: nocover
130 | """ Strip a specified character from a string
131 | Helper filter that should exist in Django, not currently used
132 | :param string:
133 | :param char:
134 | :return: stripped string
135 | """
136 | return string.strip(char)
137 |
138 | @register.filter
139 | def check_path_active(node_path, request_path):
140 | """ Check that the two paths are equal, ignoring leading or trailing slashes
141 | Helper function for improving readability
142 | Mainly used in menu templates
143 | :param node_path: the menu item path
144 | :param request_path: the request path
145 | :return: boolean
146 | """
147 | url = get_clean_url(request_path)
148 | return node_path == url or node_path == url.strip('/')
149 |
150 | @register.inclusion_tag('ninecms/glyphicon.html')
151 | def glyphicon(icon):
152 | """ Shorthand for bootstrap glyphicon markup
153 | :param icon: the icon to present, gets appended to glyphicon-{{ icon }}
154 | :return: the template context
155 | """
156 | return {'icon': icon}
157 |
158 | @register.assignment_tag(takes_context=True)
159 | def get_status(context):
160 | return {
161 | 'version': status.version(),
162 | 'packages': status.packages(),
163 | 'updates': status.updates(),
164 | 'django_check': status.django_check(),
165 | 'migrations': status.django_migrations(),
166 | 'permissions': status.permissions_status(),
167 | 'imagemagick': status.imagemagick_status(),
168 | 'user_stats': status.user_stat(context['request'].user),
169 | }
170 |
--------------------------------------------------------------------------------
/ninecms/tests/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'gkarak'
2 |
--------------------------------------------------------------------------------
/ninecms/tests/tests_content_i18n.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests declaration for Nine CMS
3 |
4 | All tests assume settings.LANGUAGE_CODE is defined
5 | """
6 | __author__ = 'George Karakostas'
7 | __copyright__ = 'Copyright 2015, George Karakostas'
8 | __licence__ = 'BSD-3'
9 | __email__ = 'gkarak@9-dev.com'
10 |
11 | from django.test import TestCase
12 | from django.core.urlresolvers import reverse
13 | from django.utils import translation
14 | from django.conf import settings
15 | from ninecms.tests.setup import get_second_language, create_front, create_basic, create_block_static, create_menu, \
16 | create_block_menu, assert_front, assert_basic, assert_menu
17 |
18 |
19 | class ContentI18nTests(TestCase):
20 | """ Tests with default initial content, i18n, no login """
21 | @classmethod
22 | def setUpTestData(cls):
23 | """ Setup initial data:
24 | Create 2 front pages
25 | Create 2 basic pages
26 | :return: None
27 | """
28 | cls.second_lang = get_second_language()
29 | cls.node_rev_front_first = create_front('/', settings.LANGUAGE_CODE)
30 | cls.node_rev_front_sec = create_front('/', cls.second_lang)
31 | cls.node_rev_basic_first = create_basic('about', settings.LANGUAGE_CODE)
32 | cls.node_rev_basic_sec = create_basic('about', cls.second_lang)
33 | for i in range(0, 3):
34 | node_rev_basic_first = create_basic('block/' + str(i), settings.LANGUAGE_CODE, '1st lang ' + str(i))
35 | node_rev_basic_second = create_basic('block/' + str(i), cls.second_lang, '2nd lang ' + str(i))
36 | create_block_static(cls.node_rev_front_first.node.page_type, node_rev_basic_first.node)
37 | # same page type for both
38 | create_block_static(cls.node_rev_front_first.node.page_type, node_rev_basic_second.node)
39 | cls.menu_en = create_menu('en')
40 | cls.menu_el = create_menu('el')
41 | create_block_menu(cls.node_rev_front_first.node.page_type, cls.menu_en)
42 | create_block_menu(cls.node_rev_front_first.node.page_type, cls.menu_el)
43 |
44 | """ Node System """
45 | def test_node_model_methods(self):
46 | """ Test model methods
47 | :return: None
48 | """
49 | lang = ''
50 | if settings.I18N_URLS: # pragma: nocover
51 | lang = '/' + settings.LANGUAGE_CODE
52 | self.assertEqual(str(self.node_rev_front_first.node.get_absolute_url()), lang + '/')
53 | self.assertEqual(str(self.node_rev_basic_first.node.get_absolute_url()), lang + '/about/')
54 |
55 | def test_node_view_with_front_i18n(self):
56 | """ Test front page with multiple languages
57 | :return: None
58 | """
59 | translation.activate(settings.LANGUAGE_CODE)
60 | assert_front(self, reverse('ninecms:index'))
61 | # translation.activate needs to happen before reverse for non-default language
62 | translation.activate(self.second_lang)
63 | assert_front(self, reverse('ninecms:index'), self.second_lang)
64 |
65 | def test_node_view_with_basic_i18n(self):
66 | """ Test basic page with multiple languages
67 | :return: None
68 | """
69 | assert_basic(self, 'about/')
70 | assert_basic(self, 'about/', 'alias', self.second_lang)
71 |
72 | """ Block System """
73 | def test_node_view_block_static_i18n(self):
74 | """ Test static block for i18n front view
75 | Change in v0.2: require block for each language
76 | :return: None
77 | """
78 | translation.activate(settings.LANGUAGE_CODE)
79 | response = assert_front(self, reverse('ninecms:index'))
80 | for i in range(0, 3):
81 | self.assertContains(response, '1st lang ' + str(i) + ' page.
', html=True)
82 | if settings.I18N_URLS: # pragma: nocover
83 | self.assertNotContains(response, '2nd lang ' + str(i) + ' page.
', html=True)
84 | translation.activate(self.second_lang)
85 | response = assert_front(self, reverse('ninecms:index'), self.second_lang)
86 | for i in range(0, 3):
87 | if settings.I18N_URLS: # pragma: nocover
88 | self.assertNotContains(response, '1st lang ' + str(i) + ' page.
', html=True)
89 | self.assertContains(response, '2nd lang ' + str(i) + ' page.
', html=True)
90 |
91 | def test_node_view_block_menu_i18n(self):
92 | """ Test menu block for i18n front view
93 | :return: None
94 | """
95 | translation.activate(settings.LANGUAGE_CODE)
96 | response = assert_front(self, reverse('ninecms:index'))
97 | assert_menu(self, response)
98 | translation.activate(self.second_lang)
99 | response = assert_front(self, reverse('ninecms:index'), self.second_lang)
100 | assert_menu(self, response, self.second_lang)
101 |
--------------------------------------------------------------------------------
/ninecms/tests/tests_content_login_simple.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests declaration for Nine CMS
3 |
4 | All tests assume settings.LANGUAGE_CODE is defined
5 | """
6 | __author__ = 'George Karakostas'
7 | __copyright__ = 'Copyright 2015, George Karakostas'
8 | __licence__ = 'BSD-3'
9 | __email__ = 'gkarak@9-dev.com'
10 |
11 | from django.test import TestCase
12 | from django.contrib.auth.models import Permission, Group
13 | from django.core.urlresolvers import reverse
14 | from ninecms.tests.setup import create_front, create_basic, create_simple_user, create_image, data_node
15 | from ninecms.utils.perms import set_perms
16 | from ninecms.forms import ContentTypePermissionsForm, ContentNodeEditForm
17 |
18 |
19 | class ContentLoginSimpleTests(TestCase):
20 | """ Tests for access, with content, with login from simple user
21 | The first page (front) is allowed to be edited by simple user
22 | The second is not.
23 | """
24 | @classmethod
25 | def setUpTestData(cls):
26 | """ Setup initial data:
27 | Create front page
28 | Create basic page
29 | Create simple user
30 | Create image
31 | :return: None
32 | """
33 | cls.node_rev_front = create_front('/')
34 | cls.node_rev_basic = create_basic('about')
35 | cls.simple_user = create_simple_user()
36 | cls.simple_group = Group.objects.create(name='editor')
37 | cls.simple_user.groups.add(cls.simple_group)
38 | perms = Permission.objects.filter(codename__in=['add_node', 'change_node'])
39 | cls.simple_user.user_permissions.add(*perms)
40 | cls.default_perms = {
41 | 'change_node': [cls.simple_group],
42 | 'delete_node': [],
43 | 'add_node': [cls.simple_group],
44 | }
45 | cls.fields = list(ContentTypePermissionsForm().fields.keys())
46 | cls.img = create_image()
47 |
48 | def setUp(self):
49 | """ Setup each test: login
50 | :return: None
51 | """
52 | self.client.login(username='editor', password='1234')
53 |
54 | """ Admin index """
55 | def test_admin_index_page(self):
56 | """ Test status page
57 | :return: None
58 | """
59 | response = self.client.get(reverse('admin:index'))
60 | self.assertContains(response, "administration")
61 | self.assertContains(response, '')
62 | self.assertContains(response, '')
63 | self.assertContains(response, '')
64 | self.assertContains(response, '')
65 | self.assertNotContains(response, '')
66 | self.assertNotContains(response, '')
67 | self.assertNotContains(response, "Utilities")
68 |
69 | """ Nodes """
70 | def test_admin_node_changelist_page(self):
71 | """ Test view /admin/ninecms/node/ properly
72 | :return: None
73 | """
74 | response = self.client.get(reverse('admin:ninecms_node_changelist'))
75 | self.assertEqual(response.status_code, 403)
76 | perms = {'add_node': [self.simple_group], 'change_node': [self.simple_group], 'delete_node': []}
77 | obj = self.node_rev_basic.node.page_type
78 | set_perms(obj, self.fields, '_pagetype', perms)
79 | response = self.client.get(reverse('admin:ninecms_node_changelist'))
80 | self.assertContains(response, "Basic Page")
81 | self.assertNotContains(response, "Clear cache")
82 | self.assertNotContains(response, '')
83 |
84 | def test_admin_node_change_page(self):
85 | """ Test that renders properly /admin/ninecms/node//
86 | Get the basic page to test image as well
87 | Also test fields exclusive to superuser
88 | Check that user field populates only with current user
89 | Notice that here the field is not selected as there is a different user for this node, not present in queryset
90 | :return: None
91 | """
92 | response = self.client.get(reverse('admin:ninecms_node_change', args=(self.node_rev_basic.node_id,)),
93 | follow=True)
94 | self.assertEqual(response.status_code, 404) # this returns a 404
95 | perms = {'add_node': [self.simple_group], 'change_node': [self.simple_group], 'delete_node': []}
96 | obj = self.node_rev_basic.node.page_type
97 | set_perms(obj, self.fields, '_pagetype', perms)
98 | response = self.client.get(reverse('admin:ninecms_node_change', args=(self.node_rev_basic.node_id,)))
99 | self.assertContains(response, (' ' % self.node_rev_basic.node.title), html=True)
102 | self.assertNotContains(response, 'Alias: ', html=True)
103 | self.assertNotContains(response, 'Redirect ', html=True)
104 | self.assertContains(response, ('--------- '
106 | '%s '
107 | ' ' % (self.simple_user.pk, self.simple_user.username)), html=True)
108 |
109 | def test_admin_node_add_page(self):
110 | """ Test that renders properly /admin/ninecms/node/add/
111 | Also test fields exclusive to superuser
112 | Also test initial data
113 | Check that user field populates only with current user
114 | Notice that here the user field is selected by form initial data
115 | :return: None
116 | """
117 | response = self.client.get(reverse('admin:ninecms_node_add'))
118 | self.assertEqual(response.status_code, 403)
119 | perms = {'add_node': [self.simple_group], 'change_node': [self.simple_group], 'delete_node': []}
120 | obj = self.node_rev_basic.node.page_type
121 | set_perms(obj, self.fields, '_pagetype', perms)
122 | response = self.client.get(reverse('admin:ninecms_node_add'))
123 | self.assertNotContains(response, 'Alias: ', html=True)
124 | self.assertNotContains(response, 'Redirect ', html=True)
125 | self.assertContains(response, ('--------- '
127 | '%s '
128 | ' ' % (self.simple_user.pk, self.simple_user.username)), html=True)
129 | self.assertContains(response, (' '), html=True)
131 | self.assertContains(response, ' ', html=True)
132 | self.assertNotContains(response, (' '), html=True)
134 | self.assertContains(response, ' ', html=True)
135 | self.assertNotContains(response, (' '), html=True)
137 |
138 | def test_content_node_edit_form_valid(self):
139 | """ Test that a form is valid
140 | :return: None
141 | """
142 | data = data_node(self.node_rev_front.node.page_type_id, self.simple_user)
143 | form = ContentNodeEditForm(data=data, user=self.simple_user)
144 | r = form.is_valid()
145 | self.assertEqual(r, True)
146 | self.assertEqual(form.cleaned_data['body'],
147 | '<div> </div><script>alert("This is a test.");</script>')
148 |
149 | """ Permissions: utility functions """
150 | def test_utils_perms(self):
151 | """ Test guardian and utility permissions
152 | Performed in a single test in order to cover changes from multiple states
153 | :return: None
154 | """
155 | obj = self.node_rev_basic.node.page_type
156 | # test set empty
157 | perms = {'change_node': [], 'delete_node': [], 'add_node': []}
158 | set_perms(obj, self.fields, '_pagetype', perms)
159 | self.assertFalse(self.simple_user.has_perm('change_node_pagetype', obj))
160 | self.assertFalse(self.simple_user.has_perm('delete_node_pagetype', obj))
161 | self.assertFalse(self.simple_user.has_perm('add_node_pagetype', obj))
162 | # test set existing
163 | perms = {'change_node': [], 'delete_node': [], 'add_node': [self.simple_group]}
164 | set_perms(obj, self.fields, '_pagetype', perms)
165 | self.assertFalse(self.simple_user.has_perm('change_node_pagetype', obj))
166 | self.assertFalse(self.simple_user.has_perm('delete_node_pagetype', obj))
167 | self.assertTrue(self.simple_user.has_perm('add_node_pagetype', obj))
168 | # test set/unset
169 | perms = {'change_node': [self.simple_group], 'delete_node': [], 'add_node': []}
170 | set_perms(obj, self.fields, '_pagetype', perms)
171 | self.assertTrue(self.simple_user.has_perm('change_node_pagetype', obj))
172 | self.assertFalse(self.simple_user.has_perm('delete_node_pagetype', obj))
173 | self.assertFalse(self.simple_user.has_perm('add_node_pagetype', obj))
174 |
--------------------------------------------------------------------------------
/ninecms/tests/tests_no_content.py:
--------------------------------------------------------------------------------
1 | """
2 | Tests declaration for Nine CMS
3 |
4 | All tests assume settings.LANGUAGE_CODE is defined
5 | """
6 | __author__ = 'George Karakostas'
7 | __copyright__ = 'Copyright 2015, George Karakostas'
8 | __licence__ = 'BSD-3'
9 | __email__ = 'gkarak@9-dev.com'
10 |
11 | from django.test import TestCase
12 | from django.core.urlresolvers import reverse
13 | from django.utils import translation
14 | from django.conf import settings
15 | from django.core.management import call_command
16 | from django.core import mail
17 | from ninecms.models import Node
18 | from ninecms.tests.setup import assert_no_front, create_front, assert_front, create_basic, assert_basic, url_with_lang
19 | from ninecms.management.commands.check_updates import Capturing
20 | from ninecms.checks import check_settings
21 | from io import StringIO
22 | # noinspection PyPackageRequirements
23 | import pip
24 |
25 |
26 | class NoContentTests(TestCase):
27 | """ Tests with no initial content and no login """
28 | """ Node System """
29 | def test_node_view_with_no_content(self):
30 | """ Test node view if no content exists
31 | Explicitly remove url aliases if any remain, which has the same effect (see below)
32 | :return: None
33 | """
34 | Node.objects.all().delete()
35 | assert_no_front(self)
36 |
37 | def test_node_view_with_no_front(self):
38 | """ Test node view with no / alias
39 | :return: None
40 | """
41 | Node.objects.all().delete()
42 | create_front('/wrong-slug')
43 | assert_no_front(self)
44 |
45 | def test_node_view_with_front_title_not_repeating(self):
46 | """ Test that a front page does not repeat the title if it is the site title
47 | :return: None
48 | """
49 | title = settings.SITE_NAME
50 | create_front('/', '', title)
51 | response = assert_front(self, reverse('ninecms:index'), '', title)
52 | self.assertContains(response, '' + title + ' ', html=True)
53 | self.assertNotContains(response, '' + title + ' | ' + title + ' ', html=True)
54 |
55 | def test_node_view_with_basic_two_level(self):
56 | """ Test basic node, two levels in alias
57 | Test node view of basic page, slash is missing (expecting redirect)
58 | Test properly, slash is not missing
59 | :return: None
60 | """
61 | create_basic('about/company')
62 | response = assert_basic(self, 'about/company')
63 | self.assertRedirects(response, url_with_lang('/about/company/'), status_code=301)
64 | assert_basic(self, 'about/company/')
65 |
66 | def test_node_view_with_basic_wrong_alias(self):
67 | """ Test that a basic page with trailing / in alias is not found
68 | :return: None
69 | """
70 | create_basic('about/')
71 | response = self.client.get(reverse('ninecms:alias', args=('about/',)))
72 | self.assertEqual(response.status_code, 404)
73 |
74 | def test_content_node_view_with_no_content(self):
75 | """ Test view with no content
76 | :return: None
77 | """
78 | Node.objects.all().delete()
79 | translation.activate(settings.LANGUAGE_CODE)
80 | response = self.client.get(reverse('ninecms:content_node', args=(1,)))
81 | self.assertEqual(response.status_code, 404)
82 |
83 | def test_content_node_view_with_no_alias(self):
84 | """ Test view with a node with no path alias
85 | :return: None
86 | """
87 | node_revision = create_basic('')
88 | assert_basic(self, node_revision.node_id, 'content_node')
89 |
90 | def test_command_check_updates(self):
91 | """ Test command check updates
92 | :return: None
93 | """
94 | call_command('check_updates')
95 | with Capturing() as updates:
96 | pip.main(['list', '--outdated', '--retries', '1'])
97 | # noinspection PyUnresolvedReferences
98 | n = len(mail.outbox)
99 | if not updates:
100 | self.assertEqual(n, 0) # pragma: nocover
101 | else:
102 | self.assertEqual(n, 1) # pragma: nocover
103 |
104 | def test_command_cache_clear(self):
105 | """ Test command clear cache
106 | :return: None
107 | """
108 | out = StringIO()
109 | call_command('cache_clear', stdout=out)
110 | self.assertEqual(out.getvalue(), 'Cache cleared.\n')
111 |
112 | def test_checks(self):
113 | """ Test custom system checks
114 | :return: None
115 | """
116 | self.assertFalse(check_settings(None))
117 |
118 | with self.settings(
119 | MEDIA_ROOT=None,
120 | MEDIA_URL=None,
121 | ADMINS=None,
122 | MANAGERS=None,
123 | SESSION_COOKIE_NAME='sessionid',
124 | CACHES={
125 | 'default': {
126 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
127 | 'KEY_PREFIX': None,
128 | }
129 | }):
130 | self.assertEqual(len(check_settings(None)), 6)
131 |
--------------------------------------------------------------------------------
/ninecms/urls.py:
--------------------------------------------------------------------------------
1 | """ URL specification for Nine CMS """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.conf.urls import url
8 | from django.contrib.auth.decorators import login_required
9 | from django.contrib.admin.views.decorators import staff_member_required
10 | from django.views.generic import RedirectView
11 | from ninecms import views
12 |
13 |
14 | urlpatterns = [
15 | # Content
16 |
17 | # Cms landing redirect
18 | url(r'^cms/$', RedirectView.as_view(pattern_name='admin:index', permanent=True)),
19 | url(r'^cms/content/$', RedirectView.as_view(pattern_name='admin:ninecms_node_changelist', permanent=True)),
20 |
21 | # Node id redirect: cms/content/
22 | url(r'^cms/content/(?P\d+)/$', views.ContentNodeView.as_view(),
23 | name="content_node"),
24 |
25 | # Status
26 |
27 | # status page: cms/status
28 | url(r'^cms/status/$', staff_member_required(views.StatusView.as_view()),
29 | name='status'),
30 |
31 | # Other forms
32 |
33 | # Contact: contact/form/
34 | url(r'^contact/form/$', views.ContactView.as_view(),
35 | name='contact'),
36 |
37 | # Login: user/login/
38 | url(r'^user/login/$', views.LoginView.as_view(),
39 | name='login'),
40 |
41 | # Logout: user/logout/
42 | url(r'^user/logout/$', login_required(views.LogoutView.as_view()),
43 | name='logout'),
44 |
45 | # Node render
46 |
47 | # Render page: /
48 | url(r'^(?P[/\w\-_]+)$', views.AliasView.as_view(),
49 | name='alias'),
50 |
51 | # Default index: /
52 | url(r'^$', views.IndexView.as_view(),
53 | name='index'),
54 | ]
55 |
--------------------------------------------------------------------------------
/ninecms/utils/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'gkarak'
2 |
--------------------------------------------------------------------------------
/ninecms/utils/manytomany.py:
--------------------------------------------------------------------------------
1 | """ https://gist.github.com/Wtower/0b181cc06f816e4feac14e7c0aa2e9d0 """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django import forms
8 | from django.contrib.admin.widgets import FilteredSelectMultiple
9 |
10 |
11 | class ModelBiMultipleChoiceField(forms.ModelMultipleChoiceField):
12 | """ This shows both ends of m2m in admin """
13 | def __init__(self, queryset, required=False, widget=None, label=None, initial=None, help_text='',
14 | double_list=None, *args, **kwargs):
15 | """ First add a custom ModelMultipleChoiceField
16 | Specify a `double_list` label in order to use the double list widget
17 | Field name should be the same with model's m2m field
18 | https://www.lasolution.be/blog/related-manytomanyfield-django-admin-site.html
19 | https://github.com/django/django/blob/master/django/contrib/admin/widgets.py#L24
20 | """
21 | if double_list:
22 | widget = FilteredSelectMultiple(double_list, True)
23 | super(ModelBiMultipleChoiceField, self).__init__(
24 | queryset, required, widget, label, initial, help_text, *args, **kwargs)
25 |
26 |
27 | class ManyToManyModelForm(forms.ModelForm):
28 | """ This is a generic form to use with the ModelBiMultipleChoiceField """
29 | def __init__(self, *args, **kwargs):
30 | """ Initialize form
31 | :param args
32 | :param kwargs
33 | :return: None
34 | """
35 | super(ManyToManyModelForm, self).__init__(*args, **kwargs)
36 | # If this is an existing object, load related
37 | if self.instance.pk:
38 | # browse through all form fields and pick the ModelBiMultipleChoiceField
39 | for field_name in self.base_fields:
40 | field = self.base_fields[field_name]
41 | if type(field).__name__ == 'ModelBiMultipleChoiceField':
42 | # Get instance property dynamically
43 | # field should be same name with reverse model (ie. form.blocks vs instance.blocks)
44 | self.initial[field_name] = getattr(self.instance, field_name).values_list('pk', flat=True)
45 |
46 | # # Use the following to add an add new block icon
47 | # from django.db.models import ManyToManyRel
48 | # from django.contrib import admin
49 | # rel = ManyToManyRel(ContentBlock, PageType)
50 | # self.fields['blocks'].widget = RelatedFieldWidgetWrapper(self.fields['blocks'].widget, rel, admin.site)
51 |
52 | def save(self, *args, **kwargs):
53 | """ Handle saving of related
54 | :param args
55 | :param kwargs
56 | :return: instance
57 | """
58 | instance = super(ManyToManyModelForm, self).save(*args, **kwargs)
59 | if instance.pk:
60 | # browse through all form fields and pick the ModelBiMultipleChoiceField
61 | for field_name in self.base_fields:
62 | field = self.base_fields[field_name]
63 | if type(field).__name__ == 'ModelBiMultipleChoiceField':
64 | # the m2m records, eg if model field is `blocks`, this would be `instance.blocks.all()`
65 | recordset = getattr(self.instance, field_name)
66 | records = recordset.all()
67 | # remove records that have been removed in form
68 | for record in records:
69 | if record not in self.cleaned_data[field_name]:
70 | recordset.remove(record)
71 | # add records that have been added in form
72 | for record in self.cleaned_data[field_name]:
73 | if record not in records:
74 | recordset.add(record)
75 | return instance
76 |
--------------------------------------------------------------------------------
/ninecms/utils/media.py:
--------------------------------------------------------------------------------
1 | """ Media system utility functions """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.core.exceptions import ValidationError
8 | from django.conf import settings
9 | from ninecms.utils.transliterate import transliterate
10 | from subprocess import check_output, call, CalledProcessError
11 | import os
12 |
13 |
14 | def path_file_name(instance, context, filename):
15 | """ Get path file name
16 | Transliterate filename
17 | Filter out any empty component from list
18 | :param instance: the image field
19 | :param context: the context such as node field name
20 | :param filename: the file name
21 | :return: the path file name
22 | """
23 | page_type_name = transliterate(instance.node.page_type.name, True, True)
24 | context = transliterate(context, True, True)
25 | group = transliterate(instance.group, True, True)
26 | filename = transliterate(filename, True, True)
27 | return os.path.join(*filter(None, ('ninecms', page_type_name, context, group, filename)))
28 |
29 |
30 | def image_path_file_name(instance, filename):
31 | """ Callback for image node field to get path file name
32 | :param instance: the image field
33 | :param filename: the file name
34 | :return: the path file name
35 | """
36 | return path_file_name(instance, 'image', filename)
37 |
38 |
39 | def file_path_file_name(instance, filename):
40 | """ Callback for file node field to get path file name
41 | :param instance: the image field
42 | :param filename: the file name
43 | :return: the path file name
44 | """
45 | return path_file_name(instance, 'file', filename)
46 |
47 |
48 | def video_path_file_name(instance, filename):
49 | """ Callback for video node field to get path file name
50 | :param instance: the image field
51 | :param filename: the file name
52 | :return: the path file name
53 | """
54 | return path_file_name(instance, 'video', filename)
55 |
56 |
57 | def validate_ext(value, valid):
58 | """ Validate a file field value for allowed extensions
59 | :param value: the field file value to validate extension
60 | :param valid: the allowed extensions to validate against
61 | :return: None
62 | """
63 | ext = os.path.splitext(value.name)[1] # [0] returns path filename
64 | if ext not in valid:
65 | raise ValidationError("Unsupported file extension.")
66 |
67 |
68 | def validate_file_ext(value):
69 | """ Validate a file field value for allowed file extensions
70 | :param value: the value to validate
71 | :return: None
72 | """
73 | validate_ext(value, ['.txt', '.pdf', '.doc', '.docx', '.odt', '.xls', '.xlsx', '.ods'])
74 |
75 |
76 | def validate_video_ext(value):
77 | """ Validate a video field value for allowed file extensions
78 | :param value: the value to validate
79 | :return: None
80 | """
81 | validate_ext(value, ['.mp4', '.mpeg', '.m4v', '.webm', '.ogg', '.ogv', '.flv', '.jpg'])
82 |
83 |
84 | def find_all(filename):
85 | """ Get all path file names under the path of a given path file name
86 | Useful in order to get all files and files from image styles
87 | Used in media delete signal
88 | http://stackoverflow.com/questions/1724693/find-a-file-in-python
89 | :param filename: the absolute path file name to use
90 | :return: a list of all absolute path file names
91 | """
92 | path = os.path.dirname(filename)
93 | name = os.path.basename(filename)
94 | result = []
95 | for root, dirs, files in os.walk(path):
96 | if name in files:
97 | result.append(os.path.join(root, name))
98 | return result
99 |
100 |
101 | def delete_all(filename):
102 | """ Delete all files that `find_all` returns
103 | :param filename: the absolute path file name to use
104 | :return: None
105 | """
106 | for file in find_all(filename):
107 | os.remove(file)
108 |
109 |
110 | def image_style(image, style):
111 | """ Return the url of different image style
112 | Construct appropriately if not exist
113 | See 9cms-crop.odt
114 |
115 | Available styles
116 | - thumbnail: create a thumbnail restricted to the smaller dimension
117 | - thumbnail-upscale: create a thumbnail that is upscaled if smaller
118 | - thumbnail-crop: create a thumbnail that is cropped to the exact dimension
119 |
120 | :param image: ImageFieldFile
121 | :param style: Specify style to return image
122 | :return: image url of specified style
123 | """
124 | if not image: # pragma: nocover
125 | return image
126 | # original url full: /media/ninecms/basic/image/test.png
127 | url = image.url
128 | # original url without file: /media/ninecms/basic/image
129 | url_path = '/'.join(url.split('/')[:-1])
130 | # original path full: ~/ninecms/media/ninecms/basic/image/test.png
131 | img_path_file_name = str(image.file)
132 | # original file: test.png
133 | img_file_name = os.path.basename(img_path_file_name)
134 |
135 | # style url without file: /media/ninecms/basic/image/large
136 | style_url_path = '/'.join((url_path, style))
137 | # style url full: /media/ninecms/basic/image/large/test.png
138 | style_url = '/'.join((style_url_path, img_file_name))
139 | # style path without file: ~/ninecms/media/ninecms/basic/image/large
140 | style_path = os.path.join(os.path.dirname(img_path_file_name), style)
141 | # style path full: ~/ninecms/media/ninecms/basic/image/large/test.png
142 | style_path_file_name = os.path.join(style_path, img_file_name)
143 |
144 | # the style dict
145 | style_def = settings.IMAGE_STYLES[style]
146 |
147 | if not os.path.exists(style_path_file_name):
148 | if not os.path.exists(style_path):
149 | os.makedirs(style_path)
150 | by = chr(120) # x
151 | plus = chr(43) # +
152 |
153 | # remove original path file name as it may contain spaces, before splitting
154 | # exception: usually file not exists (db or memcached inconsistency)
155 | try:
156 | # noinspection PyUnresolvedReferences
157 | source_size_str = check_output(['identify', img_path_file_name]).decode()
158 | except CalledProcessError: # pragma: nocover
159 | return url
160 | source_size_str = source_size_str[len(img_path_file_name):].split(' ')[2]
161 |
162 | source_size_array = source_size_str.split(by)
163 | source_size_x = int(source_size_array[0])
164 | source_size_y = int(source_size_array[1])
165 | target_size_x = style_def['size'][0]
166 | target_size_y = style_def['size'][1]
167 | target_size_str = str(target_size_x) + by + str(target_size_y)
168 |
169 | # thumbnail
170 | if style_def['type'] == 'thumbnail':
171 | if target_size_x > source_size_x and target_size_y > source_size_y:
172 | target_size_str = source_size_str
173 | call(['convert', img_path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
174 |
175 | # thumbnail-upscale
176 | elif style_def['type'] == 'thumbnail-upscale':
177 | call(['convert', img_path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
178 |
179 | # thumbnail-crop
180 | elif style_def['type'] == 'thumbnail-crop':
181 | source_ratio = float(source_size_x) / float(source_size_y)
182 | target_ratio = float(target_size_x) / float(target_size_y)
183 | if source_ratio > target_ratio: # crop vertically
184 | crop_target_size_x = source_size_y * target_ratio
185 | crop_target_size_y = source_size_y
186 | offset = (source_size_x - crop_target_size_x) / 2
187 | crop_size_str = str(crop_target_size_x) + by + str(crop_target_size_y) + plus + str(offset) + plus + '0'
188 | else: # crop horizontally
189 | crop_target_size_x = source_size_x
190 | crop_target_size_y = source_size_x / target_ratio
191 | offset = (source_size_y - crop_target_size_y) / 2
192 | crop_size_str = str(crop_target_size_x) + by + str(crop_target_size_y) + plus + '0' + plus + str(offset)
193 | call(['convert', img_path_file_name, '-crop', crop_size_str, style_path_file_name])
194 | call(['convert', style_path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
195 | # moderators ^ and \> for -thumbnail and -resize do not work consistently:
196 | # "invalid argument for option `-resize'"
197 | # call(['convert', path_file_name, '-thumbnail', target_size_str + '^', '-gravity', 'center', '-extent',
198 | # target_size_str, '-antialias', style_path_file_name])
199 |
200 | # # crop
201 | # elif style_def['type'] == 'crop':
202 | # call(['convert', img_path_file_name, '-gravity', 'center', '-crop', target_size_str, style_path_file_name])
203 | # call(['convert', img_path_file_name, '-gravity', 'center', '-background', 'None', '-extent',
204 | # target_size_str, style_path_file_name])
205 | return style_url
206 |
--------------------------------------------------------------------------------
/ninecms/utils/nodes.py:
--------------------------------------------------------------------------------
1 | """ Node system utility functions """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.conf import settings
8 |
9 |
10 | def get_full_path(path, language, bookmark=''):
11 | """ Utility function to build a valid path based on an initial
12 | :param path: the initial path
13 | :param language: language component
14 | :param bookmark: bookmark, if any
15 | :return: a valid path string
16 | """
17 | if not path.startswith('/'):
18 | path = '/' + path
19 | if not path.endswith('/'):
20 | path += '/'
21 | path += bookmark
22 | if language and settings.I18N_URLS: # pragma: nocover
23 | path = '/' + language + path
24 | return path
25 |
26 |
27 | def get_clean_url(url):
28 | """ Get a url without the language part, if i18n urls are defined
29 | :param url: a string with the url to clean
30 | :return: a string with the cleaned url
31 | """
32 | url = url.strip('/')
33 | url = '/' if not url else url
34 | return '/'.join(url.split('/')[1:]) if settings.I18N_URLS else url
35 |
--------------------------------------------------------------------------------
/ninecms/utils/perms.py:
--------------------------------------------------------------------------------
1 | """ Permissions utility functions """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | # noinspection PyPackageRequirements
8 | from guardian.shortcuts import get_groups_with_perms, assign_perm, remove_perm
9 |
10 |
11 | def get_perms(obj, fields, suffix):
12 | """ Get all groups which have perms on an object, categorized by permission names (fields)
13 | To be mainly used in form operations to obtain the initial value of fields
14 | The guardian `get_groups_with_perms()` returns a dictionary of groups, with relevant permissions
15 | The list comprehension statement below hecks if the field is in the dict, and appends the group
16 |
17 | :param obj: the record object for which to get permissions
18 | :param fields: the permission names to categorize
19 | :param suffix: an additional string that, when appended on the field name, will give us the perm name
20 | :return: a dictionary of fields: groups list
21 | """
22 | groups = get_groups_with_perms(obj, attach_perms=True) if obj else {}
23 | perms = {}
24 | for field in fields:
25 | perms[field] = list(key for (key, val) in groups.items() if field + suffix in val)
26 | return perms
27 |
28 |
29 | def set_perms(obj, fields, suffix, perms):
30 | """ Set group perms on an object
31 | To be mainly used in form operations, from which the cleaned data of fields are obtained
32 | Check if group is in new perms and not in old, and assign, or vice versa
33 |
34 | :param obj: the record object for which to get permissions
35 | :param fields: the permission names to categorize
36 | :param suffix: an additional string that, when appended on the field name, will give us the perm name
37 | :param perms:
38 | :return: a dictionary of fields: groups list
39 | """
40 | old_perms = get_perms(obj, fields, suffix)
41 | for field in fields:
42 | for group in perms[field]:
43 | if group not in old_perms[field]:
44 | assign_perm(field + suffix, group, obj)
45 | for group in old_perms[field]:
46 | if group not in perms[field]:
47 | remove_perm(field + suffix, group, obj)
48 |
--------------------------------------------------------------------------------
/ninecms/utils/render.py:
--------------------------------------------------------------------------------
1 | """ Node render utility functions """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.conf import settings
8 | from django.views.generic import View
9 | from django.db.models import Q
10 | from django.http import HttpResponse
11 | from django.template import loader
12 | from django.utils.text import slugify
13 | from ninecms.models import Node
14 | from ninecms.signals import block_signal
15 | from ninecms.forms import ContactForm, LoginForm, SearchForm
16 |
17 |
18 | # noinspection PyMethodMayBeStatic
19 | class NodeView(View):
20 | """ Basic page render functions
21 | Base class for ContentNodeEditView, AliasView, IndexView
22 | """
23 | def get_node_by_alias(self, alias, request):
24 | """ Get a node given a path alias
25 | Query from the opposite direction: https://docs.djangoproject.com/en/1.7/topics/db/examples/many_to_one/
26 | The order -language returns first objects with language not empty and then with language empty (tested utf8)
27 | Prefetch related terms is not necessary if node.terms.all are not called in template (adds 1 query)
28 | But if terms are populated in template then this reduces the number of queries to 1 from 2
29 | Also .prefetch_related('terms__nodes')\ can be added if necessary
30 | Requires url alias in db without slashes
31 | :param alias: a url path alias
32 | :param request: the request object
33 | :return: a Node object
34 | """
35 | # .prefetch_related('image_set')\
36 | # .prefetch_related('terms')\
37 | return Node.objects\
38 | .filter(alias=alias)\
39 | .filter(language__in=(request.LANGUAGE_CODE, ''))\
40 | .select_related('page_type')\
41 | .order_by('-language', 'id')[0]
42 |
43 | def construct_classes(self, type_classes, request):
44 | """ Construct default body classes for a page
45 | :param type_classes: an individual page type name
46 | :param request: the request object
47 | :return: a classes string
48 | """
49 | classes = ' '.join(list('page-' + slugify(c) for c in type_classes))
50 | classes += ' i18n-' + request.LANGUAGE_CODE
51 | if request.user.is_authenticated():
52 | classes += ' logged-in'
53 | if request.user.is_superuser:
54 | classes += ' superuser'
55 | return classes
56 |
57 | def session_pop(self, request, key, default):
58 | """ Return the value of a session key if exists and pop it; otherwise return default
59 | :param request: request object
60 | :param key: the session key to pop if exists
61 | :param default: a default value if key not exists
62 | :return: session key or default value
63 | """
64 | return request.session.pop(key) if key in request.session else default
65 |
66 | def construct_context(self, node, request):
67 | """ Construct the page context
68 | Render all blocks in a node page
69 | :param node: the node requested
70 | :param request: the request object
71 | :return: context dictionary
72 | """
73 | # construct the page context
74 | title = node.title if node.title == settings.SITE_NAME else ' | '.join((node.title, settings.SITE_NAME))
75 | status = "published" if node.status else "unpublished"
76 | page = {
77 | 'title': title,
78 | 'classes': self.construct_classes((node.page_type.name, 'content', status), request),
79 | 'node': node,
80 | 'author': settings.SITE_AUTHOR,
81 | 'keywords': settings.SITE_KEYWORDS,
82 | }
83 |
84 | # get all elements (block instances) for this page type and append to page context
85 | # conveniently structure blocks to be able to access by name instead of looping in template
86 | for block in node.page_type.blocks.all():
87 | reg = slugify(block.name).replace('-', '_')
88 | # static node render
89 | if block.type == 'static':
90 | if block.node.language in (request.LANGUAGE_CODE, '') and block.node.status == 1:
91 | page[reg] = block.node
92 | # menu render
93 | elif block.type == 'menu':
94 | if block.menu_item.language in (request.LANGUAGE_CODE, '') and block.menu_item.disabled == 0:
95 | page[reg] = block.menu_item.get_descendants()
96 | # signal (view) render
97 | elif block.type == 'signal':
98 | responses = block_signal.send(sender=self.__class__, view=block.signal, node=node, request=request)
99 | responses = list(filter(lambda response: response[1] is not None, responses))
100 | if responses:
101 | page[reg] = responses[-1][1]
102 | # contact form render
103 | elif block.type == 'contact':
104 | page[reg] = ContactForm(self.session_pop(request, 'contact_form_post', None), initial=request.GET)
105 | # language menu render
106 | elif block.type == 'language':
107 | page[reg] = settings.LANGUAGE_MENU_LABELS
108 | # login
109 | elif block.type == 'login':
110 | page[reg] = LoginForm(self.session_pop(request, 'login_form_post', None))
111 | # user menu
112 | elif block.type == 'user-menu':
113 | page[reg] = True
114 | # search form
115 | elif block.type == 'search':
116 | page[reg] = SearchForm(request.GET)
117 | # search results
118 | elif block.type == 'search-results':
119 | form = SearchForm(request.GET)
120 | form.is_valid()
121 | results = None
122 | if 'q' in form.cleaned_data:
123 | q = form.cleaned_data['q']
124 | results = Node.objects.filter(Q(title__icontains=q) | Q(body__icontains=q) |
125 | Q(summary__icontains=q) | Q(highlight__icontains=q))
126 | results = {'q': q, 'nodes': results}
127 | page[reg] = results
128 | return page
129 |
130 | def render(self, node, request):
131 | """
132 | Render shortcut function
133 | Select the proper template based on page type and construct context
134 | :param node: the node requested
135 | :param request: the request object
136 | :return: rendered http response
137 | """
138 | page_type_name = slugify(node.page_type.name).replace('-', '_')
139 | t = loader.select_template((
140 | 'ninecms/page_%s.html' % page_type_name,
141 | 'ninecms/%s.html' % page_type_name,
142 | 'ninecms/index.html',
143 | ))
144 | return HttpResponse(t.render(self.construct_context(node, request), request))
145 |
--------------------------------------------------------------------------------
/ninecms/utils/sanitize.py:
--------------------------------------------------------------------------------
1 | """ Sanitize text input """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.utils.html import strip_tags
8 | from django import forms
9 | import bleach
10 |
11 |
12 | def sanitize(t, allow_html=True, full_html=False):
13 | """ Bleach clean shortcut function based on pre-defined tags, attributes, styles
14 | NOTE: the allow_html option makes a strip_tag, not bleach, NEVER expose these values with |safe
15 | This is because bleach and escape turn all <>& to html entities
16 | :param t: input text
17 | :return: output text
18 | """
19 | if not t:
20 | return t
21 | if not allow_html:
22 | return strip_tags(t)
23 | allowed_tags = bleach.ALLOWED_TAGS + ['cite', 'dl', 'dt', 'dd', 'p', 'u', 's', 'sub', 'sup', 'img',
24 | 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr', 'iframe',
25 | 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'br']
26 | if full_html:
27 | allowed_tags += ['div']
28 | allowed_attributes = {
29 | 'a': ['href', 'title', 'name', 'target', 'class'],
30 | 'abbr': ['title'],
31 | 'acronym': ['title'],
32 | 'p': ['style', 'class'],
33 | 'img': ['src', 'alt', 'title', 'class'],
34 | 'iframe': ['src', 'height', 'width', 'class'],
35 | 'table': ['border', 'cellpadding', 'cellspacing'],
36 | 'th': ['scope', 'rowspan', 'colspan', 'class'],
37 | 'td': ['scope', 'rowspan', 'colspan', 'class'],
38 | 'span': ['style', 'class'],
39 | 'div': ['style', 'class'],
40 | }
41 | allowed_styles = ['margin-left', 'text-align', 'width', 'page-break-after', 'display', 'float']
42 | return bleach.clean(t, tags=allowed_tags, attributes=allowed_attributes, styles=allowed_styles)
43 |
44 |
45 | class ModelSanitizeForm(forms.ModelForm):
46 | """ A ModelForm that sanitizes specified fields """
47 | def clean(self):
48 | """ Override clean function to sanitize data
49 | :return: cleaned data
50 | """
51 | cleaned_data = super(ModelSanitizeForm, self).clean()
52 | for field in self.Meta.sanitize:
53 | if field in cleaned_data:
54 | cleaned_data[field] = sanitize(cleaned_data[field], allow_html=False)
55 | return cleaned_data
56 |
--------------------------------------------------------------------------------
/ninecms/utils/status.py:
--------------------------------------------------------------------------------
1 | """ Status utility functions """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.conf import settings
8 | from django.core.cache import caches, cache
9 | from django.contrib.auth.models import User
10 | from django.core.management import call_command
11 | from ninecms.models import Node, PageType, Image
12 | from subprocess import call, CalledProcessError
13 | from io import StringIO
14 | import sys
15 | import os
16 | # noinspection PyPackageRequirements
17 | import pip
18 | import ninecms
19 |
20 |
21 | class Capturing(list):
22 | """ Capture the stdout using a with statement """
23 | def __enter__(self):
24 | """ Initialise the stdout to StringIO
25 | Keep a reference of the initial stdout to self_stdout before
26 | :return: self
27 | """
28 | self._stdout = sys.stdout
29 | sys.stdout = self._stringio = StringIO()
30 | return self
31 |
32 | # noinspection PyUnusedLocal
33 | def __exit__(self, *args):
34 | """ Reset the stdout to the initial
35 | :param args
36 | :return: None
37 | """
38 | self.extend(self._stringio.getvalue().splitlines())
39 | sys.stdout = self._stdout
40 |
41 |
42 | def version():
43 | """ Return the current 9cms version
44 | :return: a version string
45 | """
46 | return 'v%s' % ninecms.__version__
47 |
48 |
49 | def packages():
50 | """ Get a list of all installed packages
51 | Cannot use pip.main as this prints in console and returns integer code
52 | :return: a list with all installed packages
53 | """
54 | return pip.get_installed_distributions()
55 |
56 |
57 | def updates():
58 | """ Get a list of updates from the cache, as written by admin command check_updates
59 | :return: List or None
60 | """
61 | return caches['default'].get('updates')
62 |
63 |
64 | def django_check():
65 | """ Perform django system checks using the admin command
66 | :return: a dictionary with the normal and the error outputs of the command
67 | """
68 | stdout = StringIO()
69 | stderr = StringIO()
70 | call_command('check', stdout=stdout, stderr=stderr)
71 | return {'stdout': stdout.getvalue(), 'stderr': stderr.getvalue()}
72 |
73 |
74 | def django_migrations():
75 | """ Get all non-applied migrations
76 | :return: list
77 | """
78 | with Capturing() as migrations:
79 | call_command('showmigrations')
80 | return list(x for x in migrations if '[ ]' in x)
81 |
82 |
83 | def permissions():
84 | """ Check permissions for certain paths or files
85 | :return: a list of (permission name, result)
86 | """
87 | perms = [
88 | ("Media folder `%s` writable" % settings.MEDIA_ROOT.split('/')[-1], os.access(settings.MEDIA_ROOT, os.W_OK)),
89 | ("9cms folder `ninecms` not writable", not os.access(os.path.join(settings.BASE_DIR, 'ninecms'), os.W_OK)),
90 | ("WSGI file `index.wsgi` not writable", not os.access(os.path.join(settings.BASE_DIR, 'index.wsgi'), os.W_OK)),
91 | ("Script `manage.py` not writable", not os.access(os.path.join(settings.BASE_DIR, 'manage.py'), os.W_OK)),
92 | ]
93 | for folder in settings.STATICFILES_DIRS:
94 | perms += [("Static folder `%s` not writable" % folder.split('/')[-1], not os.access(folder, os.W_OK))]
95 | for template in settings.TEMPLATES:
96 | for folder in template['DIRS']: # pragma: nocover
97 | perms += [("Templates folder `%s` not writable" % folder.split('/')[-1], not os.access(folder, os.W_OK))]
98 | if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3':
99 | perms += [(
100 | "Sqlite database",
101 | os.access(os.path.join(settings.BASE_DIR, settings.DATABASES['default']['NAME']), os.W_OK)
102 | )]
103 | return perms
104 |
105 |
106 | def permissions_status():
107 | """ Return a permissions list along with a status
108 | Functin permissions() to become obsolete in next version
109 | :return: a dictionary with a perms list and a bool
110 | """
111 | perms = permissions()
112 | return {
113 | 'perms': perms,
114 | 'status': all(x[1] for x in perms)
115 | }
116 |
117 |
118 | def check_command(command):
119 | """ Check if a command is supported
120 | :param command: the command to execute
121 | :return: boolean
122 | """
123 | try:
124 | call([command])
125 | except CalledProcessError: # pragma: nocover
126 | return False
127 | except FileNotFoundError: # pragma: nocover
128 | return False
129 | else:
130 | return True
131 |
132 |
133 | def imagemagick_status():
134 | """ Check that imagemagick utilities are available
135 | :return: a list of (imagemagick utility, result)
136 | """
137 | return not(check_command('identify') and check_command('convert'))
138 |
139 |
140 | def user_stat(user):
141 | """ Get user stats
142 | Use a list in loop to avoid repeating code
143 | Use lists for configuring loop to avoid re-typing keys
144 | In the loop we then zip it in a dictionary for the sake of clarity
145 | :return: a list of stats
146 | """
147 | u = User.objects.all()
148 | user_stats_config_keys = ('type', 'icon', 'url', 'url_parameters', 'queryset', 'date_field')
149 | user_stats_config = (
150 | ('users', 'user', 'admin:auth_user_changelist', '', u, 'date_joined'),
151 | ('staff', 'pawn', 'admin:auth_user_changelist', 'is_staff__exact=1', u.filter(is_staff=True), 'date_joined'),
152 | ('superusers', 'king', 'admin:auth_user_changelist', 'is_superuser__exact=1', u.filter(is_superuser=True),
153 | 'date_joined'),
154 | ('nodes', 'file', 'admin:ninecms_node_changelist', '', Node.objects.all(), 'created')
155 | )
156 | if user.is_superuser:
157 | user_stats_config += (
158 | ('page types', 'book', 'admin:ninecms_pagetype_changelist', '', PageType.objects.all(), ''),
159 | ('images', 'camera', 'admin:ninecms_node_changelist', '', Image.objects.all(), ''),
160 | # ('terms', 'tags', 'admin:ninecms_taxonomyterm_changelist', '', TaxonomyTerm.objects.all(), ''),
161 | )
162 | user_stats = []
163 | for stat_list in user_stats_config:
164 | stat = dict(zip(user_stats_config_keys, stat_list))
165 | count = len(stat['queryset'])
166 | if stat['queryset'] == u: # if qs is all users, decrease the anonymous user
167 | count -= 1
168 | last = None
169 | is_recent = False
170 | if count:
171 | last = stat['queryset'].latest('pk')
172 | last_date = getattr(last, stat['date_field'], None) if stat['date_field'] else None
173 | is_recent = (last_date > user.last_login) if last_date else False
174 | user_stats.append({
175 | 'stat_type': stat['type'],
176 | 'icon': stat['icon'],
177 | 'url': stat['url'],
178 | 'parameters': stat['url_parameters'],
179 | 'count': count,
180 | 'last': last,
181 | 'is_recent': is_recent,
182 | })
183 | return user_stats
184 |
185 |
186 | def cache_clear():
187 | """ Clear cache
188 | If not working try: (memcached only) cache._cache.flush_all()
189 | :return: None
190 | """
191 | cache.clear()
192 |
--------------------------------------------------------------------------------
/ninecms/utils/transliterate.py:
--------------------------------------------------------------------------------
1 | """ Transliterate unicode characters """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.conf import settings
8 |
9 |
10 | def transliterate(s, filename=False, to_lower=False):
11 | """ Transliterate unicode characters
12 | Currently supporting Greek, Serbian, Russian, Bulgarian
13 | Priority by order
14 | :param s: the string to transliterate
15 | :param filename: if true, different rule for punctuation is followed
16 | :param to_lower: convert to lowercase
17 | :return: the transliterated string
18 | """
19 | mapping = {
20 | 'el': (
21 | 'αβγδεζηικλμνξοπρστυφωΑΒΓΔΕΖΗΙΚΛΜΝΞΟΠΡΣΤΥΦΩάέίήύόώϊϋΐΰςΆΈΊΉΎΌΏ',
22 | 'abgdeziiklmnxoprstyfoABGDEZIIKLMNXOPRSTYFOaeiiyooiyiysAEIIYOO',
23 | ),
24 | 'rs': (
25 | 'абвгдезијклмнопрстуфхцАБВГДЕЗИЈКЛМНОПРСТУФХЦ',
26 | 'abvgdezijklmnoprstufhcABVGDEZIJKLMNOPRSTUFHC',
27 | ),
28 | 'ru': (
29 | 'абвгдезийклмнопрстуфхъыьАБВГДЕЗИЙКЛМНОПРСТУФХЪЫЬ',
30 | 'abvgdezijklmnoprstufh_y_ABVGDEZIJKLMNOPRSTUFH_Y_',
31 | ),
32 | 'bg': (
33 | 'абвгдезийклмнопрстуфхАБВГДЕЗИЙКЛМНОПРСТУФХ',
34 | 'abvgdeziyklmnoprstufhABVGDEZIYKLMNOPRSTUFH',
35 | ),
36 | }
37 | ext_mapping = {
38 | 'el': (
39 | ('θ', 'χ', 'ψ', 'Θ', 'Χ', 'Ψ'),
40 | ('th', 'ch', 'ps', 'Th', 'Ch', 'Ps')
41 | ),
42 | 'rs': (
43 | ('ђ', 'ж', 'љ', 'њ', 'ћ', 'ч', 'џ', 'ш', 'Ђ', 'Ж', 'Љ', 'Њ', 'Ћ', 'Ч', 'Џ', 'Ш'),
44 | ('dj', 'zh', 'lj', 'nj', 'c', 'ch', 'dz', 'sh', 'Dj', 'Zh', 'Lj', 'Nj', 'C', 'Ch', 'Dz', 'Sh'),
45 | ),
46 | 'rs_latin': (
47 | ('đ', 'ž', 'ć', 'č', 'š', 'Đ', 'Ž', 'Ć', 'Č', 'Š'),
48 | ('dj', 'zh', 'c', 'ch', 'sh', 'Dj', 'Zh', 'C', 'Ch', 'Sh'),
49 | ),
50 | 'ru': (
51 | ('ж', 'ц', 'ч', 'ш', 'щ', 'ю', 'я', 'Ж', 'Ц', 'Ч', 'Ш', 'Щ', 'Ю', 'Я'),
52 | ('zh', 'ts', 'ch', 'sh', 'sch', 'ju', 'ja', 'Zh', 'Ts', 'Ch', 'Sh', 'Sch', 'Ju', 'Ja'),
53 | ),
54 | 'bg': (
55 | ('ж', 'ц', 'ч', 'ш', 'щ', 'ю', 'я', 'Ж', 'Ц', 'Ч', 'Ш', 'Щ', 'Ю', 'Я'),
56 | ('zh', 'ts', 'ch', 'sh', 'sht', 'yu', 'ya', 'Zh', 'Ts', 'Ch', 'Sh', 'Sht', 'Yu', 'Ya'),
57 | ),
58 | }
59 | for lang in mapping:
60 | s = s.translate(str.maketrans(mapping[lang][0], mapping[lang][1]))
61 | for lang in ext_mapping:
62 | for i, val in enumerate(ext_mapping[lang][0]):
63 | s = s.replace(ext_mapping[lang][0][i], ext_mapping[lang][1][i])
64 | # "'`,.-_:;|{[}]+=*&%^$#@!~()?<>/\
65 | remove = settings.TRANSLITERATE_REMOVE
66 | if filename:
67 | remove += '/\?%*:|"<>'
68 | s = s.replace(' ', '_')
69 | else:
70 | s = s.translate(str.maketrans(settings.TRANSLITERATE_REPLACE[0], settings.TRANSLITERATE_REPLACE[1]))
71 | s = s.translate({ord(i): None for i in remove})
72 | if to_lower:
73 | s = s.lower()
74 | return s
75 |
76 |
77 | def upper_no_intonation(s):
78 | """ Convert a string to uppercase, removing any intonation
79 | :param s: the string to convert
80 | :return: the converted string
81 | """
82 | mapping = ('ΆΈΊΉΎΌΏ', 'ΑΕΙΗΥΟΩ')
83 | s = s.upper()
84 | s = s.translate(str.maketrans(mapping[0], mapping[1]))
85 | return s
86 |
--------------------------------------------------------------------------------
/ninecms/views.py:
--------------------------------------------------------------------------------
1 | """ View handler definitions for Nine CMS """
2 | __author__ = 'George Karakostas'
3 | __copyright__ = 'Copyright 2015, George Karakostas'
4 | __licence__ = 'BSD-3'
5 | __email__ = 'gkarak@9-dev.com'
6 |
7 | from django.shortcuts import render, redirect, get_object_or_404
8 | from django.views.generic import View
9 | from django.template import loader
10 | from django.http import Http404
11 | from django.core.mail import mail_managers, BadHeaderError
12 | from django.core.exceptions import PermissionDenied
13 | from django.contrib import messages
14 | from django.contrib.auth import authenticate, login, logout
15 | from django.contrib.auth.models import Group
16 | from django.utils.translation import ugettext as _
17 | from ninecms.utils.render import NodeView
18 | from ninecms.utils.perms import get_perms, set_perms
19 | from ninecms.utils import status
20 | from ninecms.models import Node, PageType, MenuItem
21 | from ninecms.forms import ContactForm, LoginForm, RedirectForm, ContentTypePermissionsForm
22 |
23 |
24 | class ContentNodeView(NodeView):
25 | """ Display a node as invoked by its node id from /cms/content/
26 | If an alias exists then issue redirect to allow a single content page per URL
27 | """
28 | def get(self, request, **kwargs):
29 | """ HTML get for /cms/content/
30 | :param request: the request object
31 | :param kwargs: contains node_id
32 | :return: response object
33 | """
34 | node = get_object_or_404(Node, id=kwargs['node_id'])
35 | if node.alias:
36 | return redirect('ninecms:alias', url_alias=(node.alias + '/'), permanent=True)
37 | if not node.status and not request.user.has_perm('ninecms.view_unpublished'):
38 | raise PermissionDenied
39 | return self.render(node, request)
40 |
41 |
42 | class AliasView(NodeView):
43 | """ Render content based on Url Alias """
44 | def get(self, request, **kwargs):
45 | """ HTML get for /
46 | :param request: the request object
47 | :param kwargs: contains url_alias
48 | :return: response object
49 | """
50 | if kwargs['url_alias'][-1] == '/':
51 | alias = kwargs['url_alias'][:-1]
52 | if alias == '/':
53 | return redirect('ninecms:index', permanent=True) # pragma: no cover
54 | try:
55 | node = self.get_node_by_alias(alias, request)
56 | except IndexError:
57 | raise Http404
58 | if not node.status and not request.user.has_perm('ninecms.view_unpublished'):
59 | raise PermissionDenied
60 | if node.redirect:
61 | return redirect(node.get_redirect_path(), permanent=True)
62 | return self.render(node, request)
63 | else:
64 | return redirect('ninecms:alias', url_alias=(kwargs['url_alias'] + '/'), permanent=True)
65 |
66 |
67 | class IndexView(NodeView):
68 | """ Render index at root / """
69 | def get(self, request):
70 | """ HTML get for /
71 | :param request: the request object
72 | :return: response object
73 | """
74 | try:
75 | node = self.get_node_by_alias('/', request)
76 | except IndexError:
77 | messages.warning(request, "No front page has been created yet.")
78 | return redirect('admin:index')
79 | return self.render(node, request)
80 |
81 |
82 | class ContactView(View):
83 | """ Handle contact post request """
84 | form_class = ContactForm
85 |
86 | def post(self, request):
87 | """ Handle contact form send
88 | :param request: the request object
89 | :return: response object
90 | """
91 | form = self.form_class(request.POST)
92 | if form.is_valid():
93 | t = loader.get_template('ninecms/mail_contact.txt')
94 | c = {
95 | 'sender_name': form.cleaned_data['sender_name'],
96 | 'sender_email': form.cleaned_data['sender_email'],
97 | 'message': form.cleaned_data['message'],
98 | }
99 | try:
100 | mail_managers(form.cleaned_data['subject'], t.render(c))
101 | except BadHeaderError: # pragma: no cover
102 | messages.error(request, _("Contact form message has NOT been sent. Invalid header found."))
103 | else:
104 | messages.success(request, _("A message has been sent to the site using the contact form."))
105 | return redirect(form.cleaned_data['redirect'])
106 | messages.warning(request, _("Contact form message has NOT been sent. Please fill in all contact form fields."))
107 | request.session['contact_form_post'] = request.POST
108 | return redirect(form.cleaned_data['redirect'])
109 |
110 |
111 | class LoginView(View):
112 | """ Handle login post request """
113 | form_class = LoginForm
114 |
115 | def post(self, request):
116 | """ Handle login form send
117 | :param request: the request object
118 | :return: response object
119 | """
120 | form = self.form_class(request.POST)
121 | if form.is_valid():
122 | user = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password'])
123 | if user is not None:
124 | if user.is_active:
125 | login(request, user)
126 | messages.success(request, _("Login successful for %s.") % user.username)
127 | else:
128 | msg = _("The account is disabled. Please use the contact form for more information.")
129 | messages.warning(request, msg)
130 | else:
131 | msg = _("Unfortunately the username or password are not correct. "
132 | "If you have forgotten your password use the link below the form to recover it.")
133 | messages.warning(request, msg)
134 | else:
135 | messages.warning(request, _("Please fill in all login form fields."))
136 | request.session['login_form_post'] = request.POST
137 | return redirect(form.cleaned_data['redirect'])
138 |
139 |
140 | class LogoutView(View):
141 | """ Handle logout post request """
142 | form_class = RedirectForm
143 |
144 | def post(self, request):
145 | """ Handle logout form send
146 | :param request: the request object
147 | :return: response object
148 | """
149 | form = self.form_class(request.POST)
150 | if form.is_valid():
151 | logout(request)
152 | messages.success(request, _("Logout successful."))
153 | return redirect(form.cleaned_data['redirect'])
154 |
155 |
156 | class ContentTypePermsView(View):
157 | """ Content edit form at /cms/types//edit """
158 | permissions_form_class = ContentTypePermissionsForm
159 |
160 | def get(self, request, **kwargs):
161 | """ HTML get for /cms/types//edit
162 | :param request: the request object
163 | :param kwargs: contains node_id
164 | :return: response object
165 | """
166 | page_type = get_object_or_404(PageType, id=kwargs['type_id'])
167 | perms = get_perms(page_type, list(self.permissions_form_class().fields.keys()), '_pagetype')
168 | permissions_form = self.permissions_form_class(initial=perms)
169 | groups = Group.objects.all().count()
170 | return render(request, 'admin/ninecms/pagetype/perms_form.html', {
171 | 'permissions_form': permissions_form,
172 | 'groups': groups,
173 | 'page_type': page_type,
174 | 'clone': kwargs.get('clone', False),
175 | })
176 |
177 | def post(self, request, **kwargs):
178 | """ HTML post for /cms/types//edit
179 | :param request: the request object
180 | :param kwargs: contains node_id
181 | :return: response object
182 | """
183 | page_type = get_object_or_404(PageType, id=kwargs['type_id'])
184 | permissions_form = self.permissions_form_class(request.POST)
185 | groups = Group.objects.all().count()
186 | if permissions_form.is_valid():
187 | if request.user.has_perm(('guardian.add_groupobjectpermission',
188 | 'guardian.change_groupobjectpermission',
189 | 'guardian.delete_groupobjectpermission')):
190 | set_perms(page_type, list(permissions_form.fields.keys()), '_pagetype', permissions_form.cleaned_data)
191 | messages.success(request, _("Content type '%s' has been updated.") % page_type.name)
192 | return redirect('admin:ninecms_pagetype_changelist')
193 | else: # pragma: nocover
194 | messages.warning(request, _("Content type has not been updated. Please check the form for errors."))
195 | return render(request, 'admin/ninecms/pagetype/perms_form.html', {
196 | 'permissions_form': permissions_form,
197 | 'groups': groups,
198 | 'page_type': page_type,
199 | })
200 |
201 |
202 | class StatusView(View):
203 | """ Status page at cms/status """
204 | # noinspection PyUnusedLocal
205 | def get(self, request):
206 | """ HTML get for status page
207 | :return: response object
208 | """
209 | return redirect('admin:index', permanent=True)
210 |
211 | # noinspection PyMethodMayBeStatic
212 | def post(self, request):
213 | """ HTML post for status page
214 | If menu-rebuild has been posted, rebuild menu and redirect
215 | If clear-cache has been posted, clear cache and redirect
216 | :param request: the request object
217 | :return: response object (redirect to get)
218 | """
219 | if 'menu-rebuild' in request.POST:
220 | # noinspection PyUnresolvedReferences
221 | MenuItem.objects.rebuild()
222 | messages.success(request, _("Menu has been rebuilt."))
223 | if 'clear-cache' in request.POST:
224 | status.cache_clear()
225 | messages.success(request, _("Cache has been cleared."))
226 | return redirect('admin:index')
227 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | bleach~=1.4.2
2 | django-admin-bootstrapped~=2.5.7
3 | django-bootstrap3~=7.0.1
4 | Django~=1.9.4
5 | django-guardian~=1.4.2
6 | django-mptt~=0.8.3
7 | Pillow~=3.2.0
8 | pytz~=2016.3
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import ninecms
3 | from setuptools import setup
4 |
5 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
6 | README = readme.read()
7 |
8 | # allow setup.py to be run from any path
9 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
10 |
11 | setup(
12 | name='django-ninecms',
13 | version=ninecms.__version__,
14 | description="Nine CMS is a Django app to manage content.",
15 | long_description=README,
16 | url='https://github.com/Wtower/django-ninecms/',
17 | author='George Karakostas',
18 | author_email='info@9-dev.com',
19 | license='BSD-3 License',
20 | keywords='cms content management system',
21 | packages=['ninecms'],
22 | include_package_data=True,
23 | install_requires=[
24 | 'Django',
25 | 'django-guardian',
26 | 'django-mptt',
27 | 'bleach',
28 | 'Pillow',
29 | 'pytz',
30 | ],
31 | classifiers=[
32 | 'Environment :: Web Environment',
33 | 'Framework :: Django',
34 | 'Intended Audience :: Developers',
35 | 'License :: OSI Approved :: BSD License',
36 | 'Operating System :: OS Independent',
37 | 'Programming Language :: Python',
38 | 'Programming Language :: Python :: 3',
39 | 'Programming Language :: Python :: 3.4',
40 | 'Programming Language :: Python :: 3.5',
41 | 'Topic :: Internet :: WWW/HTTP',
42 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
43 | ],
44 | )
45 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The `/tests` package contains all necessary files in order to support
3 | the execution of tests under `/ninecms/tests`
4 | """
5 |
--------------------------------------------------------------------------------
/tests/media/ninecms/basic/image/test_big.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/tests/media/ninecms/basic/image/test_big.jpg
--------------------------------------------------------------------------------
/tests/media/ninecms/basic/image/test_big_portrait.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/tests/media/ninecms/basic/image/test_big_portrait.jpg
--------------------------------------------------------------------------------
/tests/media/ninecms/basic/image/test_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wtower/django-ninecms/e500010fb11f06c8dfe8d8c9c4d2aab0b15bc127/tests/media/ninecms/basic/image/test_small.png
--------------------------------------------------------------------------------
/tests/settings_test.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for testing
3 | """
4 |
5 | import os
6 |
7 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8 |
9 | SECRET_KEY = '1234567890'
10 |
11 | DEBUG = True
12 |
13 | ALLOWED_HOSTS = []
14 |
15 | PASSWORD_HASHERS = (
16 | 'django.contrib.auth.hashers.MD5PasswordHasher',
17 | )
18 |
19 |
20 | # Application definition
21 |
22 | INSTALLED_APPS = (
23 | 'django_admin_bootstrapped',
24 | 'django.contrib.admin',
25 | 'django.contrib.auth',
26 | 'django.contrib.contenttypes',
27 | 'django.contrib.sessions',
28 | 'django.contrib.messages',
29 | 'django.contrib.staticfiles',
30 | 'mptt',
31 | 'guardian',
32 | 'ninecms',
33 | )
34 |
35 | MIDDLEWARE_CLASSES = (
36 | 'django.middleware.cache.UpdateCacheMiddleware',
37 | 'django.contrib.sessions.middleware.SessionMiddleware',
38 | 'django.middleware.locale.LocaleMiddleware',
39 | 'django.middleware.common.CommonMiddleware',
40 | 'django.middleware.cache.FetchFromCacheMiddleware',
41 | 'django.middleware.csrf.CsrfViewMiddleware',
42 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
43 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
44 | 'django.contrib.messages.middleware.MessageMiddleware',
45 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
46 | 'django.middleware.security.SecurityMiddleware',
47 | )
48 |
49 | TEMPLATES = [
50 | {
51 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
52 | 'DIRS': [
53 | os.path.join(BASE_DIR, 'tests', 'templates'),
54 | ],
55 | 'APP_DIRS': True,
56 | 'OPTIONS': {
57 | 'context_processors': [
58 | 'django.template.context_processors.debug',
59 | 'django.template.context_processors.request',
60 | 'django.contrib.auth.context_processors.auth',
61 | 'django.contrib.messages.context_processors.messages',
62 | ],
63 | 'debug': True,
64 | },
65 | },
66 | ]
67 |
68 | # WSGI_APPLICATION = 'ninecms_starter.wsgi.application'
69 |
70 | ROOT_URLCONF = 'tests.urls'
71 |
72 |
73 | # Database
74 |
75 | DATABASES = {
76 | 'default': {
77 | 'ENGINE': 'django.db.backends.sqlite3',
78 | 'NAME': ':memory:',
79 | },
80 | }
81 |
82 |
83 | # Internationalization
84 |
85 | LANGUAGE_CODE = 'en'
86 |
87 | LANGUAGES = (
88 | ('en', 'English'),
89 | ('el', 'Greek'),
90 | )
91 |
92 | TIME_ZONE = 'UTC'
93 |
94 | USE_I18N = True
95 |
96 | USE_L10N = True
97 |
98 | USE_TZ = True
99 |
100 |
101 | # Static files (CSS, JavaScript, Images)
102 | # https://docs.djangoproject.com/en/1.8/howto/static-files/
103 |
104 | STATIC_URL = '/static/'
105 |
106 | STATICFILES_DIRS = (
107 | os.path.join(BASE_DIR, "static"),
108 | )
109 |
110 |
111 | # Media
112 |
113 | MEDIA_ROOT = os.path.join(BASE_DIR, 'tests', 'media')
114 |
115 | MEDIA_URL = '/media/'
116 |
117 |
118 | # Error reporting
119 |
120 | ADMINS = (
121 | ("Webmaster", "web@9-dev.com"),
122 | )
123 |
124 | MANAGERS = (
125 | ("Webmaster", "web@9-dev.com"),
126 | )
127 |
128 | EMAIL_HOST = 'mail.9-dev.com'
129 |
130 | EMAIL_HOST_USER = 'do-not-reply@9-dev.com'
131 |
132 | EMAIL_HOST_PASSWORD = ''
133 |
134 | EMAIL_USE_SSL = True
135 |
136 | EMAIL_PORT = 465
137 |
138 | EMAIL_SUBJECT_PREFIX = '[9cms] '
139 |
140 | SERVER_EMAIL = 'do-not-reply@9-dev.com'
141 |
142 | DEFAULT_FROM_EMAIL = 'do-not-reply@9-dev.com'
143 |
144 |
145 | # Security
146 |
147 | LOGIN_URL = '/admin/login/'
148 |
149 | SECURE_CONTENT_TYPE_NOSNIFF = True
150 |
151 | SECURE_BROWSER_XSS_FILTER = True
152 |
153 | X_FRAME_OPTIONS = 'DENY'
154 |
155 | CSRF_COOKIE_HTTPONLY = True
156 |
157 | SESSION_COOKIE_NAME = 'ninecms_sessionid'
158 |
159 |
160 | # Caches
161 |
162 | CACHES = {
163 | 'default': {
164 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
165 | }
166 | }
167 |
168 | CACHE_MIDDLEWARE_SECONDS = 3 * 60 * 60 # or whatever
169 |
170 |
171 | # Guardian
172 |
173 | AUTHENTICATION_BACKENDS = (
174 | 'django.contrib.auth.backends.ModelBackend', # this is default
175 | 'guardian.backends.ObjectPermissionBackend',
176 | )
177 |
178 | ANONYMOUS_USER_ID = -1
179 |
180 |
181 | # Django admin
182 |
183 | DAB_FIELD_RENDERER = 'django_admin_bootstrapped.renderers.BootstrapFieldRenderer'
184 |
185 | # MESSAGE_TAGS = {
186 | # messages.SUCCESS: 'alert-success success',
187 | # messages.WARNING: 'alert-warning warning',
188 | # messages.ERROR: 'alert-danger error'
189 | # }
190 |
191 |
192 | # NineCMS settings
193 |
194 | # noinspection PyUnresolvedReferences
195 | from ninecms.settings import *
196 |
197 | IMAGE_STYLES.update({
198 | 'thumbnail-upscale': {
199 | 'type': 'thumbnail-upscale',
200 | 'size': (150, 150)
201 | },
202 | })
203 |
--------------------------------------------------------------------------------
/tests/templates/ninecms/page_basic.html:
--------------------------------------------------------------------------------
1 | {% extends 'ninecms/index.html' %}
2 |
3 | {% comment %}
4 | Template override for tests
5 | Basic page
6 | {% endcomment %}
7 |
8 | {% block main %}
9 | {% include 'ninecms/block_search_results.html' with results=search_results %}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/tests/templates/ninecms/page_front.html:
--------------------------------------------------------------------------------
1 | {% extends 'ninecms/index.html' %}
2 |
3 | {% comment %}
4 | Template override for tests
5 | Front page
6 | Render various content necessary to test adequately all blocks
7 | {% endcomment %}
8 |
9 | {% block main %}
10 | {# tests_content #}
11 | {% include 'ninecms/block_contact.html' %}
12 | {% include 'ninecms/block_login.html' %}
13 | {% include 'ninecms/block_user_menu.html' %}
14 | {% include 'ninecms/block_language.html' %}
15 | {% include 'ninecms/block_menu_header.html' with menu=menu_main_menu %}
16 | {% include 'ninecms/block_static.html' with node=static_about_1 %}
17 | {% include 'ninecms/block_static.html' with node=static_about_2 %}
18 | {% include 'ninecms/block_static.html' with node=static_about_3 %}
19 | {% include 'ninecms/block_static.html' with node=static_about_4 %}
20 | {% include 'ninecms/block_signal_terms.html' with content=signal_terms %}
21 | {% include 'ninecms/block_search.html' %}
22 |
23 | {# tests_content_i18n #}
24 | {% include 'ninecms/block_static.html' with node=static_1st_lang_0 %}
25 | {% include 'ninecms/block_static.html' with node=static_2nd_lang_0 %}
26 | {% include 'ninecms/block_static.html' with node=static_1st_lang_1 %}
27 | {% include 'ninecms/block_static.html' with node=static_2nd_lang_1 %}
28 | {% include 'ninecms/block_static.html' with node=static_1st_lang_2 %}
29 | {% include 'ninecms/block_static.html' with node=static_2nd_lang_2 %}
30 | {% include 'ninecms/block_static.html' with node=static_1st_lang_3 %}
31 | {% include 'ninecms/block_static.html' with node=static_2nd_lang_3 %}
32 |
33 | {% if node.terms.all %}
34 | Tags
35 |
36 | {% for term in node.terms.all %}
37 | {{ term.name }}
38 | {% endfor %}
39 |
40 | {% endif %}
41 | {% endblock %}
42 |
--------------------------------------------------------------------------------
/tests/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | ninecms tests URL Configuration
3 | """
4 | from django.conf import settings
5 | from django.conf.urls import include, url
6 | from django.conf.urls.i18n import i18n_patterns
7 | from django.conf.urls.static import static
8 | from django.contrib import admin
9 | from django.views.generic import TemplateView
10 |
11 | urlpatterns = [
12 | url(r'^admin/', include(admin.site.urls)),
13 | url(r'^i18n/', include('django.conf.urls.i18n')),
14 | url(r'^robots\.txt/$', TemplateView.as_view(template_name='ninecms/robots.txt', content_type='text/plain')),
15 | ]
16 |
17 | # static files (images, css, javascript, etc.)
18 | if settings.DEBUG:
19 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # pragma: no cover
20 |
21 | # Last: all remaining pass to CMS
22 | if settings.I18N_URLS: # pragma: nocover
23 | urlpatterns += i18n_patterns(
24 | url(r'^', include('ninecms.urls', namespace='ninecms')),
25 | )
26 | else: # pragma: nocover
27 | urlpatterns += [
28 | url(r'^', include('ninecms.urls', namespace='ninecms')),
29 | ]
30 |
--------------------------------------------------------------------------------