kopia lustrzana https://github.com/rtts/django-simplecms
Cleanup source tree
The separate apps numberedmodel and simplesass have been merged into cmsreadwriteweb
rodzic
5f5f303187
commit
2824d290f8
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
23
cms/forms.py
23
cms/forms.py
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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'''
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
@ -93,6 +93,7 @@
|
|||
|
||||
for (field of fields_per_type[type]) {
|
||||
el = document.getElementById(field);
|
||||
console.log('showing ' + field)
|
||||
el.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
|
10
cms/views.py
10
cms/views.py
|
@ -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),
|
||||
|
|
|
@ -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
|
|
@ -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',
|
|
@ -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',),
|
||||
),
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'numberedmodel.apps.NumberedModelConfig'
|
|
@ -1,4 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
class NumberedModelConfig(AppConfig):
|
||||
name = 'numberedmodel'
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
Ładowanie…
Reference in New Issue