forked from rc/aircox
website
This commit is contained in:
@ -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
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
27
aircox/models/article.py
Normal file
27
aircox/models/article.py
Normal 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')
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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 """
|
||||
|
@ -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
100
aircox/models/signals.py
Executable 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()
|
||||
|
||||
|
Reference in New Issue
Block a user