diff --git a/cms/README.md b/cms/README.md index 03b3f7d..5f3d89c 100644 --- a/cms/README.md +++ b/cms/README.md @@ -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
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 + + diff --git a/cms/models.py b/cms/models.py index 29aecde..3f51579 100644 --- a/cms/models.py +++ b/cms/models.py @@ -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 diff --git a/cms/routes.py b/cms/routes.py index 10c0708..fb79976 100644 --- a/cms/routes.py +++ b/cms/routes.py @@ -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 '', + } diff --git a/cms/sections.py b/cms/sections.py index 94a5542..6577fa3 100644 --- a/cms/sections.py +++ b/cms/sections.py @@ -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() diff --git a/cms/static/aircox/cms/styles.css b/cms/static/aircox/cms/styles.css index ff6a84a..98bf44e 100644 --- a/cms/static/aircox/cms/styles.css +++ b/cms/static/aircox/cms/styles.css @@ -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; } - diff --git a/cms/templates/aircox/cms/comments.html b/cms/templates/aircox/cms/comments.html index 730adec..3417e57 100644 --- a/cms/templates/aircox/cms/comments.html +++ b/cms/templates/aircox/cms/comments.html @@ -7,18 +7,22 @@ {% if comment_form %} {% with comment_form as form %} {{ form.non_field_errors }} -
+ {% csrf_token %} {% render_honeypot_field "hp_website" %}
{{ form.author.errors }} {{ form.author }} - -
- {{ form.email.errors }} - {{ form.email }} - {{ form.url.errors }} - {{ form.url }} +
+ + +
+ {{ form.email.errors }} + {{ form.email }} + {{ form.url.errors }} + {{ form.url }} +
+
diff --git a/cms/templates/aircox/cms/list.html b/cms/templates/aircox/cms/list.html index c2ef39b..b3cda21 100644 --- a/cms/templates/aircox/cms/list.html +++ b/cms/templates/aircox/cms/list.html @@ -17,7 +17,6 @@ {% endif %} - {% if 'date' in list.fields or 'time' in list.fields or 'author' in list.fields %}
{% if item.date and 'date' in list.fields or 'time' in list.fields %}
- {% endif %} {% if 'image' in list.fields and item.image %} {% endif %} - {% if 'title' in list.fields %} -

{{ item.title }}

- {% endif %} - - {% if 'content' in list.fields %}
- {% if list.truncate %} - {{ item.content|striptags|truncatewords:list.truncate }} - {% else %} - {{ item.content|striptags }} + {% if 'title' in list.fields and item.title %} +

{{ item.title }}

+ {% endif %} + + {% if 'content' in list.fields and item.content %} +
+ {% if list.truncate %} + {{ item.content|striptags|truncatewords:list.truncate }} + {% else %} + {{ item.content|striptags }} + {% endif %} +
{% endif %}
- {% endif %} {% if item.detail_url %}
@@ -72,8 +78,9 @@ {% if page_obj or list.url %}