Cleanup source tree

The separate apps numberedmodel and simplesass have been merged into cms
readwriteweb
Jaap Joris Vens 2020-01-05 13:37:51 +01:00
rodzic 5f5f303187
commit 2824d290f8
41 zmienionych plików z 110 dodań i 252 usunięć

Wyświetl plik

@ -5,8 +5,8 @@ test -d $dev/$1 && echo "That project already exists!" && exit 1
mkdir $1 && cd $1 mkdir $1 && cd $1
pip3 freeze > requirements.txt pip3 freeze > requirements.txt
examples_dir=$(python3 -c 'import os,examples;print(os.path.dirname(examples.__file__))') example_dir=$(python3 -c 'import os,examples;print(os.path.dirname(example.__file__))')
cp -r $examples_dir/{project,app,manage.py} . cp -r $example_dir/{project,app,manage.py} .
sed -i s/example/$1/ project/settings.py sed -i s/example/$1/ project/settings.py
# Assume the user has sudo access to postgres # Assume the user has sudo access to postgres
@ -17,6 +17,7 @@ cat << EOF > .gitignore
__pycache__/ __pycache__/
EOF EOF
./manage.py makemigrations app
./manage.py migrate ./manage.py migrate
./manage.py createsuperuser ./manage.py createsuperuser
./manage.py runserver --nostatic ./manage.py runserver --nostatic

Wyświetl plik

@ -7,6 +7,6 @@ class CmsConfig(AppConfig):
verbose_name = _('Content Management System') verbose_name = _('Content Management System')
def ready(self): def ready(self):
# Need to load view models at startup to make the # Need to load view models of all installed apps to make the
# register_view decorator work # register_view decorator work
autodiscover_modules('views') autodiscover_modules('views')

Wyświetl plik

@ -1,16 +1,13 @@
def register_model(verbose_name): def register_model(verbose_name):
'''Decorator to register a section subclass. '''Decorator to register a section subclass'''
'''
def wrapper(model): def wrapper(model):
model.__bases__[-1].TYPES.append((model.__name__.lower(), verbose_name)) parent_model = model.__bases__[-1]
parent_model.TYPES.append((model.__name__.lower(), verbose_name))
return model return model
return wrapper return wrapper
def register_view(section_class): def register_view(section_class):
'''Decorator to connect a section model to a view class. '''Decorator to connect a section model to a view class'''
'''
def wrapper(model): def wrapper(model):
section_class.view_class = model section_class.view_class = model
return model return model

Wyświetl plik

