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'
|
||||
default_app_config = 'cms.apps.CmsConfig'
|
||||
__version__ = "1.0.5"
|
||||
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.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from django.forms import TextInput, Select
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from embed_video.fields import EmbedVideoField
|
||||
|
||||
class VarCharField(models.TextField):
|
||||
'''Variable width CharField'''
|
||||
def formfield(self, **kwargs):
|
||||
kwargs.update({'widget': TextInput})
|
||||
return super().formfield(**kwargs)
|
||||
from . import fields, mixins
|
||||
|
||||
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()
|
||||
class Model(models.Model):
|
||||
"""Felt cute, might delete later."""
|
||||
|
||||
def get_field_name(self):
|
||||
return self.__class__._meta.ordering[-1].lstrip('-')
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
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
|
||||
class BasePage(mixins.Numbered, Model):
|
||||
"""Abstract base model for pages."""
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
title = fields.CharField(_("page"))
|
||||
slug = fields.SlugField(_("slug"), blank=True, unique=True)
|
||||
number = fields.PositiveIntegerField(_("number"), blank=True)
|
||||
menu = fields.BooleanField(_("visible in menu"), default=True)
|
||||
|
||||
def __str__(self):
|
||||
if not self.pk:
|
||||
return str(_('New page'))
|
||||
return str(_("New page"))
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
if self.slug:
|
||||
return reverse('cms:page', args=[self.slug])
|
||||
return reverse('cms:page')
|
||||
return reverse("cms:page", args=[self.slug])
|
||||
return reverse("cms:page")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _('Page')
|
||||
verbose_name_plural = _('Pages')
|
||||
ordering = ['number']
|
||||
verbose_name = _("page")
|
||||
verbose_name_plural = _("pages")
|
||||
ordering = ["number"]
|
||||
|
||||
|
||||
class BaseSection(mixins.Numbered, Model):
|
||||
"""Abstract base model for sections"""
|
||||
|
||||
class BaseSection(Numbered, models.Model):
|
||||
'''Abstract base model for sections'''
|
||||
TYPES = []
|
||||
title = VarCharField(_('section'))
|
||||
type = VarCharField(_('type'))
|
||||
number = models.PositiveIntegerField(_('number'), 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'))
|
||||
href = VarCharField(_('link'), blank=True)
|
||||
title = fields.CharField(_("section"))
|
||||
type = fields.CharField(_("type"))
|
||||
number = fields.PositiveIntegerField(_("number"), blank=True)
|
||||
content = fields.TextField(_("content"), blank=True)
|
||||
image = fields.ImageField(_("image"), blank=True)
|
||||
video = EmbedVideoField(
|
||||
_("video"),
|
||||
blank=True,
|
||||
help_text=_("Paste a YouTube, Vimeo, or SoundCloud link"),
|
||||
)
|
||||
href = fields.CharField(_("link"), blank=True)
|
||||
|
||||
def number_with_respect_to(self):
|
||||
return self.page.sections.all()
|
||||
|
||||
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):
|
||||
if not self.pk:
|
||||
return str(_('New section'))
|
||||
return str(_("New section"))
|
||||
elif not self.title:
|
||||
return str(_('Untitled'))
|
||||
return str(_("Untitled"))
|
||||
else:
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _('section')
|
||||
verbose_name_plural = _('sections')
|
||||
ordering = ['number']
|
||||
verbose_name = _("section")
|
||||
verbose_name_plural = _("sections")
|
||||
ordering = ["number"]
|
||||
|
|
Ładowanie…
Reference in New Issue