From 2382fd47f079685ad8c89b84e19a4589b4dbec3c Mon Sep 17 00:00:00 2001 From: Jaap Joris Vens Date: Sun, 22 Mar 2020 19:57:48 +0100 Subject: [PATCH] Tidying up! --- README.md | 88 ++++++++++++++++++- bin/simplecms | 22 ----- cms/apps.py | 2 +- cms/bin/simplecms | 26 ++++++ cms/decorators.py | 4 +- cms/forms.py | 27 ++---- cms/middleware.py | 38 ++++---- cms/registry.py | 12 ++- cms/static/cms/admin.scss | 9 +- cms/static/cms/admin.scss.css | 13 ++- cms/static/cms/admin.scss.css.map | 2 +- cms/templates/cms/edit.html | 4 +- example/migrations/0001_initial.py | 23 ++--- example/migrations/0002_initial_homepage.bak | 16 ---- example/models.py | 2 +- example/settings.py | 15 ++-- example/static/{app => }/hamburgers.css | 0 example/static/{app => }/main1.scss | 0 example/static/{app => }/main1.scss.css | 0 example/static/{app => }/main1.scss.css.map | 0 example/templates/base.html | 4 +- .../templates/{app/sections => }/contact.html | 0 .../templates/{app/sections => }/images.html | 0 .../templates/{app/sections => }/text.html | 0 .../templates/{app/sections => }/video.html | 0 example/{cms.py => views.py} | 20 ++--- setup.py | 4 +- 27 files changed, 205 insertions(+), 126 deletions(-) delete mode 100755 bin/simplecms create mode 100755 cms/bin/simplecms delete mode 100644 example/migrations/0002_initial_homepage.bak rename example/static/{app => }/hamburgers.css (100%) rename example/static/{app => }/main1.scss (100%) rename example/static/{app => }/main1.scss.css (100%) rename example/static/{app => }/main1.scss.css.map (100%) rename example/templates/{app/sections => }/contact.html (100%) rename example/templates/{app/sections => }/images.html (100%) rename example/templates/{app/sections => }/text.html (100%) rename example/templates/{app/sections => }/video.html (100%) rename example/{cms.py => views.py} (59%) diff --git a/README.md b/README.md index 3dd465c..6757cd2 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,93 @@ *A super simple but very extensible content management system for Django websites.* -This project provides the basic building blocks of *Pages* and -*Sections* and all the views needed to display and edit them. +SimpleCMS provides the reusable Django app `cms` which contains +everything you need to create websites that can be easily edited by +end users. + +## How does it work? + +Contrary to 'regular' Django websites, SimpleCMS allows you to write a +view for each *section*, rather than for each *page* on your website. +On which pages these sections appear, and in which order, is left to +the content editor rather than the programmer. After authenticating, +the editor can use the {page,section}{create,update} forms to fill the +website with various types of content. + +Here's an example `views.py` of an app using SimpleCMS: + + from cms.views import SectionView + from cms.decorators import section_view + + @section_view + class HelloWorld(SectionView): + verbose_name = 'Hello world section' + fields = ['content'] + template_name = 'hello.html' + + def get_context_data(self, **kwargs): + context = super()get_context_data(**kwargs) + context['message'] = 'Hello World!' + +And here is the contents of `hello.html`: + +
+

{{message}}

