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
pip3 freeze > requirements.txt
examples_dir=$(python3 -c 'import os,examples;print(os.path.dirname(examples.__file__))')
cp -r $examples_dir/{project,app,manage.py} .
example_dir=$(python3 -c 'import os,examples;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
@ -17,6 +17,7 @@ cat << EOF > .gitignore
__pycache__/
EOF
./manage.py makemigrations app
./manage.py migrate
./manage.py createsuperuser
./manage.py runserver --nostatic

Wyświetl plik

@ -7,6 +7,6 @@ class CmsConfig(AppConfig):
verbose_name = _('Content Management System')
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
autodiscover_modules('views')

Wyświetl plik

@ -1,16 +1,13 @@
def register_model(verbose_name):
'''Decorator to register a section subclass.
'''
'''Decorator to register a section subclass'''
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 wrapper
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):
section_class.view_class = model
return model

Wyświetl plik

@ -1,8 +1,7 @@
import swapper
from django import forms
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
import swapper
Page = swapper.load_model('cms', 'Page')
Section = swapper.load_model('cms', 'Section')
@ -21,15 +20,12 @@ class SectionForm(forms.ModelForm):
def save(self):
section = super().save()
app_label = section._meta.app_label
model = section.type
# Explanation: we'll get the content type of the model that
# the user supplied when filling in this form, and save it's
# id to the 'polymorphic_ctype_id' field. This way, the next
# time the object is requested from the database,
# django-polymorphic will automatically convert it to the
# correct subclass.
# Explanation: get the content type of the model that the user
# supplied when filling in this form, and save it's id to the
# 'polymorphic_ctype_id' field. The next time the object is
# requested from the database, django-polymorphic will convert
# it to the correct subclass.
section.polymorphic_ctype = ContentType.objects.get(
app_label=section._meta.app_label,
model=section.type.lower(),
@ -41,3 +37,10 @@ class SectionForm(forms.ModelForm):
class Meta:
model = Section
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 sass import compile
class SimpleSassMiddleware:
class SassMiddleware:
def __init__(self, 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
from django.conf import settings
@ -20,26 +20,27 @@ class Migration(migrations.Migration):
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')),
('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')),
('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'],
'ordering': ['number'],
'abstract': False,
'swappable': 'CMS_PAGE_MODEL',
},
bases=(cms.models.Numbered, models.Model),
),
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'), ('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')),
('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')),
@ -51,9 +52,10 @@ class Migration(migrations.Migration):
options={
'verbose_name': 'section',
'verbose_name_plural': 'sections',
'ordering': ['position'],
'ordering': ['number'],
'abstract': False,
'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):
if not Page.objects.exists():
Page(slug='', title='Homepage', position=1).save()
Page(slug='', title='Homepage', number=1).save()
class Migration(migrations.Migration):
dependencies = [

Wyświetl plik

@ -1,5 +1,3 @@
'''CMS Models'''
import swapper
from django.db import models
@ -9,25 +7,62 @@ from django.utils.translation import gettext_lazy as _
from embed_video.fields import EmbedVideoField
from polymorphic.models import PolymorphicModel
from numberedmodel.models import NumberedModel
class VarCharField(models.TextField):
'''Variable width CharField'''
def formfield(self, **kwargs):
kwargs.update({'widget': TextInput})
return super().formfield(**kwargs)
class VarCharChoiceField(models.TextField):
'''Variable width CharField with choices'''
def formfield(self, **kwargs):
kwargs.update({'widget': Select})
return super().formfield(**kwargs)
class Numbered:
'''Mixin for numbered models. Overrides the save() method to
automatically renumber all instances returned by
number_with_respect_to()
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'''
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'))
position = models.PositiveIntegerField(_('position'), blank=True)
menu = models.BooleanField(_('visible in menu'), default=True)
def __str__(self):
@ -44,15 +79,16 @@ class BasePage(NumberedModel):
abstract = True
verbose_name = _('Page')
verbose_name_plural = _('Pages')
ordering = ['position']
ordering = ['number']
class BaseSection(NumberedModel, PolymorphicModel):
class BaseSection(Numbered, PolymorphicModel):
'''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)
type = VarCharChoiceField(_('type'), default='', choices=TYPES)
type = VarCharField(_('type'), blank=True)
number = models.PositiveIntegerField(_('number'), blank=True)
title = VarCharField(_('title'), blank=True)
position = models.PositiveIntegerField(_('position'), blank=True)
content = models.TextField(_('content'), blank=True)
image = models.ImageField(_('image'), blank=True)
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
@ -74,7 +110,7 @@ class BaseSection(NumberedModel, PolymorphicModel):
abstract = True
verbose_name = _('section')
verbose_name_plural = _('sections')
ordering = ['position']
ordering = ['number']
class Page(BasePage):
'''Swappable page model'''

Wyświetl plik

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

Wyświetl plik

@ -206,19 +206,13 @@ form.cms div.formfield {
padding: 10px 0;
clear: both;
box-sizing: border-box; }
form.cms div.formfield#type, form.cms div.formfield#position, form.cms div.formfield#title, form.cms div.formfield#slug {
padding: 0 1em;
form.cms div.formfield#type, form.cms div.formfield#number {
width: 75%;
clear: none;
float: left;
display: inline-block;
width: 33%; }
form.cms div.formfield#type, form.cms div.formfield#slug {
padding-left: 0; }
form.cms div.formfield#title {
padding: 0; }
form.cms div.formfield#position {
float: right;
padding-right: 0; }
float: left; }
form.cms div.formfield#number {
width: 20%;
float: right; }
form.cms div.formfield.error {
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]) {
el = document.getElementById(field);
console.log('showing ' + field)
el.style.display = 'block';
}
}

Wyświetl plik

@ -1,11 +1,9 @@
'''CMS Views'''
import json
import swapper
from django.views import generic
from django.views.generic.edit import FormMixin
from django.shortcuts import redirect
from django.views.generic.edit import FormMixin
from django.core.exceptions import ImproperlyConfigured
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.mixins import UserPassesTestMixin
@ -126,10 +124,10 @@ class TypeMixin(MenuMixin):
fields_per_type = {}
for model, _ in Section.TYPES:
ctype = ContentType.objects.get(
app_label=Section._meta.app_label,
model=model.lower(),
app_label = Section._meta.app_label,
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({
'fields_per_type': json.dumps(fields_per_type),

Wyświetl plik

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

Wyświetl plik

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