work on cms

This commit is contained in:
bkfox 2016-05-21 01:01:17 +02:00
parent 54910f4df9
commit a4f1b03bde
6 changed files with 126 additions and 88 deletions

View File

@ -34,7 +34,7 @@ parent, and give informations for bindings and so on. This is as simple as:
class MyModelPost(RelatedPost): class MyModelPost(RelatedPost):
class Relation: class Relation:
model = MyModel model = MyModel
mapping = { bindings = {
'thread': 'parent_field_name', 'thread': 'parent_field_name',
'title': 'name' 'title': 'name'
} }
@ -51,7 +51,7 @@ Routes are registered to a router (FIXME: it might be possible that we remove
this later) this later)
## Section ## Sections
Sections are used to render part of a publication, for example to render a Sections are used to render part of a publication, for example to render a
playlist related to the diffusion of a program. playlist related to the diffusion of a program.

View File

@ -1,3 +1,6 @@
from django.contrib import admin from django.contrib import admin
# Register your models here.

View File

@ -104,45 +104,57 @@ class RelatedPostBase (models.base.ModelBase):
return key return key
@classmethod @classmethod
def check_thread_mapping (cl, relation, model, field): def make_relation(cl, name, attrs):
""" """
Add information related to the mapping 'thread' info. Make instance of RelatedPost.Relation
""" """
if not field: rel = RelatedPost.Relation()
return if 'Relation' not in attrs:
raise ValueError('RelatedPost item has not defined Relation class')
rel.__dict__.update(attrs['Relation'].__dict__)
parent_model = model._meta.get_field(field).rel.to if not rel.model or not issubclass(rel.model, models.Model):
thread_model = cl.registry.get(parent_model) raise ValueError('Relation.model is not a django model (None?)')
if not thread_model: if not rel.bindings:
raise ValueError('no registered RelatedPost for the model {}' rel.bindings = {}
.format(model.__name__))
relation.thread_model = thread_model # thread model
if rel.bindings.get('thread'):
rel.thread_model = rel.bindings.get('thread')
rel.thread_model = rel.model._meta.get_field(rel.thread_model). \
rel.to
rel.thread_model = cl.registry.get(rel.thread_model)
if not rel.thread_model:
raise ValueError(
'no registered RelatedPost for the bound thread. Is there '
' a RelatedPost for {} declared before {}?'
.format(rel.bindings.get('thread').__class__.__name__,
name)
)
return rel
def __new__ (cl, name, bases, attrs): def __new__ (cl, name, bases, attrs):
rel = attrs.get('Relation') if name == 'RelatedPost':
rel = (rel and rel.__dict__) or {} return super().__new__(cl, name, bases, attrs)
related_model = rel.get('model') rel = cl.make_relation(name, attrs)
if related_model: attrs['_relation'] = rel
attrs['related'] = models.ForeignKey(related_model) attrs.update({ x:y for x,y in {
'related': models.ForeignKey(rel.model),
if not '__str__' in attrs: '__str__': lambda self: str(self.related)
attrs['__str__'] = lambda self: str(self.related) }.items() if not attrs.get(x) })
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) model = super().__new__(cl, name, bases, attrs)
cl.register(related_model, model) cl.register(rel.model, model)
# name clashes
name = rel.model._meta.object_name
if name == model._meta.object_name:
model._meta.default_related_name = '{} Post'.format(name)
return model return model
@ -152,10 +164,11 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
the field "related". the field "related".
It is possible to map attributes of the Post to the ones of the Related It is possible to map attributes of the Post to the ones of the Related
Object. It is also possible to automatically update post's thread based Object. It is also possible to automatically update Post's thread based
on the Related Object's parent if it is required. on the Related Object's parent if it is required (but not Related Object's
parent based on Post's thread).
Mapping can ensure that the Related Object will be updated when mapped Bindings can ensure that the Related Object will be updated when mapped
fields of the Post are updated. fields of the Post are updated.
To configure the Related Post, you just need to create set attributes of To configure the Related Post, you just need to create set attributes of
@ -165,7 +178,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
class MyModelPost(RelatedPost): class MyModelPost(RelatedPost):
class Relation: class Relation:
model = MyModel model = MyModel
mapping = { bindings = {
'thread': 'parent_field_name', 'thread': 'parent_field_name',
'title': 'name' 'title': 'name'
} }
@ -181,77 +194,99 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
Relation descriptor used to generate and manage the related object. Relation descriptor used to generate and manage the related object.
* model: model of the related object * model: model of the related object
* mapping: values that are bound between the post and the related * bindings: values that are bound between the post and the related
object. When the post is saved, these fields are updated on it. object. When the post is saved, these fields are updated on it.
It is a dict of { post_attr: rel_attr } It is a dict of { post_attr: rel_attr }
If there is a post_attr "thread", the corresponding rel_attr is used 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 to update the post thread to the correct Post model (in order to
establish a parent-child relation between two models) establish a parent-child relation between two models)
Note: bound values can be any value, not only Django field.
* post_to_rel: auto update related object when post is updated
* rel_to_post: auto update the post when related object is updated
* thread_model: generated by the metaclass, points to the RelatedPost * thread_model: generated by the metaclass, points to the RelatedPost
model generated for the mapping.thread object. model generated for the bindings.thread object.
Be careful with post_to_rel!
* There is no check of permissions when related object is synchronised
from the post, so be careful when enabling post_to_rel.
* In post_to_rel synchronisation, if the parent thread is not a
(sub-)class thread_model, the related parent is set to None
""" """
model = None model = None
mapping = None # values to map { post_attr: rel_attr } bindings = None # values to map { post_attr: rel_attr }
post_to_rel = False
rel_to_post = True
thread_model = None thread_model = None
def get_attribute (self, attr): def get_rel_attr(self, attr):
attr = self._relation.mappings.get(attr) attr = self._relation.bindings.get(attr)
return self.related.__dict__[attr] if attr else None return getattr(self.related, attr) if attr else None
def update_thread_mapping (self, save = True): def set_rel_attr(self, attr, value)
""" if attr not in self._relation.bindings:
Update the parent object designed by Relation.mapping.thread if the raise AttributeError('attribute {} is not bound'.format(attr))
type matches to the one related of the current instance's thread. attr = self._relation.bindings.get(attr)
setattr(self.related, attr, value)
If there is no thread assigned to self, set it to the parent of the def post_to_rel(self, save = True):
related object.
""" """
relation = self._relation Change related object using post bound values. Save the related
thread_model = relation.thread_model object if save = True.
if not thread_model: Note: does not check if Relation.post_to_rel is True
"""
rel = self._relation
if not rel.bindings:
return return
# self.related.parent -> self.thread for attr, rel_attr in rel.bindings.items()
rel_parent = relation.mapping.get('thread') if attr == 'thread':
if not self.thread: continue
rel_parent = getattr(self.related, rel_parent) value = getattr(self, attr) if hasattr(self, attr) else None
thread = thread_model.objects.filter(related = rel_parent) setattr(self.related, rel_attr, value)
if thread.count():
self.thread = thread[0] if self.thread_model:
thread = self.thread if not issubclass(thread, rel.thread_model) \
else None
self.set_rel_attr('thread', thread.related)
if save:
self.related.save()
def rel_to_post(self, save = True):
"""
Change the post using the related object bound values. Save the
post if save = True.
Note: does not check if Relation.post_to_rel is True
"""
rel = self._relation
if rel.bindings:
return
for attr, rel_attr in rel.bindings.items()
if attr == 'thread':
continue
self.set_rel_attr
value = getattr(self.related, attr) \
if hasattr(self.related, attr) else None
setattr(self, attr, value)
if self.thread_model:
thread = self.get_rel_attr('thread')
thread = rel.thread_model.objects.filter(related = thread) \
if thread else None
thread = thread[0] if thread else None
self.thread = thread
if save: if save:
self.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): def save (self, *args, **kwargs):
if not self.title and self.related: if not self.title and self.related:
self.title = self.get_attribute('title') self.title = self.get_rel_attr('title')
if self._relation.post_to_rel:
self.update_mapping() self.post_to_rel(False)
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@ -12,7 +12,7 @@ This application defines all base models and basic control of them. We have:
## Architecture ## Architecture
A Station is basically an object that represent a radio station. On each station, we use the Program object, that is declined in two different type: A Station is basically an object that represent a radio station. On each station, we use the Program object, that is declined in two different types:
* **Scheduled**: the diffusion is based on a timetable and planified through one Schedule or more; Diffusion object represent the occurrence of these programs; * **Scheduled**: the diffusion is based on a timetable and planified through one Schedule or more; Diffusion object represent the occurrence of these programs;
* **Streamed**: the diffusion is based on random playlist, used to fill gaps between the programs; * **Streamed**: the diffusion is based on random playlist, used to fill gaps between the programs;

View File

@ -690,9 +690,9 @@ class Diffusion (models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__ (self): def __str__ (self):
return '#' + str(self.pk) + ' ' + self.program.name + ', ' + \ return '{self.program.name} {date} #{self.pk}'.format(
self.date.strftime('%Y-%m-%d %H:%M') +\ self=self, date=self.date.strftime('%Y-%m-%d %H:%M')
'' # FIXME str(self.type_display) )
class Meta: class Meta:
verbose_name = _('Diffusion') verbose_name = _('Diffusion')