clean up a bit

This commit is contained in:
bkfox 2016-05-25 13:26:05 +02:00
parent 14e9994a79
commit fb659c2c30
15 changed files with 130 additions and 322 deletions

View File

@ -12,6 +12,36 @@ from django.dispatch import receiver
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from aircox.cms import routes
class ProxyPost:
"""
Class used to simulate a Post model when it is required to render an
item linked to a Post but that is not that itself. This is to ensure
the right attributes are set.
"""
detail_url = None
date = None
image = None
title = None
content = None
def __init__(self, post = None, **kwargs):
if post:
self.update_empty(post)
self.__dict__.update(**kwargs)
def update_empty(self, thread):
"""
Update empty fields using thread object
"""
for i in ('date', 'image', 'title', 'content'):
if not getattr(self, i):
setattr(self, i, getattr(thread, i))
if not self.detail_url:
self.detail_url = thread.detail_url()
class Post (models.Model): class Post (models.Model):
""" """
@ -64,6 +94,14 @@ class Post (models.Model):
blank = True, blank = True,
) )
search_fields = [ 'title', 'content' ]
def get_proxy(self):
"""
Return a ProxyPost instance using this post
"""
return ProxyPost(self)
@classmethod @classmethod
def children_of(cl, thread, queryset = None): def children_of(cl, thread, queryset = None):
""" """
@ -77,10 +115,15 @@ class Post (models.Model):
thread_type__pk = thread_type.id) thread_type__pk = thread_type.id)
return qs return qs
def detail_url (self): def detail_url(self):
return reverse(self._website.name_of_model(self.__class__) + '_detail', return self.route_url(routes.DetailRoute,
kwargs = { 'pk': self.pk, { 'pk': self.pk, 'slug': slugify(self.title) })
'slug': slugify(self.title) })
@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 Meta: class Meta:
abstract = True abstract = True
@ -170,6 +213,7 @@ class RelatedPostBase (models.base.ModelBase):
}.items() if not attrs.get(x) }) }.items() if not attrs.get(x) })
model = super().__new__(cl, name, bases, attrs) model = super().__new__(cl, name, bases, attrs)
print(model, model.related)
cl.register(rel.model, model) cl.register(rel.model, model)
# name clashes # name clashes
@ -317,7 +361,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
super().__init__(*kargs, **kwargs) super().__init__(*kargs, **kwargs)
# we use this method for sync, in order to avoid intrusive code on other # we use this method for sync, in order to avoid intrusive code on other
# applications, e.g. using signals. # applications, e.g. using signals.
if self._relation.rel_to_post: if self.pk and self._relation.rel_to_post:
self.rel_to_post(save = False) self.rel_to_post(save = False)
def save (self, *args, **kwargs): def save (self, *args, **kwargs):

View File

