Automatic detection of available section types

Use the decorator @register to register your own Section child models, and
use their fields attribute to specify which fields it uses.
readwriteweb
Jaap Joris Vens 2020-01-02 01:56:15 +01:00
rodzic 646311335b
commit a69d51a0dc
12 zmienionych plików z 163 dodań i 119 usunięć

Wyświetl plik

@ -2,10 +2,9 @@ from django.contrib import admin
from django.utils.text import Truncator from django.utils.text import Truncator
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import Page, Config from .models import Config
@admin.register(Page) class BasePageAdmin(admin.ModelAdmin):
class PageAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('title',)} prepopulated_fields = {'slug': ('title',)}
class BaseSectionAdmin(admin.ModelAdmin): class BaseSectionAdmin(admin.ModelAdmin):

Wyświetl plik

@ -1,8 +1,8 @@
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from .models import Page
import swapper import swapper
Page = swapper.load_model('cms', 'Page')
Section = swapper.load_model('cms', 'Section') Section = swapper.load_model('cms', 'Section')
class PageForm(forms.ModelForm): class PageForm(forms.ModelForm):
@ -11,6 +11,13 @@ class PageForm(forms.ModelForm):
fields = '__all__' fields = '__all__'
class SectionForm(forms.ModelForm): class SectionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Repopulate the 'choices' attribute of the type field from
# the child model.
self.fields['type'].choices = self._meta.model.TYPES
def save(self): def save(self):
section = super().save() section = super().save()
app_label = section._meta.app_label app_label = section._meta.app_label
@ -21,7 +28,7 @@ class SectionForm(forms.ModelForm):
# id to the 'polymorphic_ctype_id' field. This way, the next # id to the 'polymorphic_ctype_id' field. This way, the next
# time the object is requested from the database, # time the object is requested from the database,
# django-polymorphic will automatically convert it to the # django-polymorphic will automatically convert it to the
# correct subclass. Brilliant! # 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(),

Wyświetl plik

@ -1,6 +1,8 @@
# Generated by Django 3.0.1 on 2019-12-31 11:15 # Generated by Django 3.0.1 on 2020-01-02 00:14
import ckeditor.fields import ckeditor.fields
import cms.models
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import embed_video.fields import embed_video.fields
@ -15,6 +17,23 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Page',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', cms.models.VarCharField(verbose_name='title')),
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
],
options={
'verbose_name': 'Page',
'verbose_name_plural': 'Pages',
'ordering': ['position'],
'abstract': False,
'swappable': 'CMS_PAGE_MODEL',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Config', name='Config',
fields=[ fields=[
@ -28,38 +47,27 @@ class Migration(migrations.Migration):
'ordering': ['parameter'], 'ordering': ['parameter'],
}, },
), ),
migrations.CreateModel(
name='Page',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', models.CharField(max_length=255, verbose_name='title')),
('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
('menu', models.BooleanField(default=True, verbose_name='visible in menu')),
],
options={
'verbose_name': 'Page',
'verbose_name_plural': 'Pages',
'ordering': ['position'],
},
),
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'), ('stepsection', 'Stappenplan'), ('uploadsection', 'Upload')], default='', verbose_name='section type')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')), ('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', models.CharField(blank=True, max_length=255, verbose_name='title')), ('title', cms.models.VarCharField(blank=True, verbose_name='title')),
('type', models.CharField(choices=[('normal', 'Normaal')], default='normal', max_length=16, verbose_name='section type')),
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')), ('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')), ('content', ckeditor.fields.RichTextField(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')),
('button_text', models.CharField(blank=True, max_length=255, verbose_name='button text')), ('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),
('button_link', models.CharField(blank=True, max_length=255, verbose_name='button link')), ('button_link', cms.models.VarCharField(blank=True, verbose_name='button link')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to='cms.Page', verbose_name='page')), ('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_cms.section_set+', to='contenttypes.ContentType')), ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_cms.section_set+', to='contenttypes.ContentType')),
], ],
options={ options={
'verbose_name': 'section',
'verbose_name_plural': 'sections',
'ordering': ['position'],
'abstract': False,
'swappable': 'CMS_SECTION_MODEL', 'swappable': 'CMS_SECTION_MODEL',
}, },
), ),

Wyświetl plik

