switch to multi-table inheritance for posts; remove qcombine that is no more needed; add and integrate post.downcast + as template filter
This commit is contained in:
parent
cfce035527
commit
ff02258d8b
|
@ -54,7 +54,7 @@ class Exposure:
|
||||||
'exp': cl._exposure,
|
'exp': cl._exposure,
|
||||||
})
|
})
|
||||||
res = render_to_string(exp.template_name,
|
res = render_to_string(exp.template_name,
|
||||||
v, request = request)
|
ctx, request = request)
|
||||||
return HttpResponse(res or '')
|
return HttpResponse(res or '')
|
||||||
|
|
||||||
# id = str(uuid.uuid1())
|
# id = str(uuid.uuid1())
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils import timezone
|
from django.utils import timezone as tz
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class Comment(models.Model, Routable):
|
||||||
)
|
)
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
_('date'),
|
_('date'),
|
||||||
default = timezone.datetime.now
|
auto_now_add = True,
|
||||||
)
|
)
|
||||||
content = models.TextField (
|
content = models.TextField (
|
||||||
_('comment'),
|
_('comment'),
|
||||||
|
@ -107,10 +107,18 @@ class Post (models.Model, Routable):
|
||||||
You can declare an extra property "info" that can be used to append
|
You can declare an extra property "info" that can be used to append
|
||||||
info in lists rendering.
|
info in lists rendering.
|
||||||
"""
|
"""
|
||||||
|
# used for inherited children
|
||||||
|
real_type = models.CharField(
|
||||||
|
max_length=32,
|
||||||
|
blank = True, null = True,
|
||||||
|
)
|
||||||
|
|
||||||
# metadata
|
# metadata
|
||||||
|
# FIXME: on_delete
|
||||||
thread_type = models.ForeignKey(
|
thread_type = models.ForeignKey(
|
||||||
ContentType,
|
ContentType,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
related_name = 'thread_type',
|
||||||
blank = True, null = True
|
blank = True, null = True
|
||||||
)
|
)
|
||||||
thread_id = models.PositiveIntegerField(
|
thread_id = models.PositiveIntegerField(
|
||||||
|
@ -135,7 +143,7 @@ class Post (models.Model, Routable):
|
||||||
)
|
)
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
_('date'),
|
_('date'),
|
||||||
default = timezone.datetime.now
|
default = tz.datetime.now
|
||||||
)
|
)
|
||||||
title = models.CharField (
|
title = models.CharField (
|
||||||
_('title'),
|
_('title'),
|
||||||
|
@ -154,6 +162,11 @@ class Post (models.Model, Routable):
|
||||||
blank = True,
|
blank = True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
info = ''
|
||||||
|
"""
|
||||||
|
Used to be extended: used in template to render contextual information about
|
||||||
|
a sub-post item.
|
||||||
|
"""
|
||||||
search_fields = [ 'title', 'content', 'tags__name' ]
|
search_fields = [ 'title', 'content', 'tags__name' ]
|
||||||
"""
|
"""
|
||||||
Fields on which routes.SearchRoute must run the search
|
Fields on which routes.SearchRoute must run the search
|
||||||
|
@ -196,7 +209,7 @@ class Post (models.Model, Routable):
|
||||||
self.content = self.thread.content
|
self.content = self.thread.content
|
||||||
if not self.image:
|
if not self.image:
|
||||||
self.image = self.thread.image
|
self.image = self.thread.image
|
||||||
if not self.tags and self.pk:
|
if self.pk and not self.tags:
|
||||||
self.tags = self.thread.tags
|
self.tags = self.thread.tags
|
||||||
|
|
||||||
def get_object_list(self, request, object, **kwargs):
|
def get_object_list(self, request, object, **kwargs):
|
||||||
|
@ -228,17 +241,24 @@ class Post (models.Model, Routable):
|
||||||
for tag in self.tags.all()
|
for tag in self.tags.all()
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def downcast(self):
|
||||||
|
"""
|
||||||
|
Return a downcasted version of the post if it is from another
|
||||||
|
model, or itself
|
||||||
|
"""
|
||||||
|
if not self.real_type or type(self) != Post:
|
||||||
|
return self
|
||||||
|
return getattr(self, self.real_type)
|
||||||
|
|
||||||
def save(self, make_safe = True, *args, **kwargs):
|
def save(self, make_safe = True, *args, **kwargs):
|
||||||
|
if type(self) != Post and not self.real_type:
|
||||||
|
self.real_type = type(self).__name__.lower()
|
||||||
|
if self.date and tz.is_naive(self.date):
|
||||||
|
self.date = tz.make_aware(self.date)
|
||||||
if make_safe:
|
if make_safe:
|
||||||
self.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)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class RelatedMeta (models.base.ModelBase):
|
class RelatedMeta (models.base.ModelBase):
|
||||||
"""
|
"""
|
||||||
|
@ -499,6 +519,8 @@ class RelatedPost (Post, metaclass = RelatedMeta):
|
||||||
continue
|
continue
|
||||||
value = rel_attr(self, self.related) if callable(rel_attr) else \
|
value = rel_attr(self, self.related) if callable(rel_attr) else \
|
||||||
getattr(self.related, rel_attr)
|
getattr(self.related, rel_attr)
|
||||||
|
if type(value) == tz.datetime and tz.is_naive(value):
|
||||||
|
value = tz.make_aware(value)
|
||||||
set_attr(attr, value)
|
set_attr(attr, value)
|
||||||
|
|
||||||
if rel.thread_model:
|
if rel.thread_model:
|
||||||
|
|
153
cms/qcombine.py
153
cms/qcombine.py
|
@ -1,153 +0,0 @@
|
||||||
import operator
|
|
||||||
import itertools
|
|
||||||
import heapq
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
|
|
||||||
|
|
||||||
class QCombine:
|
|
||||||
"""
|
|
||||||
This class helps to combine querysets of different models and lists of
|
|
||||||
object, and to iter over it.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- when working on fields, we assume that they exists on all of them;
|
|
||||||
- for efficiency, there is no possibility to change order per field;
|
|
||||||
to do so, do it directly on the querysets
|
|
||||||
- we dont clone the combinator in order to avoid overhead
|
|
||||||
"""
|
|
||||||
order_fields = None
|
|
||||||
lists = None
|
|
||||||
|
|
||||||
def __init__(self, *lists):
|
|
||||||
"""
|
|
||||||
lists: list of querysets that are used to initialize the stuff.
|
|
||||||
"""
|
|
||||||
self.lists = list(lists) or []
|
|
||||||
|
|
||||||
def map(self, qs_func, non_qs = None):
|
|
||||||
"""
|
|
||||||
Map results of qs_func for QuerySet instance and of non_qs for
|
|
||||||
the others (if given), because QuerySet always clones itself.
|
|
||||||
"""
|
|
||||||
for i, qs in enumerate(self.lists):
|
|
||||||
if issubclass(type(qs), QuerySet):
|
|
||||||
self.lists[i] = qs_func(qs)
|
|
||||||
elif non_qs:
|
|
||||||
self.lists[i] = non_qs(qs)
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
self.map(lambda qs: qs.all())
|
|
||||||
|
|
||||||
def filter(self, **kwargs):
|
|
||||||
self.map(lambda qs: qs.filter(**kwargs))
|
|
||||||
return self
|
|
||||||
|
|
||||||
def exclude(self, **kwargs):
|
|
||||||
self.map(lambda qs: qs.exclude(**kwargs))
|
|
||||||
return self
|
|
||||||
|
|
||||||
def distinct(self, **kwargs):
|
|
||||||
self.map(lambda qs: qs.distinct())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def get(self, **kwargs):
|
|
||||||
self.filter(**kwargs)
|
|
||||||
it = iter(self)
|
|
||||||
return next(it)
|
|
||||||
|
|
||||||
def order_by(self, *fields, reverse = False):
|
|
||||||
"""
|
|
||||||
Order using these fields. For compatibility, if there is
|
|
||||||
at least one fields whose name starts with '-', reverse
|
|
||||||
the order
|
|
||||||
"""
|
|
||||||
for i, field in enumerate(fields):
|
|
||||||
if field[0] == '-':
|
|
||||||
reverse = True
|
|
||||||
fields[i] = field[1:]
|
|
||||||
|
|
||||||
self.order_reverse = reverse
|
|
||||||
self.order_fields = fields
|
|
||||||
self.map(
|
|
||||||
lambda qs: qs.order_by(*fields),
|
|
||||||
lambda qs: sorted(
|
|
||||||
qs,
|
|
||||||
qs.sort(
|
|
||||||
key = operator.attrgetter(*fields),
|
|
||||||
reverse = reverse
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def clone(self):
|
|
||||||
"""
|
|
||||||
Make a clone of the class. Not that lists are copied, non-deeply
|
|
||||||
"""
|
|
||||||
return QCombine(*[
|
|
||||||
qs.all() if issubclass(type(qs), QuerySet) else qs.copy()
|
|
||||||
for qs in self.lists
|
|
||||||
])
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return sum([len(qs) for qs in self.lists])
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if not self.order_fields:
|
|
||||||
return itertools.chain(self.lists)
|
|
||||||
|
|
||||||
# FIXME: need it lazy?
|
|
||||||
return heapq.merge(
|
|
||||||
*self.lists,
|
|
||||||
key = operator.attrgetter(*self.order_fields),
|
|
||||||
reverse = self.order_reverse
|
|
||||||
)
|
|
||||||
|
|
||||||
def __getitem__(self, k):
|
|
||||||
if type(k) == slice:
|
|
||||||
it = itertools.islice(iter(self), k.start, k.stop, k.step)
|
|
||||||
else:
|
|
||||||
it = itertools.islice(iter(self), k)
|
|
||||||
return list(it)
|
|
||||||
|
|
||||||
|
|
||||||
class Manager(type):
|
|
||||||
"""
|
|
||||||
Metaclass used to generate the GenericModel.objects property
|
|
||||||
"""
|
|
||||||
models = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def objects(self):
|
|
||||||
qs = QCombine(*[model.objects.all() for model in self.models])
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class GenericModel(metaclass=Manager):
|
|
||||||
"""
|
|
||||||
This class is used to register a route for multiple models to a website.
|
|
||||||
A QCombine is created with qs for all given models when objects
|
|
||||||
property is retrieved.
|
|
||||||
|
|
||||||
Note: there no other use-case.
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('publication')
|
|
||||||
verbose_name_plural = _('publications')
|
|
||||||
|
|
||||||
_meta = Meta()
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reverse(cl, route, use_default = True, **kwargs):
|
|
||||||
"""
|
|
||||||
Reverse a url using a given route for the model - simple wrapper
|
|
||||||
around cl._website.reverse
|
|
||||||
"""
|
|
||||||
return cl._website.reverse(cl, route, use_default, **kwargs)
|
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
import aircox.cms.qcombine as qcombine
|
|
||||||
|
|
||||||
|
|
||||||
class Route:
|
class Route:
|
||||||
"""
|
"""
|
||||||
|
@ -189,7 +187,8 @@ class SearchRoute(Route):
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __search(cl, model, q):
|
def get_queryset(cl, model, request, q = None, **kwargs):
|
||||||
|
q = request.GET.get('q') or q or ''
|
||||||
qs = None
|
qs = None
|
||||||
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 })
|
||||||
|
@ -197,17 +196,6 @@ class SearchRoute(Route):
|
||||||
else: qs = r
|
else: qs = r
|
||||||
return model.objects.filter(qs).distinct()
|
return model.objects.filter(qs).distinct()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_queryset(cl, model, request, q = None, **kwargs):
|
|
||||||
q = request.GET.get('q') or q or ''
|
|
||||||
if issubclass(model, qcombine.GenericModel):
|
|
||||||
models = model.models
|
|
||||||
return qcombine.QCombine(
|
|
||||||
*(cl.__search(model, q) for model in models)
|
|
||||||
)
|
|
||||||
return cl.__search(model, q)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_title(cl, model, request, q = None, **kwargs):
|
def get_title(cl, model, request, q = None, **kwargs):
|
||||||
return _('Search <i>%(search)s</i> in %(model)s') % {
|
return _('Search <i>%(search)s</i> in %(model)s') % {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
|
{% load aircox_cms %}
|
||||||
|
|
||||||
|
{% with object|downcast as object %}
|
||||||
<li {% if object.css_class %}class="{{ object.css_class }}"{% endif %}
|
<li {% if object.css_class %}class="{{ object.css_class }}"{% endif %}
|
||||||
{% for k, v in object.attrs.items %}
|
{% for k, v in object.attrs.items %}
|
||||||
{{ k }} = "{{ v|addslashes }}"
|
{{ k }} = "{{ v|addslashes }}"
|
||||||
|
@ -61,5 +62,5 @@
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,17 @@ import aircox.cms.utils as utils
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter(name='downcast')
|
||||||
|
def downcast(post):
|
||||||
|
"""
|
||||||
|
Downcast an object if it has a downcast function, or just return the
|
||||||
|
post.
|
||||||
|
"""
|
||||||
|
if hasattr(post, 'downcast') and callable(post.downcast):
|
||||||
|
return post.downcast
|
||||||
|
return post
|
||||||
|
|
||||||
|
|
||||||
@register.filter(name='post_tags')
|
@register.filter(name='post_tags')
|
||||||
def post_tags(post, sep = ' - '):
|
def post_tags(post, sep = ' - '):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -9,7 +9,6 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
|
||||||
import aircox.programs.models as programs
|
import aircox.programs.models as programs
|
||||||
import aircox.cms.models as cms
|
import aircox.cms.models as cms
|
||||||
import aircox.cms.qcombine as qcombine
|
|
||||||
|
|
||||||
|
|
||||||
class Article (cms.Post):
|
class Article (cms.Post):
|
||||||
|
@ -129,11 +128,3 @@ class Sound (cms.RelatedPost):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Publications (qcombine.GenericModel):
|
|
||||||
"""
|
|
||||||
Combine views
|
|
||||||
"""
|
|
||||||
models = [ Article, Program, Diffusion, Sound ]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user