diff --git a/aircox_cms/static/aircox_cms/styles.css b/aircox_cms/static/aircox_cms/styles.css index 54d97e3..db2fe27 100644 --- a/aircox_cms/static/aircox_cms/styles.css +++ b/aircox_cms/static/aircox_cms/styles.css @@ -1,10 +1,41 @@ -body { padding: 0; margin: 0; } - -nav.menu_top { - width: 100% - position: absolute; - border-bottom: 1px grey solid; +body { + padding: 0; + margin: 0; } +.page { + display: flex; +} + + .page .menu { + width: 15em; + } + + .page .menu_left { margin-right: 0.5em; } + .page .menu_right { margin-left: 0.5em; } + + .page main { + flex-grow: 1; + } + + +main .section { + width: calc(50% - 1em); + float: left; + padding: 0.5em; +} + + main .section .section_content { + font-size: 0.95em; + } + + main .section h1 { + font-size: 1.2em; + } + + main .section * { + max-width: 100%; + } + diff --git a/aircox_cms/templates/aircox_cms/base_section.html b/aircox_cms/templates/aircox_cms/base_section.html new file mode 100644 index 0000000..4a054b6 --- /dev/null +++ b/aircox_cms/templates/aircox_cms/base_section.html @@ -0,0 +1,9 @@ + +<{{ tag }} class="section {{ classes }}" + {% for key, value in attrs.items %}{{ key }} = "{{ value|addslashes }}" + {% endfor %} > +{% block content %} +{{ content|safe }} +{% endblock %} + + diff --git a/aircox_cms/templates/aircox_cms/base_site.html b/aircox_cms/templates/aircox_cms/base_site.html index 3646555..5f14dc4 100644 --- a/aircox_cms/templates/aircox_cms/base_site.html +++ b/aircox_cms/templates/aircox_cms/base_site.html @@ -8,22 +8,23 @@ - + + {% if website.styles %} + + {% endif %} {{ website.name }} {% if title %}- {{ title }} {% endif %} + {% block header %} + {% if menus.header %} + {{ menus.header|safe }} + {% endif %} + {% endblock %} + {% if menus.top %} {{ menus.top|safe }} {% endif %} - {% block header %} - {% if menus.header %} -
- {{ menus.header|safe }} -
- {% endif %} - {% endblock %} -
{% if menus.left %} {{ menus.left|safe }} @@ -50,11 +51,13 @@ {% endif %}
+ {% if menus.page_bottom %} + {{ menus.page_bottom|safe }} + {% endif %} + {% block footer %} {% if menus.footer %} - + {{ menus.footer|safe }} {% endif %} {% endblock %} diff --git a/aircox_cms/templates/aircox_cms/menu.html b/aircox_cms/templates/aircox_cms/menu.html index 917406b..acfc3f0 100644 --- a/aircox_cms/templates/aircox_cms/menu.html +++ b/aircox_cms/templates/aircox_cms/menu.html @@ -1,10 +1,10 @@ - + diff --git a/aircox_cms/templates/aircox_cms/section.html b/aircox_cms/templates/aircox_cms/section.html index 38e477b..3f549a4 100644 --- a/aircox_cms/templates/aircox_cms/section.html +++ b/aircox_cms/templates/aircox_cms/section.html @@ -1,33 +1,34 @@ -
- {% if title %} -

- {% block section_title %} - {{ title }} - {% endblock %} -

- {% endif %} +{% extends "aircox_cms/base_section.html" %} - {% if header %} -
- {% block section_header %} - {{ header }} - {% endblock %} -
- {% endif %} +{% block content %} +{% if title %} +

+ {% block section_title %} + {{ title }} + {% endblock %} +

+{% endif %} -
- {% block section_content %} - {{ content|safe }} - {% endblock %} -
+{% if header %} +
+ {% block section_header %} + {{ header }} + {% endblock %} +
+{% endif %} - {% if bottom %} -
- {% block section_bottom %} - {{ bottom }} - {% endblock %} -
- {% endif %} +
+ {% block section_content %} + {{ content|safe }} + {% endblock %}
+{% if bottom %} +
+ {% block section_bottom %} + {{ bottom }} + {% endblock %} +
+{% endif %} +{% endblock %} diff --git a/aircox_cms/views.py b/aircox_cms/views.py index ba95209..30f681a 100644 --- a/aircox_cms/views.py +++ b/aircox_cms/views.py @@ -1,9 +1,14 @@ +import re + + +from django.templatetags.static import static from django.shortcuts import render from django.template.loader import render_to_string from django.views.generic import ListView, DetailView from django.views.generic.base import View, TemplateResponseMixin from django.core import serializers from django.utils.translation import ugettext as _, ugettext_lazy +from django.utils.html import escape import aircox_cms.routes as routes @@ -29,12 +34,8 @@ class PostBaseView: context['menus'] = { k: v.get(self.request) for k, v in { - 'top': self.website.get_menu('top'), - 'left': self.website.get_menu('left'), - 'bottom': self.website.get_menu('bottom'), - 'right': self.website.get_menu('right'), - 'header': self.website.get_menu('header'), - 'footer': self.website.get_menu('footer'), + k: self.website.get_menu(k) + for k in self.website.menu_layouts }.items() if v } @@ -162,6 +163,179 @@ class PostDetailView (DetailView, PostBaseView): return context +class Menu (View): + template_name = 'aircox_cms/menu.html' + + name = '' + tag = 'nav' + enabled = True + classes = '' + position = '' # top, left, bottom, right, header, footer, page_top, page_bottom + sections = None + + def __init__ (self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.name = self.name or ('menu_' + self.position) + + def get_context_data (self, **kwargs): + return { + 'name': self.name, + 'tag': self.tag, + 'classes': self.classes, + 'position': self.position, + 'sections': [ + section.get(self.request, object = None) + for section in self.sections + ] + } + + def get (self, request, **kwargs): + self.request = request + context = self.get_context_data(**kwargs) + return render_to_string(self.template_name, context) + + + +class BaseSection (View): + """ + Base class for sections. Sections are view that can be used in detail view + in order to have extra content about a post, or in menus. + """ + template_name = 'aircox_cms/base_section.html' + tag = 'div' # container tags + classes = '' # container classes + attrs = '' # container extra attributes + content = '' # content + + + def get_context_data (self, **kwargs): + return { + 'tag': self.tag, + 'classes': self.classes, + 'attrs': self.attrs, + 'content': self.content, + } + + def get (self, request, **kwargs): + self.request = request + context = self.get_context_data(**kwargs) + return render_to_string(self.template_name, context) + + +class Section (BaseSection): + template_name = 'aircox_cms/section.html' + require_object = False + object = None + title = '' + header = '' + bottom = '' + + def get_context_data (self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'title': self.title, + 'header': self.header, + 'bottom': self.bottom, + }) + return context + + def get (self, request, **kwargs): + self.object = kwargs.get('object') or self.object + return super().get(request, **kwargs) + + +class Sections: + class Image (BaseSection): + url = None # relative url to the image + + @property + def content (self): + return ''.format( + static(self.url), + ) + + + class PostContent (Section): + @property + def content (self): + content = escape(self.object.content) + content = re.sub(r'(^|\n\n)((\n?[^\n])+)', r'

\2

', content) + content = re.sub(r'\n', r'
', content) + return content + + + class PostImage (Section): + @property + def content (self): + return ''.format( + self.object.image.url + ) + + + class List (Section): + """ + Section to render list. The context item 'object_list' is used as list of + items to render. + """ + class Item: + icon = None + title = None + text = None + + def __init__ (self, icon, title = None, text = None): + self.icon = icon + self.title = title + self.text = text + + use_icons = True + icon_size = '32x32' + template_name = 'aircox_cms/section_list.html' + + def get_object_list (self): + return [] + + def get_context_data (self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'classes': context.get('classes') + ' section_list', + 'icon_size': self.icon_size, + 'object_list': self.get_object_list(), + }) + return context + + + class UrlList (List): + classes = 'section_urls' + targets = None + + def get_object_list (self, request, **kwargs): + return [ + List.Item( + target.image or None, + '{}'.format(target.detail_url(), target.title) + ) + for target in self.targets + ] + + + class PostList (PostListView): + route = None + model = None + embed = True + + def __init__ (self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def get_kwargs (self, request, **kwargs): + return kwargs + + def dispatch (self, request, *args, **kwargs): + kwargs = self.get_kwargs(kwargs) + response = super().dispatch(request, *args, **kwargs) + return str(response.content) + + + class ViewSet: """ A ViewSet is a class helper that groups detail and list views that can be @@ -173,7 +347,10 @@ class ViewSet: list_routes = [] detail_view = PostDetailView - detail_sections = [] + detail_sections = [ + Sections.PostContent, + Sections.PostImage, + ] def __init__ (self, website = None): self.detail_sections = [ @@ -195,131 +372,3 @@ class ViewSet: [ routes.DetailRoute.as_url(self.model, self.detail_view ) ] -class Menu (View): - template_name = 'aircox_cms/menu.html' - - name = '' - enabled = True - classes = '' - position = '' # top, left, bottom, right, header, footer - sections = None - - def __init__ (self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.name = self.name or ('menu_' + self.position) - - def get_context_data (self, **kwargs): - return { - 'name': self.name, - 'classes': self.classes, - 'position': self.position, - 'sections': [ - section.get(self.request, object = None) - for section in self.sections - ] - } - - def get (self, request, **kwargs): - self.request = request - context = self.get_context_data(**kwargs) - return render_to_string(self.template_name, context) - - -class Section (View): - """ - Base class for sections. Sections are view that can be used in detail view - in order to have extra content about a post. - """ - template_name = 'aircox_cms/section.html' - require_object = False - object = None - classes = '' - title = '' - content = '' - header = '' - bottom = '' - - def get_context_data (self, **kwargs): - context = { - 'title': self.title, - 'header': self.header, - 'content': self.content, - 'bottom': self.bottom, - 'classes': self.classes, - } - return context - - def get (self, request, **kwargs): - self.object = kwargs.get('object') or self.object - self.request = request - context = self.get_context_data(**kwargs) - return render_to_string(self.template_name, context) - - - -class ListSection (Section): - """ - Section to render list. The context item 'object_list' is used as list of - items to render. - """ - class Item: - icon = None - title = None - text = None - - def __init__ (self, icon, title = None, text = None): - self.icon = icon - self.title = title - self.text = text - - use_icons = True - icon_size = '32x32' - template_name = 'aircox_cms/section_list.html' - - def get_object_list (self): - return [] - - def get_context_data (self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'classes': context.get('classes') + ' section_list', - 'icon_size': self.icon_size, - 'object_list': self.get_object_list(), - }) - return context - - -class UrlListSection (ListSection): - classes = 'section_urls' - targets = None - - def get_object_list (self, request, **kwargs): - return [ - ListSection.Item( - target.image or None, - '{}'.format(target.detail_url(), target.title) - ) - for target in self.targets - ] - - -class PostListSection (PostListView): - route = None - model = None - embed = True - - def __init__ (self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def get_kwargs (self, request, **kwargs): - return kwargs - - def dispatch (self, request, *args, **kwargs): - kwargs = self.get_kwargs(kwargs) - response = super().dispatch(request, *args, **kwargs) - return str(response.content) - -# TODO: -# - get_title: pass object / queryset - - diff --git a/aircox_cms/website.py b/aircox_cms/website.py index fe3f877..45a4a6c 100644 --- a/aircox_cms/website.py +++ b/aircox_cms/website.py @@ -6,38 +6,46 @@ class Website: description = 'An aircox website' # public description (used in meta info) tags = 'aircox,radio,music' # public keywords (used in meta info) - logo = None - menus = None + styles = '' # relative url to stylesheet file + menus = None # list of menus + menu_layouts = ['top', 'left', # available positions + 'right', 'bottom', + 'header', 'footer'] router = None + @property + def urls (self): + return self.router.get_urlpatterns() + def __init__ (self, **kwargs): self.__dict__.update(kwargs) if not self.router: self.router = routes.Router() def register_set (self, view_set): + """ + Register a ViewSet (or subclass) to the router, + and connect it to self. + """ view_set = view_set(website = self) self.router.register_set(view_set) def get_menu (self, position): + """ + Get an enabled menu by its position + """ for menu in self.menus: if menu.enabled and menu.position == position: + self.check_menu_tag(menu) return menu - def get_top_menu (self): - return self.get_menu('top') - - def get_left_menu (self): - return self.get_menu('left') - - def get_bottom_menu (self): - return self.get_menu('bottom') - - def get_right_menu (self): - return self.get_menu('right') - - @property - def urls (self): - return self.router.get_urlpatterns() + def check_menu_tag (self, menu): + """ + Update menu tag if it is a footer or a header + """ + if menu.position in ('footer','header'): + menu.tag = menu.position + if menu.position in ('left', 'right'): + menu.tag = 'side' diff --git a/website/static/website/colony.png b/website/static/website/colony.png new file mode 100644 index 0000000..80938a2 Binary files /dev/null and b/website/static/website/colony.png differ diff --git a/website/static/website/logo.png b/website/static/website/logo.png new file mode 100644 index 0000000..89fa4c2 Binary files /dev/null and b/website/static/website/logo.png differ diff --git a/website/static/website/styles.css b/website/static/website/styles.css new file mode 100644 index 0000000..7117ce0 --- /dev/null +++ b/website/static/website/styles.css @@ -0,0 +1,44 @@ + +body { + background-color: #F2F2F2; + font-family: "Myriad Pro",Calibri,Helvetica,Arial,sans-serif; +} + + +h1, h2, h3 { + font-family: "Myriad Pro",Calibri,Helvetica,Arial,sans-serif +} + + +nav.menu { + padding: 0.5em; +} + +nav.menu_top { + background-color: #212121; + color: #007EDF; + font-size: 1.1em; +} + + + header { + height: 9em; + } + + header img { + height: 100%; + float: left; + } + + header .colony { + position: fixed; + top: 0em; + right: 0; + z-index: -1; + } + +.page { + width: 100%; + background-color: rgba(255, 255, 255, 0.8); +} + diff --git a/website/urls.py b/website/urls.py index 7743b78..8026014 100644 --- a/website/urls.py +++ b/website/urls.py @@ -4,7 +4,7 @@ from website.models import * from website.views import * from aircox_cms.models import Article -from aircox_cms.views import ViewSet, Menu, Section +from aircox_cms.views import Menu, Section, Sections, ViewSet from aircox_cms.routes import * from aircox_cms.website import Website @@ -17,8 +17,8 @@ class ProgramSet (ViewSet): DateRoute, ] - detail_sections = [ - ScheduleSection + detail_sections = ViewSet.detail_sections + [ + ScheduleSection, ] class EpisodeSet (ViewSet): @@ -42,17 +42,33 @@ class ArticleSet (ViewSet): website = Website( name = 'RadioCampus', + styles = 'website/styles.css', + menus = [ + Menu( + position = 'header', + sections = [ + Sections.Image(url = 'website/logo.png'), + Sections.Image(url = 'website/colony.png', classes='colony'), + ] + ), + Menu( position = 'top', sections = [ Section(content = "Radio Campus le SITE") ] - ) + ), + + Menu( + position = 'left', + sections = [ + Section(content = 'loool
blob') + ], + ), ], ) - website.register_set(ProgramSet) website.register_set(EpisodeSet) website.register_set(ArticleSet) diff --git a/website/views.py b/website/views.py index dd7c3b3..9aa4676 100644 --- a/website/views.py +++ b/website/views.py @@ -6,21 +6,21 @@ from django.core import serializers from django.utils.translation import ugettext as _, ugettext_lazy import aircox_programs.models as programs -from aircox_cms.views import ListSection +from aircox_cms.views import Sections -class PlaylistSection (ListSection): +class PlayListSection (Sections.List): title = _('Playlist') def get_object_list (self): tracks = programs.Track.objects \ .filter(episode = self.object) \ .order_by('position') - return [ ListSection.Item(None, track.title, track.artist) + return [ Sections.List.Item(None, track.title, track.artist) for track in tracks ] -class ScheduleSection (ListSection): +class ScheduleSection (Sections.List): title = _('Schedule') def get_object_list (self): @@ -28,7 +28,7 @@ class ScheduleSection (ListSection): .filter(program = self.object.pk) return [ - ListSection.Item(None, sched.get_frequency_display(), + Sections.List.Item(None, sched.get_frequency_display(), _('rerun') if sched.rerun else None) for sched in scheds ]