doc; error in sections with object; update styles and templates

This commit is contained in:
bkfox 2016-06-01 20:07:22 +02:00
parent e0a268505e
commit ad58d3c332
10 changed files with 153 additions and 81 deletions

View File

@ -79,3 +79,15 @@ website, using instance of the Website class:
4. Change templates and css styles if needed. 4. Change templates and css styles if needed.
# Generated content
## CSS
* **.meta**: metadata of any item (author, date, info, tags...)
* **.info**: used to render extra information, usually in lists
The following classes are used for sections (on the section container) and page-wide views (on the <main> tag):
* **.section**: associated to all sections
* **.section_*class***: associated to all section, where name is the name of the classe used to generate the section;
* **.list**: for lists (sections and general list)
* **.detail**: for the detail page view

View File

@ -14,6 +14,7 @@ import bleach
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from aircox.cms import routes from aircox.cms import routes
from aircox.cms import settings
class Comment(models.Model): class Comment(models.Model):
@ -64,6 +65,10 @@ class Comment(models.Model):
attributes=settings.AIRCOX_CMS_BLEACH_COMMENT_ATTRS attributes=settings.AIRCOX_CMS_BLEACH_COMMENT_ATTRS
) )
def save(self, make_safe = True, *args, **kwargs):
if make_safe:
self.make_safe()
return super().save(*args, **kwargs)
class Post (models.Model): class Post (models.Model):
@ -120,19 +125,23 @@ class Post (models.Model):
search_fields = [ 'title', 'content' ] search_fields = [ 'title', 'content' ]
@classmethod @classmethod
def children_of(cl, thread, queryset = None): def get_with_thread(cl, thread = None, queryset = None,
thread_model = None, thread_id = None):
""" """
Return children of the given thread of the cl's type. If queryset Return posts of the cl's type that are children of the given thread.
is not given, use cl.objects as starting queryset.
""" """
if not queryset: if not queryset:
queryset = cl.objects queryset = cl.objects
thread_type = ContentType.objects.get_for_model(thread)
qs = queryset.filter( if thread:
thread_id = thread.pk, thread_model = type(thread)
thread_type__pk = thread_type.id 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
) )
return qs
def get_comments(self): def get_comments(self):
""" """
@ -176,6 +185,11 @@ class Post (models.Model):
) )
self.tags = [ bleach.clean(tag, tags=[]) for tag in self.tags.all() ] self.tags = [ bleach.clean(tag, tags=[]) for tag in self.tags.all() ]
def save(self, make_safe = True, *args, **kwargs):
if make_safe:
self.make_safe()
return super().save(*args, **kwargs)
class Meta: class Meta:
abstract = True abstract = True

View File

@ -23,14 +23,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, website, model, request, **kwargs): def get_queryset(cl, 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, website, model, request, **kwargs): def get_object(cl, 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
""" """
@ -76,7 +76,7 @@ class DetailRoute(Route):
] ]
@classmethod @classmethod
def get_object(cl, website, model, request, pk, **kwargs): def get_object(cl, model, request, pk, **kwargs):
return model.objects.get(pk = int(pk)) return model.objects.get(pk = int(pk))
@ -84,7 +84,7 @@ class AllRoute(Route):
name = 'all' name = 'all'
@classmethod @classmethod
def get_queryset(cl, website, model, request, **kwargs): def get_queryset(cl, model, request, **kwargs):
return model.objects.all() return model.objects.all()
@classmethod @classmethod
@ -108,19 +108,30 @@ class ThreadRoute(Route):
('pk', '[0-9]+'), ('pk', '[0-9]+'),
] ]
@classmethod @classmethod
def get_queryset(cl, website, model, request, thread_model, pk, **kwargs): def get_thread(cl, model, thread_model, pk=None):
"""
Return a model if not pk, otherwise the model element of given id
"""
if type(thread_model) is str: if type(thread_model) is str:
thread_model = website.registry.get(thread_model) thread_model = model._website.registry.get(thread_model)
if not thread_model or not pk:
return thread_model
return thread_model.objects.get(pk=pk)
if not thread_model:
return
thread_model = ContentType.objects.get_for_model(thread_model) @classmethod
return model.objects.filter( def get_queryset(cl, model, request, thread_model, pk, **kwargs):
thread_type = thread_model, thread = cl.get_thread(model, thread_model, pk)
thread_id = int(pk) return model.get_with_thread(thread_model = thread, thread_id = pk)
)
@classmethod
def get_title(cl, model, request, thread_model, pk, **kwargs):
return _('%(name)s: %(model)s') % {
'model': model._meta.verbose_name_plural,
'name': cl.get_thread(model, thread_model, pk).title,
}
class DateRoute(Route): class DateRoute(Route):
@ -132,22 +143,32 @@ class DateRoute(Route):
] ]
@classmethod @classmethod
def get_queryset(cl, website, model, request, year, month, day, **kwargs): def get_queryset(cl, 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),
date__day = int(day), date__day = int(day),
) )
@classmethod
def get_title(cl, model, request, year, month, day, **kwargs):
return _('%(model)s of %(year)/%(month)/%(day)') % {
'model': model._meta.verbose_name_plural,
'year': year,
'month': month,
'day': day
}
class SearchRoute(Route): class SearchRoute(Route):
name = 'search' name = 'search'
@classmethod @classmethod
def get_queryset(cl, website, model, request, **kwargs): def get_queryset(cl, model, request, **kwargs):
q = request.GET.get('q') or '' 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
@ -155,5 +176,10 @@ class SearchRoute(Route):
return model.objects.filter(qs).distinct() return model.objects.filter(qs).distinct()
## TODO: by tag @classmethod
def get_title(cl, model, request, **kwargs):
return _('Search "%(search)s" in %(model)s') % {
'model': model._meta.verbose_name_plural,
'search': request.GET.get('q') or '',
}