+ {{section.content}} +
+ +Everytime a section needs to be rendered, your section view will be +called by SimpleCMS and return a response which will get inserted into +the final rendered page. + +## The "Edit" Interface + +Somewhat like the Django Admin site, SimpleCMS comes with its own +editing environment, albeit much simpler and only suitable for editing +pages and sections. After authenticating, the content editor can click +the "edit" button on any page of the website to alter, add or +rearrange sections. + +For each section, the section type can be selected from a dropdown +menu. As you can see in `views.py` above, each section type comes with +its own list of editable fields. Client-side javascript will hide/show +the relevant fields based on the selected section type. All sections +are stored in the same database table. + +## Batteries included! + +SimpleCMS has been specifically crafted to fit my own personal needs +when building websites for customers. Therefore it includes a variety +of useful template tags, default *Page* and *Section* models, and all +the other boilerplate code needed for new projects. + +One notable inclusion is the `eval` template tag. It will pass its +argument first through Django's templating system and then through +Markdown, making for instance the following possible. (Disclaimer: use +with caution!) + + Welcome to **{% now 'Y' %}!** + +Another useful feature is the automatic compilation of `SCSS` files to +`CSS` files using a custom middleware. ## Installation Use the provided helper command `simplecms` to quickly setup a new project: - pip install https://github.com/rtts/django-simplecms.git - simplecms mysite + $ pip install https://github.com/rtts/django-simplecms.git + $ simplecms mysite + +After the project files have been created, initialize the database and +create a superuser: + + $ cd mysite + $ sudo su postgres -c "createuser mysite; createdb -O mysite mysite" + $ ./manage.py migrate + $ ./manage.py createsuperuser + +Finally, run the development server and visit +http://localhost:8000/login/ in your browser to log in! + + $ ./manage.py runserver diff --git a/bin/simplecms b/bin/simplecms deleted file mode 100755 index 159fb27..0000000 --- a/bin/simplecms +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -e - -test -z $1 && echo "Please provide a project name!" && exit 1 - -mkdir -p $1 && cd $1 -pip3 freeze > requirements.txt -example_dir=$(python3 -c 'import os,example;print(os.path.dirname(example.__file__))') -cp -r $example_dir/{project,app,manage.py} . -sed -i s/example/$1/ project/settings.py - -# Assume the user has sudo access to postgres -sudo su postgres -c "createuser $1; createdb -O $1 $1" || true - -cat << EOF > .gitignore -*.pyc -__pycache__/ -EOF - -./manage.py makemigrations app -./manage.py migrate -./manage.py createsuperuser -./manage.py runserver --nostatic diff --git a/cms/apps.py b/cms/apps.py index 6807913..a7614d9 100644 --- a/cms/apps.py +++ b/cms/apps.py @@ -7,4 +7,4 @@ class CmsConfig(AppConfig): verbose_name = _('Content Management System') def ready(self): - autodiscover_modules('cms') + autodiscover_modules('views') diff --git a/cms/bin/simplecms b/cms/bin/simplecms new file mode 100755 index 0000000..ed70e3b --- /dev/null +++ b/cms/bin/simplecms @@ -0,0 +1,26 @@ +#!/bin/bash -e + +test -z $1 && echo "Please provide a project name!" && exit 1 +mkdir "$1" +cd "$1" +pip3 freeze > requirements.txt +example_dir=$(python3 -c 'import os,example;print(os.path.dirname(example.__file__))') +cp -r "$example_dir" "$1" +cp "$example_dir"/../manage.py . +sed -i "s/example/$1/" manage.py +cat << EOF > .gitignore +*.pyc +__pycache__/ +EOF + +cat < {% trans 'Please correct the error(s) below and save again' %} + {{form.errors}} {% endif %} @@ -75,8 +76,7 @@ function resizeTextareas() { let tx = document.getElementsByTagName('textarea'); for (let i = 0; i < tx.length; i++) { - tx[i].setAttribute('style', 'height:0;overflow-y:hidden;'); - tx[i].style.height = (tx[i].scrollHeight) + 'px'; + tx[i].style.height = (tx[i].scrollHeight) + 1 + 'px'; tx[i].addEventListener('input', function() { this.style.height = (this.scrollHeight) + 1 + 'px'; // why the 1??? }); diff --git a/example/migrations/0001_initial.py b/example/migrations/0001_initial.py index 1afc47d..9428392 100644 --- a/example/migrations/0001_initial.py +++ b/example/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.2 on 2020-03-22 11:11 +# Generated by Django 3.0.2 on 2020-03-22 15:45 import cms.models from django.db import migrations, models @@ -31,16 +31,6 @@ class Migration(migrations.Migration): }, bases=(cms.models.Numbered, models.Model), ), - migrations.CreateModel( - name='SectionImage', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('image', models.ImageField(upload_to='', verbose_name='Image')), - ], - options={ - 'ordering': ['pk'], - }, - ), migrations.CreateModel( name='Section', fields=[ @@ -62,4 +52,15 @@ class Migration(migrations.Migration): }, bases=(cms.models.Numbered, models.Model), ), + migrations.CreateModel( + name='SectionImage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(upload_to='', verbose_name='Image')), + ('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='example.Section')), + ], + options={ + 'ordering': ['pk'], + }, + ), ] diff --git a/example/migrations/0002_initial_homepage.bak b/example/migrations/0002_initial_homepage.bak deleted file mode 100644 index 1263099..0000000 --- a/example/migrations/0002_initial_homepage.bak +++ /dev/null @@ -1,16 +0,0 @@ -import swapper -from django.db import migrations -Page = swapper.load_model('cms', 'Page') - -def add_homepage(apps, schema_editor): - if not Page.objects.exists(): - Page(slug='', title='Homepage', number=1).save() - -class Migration(migrations.Migration): - dependencies = [ - ('cms', '0001_initial'), - ] - - operations = [ - migrations.RunPython(add_homepage, migrations.RunPython.noop), - ] diff --git a/example/models.py b/example/models.py index d3f8edf..bee5108 100644 --- a/example/models.py +++ b/example/models.py @@ -19,7 +19,7 @@ class Section(BaseSection): page = models.ForeignKey(Page, verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT) class SectionImage(models.Model): - #section = models.ForeignKey(Section, related_name='images', on_delete=models.CASCADE) + section = models.ForeignKey(Section, related_name='images', on_delete=models.CASCADE) image = models.ImageField(_('Image')) class Meta: diff --git a/example/settings.py b/example/settings.py index 75ead46..50ceed2 100644 --- a/example/settings.py +++ b/example/settings.py @@ -26,17 +26,13 @@ except ImportError: DEBUG = True EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -def read(file): - with open(file) as f: - return f.read() -def write(file, content): - with open(file, 'w') as f: - f.write(content) try: - SECRET_KEY = read(KEYFILE) + with open(KEYFILE) as f: + SECRET_KEY = f.read() except IOError: SECRET_KEY = ''.join(random.choice(string.printable) for x in range(50)) - write(KEYFILE, SECRET_KEY) + with open(KEYFILE, 'w') as f: + f.write(SECRET_KEY) INSTALLED_APPS = [ PROJECT_NAME, @@ -45,12 +41,13 @@ INSTALLED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', - 'django.contrib.staticfiles', 'cms', 'embed_video', 'easy_thumbnails', 'django_extensions', ] +if not DEBUG: + INSTALLED_APPS += ['django.contrib.staticfiles'] MIDDLEWARE = [ 'cms.middleware.SassMiddleware', diff --git a/example/static/app/hamburgers.css b/example/static/hamburgers.css similarity index 100% rename from example/static/app/hamburgers.css rename to example/static/hamburgers.css diff --git a/example/static/app/main1.scss b/example/static/main1.scss similarity index 100% rename from example/static/app/main1.scss rename to example/static/main1.scss diff --git a/example/static/app/main1.scss.css b/example/static/main1.scss.css similarity index 100% rename from example/static/app/main1.scss.css rename to example/static/main1.scss.css diff --git a/example/static/app/main1.scss.css.map b/example/static/main1.scss.css.map similarity index 100% rename from example/static/app/main1.scss.css.map rename to example/static/main1.scss.css.map diff --git a/example/templates/base.html b/example/templates/base.html index db51408..35770c1 100644 --- a/example/templates/base.html +++ b/example/templates/base.html @@ -4,8 +4,8 @@ {% block title %}Awesome Website{% endblock %} {% block extrahead %} - - + + {% endblock %} {% block header %} diff --git a/example/templates/app/sections/contact.html b/example/templates/contact.html similarity index 100% rename from example/templates/app/sections/contact.html rename to example/templates/contact.html diff --git a/example/templates/app/sections/images.html b/example/templates/images.html similarity index 100% rename from example/templates/app/sections/images.html rename to example/templates/images.html diff --git a/example/templates/app/sections/text.html b/example/templates/text.html similarity index 100% rename from example/templates/app/sections/text.html rename to example/templates/text.html diff --git a/example/templates/app/sections/video.html b/example/templates/video.html similarity index 100% rename from example/templates/app/sections/video.html rename to example/templates/video.html diff --git a/example/cms.py b/example/views.py similarity index 59% rename from example/cms.py rename to example/views.py index c388929..e891d71 100644 --- a/example/cms.py +++ b/example/views.py @@ -1,30 +1,30 @@ +from django.utils.translation import gettext_lazy as _ from cms.views import SectionView, SectionFormView from cms.decorators import section_view from cms.forms import ContactForm -from django.utils.translation import gettext_lazy as _ @section_view class Text(SectionView): verbose_name = _('Text') - fields = ['title', 'content'] - template_name = 'app/sections/text.html' + fields = ['content'] + template_name = 'text.html' @section_view class Images(SectionView): - verbose_name = _('Images') - fields = ['title', 'images'] - template_name = 'app/sections/images.html' + verbose_name = _('Image(s)') + fields = ['images'] + template_name = 'images.html' @section_view class Video(SectionView): verbose_name = _('Video') - fields = ['title', 'video'] - template_name = 'app/sections/video.html' + fields = ['video'] + template_name = 'video.html' @section_view class Contact(SectionFormView): verbose_name = _('Contact') - fields = ['title'] + fields = [] form_class = ContactForm success_url = '/thanks/' - template_name = 'app/sections/contact.html' + template_name = 'contact.html' diff --git a/setup.py b/setup.py index edd1a48..751ba92 100755 --- a/setup.py +++ b/setup.py @@ -3,13 +3,13 @@ from setuptools import setup, find_packages setup( name = 'django-simplecms', - version = '3.0.0', + version = '1.0.0', url = 'https://github.com/rtts/django-simplecms', author = 'Jaap Joris Vens', author_email = 'jj@rtts.eu', license = 'GPL3', packages = find_packages(), - scripts = ['bin/simplecms'], + scripts = ['cms/bin/simplecms'], include_package_data = True, install_requires = [ 'django',