From 4a9263cdd83d93fd7e36fe5e1c1cfa8a3867e598 Mon Sep 17 00:00:00 2001 From: bkfox Date: Sun, 22 May 2016 17:43:13 +0200 Subject: [PATCH] logo; RelatedPost sync with related object --- README.md | 5 +- cms/models.py | 48 ++++-- cms/templates/aircox/cms/base_site.html | 2 +- cms/templates/aircox/cms/detail.html | 2 +- cms/templates/aircox/cms/list.html | 2 +- cms/templates/aircox/cms/section.html | 2 +- cms/templates/aircox/cms/section_list.html | 2 +- cms/views.py | 2 + data/logo.png | Bin 0 -> 18511 bytes data/logo.svg | 165 +++++++++++++++++++++ 10 files changed, 212 insertions(+), 18 deletions(-) create mode 100644 data/logo.png create mode 100644 data/logo.svg diff --git a/README.md b/README.md index 508fab6..5a62d76 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -# Aircox -Platform to manage a radio. We use the power of Django +![](/data/logo.png) + +Platform to manage a radio, schedules, website, and so on. We use the power of Django and Liquidsoap. ## Current features * **streams**: multiple random music streams when no program is played. We also can specify a time range and frequency; diff --git a/cms/models.py b/cms/models.py index aef69e3..33e538b 100644 --- a/cms/models.py +++ b/cms/models.py @@ -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) diff --git a/cms/templates/aircox/cms/base_site.html b/cms/templates/aircox/cms/base_site.html index 6320787..7172f8a 100644 --- a/cms/templates/aircox/cms/base_site.html +++ b/cms/templates/aircox/cms/base_site.html @@ -8,7 +8,7 @@ - + {% if website.styles %} {% endif %} diff --git a/cms/templates/aircox/cms/detail.html b/cms/templates/aircox/cms/detail.html index c022427..2859c4b 100644 --- a/cms/templates/aircox/cms/detail.html +++ b/cms/templates/aircox/cms/detail.html @@ -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 }} diff --git a/cms/templates/aircox/cms/list.html b/cms/templates/aircox/cms/list.html index 1edd77d..abce834 100644 --- a/cms/templates/aircox/cms/list.html +++ b/cms/templates/aircox/cms/list.html @@ -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 %} diff --git a/cms/templates/aircox/cms/section.html b/cms/templates/aircox/cms/section.html index 25febd0..494006c 100644 --- a/cms/templates/aircox/cms/section.html +++ b/cms/templates/aircox/cms/section.html @@ -1,4 +1,4 @@ -{% extends "aircox_cms/base_section.html" %} +{% extends "aircox/cms/base_section.html" %} {% block content %} {% if title %} diff --git a/cms/templates/aircox/cms/section_list.html b/cms/templates/aircox/cms/section_list.html index 35589ee..f1eb561 100644 --- a/cms/templates/aircox/cms/section_list.html +++ b/cms/templates/aircox/cms/section_list.html @@ -1,4 +1,4 @@ -{% extends "aircox_cms/section.html" %} +{% extends "aircox/cms/section.html" %} {% load thumbnail %} diff --git a/cms/views.py b/cms/views.py index 0ca3e71..5ac4678 100644 --- a/cms/views.py +++ b/cms/views.py @@ -307,6 +307,8 @@ class Sections: """ @property def content (self): + if not self.object.image: + return '' return ''.format( self.object.image.url ) diff --git a/data/logo.png b/data/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cfab6722fd30a6ed026d8a6ee37c18099cf164ff GIT binary patch literal 18511 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 literal 0 HcmV?d00001 diff --git a/data/logo.svg b/data/logo.svg new file mode 100644 index 0000000..e3fbe21 --- /dev/null +++ b/data/logo.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + Aircox + + + + +