@ -1,12 +1,12 @@
import swapper
from django.db import migrations from django.db import migrations
Page = swapper.load_model('cms', 'Page')
def add_homepage(apps, schema_editor): def add_homepage(apps, schema_editor):
Page = apps.get_model('cms', 'Page')
if not Page.objects.exists(): if not Page.objects.exists():
Page(slug='', title='Homepage', position=1).save() Page(slug='', title='Homepage', position=1).save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('cms', '0001_initial'), ('cms', '0001_initial'),
] ]

Wyświetl plik

@ -2,7 +2,7 @@ import swapper
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.conf import settings from django.conf import settings
from django.forms import TextInput from django.forms import TextInput, Select
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ckeditor.fields import RichTextField from ckeditor.fields import RichTextField
from embed_video.fields import EmbedVideoField from embed_video.fields import EmbedVideoField
@ -10,14 +10,25 @@ from polymorphic.models import PolymorphicModel
from numberedmodel.models import NumberedModel from numberedmodel.models import NumberedModel
def register(verbose_name):
def wrapper(model):
model.__bases__[-1].TYPES.append((model.__name__.lower(), verbose_name))
return model
return wrapper
class VarCharField(models.TextField): class VarCharField(models.TextField):
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 Page(NumberedModel): class VarCharChoiceField(models.TextField):
def formfield(self, **kwargs):
kwargs.update({'widget': Select})
return super().formfield(**kwargs)
class BasePage(NumberedModel):
position = models.PositiveIntegerField(_('position'), blank=True) position = models.PositiveIntegerField(_('position'), blank=True)
title = models.CharField(_('title'), max_length=255) title = VarCharField(_('title'))
slug = models.SlugField(_('slug'), help_text=_('A short identifier to use in URLs'), blank=True, unique=True) slug = models.SlugField(_('slug'), help_text=_('A short identifier to use in URLs'), blank=True, unique=True)
menu = models.BooleanField(_('visible in menu'), default=True) menu = models.BooleanField(_('visible in menu'), default=True)
@ -29,29 +40,28 @@ class Page(NumberedModel):
def get_absolute_url(self): def get_absolute_url(self):
if self.slug: if self.slug:
return reverse(settings.PAGE_URL_PATTERN, args=[self.slug]) return reverse('cms:page', args=[self.slug])
else: else:
return reverse(settings.PAGE_URL_PATTERN) return reverse('cms:page')
class Meta: class Meta:
abstract = True
verbose_name = _('Page') verbose_name = _('Page')
verbose_name_plural = _('Pages') verbose_name_plural = _('Pages')
ordering = ['position'] ordering = ['position']
choices = settings.SECTION_TYPES
class BaseSection(NumberedModel, PolymorphicModel): class BaseSection(NumberedModel, PolymorphicModel):
page = models.ForeignKey(Page, verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT) TYPES = []
type = models.CharField(_('section type'), max_length=16, default=choices[0][0], choices=choices) page = models.ForeignKey(swapper.get_model_name('cms', 'Page'), verbose_name=_('page'), related_name='sections', on_delete=models.PROTECT)
type = VarCharChoiceField(_('section type'), default='', choices=TYPES)
position = models.PositiveIntegerField(_('position'), blank=True) position = models.PositiveIntegerField(_('position'), blank=True)
title = models.CharField(_('title'), max_length=255, blank=True) title = VarCharField(_('title'), blank=True)
color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS) color = models.PositiveIntegerField(_('color'), default=1, choices=settings.SECTION_COLORS)
content = RichTextField(_('content'), blank=True) content = RichTextField(_('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'))
button_text = models.CharField(_('button text'), max_length=255, blank=True) button_text = VarCharField(_('button text'), blank=True)
button_link = models.CharField(_('button link'), max_length=255, blank=True) button_link = VarCharField(_('button link'), blank=True)
def number_with_respect_to(self): def number_with_respect_to(self):
return self.page.sections.all() return self.page.sections.all()
@ -69,10 +79,13 @@ class BaseSection(NumberedModel, PolymorphicModel):
verbose_name = _('section') verbose_name = _('section')
verbose_name_plural = _('sections') verbose_name_plural = _('sections')
ordering = ['position'] ordering = ['position']
#app_label = 'cms'
class Page(BasePage):
class Meta(BasePage.Meta):
swappable = swapper.swappable_setting('cms', 'Page')
class Section(BaseSection): class Section(BaseSection):
class Meta: class Meta(BaseSection.Meta):
swappable = swapper.swappable_setting('cms', 'Section') swappable = swapper.swappable_setting('cms', 'Section')
class Config(models.Model): class Config(models.Model):

Wyświetl plik

