update sections, work a bit on style
This commit is contained in:
parent
cfdc9b6de0
commit
cde58334bd
|
@ -3,8 +3,8 @@ Platform to manage radio programs, schedules, cms, etc. -- main test repo
|
||||||
|
|
||||||
# Applications
|
# Applications
|
||||||
* **programs**: programs, episodes, schedules, sounds and tracks;
|
* **programs**: programs, episodes, schedules, sounds and tracks;
|
||||||
* **streams**: streams and diffusions, links with LiquidSoap;
|
* **cms**: cms renderer
|
||||||
* **website**: website rendering, using models defined by the previous apps;
|
* **website**: the website using the cms and the programs
|
||||||
|
|
||||||
|
|
||||||
# Code and names conventions and uses
|
# Code and names conventions and uses
|
||||||
|
|
|
@ -12,57 +12,18 @@ from django.dispatch import receiver
|
||||||
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
class Thread (models.Model):
|
|
||||||
"""
|
|
||||||
Object assigned to any Post and children that can be used to have parent and
|
|
||||||
children relationship between posts of different kind.
|
|
||||||
|
|
||||||
We use this system instead of having directly a GenericForeignKey into the
|
|
||||||
Post because it avoids having to define the relationship with two models for
|
|
||||||
routing (one for the parent and one for the children).
|
|
||||||
"""
|
|
||||||
post_type = models.ForeignKey(ContentType)
|
|
||||||
post_id = models.PositiveIntegerField()
|
|
||||||
post = GenericForeignKey('post_type', 'post_id')
|
|
||||||
|
|
||||||
__initial_post = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __get_query_set (cl, function, model, post, kwargs):
|
|
||||||
if post:
|
|
||||||
model = type(post)
|
|
||||||
kwargs['post_id'] = post.id
|
|
||||||
|
|
||||||
kwargs['post_type'] = ContentType.objects.get_for_model(model)
|
|
||||||
return getattr(cl.objects, function)(**kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get (cl, model = None, post = None, **kwargs):
|
|
||||||
return cl.__get_query_set('get', model, post, kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def filter (cl, model = None, post = None, **kwargs):
|
|
||||||
return self.__get_query_set('filter', model, post, kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def exclude (cl, model = None, post = None, **kwargs):
|
|
||||||
return self.__get_query_set('exclude', model, post, kwargs)
|
|
||||||
|
|
||||||
def save (self, *args, **kwargs):
|
|
||||||
self.post = self.__initial_post or self.post
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def __str__ (self):
|
|
||||||
return self.post_type.name + ': ' + str(self.post)
|
|
||||||
|
|
||||||
|
|
||||||
class Post (models.Model):
|
class Post (models.Model):
|
||||||
thread = models.ForeignKey(
|
thread_type = models.ForeignKey(
|
||||||
Thread,
|
ContentType,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
blank = True, null = True,
|
blank = True, null = True
|
||||||
help_text = _('the publication is posted on this thread'),
|
|
||||||
)
|
)
|
||||||
|
thread_pk = models.PositiveIntegerField(
|
||||||
|
blank = True, null = True
|
||||||
|
)
|
||||||
|
thread = GenericForeignKey('thread_type', 'thread_pk')
|
||||||
|
|
||||||
author = models.ForeignKey(
|
author = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
verbose_name = _('author'),
|
verbose_name = _('author'),
|
||||||
|
@ -94,7 +55,7 @@ class Post (models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
def detail_url (self):
|
def detail_url (self):
|
||||||
return reverse(self._meta.verbose_name_plural.lower() + '_detail',
|
return reverse(self._meta.verbose_name.lower() + '_detail',
|
||||||
kwargs = { 'pk': self.pk,
|
kwargs = { 'pk': self.pk,
|
||||||
'slug': slugify(self.title) })
|
'slug': slugify(self.title) })
|
||||||
|
|
||||||
|
@ -151,7 +112,6 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
||||||
mapping = None # dict of related mapping values
|
mapping = None # dict of related mapping values
|
||||||
bind_mapping = False # update fields of related data on save
|
bind_mapping = False # update fields of related data on save
|
||||||
|
|
||||||
|
|
||||||
def get_attribute (self, attr):
|
def get_attribute (self, attr):
|
||||||
attr = self._relation.mappings.get(attr)
|
attr = self._relation.mappings.get(attr)
|
||||||
return self.related.__dict__[attr] if attr else None
|
return self.related.__dict__[attr] if attr else None
|
||||||
|
@ -163,32 +123,10 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
||||||
if self._relation.bind_mapping:
|
if self._relation.bind_mapping:
|
||||||
self.related.__dict__.update({
|
self.related.__dict__.update({
|
||||||
rel_attr: self.__dict__[attr]
|
rel_attr: self.__dict__[attr]
|
||||||
for attr, rel_attr in self.Relation.mapping
|
for attr, rel_attr in self.Relation.mapping.items()
|
||||||
})
|
})
|
||||||
|
|
||||||
self.related.save()
|
self.related.save()
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_init)
|
|
||||||
def on_thread_init (sender, instance, **kwargs):
|
|
||||||
if not issubclass(Thread, sender):
|
|
||||||
return
|
|
||||||
instance.__initial_post = instance.post
|
|
||||||
|
|
||||||
@receiver(post_save)
|
|
||||||
def on_post_save (sender, instance, created, *args, **kwargs):
|
|
||||||
if not issubclass(sender, Post) or not created:
|
|
||||||
return
|
|
||||||
|
|
||||||
thread = Thread(post = instance)
|
|
||||||
thread.save()
|
|
||||||
|
|
||||||
@receiver(post_delete)
|
|
||||||
def on_post_delete (sender, instance, using, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
Thread.get(sender, post = instance).delete()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
4
aircox_cms/requirements.txt
Normal file
4
aircox_cms/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Django>=1.9.0
|
||||||
|
django-taggit>=0.12.1
|
||||||
|
easy_thumbnails
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
|
@ -27,9 +28,9 @@ class Route:
|
||||||
"""
|
"""
|
||||||
Base class for routing. Given a model, we generate url specific for each
|
Base class for routing. Given a model, we generate url specific for each
|
||||||
route type. The generated url takes this form:
|
route type. The generated url takes this form:
|
||||||
base_name + '/' + route_name + '/' + '/'.join(route_url_args)
|
model_name + '/' + route_name + '/' + '/'.join(route_url_args)
|
||||||
|
|
||||||
Where base_name by default is the given model's verbose_name (uses plural if
|
Where model_name by default is the given model's verbose_name (uses plural if
|
||||||
Route is for a list).
|
Route is for a list).
|
||||||
|
|
||||||
The given view is considered as a django class view, and has view_
|
The given view is considered as a django class view, and has view_
|
||||||
|
@ -38,14 +39,14 @@ class Route:
|
||||||
url_args = [] # arguments passed from the url [ (name : regex),... ]
|
url_args = [] # arguments passed from the url [ (name : regex),... ]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset (cl, model, request, **kwargs):
|
def get_queryset (cl, website, model, request, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called by the view to get the queryset when it is needed
|
Called by the view to get the queryset when it is needed
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_object (cl, model, request, **kwargs):
|
def get_object (cl, website, model, request, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called by the view to get the object when it is needed
|
Called by the view to get the object when it is needed
|
||||||
"""
|
"""
|
||||||
|
@ -56,10 +57,8 @@ class Route:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def as_url (cl, model, view, view_kwargs = None):
|
def as_url (cl, model, model_name, view, view_kwargs = None):
|
||||||
base_name = model._meta.verbose_name_plural.lower()
|
pattern = '^{}/{}'.format(model_name, cl.name)
|
||||||
|
|
||||||
pattern = '^{}/{}'.format(base_name, cl.name)
|
|
||||||
if cl.url_args:
|
if cl.url_args:
|
||||||
url_args = '/'.join([
|
url_args = '/'.join([
|
||||||
'(?P<{}>{}){}'.format(
|
'(?P<{}>{}){}'.format(
|
||||||
|
@ -78,7 +77,7 @@ class Route:
|
||||||
kwargs.update(view_kwargs)
|
kwargs.update(view_kwargs)
|
||||||
|
|
||||||
return url(pattern, view, kwargs = kwargs,
|
return url(pattern, view, kwargs = kwargs,
|
||||||
name = base_name + '_' + cl.name)
|
name = model_name + '_' + cl.name)
|
||||||
|
|
||||||
|
|
||||||
class DetailRoute (Route):
|
class DetailRoute (Route):
|
||||||
|
@ -89,7 +88,7 @@ class DetailRoute (Route):
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_object (cl, model, request, pk, **kwargs):
|
def get_object (cl, website, model, request, pk, **kwargs):
|
||||||
return model.objects.get(pk = int(pk))
|
return model.objects.get(pk = int(pk))
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,7 +96,7 @@ class AllRoute (Route):
|
||||||
name = 'all'
|
name = 'all'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset (cl, model, request, **kwargs):
|
def get_queryset (cl, website, model, request, **kwargs):
|
||||||
return model.objects.all()
|
return model.objects.all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -108,14 +107,32 @@ class AllRoute (Route):
|
||||||
|
|
||||||
|
|
||||||
class ThreadRoute (Route):
|
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'
|
name = 'thread'
|
||||||
url_args = [
|
url_args = [
|
||||||
|
('thread_model', '(\w|_|-)+'),
|
||||||
('pk', '[0-9]+'),
|
('pk', '[0-9]+'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset (cl, model, request, pk, **kwargs):
|
def get_queryset (cl, website, model, request, thread_model, pk, **kwargs):
|
||||||
return model.objects.filter(thread__pk = int(pk))
|
if type(thread_model) is str:
|
||||||
|
thread_model = website.registry.get(thread_model).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):
|
class DateRoute (Route):
|
||||||
|
@ -127,7 +144,7 @@ class DateRoute (Route):
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset (cl, model, request, year, month, day, **kwargs):
|
def get_queryset (cl, website, model, request, year, month, day, **kwargs):
|
||||||
return model.objects.filter(
|
return model.objects.filter(
|
||||||
date__year = int(year),
|
date__year = int(year),
|
||||||
date__month = int(month),
|
date__month = int(month),
|
||||||
|
@ -139,7 +156,7 @@ class SearchRoute (Route):
|
||||||
name = 'search'
|
name = 'search'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset (cl, model, request, **kwargs):
|
def get_queryset (cl, website, model, request, **kwargs):
|
||||||
q = request.GET.get('q') or ''
|
q = request.GET.get('q') or ''
|
||||||
qs = model.objects
|
qs = model.objects
|
||||||
for search_field in model.search_fields or []:
|
for search_field in model.search_fields or []:
|
||||||
|
@ -151,4 +168,3 @@ class SearchRoute (Route):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,13 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.section {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
main .section {
|
main .section {
|
||||||
width: calc(50% - 1em);
|
width: calc(50% - 2em);
|
||||||
float: left;
|
display: inline-block;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,3 +43,8 @@ main .section {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.post_item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,23 +12,23 @@
|
||||||
<time datetime="{{ post.date }}" class="post_datetime">
|
<time datetime="{{ post.date }}" class="post_datetime">
|
||||||
{% if 'date' in view.fields %}
|
{% if 'date' in view.fields %}
|
||||||
<span class="post_date">
|
<span class="post_date">
|
||||||
{{ post.date|date:'D. d F' }},
|
{{ post.date|date:'D. d F' }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if 'time' in view.fields %}
|
{% if 'time' in view.fields %}
|
||||||
<span class="post_time">
|
<span class="post_time">
|
||||||
{{ post.date|date:'H:i' }},
|
{{ post.date|date:'H:i' }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</time>
|
</time>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if 'image' in view.fields %}
|
{% if 'image' in view.fields %}
|
||||||
<img src="{% thumbnail post.image "64x64" crop %}" class="post_image">
|
<img src="{% thumbnail post.image view.icon_size crop %}" class="post_image">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if 'title' in view.fields %}
|
{% if 'title' in view.fields %}
|
||||||
<h4 class="post_title">{{ post.title }}</h4>
|
<h3 class="post_title">{{ post.title }}</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if 'content' in view.fields %}
|
{% if 'content' in view.fields %}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
<ul style="padding:0; margin:0">
|
<ul style="padding:0; margin:0">
|
||||||
{% for item in object_list %}
|
{% for item in object_list %}
|
||||||
<li>
|
<li>
|
||||||
|
{% if item.url %}
|
||||||
|
<a href="{{item.url}}">
|
||||||
|
{% endif %}
|
||||||
{% if use_icons and item.icon %}
|
{% if use_icons and item.icon %}
|
||||||
<img src="{% thumbnail item.icon icon_size crop %}" class="icon">
|
<img src="{% thumbnail item.icon icon_size crop %}" class="icon">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -15,6 +18,9 @@
|
||||||
{% if item.text %}
|
{% if item.text %}
|
||||||
<small>{{ item.text }}</small>
|
<small>{{ item.text }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if item.url %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
@ -19,7 +18,7 @@ class PostBaseView:
|
||||||
embed = False # page is embed (if True, only post content is printed
|
embed = False # page is embed (if True, only post content is printed
|
||||||
classes = '' # extra classes for the content
|
classes = '' # extra classes for the content
|
||||||
|
|
||||||
def get_base_context (self):
|
def get_base_context (self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return a context with all attributes of this classe plus 'view' set
|
Return a context with all attributes of this classe plus 'view' set
|
||||||
to self.
|
to self.
|
||||||
|
@ -32,7 +31,7 @@ class PostBaseView:
|
||||||
|
|
||||||
if not self.embed:
|
if not self.embed:
|
||||||
context['menus'] = {
|
context['menus'] = {
|
||||||
k: v.get(self.request)
|
k: v.get(self.request, **kwargs)
|
||||||
for k, v in {
|
for k, v in {
|
||||||
k: self.website.get_menu(k)
|
k: self.website.get_menu(k)
|
||||||
for k in self.website.menu_layouts
|
for k in self.website.menu_layouts
|
||||||
|
@ -70,10 +69,12 @@ class PostListView (PostBaseView, ListView):
|
||||||
|
|
||||||
template_name = 'aircox_cms/list.html'
|
template_name = 'aircox_cms/list.html'
|
||||||
allow_empty = True
|
allow_empty = True
|
||||||
|
model = None
|
||||||
|
|
||||||
route = None
|
route = None
|
||||||
query = None
|
query = None
|
||||||
fields = [ 'date', 'time', 'image', 'title', 'content' ]
|
fields = [ 'date', 'time', 'image', 'title', 'content' ]
|
||||||
|
icon_size = '64x64'
|
||||||
|
|
||||||
def __init__ (self, *args, **kwargs):
|
def __init__ (self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -84,7 +85,11 @@ class PostListView (PostBaseView, ListView):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset (self):
|
def get_queryset (self):
|
||||||
qs = self.route.get_queryset(self.model, self.request, **self.kwargs)
|
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 = self.query
|
||||||
|
|
||||||
query.update(self.request.GET)
|
query.update(self.request.GET)
|
||||||
|
@ -156,7 +161,7 @@ class PostDetailView (DetailView, PostBaseView):
|
||||||
context.update(self.get_base_context())
|
context.update(self.get_base_context())
|
||||||
context.update({
|
context.update({
|
||||||
'sections': [
|
'sections': [
|
||||||
section.get(self.request, object = self.object)
|
section.get(self.request, **kwargs)
|
||||||
for section in self.sections
|
for section in self.sections
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -184,7 +189,7 @@ class Menu (View):
|
||||||
'classes': self.classes,
|
'classes': self.classes,
|
||||||
'position': self.position,
|
'position': self.position,
|
||||||
'sections': [
|
'sections': [
|
||||||
section.get(self.request, object = None)
|
section.get(self.request, object = None, **kwargs)
|
||||||
for section in self.sections
|
for section in self.sections
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -202,36 +207,48 @@ class BaseSection (View):
|
||||||
in order to have extra content about a post, or in menus.
|
in order to have extra content about a post, or in menus.
|
||||||
"""
|
"""
|
||||||
template_name = 'aircox_cms/base_section.html'
|
template_name = 'aircox_cms/base_section.html'
|
||||||
|
kwargs = None # kwargs argument passed to get
|
||||||
tag = 'div' # container tags
|
tag = 'div' # container tags
|
||||||
classes = '' # container classes
|
classes = '' # container classes
|
||||||
attrs = '' # container extra attributes
|
attrs = '' # container extra attributes
|
||||||
content = '' # content
|
content = '' # content
|
||||||
|
visible = True # if false renders an empty string
|
||||||
|
|
||||||
|
|
||||||
def get_context_data (self, **kwargs):
|
def get_context_data (self):
|
||||||
return {
|
return {
|
||||||
|
'view': self,
|
||||||
'tag': self.tag,
|
'tag': self.tag,
|
||||||
'classes': self.classes,
|
'classes': self.classes,
|
||||||
'attrs': self.attrs,
|
'attrs': self.attrs,
|
||||||
|
'visible': self.visible,
|
||||||
'content': self.content,
|
'content': self.content,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get (self, request, **kwargs):
|
def get (self, request, **kwargs):
|
||||||
self.request = request
|
self.request = request
|
||||||
context = self.get_context_data(**kwargs)
|
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 render_to_string(self.template_name, context)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class Section (BaseSection):
|
class Section (BaseSection):
|
||||||
|
"""
|
||||||
|
A Section that can be related to an object.
|
||||||
|
"""
|
||||||
template_name = 'aircox_cms/section.html'
|
template_name = 'aircox_cms/section.html'
|
||||||
require_object = False
|
|
||||||
object = None
|
object = None
|
||||||
|
object_required = False
|
||||||
title = ''
|
title = ''
|
||||||
header = ''
|
header = ''
|
||||||
bottom = ''
|
bottom = ''
|
||||||
|
|
||||||
def get_context_data (self, **kwargs):
|
def get_context_data (self):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data()
|
||||||
context.update({
|
context.update({
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'header': self.header,
|
'header': self.header,
|
||||||
|
@ -239,14 +256,20 @@ class Section (BaseSection):
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get (self, request, **kwargs):
|
def get (self, request, object = None, **kwargs):
|
||||||
self.object = kwargs.get('object') or self.object
|
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)
|
return super().get(request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Sections:
|
class Sections:
|
||||||
class Image (BaseSection):
|
class Image (BaseSection):
|
||||||
url = None # relative url to the image
|
"""
|
||||||
|
Render an image with the given relative url.
|
||||||
|
"""
|
||||||
|
url = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content (self):
|
def content (self):
|
||||||
|
@ -256,6 +279,10 @@ class Sections:
|
||||||
|
|
||||||
|
|
||||||
class PostContent (Section):
|
class PostContent (Section):
|
||||||
|
"""
|
||||||
|
Render the content of the Post (format the text a bit and escape HTML
|
||||||
|
tags).
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def content (self):
|
def content (self):
|
||||||
content = escape(self.object.content)
|
content = escape(self.object.content)
|
||||||
|
@ -265,6 +292,9 @@ class Sections:
|
||||||
|
|
||||||
|
|
||||||
class PostImage (Section):
|
class PostImage (Section):
|
||||||
|
"""
|
||||||
|
Render the image of the Post
|
||||||
|
"""
|
||||||
@property
|
@property
|
||||||
def content (self):
|
def content (self):
|
||||||
return '<img src="{}" class="post_image">'.format(
|
return '<img src="{}" class="post_image">'.format(
|
||||||
|
@ -281,59 +311,78 @@ class Sections:
|
||||||
icon = None
|
icon = None
|
||||||
title = None
|
title = None
|
||||||
text = None
|
text = None
|
||||||
|
url = None
|
||||||
|
|
||||||
def __init__ (self, icon, title = None, text = None):
|
def __init__ (self, icon, title = None, text = None, url = None):
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.title = title
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
use_icons = True
|
hide_empty = False # hides the section if the list is empty
|
||||||
icon_size = '32x32'
|
use_icons = True # print icons
|
||||||
|
icon_size = '32x32' # icons size
|
||||||
template_name = 'aircox_cms/section_list.html'
|
template_name = 'aircox_cms/section_list.html'
|
||||||
|
|
||||||
def get_object_list (self):
|
def get_object_list (self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_context_data (self, **kwargs):
|
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 = super().get_context_data(**kwargs)
|
||||||
context.update({
|
context.update({
|
||||||
'classes': context.get('classes') + ' section_list',
|
'classes': context.get('classes') + ' section_list',
|
||||||
'icon_size': self.icon_size,
|
'icon_size': self.icon_size,
|
||||||
'object_list': self.get_object_list(),
|
'object_list': object_list,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class UrlList (List):
|
class Urls (List):
|
||||||
|
"""
|
||||||
|
Render a list of urls of targets that are Posts
|
||||||
|
"""
|
||||||
classes = 'section_urls'
|
classes = 'section_urls'
|
||||||
targets = None
|
targets = None
|
||||||
|
|
||||||
def get_object_list (self, request, **kwargs):
|
def get_object_list (self):
|
||||||
return [
|
return [
|
||||||
List.Item(
|
List.Item(
|
||||||
target.image or None,
|
target.image or None,
|
||||||
'<a href="{}">{}</a>'.format(target.detail_url(), target.title)
|
target.title,
|
||||||
|
url = target.detail_url(),
|
||||||
)
|
)
|
||||||
for target in self.targets
|
for target in self.targets
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class Posts (PostBaseView, Section):
|
||||||
class PostList (PostListView):
|
"""
|
||||||
route = None
|
Render a list using PostListView's template.
|
||||||
model = None
|
"""
|
||||||
embed = True
|
embed = True
|
||||||
|
icon_size = '64x64'
|
||||||
|
fields = [ 'date', 'time', 'image', 'title', 'content' ]
|
||||||
|
|
||||||
def __init__ (self, *args, **kwargs):
|
def get_object_list (self):
|
||||||
super().__init__(*args, **kwargs)
|
return []
|
||||||
|
|
||||||
def get_kwargs (self, request, **kwargs):
|
def render_list (self):
|
||||||
return kwargs
|
self.embed = True
|
||||||
|
context = self.get_base_context(**self.kwargs)
|
||||||
def dispatch (self, request, *args, **kwargs):
|
context.update({
|
||||||
kwargs = self.get_kwargs(kwargs)
|
'object_list': self.get_object_list(),
|
||||||
response = super().dispatch(request, *args, **kwargs)
|
'embed': True,
|
||||||
return str(response.content)
|
})
|
||||||
|
print(context['object_list'][0].image)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
class ViewSet:
|
class ViewSet:
|
||||||
|
@ -348,15 +397,25 @@ class ViewSet:
|
||||||
|
|
||||||
detail_view = PostDetailView
|
detail_view = PostDetailView
|
||||||
detail_sections = [
|
detail_sections = [
|
||||||
Sections.PostContent,
|
Sections.PostContent(),
|
||||||
Sections.PostImage,
|
Sections.PostImage(),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__ (self, website = None):
|
|
||||||
self.detail_sections = [
|
def get_list_name (self):
|
||||||
section()
|
"""
|
||||||
for section in self.detail_sections
|
Return a string with the name to use in the route for the lists
|
||||||
]
|
"""
|
||||||
|
return self.model._meta.verbose_name_plural.lower()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_detail_name (cl):
|
||||||
|
"""
|
||||||
|
Return a string with the name to use in the route for the details
|
||||||
|
"""
|
||||||
|
return cl.model._meta.verbose_name.lower()
|
||||||
|
|
||||||
|
def connect (self, website = None):
|
||||||
self.detail_view = self.detail_view.as_view(
|
self.detail_view = self.detail_view.as_view(
|
||||||
model = self.model,
|
model = self.model,
|
||||||
sections = self.detail_sections,
|
sections = self.detail_sections,
|
||||||
|
@ -367,8 +426,9 @@ class ViewSet:
|
||||||
website = website,
|
website = website,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.urls = [ route.as_url(self.model, self.list_view)
|
self.urls = [ route.as_url(self.model, self.get_list_name(),
|
||||||
for route in self.list_routes ] + \
|
self.list_view) for route in self.list_routes ] + \
|
||||||
[ routes.DetailRoute.as_url(self.model, self.detail_view ) ]
|
[ routes.DetailRoute.as_url(self.model,
|
||||||
|
self.get_detail_name(), self.detail_view ) ]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Website:
|
||||||
'right', 'bottom',
|
'right', 'bottom',
|
||||||
'header', 'footer']
|
'header', 'footer']
|
||||||
router = None
|
router = None
|
||||||
|
registry = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def urls (self):
|
def urls (self):
|
||||||
|
@ -27,7 +28,9 @@ class Website:
|
||||||
Register a ViewSet (or subclass) to the router,
|
Register a ViewSet (or subclass) to the router,
|
||||||
and connect it to self.
|
and connect it to self.
|
||||||
"""
|
"""
|
||||||
view_set = view_set(website = self)
|
view_set = view_set()
|
||||||
|
view_set.connect(website = self)
|
||||||
|
self.registry[view_set.get_detail_name()] = view_set
|
||||||
self.router.register_set(view_set)
|
self.router.register_set(view_set)
|
||||||
|
|
||||||
def get_menu (self, position):
|
def get_menu (self, position):
|
||||||
|
|
|
@ -71,7 +71,7 @@ class ProgramAdmin (NameableAdmin):
|
||||||
@admin.register(Episode)
|
@admin.register(Episode)
|
||||||
class EpisodeAdmin (NameableAdmin):
|
class EpisodeAdmin (NameableAdmin):
|
||||||
list_filter = ['program'] + NameableAdmin.list_filter
|
list_filter = ['program'] + NameableAdmin.list_filter
|
||||||
fields = NameableAdmin.fields + ['sounds']
|
fields = NameableAdmin.fields + ['sounds', 'program']
|
||||||
|
|
||||||
inlines = (TrackInline, DiffusionInline)
|
inlines = (TrackInline, DiffusionInline)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ def add_inline (base_model, post_model, prepend = False):
|
||||||
'fields': ['title', 'content', 'image', 'tags']
|
'fields': ['title', 'content', 'image', 'tags']
|
||||||
}),
|
}),
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ['date', 'published', 'author', 'thread']
|
'fields': ['date', 'published', 'author', 'thread_pk', 'thread_type']
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ def add_inline (base_model, post_model, prepend = False):
|
||||||
add_inline(programs.Program, Program, True)
|
add_inline(programs.Program, Program, True)
|
||||||
add_inline(programs.Episode, Episode, True)
|
add_inline(programs.Episode, Episode, True)
|
||||||
|
|
||||||
|
admin.site.register(Program)
|
||||||
|
admin.site.register(Episode)
|
||||||
|
|
||||||
#class ArticleAdmin (DescriptionAdmin):
|
#class ArticleAdmin (DescriptionAdmin):
|
||||||
# fieldsets = copy.deepcopy(DescriptionAdmin.fieldsets)
|
# fieldsets = copy.deepcopy(DescriptionAdmin.fieldsets)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import aircox_programs.models as programs
|
||||||
class Program (RelatedPost):
|
class Program (RelatedPost):
|
||||||
class Relation:
|
class Relation:
|
||||||
related_model = programs.Program
|
related_model = programs.Program
|
||||||
|
bind_mapping = True
|
||||||
mapping = {
|
mapping = {
|
||||||
'title': 'name',
|
'title': 'name',
|
||||||
'content': 'description',
|
'content': 'description',
|
||||||
|
@ -14,6 +15,7 @@ class Program (RelatedPost):
|
||||||
class Episode (RelatedPost):
|
class Episode (RelatedPost):
|
||||||
class Relation:
|
class Relation:
|
||||||
related_model = programs.Episode
|
related_model = programs.Episode
|
||||||
|
bind_mapping = True
|
||||||
mapping = {
|
mapping = {
|
||||||
'title': 'name',
|
'title': 'name',
|
||||||
'content': 'description',
|
'content': 'description',
|
||||||
|
|
|
@ -9,6 +9,20 @@ h1, h2, h3 {
|
||||||
font-family: "Myriad Pro",Calibri,Helvetica,Arial,sans-serif
|
font-family: "Myriad Pro",Calibri,Helvetica,Arial,sans-serif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #818181;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
nav.menu {
|
nav.menu {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
@ -18,27 +32,64 @@ nav.menu_top {
|
||||||
background-color: #212121;
|
background-color: #212121;
|
||||||
color: #007EDF;
|
color: #007EDF;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
box-shadow: 0em 0.2em 0.5em 0.1em black
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
header {
|
header.menu {
|
||||||
|
padding: 0.2em;
|
||||||
height: 9em;
|
height: 9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
header img {
|
header.menu img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
header .colony {
|
#colony img {
|
||||||
|
height: auto;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0em;
|
top: 1em;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 1.5em 0em;
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page img {
|
||||||
|
box-shadow: 0em 0em 0.2em 0.01em black;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.post_list {
|
||||||
|
background-color: #F2F2F2;
|
||||||
|
box-shadow: inset 0.2em 0.2em 0.2em 0.01em black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_list .post_item {
|
||||||
|
min-height: 64px;
|
||||||
|
padding: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_list .post_item:hover {
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_list h3 {
|
||||||
|
margin: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_list time {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post_list img {
|
||||||
|
float: left;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@ class ProgramSet (ViewSet):
|
||||||
]
|
]
|
||||||
|
|
||||||
detail_sections = ViewSet.detail_sections + [
|
detail_sections = ViewSet.detail_sections + [
|
||||||
ScheduleSection,
|
ScheduleSection(),
|
||||||
|
EpisodesSection(),
|
||||||
]
|
]
|
||||||
|
|
||||||
class EpisodeSet (ViewSet):
|
class EpisodeSet (ViewSet):
|
||||||
|
@ -49,7 +50,7 @@ website = Website(
|
||||||
position = 'header',
|
position = 'header',
|
||||||
sections = [
|
sections = [
|
||||||
Sections.Image(url = 'website/logo.png'),
|
Sections.Image(url = 'website/logo.png'),
|
||||||
Sections.Image(url = 'website/colony.png', classes='colony'),
|
Sections.Image(url = 'website/colony.png', attrs = { 'id': 'colony' }),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
import aircox_programs.models as programs
|
import aircox_programs.models as programs
|
||||||
from aircox_cms.views import Sections
|
from aircox_cms.views import Sections
|
||||||
|
|
||||||
|
from website.models import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PlayListSection (Sections.List):
|
class PlayListSection (Sections.List):
|
||||||
title = _('Playlist')
|
title = _('Playlist')
|
||||||
|
@ -34,4 +37,10 @@ class ScheduleSection (Sections.List):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EpisodesSection (Sections.Posts):
|
||||||
|
title = _('Episodes')
|
||||||
|
|
||||||
|
def get_object_list (self):
|
||||||
|
return Episode.objects.filter(related__program = self.object.pk)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user