@ -1,8 +1,7 @@
import swapper
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
import swapper
Page = swapper.load_model('cms', 'Page') Page = swapper.load_model('cms', 'Page')
Section = swapper.load_model('cms', 'Section') Section = swapper.load_model('cms', 'Section')
@ -21,15 +20,12 @@ class SectionForm(forms.ModelForm):
def save(self): def save(self):
section = super().save() section = super().save()
app_label = section._meta.app_label
model = section.type
# Explanation: we'll get the content type of the model that # Explanation: get the content type of the model that the user
# the user supplied when filling in this form, and save it's # supplied when filling in this form, and save it's id to the
# id to the 'polymorphic_ctype_id' field. This way, the next # 'polymorphic_ctype_id' field. The next time the object is
# time the object is requested from the database, # requested from the database, django-polymorphic will convert
# django-polymorphic will automatically convert it to the # it to the correct subclass.
# correct subclass.
section.polymorphic_ctype = ContentType.objects.get( section.polymorphic_ctype = ContentType.objects.get(
app_label=section._meta.app_label, app_label=section._meta.app_label,
model=section.type.lower(), model=section.type.lower(),
@ -41,3 +37,10 @@ class SectionForm(forms.ModelForm):
class Meta: class Meta:
model = Section model = Section
exclude = ['page'] exclude = ['page']
#field_classes = {
# 'type': forms.ChoiceField,
#}
# There is definitely a bug in Django, since the above 'field_classes' gets
# ignored entirely. Workaround to force a ChoiceField anyway:
type = forms.ChoiceField()

Wyświetl plik

@ -3,7 +3,7 @@ from django.conf import settings
from django.apps import apps from django.apps import apps
from sass import compile from sass import compile
class SimpleSassMiddleware: class SassMiddleware:
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response

Wyświetl plik

@ -1,4 +1,4 @@
# Generated by Django 3.0.2 on 2020-01-04 22:55 # Generated by Django 3.0.2 on 2020-01-05 11:43
import cms.models import cms.models
from django.conf import settings from django.conf import settings
@ -20,26 +20,27 @@ class Migration(migrations.Migration):
name='Page', name='Page',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')), ('number', models.PositiveIntegerField(blank=True, verbose_name='number')),
('slug', models.SlugField(blank=True, unique=True, verbose_name='slug')),
('title', cms.models.VarCharField(verbose_name='title')), ('title', cms.models.VarCharField(verbose_name='title')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('menu', models.BooleanField(default=True, verbose_name='visible in menu')), ('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
], ],
options={ options={
'verbose_name': 'Page', 'verbose_name': 'Page',
'verbose_name_plural': 'Pages', 'verbose_name_plural': 'Pages',
'ordering': ['position'], 'ordering': ['number'],
'abstract': False, 'abstract': False,
'swappable': 'CMS_PAGE_MODEL', 'swappable': 'CMS_PAGE_MODEL',
}, },
bases=(cms.models.Numbered, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Section', name='Section',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', cms.models.VarCharChoiceField(choices=[('textsection', 'Tekst'), ('imagesection', 'Afbeelding'), ('contactsection', 'Contact')], default='', verbose_name='type')), ('type', cms.models.VarCharField(blank=True, verbose_name='type')),
('number', models.PositiveIntegerField(blank=True, verbose_name='number')),
('title', cms.models.VarCharField(blank=True, verbose_name='title')), ('title', cms.models.VarCharField(blank=True, verbose_name='title')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('content', models.TextField(blank=True, verbose_name='content')), ('content', models.TextField(blank=True, verbose_name='content')),
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')), ('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
('video', embed_video.fields.EmbedVideoField(blank=True, help_text='Paste a YouTube, Vimeo, or SoundCloud link', verbose_name='video')), ('video', embed_video.fields.EmbedVideoField(blank=True, help_text='Paste a YouTube, Vimeo, or SoundCloud link', verbose_name='video')),
@ -51,9 +52,10 @@ class Migration(migrations.Migration):
options={ options={
'verbose_name': 'section', 'verbose_name': 'section',
'verbose_name_plural': 'sections', 'verbose_name_plural': 'sections',
'ordering': ['position'], 'ordering': ['number'],
'abstract': False, 'abstract': False,
'swappable': 'CMS_SECTION_MODEL', 'swappable': 'CMS_SECTION_MODEL',
}, },
bases=(cms.models.Numbered, models.Model),
), ),
] ]

Wyświetl plik

