move files

This commit is contained in:
bkfox
2015-12-22 08:37:17 +01:00
parent 0511ec5bc3
commit 6bb13904da
55 changed files with 0 additions and 0 deletions

0
cms/__init__.py Normal file
View File

3
cms/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

236
cms/models.py Normal file
View File

@ -0,0 +1,236 @@
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from django.db.models.signals import post_init, post_save, post_delete
from django.dispatch import receiver
from taggit.managers import TaggableManager
class Post (models.Model):
thread_type = models.ForeignKey(
ContentType,
on_delete=models.SET_NULL,
blank = True, null = True
)
thread_pk = models.PositiveIntegerField(
blank = True, null = True
)
thread = GenericForeignKey('thread_type', 'thread_pk')
author = models.ForeignKey(
User,
verbose_name = _('author'),
blank = True, null = True,
)
date = models.DateTimeField(
_('date'),
default = timezone.datetime.now
)
published = models.BooleanField(
verbose_name = _('public'),
default = True
)
title = models.CharField (
_('title'),
max_length = 128,
)
content = models.TextField (
_('description'),
blank = True, null = True
)
image = models.ImageField(
blank = True, null = True
)
tags = TaggableManager(
_('tags'),
blank = True,
)
def detail_url (self):
return reverse(self._meta.verbose_name.lower() + '_detail',
kwargs = { 'pk': self.pk,
'slug': slugify(self.title) })
class Meta:
abstract = True
class Article (Post):
static_page = models.BooleanField(
_('static page'),
default = False,
)
focus = models.BooleanField(
_('article is focus'),
default = False,
)
class Meta:
verbose_name = _('Article')
verbose_name_plural = _('Articles')
class RelatedPostBase (models.base.ModelBase):
"""
Metaclass for RelatedPost children.
"""
registry = {}
@classmethod
def register (cl, key, model):
"""
Register a model and return the key under which it is registered.
Raise a ValueError if another model is yet associated under this key.
"""
if key in cl.registry and cl.registry[key] is not model:
raise ValueError('A model has yet been registered with "{}"'
.format(key))
cl.registry[key] = model
return key
@classmethod
def check_thread_mapping (cl, relation, model, field):
"""
Add information related to the mapping 'thread' info.
"""
if not field:
return
parent_model = model._meta.get_field(field).rel.to
thread_model = cl.registry.get(parent_model)
if not thread_model:
raise ValueError('no registered RelatedPost for the model {}'
.format(model.__name__))
relation.thread_model = thread_model
def __new__ (cl, name, bases, attrs):
rel = attrs.get('Relation')
rel = (rel and rel.__dict__) or {}
related_model = rel.get('model')
if related_model:
attrs['related'] = models.ForeignKey(related_model)
if not '__str__' in attrs:
attrs['__str__'] = lambda self: str(self.related)
if name is not 'RelatedPost':
_relation = RelatedPost.Relation()
_relation.__dict__.update(rel)
mapping = rel.get('mapping')
cl.check_thread_mapping(
_relation,
related_model,
mapping and mapping.get('thread')
)
attrs['_relation'] = _relation
model = super().__new__(cl, name, bases, attrs)
cl.register(related_model, model)
return model
class RelatedPost (Post, metaclass = RelatedPostBase):
"""
Use this post to generate Posts that are related to an external model. An
extra field "related" will be generated, and some bindings are possible to
update te related object on save if desired;
This is done through a class name Relation inside the declaration of the new
model.
"""
related = None
class Meta:
abstract = True
class Relation:
"""
Relation descriptor used to generate and manage the related object.
* model: model of the related object
* mapping: values that are bound between the post and the related
object. When the post is saved, these fields are updated on it.
It is a dict of { post_attr: rel_attr }
If there is a post_attr "thread", the corresponding rel_attr is used
to update the post thread to the correct Post model (in order to
establish a parent-child relation between two models)
* thread_model: generated by the metaclass that point to the
RelatedModel class related to the model that is the parent of
the current related one.
"""
model = None
mapping = None # values to map { post_attr: rel_attr }
thread = None
thread_model = None
def get_attribute (self, attr):
attr = self._relation.mappings.get(attr)
return self.related.__dict__[attr] if attr else None
def update_thread_mapping (self, save = True):
"""
Update the parent object designed by Relation.mapping.thread if the
type matches to the one related of the current instance's thread.
If there is no thread assigned to self, set it to the parent of the
related object.
"""
relation = self._relation
thread_model = relation.thread_model
if not thread_model:
return
# self.related.parent -> self.thread
rel_parent = relation.mapping.get('thread')
if not self.thread:
rel_parent = getattr(self.related, rel_parent)
thread = thread_model.objects.filter(related = rel_parent)
if thread.count():
self.thread = thread[0]
if save:
self.save()
return
# self.thread -> self.related.parent
if thread_model is not self.thread_type.model_class():
return
setattr(self.related, rel_parent, self.thread.related)
if save:
self.save()
def update_mapping (self):
relation = self._relation
mapping = relation.mapping
if not mapping:
return
related = self.related
related.__dict__.update({
rel_attr: self.__dict__[attr]
for attr, rel_attr in mapping.items()
if attr is not 'thread' and attr in self.__dict__
})
self.update_thread_mapping(save = False)
related.save()
def save (self, *args, **kwargs):
if not self.title and self.related:
self.title = self.get_attribute('title')
self.update_mapping()
super().save(*args, **kwargs)

