From 0032e216b87ac8829c959ab2887491cc3bfb8a5b Mon Sep 17 00:00:00 2001 From: bkfox Date: Tue, 13 Oct 2015 12:38:18 +0200 Subject: [PATCH] RelatedPost: mapping['thread'] now manage relation between thread and parent of related object --- aircox_cms/models.py | 117 +++++++++++++++++++++++++++++++++++++----- aircox_cms/routes.py | 6 +-- aircox_cms/views.py | 47 ----------------- aircox_cms/website.py | 68 +++++++++++++++++++----- website/models.py | 1 + website/urls.py | 72 +++++++++++++------------- 6 files changed, 200 insertions(+), 111 deletions(-) diff --git a/aircox_cms/models.py b/aircox_cms/models.py index 820809f..6950089 100644 --- a/aircox_cms/models.py +++ b/aircox_cms/models.py @@ -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) diff --git a/aircox_cms/routes.py b/aircox_cms/routes.py index c31f2d3..82467ab 100644 --- a/aircox_cms/routes.py +++ b/aircox_cms/routes.py @@ -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): diff --git a/aircox_cms/views.py b/aircox_cms/views.py index c579d8d..451eefd 100644 --- a/aircox_cms/views.py +++ b/aircox_cms/views.py @@ -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 ) ] - - diff --git a/aircox_cms/website.py b/aircox_cms/website.py index dd35c70..db53ffb 100644 --- a/aircox_cms/website.py +++ b/aircox_cms/website.py @@ -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): """ diff --git a/website/models.py b/website/models.py index 1551a04..5107b70 100644 --- a/website/models.py +++ b/website/models.py @@ -17,6 +17,7 @@ class Episode (RelatedPost): related_model = programs.Episode bind_mapping = True mapping = { + 'thread': 'program', 'title': 'name', 'content': 'description', } diff --git a/website/urls.py b/website/urls.py index 71db994..dc95216 100644 --- a/website/urls.py +++ b/website/urls.py @@ -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