@ -27,7 +27,7 @@
{% if pages %} {% if pages %}
<ul id="menu"> <ul id="menu">
{% for p in pages %} {% for p in pages %}
<li><a href="{% if p.slug %}{% url page_url_pattern p.slug %}{% else %}{% url page_url_pattern %}{% endif %}" {% if p.pk == page.pk %}class="current"{% endif %}>{{p.title}}</a></li> <li><a href="{% if p.slug %}{% url 'cms:page' p.slug %}{% else %}{% url 'cms:page' %}{% endif %}" {% if p.pk == page.pk %}class="current"{% endif %}>{{p.title}}</a></li>
{% endfor %} {% endfor %}
{% if user.is_staff %} {% if user.is_staff %}
<li><a class="edit" href="{% url 'cms:createpage' %}">+ {% trans 'new page' %}</a></li> <li><a class="edit" href="{% url 'cms:createpage' %}">+ {% trans 'new page' %}</a></li>

Wyświetl plik

@ -77,6 +77,7 @@
} }
} }
{% if fields_per_type %}
/* Only show relevant fields */ /* Only show relevant fields */
if (typefield) { if (typefield) {
@ -99,5 +100,6 @@
}); });
show_relevant_fields(typefield.value.toLowerCase()); show_relevant_fields(typefield.value.toLowerCase());
} }
{% endif %}
</script> </script>
{% endblock %} {% endblock %}

Wyświetl plik

@ -1,87 +1,38 @@
import json import json
from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.views import generic from django.views import generic
from django.shortcuts import redirect from django.shortcuts import redirect
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
from .models import Page
from .forms import PageForm, SectionForm from .forms import PageForm, SectionForm
from .utils import get_config from .utils import get_config
import swapper import swapper
Page = swapper.load_model('cms', 'Page')
Section = swapper.load_model('cms', 'Section') Section = swapper.load_model('cms', 'Section')
class StaffRequiredMixin(UserPassesTestMixin): class StaffRequiredMixin(UserPassesTestMixin):
def test_func(self): def test_func(self):
return self.request.user.is_staff return self.request.user.is_staff
class MenuMixin(object): class MenuMixin:
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
pages = Page.objects.filter(menu=True) pages = Page.objects.filter(menu=True)
footer = get_config(10) footer = get_config(10)
context.update({ context.update({
'page_url_pattern': settings.PAGE_URL_PATTERN,
'pages': pages, 'pages': pages,
'footer': footer, 'footer': footer,
}) })
return context return context
class MemoryMixin(object): class TypeMixin(MenuMixin):
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated:
request.session['previous_url'] = request.path
return super().dispatch(request, *args, **kwargs)
class BasePageView(MenuMixin, MemoryMixin, generic.DetailView):
model = Page
template_name = 'cms/page.html'
def setup(self, request, *args, slug='', **kwargs):
self.request = request
self.args = args
self.kwargs = kwargs
self.kwargs['slug'] = slug
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
page = self.object
sections = page.sections.all()
context.update({
'page': page,
'sections': sections,
})
return context
class PageView(BasePageView):
pass
class CreatePage(StaffRequiredMixin, MenuMixin, generic.CreateView):
model = Page
form_class = PageForm
template_name = 'cms/new.html'
class CreateSection(StaffRequiredMixin, MenuMixin, generic.CreateView):
model = Section
form_class = SectionForm
template_name = 'cms/new.html'
def form_valid(self, form):
form.instance.page = Page.objects.get(pk=self.kwargs.get('pk'))
form.save()
return redirect(self.request.session.get('previous_url'))
class BaseUpdateView(StaffRequiredMixin, MenuMixin, generic.UpdateView):
template_name = 'cms/edit.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
section_types = settings.SECTION_TYPES
fields_per_type = {} fields_per_type = {}
for model, desc in section_types: for model, desc 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(),
@ -93,14 +44,60 @@ class BaseUpdateView(StaffRequiredMixin, MenuMixin, generic.UpdateView):
}) })
return context return context
class MemoryMixin:
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated:
request.session['previous_url'] = request.path
return super().dispatch(request, *args, **kwargs)
class PageView(MenuMixin, MemoryMixin, generic.DetailView):
model = Page
template_name = 'cms/page.html'
# Supplies a default argument for slug
def setup(self, *args, slug='', **kwargs):
super().setup(*args, slug=slug, **kwargs)
#self.request = request
#self.args = args
#self.kwargs = kwargs
#self.kwargs['slug'] = slug
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page = self.object
sections = page.sections.all()
context.update({
'page': page,
'sections': sections,
})
return context
class BaseUpdateView(generic.UpdateView):
template_name = 'cms/edit.html'
def form_valid(self, form): def form_valid(self, form):
form.save() form.save()
return redirect(self.request.session.get('previous_url')) return redirect(self.request.session.get('previous_url'))
class UpdatePage(BaseUpdateView): class UpdatePage(StaffRequiredMixin, MenuMixin, BaseUpdateView):
model = Page model = Page
form_class = PageForm form_class = PageForm
class UpdateSection(BaseUpdateView): class UpdateSection(StaffRequiredMixin, TypeMixin, BaseUpdateView):
model = Section model = Section
form_class = SectionForm form_class = SectionForm
class CreatePage(StaffRequiredMixin, MenuMixin, generic.CreateView):
model = Page
form_class = PageForm
template_name = 'cms/new.html'
class CreateSection(StaffRequiredMixin, TypeMixin, generic.CreateView):
model = Section
form_class = SectionForm
template_name = 'cms/new.html'
def form_valid(self, form):
form.instance.page = Page.objects.get(pk=self.kwargs.get('pk'))
form.save()
return redirect(self.request.session.get('previous_url'))

