diff --git a/cms/README.md b/cms/README.md
index e18d852..ffd080a 100644
--- a/cms/README.md
+++ b/cms/README.md
@@ -14,6 +14,11 @@ Simple CMS generator used in Aircox. Main features includes:
We aims here to automatize most common tasks and to ease website
configuration.
+# Dependencies
+* ```django-taggit```: publications tags
+
+Note: this application can be used outside Aircox if needed.
+
# Architecture
A **Website** holds all required informations to run the server instance. It
is used to register all kind of posts, routes to the views, menus, etc.
diff --git a/cms/admin.py b/cms/admin.py
index 8bed95d..2c18556 100644
--- a/cms/admin.py
+++ b/cms/admin.py
@@ -1,6 +1,8 @@
from django.contrib import admin
-
-
+import aircox.cms.models as models
+
+admin.site.register(cms.Article)
+admin.site.register(cms.Comment)
diff --git a/cms/templates/aircox/cms/list.html b/cms/templates/aircox/cms/list.html
index b3cda21..e161d5a 100644
--- a/cms/templates/aircox/cms/list.html
+++ b/cms/templates/aircox/cms/list.html
@@ -75,6 +75,7 @@
{% endfor %}
+{% if object_list %}
{% if page_obj or list.url %}
{% endif %}
+{% endif %}
{% endblock %}
diff --git a/website/README.md b/website/README.md
new file mode 100644
index 0000000..363bba7
--- /dev/null
+++ b/website/README.md
@@ -0,0 +1,30 @@
+# Website
+Application that propose a set of different tools that might be common to
+different radio projects. This application has been started to avoid to
+pollute *aircox.cms* with aircox specific code and models that might not
+be used in other cases.
+
+We define here different models and sections that can be used to construct
+a website in a fast and simple manner.
+
+# Dependencies
+* ```django-suit```: admin interface;
+* ```django-autocomplete-light```: autocompletion in the admin interface;
+* ```aircox.cms```, ```aircox.programs```
+
+# Features
+## Models
+* **Program**: publication related to a program;
+* **Diffusion**: publication related to an initial Diffusion;
+
+
+## Sections
+* **Diffusions**: generic section list to retrieve diffusions by date, related
+ or not to a specific Program. If wanted, can show schedule in the header of
+ the section (with indication of reruns).
+* **Playlist**: playlist of a given Diffusion
+
+## Admin
+Register all models declared upper, uses django-suit features in order to manage
+some fields and autocompletion.
+
diff --git a/website/__init__.py b/website/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/website/admin.py b/website/admin.py
new file mode 100644
index 0000000..d048aef
--- /dev/null
+++ b/website/admin.py
@@ -0,0 +1,8 @@
+from django.contrib import admin
+
+import aircox.website.models as models
+
+admin.site.register(models.Program)
+admin.site.register(models.Diffusion)
+
+
diff --git a/website/models.py b/website/models.py
new file mode 100644
index 0000000..1d0cea3
--- /dev/null
+++ b/website/models.py
@@ -0,0 +1,52 @@
+from django.db import models
+from django.utils.translation import ugettext as _, ugettext_lazy
+
+from aircox.cms.models import RelatedPost, Article
+import aircox.programs.models as programs
+
+class Program (RelatedPost):
+ url = models.URLField(_('website'), blank=True, null=True)
+ # rss = models.URLField()
+ email = models.EmailField(
+ _('email'), blank=True, null=True,
+ help_text=_('contact address, stays private')
+ )
+
+ class Relation:
+ model = programs.Program
+ bindings = {
+ 'title': 'name',
+ }
+ rel_to_post = True
+ auto_create = True
+
+class Diffusion (RelatedPost):
+ class Relation:
+ model = programs.Diffusion
+ bindings = {
+ 'thread': 'program',
+ 'date': 'start',
+ }
+ fields_args = {
+ 'limit_choice_to': {
+ 'initial': None
+ }
+ }
+ rel_to_post = True
+ auto_create = True
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.thread:
+ if not self.title:
+ self.title = _('{name} on {first_diff}').format(
+ self.related.program.name,
+ self.related.start.strftime('%A %d %B')
+ )
+ if not self.content:
+ self.content = self.thread.content
+ if not self.image:
+ self.image = self.thread.image
+ if not self.tags and self.pk:
+ self.tags = self.thread.tags
+
diff --git a/website/sections.py b/website/sections.py
new file mode 100644
index 0000000..9ce1741
--- /dev/null
+++ b/website/sections.py
@@ -0,0 +1,140 @@
+from django.utils import timezone as tz
+from django.utils.translation import ugettext as _, ugettext_lazy
+
+import aircox.programs.models as programs
+import aircox.cms.models as cms
+import aircox.cms.routes as routes
+import aircox.cms.sections as sections
+
+import website.models as models
+
+
+class Diffusions(sections.List):
+ """
+ Section that print diffusions. When rendering, if there is no post yet
+ associated, use the programs' article.
+ """
+ next_count = 5
+ prev_count = 5
+ show_schedule = False
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.__dict__.update(kwargs)
+
+ def get_diffs(self):
+ qs = programs.Diffusion.objects.filter(
+ type = programs.Diffusion.Type.normal
+ )
+ if self.object:
+ qs = qs.filter(program = self.object.related)
+
+ r = []
+ if self.next_count:
+ r += list(programs.Diffusion.get(next=True, queryset = qs)
+ .order_by('-start')[:self.next_count])
+ if self.prev_count:
+ r += list(programs.Diffusion.get(prev=True, queryset = qs)
+ .order_by('-start')[:self.prev_count])
+ return r
+
+ def get_object_list(self):
+ diffs = self.get_diffs()
+
+ posts = models.Diffusion.objects.filter(related__in = diffs)
+ r = []
+ for diff in diffs:
+ diff_ = diff.initial if diff.initial else diff
+ post = next((x for x in posts if x.related == diff_), None)
+ if not post:
+ post = sections.ListItem(date = diff.start)
+ else:
+ post = sections.ListItem(post=post)
+ post.date = diff.start
+
+ if diff.initial:
+ post.info = _('rerun of %(day)s') % {
+ 'day': diff.initial.date.strftime('%A %d/%m')
+ }
+
+ if self.object:
+ post.update(self.object)
+ else:
+ thread = models.Program.objects. \
+ filter(related = diff.program, published = True)
+ if thread:
+ post.update(thread[0])
+ r.append(post)
+ return [ sections.ListItem(post=post) for post in r ]
+
+ @property
+ def url(self):
+ if self.object:
+ return models.Diffusion.route_url(routes.ThreadRoute, {
+ 'pk': self.object.id,
+ 'thread_model': 'program',
+ })
+ return models.Diffusion.route_url(routes.AllRoute)
+
+ @property
+ def header(self):
+ if not self.show_schedule:
+ return None
+
+
+ def str_sched(sched):
+ info = ' (' + _('rerun of %(day)s') % {
+ 'day': sched.initial.date.strftime('%A')
+ } + ')' if sched.initial else ''
+
+ text = _('%(day)s at %(time)s, %(freq)s') % {
+ 'day': sched.date.strftime('%A'),
+ 'time': sched.date.strftime('%H:%M'),
+ 'freq': sched.get_frequency_display(),
+ }
+ return text + info
+
+ return ' / \n'.join([str_sched(sched)
+ for sched in programs.Schedule.objects \
+ .filter(program = self.object.related.pk)
+ ])
+
+
+class Playlist(sections.List):
+ title = _('Playlist')
+ message_empty = ''
+
+ def get_object_list(self):
+ tracks = programs.Track.objects \
+ .filter(diffusion = self.object.related) \
+ .order_by('position')
+ return [ sections.ListItem(title=track.title, content=track.artist)
+ for track in tracks ]
+
+
+#class DatesOfDiffusion(sections.List):
+# title = _('Dates of diffusion')
+#
+# def get_object_list(self):
+# diffs = list(programs.Diffusion.objects. \
+# filter(initial = self.object.related). \
+# exclude(type = programs.Diffusion.Type.unconfirmed)
+# )
+# diffs.append(self.object.related)
+#
+# items = []
+# for diff in sorted(diffs, key = lambda d: d.date, reverse = True):
+# info = ''
+# if diff.initial:
+# info = _('rerun')
+# if diff.type == programs.Diffusion.Type.canceled:
+# info += ' ' + _('canceled')
+# items.append(
+# sections.List.Item(None, diff.start.strftime('%c'), info, None,
+# 'canceled')
+# )
+# return items
+#
+## TODO sounds
+#
+