This commit is contained in:
bkfox
2019-08-07 01:45:27 +02:00
parent 324cf2ef0f
commit 248b77fca4
52 changed files with 794 additions and 384 deletions

View File

@ -1,8 +1,11 @@
from .page import Page, NavItem
from .article import Article
from .page import Category, Page, NavItem
from .program import Program, Stream, Schedule
from .episode import Episode, Diffusion
from .log import Log
from .sound import Sound, Track
from .station import Station, Port
from . import signals

27
aircox/models/article.py Normal file
View File

@ -0,0 +1,27 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .page import Page
from .program import Program, InProgramQuerySet
class Article(Page):
program = models.ForeignKey(
Program, models.SET_NULL,
verbose_name=_('program'), blank=True, null=True,
help_text=_("publish as this program's article"),
)
is_static = models.BooleanField(
_('is static'), default=False,
help_text=_('Should this article be considered as a page '
'instead of a blog article'),
)
objects = InProgramQuerySet.as_manager()
class Meta:
verbose_name = _('Article')
verbose_name_plural = _('Articles')

View File

@ -18,13 +18,17 @@ from .page import Page, PageQuerySet
__all__ = ['Episode', 'Diffusion', 'DiffusionQuerySet']
class EpisodeQuerySet(PageQuerySet, InProgramQuerySet):
pass
class Episode(Page):
program = models.ForeignKey(
Program, models.CASCADE,
verbose_name=_('program'),
)
objects = InProgramQuerySet.as_manager()
objects = EpisodeQuerySet.as_manager()
detail_url_name = 'episode-detail'
class Meta:
@ -37,17 +41,14 @@ class Episode(Page):
super().save(*args, **kwargs)
@classmethod
def get_default_title(cls, program, date):
def get_init_kwargs_from(cls, page, date, title=None, **kwargs):
""" Get default Episode's title """
return settings.AIRCOX_EPISODE_TITLE.format(
program=program,
title = settings.AIRCOX_EPISODE_TITLE.format(
program=page,
date=date.strftime(settings.AIRCOX_EPISODE_TITLE_DATE_FORMAT),
)
@classmethod
def from_date(cls, program, date):
title = cls.get_default_title(program, date)
return cls(program=program, title=title, cover=program.cover)
) if title is None else title
return super().get_init_kwargs_from(page, title=title, program=page,
**kwargs)
class DiffusionQuerySet(BaseRerunQuerySet):

View File

@ -1,13 +1,13 @@
from enum import IntEnum
import re
from django.db import models
from django.urls import reverse
from django.utils import timezone as tz
from django.utils.text import slugify
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils.functional import cached_property
from ckeditor.fields import RichTextField
from filer.fields.image import FilerImageField
@ -16,13 +16,36 @@ from model_utils.managers import InheritanceQuerySet
from .station import Station
__all__ = ['PageQuerySet', 'Page', 'NavItem']
__all__ = ['Category', 'PageQuerySet', 'Page', 'NavItem']
headline_re = re.compile(r'(<p>)?'
r'(?P<headline>[^\n]{1,140}(\n|[^\.]*?\.))'
r'(</p>)?')
class Category(models.Model):
title = models.CharField(_('title'), max_length=64)
slug = models.SlugField(_('slug'), max_length=64, db_index=True)
class Meta:
verbose_name = _('Category')
verbose_name_plural = _('Categories')
def __str__(self):
return self.title
class PageQuerySet(InheritanceQuerySet):
def draft(self):
return self.filter(status=Page.STATUS.draft)
def published(self):
return self.filter(status=Page.STATUS.published)
def trash(self):
return self.filter(status=Page.STATUS.trash)
class Page(models.Model):
""" Base class for publishable content """
@ -38,13 +61,18 @@ class Page(models.Model):
default=STATUS.draft,
choices=[(int(y), _(x)) for x, y in STATUS.__members__.items()],
)
category = models.ForeignKey(
Category, models.SET_NULL,
verbose_name=_('category'), blank=True, null=True, db_index=True
)
cover = FilerImageField(
on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_('Cover'),
on_delete=models.SET_NULL,
verbose_name=_('Cover'), null=True, blank=True,
)
content = RichTextField(
_('content'), blank=True, null=True,
)
date = models.DateTimeField(default=tz.now)
featured = models.BooleanField(
_('featured'), default=False,
)
@ -86,6 +114,23 @@ class Page(models.Model):
def is_trash(self):
return self.status == self.STATUS.trash
@cached_property
def headline(self):
if not self.content:
return ''
headline = headline_re.search(self.content)
return headline.groupdict()['headline'] if headline else ''
@classmethod
def get_init_kwargs_from(cls, page, **kwargs):
kwargs.setdefault('cover', page.cover)
kwargs.setdefault('category', page.category)
return kwargs
@classmethod
def from_page(cls, page, **kwargs):
return cls(**cls.get_init_kwargs_from(page, **kwargs))
class NavItem(models.Model):
""" Navigation menu items """

