doc; error in sections with object; update styles and templates
This commit is contained in:
parent
e0a268505e
commit
ad58d3c332
|
@ -79,3 +79,15 @@ website, using instance of the Website class:
|
|||
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
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import bleach
|
|||
from taggit.managers import TaggableManager
|
||||
|
||||
from aircox.cms import routes
|
||||
from aircox.cms import settings
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
|
@ -64,6 +65,10 @@ class Comment(models.Model):
|
|||
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):
|
||||
|
@ -120,19 +125,23 @@ class Post (models.Model):
|
|||
search_fields = [ 'title', 'content' ]
|
||||
|
||||
@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
|
||||
is not given, use cl.objects as starting queryset.
|
||||
Return posts of the cl's type that are children of the given thread.
|
||||
"""
|
||||
if not queryset:
|
||||
queryset = cl.objects
|
||||
thread_type = ContentType.objects.get_for_model(thread)
|
||||
qs = queryset.filter(
|
||||
thread_id = thread.pk,
|
||||
thread_type__pk = thread_type.id
|
||||
|
||||
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
|
||||
)
|
||||
return qs
|
||||
|
||||
def get_comments(self):
|
||||
"""
|
||||
|
@ -176,6 +185,11 @@ class Post (models.Model):
|
|||
)
|
||||
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:
|
||||
abstract = True
|
||||
|
||||
|
|
|
@ -23,14 +23,14 @@ class Route:
|
|||
url_args = [] # arguments passed from the url [ (name : regex),... ]
|
||||
|
||||
@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
|
||||
"""
|
||||
pass
|
||||
|
||||
@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
|
||||
"""
|
||||
|
@ -76,7 +76,7 @@ class DetailRoute(Route):
|
|||
]
|
||||
|
||||
@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))
|
||||
|
||||
|
||||
|
@ -84,7 +84,7 @@ class AllRoute(Route):
|
|||
name = 'all'
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cl, website, model, request, **kwargs):
|
||||
def get_queryset(cl, model, request, **kwargs):
|
||||
return model.objects.all()
|
||||
|
||||
@classmethod
|
||||
|
@ -108,19 +108,30 @@ class ThreadRoute(Route):
|
|||
('pk', '[0-9]+'),
|
||||
]
|
||||
|
||||
|
||||
@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:
|
||||
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)
|
||||
return model.objects.filter(
|
||||
thread_type = thread_model,
|
||||
thread_id = int(pk)
|
||||
)
|
||||
@classmethod
|
||||
def get_queryset(cl, model, request, thread_model, pk, **kwargs):
|
||||
thread = cl.get_thread(model, thread_model, 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):
|
||||
|
@ -132,22 +143,32 @@ class DateRoute(Route):
|
|||
]
|
||||
|
||||
@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(
|
||||
date__year = int(year),
|
||||
date__month = int(month),
|
||||
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):
|
||||
name = 'search'
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cl, website, model, request, **kwargs):
|
||||
def get_queryset(cl, model, request, **kwargs):
|
||||
q = request.GET.get('q') or ''
|
||||
qs = None
|
||||
|
||||
## TODO: by tag
|
||||
for search_field in model.search_fields or []:
|
||||
r = models.Q(**{ search_field + '__icontains': q })
|
||||
if qs: qs = qs | r
|
||||
|
@ -155,5 +176,10 @@ class SearchRoute(Route):
|
|||
|
||||
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 '',
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,7 @@ class Section(View):
|
|||
* header: header of the section
|
||||
* footer: footer of the section
|
||||
|
||||
* object_required: if true and not object has been given, gets angry
|
||||
* object: (can be persistent) related object
|
||||
* force_object: (can be persistent) related object
|
||||
|
||||
"""
|
||||
template_name = 'aircox/cms/section.html'
|
||||
|
@ -54,6 +53,7 @@ class Section(View):
|
|||
header = ''
|
||||
footer = ''
|
||||
object = None
|
||||
force_object = None
|
||||
|
||||
def __init__ (self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -85,8 +85,7 @@ class Section(View):
|
|||
}
|
||||
|
||||
def get(self, request, object=None, **kwargs):
|
||||
if not self.object:
|
||||
self.object = object
|
||||
self.object = self.force_object or object
|
||||
self.request = request
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
@ -146,6 +145,7 @@ class ListItem:
|
|||
author = None
|
||||
date = None
|
||||
image = None
|
||||
info = None
|
||||
detail_url = None
|
||||
|
||||
css_class = None
|
||||
|
@ -153,10 +153,10 @@ class ListItem:
|
|||
|
||||
def __init__ (self, post = None, **kwargs):
|
||||
if post:
|
||||
self.update_from(post)
|
||||
self.update(post)
|
||||
self.__dict__.update(**kwargs)
|
||||
|
||||
def update_from(self, post):
|
||||
def update(self, post):
|
||||
"""
|
||||
Update empty fields using the given post
|
||||
"""
|
||||
|
@ -177,7 +177,6 @@ class List(Section):
|
|||
* object_list: force an object list to be used
|
||||
* url: url to the list in full page
|
||||
* message_empty: message to print when list is empty (if not hiding)
|
||||
|
||||
* fields: fields of the items to render
|
||||
* image_size: size of the images
|
||||
* truncate: number of words to keep in content (0 = full content)
|
||||
|
@ -188,7 +187,7 @@ class List(Section):
|
|||
url = None
|
||||
message_empty = _('nothing')
|
||||
|
||||
fields = [ 'date', 'time', 'image', 'title', 'content' ]
|
||||
fields = [ 'date', 'time', 'image', 'title', 'content', 'info' ]
|
||||
image_size = '64x64'
|
||||
truncate = 16
|
||||
|
||||
|
@ -222,7 +221,9 @@ class Comments(List):
|
|||
Section used to render comment form and comments list. It renders the
|
||||
form and comments, and save them.
|
||||
"""
|
||||
css_class="comments"
|
||||
title=_('Comments')
|
||||
css_class='comments'
|
||||
paginate_by = 0
|
||||
truncate = 0
|
||||
fields = [ 'date', 'time', 'author', 'content' ]
|
||||
|
||||
|
@ -259,11 +260,10 @@ class Comments(List):
|
|||
"""
|
||||
Forward data to this view
|
||||
"""
|
||||
# TODO: comment satanize
|
||||
comment_form = CommentForm(request.POST)
|
||||
if comment_form.is_valid():
|
||||
comment = comment_form.save(commit=False)
|
||||
comment.thread = self.object
|
||||
comment.thread = object
|
||||
comment.published = view.website.auto_publish_comments
|
||||
comment.save()
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ body {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -52,35 +51,34 @@ main .section {
|
|||
|
||||
|
||||
/** comments **/
|
||||
.comment_form label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment_form input:not([type=checkbox]),
|
||||
.comment_form textarea {
|
||||
.comments form input:not([type=checkbox]),
|
||||
.comments form textarea {
|
||||
display: inline-block;
|
||||
width: calc(100% - 5em);
|
||||
width: 100%;
|
||||
max-height: 6em;
|
||||
margin: 0.2em 0em;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.comment_form input[type=checkbox],
|
||||
.comment_form button[type=submit] {
|
||||
max-width: 4em;
|
||||
.comments form input[type=checkbox],
|
||||
.comments form button[type=submit] {
|
||||
vertical-align:bottom;
|
||||
margin: 0.2em 0em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comment_form .extra {
|
||||
.comments form button[type=submit] {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.comments form #show_more:not(:checked) ~ .extra {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment_form input[type="checkbox"]:checked + .extra {
|
||||
display: block;
|
||||
.comments label[for="show_more"] {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,18 +7,22 @@
|
|||
{% if comment_form %}
|
||||
{% with comment_form as form %}
|
||||
{{ form.non_field_errors }}
|
||||
<form action="" method="POST" class="comment_form">
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{% render_honeypot_field "hp_website" %}
|
||||
<div>
|
||||
{{ form.author.errors }}
|
||||
{{ form.author }}
|
||||
<input type="checkbox" value="1">
|
||||
<div class="extra">
|
||||
{{ form.email.errors }}
|
||||
{{ form.email }}
|
||||
{{ form.url.errors }}
|
||||
{{ form.url }}
|
||||
<div>
|
||||
<input type="checkbox" value="1" id="show_more">
|
||||
<label for="show_more">{% trans "show more options" %}</label>
|
||||
<div class="extra">
|
||||
{{ form.email.errors }}
|
||||
{{ form.email }}
|
||||
{{ form.url.errors }}
|
||||
{{ form.url }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
<a href="{{ item.detail_url }}">
|
||||
{% endif %}
|
||||
|
||||
{% if 'date' in list.fields or 'time' in list.fields or 'author' in list.fields %}
|
||||
<div class="meta">
|
||||
{% if item.date and 'date' in list.fields or 'time' in list.fields %}
|
||||
<time datetime="{{ item.date }}">
|
||||
|
@ -38,26 +37,33 @@
|
|||
{{ item.author }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.info and 'info' in list.fields %}
|
||||
<span class="info">
|
||||
{{ item.info }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if 'image' in list.fields and item.image %}
|
||||
<img src="{% thumbnail item.image list.image_size crop %}">
|
||||
{% endif %}
|
||||
|
||||
{% if 'title' in list.fields %}
|
||||
<h2 class="title">{{ item.title }}</h2>
|
||||
{% endif %}
|
||||
|
||||
{% if 'content' in list.fields %}
|
||||
<div class="content">
|
||||
{% if list.truncate %}
|
||||
{{ item.content|striptags|truncatewords:list.truncate }}
|
||||
{% else %}
|
||||
{{ item.content|striptags }}
|
||||
{% if 'title' in list.fields and item.title %}
|
||||
<h2 class="title">{{ item.title }}</h2>
|
||||
{% endif %}
|
||||
|
||||
{% if 'content' in list.fields and item.content %}
|
||||
<div class="text">
|
||||
{% if list.truncate %}
|
||||
{{ item.content|striptags|truncatewords:list.truncate }}
|
||||
{% else %}
|
||||
{{ item.content|striptags }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if item.detail_url %}
|
||||
</a>
|
||||
|
@ -72,8 +78,9 @@
|
|||
{% if page_obj or list.url %}
|
||||
<nav>
|
||||
{% if not page_obj or embed %}
|
||||
<a href="{{list.url}}" title={% trans "More elements" %}>⇲</a>
|
||||
{% else %}
|
||||
{% comment %}link to show more elements of the list{% endcomment %}
|
||||
<a href="{{list.url}}">{% trans "•••" %}</a>
|
||||
{% elif page_obj.paginator.num_pages > 1 %}
|
||||
{% with page_obj.paginator.num_pages as num_pages %}
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
<{{ tag }} class="{{ css_class }}"
|
||||
<{{ tag }} {% if css_class %} class="{{ css_class }}" {% endif %}
|
||||
{% for k, v in attrs.items %}
|
||||
{{ k }} = "{{ v|addslashes }}"
|
||||
{% endfor %} >
|
||||
|
@ -15,7 +15,7 @@
|
|||
{% block header %}
|
||||
{% if header %}
|
||||
<header>
|
||||
{{ header }}
|
||||
{{ header|safe }}
|
||||
</header>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -27,7 +27,7 @@
|
|||
{% block footer %}
|
||||
{% if footer %}
|
||||
<footer>
|
||||
{{ footer }}
|
||||
{{ footer|safe }}
|
||||
</footer>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
{{ menus.left|safe }}
|
||||
{% endif %}
|
||||
|
||||
<main>
|
||||
<main {% if css_class %} class="{{ css_class }}" {% endif %}
|
||||
{% for k, v in attrs.items %}
|
||||
{{ k }} = "{{ v|addslashes }}"
|
||||
{% endfor %} >
|
||||
{% endif %}
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
|
|
20
cms/views.py
20
cms/views.py
|
@ -49,7 +49,7 @@ class PostListView(PostBaseView, ListView):
|
|||
"""
|
||||
template_name = 'aircox/cms/list.html'
|
||||
allow_empty = True
|
||||
paginate_by = 3
|
||||
paginate_by = 25
|
||||
model = None
|
||||
|
||||
route = None
|
||||
|
@ -64,11 +64,13 @@ class PostListView(PostBaseView, ListView):
|
|||
|
||||
def get_queryset(self):
|
||||
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)
|
||||
else:
|
||||
qs = self.queryset or self.model.objects.all()
|
||||
|
||||
qs = qs.filter(published = True)
|
||||
|
||||
query = self.request.GET
|
||||
if query.get('exclude'):
|
||||
qs = qs.exclude(id = int(query['exclude']))
|
||||
|
@ -93,11 +95,16 @@ class PostListView(PostBaseView, ListView):
|
|||
context = super().get_context_data(**kwargs)
|
||||
context.update(self.get_base_context(**kwargs))
|
||||
|
||||
title = self.title if self.title else \
|
||||
self.route and self.route.get_title(self.model, self.request,
|
||||
**self.kwargs)
|
||||
if self.title:
|
||||
title = self.title
|
||||
else:
|
||||
title = self.route and \
|
||||
self.route.get_title(self.model, self.request,
|
||||
**self.kwargs)
|
||||
|
||||
context['title'] = title
|
||||
context['base_template'] = 'aircox/cms/website.html'
|
||||
context['css_class'] = 'list'
|
||||
|
||||
if not self.list:
|
||||
import aircox.cms.sections as sections
|
||||
|
@ -153,6 +160,7 @@ class PostDetailView(DetailView, PostBaseView):
|
|||
section.get(request = self.request, **kwargs)
|
||||
for section in self.sections
|
||||
])
|
||||
context['css_class'] = 'detail'
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
@ -165,7 +173,7 @@ class PostDetailView(DetailView, PostBaseView):
|
|||
self.comments = section
|
||||
|
||||
self.object = self.get_object()
|
||||
self.comments.post(self, request, object)
|
||||
self.comments.post(self, request, self.object)
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user