forked from rc/aircox
integrate qcombine with routes; default routes in website; search in tags too; qcombine fixes (search, model); website's Publications model
This commit is contained in:
parent
7ba887b3cd
commit
47991dfa3d
|
@ -5,7 +5,6 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
|
|
||||||
from django.db.models.signals import Signal, post_save, pre_save
|
from django.db.models.signals import Signal, post_save, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -38,11 +37,12 @@ class Routable:
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def route_url(cl, route, **kwargs):
|
def reverse(cl, route, use_default = True, **kwargs):
|
||||||
name = cl._website.name_of_model(cl)
|
"""
|
||||||
name = route.get_view_name(name)
|
Reverse a url using a given route for the model - simple wrapper
|
||||||
r = reverse(name, kwargs = kwargs)
|
around cl._website.reverse
|
||||||
return r
|
"""
|
||||||
|
return cl._website.reverse(cl, route, use_default, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Comment(models.Model, Routable):
|
class Comment(models.Model, Routable):
|
||||||
|
@ -154,7 +154,10 @@ class Post (models.Model, Routable):
|
||||||
blank = True,
|
blank = True,
|
||||||
)
|
)
|
||||||
|
|
||||||
search_fields = [ 'title', 'content' ]
|
search_fields = [ 'title', 'content', 'tags__name' ]
|
||||||
|
"""
|
||||||
|
Fields on which routes.SearchRoute must run the search
|
||||||
|
"""
|
||||||
|
|
||||||
def get_comments(self):
|
def get_comments(self):
|
||||||
"""
|
"""
|
||||||
|
@ -171,12 +174,33 @@ class Post (models.Model, Routable):
|
||||||
"""
|
"""
|
||||||
Return an url to the post detail view.
|
Return an url to the post detail view.
|
||||||
"""
|
"""
|
||||||
return self.route_url(
|
return self.reverse(
|
||||||
routes.DetailRoute,
|
routes.DetailRoute,
|
||||||
pk = self.pk, slug = slugify(self.title)
|
pk = self.pk, slug = slugify(self.title)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def fill_empty(self):
|
||||||
|
"""
|
||||||
|
Fill empty values using parent thread. Can be used before saving or
|
||||||
|
at loading
|
||||||
|
"""
|
||||||
|
if not self.thread:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.title:
|
||||||
|
self.title = _('{name} // {date}').format(
|
||||||
|
name = self.thread.title,
|
||||||
|
date = self.date
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
def get_object_list(self, request, object, **kwargs):
|
def get_object_list(self, request, object, **kwargs):
|
||||||
|
# FIXME: wtf
|
||||||
type = ContentType.objects.get_for_model(object)
|
type = ContentType.objects.get_for_model(object)
|
||||||
qs = Comment.objects.filter(
|
qs = Comment.objects.filter(
|
||||||
thread_id = object.pk,
|
thread_id = object.pk,
|
||||||
|
@ -185,6 +209,9 @@ class Post (models.Model, Routable):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def make_safe(self):
|
def make_safe(self):
|
||||||
|
"""
|
||||||
|
Ensure that data of the publication are safe from code injection.
|
||||||
|
"""
|
||||||
self.title = bleach.clean(
|
self.title = bleach.clean(
|
||||||
self.title,
|
self.title,
|
||||||
tags=settings.AIRCOX_CMS_BLEACH_TITLE_TAGS,
|
tags=settings.AIRCOX_CMS_BLEACH_TITLE_TAGS,
|
||||||
|
@ -293,6 +320,7 @@ class RelatedMeta (models.base.ModelBase):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
post.rel_to_post()
|
post.rel_to_post()
|
||||||
|
post.fill_empty()
|
||||||
post.save(avoid_sync = True)
|
post.save(avoid_sync = True)
|
||||||
post_save.connect(handler_rel, model._relation.model, False)
|
post_save.connect(handler_rel, model._relation.model, False)
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,12 @@ import operator
|
||||||
import itertools
|
import itertools
|
||||||
import heapq
|
import heapq
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
|
from aircox.cms.models import Routable
|
||||||
|
|
||||||
|
|
||||||
class QCombine:
|
class QCombine:
|
||||||
"""
|
"""
|
||||||
This class helps to combine querysets of different models and lists of
|
This class helps to combine querysets of different models and lists of
|
||||||
|
@ -29,9 +33,9 @@ class QCombine:
|
||||||
Map results of qs_func for QuerySet instance and of non_qs for
|
Map results of qs_func for QuerySet instance and of non_qs for
|
||||||
the others (if given), because QuerySet always clones itself.
|
the others (if given), because QuerySet always clones itself.
|
||||||
"""
|
"""
|
||||||
for i, qs in self.lists:
|
for i, qs in enumerate(self.lists):
|
||||||
if issubclass(type(qs, QuerySet):
|
if issubclass(type(qs), QuerySet):
|
||||||
self.lists[i] = func(qs)
|
self.lists[i] = qs_func(qs)
|
||||||
elif non_qs:
|
elif non_qs:
|
||||||
self.lists[i] = non_qs(qs)
|
self.lists[i] = non_qs(qs)
|
||||||
|
|
||||||
|
@ -47,7 +51,7 @@ class QCombine:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def distinct(self, **kwargs):
|
def distinct(self, **kwargs):
|
||||||
self.map(qs.distinct())
|
self.map(lambda qs: qs.distinct())
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get(self, **kwargs):
|
def get(self, **kwargs):
|
||||||
|
@ -68,13 +72,12 @@ class QCombine:
|
||||||
|
|
||||||
self.order_reverse = reverse
|
self.order_reverse = reverse
|
||||||
self.order_fields = fields
|
self.order_fields = fields
|
||||||
|
|
||||||
self.map(
|
self.map(
|
||||||
lambda qs: qs.order_by(*fields),
|
lambda qs: qs.order_by(*fields),
|
||||||
lambda qs: sorted(
|
lambda qs: sorted(
|
||||||
qs,
|
qs,
|
||||||
qs.sort(
|
qs.sort(
|
||||||
key = operator.attrgetter(fields),
|
key = operator.attrgetter(*fields),
|
||||||
reverse = reverse
|
reverse = reverse
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -112,7 +115,18 @@ class QCombine:
|
||||||
return list(it)
|
return list(it)
|
||||||
|
|
||||||
|
|
||||||
class QCombined:
|
|
||||||
|
|
||||||
|
class Manager(type):
|
||||||
|
models = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def objects(self):
|
||||||
|
qs = QCombine(*[model.objects.all() for model in self.models])
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class FakeModel(Routable,metaclass=Manager):
|
||||||
"""
|
"""
|
||||||
This class is used to register a route for multiple models to a website.
|
This class is used to register a route for multiple models to a website.
|
||||||
A QCombine is created with qs for all given models when objects
|
A QCombine is created with qs for all given models when objects
|
||||||
|
@ -120,21 +134,12 @@ class QCombined:
|
||||||
|
|
||||||
Note: there no other use-case.
|
Note: there no other use-case.
|
||||||
"""
|
"""
|
||||||
def __init__(*models):
|
|
||||||
self.models = models
|
|
||||||
self._meta = self.Meta()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('publication')
|
verbose_name = _('publication')
|
||||||
verbose_name_plural = _('publications')
|
verbose_name_plural = _('publications')
|
||||||
|
|
||||||
@property
|
_meta = Meta()
|
||||||
def objects(self):
|
|
||||||
"""
|
|
||||||
The QCombine that is returned actually holds the models' managers,
|
|
||||||
in order to simulate the same behaviour than a regular model.
|
|
||||||
"""
|
|
||||||
qs = QCombine([model.objects for model in self.models])
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ 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
|
||||||
|
|
||||||
|
import aircox.cms.qcombine as qcombine
|
||||||
|
|
||||||
|
|
||||||
class Route:
|
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
|
||||||
|
@ -41,7 +44,7 @@ class Route:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_view_name(cl, name):
|
def make_view_name(cl, name):
|
||||||
return name + '.' + cl.name
|
return name + '.' + cl.name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -65,7 +68,7 @@ class Route:
|
||||||
kwargs.update(view_kwargs)
|
kwargs.update(view_kwargs)
|
||||||
|
|
||||||
return url(pattern, view, kwargs = kwargs,
|
return url(pattern, view, kwargs = kwargs,
|
||||||
name = cl.get_view_name(name))
|
name = cl.make_view_name(name))
|
||||||
|
|
||||||
|
|
||||||
class DetailRoute(Route):
|
class DetailRoute(Route):
|
||||||
|
@ -174,21 +177,28 @@ class SearchRoute(Route):
|
||||||
name = 'search'
|
name = 'search'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset(cl, model, request, **kwargs):
|
def __search(cl, model, q):
|
||||||
q = request.GET.get('q') or ''
|
|
||||||
qs = None
|
qs = None
|
||||||
|
|
||||||
## TODO: by tag
|
|
||||||
for search_field in model.search_fields or []:
|
for search_field in model.search_fields or []:
|
||||||
r = models.Q(**{ search_field + '__icontains': q })
|
r = models.Q(**{ search_field + '__icontains': q })
|
||||||
if qs: qs = qs | r
|
if qs: qs = qs | r
|
||||||
else: qs = r
|
else: qs = r
|
||||||
|
|
||||||
return model.objects.filter(qs).distinct()
|
return model.objects.filter(qs).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_queryset(cl, model, request, **kwargs):
|
||||||
|
q = request.GET.get('q') or ''
|
||||||
|
if issubclass(model, qcombine.FakeModel):
|
||||||
|
models = model.models
|
||||||
|
return qcombine.QCombine(
|
||||||
|
*(cl.__search(model, q) for model in models)
|
||||||
|
)
|
||||||
|
return cl.__search(model, q)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_title(cl, model, request, **kwargs):
|
def get_title(cl, model, request, **kwargs):
|
||||||
return _('Search "%(search)s" in %(model)s') % {
|
return _('Search <i>%(search)s</i> in %(model)s') % {
|
||||||
'model': model._meta.verbose_name_plural,
|
'model': model._meta.verbose_name_plural,
|
||||||
'search': request.GET.get('q') or '',
|
'search': request.GET.get('q') or '',
|
||||||
}
|
}
|
||||||
|
@ -207,14 +217,14 @@ class TagsRoute(Route):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_queryset(cl, model, request, tags, **kwargs):
|
def get_queryset(cl, model, request, tags, **kwargs):
|
||||||
tags = tags.split('+')
|
tags = tags.split('+')
|
||||||
return model.objects.filter(tags__name__in=tags)
|
return model.objects.filter(tags__slug__in=tags).distinct()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_title(cl, model, request, tags, **kwargs):
|
def get_title(cl, model, request, tags, **kwargs):
|
||||||
return _('Tagged %(model)s with %(tags)s') % {
|
# FIXME: get tag name instead of tag slug
|
||||||
|
return _('%(model)s tagged with %(tags)s') % {
|
||||||
'model': model._meta.verbose_name_plural,
|
'model': model._meta.verbose_name_plural,
|
||||||
'tags': tags.replace('+', ', ')
|
'tags': model.tags_to_html(model, tags = tags.split('+'))
|
||||||
|
if '+' in tags else tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
Define different Section css_class that can be used by views.Sections;
|
Define different Section css_class that can be used by views.Sections;
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from random import shuffle
|
||||||
|
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
|
@ -329,13 +330,22 @@ class Similar(List):
|
||||||
List of models allowed in the resulting list. If not set, all models
|
List of models allowed in the resulting list. If not set, all models
|
||||||
are available.
|
are available.
|
||||||
"""
|
"""
|
||||||
|
shuffle = 20
|
||||||
|
"""
|
||||||
|
Shuffle results in the self.shuffle most recents articles. If 0 or
|
||||||
|
None, do not shuffle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FIXME: limit in a date range
|
||||||
def get_object_list(self):
|
def get_object_list(self):
|
||||||
if not self.object:
|
if not self.object:
|
||||||
return
|
return
|
||||||
|
|
||||||
qs = self.object.tags.similar_objects()
|
qs = self.object.tags.similar_objects()
|
||||||
qs.sort(key = lambda post: post.date, reverse=True)
|
qs.sort(key = lambda post: post.date, reverse=True)
|
||||||
|
if self.shuffle:
|
||||||
|
qs = qs[:self.shuffle]
|
||||||
|
shuffle(qs)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
@ -371,11 +381,9 @@ class Comments(List):
|
||||||
import aircox.cms.models as models
|
import aircox.cms.models as models
|
||||||
import aircox.cms.routes as routes
|
import aircox.cms.routes as routes
|
||||||
if self.object:
|
if self.object:
|
||||||
return models.Comment.route_url(routes.ThreadRoute, {
|
return models.Comment.reverse(routes.ThreadRoute, {
|
||||||
'pk': self.object.id,
|
'pk': self.object.id,
|
||||||
'thread_model': self.object._website.name_of_model(
|
'thread_model': self.object._registration.name
|
||||||
self.object.__class__
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,16 @@
|
||||||
from django import template
|
from django import template
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
import aircox.cms.routes as routes
|
import aircox.cms.utils as utils
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.filter(name='post_tags')
|
@register.filter(name='post_tags')
|
||||||
def post_tags(post, sep = '-'):
|
def post_tags(post, sep = ' - '):
|
||||||
"""
|
"""
|
||||||
print the list of all the tags of the given post, with url if available
|
return the result of post.tags_url
|
||||||
"""
|
"""
|
||||||
tags = post.tags.all()
|
return utils.tags_to_html(type(post), post.tags.all(), sep)
|
||||||
r = []
|
|
||||||
for tag in tags:
|
|
||||||
try:
|
|
||||||
r.append('<a href="{url}">{name}</a>'.format(
|
|
||||||
url = post.route_url(routes.TagsRoute, tags = tag),
|
|
||||||
name = tag,
|
|
||||||
))
|
|
||||||
except:
|
|
||||||
r.push(tag)
|
|
||||||
return sep.join(r)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='threads')
|
@register.filter(name='threads')
|
||||||
|
@ -37,7 +26,7 @@ def threads(post, sep = '/'):
|
||||||
|
|
||||||
return sep.join([
|
return sep.join([
|
||||||
'<a href="{}">{}</a>'.format(post.url(), post.title)
|
'<a href="{}">{}</a>'.format(post.url(), post.title)
|
||||||
for post in posts if post.published
|
for post in posts[:-1] if post.published
|
||||||
])
|
])
|
||||||
|
|
||||||
@register.filter(name='around')
|
@register.filter(name='around')
|
||||||
|
|
143
cms/website.py
143
cms/website.py
|
@ -1,4 +1,7 @@
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
import aircox.cms.routes as routes
|
import aircox.cms.routes as routes
|
||||||
|
@ -33,6 +36,10 @@ class Website:
|
||||||
"""register list routes for the Comment model"""
|
"""register list routes for the Comment model"""
|
||||||
|
|
||||||
## components
|
## components
|
||||||
|
Registration = namedtuple('Registration',
|
||||||
|
'name model routes as_default'
|
||||||
|
)
|
||||||
|
|
||||||
urls = []
|
urls = []
|
||||||
"""list of urls generated thourgh registrations"""
|
"""list of urls generated thourgh registrations"""
|
||||||
exposures = []
|
exposures = []
|
||||||
|
@ -57,44 +64,26 @@ class Website:
|
||||||
if self.comments_routes:
|
if self.comments_routes:
|
||||||
self.register_comments()
|
self.register_comments()
|
||||||
|
|
||||||
def name_of_model(self, model):
|
def register_model(self, name, model, as_default):
|
||||||
"""
|
"""
|
||||||
Return the registered name for a given model if found.
|
Register a model and update model's fields with few data:
|
||||||
"""
|
- _website: back ref to self
|
||||||
for name, _model in self.registry.items():
|
- _registration: ref to the registration object
|
||||||
if model is _model:
|
|
||||||
return name
|
|
||||||
|
|
||||||
def register_comments(self):
|
|
||||||
"""
|
|
||||||
Register routes for comments, for the moment, only
|
|
||||||
ThreadRoute
|
|
||||||
"""
|
|
||||||
self.register(
|
|
||||||
'comment',
|
|
||||||
view = views.PostListView,
|
|
||||||
routes = [routes.ThreadRoute],
|
|
||||||
model = models.Comment,
|
|
||||||
css_class = 'comments',
|
|
||||||
list = sections.Comments(
|
|
||||||
truncate = 30,
|
|
||||||
fields = ['content','author','date','time'],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
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.
|
Raise a ValueError if another model is yet associated under this name.
|
||||||
"""
|
"""
|
||||||
if name in self.registry:
|
if name in self.registry:
|
||||||
if self.registry[name] is model:
|
reg = self.registry[name]
|
||||||
return name
|
if reg.model is model:
|
||||||
|
return reg
|
||||||
raise ValueError('A model has yet been registered under "{}"'
|
raise ValueError('A model has yet been registered under "{}"'
|
||||||
.format(name))
|
.format(name))
|
||||||
self.registry[name] = model
|
|
||||||
|
reg = self.Registration(name, model, [], as_default)
|
||||||
|
self.registry[name] = reg
|
||||||
|
model._registration = reg
|
||||||
model._website = self
|
model._website = self
|
||||||
return name
|
return reg
|
||||||
|
|
||||||
def register_exposures(self, sections):
|
def register_exposures(self, sections):
|
||||||
"""
|
"""
|
||||||
|
@ -112,7 +101,8 @@ class Website:
|
||||||
]
|
]
|
||||||
|
|
||||||
def register(self, name, routes = [], view = views.PageView,
|
def register(self, name, routes = [], view = views.PageView,
|
||||||
model = None, sections = None, **view_kwargs):
|
model = None, sections = None,
|
||||||
|
as_default = False, **view_kwargs):
|
||||||
"""
|
"""
|
||||||
Register a view using given name and routes. If model is given,
|
Register a view using given name and routes. If model is given,
|
||||||
register the views for it.
|
register the views for it.
|
||||||
|
@ -120,11 +110,21 @@ class Website:
|
||||||
* name is used to register the routes as urls and the model if given
|
* name is used to register the routes as urls and the model if given
|
||||||
* routes: can be a path or a route used to generate urls for the view.
|
* routes: can be a path or a route used to generate urls for the view.
|
||||||
Can be a one item or a list of items.
|
Can be a one item or a list of items.
|
||||||
|
* view: route that is registered for the given routes
|
||||||
|
* model: model being registrated. If given, register it in the website
|
||||||
|
under the given name, and make it available to the view.
|
||||||
|
* as_default: make the view available as a default view.
|
||||||
"""
|
"""
|
||||||
|
if type(routes) not in (tuple, list):
|
||||||
|
routes = [ routes ]
|
||||||
|
|
||||||
|
# model registration
|
||||||
if model:
|
if model:
|
||||||
name = self.__register_model(name, model)
|
reg = self.register_model(name, model, as_default)
|
||||||
|
reg.routes.extend(routes)
|
||||||
view_kwargs['model'] = model
|
view_kwargs['model'] = model
|
||||||
|
|
||||||
|
# init view
|
||||||
if not view_kwargs.get('menus'):
|
if not view_kwargs.get('menus'):
|
||||||
view_kwargs['menus'] = self.menus
|
view_kwargs['menus'] = self.menus
|
||||||
|
|
||||||
|
@ -137,9 +137,7 @@ class Website:
|
||||||
**view_kwargs
|
**view_kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
if type(routes) not in (tuple, list):
|
# url gen
|
||||||
routes = [ routes ]
|
|
||||||
|
|
||||||
self.urls += [
|
self.urls += [
|
||||||
route.as_url(name, view)
|
route.as_url(name, view)
|
||||||
if type(route) == type and issubclass(route, routes_.Route)
|
if type(route) == type and issubclass(route, routes_.Route)
|
||||||
|
@ -148,24 +146,51 @@ class Website:
|
||||||
for route in routes
|
for route in routes
|
||||||
]
|
]
|
||||||
|
|
||||||
def register_post(self, name, model, sections = None, routes = None,
|
def register_dl(self, name, model, sections = None, routes = None,
|
||||||
list_view = views.PostListView,
|
list_view = views.PostListView,
|
||||||
detail_view = views.PostDetailView,
|
detail_view = views.PostDetailView,
|
||||||
list_kwargs = {}, detail_kwargs = {}):
|
list_kwargs = {}, detail_kwargs = {},
|
||||||
|
as_default = False):
|
||||||
"""
|
"""
|
||||||
Register a detail and list view for a given model, using
|
Register a detail and list view for a given model, using
|
||||||
routes. Just a wrapper around register.
|
routes.
|
||||||
|
|
||||||
|
Just a wrapper around `register`.
|
||||||
"""
|
"""
|
||||||
if sections:
|
if sections:
|
||||||
self.register(name, [ routes_.DetailRoute ], view = detail_view,
|
self.register(name, [ routes_.DetailRoute ], view = detail_view,
|
||||||
model = model, sections = sections, **detail_kwargs)
|
model = model, sections = sections,
|
||||||
|
as_default = as_default,
|
||||||
|
**detail_kwargs)
|
||||||
if routes:
|
if routes:
|
||||||
self.register(name, routes, view = list_view,
|
self.register(name, routes, view = list_view,
|
||||||
model = model, **list_kwargs)
|
model = model, as_default = as_default,
|
||||||
|
**list_kwargs)
|
||||||
|
|
||||||
|
def register_comments(self):
|
||||||
|
"""
|
||||||
|
Register routes for comments, for the moment, only
|
||||||
|
ThreadRoute.
|
||||||
|
|
||||||
|
Just a wrapper around `register`.
|
||||||
|
"""
|
||||||
|
self.register(
|
||||||
|
'comment',
|
||||||
|
view = views.PostListView,
|
||||||
|
routes = [routes.ThreadRoute],
|
||||||
|
model = models.Comment,
|
||||||
|
css_class = 'comments',
|
||||||
|
list = sections.Comments(
|
||||||
|
truncate = 30,
|
||||||
|
fields = ['content','author','date','time'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def set_menu(self, menu):
|
def set_menu(self, menu):
|
||||||
"""
|
"""
|
||||||
Set a menu, and remove any previous menu at the same position
|
Set a menu, and remove any previous menu at the same position.
|
||||||
|
Also update the menu's tag depending on its position, in order
|
||||||
|
to have a semantic HTML5 on the web 2.0 (lol).
|
||||||
"""
|
"""
|
||||||
if menu.position in ('footer','header'):
|
if menu.position in ('footer','header'):
|
||||||
menu.tag = menu.position
|
menu.tag = menu.position
|
||||||
|
@ -174,10 +199,40 @@ class Website:
|
||||||
self.menus[menu.position] = menu
|
self.menus[menu.position] = menu
|
||||||
self.register_exposures(menu.sections)
|
self.register_exposures(menu.sections)
|
||||||
|
|
||||||
def get_menu(self, position):
|
def find_default(self, route):
|
||||||
"""
|
"""
|
||||||
Get an enabled menu by its position
|
Return a registration that can be used as default for the
|
||||||
|
given route.
|
||||||
"""
|
"""
|
||||||
return self.menus.get(position)
|
for r in self.registry.values():
|
||||||
|
if r.as_default and route in r.routes:
|
||||||
|
return r
|
||||||
|
|
||||||
|
def reverse(self, model, route, use_default = True, **kwargs):
|
||||||
|
"""
|
||||||
|
Reverse a url using the given model and route. If the reverse does
|
||||||
|
not function and use_default is True, use a model that have been
|
||||||
|
registered as a default view and that have the given road.
|
||||||
|
|
||||||
|
If no model is given reverse with default.
|
||||||
|
"""
|
||||||
|
if model and route in model._registration.routes:
|
||||||
|
try:
|
||||||
|
name = route.make_view_name(model._registration.name)
|
||||||
|
return reverse(name, kwargs = kwargs)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if model and not use_default:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
for r in self.registry.values():
|
||||||
|
if r.as_default and route in r.routes:
|
||||||
|
try:
|
||||||
|
name = route.make_view_name(r.name)
|
||||||
|
return reverse(name, kwargs = kwargs)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
|
5
notes.md
5
notes.md
|
@ -21,11 +21,12 @@
|
||||||
- empty content -> empty string
|
- empty content -> empty string
|
||||||
- update documentation:
|
- update documentation:
|
||||||
- cms.views
|
- cms.views
|
||||||
- cms.parts
|
- cms.exposure
|
||||||
- cms.script
|
- cms.script
|
||||||
- cms.qcombine
|
- cms.qcombine
|
||||||
- routes
|
- routes
|
||||||
- integrate QCombine
|
- tag name instead of tag slug for the title
|
||||||
|
- optional url args
|
||||||
- admin cms
|
- admin cms
|
||||||
- content management -> do we use a markup language?
|
- content management -> do we use a markup language?
|
||||||
- sections:
|
- sections:
|
||||||
|
|
|
@ -7,11 +7,12 @@ logger = logging.getLogger('aircox')
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
from aircox.cms.models import Post, RelatedPost
|
|
||||||
import aircox.programs.models as programs
|
import aircox.programs.models as programs
|
||||||
|
import aircox.cms.models as cms
|
||||||
|
import aircox.cms.qcombine as qcombine
|
||||||
|
|
||||||
|
|
||||||
class Article (Post):
|
class Article (cms.Post):
|
||||||
"""
|
"""
|
||||||
Represent an article or a static page on the website.
|
Represent an article or a static page on the website.
|
||||||
"""
|
"""
|
||||||
|
@ -29,7 +30,7 @@ class Article (Post):
|
||||||
verbose_name_plural = _('Articles')
|
verbose_name_plural = _('Articles')
|
||||||
|
|
||||||
|
|
||||||
class Program (RelatedPost):
|
class Program (cms.RelatedPost):
|
||||||
website = models.URLField(
|
website = models.URLField(
|
||||||
_('website'),
|
_('website'),
|
||||||
blank=True, null=True
|
blank=True, null=True
|
||||||
|
@ -49,7 +50,7 @@ class Program (RelatedPost):
|
||||||
auto_create = True
|
auto_create = True
|
||||||
|
|
||||||
|
|
||||||
class Diffusion (RelatedPost):
|
class Diffusion (cms.RelatedPost):
|
||||||
class Relation:
|
class Relation:
|
||||||
model = programs.Diffusion
|
model = programs.Diffusion
|
||||||
bindings = {
|
bindings = {
|
||||||
|
@ -68,18 +69,7 @@ class Diffusion (RelatedPost):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.thread:
|
self.fill_empty()
|
||||||
if not self.title:
|
|
||||||
self.title = _('{name} // {first_diff}').format(
|
|
||||||
name = self.related.program.name,
|
|
||||||
first_diff = 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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def info(self):
|
def info(self):
|
||||||
|
@ -90,7 +80,7 @@ class Diffusion (RelatedPost):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Sound (RelatedPost):
|
class Sound (cms.RelatedPost):
|
||||||
"""
|
"""
|
||||||
Publication concerning sound. In order to manage access of sound
|
Publication concerning sound. In order to manage access of sound
|
||||||
files in the filesystem, we use permissions -- it is up to the
|
files in the filesystem, we use permissions -- it is up to the
|
||||||
|
@ -138,3 +128,12 @@ class Sound (RelatedPost):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Publications (qcombine.FakeModel):
|
||||||
|
"""
|
||||||
|
Combine views
|
||||||
|
"""
|
||||||
|
models = [ Article, Program, Diffusion, Sound ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,9 @@ class Diffusions(sections.List):
|
||||||
continue
|
continue
|
||||||
name = post.related.program.name
|
name = post.related.program.name
|
||||||
if name not in post.title:
|
if name not in post.title:
|
||||||
post.title = '{}: {}'.format(name, post.title)
|
post.title = ': ' + post.title if post.title else \
|
||||||
|
' // ' + post.related.start.strftime('%A %d %B')
|
||||||
|
post.title = name + post.title
|
||||||
return object_list
|
return object_list
|
||||||
|
|
||||||
def get_object_list(self):
|
def get_object_list(self):
|
||||||
|
|
|
@ -524,8 +524,10 @@ player = {
|
||||||
/// Select the next track in the current playlist, eventually play it
|
/// Select the next track in the current playlist, eventually play it
|
||||||
next: function(play = true) {
|
next: function(play = true) {
|
||||||
var playlist = this.playlist;
|
var playlist = this.playlist;
|
||||||
|
if(playlist == this.live)
|
||||||
|
return
|
||||||
|
|
||||||
var index = this.playlist.items.indexOf(this.item);
|
var index = this.playlist.items.indexOf(this.item);
|
||||||
console.log(index, this.item, this.playlist.items)
|
|
||||||
if(index == -1)
|
if(index == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user