logo; RelatedPost sync with related object

This commit is contained in:
bkfox
2016-05-22 17:43:13 +02:00
parent a4f1b03bde
commit 4a9263cdd8
10 changed files with 212 additions and 18 deletions

View File

@ -92,15 +92,15 @@ class RelatedPostBase (models.base.ModelBase):
registry = {}
@classmethod
def register (cl, key, model):
def register (cl, key, post_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:
if key in cl.registry and cl.registry[key] is not post_model:
raise ValueError('A model has yet been registered with "{}"'
.format(key))
cl.registry[key] = model
cl.registry[key] = post_model
return key
@classmethod
@ -137,13 +137,15 @@ class RelatedPostBase (models.base.ModelBase):
return rel
def __new__ (cl, name, bases, attrs):
# TODO: allow proxy models and better inheritance
if name == 'RelatedPost':
return super().__new__(cl, name, bases, attrs)
rel = cl.make_relation(name, attrs)
field_args = rel.field_args or {}
attrs['_relation'] = rel
attrs.update({ x:y for x,y in {
'related': models.ForeignKey(rel.model),
'related': models.ForeignKey(rel.model, **field_args),
'__str__': lambda self: str(self.related)
}.items() if not attrs.get(x) })
@ -207,6 +209,8 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
* rel_to_post: auto update the post when related object is updated
* thread_model: generated by the metaclass, points to the RelatedPost
model generated for the bindings.thread object.
* field_args: dict of arguments to pass to the ForeignKey constructor,
such as: ForeignKey(related_model, **field_args)
Be careful with post_to_rel!
* There is no check of permissions when related object is synchronised
@ -217,14 +221,15 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
model = None
bindings = None # values to map { post_attr: rel_attr }
post_to_rel = False
rel_to_post = True
rel_to_post = False
thread_model = None
field_args = None
def get_rel_attr(self, attr):
attr = self._relation.bindings.get(attr)
return getattr(self.related, attr) if attr else None
def set_rel_attr(self, attr, value)
def set_rel_attr(self, attr, value):
if attr not in self._relation.bindings:
raise AttributeError('attribute {} is not bound'.format(attr))
attr = self._relation.bindings.get(attr)
@ -240,13 +245,13 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if not rel.bindings:
return
for attr, rel_attr in rel.bindings.items()
for attr, rel_attr in rel.bindings.items():
if attr == 'thread':
continue
value = getattr(self, attr) if hasattr(self, attr) else None
setattr(self.related, rel_attr, value)
if self.thread_model:
if rel.thread_model:
thread = self.thread if not issubclass(thread, rel.thread_model) \
else None
self.set_rel_attr('thread', thread.related)
@ -254,6 +259,19 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if save:
self.related.save()
@classmethod
def sync_from_rel(cl, rel, save = True):
"""
Update a rel_to_post from a given rel object. Return -1 if there is no
related post to update
"""
self = cl.objects.filter(related = rel)
if not self or not self.count():
return -1
self[0].rel_to_post(save)
def rel_to_post(self, save = True):
"""
Change the post using the related object bound values. Save the
@ -264,7 +282,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if rel.bindings:
return
for attr, rel_attr in rel.bindings.items()
for attr, rel_attr in rel.bindings.items():
if attr == 'thread':
continue
self.set_rel_attr
@ -272,7 +290,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if hasattr(self.related, attr) else None
setattr(self, attr, value)
if self.thread_model:
if rel.thread_model:
thread = self.get_rel_attr('thread')
thread = rel.thread_model.objects.filter(related = thread) \
if thread else None
@ -282,11 +300,19 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if save:
self.save()
def __init__ (self, *kargs, **kwargs):
super().__init__(*kargs, **kwargs)
# we use this method for sync, in order to avoid intrusive code on other
# applications, e.g. using signals.
if self._relation.rel_to_post:
self.rel_to_post(save = False)
def save (self, *args, **kwargs):
# TODO handle when related change
if not self.title and self.related:
self.title = self.get_rel_attr('title')
if self._relation.post_to_rel:
self.post_to_rel(False)
self.post_to_rel(save = True)
super().save(*args, **kwargs)

View File

@ -8,7 +8,7 @@
<meta name="description" content="{{ website.description }}">
<meta name="keywords" content="{{ website.tags }}">
<link rel="stylesheet" href="{% static "aircox_cms/styles.css" %}" type="text/css">
<link rel="stylesheet" href="{% static "aircox/cms/styles.css" %}" type="text/css">
{% if website.styles %}
<link rel="stylesheet" href="{% static website.styles %}" type="text/css">
{% endif %}

View File

@ -1,4 +1,4 @@
{% extends embed|yesno:"aircox_cms/base_content.html,aircox_cms/base_site.html" %}
{% extends embed|yesno:"aircox/cms/base_content.html,aircox/cms/base_site.html" %}
{% block title %}
{{ object.title }}

View File

@ -1,4 +1,4 @@
{% extends embed|yesno:"aircox_cms/base_content.html,aircox_cms/base_site.html" %}
{% extends embed|yesno:"aircox/cms/base_content.html,aircox/cms/base_site.html" %}
{% load i18n %}
{% load thumbnail %}

View File

@ -1,4 +1,4 @@
{% extends "aircox_cms/base_section.html" %}
{% extends "aircox/cms/base_section.html" %}
{% block content %}
{% if title %}

View File

@ -1,4 +1,4 @@
{% extends "aircox_cms/section.html" %}
{% extends "aircox/cms/section.html" %}
{% load thumbnail %}

View File

@ -307,6 +307,8 @@ class Sections:
"""
@property
def content (self):
if not self.object.image:
return ''
return '<img src="{}" class="post_image">'.format(
self.object.image.url
)