Wyświetl plik

@ -1,7 +0,0 @@
from django.contrib import admin
from cms.admin import BaseSectionAdmin
from .models import Section
@admin.register(Section)
class SectionAdmin(BaseSectionAdmin):
pass

Wyświetl plik

@ -1,6 +1,8 @@
# Generated by Django 3.0.1 on 2019-12-31 11:16 # Generated by Django 3.0.1 on 2020-01-02 00:06
import ckeditor.fields import ckeditor.fields
import cms.models
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import embed_video.fields import embed_video.fields
@ -11,25 +13,41 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.CMS_PAGE_MODEL),
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
('cms', '0001_initial'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Section', 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')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')), ('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', models.CharField(blank=True, max_length=255, verbose_name='title')), ('title', cms.models.VarCharField(verbose_name='title')),
('type', models.CharField(choices=[('normal', 'Normaal')], default='normal', max_length=16, verbose_name='section type')), ('slug', models.SlugField(blank=True, help_text='A short identifier to use in URLs', unique=True, verbose_name='slug')),
('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'), ('imagesection', 'Afbeelding')], default='', verbose_name='section type')),
('position', models.PositiveIntegerField(blank=True, verbose_name='position')),
('title', cms.models.VarCharField(blank=True, verbose_name='title')),
('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')), ('color', models.PositiveIntegerField(choices=[(1, 'Wit')], default=1, verbose_name='color')),
('content', ckeditor.fields.RichTextField(blank=True, verbose_name='content')), ('content', ckeditor.fields.RichTextField(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')),
('button_text', models.CharField(blank=True, max_length=255, verbose_name='button text')), ('button_text', cms.models.VarCharField(blank=True, verbose_name='button text')),
('button_link', models.CharField(blank=True, max_length=255, verbose_name='button link')), ('button_link', cms.models.VarCharField(blank=True, verbose_name='button link')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sections', to='cms.Page', verbose_name='page')), ('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')), ('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={ options={

Wyświetl plik

@ -1,16 +1,24 @@
from cms.models import BaseSection from cms.models import *
class Section(BaseSection): class Page(BasePage):
'''Add custom fields here. Already existing fields: title, color, '''Add custom fields here. Already existing fields: position, title,
content, image, video, button_text, button_link slug, menu
''' '''
class Section(BaseSection):
'''Add custom fields here. Already existing fields: type, position,
title, color, content, image, video, button_text, button_link
'''
@register('Tekst')
class TextSection(Section): class TextSection(Section):
fields = ['type', 'position', 'title', 'content'] fields = ['type', 'position', 'title', 'content']
class Meta: class Meta:
proxy = True proxy = True
@register('Afbeelding')
class ImageSection(Section): class ImageSection(Section):
fields = ['type', 'position', 'title', 'image'] fields = ['type', 'position', 'title', 'image']
class Meta: class Meta:

Wyświetl plik

@ -21,7 +21,6 @@ STATIC_ROOT = '/srv/' + PROJECT_NAME + '/static'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media' MEDIA_ROOT = '/srv/' + PROJECT_NAME + '/media'
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
PAGE_URL_PATTERN = 'cms:page'
CMS_SECTION_MODEL = 'app.Section' CMS_SECTION_MODEL = 'app.Section'
def read(file): def read(file):