aircox-radiocampus/aircox_cms/models.py

226 lines
6.6 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('related_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):
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.
"""
model = None
mapping = None # values to map { post_attr: rel_attr }
bind = False # update fields of related data on save
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
print(relation.__dict__)
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)