173
cms/routes.py Normal file
View File

@ -0,0 +1,173 @@
from django.conf.urls import url
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_lazy
from website.models import *
from website.views import *
class Router:
registry = []
def register (self, route):
if not route in self.registry:
self.registry.append(route)
def register_set (self, view_set):
for url in view_set.urls:
self.register(url)
def unregister (self, route):
self.registry.remove(route)
def get_urlpatterns (self):
return [ url for url in self.registry ]
class Route:
"""
Base class for routing. Given a model, we generate url specific for each
route type. The generated url takes this form:
model_name + '/' + route_name + '/' + '/'.join(route_url_args)
Where model_name by default is the given model's verbose_name (uses plural if
Route is for a list).
The given view is considered as a django class view, and has view_
"""
name = None # route name
url_args = [] # arguments passed from the url [ (name : regex),... ]
@classmethod
def get_queryset (cl, website, model, request, **kwargs):
"""
Called by the view to get the queryset when it is needed
"""
pass
@classmethod
def get_object (cl, website, model, request, **kwargs):
"""
Called by the view to get the object when it is needed
"""
pass
@classmethod
def get_title (cl, model, request, **kwargs):
return ''
@classmethod
def get_view_name (cl, name):
return name + '_' + cl.name
@classmethod
def as_url (cl, name, model, view, view_kwargs = None):
pattern = '^{}/{}'.format(name, cl.name)
if cl.url_args:
url_args = '/'.join([
'(?P<{}>{}){}'.format(
arg, expr,
(optional and optional[0] and '?') or ''
)
for arg, expr, *optional in cl.url_args
])
pattern += '/' + url_args
pattern += '/?$'
kwargs = {
'route': cl,
}
if view_kwargs:
kwargs.update(view_kwargs)
return url(pattern, view, kwargs = kwargs,
name = cl.get_view_name(name))
class DetailRoute (Route):
name = 'detail'
url_args = [
('pk', '[0-9]+'),
('slug', '(\w|-|_)+', True),
]
@classmethod
def get_object (cl, website, model, request, pk, **kwargs):
return model.objects.get(pk = int(pk))
class AllRoute (Route):
name = 'all'
@classmethod
def get_queryset (cl, website, model, request, **kwargs):
return model.objects.all()
@classmethod
def get_title (cl, model, request, **kwargs):
return _('All %(model)s') % {
'model': model._meta.verbose_name_plural
}
class ThreadRoute (Route):
"""
Select posts using by their assigned thread.
- "thread_model" can be a string with the name of a registered item on
website or a model.
- "pk" is the pk of the thread item.
"""
name = 'thread'
url_args = [
('thread_model', '(\w|_|-)+'),
('pk', '[0-9]+'),
]
@classmethod
def get_queryset (cl, website, model, request, thread_model, pk, **kwargs):
if type(thread_model) is str:
thread_model = website.registry.get(thread_model)
if not thread_model:
return
thread_model = ContentType.objects.get_for_model(thread_model)
return model.objects.filter(
thread_type = thread_model,
thread_pk = int(pk)
)
class DateRoute (Route):
name = 'date'
url_args = [
('year', '[0-9]{4}'),
('month', '[0-9]{2}'),
('day', '[0-9]{1,2}'),
]
@classmethod
def get_queryset (cl, website, model, request, year, month, day, **kwargs):
return model.objects.filter(
date__year = int(year),
date__month = int(month),
date__day = int(day),
)
class SearchRoute (Route):
name = 'search'
@classmethod
def get_queryset (cl, website, model, request, q, **kwargs):
qs = model.objects
for search_field in model.search_fields or []:
r = model.objects.filter(**{ search_field + '__icontains': q })
if qs: qs = qs | r
else: qs = r
qs.distinct()
return qs

