diff --git a/aircox/admin.py b/aircox/admin.py
index 09b71bd..e763b7a 100755
--- a/aircox/admin.py
+++ b/aircox/admin.py
@@ -82,7 +82,7 @@ class ProgramAdmin(NameableAdmin):
schedule.short_description = _("Schedule")
list_display = ('id', 'name', 'active', 'schedule')
- fields = NameableAdmin.fields + [ 'active' ]
+ fields = NameableAdmin.fields + [ 'active', 'station' ]
# TODO list_display
inlines = [ ScheduleInline, StreamInline ]
diff --git a/aircox/models.py b/aircox/models.py
index e981fed..66cfc49 100755
--- a/aircox/models.py
+++ b/aircox/models.py
@@ -292,7 +292,7 @@ class Program(Nameable):
active = models.BooleanField(
_('active'),
default = True,
- help_text = _('if not set this program is no longer active')
+ help_text = _('if not checked this program is no longer active')
)
@property
diff --git a/aircox_cms/forms.py b/aircox_cms/forms.py
index 69c6ae0..e5cbdbf 100644
--- a/aircox_cms/forms.py
+++ b/aircox_cms/forms.py
@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from honeypot.decorators import verify_honeypot_value
-import aircox.cms.models as models
+import aircox_cms.models as models
class CommentForm(forms.ModelForm):
diff --git a/aircox_cms/management/commands/programs_to_cms.py b/aircox_cms/management/commands/programs_to_cms.py
index d576793..3ef1a58 100644
--- a/aircox_cms/management/commands/programs_to_cms.py
+++ b/aircox_cms/management/commands/programs_to_cms.py
@@ -41,7 +41,7 @@ class Command (BaseCommand):
# programs
logger.info('Programs...')
- parent = settings.default_program_parent_page
+ parent = settings.default_programs_page
qs = Program.objects.filter(
active = True,
stream__isnull = True,
diff --git a/aircox_cms/models.py b/aircox_cms/models.py
index a4fe605..c8dd9a4 100644
--- a/aircox_cms/models.py
+++ b/aircox_cms/models.py
@@ -33,11 +33,22 @@ import aircox_cms.settings as settings
from aircox_cms.utils import image_url
from aircox_cms.sections import *
+import aircox_cms.signals
+
@register_setting
class WebsiteSettings(BaseSetting):
- # TODO: #Station assign a website to a aircox.models.model.station when it will
- # exist. Update all dependent code such as signal handling
+ station = models.OneToOneField(
+ aircox.models.Station,
+ verbose_name = _('aircox station'),
+ related_name = 'website_settings',
+ unique = True,
+ blank = True, null = True,
+ help_text = _(
+ 'refers to an Aircox\'s station; it is used to make the link '
+ 'between the website and Aircox'
+ ),
+ )
# general website information
favicon = models.ImageField(
@@ -58,11 +69,12 @@ class WebsiteSettings(BaseSetting):
help_text = _('public description of the website; used for referencing'),
)
list_page = models.ForeignKey(
- 'aircox_cms.GenericPage',
+ 'aircox_cms.DynamicListPage',
verbose_name = _('page for lists'),
help_text=_('page used to display the results of a search and other '
'lists'),
- related_name= 'list_page'
+ related_name= 'list_page',
+ blank = True, null = True,
)
# comments
accept_comments = models.BooleanField(
@@ -76,36 +88,50 @@ class WebsiteSettings(BaseSetting):
comment_success_message = models.TextField(
_('success message'),
default = _('Your comment has been successfully posted!'),
- help_text = _('message to display when a post has been posted'),
+ help_text = _(
+ 'message displayed when a comment has been successfully posted'
+ ),
)
comment_wait_message = models.TextField(
_('waiting message'),
default = _('Your comment is awaiting for approval.'),
- help_text = _('message to display when a post waits to be reviewed'),
+ help_text = _(
+ 'message displayed when a comment has been sent, but waits for '
+ ' website administrators\' approval.'
+ ),
)
comment_error_message = models.TextField(
_('error message'),
default = _('We could not save your message. Please correct the error(s) below.'),
- help_text = _('message to display there is an error an incomplete form.'),
+ help_text = _(
+ 'message displayed when the form of the comment has been '
+ ' submitted but there is an error, such as an incomplete field'
+ ),
)
- auto_create = models.BooleanField(
- _('automatic publications'),
+ sync = models.BooleanField(
+ _('synchronize with Aircox'),
default = False,
help_text = _(
- 'Create automatically new publications for new programs and '
- 'diffusions in the timetable. If set, please complete other '
- 'options of this panel'
+ 'create publication for each object added to an Aircox\'s '
+ 'station; for example when there is a new program, or '
+ 'when a diffusion has been added to the timetable. Note: '
+ 'it does not concern the Station themselves.'
+ # /doc/ the page is saved but not pubished -- this must be
+ # done manually, when the user edit it.
)
)
- default_program_parent_page = ParentalKey(
+ default_programs_page = ParentalKey(
Page,
- verbose_name = _('default program parent page'),
+ verbose_name = _('default programs page'),
blank = True, null = True,
help_text = _(
- 'Default parent page for program\'s pages. It is used to assign '
- 'a page to the publication of a newly created program and can '
- 'be changed later'
+ 'when a new program is saved and a publication is created, '
+ 'put this publication as a child of this page. If no page '
+ 'has been specified, try to put it as the child of the '
+ 'website\'s root page (otherwise, do not create the page).'
+ # /doc/ (technicians, admin): if the page has not been created,
+ # it still can be created using the `programs_to_cms` command.
),
limit_choices_to = lambda: {
'show_in_menus': True,
@@ -119,7 +145,7 @@ class WebsiteSettings(BaseSetting):
FieldPanel('tags'),
FieldPanel('description'),
FieldPanel('list_page'),
- ], heading=_('promotion')),
+ ], heading=_('Promotion')),
MultiFieldPanel([
FieldPanel('allow_comments'),
FieldPanel('accept_comments'),
@@ -128,8 +154,8 @@ class WebsiteSettings(BaseSetting):
FieldPanel('comment_error_message'),
], heading = _('Comments')),
MultiFieldPanel([
- FieldPanel('auto_create'),
- FieldPanel('default_program_parent_page'),
+ FieldPanel('sync'),
+ FieldPanel('default_programs_page'),
], heading = _('Programs and controls')),
]
@@ -559,46 +585,38 @@ class DiffusionPage(Publication):
#
-# Other type of pages
+# Others types of pages
#
-class GenericPage(Page):
+class DynamicListPage(Page):
"""
- Page for simple lists, query is done though request' GET fields.
- Look at get_queryset for more information.
+ Displays a list of publications using query passed by the url.
+ This can be used for search/tags page, and generally only one
+ page is used per website.
+
+ If a title is given, use it instead of the generated one.
"""
+ # FIXME/TODO: title in template
body = RichTextField(
_('body'),
blank = True, null = True,
help_text = _('add an extra description for this list')
)
- list_from_request = models.BooleanField(
- _('list from the request'),
- default = False,
- help_text = _(
- 'if set, the page print a list based on the request made by '
- 'the website visitor, and its title will be adapted to this '
- 'request. Can be usefull for search pages, etc. and should '
- 'only be set on one page.'
- )
- )
content_panels = [
MultiFieldPanel([
FieldPanel('title'),
FieldPanel('body'),
- FieldPanel('list_from_request'),
], heading=_('Content'))
]
class Meta:
- verbose_name = _('Generic Page')
- verbose_name_plural = _('Generic Page')
+ verbose_name = _('Dynamic List Page')
+ verbose_name_plural = _('Dynamic List Pages')
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
- if self.list_from_request:
- qs = ListBase.from_request(request, context=context)
- context['object_list'] = qs
+ qs = ListBase.from_request(request, context=context)
+ context['object_list'] = qs
return context
@@ -647,6 +665,7 @@ class DatedListPage(DatedListBase,Page):
class LogsPage(DatedListPage):
template = 'aircox_cms/dated_list_page.html'
+ # TODO: make it a property that automatically select the station
station = models.ForeignKey(
aircox.models.Station,
verbose_name = _('station'),
diff --git a/aircox_cms/sections.py b/aircox_cms/sections.py
index 46faa99..ce04ef4 100644
--- a/aircox_cms/sections.py
+++ b/aircox_cms/sections.py
@@ -176,8 +176,10 @@ class ListBase(models.Model):
siblings = models.BooleanField(
verbose_name = _('select siblings of related'),
default = False,
- help_text = _('if selected select related publications that are '
- 'siblings of this one'),
+ help_text = _(
+ 'if checked, related publications are siblings instead of '
+ 'the children.'
+ ),
)
asc = models.BooleanField(
verbose_name = _('ascending order'),
@@ -200,7 +202,6 @@ class ListBase(models.Model):
], heading=_('sorting'))
]
-
def get_queryset(self):
"""
Get queryset based on the arguments. This class is intended to be
@@ -223,8 +224,8 @@ class ListBase(models.Model):
else:
qs = qs.descendant_of(related)
- date = self.related.date if hasattr(related, 'date') else \
- self.related.first_published_at
+ date = related.date if hasattr(related, 'date') else \
+ related.first_published_at
if self.date_filter == self.DateFilter.before_related:
qs = qs.filter(date__lt = date)
elif self.date_filter == self.DateFilter.after_related:
@@ -247,7 +248,7 @@ class ListBase(models.Model):
to override values of self or add some to the parameters.
If there is related field use it to get the page, otherwise use the
- given list_page or the first GenericPage it finds.
+ given list_page or the first DynamicListPage it finds.
"""
import aircox_cms.models as models
@@ -261,7 +262,7 @@ class ListBase(models.Model):
params.update(kwargs)
page = params.get('related') or list_page or \
- models.GenericPage.objects.all().first()
+ models.DynamicListPage.objects.all().first()
if params.get('related'):
params['related'] = True
@@ -483,9 +484,6 @@ class Section(ClusterableModel):
InlinePanel('places', label=_('Section Items')),
]
- def __str__(self):
- return '{}: {}'.format(self.__class__.__name__, self.name or self.pk)
-
@classmethod
def get_sections_at (cl, position, page = None):
"""
@@ -503,22 +501,40 @@ class Section(ClusterableModel):
)
return qs
+ def add_item(self, item):
+ """
+ Add an item to the section. Automatically save the item and
+ create the corresponding SectionPlace.
+ """
+ # TODO: arbitrary position; Orderable.sort_order?
+ item.save()
+ place, created = SectionPlace.objects.get_or_create(
+ section = self,
+ item = item,
+ )
+
def render(self, request, page = None, context = None, *args, **kwargs):
return ''.join([
place.item.specific.render(request, page, context, *args, **kwargs)
for place in self.places.all()
])
+ def __str__(self):
+ return '{}: {}'.format(self.__class__.__name__, self.name or self.pk)
+
class SectionPlace(Orderable):
section = ParentalKey(Section, related_name='places')
item = models.ForeignKey(
'SectionItem',
- verbose_name=_('item')
+ verbose_name=_('item'),
)
panels = [ SnippetChooserPanel('item'), ]
+ class Meta:
+ unique_together = ('section', 'item')
+
def __str__(self):
return '{}: {}'.format(self.__class__.__name__, self.title or self.pk)
@@ -954,7 +970,8 @@ class SectionSearchField(SectionItem):
]
def get_context(self, request, page):
- from aircox_cms.models import GenericPage
+ # FIXME ?????
+ from aircox_cms.models import DynamicListPage
context = super().get_context(request, page)
return context
diff --git a/aircox_cms/signals.py b/aircox_cms/signals.py
index fcbf3b9..49bc113 100644
--- a/aircox_cms/signals.py
+++ b/aircox_cms/signals.py
@@ -1,13 +1,134 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
+from django.utils.translation import ugettext as _, ugettext_lazy
+from django.contrib.contenttypes.models import ContentType
-import aircox.models
+from wagtail.wagtailcore.models import Page, Site
+
+import aircox.models as aircox
+import aircox_cms.utils as utils
+
+# on a new diffusion
+
+@receiver(post_save, sender=aircox.Station)
+def station_post_saved(sender, instance, created, *args, **kwargs):
+ """
+ Create the basis for the website: set up settings and pages
+ that are common.
+ """
+ from aircox_cms.models import \
+ WebsiteSettings, Publication, ProgramPage, \
+ TimetablePage, DynamicListPage, LogsPage
+ import aircox_cms.sections as sections
+ if not created:
+ return
+
+ root_page = Page.objects.get(id=1)
+
+ homepage = Publication(
+ title = instance.name,
+ slug = instance.slug,
+ body = _(
+ 'If you see this page, then Aircox is running for the station '
+ '{station.name}. You might want to change it to a better one. '
+ ),
+ )
+ root_page.add_child(instance=homepage)
+
+ site = Site(
+ # /doc/ when a Station is created, a wagtail Site is generated with
+ # default options. User must set the correct localhost afterwards
+ hostname = instance.slug + ".local",
+ port = 80,
+ site_name = instance.name.capitalize(),
+ root_page = homepage,
+ )
+ site.save()
+
+ # settings
+ website_settings = WebsiteSettings(
+ site = site,
+ station = instance,
+ description = _("The website of the {name} radio").format(
+ name = instance.name
+ ),
+ # Translators: tags set by default in description of the website
+ tags = _('radio,{station.name}').format(station = instance)
+ )
+
+ # timetable
+ timetable = TimetablePage(
+ title = _('Timetable'),
+ )
+ homepage.add_child(instance = timetable)
+
+ # list page (search, terms)
+ list_page = DynamicListPage(
+ # title is dynamic: no need to specify
+ title = _('Search'),
+ )
+ homepage.add_child(instance = list_page)
+ website_settings.list_page = list_page
+
+ # programs' page: list of programs in a section
+ programs = Publication(
+ title = _('Programs'),
+ )
+ homepage.add_child(instance = programs)
+
+ section = sections.Section(
+ name = _('Programs List'),
+ position = 'post_content',
+ page = programs,
+ )
+ section.save();
+ section.add_item(sections.SectionList(
+ count = 15,
+ url_text = _('All programs'),
+ model = ContentType.objects.get_for_model(ProgramPage),
+ related = programs,
+ ))
+
+ website_settings.default_programs_page = programs
+ website_settings.sync = True
+
+ # logs (because it is a cool feature)
+ logs = LogsPage(
+ title = _('Previously on air'),
+ station = instance,
+ )
+ homepage.add_child(instance = logs)
+
+ # save
+ site.save()
+ website_settings.save()
-@receiver(post_save, sender=programs.Program)
-def on_new_program(sender, instance, created, *args):
+@receiver(post_save, sender=aircox.Program)
+def program_post_saved(sender, instance, created, *args, **kwargs):
import aircox_cms.models as models
if not created or instance.page.count():
return
+ settings = utils.get_station_settings(instance.station)
+ if not settings or not settings.sync:
+ return
+
+ parent = settings.default_programs_page or \
+ settings.site.root_page
+ if not parent:
+ return
+
+ page = models.ProgramPage(
+ program = instance,
+ title = instance.name,
+ live = False,
+ # Translators: default content of a page for program
+ body = _('{program.name} is a program on {station.name}.').format(
+ program = instance,
+ station = instance.station
+ )
+ )
+ parent.add_child(instance = page)
+
diff --git a/aircox_cms/templates/aircox_cms/base_site.html b/aircox_cms/templates/aircox_cms/base_site.html
index 5299db2..5a5f8f6 100644
--- a/aircox_cms/templates/aircox_cms/base_site.html
+++ b/aircox_cms/templates/aircox_cms/base_site.html
@@ -12,8 +12,8 @@
-
-
+
+
{% with favicon=settings.cms.WebsiteSettings.favicon %}
diff --git a/aircox_cms/templates/aircox_cms/dynamic_list_page.html b/aircox_cms/templates/aircox_cms/dynamic_list_page.html
new file mode 100644
index 0000000..17c28dd
--- /dev/null
+++ b/aircox_cms/templates/aircox_cms/dynamic_list_page.html
@@ -0,0 +1,54 @@
+{% extends "aircox_cms/base_site.html" %}
+{# generic page to display list of articles #}
+
+{% load i18n %}
+{% load wagtailcore_tags %}
+{% load wagtailimages_tags %}
+
+
+{% block title %}
+
+{# Translators: titles for the page that shows a list of elements. #}
+{# Translators: terms are search terms, or tag tarms. url: url to the page #}
+{% with terms=list_selector.terms %}
+{% if terms %}
+{% blocktrans %}Search in publications for {{ terms }}{% endblocktrans %}
+{% elif list_selector.filter_related %}
+{# should never happen #}
+{% with title=list_selector.filter_related.title url=list_selector.filter_related.url %}
+{% blocktrans %}
+ Related to {{ title }}{% endblocktrans %}
+{% endwith %}
+{% else %}
+{% blocktrans %}All the publications{% endblocktrans %}
+{% endif %}
+{% endwith %}
+{% endif %}
+
+{% endblock %}
+
+
+{% block content %}
+{% with related=list_selector.filter_related %}
+ {% if related %}
+
+ {% image related.cover fill-128x128 class="cover item_cover" %}
+ {{ related.summary }}
+ {% trans "More about it" %}
+
-{% endif %}
-{% endblock %}
-
-
-
diff --git a/aircox_cms/templates/aircox_cms/publication.html b/aircox_cms/templates/aircox_cms/publication.html
index ef2bd10..14e7d94 100644
--- a/aircox_cms/templates/aircox_cms/publication.html
+++ b/aircox_cms/templates/aircox_cms/publication.html
@@ -57,4 +57,3 @@
{% endblock %}
-
diff --git a/aircox_cms/utils.py b/aircox_cms/utils.py
index 361289d..4c9a474 100644
--- a/aircox_cms/utils.py
+++ b/aircox_cms/utils.py
@@ -2,9 +2,20 @@
from django.core.urlresolvers import reverse
from wagtail.wagtailimages.utils import generate_signature
+
def image_url(image, filter_spec):
signature = generate_signature(image.id, filter_spec)
url = reverse('wagtailimages_serve', args=(signature, image.id, filter_spec))
url += image.file.name[len('original_images/'):]
return url
+def get_station_settings(station):
+ import aircox_cms.models as models
+ return models.WebsiteSettings.objects \
+ .filter(station = station).first()
+
+def get_station_site(station):
+ settings = get_station_settings(station)
+ return settings and settings.site
+
+
diff --git a/notes.md b/notes.md
index 07a4766..ecee3c6 100644
--- a/notes.md
+++ b/notes.md
@@ -18,13 +18,16 @@ This file is used as a reminder, can be used as crappy documentation too.
-# Long term TODO
-- debug/prod configuration
+# To discuss / To think
+- aircox_cms.signals: handle renaming of the program if the article's title has
+ not been changed -> need to cache of the title at __init__
+- ensure per station website for all generated publications
+- aircox_cms: remove "station" fields when it is possible in the pages & sections
+
+# Long term TODO
programs:
- sounds monitor: max_size of path, take in account
-
-controllers:
- archives can be set afterwards for rerun, so check must be done
at the same time we monitor
- logs: archive functionnality