Refactor models into models, fields and mixins

main
Jaap Joris Vens 2021-06-30 00:48:50 +02:00
rodzic 5957b91f5b
commit cd8151b2ce
4 zmienionych plików z 188 dodań i 81 usunięć

Wyświetl plik

@ -1,2 +1,2 @@
__version__ = '1.0.4'
default_app_config = 'cms.apps.CmsConfig'
__version__ = "1.0.5"
default_app_config = "cms.apps.CmsConfig"

74
cms/fields.py 100644
Wyświetl plik

@ -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

74
cms/mixins.py 100644
Wyświetl plik

@ -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)

Wyświetl plik

@ -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"]