View File

@ -0,0 +1,50 @@
body {
padding: 0;
margin: 0;
}
.page {
display: flex;
}
.page .menu {
width: 20em;
}
.page .menu_left { margin-right: 0.5em; }
.page .menu_right { margin-left: 0.5em; }
.page main {
flex-grow: 1;
}
.section {
vertical-align: top;
}
main .section {
width: calc(50% - 2em);
display: inline-block;
padding: 0.5em;
}
main .section .section_content {
font-size: 0.95em;
}
main .section h1 {
font-size: 1.2em;
}
main .section * {
max-width: 100%;
}
.post_item {
display: block;
}

View File

@ -0,0 +1,51 @@
{% extends "admin/base.html" %}
{% block extrahead %}
{% include 'autocomplete_light/static.html' %}
<style>
/** autocomplete override **/
.autocomplete-light-widget .deck [data-value] .remove {
float: right;
}
.autocomplete-light-widget .deck [data-value],
.autocomplete-light-widget .deck .choice {
display: block;
}
.control-group .add-related,
.inline-group .add-related {
vertical-align: bottom;
}
/** suit **/
.controls textarea,
.controls .vTextField {
width: calc(100% - 10px);
}
/** grappelli **/
.grp-autocomplete-wrapper-m2m:focus, .grp-autocomplete-wrapper-m2m.grp-state-focus,
.grp-autocomplete-wrapper-m2m {
background: rgba(255, 255, 255, 0.2);
border: none;
box-shadow: none;
}
.grp-autocomplete-wrapper-m2m ul.grp-repr li.grp-search {
background-color: #FDFDFD;
border: 1px solid #CCC;
}
.grp-autocomplete-wrapper-m2m ul.grp-repr li {
float: none;
display: block;
}
</style>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% block pre_title %}
{% endblock %}
{% block title %}
{% endblock %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,9 @@
<{{ tag }} class="section {{ classes }}"
{% for key, value in attrs.items %}{{ key }} = "{{ value|addslashes }}"
{% endfor %} >
{% block content %}
{{ content|safe }}
{% endblock %}
</{{ tag }}>

View File

@ -0,0 +1,71 @@
{% load staticfiles %}
<html>
<head>
{# FIXME: page tags #}
<meta charset="utf-8">
<meta name="application-name" content="aircox-cms">
<meta name="description" content="{{ website.description }}">
<meta name="keywords" content="{{ website.tags }}">
<link rel="stylesheet" href="{% static "aircox_cms/styles.css" %}" type="text/css">
{% if website.styles %}
<link rel="stylesheet" href="{% static website.styles %}" type="text/css">
{% endif %}
<title>{{ website.name }} {% if title %}- {{ title }} {% endif %}</title>
</head>
<body>
{% block header %}
{% if menus.header %}
{{ menus.header|safe }}
{% endif %}
{% endblock %}
<div class="page-container">
{% if menus.top %}
{{ menus.top|safe }}
{% endif %}
<div class="page">
{% if menus.left %}
{{ menus.left|safe }}
{% endif %}
<main>
{% block pre_title %}
{% endblock %}
<h1>
{% block title %}
{{ title }}
{% endblock %}
</h1>
{% block post_title %}
{% endblock %}
<div class="content">
{% block content %}
{% endblock %}
</div>
</main>
{% if menus.right %}
{{ menus.right|safe }}
{% endif %}
</div>
{% if menus.page_bottom %}
{{ menus.page_bottom|safe }}
{% endif %}
</div>
{% block footer %}
{% if menus.footer %}
{{ menus.footer|safe }}
{% endif %}
{% endblock %}
{% if menus.bottom %}
{{ menus.bottom|safe }}
{% endif %}
</body>
</html>

View File

@ -0,0 +1,19 @@
{% extends embed|yesno:"aircox_cms/base_content.html,aircox_cms/base_site.html" %}
{% block title %}
{{ object.title }}
{% endblock %}
{% block pre_title %}
<time datetime="{{ object.date }}">
{{ object.date|date:'l d F Y' }},
{{ object.date|time:'H\hi' }}
</time>
{% endblock %}
{% block content %}
{% for section in sections %}
{{ section|safe }}
{% endfor %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{# Used for embedded content #}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,65 @@
{% extends embed|yesno:"aircox_cms/base_content.html,aircox_cms/base_site.html" %}
{% load i18n %}
{% load thumbnail %}
{% block content %}
<div class="post_list {{ classes }} {% if embed %}embed{% endif %}">
{% for post in object_list %}
<a class="post_item"
href="{{ post.detail_url }}">
{% if 'date' in view.fields or 'time' in view.fields %}
<time datetime="{{ post.date }}" class="post_datetime">
{% if 'date' in view.fields %}
<span class="post_date">
{{ post.date|date:'D. d F' }}
</span>
{% endif %}
{% if 'time' in view.fields %}
<span class="post_time">
{{ post.date|date:'H:i' }}
</span>
{% endif %}
</time>
{% endif %}
{% if 'image' in view.fields %}
<img src="{% thumbnail post.image view.icon_size crop %}" class="post_image">
{% endif %}
{% if 'title' in view.fields %}
<h3 class="post_title">{{ post.title }}</h3>
{% endif %}
{% if 'content' in view.fields %}
<div class="post_content">
{{ post.content|safe|striptags|truncatechars:"64" }}
</div>
{% endif %}
</a>
{% endfor %}
</div>
<nav>
{% if embed %}
{% with view.get_url as list_url %}
{% if list_url %}
<a href="{{list_url}}" title="More elements">&#8690;</a>
{% endif %}
{% endwith %}
{% else %}
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
{% endif %}
</nav>
{% endblock %}

View File

@ -0,0 +1,10 @@
<{{ tag }} class="menu menu_{{ position }} {{ classes }}" {% if name %}
name="{{ name }}"
id="{{ name }}"
{% endif %}>
{% for section in sections %}
{{ section|safe }}
{% endfor %}
</{{ tag }}>

View File

@ -0,0 +1,34 @@
{% extends "aircox_cms/base_section.html" %}
{% block content %}
{% if title %}
<h1>
{% block section_title %}
{{ title }}
{% endblock %}
</h1>
{% endif %}
{% if header %}
<header class="section_header">
{% block section_header %}
{{ header }}
{% endblock %}
</header>
{% endif %}
<div class="section_content">
{% block section_content %}
{{ content|safe }}
{% endblock %}
</div>
{% if footer %}
<footer class="section_footer">
{% block section_footer %}
{{ footer }}
{% endblock %}
</footer>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends "aircox_cms/section.html" %}
{% load thumbnail %}
{% block section_content %}
<ul style="padding:0; margin:0">
{% for item in object_list %}
<li>
{% if item.url %}
<a href="{{item.url}}">
{% endif %}
{% if use_icons and item.icon %}
<img src="{% thumbnail item.icon icon_size crop %}" class="icon">
{% endif %}
{{ item.title }}
{% if item.text %}
<small>{{ item.text }}</small>
{% endif %}
{% if item.url %}
</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endblock %}

3
cms/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

21
cms/utils.py Normal file
View File

@ -0,0 +1,21 @@
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
def get_url (website, route, model, kwargs):
name = website.name_of_model(model)
if not name:
return
name = route.get_view_name(name)
return reverse(name, kwargs = kwargs)
def filter_thread (qs, object):
model_type = ContentType.objects.get_for_model(object.__class__)
return qs.filter(
thread_pk = object.pk,
thread_type__pk = model_type.pk
)

401
cms/views.py Normal file
View File

@ -0,0 +1,401 @@
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.paginator import Paginator
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
import aircox.cms.utils as utils
class PostBaseView:
website = None # corresponding website
title = '' # title of the page
embed = False # page is embed (if True, only post content is printed
classes = '' # extra classes for the content
def get_base_context (self, **kwargs):
"""
Return a context with all attributes of this classe plus 'view' set
to self.
"""
context = {
k: getattr(self, k)
for k, v in PostBaseView.__dict__.items()
if not k.startswith('__')
}
if not self.embed:
context['menus'] = {
k: v.get(self.request, website = self.website, **kwargs)
for k, v in {
k: self.website.get_menu(k)
for k in self.website.menu_layouts
}.items() if v
}
context['view'] = self
return context
class PostListView (PostBaseView, ListView):
"""
List view for posts and children
"""
class Query:
"""
Request availables parameters
"""
embed = False # view is embedded (only the list is shown)
exclude = None # exclude item of this id
order = 'desc' # order of the list when query
fields = None # fields to show
page = 1 # page number
q = None # query search
def __init__ (self, query):
if query:
self.update(query)
def update (self, query):
my_class = self.__class__
if type(query) is my_class:
self.__dict__.update(query.__dict__)
return
self.__dict__.update(query)
template_name = 'aircox/cms/list.html'
allow_empty = True
paginate_by = 50
model = None
route = None
query = None
fields = [ 'date', 'time', 'image', 'title', 'content' ]
icon_size = '64x64'
def __init__ (self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.query = PostListView.Query(self.query)
def dispatch (self, request, *args, **kwargs):
self.route = self.kwargs.get('route') or self.route
return super().dispatch(request, *args, **kwargs)
def get_queryset (self):
if self.route:
qs = self.route.get_queryset(self.website, self.model, self.request,
**self.kwargs)
else:
qs = self.queryset or self.model.objects.all()
query = self.query
query.update(self.request.GET)
if query.exclude:
qs = qs.exclude(id = int(exclude))
if query.embed:
self.embed = True
if query.order == 'asc':
qs.order_by('date', 'id')
else:
qs.order_by('-date', '-id')
if query.fields:
self.fields = [
field for field in query.fields
if field in self.__class__.fields
]
return qs
def get_context_data (self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.get_base_context(**kwargs))
context.update({
'title': self.get_title(),
})
return context
def get_title (self):
if self.title:
return self.title
title = self.route and self.route.get_title(self.model, self.request,
**self.kwargs)
return title
def get_url (self):
if self.route:
return utils.get_urls(self.website, self.route,
self.model, self.kwargs)
return ''
class PostDetailView (DetailView, PostBaseView):
"""
Detail view for posts and children
"""
template_name = 'aircox/cms/detail.html'
sections = []
def __init__ (self, sections = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sections = sections or []
def get_queryset (self):
if self.request.GET.get('embed'):
self.embed = True
if self.model:
return super().get_queryset().filter(published = True)
return []
def get_object (self, **kwargs):
if self.model:
object = super().get_object(**kwargs)
if object.published:
return object
return None
def get_context_data (self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.get_base_context())
context.update({
'sections': [
section.get(self.request, website = self.website, **kwargs)
for section in self.sections
]
})
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, website = self.website,
object = None, **kwargs)
for section in self.sections
]
}
def get (self, request, website, **kwargs):
self.request = request
self.website = website
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'
kwargs = None # kwargs argument passed to get
tag = 'div' # container tags
classes = '' # container classes
attrs = '' # container extra attributes
content = '' # content
visible = True # if false renders an empty string
def get_context_data (self):
return {
'view': self,
'tag': self.tag,
'classes': self.classes,
'attrs': self.attrs,
'visible': self.visible,
'content': self.content,
}
def get (self, request, website, **kwargs):
self.request = request
self.website = website
self.kwargs = kwargs
context = self.get_context_data()
# get_context_data may call extra function that can change visibility
if self.visible:
return render_to_string(self.template_name, context)
return ''
class Section (BaseSection):
"""
A Section that can be related to an object.
"""
template_name = 'aircox/cms/section.html'
object = None
object_required = False
title = ''
header = ''
footer = ''
def get_context_data (self):
context = super().get_context_data()
context.update({
'title': self.title,
'header': self.header,
'footer': self.footer,
})
return context
def get (self, request, object = None, **kwargs):
self.object = object or self.object
if self.object_required and not self.object:
raise ValueError('object is required by this Section but not given')
return super().get(request, **kwargs)
class Sections:
class Image (BaseSection):
"""
Render an image with the given relative url.
"""
url = None
@property
def content (self):
return '<img src="{}">'.format(
static(self.url),
)
class PostContent (Section):
"""
Render the content of the Post (format the text a bit and escape HTML
tags).
"""
@property
def content (self):
content = escape(self.object.content)
content = re.sub(r'(^|\n\n)((\n?[^\n])+)', r'<p>\2</p>', content)
content = re.sub(r'\n', r'<br>', content)
return content
class PostImage (Section):
"""
Render the image of the Post
"""
@property
def content (self):
return '<img src="{}" class="post_image">'.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
url = None
def __init__ (self, icon, title = None, text = None, url = None):
self.icon = icon
self.title = title
self.text = text
hide_empty = False # hides the section if the list is empty
use_icons = True # print icons
paginate_by = 0 # number of items
icon_size = '32x32' # icons size
template_name = 'aircox/cms/section_list.html'
def get_object_list (self):
return []
def get_context_data (self, **kwargs):
object_list = self.get_object_list()
self.visibility = True
if not object_list and hide_empty:
self.visibility = False
context = super().get_context_data(**kwargs)
context.update({
'classes': context.get('classes') + ' section_list',
'icon_size': self.icon_size,
'object_list': object_list,
'paginate_by': self.paginate_by,
})
return context
class Urls (List):
"""
Render a list of urls of targets that are Posts
"""
classes = 'section_urls'
targets = None
def get_object_list (self):
return [
List.Item(
target.image or None,
target.title,
url = target.detail_url(),
)
for target in self.targets
]
class Posts (PostBaseView, Section):
"""
Render a list using PostListView's template.
"""
embed = True
paginate_by = 5
icon_size = '64x64'
fields = [ 'date', 'time', 'image', 'title', 'content' ]
def get_url (self):
return ''
def get_object_list (self):
return []
def render_list (self):
self.embed = True
context = self.get_base_context(**self.kwargs)
context.update({
'object_list': self.get_object_list(),
'embed': True,
'paginate_by': self.paginate_by,
})
return render_to_string(PostListView.template_name, context)
def get_context_data (self, **kwargs):
context = super().get_context_data(**kwargs)
context['content'] = self.render_list()
return context

101
cms/website.py Normal file
View File

@ -0,0 +1,101 @@
import aircox.cms.routes as routes
import aircox.cms.views as views
class Website:
name = ''
domain = ''
description = 'An aircox website' # public description (used in meta info)
tags = 'aircox,radio,music' # public keywords (used in meta info)
styles = '' # relative url to stylesheet file
menus = None # list of menus
menu_layouts = ['top', 'left', # available positions
'right', 'bottom',
'header', 'footer']
router = None
urls = []
registry = {}
def __init__ (self, **kwargs):
self.registry = {}
self.urls = []
self.__dict__.update(kwargs)
def name_of_model (self, model):
for name, _model in self.registry.items():
if model is _model:
return name
def register_model (self, name, model):
"""
Register a model and return the name under which it is registered.
Raise a ValueError if another model is yet associated under this name.
"""
if name in self.registry and self.registry[name] is not model:
raise ValueError('A model has yet been registered under "{}"'
.format(name))
self.registry[name] = model
return name
def register_detail (self, name, model, view = views.PostDetailView,
**view_kwargs):
"""
Register a model and the detail view
"""
name = self.register_model(name, model)
view = view.as_view(
website = self,
model = model,
**view_kwargs,
)
self.urls.append(routes.DetailRoute.as_url(name, model, view))
self.registry[name] = model
def register_list (self, name, model, view = views.PostListView,
routes = [], **view_kwargs):
"""
Register a model and the given list view using the given routes
"""
name = self.register_model(name, model)
view = view.as_view(
website = self,
model = model,
**view_kwargs
)
self.urls += [ route.as_url(name, model, view) for route in routes ]
self.registry[name] = model
def register (self, name, model, sections = None, routes = None,
list_kwargs = {}, detail_kwargs = {}):
if sections:
self.register_detail(
name, model,
sections = sections,
**detail_kwargs
)
if routes:
self.register_list(
name, model,
routes = routes,
**list_kwargs
)
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 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'