@ -4,7 +4,7 @@ Page = swapper.load_model('cms', 'Page')
def add_homepage(apps, schema_editor): def add_homepage(apps, schema_editor):
if not Page.objects.exists(): if not Page.objects.exists():
Page(slug='', title='Homepage', position=1).save() Page(slug='', title='Homepage', number=1).save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [

Wyświetl plik

@ -1,5 +1,3 @@
'''CMS Models'''
import swapper import swapper
from django.db import models from django.db import models
@ -9,25 +7,62 @@ from django.utils.translation import gettext_lazy as _
from embed_video.fields import EmbedVideoField from embed_video.fields import EmbedVideoField
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from numberedmodel.models import NumberedModel
class VarCharField(models.TextField): class VarCharField(models.TextField):
'''Variable width CharField''' '''Variable width CharField'''
def formfield(self, **kwargs): def formfield(self, **kwargs):
kwargs.update({'widget': TextInput}) kwargs.update({'widget': TextInput})
return super().formfield(**kwargs) return super().formfield(**kwargs)
class VarCharChoiceField(models.TextField): class Numbered:
'''Variable width CharField with choices''' '''Mixin for numbered models. Overrides the save() method to
def formfield(self, **kwargs): automatically renumber all instances returned by
kwargs.update({'widget': Select}) number_with_respect_to()
return super().formfield(**kwargs)
class BasePage(NumberedModel): '''
def number_with_respect_to(self):
return self.__class__.objects.all()
def _renumber(self):
'''Renumbers the queryset while preserving the instance's number'''
queryset = self.number_with_respect_to()
field_name = self.__class__._meta.ordering[-1].lstrip('-')
this_nr = getattr(self, field_name)
if this_nr is None:
this_nr = len(queryset) + 1
# The algorithm: loop over the queryset and set each object's
# number to the counter. When an object's number equals the
# number of this instance, set this instance's number to the
# counter, increment the counter by 1, and finish the loop
counter = 1
inserted = False
for other in queryset.exclude(pk=self.pk):
other_nr = getattr(other, field_name)
if counter >= this_nr and not inserted:
setattr(self, field_name, counter)
inserted = True
counter += 1
if other_nr != counter:
setattr(other, field_name, counter)
super(NumberedModel, other).save()
counter += 1
if not inserted:
setattr(self, field_name, counter)
def save(self, *args, **kwargs):
self._renumber()
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
self._renumber()
class BasePage(Numbered, models.Model):
'''Abstract base model for pages''' '''Abstract base model for pages'''
slug = models.SlugField(_('slug'), help_text=_('A short identifier to use in URLs'), blank=True, unique=True) number = models.PositiveIntegerField(_('number'), blank=True)
slug = models.SlugField(_('slug'), blank=True, unique=True)
title = VarCharField(_('title')) title = VarCharField(_('title'))
position = models.PositiveIntegerField(_('position'), blank=True)
menu = models.BooleanField(_('visible in menu'), default=True) menu = models.BooleanField(_('visible in menu'), default=True)
def __str__(self): def __str__(self):
@ -44,15 +79,16 @@ class BasePage(NumberedModel):
abstract = True abstract = True
verbose_name = _('Page') verbose_name = _('Page')
verbose_name_plural = _('Pages') verbose_name_plural = _('Pages')
ordering = ['position'] ordering = ['number']
class BaseSection(NumberedModel, PolymorphicModel): class BaseSection(Numbered, PolymorphicModel):
'''Abstract base model for sections''' '''Abstract base model for sections'''
TYPES = [] TYPES = [] # Will be populated by @register_model()
page = models.ForeignKey(swapper.get_model_name('cms', 'Page'), verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT) page = models.ForeignKey(swapper.get_model_name('cms', 'Page'), verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT)
type = VarCharChoiceField(_('type'), default='', choices=TYPES) type = VarCharField(_('type'), blank=True)
number = models.PositiveIntegerField(_('number'), blank=True)
title = VarCharField(_('title'), blank=True) title = VarCharField(_('title'), blank=True)
position = models.PositiveIntegerField(_('position'), blank=True)
content = models.TextField(_('content'), blank=True) content = models.TextField(_('content'), blank=True)
image = models.ImageField(_('image'), blank=True) image = models.ImageField(_('image'), blank=True)
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link')) video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
@ -74,7 +110,7 @@ class BaseSection(NumberedModel, PolymorphicModel):
abstract = True abstract = True
verbose_name = _('section') verbose_name = _('section')
verbose_name_plural = _('sections') verbose_name_plural = _('sections')
ordering = ['position'] ordering = ['number']
class Page(BasePage): class Page(BasePage):
'''Swappable page model''' '''Swappable page model'''

Wyświetl plik

@ -289,22 +289,14 @@ form.cms {
clear: both; clear: both;
box-sizing: border-box; box-sizing: border-box;
&#type, &#position, &#title, &#slug { &#type, &#number {
padding: 0 1em; width: 75%;
clear: none; clear: none;
float: left; float: left;
display: inline-block;
width: 33%;
} }
&#type, &#slug { &#number {
padding-left: 0; width: 20%;
}
&#title {
padding: 0;
}
&#position {
float: right; float: right;
padding-right: 0;
} }
} }
div.formfield.error { div.formfield.error {

Wyświetl plik

@ -206,19 +206,13 @@ form.cms div.formfield {
padding: 10px 0; padding: 10px 0;
clear: both; clear: both;
box-sizing: border-box; } box-sizing: border-box; }
form.cms div.formfield#type, form.cms div.formfield#position, form.cms div.formfield#title, form.cms div.formfield#slug { form.cms div.formfield#type, form.cms div.formfield#number {
padding: 0 1em; width: 75%;
clear: none; clear: none;
float: left; float: left; }
display: inline-block; form.cms div.formfield#number {
width: 33%; } width: 20%;
form.cms div.formfield#type, form.cms div.formfield#slug { float: right; }
padding-left: 0; }
form.cms div.formfield#title {
padding: 0; }
form.cms div.formfield#position {
float: right;
padding-right: 0; }
form.cms div.formfield.error { form.cms div.formfield.error {
border: 2px dotted red; border: 2px dotted red;

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -93,6 +93,7 @@
for (field of fields_per_type[type]) { for (field of fields_per_type[type]) {
el = document.getElementById(field); el = document.getElementById(field);
console.log('showing ' + field)
el.style.display = 'block'; el.style.display = 'block';
} }
} }