View File

@ -470,7 +470,8 @@ class Schedule(BaseRerun):
continue
if initial is None:
episode = Episode.from_date(self.program, date)
episode = Episode.from_page(self.program, date=date)
episode.date = date
episodes[date] = episode
else:
episode = episodes[initial]
@ -489,10 +490,6 @@ class Schedule(BaseRerun):
if self.initial is not None and self.date > self.date:
raise ValueError('initial must be later')
# initial only if it has been yet saved
if self.pk:
self.__initial = self.__dict__.copy()
class Stream(models.Model):
"""

100
aircox/models/signals.py Executable file
View File

@ -0,0 +1,100 @@
import pytz
from django.contrib.auth.models import User, Group, Permission
from django.db.models import F, signals
from django.dispatch import receiver
from django.utils import timezone as tz
from .. import settings, utils
from . import Diffusion, Episode, Program, Schedule
# Add a default group to a user when it is created. It also assigns a list
# of permissions to the group if it is created.
#
# - group name: settings.AIRCOX_DEFAULT_USER_GROUP
# - group permissions: settings.AIRCOX_DEFAULT_USER_GROUP_PERMS
#
@receiver(signals.post_save, sender=User)
def user_default_groups(sender, instance, created, *args, **kwargs):
"""
Set users to different default groups
"""
if not created or instance.is_superuser:
return
for groupName, permissions in settings.AIRCOX_DEFAULT_USER_GROUPS.items():
if instance.groups.filter(name=groupName).count():
continue
group, created = Group.objects.get_or_create(name=groupName)
if created and permissions:
for codename in permissions:
permission = Permission.objects.filter(
codename=codename).first()
if permission:
group.permissions.add(permission)
group.save()
instance.groups.add(group)
@receiver(signals.post_save, sender=Program)
def program_post_save(sender, instance, created, *args, **kwargs):
"""
Clean-up later diffusions when a program becomes inactive
"""
if not instance.active:
Diffusion.objects.program(instance).after().delete()
Episode.object.program(instance).filter(diffusion__isnull=True) \
.delete()
@receiver(signals.pre_save, sender=Schedule)
def schedule_pre_save(sender, instance, *args, **kwargs):
if getattr(instance, 'pk') is not None:
instance._initial = Schedule.objects.get(pk=instance.pk)
# TODO
@receiver(signals.post_save, sender=Schedule)
def schedule_post_save(sender, instance, created, *args, **kwargs):
"""
Handles Schedule's time, duration and timezone changes and update
corresponding diffusions accordingly.
"""
initial = getattr(instance, '_initial', None)
if not initial or ((instance.time, instance.duration, instance.timezone) ==
(initial.time, initial.duration, initial.timezone)):
return
today = tz.datetime.today()
delta = instance.normalize(today) - initial.normalize(today)
qs = Diffusion.objects.program(instance.program).after()
pks = [d.pk for d in qs if initial.match(d.date)]
qs.filter(pk__in=pks).update(
start=F('start') + delta,
end=F('start') + delta + utils.to_timedelta(instance.duration)
)
@receiver(signals.pre_delete, sender=Schedule)
def schedule_pre_delete(sender, instance, *args, **kwargs):
"""
Delete later corresponding diffusion to a changed schedule.
"""
if not instance.program.sync:
return
qs = Diffusion.objects.program(instance.program).after()
pks = [d.pk for d in qs if instance.match(d.date)]
qs.filter(pk__in=pks).delete()
@receiver(signals.post_delete, sender=Diffusion)
def diffusion_post_delete(sender, instance, *args, **kwargs):
Episode.objects.filter(diffusion__isnull=True, content_isnull=True,
sound__isnull=True) \
.delete()