kopia lustrzana https://github.com/rtts/django-simplecms
Refactor models into models, fields and mixins
rodzic
5957b91f5b
commit
cd8151b2ce
|
@ -1,2 +1,2 @@
|
||||||
__version__ = '1.0.4'
|
__version__ = "1.0.5"
|
||||||
default_app_config = 'cms.apps.CmsConfig'
|
default_app_config = "cms.apps.CmsConfig"
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.forms import TextInput
|
||||||
|
|
||||||
|
from .mixins import EasilyMigratable
|
||||||
|
|
||||||
|
|
||||||
|
class CharField(EasilyMigratable, models.TextField):
|
||||||
|
"""Variable width CharField."""
|
||||||
|
|
||||||
|
def formfield(self, **kwargs):
|
||||||
|
if not self.choices:
|
||||||
|
kwargs.update({"widget": TextInput})
|
||||||
|
return super().formfield(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TextField(EasilyMigratable, models.TextField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SlugField(EasilyMigratable, models.SlugField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EmailField(EasilyMigratable, models.EmailField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanField(EasilyMigratable, models.BooleanField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DateField(EasilyMigratable, models.DateField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimeField(EasilyMigratable, models.DateTimeField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PositiveIntegerField(EasilyMigratable, models.PositiveIntegerField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DecimalField(EasilyMigratable, models.DecimalField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JSONField(EasilyMigratable, models.JSONField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FileField(EasilyMigratable, models.FileField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImageField(EasilyMigratable, models.ImageField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ForeignKey(EasilyMigratable, models.ForeignKey):
|
||||||
|
def __init__(self, *args, related_name="+", **kwargs):
|
||||||
|
super().__init__(
|
||||||
|
*args,
|
||||||
|
related_name=related_name,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ManyToManyField(EasilyMigratable, models.ManyToManyField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OneToOneField(EasilyMigratable, models.OneToOneField):
|
||||||
|
pass
|
|
@ -0,0 +1,74 @@
|
||||||
|
class EasilyMigratable:
|
||||||
|
"""
|
||||||
|
Mixin for model fields. Prevents the generation of migrations that
|
||||||
|
don't affect the database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def deconstruct(self):
|
||||||
|
name, path, args, kwargs = super().deconstruct()
|
||||||
|
for field in [
|
||||||
|
"blank",
|
||||||
|
"choices",
|
||||||
|
"editable",
|
||||||
|
"help_text",
|
||||||
|
"limit_choices_to",
|
||||||
|
"related_name",
|
||||||
|
"storage",
|
||||||
|
"upload_to",
|
||||||
|
"validators",
|
||||||
|
"verbose_name",
|
||||||
|
]:
|
||||||
|
if field in kwargs:
|
||||||
|
del kwargs[field]
|
||||||
|
return name, path, args, kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class Numbered:
|
||||||
|
"""
|
||||||
|
Mixin for numbered models. Overrides the save() method to
|
||||||
|
automatically renumber all instances returned by
|
||||||
|
number_with_respect_to()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def number_with_respect_to(self):
|
||||||
|
return self.__class__.objects.all()
|
||||||
|
|
||||||
|
def get_field_name(self):
|
||||||
|
return self.__class__._meta.ordering[-1].lstrip("-")
|
||||||
|
|
||||||
|
def _renumber(self):
|
||||||
|
"""Renumbers the queryset while preserving the instance's number"""
|
||||||
|
|
||||||
|
queryset = self.number_with_respect_to()
|
||||||
|
field_name = self.get_field_name()
|
||||||
|
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(Numbered, 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):
|
||||||
|
setattr(self, self.get_field_name(), 9999) # hack
|
||||||
|
self._renumber()
|
||||||
|
super().delete(*args, **kwargs)
|
117
cms/models.py
117
cms/models.py
|
@ -1,117 +1,76 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.forms import TextInput, Select
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
|
|
||||||
from embed_video.fields import EmbedVideoField
|
from embed_video.fields import EmbedVideoField
|
||||||
|
|
||||||
class VarCharField(models.TextField):
|
from . import fields, mixins
|
||||||
'''Variable width CharField'''
|
|
||||||
def formfield(self, **kwargs):
|
|
||||||
kwargs.update({'widget': TextInput})
|
|
||||||
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 Model(models.Model):
|
||||||
def number_with_respect_to(self):
|
"""Felt cute, might delete later."""
|
||||||
return self.__class__.objects.all()
|
|
||||||
|
|
||||||
def get_field_name(self):
|
class Meta:
|
||||||
return self.__class__._meta.ordering[-1].lstrip('-')
|
abstract = True
|
||||||
|
|
||||||
def _renumber(self):
|
|
||||||
'''Renumbers the queryset while preserving the instance's number'''
|
|
||||||
|
|
||||||
queryset = self.number_with_respect_to()
|
class BasePage(mixins.Numbered, Model):
|
||||||
field_name = self.get_field_name()
|
"""Abstract base model for pages."""
|
||||||
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
|
title = fields.CharField(_("page"))
|
||||||
# number to the counter. When an object's number equals the
|
slug = fields.SlugField(_("slug"), blank=True, unique=True)
|
||||||
# number of this instance, set this instance's number to the
|
number = fields.PositiveIntegerField(_("number"), blank=True)
|
||||||
# counter, increment the counter by 1, and finish the loop
|
menu = fields.BooleanField(_("visible in menu"), default=True)
|
||||||
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(Numbered, 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):
|
|
||||||
setattr(self, self.get_field_name(), 9999) # hack
|
|
||||||
self._renumber()
|
|
||||||
super().delete(*args, **kwargs)
|
|
||||||
|
|
||||||
class BasePage(Numbered, models.Model):
|
|
||||||
'''Abstract base model for pages'''
|
|
||||||
title = VarCharField(_('page'))
|
|
||||||
slug = models.SlugField(_('slug'), blank=True, unique=True)
|
|
||||||
number = models.PositiveIntegerField(_('number'), blank=True)
|
|
||||||
menu = models.BooleanField(_('visible in menu'), default=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
return str(_('New page'))
|
return str(_("New page"))
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
if self.slug:
|
if self.slug:
|
||||||
return reverse('cms:page', args=[self.slug])
|
return reverse("cms:page", args=[self.slug])
|
||||||
return reverse('cms:page')
|
return reverse("cms:page")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name = _('Page')
|
verbose_name = _("page")
|
||||||
verbose_name_plural = _('Pages')
|
verbose_name_plural = _("pages")
|
||||||
ordering = ['number']
|
ordering = ["number"]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSection(mixins.Numbered, Model):
|
||||||
|
"""Abstract base model for sections"""
|
||||||
|
|
||||||
class BaseSection(Numbered, models.Model):
|
|
||||||
'''Abstract base model for sections'''
|
|
||||||
TYPES = []
|
TYPES = []
|
||||||
title = VarCharField(_('section'))
|
title = fields.CharField(_("section"))
|
||||||
type = VarCharField(_('type'))
|
type = fields.CharField(_("type"))
|
||||||
number = models.PositiveIntegerField(_('number'), blank=True)
|
number = fields.PositiveIntegerField(_("number"), blank=True)
|
||||||
content = models.TextField(_('content'), blank=True)
|
content = fields.TextField(_("content"), blank=True)
|
||||||
image = models.ImageField(_('image'), blank=True)
|
image = fields.ImageField(_("image"), blank=True)
|
||||||
video = EmbedVideoField(_('video'), blank=True, help_text=_('Paste a YouTube, Vimeo, or SoundCloud link'))
|
video = EmbedVideoField(
|
||||||
href = VarCharField(_('link'), blank=True)
|
_("video"),
|
||||||
|
blank=True,
|
||||||
|
help_text=_("Paste a YouTube, Vimeo, or SoundCloud link"),
|
||||||
|
)
|
||||||
|
href = fields.CharField(_("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()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.page.get_absolute_url() + '#' + slugify(self.title)
|
return self.page.get_absolute_url() + "#" + slugify(self.title)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
return str(_('New section'))
|
return str(_("New section"))
|
||||||
elif not self.title:
|
elif not self.title:
|
||||||
return str(_('Untitled'))
|
return str(_("Untitled"))
|
||||||
else:
|
else:
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name = _('section')
|
verbose_name = _("section")
|
||||||
verbose_name_plural = _('sections')
|
verbose_name_plural = _("sections")
|
||||||
ordering = ['number']
|
ordering = ["number"]
|
||||||
|
|
Ładowanie…
Reference in New Issue