Wyświetl plik

@ -1,11 +1,9 @@
'''CMS Views'''
import json import json
import swapper import swapper
from django.views import generic from django.views import generic
from django.views.generic.edit import FormMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.views.generic.edit import FormMixin
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.auth.mixins import UserPassesTestMixin
@ -126,10 +124,10 @@ class TypeMixin(MenuMixin):
fields_per_type = {} fields_per_type = {}
for model, _ in Section.TYPES: for model, _ in Section.TYPES:
ctype = ContentType.objects.get( ctype = ContentType.objects.get(
app_label=Section._meta.app_label, app_label = Section._meta.app_label,
model=model.lower(), model = model.lower(),
) )
fields_per_type[ctype.model] = ctype.model_class().fields fields_per_type[ctype.model] = ['type', 'number'] + ctype.model_class().fields
context.update({ context.update({
'fields_per_type': json.dumps(fields_per_type), 'fields_per_type': json.dumps(fields_per_type),

Wyświetl plik

@ -18,30 +18,30 @@ class Section(BaseSection):
@register_model('Tekst') @register_model('Tekst')
class TextSection(Section): class TextSection(Section):
fields = ['type', 'position', 'title', 'color', 'content'] fields = ['title', 'content']
class Meta: class Meta:
proxy = True proxy = True
@register_model('Button') @register_model('Button')
class ButtonSection(Section): class ButtonSection(Section):
fields = ['type', 'position', 'title', 'button_text', 'button_link'] fields = ['button_text', 'button_link']
class Meta: class Meta:
proxy = True proxy = True
@register_model('Afbeelding') @register_model('Afbeelding')
class ImageSection(Section): class ImageSection(Section):
fields = ['type', 'position', 'title', 'image'] fields = ['title', 'image']
class Meta: class Meta:
proxy = True proxy = True
@register_model('Video') @register_model('Video')
class VideoSection(Section): class VideoSection(Section):
fields = ['type', 'position', 'title', 'video'] fields = ['title', 'video']
class Meta: class Meta:
proxy = True proxy = True
@register_model('Contact') @register_model('Contact')
class ContactSection(Section): class ContactSection(Section):
fields = ['type', 'position', 'title'] fields = ['title']
class Meta: class Meta:
proxy = True proxy = True

Wyświetl plik

@ -38,7 +38,8 @@ except IOError:
write(KEYFILE, SECRET_KEY) write(KEYFILE, SECRET_KEY)
SECTION_COLORS = [ SECTION_COLORS = [
(1, 'Wit'), (1, 'Licht'),
(2, 'Donker'),
] ]
INSTALLED_APPS = [ INSTALLED_APPS = [
@ -50,7 +51,6 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'cms', 'cms',
'simplesass',
'polymorphic', 'polymorphic',
'embed_video', 'embed_video',
'easy_thumbnails', 'easy_thumbnails',
@ -58,7 +58,7 @@ INSTALLED_APPS = [
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'simplesass.middleware.SimpleSassMiddleware', 'cms.middleware.SassMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',

Wyświetl plik

@ -1,114 +0,0 @@
# Generated by Django 3.0.2 on 2020-01-05 02:29
import cms.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import embed_video.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.CMS_PAGE_MODEL),
]
operations = [
migrations.CreateModel(
name='Page',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
('title', cms.models.VarCharField(verbose_name='title')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
],
options={
'verbose_name': 'Page',
'verbose_name_plural': 'Pages',
'ordering': ['position'],
'abstract': False,
},
),
migrations.CreateModel(
name='Section',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', cms.models.VarCharChoiceField(choices=[('textsection', 'Tekst'), ('buttonsection', 'Button'), ('imagesection', 'Afbeelding'), ('videosection', 'Video'), ('contactsection', 'Contact')], default='', verbose_name='type')),
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('content', models.TextField(blank=True, verbose_name='content')),
('image', models.ImageField(blank=True, upload_to='', verbose_name='image')),
('video', embed_video.fields.EmbedVideoField(blank=True, help_text='Paste a YouTube, Vimeo, or SoundCloud link', verbose_name='video')),
('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),
('button_link', cms.models.VarCharField(blank=True, verbose_name='button link')),
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='kleur')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to=settings.CMS_PAGE_MODEL, verbose_name='page')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_app.section_set+', to='contenttypes.ContentType')),
],
options={
'verbose_name': 'section',
'verbose_name_plural': 'sections',
'ordering': ['position'],
'abstract': False,
},
),
migrations.CreateModel(
name='ButtonSection',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('app.section',),
),
migrations.CreateModel(
name='ContactSection',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('app.section',),
),
migrations.CreateModel(
name='ImageSection',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('app.section',),
),
migrations.CreateModel(
name='TextSection',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('app.section',),
),
migrations.CreateModel(
name='VideoSection',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('app.section',),
),
]

Wyświetl plik

@ -1 +0,0 @@
default_app_config = 'numberedmodel.apps.NumberedModelConfig'

Wyświetl plik

@ -1,4 +0,0 @@
from django.apps import AppConfig
class NumberedModelConfig(AppConfig):
name = 'numberedmodel'

Wyświetl plik

@ -1,44 +0,0 @@
from django.db import models
class NumberedModel(models.Model):
def number_with_respect_to(self):
return self.__class__.objects.all()
def _renumber(self):
'''Renumbers the queryset while preserving the instance's number'''
queryset = self.number_with_respect_to()
field_name = self.__class__._meta.ordering[-1].lstrip('-')
this_nr = getattr(self, field_name)
if this_nr is None:
this_nr = len(queryset) + 1
# The algorithm: loop over the queryset and set each object's
# number to the counter. When an object's number equals the
# number of this instance, set this instance's number to the
# counter, increment the counter by 1, and finish the loop
counter = 1
inserted = False
for other in queryset.exclude(pk=self.pk):
other_nr = getattr(other, field_name)
if counter >= this_nr and not inserted:
setattr(self, field_name, counter)
inserted = True
counter += 1
if other_nr != counter:
setattr(other, field_name, counter)
super(NumberedModel, other).save()
counter += 1
if not inserted:
setattr(self, field_name, counter)
def save(self, *args, **kwargs):
self._renumber()
super(NumberedModel, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
super(NumberedModel, self).delete(*args, **kwargs)
self._renumber()
class Meta:
abstract = True

Wyświetl plik

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.