forked from rc/aircox
RelatedPost: mapping['thread'] now manage relation between thread and parent of related object
This commit is contained in:
@ -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):
|
||||
"""
|
||||
|
Reference in New Issue
Block a user