This commit is contained in:
bkfox
2015-10-02 15:31:44 +02:00
parent 2af9cf8b13
commit 7069ed8918
7 changed files with 224 additions and 105 deletions

View File

@ -7,35 +7,48 @@ from django.utils.text import slugify
from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from django.db.models.signals import post_save
from django.db.models.signals import post_init, post_save, post_delete
from django.dispatch import receiver
from taggit.managers import TaggableManager
# Using a separate thread helps for routing, by avoiding to specify an
# additional argument to get the second model that implies to find it by
# the name that can be non user-friendly, like /thread/relatedpost/id
class Thread (models.Model):
"""
Object assigned to any Post and children that can be used to have parent and
children relationship between posts of different kind.
We use this system instead of having directly a GenericForeignKey into the
Post because it avoids having to define the relationship with two models for
routing (one for the parent and one for the children).
"""
post_type = models.ForeignKey(ContentType)
post_id = models.PositiveIntegerField()
post = GenericForeignKey('post_type', 'post_id')
@classmethod
def get (cl, model, **kwargs):
post_type = ContentType.objects.get_for_model(model)
return cl.objects.get(post_type__pk = post_type.id,
**kwargs)
def __get_query_set (cl, function, model, post, kwargs):
if post:
model = type(post)
kwargs['post_id'] = post.id
kwargs['post_type'] = ContentType.objects.get_for_model(model)
return getattr(cl.objects, function)(**kwargs)
@classmethod
def filter (cl, model, **kwargs):
post_type = ContentType.objects.get_for_model(model)
return cl.objects.filter(post_type__pk = post_type.id,
**kwargs)
def get (cl, model = None, post = None, **kwargs):
return cl.__get_query_set('get', model, post, kwargs)
@classmethod
def exclude (cl, model, **kwargs):
post_type = ContentType.objects.get_for_model(model)
return cl.objects.exclude(post_type__pk = post_type.id,
**kwargs)
def filter (cl, model = None, post = None, **kwargs):
return self.__get_query_set('filter', model, post, kwargs)
@classmethod
def exclude (cl, model = None, post = None, **kwargs):
return self.__get_query_set('exclude', model, post, kwargs)
def save (self, *args, **kwargs):
self.post = self.__initial_post
super().save(*args, **kwargs)
def __str__ (self):
return self.post_type.name + ': ' + str(self.post)
@ -57,16 +70,26 @@ class Post (models.Model):
_('date'),
default = timezone.datetime.now
)
public = models.BooleanField(
published = models.BooleanField(
verbose_name = _('public'),
default = True
)
title = models.CharField (
_('title'),
max_length = 128,
)
content = models.TextField (
_('description'),
blank = True, null = True
)
image = models.ImageField(
blank = True, null = True
)
title = ''
content = ''
tags = TaggableManager(
_('tags'),
blank = True,
)
def detail_url (self):
return reverse(self._meta.verbose_name_plural.lower() + '_detail',
@ -77,28 +100,7 @@ class Post (models.Model):
abstract = True
@receiver(post_save)
def on_new_post (sender, instance, created, *args, **kwargs):
"""
Signal handler to create a thread that is attached to the newly post
"""
if not issubclass(sender, Post) or not created:
return
thread = Thread(post = instance)
thread.save()
class Article (Post):
title = models.CharField(
_('title'),
max_length = 128,
blank = False, null = False
)
content = models.TextField(
_('content'),
blank = False, null = False
)
static_page = models.BooleanField(
_('static page'),
default = False,
@ -125,18 +127,6 @@ class RelatedPostBase (models.base.ModelBase):
if related_model:
attrs['related'] = models.ForeignKey(related_model)
mapping = rel.get('mapping')
if mapping:
def get_prop (name, related_name):
return property(related_name) if callable(related_name) \
else property(lambda self:
getattr(self.related, related_name))
attrs.update({
name: get_prop(name, related_name)
for name, related_name in mapping.items()
})
if not '__str__' in attrs:
attrs['__str__'] = lambda self: str(self.related)
@ -144,11 +134,54 @@ class RelatedPostBase (models.base.ModelBase):
class RelatedPost (Post, metaclass = RelatedPostBase):
related = None
class Meta:
abstract = True
class Relation:
related_model = None
mapping = None
mapping = None # dict of related mapping values
bind_mapping = False # update fields of related data on save
def get_attribute (self, attr):
attr = self.Relation.mappings.get(attr)
return self.related.__dict__[attr] if attr else None
def save (self, *args, **kwargs):
if not self.title and self.related:
self.title = self.get_attribute('title')
if self.Relation.bind_mapping:
self.related.__dict__.update({
rel_attr: self.__dict__[attr]
for attr, rel_attr in self.Relation.mapping
})
self.related.save()
super().save(*args, **kwargs)
@receiver(post_init)
def on_thread_init (sender, instance, **kwargs):
if not issubclass(Thread, sender):
return
instance.__initial_post = instance.post
@receiver(post_save)
def on_post_save (sender, instance, created, *args, **kwargs):
if not issubclass(sender, Post) or not created:
return
thread = Thread(post = instance)
thread.save()
@receiver(post_delete)
def on_post_delete (sender, instance, using, *args, **kwargs):
try:
Thread.get(sender, post = instance).delete()
except:
pass