@ -1,3 +1,4 @@
from django.db import models
from django.conf.urls import url from django.conf.urls import url
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils import timezone from django.utils import timezone
@ -118,7 +119,7 @@ class ThreadRoute(Route):
thread_model = ContentType.objects.get_for_model(thread_model) thread_model = ContentType.objects.get_for_model(thread_model)
return model.objects.filter( return model.objects.filter(
thread_type = thread_model, thread_type = thread_model,
thread_pk = int(pk) thread_id = int(pk)
) )
@ -143,15 +144,16 @@ class SearchRoute(Route):
name = 'search' name = 'search'
@classmethod @classmethod
def get_queryset(cl, website, model, request, q, **kwargs): def get_queryset(cl, website, model, request, **kwargs):
qs = model.objects q = request.GET.get('q') or ''
qs = None
for search_field in model.search_fields or []: for search_field in model.search_fields or []:
r = model.objects.filter(**{ 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
qs.distinct() return model.objects.filter(qs).distinct()
return qs
## TODO: by tag ## TODO: by tag

View File

@ -24,9 +24,10 @@ body {
vertical-align: top; vertical-align: top;
} }
main .section { main .section {
width: calc(50% - 2em); /* width: calc(50% - 2em);
display: inline-block; display: inline-block; */
padding: 0.5em; padding: 0.5em;
} }

View File

@ -1,8 +0,0 @@
{% block pre_title %}
{% endblock %}
{% block title %}
{% endblock %}
{% block content %}
{% endblock %}

View File

@ -1,9 +0,0 @@
<{{ tag }} class="section {{ classes }}"
{% for key, value in attrs.items %}{{ key }} = "{{ value|addslashes }}"
{% endfor %} >
{% block content %}
{{ content|safe }}
{% endblock %}
</{{ tag }}>

View File

@ -0,0 +1,8 @@
<{{ tag }} class="{{ classes }}"
{% for k, v in attrs.items %}{{ k }} = "{{ v|addslashes }}" {% endfor %} >
{% block content %}
{{ content|safe }}
{% endblock %}
</{{ tag }}>

View File

@ -1,4 +1,4 @@
{% extends embed|yesno:"aircox/cms/base_content.html,aircox/cms/base_site.html" %} {% extends "aircox/cms/website.html" %}
{% load aircox_cms %} {% load aircox_cms %}
{% block title %} {% block title %}
@ -29,8 +29,6 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% for section in sections %} {{ content }}
{{ section|safe }}
{% endfor %}
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
{% extends embed|yesno:"aircox/cms/base_content.html,aircox/cms/base_site.html" %} {% extends "aircox/cms/website.html" %}
{% load i18n %} {% load i18n %}
{% load thumbnail %} {% load thumbnail %}
@ -23,7 +23,7 @@
</time> </time>
{% endif %} {% endif %}
{% if 'image' in view.fields %} {% if 'image' in view.fields and post.image %}
<img src="{% thumbnail post.image view.icon_size crop %}" class="post_image"> <img src="{% thumbnail post.image view.icon_size crop %}" class="post_image">
{% endif %} {% endif %}

View File

@ -1,4 +1,4 @@
{% extends "aircox/cms/base_section.html" %} {% extends "aircox/cms/content_object.html" %}
{% block content %} {% block content %}
{% if title %} {% if title %}
@ -30,5 +30,5 @@
{% endblock %} {% endblock %}
</footer> </footer>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -2,7 +2,7 @@
{% load thumbnail %} {% load thumbnail %}
{% block section_content %} % block section_content %}
<ul style="padding:0; margin:0"> <ul style="padding:0; margin:0">
{% for item in object_list %} {% for item in object_list %}
<li class="{{item.css}}"> <li class="{{item.css}}">

View File

@ -1,3 +1,4 @@
{% if not embed %}
{% load staticfiles %} {% load staticfiles %}
<html> <html>
@ -32,6 +33,7 @@
{% endif %} {% endif %}
<main> <main>
{% endif %}
{% block pre_title %} {% block pre_title %}
{% endblock %} {% endblock %}
<h1> <h1>
@ -45,6 +47,7 @@
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
{% if not embed %}
</main> </main>
{% if menus.right %} {% if menus.right %}
@ -68,4 +71,5 @@
{% endif %} {% endif %}
</body> </body>
</html> </html>
{% endif %}

View File

@ -1,21 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
def get_url(website, route, model, kwargs):
name = website.name_of_model(model)
if not name:
return
name = route.get_view_name(name)
return reverse(name, kwargs = kwargs)
def filter_thread(qs, object):
model_type = ContentType.objects.get_for_model(object.__class__)
return qs.filter(
thread_pk = object.pk,
thread_type__pk = model_type.pk
)

View File

