237 lines
7.1 KiB
Python
237 lines
7.1 KiB
Python
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.utils import timezone
|
|
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_init, post_save, post_delete
|
|
from django.dispatch import receiver
|
|
|
|
from taggit.managers import TaggableManager
|
|
|
|
|
|
class Post (models.Model):
|
|
thread_type = models.ForeignKey(
|
|
ContentType,
|
|
on_delete=models.SET_NULL,
|
|
blank = True, null = True
|
|
)
|
|
thread_pk = models.PositiveIntegerField(
|
|
blank = True, null = True
|
|
)
|
|
thread = GenericForeignKey('thread_type', 'thread_pk')
|
|
|
|
author = models.ForeignKey(
|
|
User,
|
|
verbose_name = _('author'),
|
|
blank = True, null = True,
|
|
)
|
|
date = models.DateTimeField(
|
|
_('date'),
|
|
default = timezone.datetime.now
|
|
)
|
|
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
|
|
)
|
|
tags = TaggableManager(
|
|
_('tags'),
|
|
blank = True,
|
|
)
|
|
|
|
def detail_url (self):
|
|
return reverse(self._meta.verbose_name.lower() + '_detail',
|
|
kwargs = { 'pk': self.pk,
|
|
'slug': slugify(self.title) })
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
class Article (Post):
|
|
static_page = models.BooleanField(
|
|
_('static page'),
|
|
default = False,
|
|
)
|
|
focus = models.BooleanField(
|
|
_('article is focus'),
|
|
default = False,
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _('Article')
|
|
verbose_name_plural = _('Articles')
|
|
|
|
|
|
class RelatedPostBase (models.base.ModelBase):
|
|
"""
|
|
Metaclass for RelatedPost children.
|
|
"""
|
|
registry = {}
|
|
|
|
@classmethod
|
|
def register (cl, key, model):
|
|
"""
|
|
Register a model and return the key under which it is registered.
|
|
Raise a ValueError if another model is yet associated under this key.
|
|
"""
|
|
if key in cl.registry and cl.registry[key] is not model:
|
|
raise ValueError('A model has yet been registered with "{}"'
|
|
.format(key))
|
|
cl.registry[key] = model
|
|
return key
|
|
|
|
@classmethod
|
|
def check_thread_mapping (cl, relation, model, field):
|
|
"""
|
|
Add information related to the mapping 'thread' info.
|
|
"""
|
|
if not field:
|
|
return
|
|
|
|
parent_model = model._meta.get_field(field).rel.to
|
|
thread_model = cl.registry.get(parent_model)
|
|
|
|
if not thread_model:
|
|
raise ValueError('no registered RelatedPost for the model {}'
|
|
.format(model.__name__))
|
|
relation.thread_model = thread_model
|
|
|
|
def __new__ (cl, name, bases, attrs):
|
|
rel = attrs.get('Relation')
|
|
rel = (rel and rel.__dict__) or {}
|
|
|
|
related_model = rel.get('model')
|
|
if related_model:
|
|
attrs['related'] = models.ForeignKey(related_model)
|
|
|
|
if not '__str__' in attrs:
|
|
attrs['__str__'] = lambda self: str(self.related)
|
|
|
|
if name is not 'RelatedPost':
|
|
_relation = RelatedPost.Relation()
|
|
_relation.__dict__.update(rel)
|
|
mapping = rel.get('mapping')
|
|
cl.check_thread_mapping(
|
|
_relation,
|
|
related_model,
|
|
mapping and mapping.get('thread')
|
|
)
|
|
attrs['_relation'] = _relation
|
|
|
|
model = super().__new__(cl, name, bases, attrs)
|
|
cl.register(related_model, model)
|
|
return model
|
|
|
|
|
|
class RelatedPost (Post, metaclass = RelatedPostBase):
|
|
"""
|
|
Use this post to generate Posts that are related to an external model. An
|
|
extra field "related" will be generated, and some bindings are possible to
|
|
update te related object on save if desired;
|
|
|
|
This is done through a class name Relation inside the declaration of the new
|
|
model.
|
|
"""
|
|
related = None
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
class Relation:
|
|
"""
|
|
Relation descriptor used to generate and manage the related object.
|
|
|
|
* model: model of the related object
|
|
* mapping: values that are bound between the post and the related
|
|
object. When the post is saved, these fields are updated on it.
|
|
It is a dict of { post_attr: rel_attr }
|
|
|
|
If there is a post_attr "thread", the corresponding rel_attr is used
|
|
to update the post thread to the correct Post model (in order to
|
|
establish a parent-child relation between two models)
|
|
* thread_model: generated by the metaclass that point to the
|
|
RelatedModel class related to the model that is the parent of
|
|
the current related one.
|
|
"""
|
|
model = None
|
|
mapping = None # values to map { post_attr: rel_attr }
|
|
thread = None
|
|
thread_model = None
|
|
|
|
def get_attribute (self, attr):
|
|
attr = self._relation.mappings.get(attr)
|
|
return self.related.__dict__[attr] if attr else None
|
|
|
|
def update_thread_mapping (self, save = True):
|
|
"""
|
|
Update the parent object designed by Relation.mapping.thread if the
|
|
type matches to the one related of the current instance's thread.
|
|
|
|
If there is no thread assigned to self, set it to the parent of the
|
|
related object.
|
|
"""
|
|
relation = self._relation
|
|
thread_model = relation.thread_model
|
|
if not thread_model:
|
|
return
|
|
|
|
# self.related.parent -> self.thread
|
|
rel_parent = relation.mapping.get('thread')
|
|
if not self.thread:
|
|
rel_parent = getattr(self.related, rel_parent)
|
|
thread = thread_model.objects.filter(related = rel_parent)
|
|
if thread.count():
|
|
self.thread = thread[0]
|
|
if save:
|
|
self.save()
|
|
return
|
|
|
|
# self.thread -> self.related.parent
|
|
if thread_model is not self.thread_type.model_class():
|
|
return
|
|
|
|
setattr(self.related, rel_parent, self.thread.related)
|
|
if save:
|
|
self.save()
|
|
|
|
def update_mapping (self):
|
|
relation = self._relation
|
|
mapping = relation.mapping
|
|
if not mapping:
|
|
return
|
|
|
|
related = self.related
|
|
related.__dict__.update({
|
|
rel_attr: self.__dict__[attr]
|
|
for attr, rel_attr in mapping.items()
|
|
if attr is not 'thread' and attr in self.__dict__
|
|
})
|
|
|
|
self.update_thread_mapping(save = False)
|
|
related.save()
|
|
|
|
def save (self, *args, **kwargs):
|
|
if not self.title and self.related:
|
|
self.title = self.get_attribute('title')
|
|
|
|
self.update_mapping()
|
|
super().save(*args, **kwargs)
|
|
|
|
|