fix error with tags; add callable for related bindings; comments in PostListView; ...
This commit is contained in:
parent
ad58d3c332
commit
392d48ac0c
|
@ -40,6 +40,10 @@ class MyModelPost(RelatedPost):
|
|||
}
|
||||
```
|
||||
|
||||
Note: it is possible to assign a function as a bounded value; in such case, the
|
||||
function will be called using arguments **(post, related_object)**.
|
||||
|
||||
|
||||
## Routes
|
||||
Routes are used to generate the URLs of the website. We provide some of the
|
||||
common routes: for the detail view of course, but also to select all posts or
|
||||
|
|
|
@ -17,7 +17,33 @@ from aircox.cms import routes
|
|||
from aircox.cms import settings
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
class Routable:
|
||||
@classmethod
|
||||
def get_with_thread(cl, thread = None, queryset = None,
|
||||
thread_model = None, thread_id = None):
|
||||
"""
|
||||
Return posts of the cl's type that are children of the given thread.
|
||||
"""
|
||||
if not queryset:
|
||||
queryset = cl.objects
|
||||
|
||||
if thread:
|
||||
thread_model = type(thread)
|
||||
thread_id = thread.id
|
||||
|
||||
thread_model = ContentType.objects.get_for_model(thread_model)
|
||||
return queryset.filter(
|
||||
thread_id = thread_id,
|
||||
thread_type__pk = thread_model.id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def route_url(cl, route, kwargs = None):
|
||||
name = cl._website.name_of_model(cl)
|
||||
name = route.get_view_name(name)
|
||||
return reverse(name, kwargs = kwargs)
|
||||
|
||||
class Comment(models.Model, Routable):
|
||||
thread_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.SET_NULL,
|
||||
|
@ -71,7 +97,7 @@ class Comment(models.Model):
|
|||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Post (models.Model):
|
||||
class Post (models.Model, Routable):
|
||||
"""
|
||||
Base model that can be used as is if wanted. Represent a generic
|
||||
publication on the website.
|
||||
|
@ -115,34 +141,15 @@ class Post (models.Model):
|
|||
default = '',
|
||||
)
|
||||
image = models.ImageField(
|
||||
blank = True, null = True
|
||||
blank = True, null = True,
|
||||
)
|
||||
tags = TaggableManager(
|
||||
_('tags'),
|
||||
verbose_name = _('tags'),
|
||||
blank = True,
|
||||
)
|
||||
|
||||
search_fields = [ 'title', 'content' ]
|
||||
|
||||
@classmethod
|
||||
def get_with_thread(cl, thread = None, queryset = None,
|
||||
thread_model = None, thread_id = None):
|
||||
"""
|
||||
Return posts of the cl's type that are children of the given thread.
|
||||
"""
|
||||
if not queryset:
|
||||
queryset = cl.objects
|
||||
|
||||
if thread:
|
||||
thread_model = type(thread)
|
||||
thread_id = thread.id
|
||||
|
||||
thread_model = ContentType.objects.get_for_model(thread_model)
|
||||
return queryset.filter(
|
||||
thread_id = thread_id,
|
||||
thread_type__pk = thread_model.id
|
||||
)
|
||||
|
||||
def get_comments(self):
|
||||
"""
|
||||
Return comments pointing to this post
|
||||
|
@ -166,12 +173,6 @@ class Post (models.Model):
|
|||
)
|
||||
return qs
|
||||
|
||||
@classmethod
|
||||
def route_url(cl, route, kwargs = None):
|
||||
name = cl._website.name_of_model(cl)
|
||||
name = route.get_view_name(name)
|
||||
return reverse(name, kwargs = kwargs)
|
||||
|
||||
def make_safe(self):
|
||||
self.title = bleach.clean(
|
||||
self.title,
|
||||
|
@ -183,11 +184,18 @@ class Post (models.Model):
|
|||
tags=settings.AIRCOX_CMS_BLEACH_CONTENT_TAGS,
|
||||
attributes=settings.AIRCOX_CMS_BLEACH_CONTENT_ATTRS
|
||||
)
|
||||
self.tags = [ bleach.clean(tag, tags=[]) for tag in self.tags.all() ]
|
||||
if self.pk:
|
||||
self.tags.set(*[
|
||||
bleach.clean(tag, tags=[])
|
||||
for tag in self.tags.all()
|
||||
])
|
||||
|
||||
def save(self, make_safe = True, *args, **kwargs):
|
||||
if make_safe:
|
||||
self.make_safe()
|
||||
if self.date and self.date.tzinfo is None or \
|
||||
self.date.tzinfo.utcoffset(self.date) is None:
|
||||
timezone.make_aware(self.date)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
|
@ -353,6 +361,9 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
|||
to update the post thread to the correct Post model (in order to
|
||||
establish a parent-child relation between two models)
|
||||
|
||||
When a callable is set as bound value, it will be called to retrieve
|
||||
the value, as: callable_func(post, related)
|
||||
|
||||
Note: bound values can be any value, not only Django field.
|
||||
* post_to_rel: auto update related object when post is updated
|
||||
* rel_to_post: auto update the post when related object is updated
|
||||
|
@ -380,6 +391,8 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
|||
|
||||
def get_rel_attr(self, attr):
|
||||
attr = self._relation.bindings.get(attr)
|
||||
if callable(attr):
|
||||
return attr(self, self.related)
|
||||
return getattr(self.related, attr) if attr else None
|
||||
|
||||
def set_rel_attr(self, attr, value):
|
||||
|
@ -432,8 +445,8 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
|||
for attr, rel_attr in rel.bindings.items():
|
||||
if attr == 'thread':
|
||||
continue
|
||||
self.set_rel_attr
|
||||
value = getattr(self.related, rel_attr)
|
||||
value = rel_attr(self, self.related) if callable(rel_attr) else \
|
||||
getattr(self.related, rel_attr)
|
||||
set_attr(attr, value)
|
||||
|
||||
if rel.thread_model:
|
||||
|
@ -453,14 +466,16 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
|||
if self.pk and self._relation.rel_to_post:
|
||||
self.rel_to_post(False)
|
||||
|
||||
def save (self, avoid_sync = False, *args, **kwargs):
|
||||
def save (self, avoid_sync = False, save = True, *args, **kwargs):
|
||||
"""
|
||||
If avoid_relation, do not synchronise the post/related object.
|
||||
* avoid_sync: do not synchronise the post/related object;
|
||||
* save: if False, does not call parent save functions
|
||||
"""
|
||||
if not avoid_sync:
|
||||
if not self.pk and self._relation.rel_to_post:
|
||||
self.rel_to_post(False)
|
||||
if self._relation.post_to_rel:
|
||||
self.post_to_rel(True)
|
||||
if save:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
|
|
@ -108,7 +108,6 @@ class ThreadRoute(Route):
|
|||
('pk', '[0-9]+'),
|
||||
]
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_thread(cl, model, thread_model, pk=None):
|
||||
"""
|
||||
|
@ -183,3 +182,4 @@ class SearchRoute(Route):
|
|||
'search': request.GET.get('q') or '',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ class Section(View):
|
|||
to Section configuration/rendering. However, some data are considered
|
||||
as temporary, and are reset at each rendering, using given arguments.
|
||||
|
||||
When get_context_data returns None, returns an empty string
|
||||
|
||||
! Important Note: values given for rendering are considered as safe
|
||||
HTML in templates.
|
||||
|
||||
|
@ -90,6 +92,8 @@ class Section(View):
|
|||
self.kwargs = kwargs
|
||||
|
||||
context = self.get_context_data()
|
||||
if not context:
|
||||
return ''
|
||||
return render_to_string(self.template_name, context, request=request)
|
||||
|
||||
|
||||
|
@ -207,11 +211,15 @@ class List(Section):
|
|||
return self.object_list
|
||||
|
||||
def get_context_data(self):
|
||||
object_list = self.object_list or self.get_object_list()
|
||||
if not object_list and not self.message_empty:
|
||||
return
|
||||
|
||||
context = super().get_context_data()
|
||||
context.update({
|
||||
'base_template': 'aircox/cms/section.html',
|
||||
'list': self,
|
||||
'object_list': self.object_list or self.get_object_list(),
|
||||
'object_list': object_list,
|
||||
})
|
||||
return context
|
||||
|
||||
|
@ -223,9 +231,9 @@ class Comments(List):
|
|||
"""
|
||||
title=_('Comments')
|
||||
css_class='comments'
|
||||
paginate_by = 0
|
||||
truncate = 0
|
||||
fields = [ 'date', 'time', 'author', 'content' ]
|
||||
message_empty = _('no comment yet')
|
||||
|
||||
comment_form = None
|
||||
success_message = ( _('Your message is awaiting for approval'),
|
||||
|
@ -240,6 +248,19 @@ class Comments(List):
|
|||
attrs={ 'id': comment.id })
|
||||
for comment in qs ]
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
import aircox.cms.models as models
|
||||
import aircox.cms.routes as routes
|
||||
if self.object:
|
||||
return models.Comment.route_url(routes.ThreadRoute, {
|
||||
'pk': self.object.id,
|
||||
'thread_model': self.object._website.name_of_model(
|
||||
self.object.__class__
|
||||
),
|
||||
})
|
||||
return ''
|
||||
|
||||
def get_context_data(self):
|
||||
post = self.object
|
||||
if hasattr(post, 'allow_comments') and post.allow_comments:
|
||||
|
|
|
@ -6,25 +6,24 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block pre_title %}
|
||||
<div class="pre_title metadata">
|
||||
{% if object.thread %}
|
||||
<div class="threads">
|
||||
<div class="pre_title meta">
|
||||
{% if object.thread %}
|
||||
<div class="threads">
|
||||
{{ object|threads:' > '|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<time datetime="{{ object.date }}">
|
||||
<time datetime="{{ object.date }}">
|
||||
{{ object.date|date:'l d F Y' }},
|
||||
{{ object.date|time:'H\hi' }}
|
||||
</time>
|
||||
</time>
|
||||
|
||||
{% if object.tags %}
|
||||
{# TODO: url to the tags #}
|
||||
<div class="tags">
|
||||
{% if object.tags.all %}
|
||||
{# TODO: url to the tags #}
|
||||
<div class="tags">
|
||||
{{ object.tags.all|join:', ' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
21
cms/views.py
21
cms/views.py
|
@ -44,20 +44,26 @@ class PostListView(PostBaseView, ListView):
|
|||
* embed: view is embedded, render only the list
|
||||
* exclude: exclude item of the given id
|
||||
* order: 'desc' or 'asc'
|
||||
* fields: fields to render
|
||||
* page: page number
|
||||
"""
|
||||
template_name = 'aircox/cms/list.html'
|
||||
allow_empty = True
|
||||
paginate_by = 25
|
||||
paginate_by = 30
|
||||
model = None
|
||||
|
||||
route = None
|
||||
list = None
|
||||
css_class = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if not self.list:
|
||||
self.list = sections.List(
|
||||
truncate = 32,
|
||||
fields = [ 'date', 'time', 'image', 'title', 'content' ],
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.route = self.kwargs.get('route') or self.route
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -104,15 +110,8 @@ class PostListView(PostBaseView, ListView):
|
|||
|
||||
context['title'] = title
|
||||
context['base_template'] = 'aircox/cms/website.html'
|
||||
context['css_class'] = 'list'
|
||||
|
||||
if not self.list:
|
||||
import aircox.cms.sections as sections
|
||||
self.list = sections.List(
|
||||
truncate = 32,
|
||||
fields = [ 'date', 'time', 'image', 'title', 'content' ],
|
||||
)
|
||||
|
||||
context['css_class'] = 'list' if not self.css_class else \
|
||||
'list ' + self.css_class
|
||||
context['list'] = self.list
|
||||
# FIXME: list.url = if self.route: self.model(self.route, self.kwargs) else ''
|
||||
return context
|
||||
|
|
|
@ -18,12 +18,13 @@ class Website:
|
|||
# user interaction
|
||||
allow_comments = True
|
||||
auto_publish_comments = False
|
||||
comments_routes = True
|
||||
|
||||
# components
|
||||
urls = []
|
||||
registry = {}
|
||||
|
||||
def __init__ (self, menus = None, **kwargs):
|
||||
def __init__(self, menus = None, **kwargs):
|
||||
"""
|
||||
* menus: a list of menus to add to the website
|
||||
"""
|
||||
|
@ -36,13 +37,34 @@ class Website:
|
|||
for menu in menus:
|
||||
self.set_menu(menu)
|
||||
|
||||
if self.comments_routes:
|
||||
self.register_comments_routes()
|
||||
|
||||
def name_of_model (self, model):
|
||||
|
||||
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):
|
||||
def register_comments_routes(self):
|
||||
"""
|
||||
Register routes for comments, for the moment, only:
|
||||
* ThreadRoute
|
||||
"""
|
||||
import aircox.cms.models as models
|
||||
import aircox.cms.sections as sections
|
||||
|
||||
self.register_list(
|
||||
'comment', models.Comment,
|
||||
routes = [routes.ThreadRoute],
|
||||
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.
|
||||
|
@ -54,7 +76,7 @@ class Website:
|
|||
model._website = self
|
||||
return name
|
||||
|
||||
def register_detail (self, name, model, view = views.PostDetailView,
|
||||
def register_detail(self, name, model, view = views.PostDetailView,
|
||||
**view_kwargs):
|
||||
"""
|
||||
Register a model and the detail view
|
||||
|
@ -68,7 +90,7 @@ class Website:
|
|||
self.urls.append(routes.DetailRoute.as_url(name, view))
|
||||
self.registry[name] = model
|
||||
|
||||
def register_list (self, name, model, view = views.PostListView,
|
||||
def register_list(self, name, model, view = views.PostListView,
|
||||
routes = [], **view_kwargs):
|
||||
"""
|
||||
Register a model and the given list view using the given routes
|
||||
|
@ -82,7 +104,7 @@ class Website:
|
|||
self.urls += [ route.as_url(name, view) for route in routes ]
|
||||
self.registry[name] = model
|
||||
|
||||
def register (self, name, model, sections = None, routes = None,
|
||||
def register(self, name, model, sections = None, routes = None,
|
||||
list_view = views.PostListView,
|
||||
detail_view = views.PostDetailView,
|
||||
list_kwargs = {}, detail_kwargs = {}):
|
||||
|
@ -104,7 +126,7 @@ class Website:
|
|||
**list_kwargs
|
||||
)
|
||||
|
||||
def set_menu (self, menu):
|
||||
def set_menu(self, menu):
|
||||
"""
|
||||
Set a menu, and remove any previous menu at the same position
|
||||
"""
|
||||
|
@ -114,7 +136,7 @@ class Website:
|
|||
menu.tag = 'side'
|
||||
self.menus[menu.position] = menu
|
||||
|
||||
def get_menu (self, position):
|
||||
def get_menu(self, position):
|
||||
"""
|
||||
Get an enabled menu by its position
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue
Block a user