@ -1,17 +1,8 @@
import re
from django.templatetags.static import static from django.templatetags.static import static
from django.shortcuts import render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
from django.views.generic.base import View, TemplateResponseMixin from django.views.generic.base import View
from django.core.paginator import Paginator
from django.core import serializers
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils.html import escape
import aircox.cms.routes as routes
import aircox.cms.utils as utils
class PostBaseView: class PostBaseView:
@ -46,43 +37,26 @@ class PostBaseView:
class PostListView(PostBaseView, ListView): class PostListView(PostBaseView, ListView):
""" """
List view for posts and children List view for posts and children.
"""
class Query:
"""
Request availables parameters
"""
embed = False # view is embedded (only the list is shown)
exclude = None # exclude item of this id
order = 'desc' # order of the list when query
fields = None # fields to show
page = 1 # page number
q = None # query search
def __init__(self, query):
if query:
self.update(query)
def update(self, query):
my_class = self.__class__
if type(query) is my_class:
self.__dict__.update(query.__dict__)
return
self.__dict__.update(query)
Request.GET params:
* 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' template_name = 'aircox/cms/list.html'
allow_empty = True allow_empty = True
paginate_by = 50 paginate_by = 50
model = None model = None
route = None route = None
query = None
fields = [ 'date', 'time', 'image', 'title', 'content' ] fields = [ 'date', 'time', 'image', 'title', 'content' ]
icon_size = '64x64' icon_size = '64x64'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.query = PostListView.Query(self.query)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.route = self.kwargs.get('route') or self.route self.route = self.kwargs.get('route') or self.route
@ -94,23 +68,22 @@ class PostListView(PostBaseView, ListView):
**self.kwargs) **self.kwargs)
else: else:
qs = self.queryset or self.model.objects.all() qs = self.queryset or self.model.objects.all()
query = self.query
query.update(self.request.GET) query = self.request.GET
if query.exclude: if query.get('exclude'):
qs = qs.exclude(id = int(exclude)) qs = qs.exclude(id = int(query['exclude']))
if query.embed: if query.get('embed'):
self.embed = True self.embed = True
if query.order == 'asc': if query.get('order') == 'asc':
qs.order_by('date', 'id') qs.order_by('date', 'id')
else: else:
qs.order_by('-date', '-id') qs.order_by('-date', '-id')
if query.fields: if query.get('fields'):
self.fields = [ self.fields = [
field for field in query.fields field for field in query.get('fields')
if field in self.__class__.fields if field in self.__class__.fields
] ]
@ -134,14 +107,16 @@ class PostListView(PostBaseView, ListView):
def get_url(self): def get_url(self):
if self.route: if self.route:
return utils.get_urls(self.website, self.route, return self.model(self.route, self.kwargs)
self.model, self.kwargs)
return '' return ''
class PostDetailView(DetailView, PostBaseView): class PostDetailView(DetailView, PostBaseView):
""" """
Detail view for posts and children Detail view for posts and children
Request.GET params:
* embed: view is embedded, only render the content
""" """
template_name = 'aircox/cms/detail.html' template_name = 'aircox/cms/detail.html'
@ -149,12 +124,11 @@ class PostDetailView(DetailView, PostBaseView):
def __init__(self, sections = None, *args, **kwargs): def __init__(self, sections = None, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.sections = sections or [] self.sections = Sections(sections = sections)
def get_queryset(self): def get_queryset(self):
if self.request.GET.get('embed'): if self.request.GET.get('embed'):
self.embed = True self.embed = True
if self.model: if self.model:
return super().get_queryset().filter(published = True) return super().get_queryset().filter(published = True)
return [] return []
@ -169,237 +143,52 @@ class PostDetailView(DetailView, PostBaseView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context.update(self.get_base_context()) context.update(self.get_base_context())
context.update({ context['content'] = self.sections.get(request, object = self.object,
'sections': [ **kwargs)
section.get(self.request, website = self.website, **kwargs)
for section in self.sections
]
})
return context return context
class Menu(View): class Sections(View):
template_name = 'aircox/cms/menu.html' template_name = 'aircox/cms/content_object.html'
tag = 'div'
name = ''
tag = 'nav'
enabled = True
classes = '' classes = ''
position = '' # top, left, bottom, right, header, footer, page_top, page_bottom attrs = ''
sections = None sections = None
def __init__(self, *args, **kwargs): def __init__ (self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.name = self.name or ('menu_' + self.position) self.classes += ' sections'
def get_context_data(self, **kwargs): def get_context_data(self, request, object = None, **kwargs):
return { return {
'name': self.name,
'tag': self.tag,
'classes': self.classes,
'position': self.position,
'sections': [
section.get(self.request, website = self.website,
object = None, **kwargs)
for section in self.sections
]
}
def get(self, request, website, **kwargs):
self.request = request
self.website = website
context = self.get_context_data(**kwargs)
return render_to_string(self.template_name, context)
class BaseSection(View):
"""
Base class for sections. Sections are view that can be used in detail view
in order to have extra content about a post, or in menus.
"""
template_name = 'aircox/cms/base_section.html'
kwargs = None # kwargs argument passed to get
tag = 'div' # container tags
classes = '' # container classes
attrs = '' # container extra attributes
content = '' # content
visible = True # if false renders an empty string
def get_context_data(self):
return {
'view': self,
'tag': self.tag, 'tag': self.tag,
'classes': self.classes, 'classes': self.classes,
'attrs': self.attrs, 'attrs': self.attrs,
'visible': self.visible, 'content': ''.join([
'content': self.content, section.get(request, object = object, **kwargs)
for section in self.sections or []
])
} }
def get(self, request, website, **kwargs):
self.request = request
self.website = website
self.kwargs = kwargs
context = self.get_context_data()
# get_context_data may call extra function that can change visibility
if self.visible:
return render_to_string(self.template_name, context)
return ''
class Section(BaseSection):
"""
A Section that can be related to an object.
"""
template_name = 'aircox/cms/section.html'
object = None
object_required = False
title = ''
header = ''
footer = ''
def get_context_data(self):
context = super().get_context_data()
context.update({
'title': self.title,
'header': self.header,
'footer': self.footer,
})
return context
def get(self, request, object = None, **kwargs): def get(self, request, object = None, **kwargs):
self.object = object or self.object self.request = request
if self.object_required and not self.object: context = self.get_context_data(request, object, **kwargs)
raise ValueError('object is required by this Section but not given') return render_to_string(self.template_name, context)
return super().get(request, **kwargs)
class Sections: class Menu(Sections):
class Image(BaseSection): name = ''
""" tag = 'nav'
Render an image with the given relative url. enabled = True
""" position = '' # top, left, bottom, right, header, footer, page_top, page_bottom
url = None
@property def __init__(self, *args, **kwargs):
def content(self): super().__init__(*args, **kwargs)
return '<img src="{}">'.format( self.classes += ' menu menu_{}'.format(self.name or self.position)
static(self.url), if not self.attrs:
) self.attrs = {}
if self.name:
class PostContent(Section): self.attrs['name'] = self.name
""" self.attrs['id'] = self.name
Render the content of the Post (format the text a bit and escape HTML
tags).
"""
@property
def content(self):
content = escape(self.object.content)
content = re.sub(r'(^|\n\n)((\n?[^\n])+)', r'<p>\2</p>', content)
content = re.sub(r'\n', r'<br>', content)
return content
class PostImage(Section):
"""
Render the image of the Post
"""
@property
def content(self):
if not self.object.image:
return ''
return '<img src="{}" class="post_image">'.format(
self.object.image.url
)
class List(Section):
"""
Section to render list. The context item 'object_list' is used as list of
items to render.
"""
class Item:
icon = None
title = None
text = None
url = None
css = None
def __init__(self, icon, title = None, text = None, url = None, css = None):
self.icon = icon
self.title = title
self.text = text
self.css = css
hide_empty = False # hides the section if the list is empty
use_icons = True # print icons
paginate_by = 0 # number of items
icon_size = '32x32' # icons size
template_name = 'aircox/cms/section_list.html'
def get_object_list(self):
return []
def get_context_data(self, **kwargs):
object_list = self.get_object_list()
self.visibility = True
if not object_list and self.hide_empty:
self.visibility = False
context = super().get_context_data(**kwargs)
context.update({
'classes': context.get('classes') + ' section_list',
'icon_size': self.icon_size,
'object_list': object_list,
'paginate_by': self.paginate_by,
})
return context
class Urls(List):
"""
Render a list of urls of targets that are Posts
"""
classes = 'section_urls'
targets = None
def get_object_list(self):
return [
List.Item(
target.image or None,
target.title,
url = target.detail_url(),
)
for target in self.targets
]
class Posts(PostBaseView, Section):
"""
Render a list using PostListView's template.
"""
embed = True
paginate_by = 5
icon_size = '64x64'
fields = [ 'date', 'time', 'image', 'title', 'content' ]
def get_url(self):
return ''
def get_object_list(self):
return []
def render_list(self):
self.embed = True
context = self.get_base_context(**self.kwargs)
context.update({
'object_list': self.get_object_list(),
'embed': True,
'paginate_by': self.paginate_by,
})
return render_to_string(PostListView.template_name, context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content'] = self.render_list()
return context

View File

@ -47,7 +47,7 @@ class Website:
view = view.as_view( view = view.as_view(
website = self, website = self,
model = model, model = model,
**view_kwargs, **view_kwargs
) )
self.urls.append(routes.DetailRoute.as_url(name, view)) self.urls.append(routes.DetailRoute.as_url(name, view))
self.registry[name] = model self.registry[name] = model