View File

@ -39,8 +39,7 @@ class Section(View):
* header: header of the section * header: header of the section
* footer: footer of the section * footer: footer of the section
* object_required: if true and not object has been given, gets angry * force_object: (can be persistent) related object
* object: (can be persistent) related object
""" """
template_name = 'aircox/cms/section.html' template_name = 'aircox/cms/section.html'
@ -54,6 +53,7 @@ class Section(View):
header = '' header = ''
footer = '' footer = ''
object = None object = None
force_object = None
def __init__ (self, *args, **kwargs): def __init__ (self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -85,8 +85,7 @@ class Section(View):
} }
def get(self, request, object=None, **kwargs): def get(self, request, object=None, **kwargs):
if not self.object: self.object = self.force_object or object
self.object = object
self.request = request self.request = request
self.kwargs = kwargs self.kwargs = kwargs
@ -146,6 +145,7 @@ class ListItem:
author = None author = None
date = None date = None
image = None image = None
info = None
detail_url = None detail_url = None
css_class = None css_class = None
@ -153,10 +153,10 @@ class ListItem:
def __init__ (self, post = None, **kwargs): def __init__ (self, post = None, **kwargs):
if post: if post:
self.update_from(post) self.update(post)
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
def update_from(self, post): def update(self, post):
""" """
Update empty fields using the given post Update empty fields using the given post
""" """
@ -177,7 +177,6 @@ class List(Section):
* object_list: force an object list to be used * object_list: force an object list to be used
* url: url to the list in full page * url: url to the list in full page
* message_empty: message to print when list is empty (if not hiding) * message_empty: message to print when list is empty (if not hiding)
* fields: fields of the items to render * fields: fields of the items to render
* image_size: size of the images * image_size: size of the images
* truncate: number of words to keep in content (0 = full content) * truncate: number of words to keep in content (0 = full content)
@ -188,7 +187,7 @@ class List(Section):
url = None url = None
message_empty = _('nothing') message_empty = _('nothing')
fields = [ 'date', 'time', 'image', 'title', 'content' ] fields = [ 'date', 'time', 'image', 'title', 'content', 'info' ]
image_size = '64x64' image_size = '64x64'
truncate = 16 truncate = 16
@ -222,7 +221,9 @@ class Comments(List):
Section used to render comment form and comments list. It renders the Section used to render comment form and comments list. It renders the
form and comments, and save them. form and comments, and save them.
""" """
css_class="comments" title=_('Comments')
css_class='comments'
paginate_by = 0
truncate = 0 truncate = 0
fields = [ 'date', 'time', 'author', 'content' ] fields = [ 'date', 'time', 'author', 'content' ]
@ -259,11 +260,10 @@ class Comments(List):
""" """
Forward data to this view Forward data to this view
""" """
# TODO: comment satanize
comment_form = CommentForm(request.POST) comment_form = CommentForm(request.POST)
if comment_form.is_valid(): if comment_form.is_valid():
comment = comment_form.save(commit=False) comment = comment_form.save(commit=False)
comment.thread = self.object comment.thread = object
comment.published = view.website.auto_publish_comments comment.published = view.website.auto_publish_comments
comment.save() comment.save()

