RelatedPost: mapping['thread'] now manage relation between thread and parent of related object
This commit is contained in:
parent
cde58334bd
commit
0032e216b8
|
@ -82,7 +82,37 @@ class RelatedPostBase (models.base.ModelBase):
|
||||||
"""
|
"""
|
||||||
Metaclass for RelatedPost children.
|
Metaclass for RelatedPost children.
|
||||||
"""
|
"""
|
||||||
def __new__ (cls, name, bases, attrs):
|
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 = attrs.get('Relation')
|
||||||
rel = (rel and rel.__dict__) or {}
|
rel = (rel and rel.__dict__) or {}
|
||||||
|
|
||||||
|
@ -96,9 +126,17 @@ class RelatedPostBase (models.base.ModelBase):
|
||||||
if name is not 'RelatedPost':
|
if name is not 'RelatedPost':
|
||||||
_relation = RelatedPost.Relation()
|
_relation = RelatedPost.Relation()
|
||||||
_relation.__dict__.update(rel)
|
_relation.__dict__.update(rel)
|
||||||
|
mapping = rel.get('mapping')
|
||||||
|
cl.check_thread_mapping(
|
||||||
|
_relation,
|
||||||
|
related_model,
|
||||||
|
mapping and mapping.get('thread')
|
||||||
|
)
|
||||||
attrs['_relation'] = _relation
|
attrs['_relation'] = _relation
|
||||||
|
|
||||||
return super().__new__(cls, name, bases, attrs)
|
model = super().__new__(cl, name, bases, attrs)
|
||||||
|
cl.register(related_model, model)
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
class RelatedPost (Post, metaclass = RelatedPostBase):
|
class RelatedPost (Post, metaclass = RelatedPostBase):
|
||||||
|
@ -108,25 +146,80 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
class Relation:
|
class Relation:
|
||||||
related_model = None
|
"""
|
||||||
mapping = None # dict of related mapping values
|
Relation descriptor used to generate and manage the related object.
|
||||||
bind_mapping = False # update fields of related data on save
|
|
||||||
|
* 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):
|
def get_attribute (self, attr):
|
||||||
attr = self._relation.mappings.get(attr)
|
attr = self._relation.mappings.get(attr)
|
||||||
return self.related.__dict__[attr] if attr else None
|
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):
|
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_attribute('title')
|
||||||
|
|
||||||
if self._relation.bind_mapping:
|
self.update_mapping()
|
||||||
self.related.__dict__.update({
|
|
||||||
rel_attr: self.__dict__[attr]
|
|
||||||
for attr, rel_attr in self.Relation.mapping.items()
|
|
||||||
})
|
|
||||||
self.related.save()
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,8 +57,8 @@ class Route:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def as_url (cl, model, model_name, view, view_kwargs = None):
|
def as_url (cl, name, model, view, view_kwargs = None):
|
||||||
pattern = '^{}/{}'.format(model_name, cl.name)
|
pattern = '^{}/{}'.format(name, cl.name)
|
||||||
if cl.url_args:
|
if cl.url_args:
|
||||||
url_args = '/'.join([
|
url_args = '/'.join([
|
||||||
'(?P<{}>{}){}'.format(
|
'(?P<{}>{}){}'.format(
|
||||||
|
@ -77,7 +77,7 @@ class Route:
|
||||||
kwargs.update(view_kwargs)
|
kwargs.update(view_kwargs)
|
||||||
|
|
||||||
return url(pattern, view, kwargs = kwargs,
|
return url(pattern, view, kwargs = kwargs,
|
||||||
name = model_name + '_' + cl.name)
|
name = name + '_' + cl.name)
|
||||||
|
|
||||||
|
|
||||||
class DetailRoute (Route):
|
class DetailRoute (Route):
|
||||||
|
|
|
@ -385,50 +385,3 @@ class Sections:
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ViewSet:
|
|
||||||
"""
|
|
||||||
A ViewSet is a class helper that groups detail and list views that can be
|
|
||||||
used to generate views and routes given a model and a name used for the
|
|
||||||
routing.
|
|
||||||
"""
|
|
||||||
model = None
|
|
||||||
list_view = PostListView
|
|
||||||
list_routes = []
|
|
||||||
|
|
||||||
detail_view = PostDetailView
|
|
||||||
detail_sections = [
|
|
||||||
Sections.PostContent(),
|
|
||||||
Sections.PostImage(),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_list_name (self):
|
|
||||||
"""
|
|
||||||
Return a string with the name to use in the route for the lists
|
|
||||||
"""
|
|
||||||
return self.model._meta.verbose_name_plural.lower()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_detail_name (cl):
|
|
||||||
"""
|
|
||||||
Return a string with the name to use in the route for the details
|
|
||||||
"""
|
|
||||||
return cl.model._meta.verbose_name.lower()
|
|
||||||
|
|
||||||
def connect (self, website = None):
|
|
||||||
self.detail_view = self.detail_view.as_view(
|
|
||||||
model = self.model,
|
|
||||||
sections = self.detail_sections,
|
|
||||||
website = website,
|
|
||||||
)
|
|
||||||
self.list_view = self.list_view.as_view(
|
|
||||||
model = self.model,
|
|
||||||
website = website,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.urls = [ route.as_url(self.model, self.get_list_name(),
|
|
||||||
self.list_view) for route in self.list_routes ] + \
|
|
||||||
[ routes.DetailRoute.as_url(self.model,
|
|
||||||
self.get_detail_name(), self.detail_view ) ]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import aircox_cms.routes as routes
|
import aircox_cms.routes as routes
|
||||||
|
import aircox_cms.views as views
|
||||||
|
|
||||||
class Website:
|
class Website:
|
||||||
name = ''
|
name = ''
|
||||||
|
@ -12,26 +13,67 @@ class Website:
|
||||||
'right', 'bottom',
|
'right', 'bottom',
|
||||||
'header', 'footer']
|
'header', 'footer']
|
||||||
router = None
|
router = None
|
||||||
|
urls = []
|
||||||
registry = {}
|
registry = {}
|
||||||
|
|
||||||
@property
|
|
||||||
def urls (self):
|
|
||||||
return self.router.get_urlpatterns()
|
|
||||||
|
|
||||||
def __init__ (self, **kwargs):
|
def __init__ (self, **kwargs):
|
||||||
|
self.registry = {}
|
||||||
|
self.urls = []
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
if not self.router:
|
|
||||||
self.router = routes.Router()
|
|
||||||
|
|
||||||
def register_set (self, view_set):
|
def register_model (self, name, model):
|
||||||
"""
|
"""
|
||||||
Register a ViewSet (or subclass) to the router,
|
Register a model and return the name under which it is registered.
|
||||||
and connect it to self.
|
Raise a ValueError if another model is yet associated under this name.
|
||||||
"""
|
"""
|
||||||
view_set = view_set()
|
if name in self.registry and self.registry[name] is not model:
|
||||||
view_set.connect(website = self)
|
raise ValueError('A model has yet been registered under "{}"'
|
||||||
self.registry[view_set.get_detail_name()] = view_set
|
.format(name))
|
||||||
self.router.register_set(view_set)
|
self.registry[name] = model
|
||||||
|
return name
|
||||||
|
|
||||||
|
def register_detail (self, name, model, view = views.PostDetailView,
|
||||||
|
**view_kwargs):
|
||||||
|
"""
|
||||||
|
Register a model and the detail view
|
||||||
|
"""
|
||||||
|
name = self.register_model(name, model)
|
||||||
|
view = view.as_view(
|
||||||
|
website = self,
|
||||||
|
model = model,
|
||||||
|
**view_kwargs,
|
||||||
|
)
|
||||||
|
self.urls.append(routes.DetailRoute.as_url(name, model, view))
|
||||||
|
self.registry[name] = model
|
||||||
|
|
||||||
|
def register_list (self, name, model, view = views.PostListView,
|
||||||
|
routes = [], **view_kwargs):
|
||||||
|
"""
|
||||||
|
Register a model and the given list view using the given routes
|
||||||
|
"""
|
||||||
|
name = self.register_model(name, model)
|
||||||
|
view = view.as_view(
|
||||||
|
website = self,
|
||||||
|
model = model,
|
||||||
|
**view_kwargs
|
||||||
|
)
|
||||||
|
self.urls += [ route.as_url(name, model, view) for route in routes ]
|
||||||
|
self.registry[name] = model
|
||||||
|
|
||||||
|
def register (self, name, model, sections = None, routes = None,
|
||||||
|
list_kwargs = {}, detail_kwargs = {}):
|
||||||
|
if sections:
|
||||||
|
self.register_detail(
|
||||||
|
name, model,
|
||||||
|
sections = sections,
|
||||||
|
**detail_kwargs
|
||||||
|
)
|
||||||
|
if routes:
|
||||||
|
self.register_list(
|
||||||
|
name, model,
|
||||||
|
routes = routes,
|
||||||
|
**list_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def get_menu (self, position):
|
def get_menu (self, position):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -17,6 +17,7 @@ class Episode (RelatedPost):
|
||||||
related_model = programs.Episode
|
related_model = programs.Episode
|
||||||
bind_mapping = True
|
bind_mapping = True
|
||||||
mapping = {
|
mapping = {
|
||||||
|
'thread': 'program',
|
||||||
'title': 'name',
|
'title': 'name',
|
||||||
'content': 'description',
|
'content': 'description',
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,42 +4,10 @@ from website.models import *
|
||||||
from website.views import *
|
from website.views import *
|
||||||
|
|
||||||
from aircox_cms.models import Article
|
from aircox_cms.models import Article
|
||||||
from aircox_cms.views import Menu, Section, Sections, ViewSet
|
from aircox_cms.views import Menu, Section, Sections
|
||||||
from aircox_cms.routes import *
|
from aircox_cms.routes import *
|
||||||
from aircox_cms.website import Website
|
from aircox_cms.website import Website
|
||||||
|
|
||||||
class ProgramSet (ViewSet):
|
|
||||||
model = Program
|
|
||||||
list_routes = [
|
|
||||||
AllRoute,
|
|
||||||
ThreadRoute,
|
|
||||||
SearchRoute,
|
|
||||||
DateRoute,
|
|
||||||
]
|
|
||||||
|
|
||||||
detail_sections = ViewSet.detail_sections + [
|
|
||||||
ScheduleSection(),
|
|
||||||
EpisodesSection(),
|
|
||||||
]
|
|
||||||
|
|
||||||
class EpisodeSet (ViewSet):
|
|
||||||
model = Episode
|
|
||||||
list_routes = [
|
|
||||||
AllRoute,
|
|
||||||
ThreadRoute,
|
|
||||||
SearchRoute,
|
|
||||||
DateRoute,
|
|
||||||
]
|
|
||||||
|
|
||||||
class ArticleSet (ViewSet):
|
|
||||||
model = Article
|
|
||||||
list_routes = [
|
|
||||||
AllRoute,
|
|
||||||
ThreadRoute,
|
|
||||||
SearchRoute,
|
|
||||||
DateRoute,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
website = Website(
|
website = Website(
|
||||||
name = 'RadioCampus',
|
name = 'RadioCampus',
|
||||||
|
@ -70,8 +38,40 @@ website = Website(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
website.register_set(ProgramSet)
|
base_sections = [
|
||||||
website.register_set(EpisodeSet)
|
Sections.PostContent(),
|
||||||
website.register_set(ArticleSet)
|
Sections.PostImage(),
|
||||||
|
]
|
||||||
|
|
||||||
|
base_routes = [
|
||||||
|
AllRoute,
|
||||||
|
ThreadRoute,
|
||||||
|
SearchRoute,
|
||||||
|
DateRoute,
|
||||||
|
]
|
||||||
|
|
||||||
|
website.register(
|
||||||
|
'article',
|
||||||
|
Article,
|
||||||
|
sections = base_sections,
|
||||||
|
routes = base_routes
|
||||||
|
)
|
||||||
|
|
||||||
|
website.register(
|
||||||
|
'program',
|
||||||
|
Program,
|
||||||
|
sections = base_sections + [
|
||||||
|
ScheduleSection(),
|
||||||
|
EpisodesSection(),
|
||||||
|
],
|
||||||
|
routes = base_routes,
|
||||||
|
)
|
||||||
|
|
||||||
|
website.register (
|
||||||
|
'episode',
|
||||||
|
Episode,
|
||||||
|
sections = base_sections,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = website.urls
|
urlpatterns = website.urls
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user