forked from rc/aircox
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.
|
||||
"""
|
||||
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 = (rel and rel.__dict__) or {}
|
||||
|
||||
|
@ -96,9 +126,17 @@ class RelatedPostBase (models.base.ModelBase):
|
|||
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
|
||||
|
||||
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):
|
||||
|
@ -108,25 +146,80 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
|
|||
abstract = True
|
||||
|
||||
class Relation:
|
||||
related_model = None
|
||||
mapping = None # dict of related mapping values
|
||||
bind_mapping = False # update fields of related data on save
|
||||
"""
|
||||
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')
|
||||
|
||||
if self._relation.bind_mapping:
|
||||
self.related.__dict__.update({
|
||||
rel_attr: self.__dict__[attr]
|
||||
for attr, rel_attr in self.Relation.mapping.items()
|
||||
})
|
||||
self.related.save()
|
||||
|
||||
self.update_mapping()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -57,8 +57,8 @@ class Route:
|
|||
return ''
|
||||
|
||||
@classmethod
|
||||
def as_url (cl, model, model_name, view, view_kwargs = None):
|
||||
pattern = '^{}/{}'.format(model_name, cl.name)
|
||||
def as_url (cl, name, model, view, view_kwargs = None):
|
||||
pattern = '^{}/{}'.format(name, cl.name)
|
||||
if cl.url_args:
|
||||
url_args = '/'.join([
|
||||
'(?P<{}>{}){}'.format(
|
||||
|
@ -77,7 +77,7 @@ class Route:
|
|||
kwargs.update(view_kwargs)
|
||||
|
||||
return url(pattern, view, kwargs = kwargs,
|
||||
name = model_name + '_' + cl.name)
|
||||
name = name + '_' + cl.name)
|
||||
|
||||
|
||||
class DetailRoute (Route):
|
||||
|
|
|
@ -385,50 +385,3 @@ class Sections:
|
|||
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.views as views
|
||||
|
||||
class Website:
|
||||
name = ''
|
||||
|
@ -12,26 +13,67 @@ class Website:
|
|||
'right', 'bottom',
|
||||
'header', 'footer']
|
||||
router = None
|
||||
urls = []
|
||||
registry = {}
|
||||
|
||||
@property
|
||||
def urls (self):
|
||||
return self.router.get_urlpatterns()
|
||||
|
||||
def __init__ (self, **kwargs):
|
||||
self.registry = {}
|
||||
self.urls = []
|
||||
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,
|
||||
and connect it to self.
|
||||
Register a model and return the name under which it is registered.
|
||||
Raise a ValueError if another model is yet associated under this name.
|
||||
"""
|
||||
view_set = view_set()
|
||||
view_set.connect(website = self)
|
||||
self.registry[view_set.get_detail_name()] = view_set
|
||||
self.router.register_set(view_set)
|
||||
if name in self.registry and self.registry[name] is not model:
|
||||
raise ValueError('A model has yet been registered under "{}"'
|
||||
.format(name))
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -17,6 +17,7 @@ class Episode (RelatedPost):
|
|||
related_model = programs.Episode
|
||||
bind_mapping = True
|
||||
mapping = {
|
||||
'thread': 'program',
|
||||
'title': 'name',
|
||||
'content': 'description',
|
||||
}
|
||||
|
|
|
@ -4,42 +4,10 @@ from website.models import *
|
|||
from website.views import *
|
||||
|
||||
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.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(
|
||||
name = 'RadioCampus',
|
||||
|
@ -70,8 +38,40 @@ website = Website(
|
|||
],
|
||||
)
|
||||
|
||||
website.register_set(ProgramSet)
|
||||
website.register_set(EpisodeSet)
|
||||
website.register_set(ArticleSet)
|
||||
base_sections = [
|
||||
Sections.PostContent(),
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user