View File

@ -4,7 +4,6 @@ body {
margin: 0; margin: 0;
} }
.page { .page {
display: flex; display: flex;
} }
@ -52,35 +51,34 @@ main .section {
/** comments **/ /** comments **/
.comment_form label { .comments form input:not([type=checkbox]),
display: none; .comments form textarea {
}
.comment_form input:not([type=checkbox]),
.comment_form textarea {
display: inline-block; display: inline-block;
width: calc(100% - 5em); width: 100%;
max-height: 6em; max-height: 6em;
margin: 0.2em 0em; margin: 0.2em 0em;
padding: 0.2em;
} }
.comment_form input[type=checkbox], .comments form input[type=checkbox],
.comment_form button[type=submit] { .comments form button[type=submit] {
max-width: 4em;
vertical-align:bottom; vertical-align:bottom;
margin: 0.2em 0em; margin: 0.2em 0em;
text-align: center; text-align: center;
} }
.comment_form .extra { .comments form button[type=submit] {
float: right;
}
.comments form #show_more:not(:checked) ~ .extra {
display: none; display: none;
} }
.comment_form input[type="checkbox"]:checked + .extra { .comments label[for="show_more"] {
display: block; font-size: 0.8em;
} }

View File

@ -7,19 +7,23 @@
{% if comment_form %} {% if comment_form %}
{% with comment_form as form %} {% with comment_form as form %}
{{ form.non_field_errors }} {{ form.non_field_errors }}
<form action="" method="POST" class="comment_form"> <form action="" method="POST">
{% csrf_token %} {% csrf_token %}
{% render_honeypot_field "hp_website" %} {% render_honeypot_field "hp_website" %}
<div> <div>
{{ form.author.errors }} {{ form.author.errors }}
{{ form.author }} {{ form.author }}
<input type="checkbox" value="1"> <div>
<input type="checkbox" value="1" id="show_more">
<label for="show_more">{% trans "show more options" %}</label>
<div class="extra"> <div class="extra">
{{ form.email.errors }} {{ form.email.errors }}
{{ form.email }} {{ form.email }}
{{ form.url.errors }} {{ form.url.errors }}
{{ form.url }} {{ form.url }}
</div> </div>
</div>
</div> </div>
<div> <div>
{{ form.content.errors }} {{ form.content.errors }}

View File

@ -17,7 +17,6 @@
<a href="{{ item.detail_url }}"> <a href="{{ item.detail_url }}">
{% endif %} {% endif %}
{% if 'date' in list.fields or 'time' in list.fields or 'author' in list.fields %}
<div class="meta"> <div class="meta">
{% if item.date and 'date' in list.fields or 'time' in list.fields %} {% if item.date and 'date' in list.fields or 'time' in list.fields %}
<time datetime="{{ item.date }}"> <time datetime="{{ item.date }}">
@ -38,19 +37,25 @@
{{ item.author }} {{ item.author }}
</span> </span>
{% endif %} {% endif %}
</div>
{% if item.info and 'info' in list.fields %}
<span class="info">
{{ item.info }}
</span>
{% endif %} {% endif %}
</div>
{% if 'image' in list.fields and item.image %} {% if 'image' in list.fields and item.image %}
<img src="{% thumbnail item.image list.image_size crop %}"> <img src="{% thumbnail item.image list.image_size crop %}">
{% endif %} {% endif %}
{% if 'title' in list.fields %} <div class="content">
{% if 'title' in list.fields and item.title %}
<h2 class="title">{{ item.title }}</h2> <h2 class="title">{{ item.title }}</h2>
{% endif %} {% endif %}
{% if 'content' in list.fields %} {% if 'content' in list.fields and item.content %}
<div class="content"> <div class="text">
{% if list.truncate %} {% if list.truncate %}
{{ item.content|striptags|truncatewords:list.truncate }} {{ item.content|striptags|truncatewords:list.truncate }}
{% else %} {% else %}
@ -58,6 +63,7 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
</div>
{% if item.detail_url %} {% if item.detail_url %}
</a> </a>
@ -72,8 +78,9 @@
{% if page_obj or list.url %} {% if page_obj or list.url %}
<nav> <nav>
{% if not page_obj or embed %} {% if not page_obj or embed %}
<a href="{{list.url}}" title={% trans "More elements" %}>&#8690;</a> {% comment %}link to show more elements of the list{% endcomment %}
{% else %} <a href="{{list.url}}">{% trans "&#8226;&#8226;&#8226;" %}</a>
{% elif page_obj.paginator.num_pages > 1 %}
{% with page_obj.paginator.num_pages as num_pages %} {% with page_obj.paginator.num_pages as num_pages %}
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a> <a href="?page={{ page_obj.previous_page_number }}">previous</a>

View File

@ -1,5 +1,5 @@
<{{ tag }} class="{{ css_class }}" <{{ tag }} {% if css_class %} class="{{ css_class }}" {% endif %}
{% for k, v in attrs.items %} {% for k, v in attrs.items %}
{{ k }} = "{{ v|addslashes }}" {{ k }} = "{{ v|addslashes }}"
{% endfor %} > {% endfor %} >
@ -15,7 +15,7 @@
{% block header %} {% block header %}
{% if header %} {% if header %}
<header> <header>
{{ header }} {{ header|safe }}
</header> </header>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
@ -27,7 +27,7 @@
{% block footer %} {% block footer %}
{% if footer %} {% if footer %}
<footer> <footer>
{{ footer }} {{ footer|safe }}
</footer> </footer>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -32,7 +32,10 @@
{{ menus.left|safe }} {{ menus.left|safe }}
{% endif %} {% endif %}
<main> <main {% if css_class %} class="{{ css_class }}" {% endif %}
{% for k, v in attrs.items %}
{{ k }} = "{{ v|addslashes }}"
{% endfor %} >
{% endif %} {% endif %}
{% if messages %} {% if messages %}
<ul class="messages"> <ul class="messages">

View File

@ -49,7 +49,7 @@ class PostListView(PostBaseView, ListView):
""" """
template_name = 'aircox/cms/list.html' template_name = 'aircox/cms/list.html'
allow_empty = True allow_empty = True
paginate_by = 3 paginate_by = 25
model = None model = None
route = None route = None
@ -64,11 +64,13 @@ class PostListView(PostBaseView, ListView):
def get_queryset(self): def get_queryset(self):
if self.route: if self.route:
qs = self.route.get_queryset(self.website, self.model, self.request, qs = self.route.get_queryset(self.model, self.request,
**self.kwargs) **self.kwargs)
else: else:
qs = self.queryset or self.model.objects.all() qs = self.queryset or self.model.objects.all()
qs = qs.filter(published = True)
query = self.request.GET query = self.request.GET
if query.get('exclude'): if query.get('exclude'):
qs = qs.exclude(id = int(query['exclude'])) qs = qs.exclude(id = int(query['exclude']))
@ -93,11 +95,16 @@ class PostListView(PostBaseView, ListView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context.update(self.get_base_context(**kwargs)) context.update(self.get_base_context(**kwargs))
title = self.title if self.title else \ if self.title:
self.route and self.route.get_title(self.model, self.request, title = self.title
else:
title = self.route and \
self.route.get_title(self.model, self.request,
**self.kwargs) **self.kwargs)
context['title'] = title context['title'] = title
context['base_template'] = 'aircox/cms/website.html' context['base_template'] = 'aircox/cms/website.html'
context['css_class'] = 'list'
if not self.list: if not self.list:
import aircox.cms.sections as sections import aircox.cms.sections as sections
@ -153,6 +160,7 @@ class PostDetailView(DetailView, PostBaseView):
section.get(request = self.request, **kwargs) section.get(request = self.request, **kwargs)
for section in self.sections for section in self.sections
]) ])
context['css_class'] = 'detail'
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -165,7 +173,7 @@ class PostDetailView(DetailView, PostBaseView):
self.comments = section self.comments = section
self.object = self.get_object() self.object = self.get_object()
self.comments.post(self, request, object) self.comments.post(self, request, self.object)
return self.get(request, *args, **kwargs) return self.get(request, *args, **kwargs)