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

@ -1,5 +1,6 @@
# Aircox ![](/data/logo.png)
Platform to manage a radio. We use the power of Django
Platform to manage a radio, schedules, website, and so on. We use the power of Django and Liquidsoap.
## Current features ## Current features
* **streams**: multiple random music streams when no program is played. We also can specify a time range and frequency; * **streams**: multiple random music streams when no program is played. We also can specify a time range and frequency;

View File

@ -92,15 +92,15 @@ class RelatedPostBase (models.base.ModelBase):
registry = {} registry = {}
@classmethod @classmethod
def register (cl, key, model): def register (cl, key, post_model):
""" """
Register a model and return the key under which it is registered. Register a model and return the key under which it is registered.
Raise a ValueError if another model is yet associated under this key. 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 "{}"' raise ValueError('A model has yet been registered with "{}"'
.format(key)) .format(key))
cl.registry[key] = model cl.registry[key] = post_model
return key return key
@classmethod @classmethod
@ -137,13 +137,15 @@ class RelatedPostBase (models.base.ModelBase):
return rel return rel
def __new__ (cl, name, bases, attrs): def __new__ (cl, name, bases, attrs):
# TODO: allow proxy models and better inheritance
if name == 'RelatedPost': if name == 'RelatedPost':
return super().__new__(cl, name, bases, attrs) return super().__new__(cl, name, bases, attrs)
rel = cl.make_relation(name, attrs) rel = cl.make_relation(name, attrs)
field_args = rel.field_args or {}
attrs['_relation'] = rel attrs['_relation'] = rel
attrs.update({ x:y for x,y in { 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) '__str__': lambda self: str(self.related)
}.items() if not attrs.get(x) }) }.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 * rel_to_post: auto update the post when related object is updated
* thread_model: generated by the metaclass, points to the RelatedPost * thread_model: generated by the metaclass, points to the RelatedPost
model generated for the bindings.thread object. 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! Be careful with post_to_rel!
* There is no check of permissions when related object is synchronised * There is no check of permissions when related object is synchronised
@ -217,14 +221,15 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
model = None model = None
bindings = None # values to map { post_attr: rel_attr } bindings = None # values to map { post_attr: rel_attr }
post_to_rel = False post_to_rel = False
rel_to_post = True rel_to_post = False
thread_model = None thread_model = None
field_args = None
def get_rel_attr(self, attr): def get_rel_attr(self, attr):
attr = self._relation.bindings.get(attr) attr = self._relation.bindings.get(attr)
return getattr(self.related, attr) if attr else None 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: if attr not in self._relation.bindings:
raise AttributeError('attribute {} is not bound'.format(attr)) raise AttributeError('attribute {} is not bound'.format(attr))
attr = self._relation.bindings.get(attr) attr = self._relation.bindings.get(attr)
@ -240,13 +245,13 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if not rel.bindings: if not rel.bindings:
return return
for attr, rel_attr in rel.bindings.items() for attr, rel_attr in rel.bindings.items():
if attr == 'thread': if attr == 'thread':
continue continue
value = getattr(self, attr) if hasattr(self, attr) else None value = getattr(self, attr) if hasattr(self, attr) else None
setattr(self.related, rel_attr, value) setattr(self.related, rel_attr, value)
if self.thread_model: if rel.thread_model:
thread = self.thread if not issubclass(thread, rel.thread_model) \ thread = self.thread if not issubclass(thread, rel.thread_model) \
else None else None
self.set_rel_attr('thread', thread.related) self.set_rel_attr('thread', thread.related)
@ -254,6 +259,19 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if save: if save:
self.related.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): def rel_to_post(self, save = True):
""" """
Change the post using the related object bound values. Save the Change the post using the related object bound values. Save the
@ -264,7 +282,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if rel.bindings: if rel.bindings:
return return
for attr, rel_attr in rel.bindings.items() for attr, rel_attr in rel.bindings.items():
if attr == 'thread': if attr == 'thread':
continue continue
self.set_rel_attr self.set_rel_attr
@ -272,7 +290,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if hasattr(self.related, attr) else None if hasattr(self.related, attr) else None
setattr(self, attr, value) setattr(self, attr, value)
if self.thread_model: if rel.thread_model:
thread = self.get_rel_attr('thread') thread = self.get_rel_attr('thread')
thread = rel.thread_model.objects.filter(related = thread) \ thread = rel.thread_model.objects.filter(related = thread) \
if thread else None if thread else None
@ -282,11 +300,19 @@ class RelatedPost (Post, metaclass = RelatedPostBase):
if save: if save:
self.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): def save (self, *args, **kwargs):
# TODO handle when related change
if not self.title and self.related: if not self.title and self.related:
self.title = self.get_rel_attr('title') self.title = self.get_rel_attr('title')
if self._relation.post_to_rel: if self._relation.post_to_rel:
self.post_to_rel(False) self.post_to_rel(save = True)
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@ -8,7 +8,7 @@
<meta name="description" content="{{ website.description }}"> <meta name="description" content="{{ website.description }}">
<meta name="keywords" content="{{ website.tags }}"> <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 %} {% if website.styles %}
<link rel="stylesheet" href="{% static website.styles %}" type="text/css"> <link rel="stylesheet" href="{% static website.styles %}" type="text/css">
{% endif %} {% 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 %} {% block title %}
{{ object.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 i18n %}
{% load thumbnail %} {% load thumbnail %}

View File

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

View File

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

View File

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

BIN
data/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

165
data/logo.svg Normal file
View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 744.09448819 1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="logo.svg">
<defs
id="defs4">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4570">
<path
d="M 124.61621,447.85033 A 128.45006,128.45006 0 0 1 26.456632,334.05117 128.45006,128.45006 0 0 1 103.46536,204.99611 l 50.95693,117.91017 z"
sodipodi:end="4.3044632"
sodipodi:start="1.8049749"
sodipodi:ry="128.45006"
sodipodi:rx="128.45006"
sodipodi:cy="322.90628"
sodipodi:cx="154.42229"
sodipodi:type="arc"
style="opacity:1;fill:#9f9f9f;fill-opacity:0.38967136;fill-rule:nonzero;stroke:none;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path4572" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4589">
<path
d="m -417.18553,459.80156 a 128.45006,128.45006 0 0 1 -98.15958,-113.79916 128.45006,128.45006 0 0 1 77.00872,-129.05506 l 50.95693,117.91017 z"
sodipodi:end="4.3044632"
sodipodi:start="1.8049749"
sodipodi:ry="128.45006"
sodipodi:rx="128.45006"
sodipodi:cy="334.85751"
sodipodi:cx="-387.37946"
sodipodi:type="arc"
style="opacity:1;fill:#9f9f9f;fill-opacity:0.38967136;fill-rule:nonzero;stroke:none;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path4591"
transform="matrix(-0.99924777,0.03878018,0.03878018,0.99924777,0,0)" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.81249936"
inkscape:cx="87.39633"
inkscape:cy="611.94509"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
showguides="false"
inkscape:window-width="1364"
inkscape:window-height="736"
inkscape:window-x="0"
inkscape:window-y="1454"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g4202"
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.89378493,0,0,0.89378493,41.184024,25.814665)" />
<g
id="g4376"
transform="matrix(0.97221505,-0.23408952,0.23408952,0.97221505,-73.655087,41.382345)"
clip-path="url(#clipPath4570)">
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4155-6"
d="m 110.80568,266.04718 c -3.29167,-19.8207 -11.124776,-15.37046 -12.352066,-24.27303 -0.14135,-3.58896 4.663986,-8.38983 8.867286,-4.51659 7.90435,9.32416 0.41767,10.96241 14.73478,22.36105 -2.96184,1.98522 -7.49488,4.28469 -11.25,6.42857 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4155-7-80"
d="m 97.267374,355.00848 c -19.8207,3.29167 -15.37045,11.12478 -24.27302,12.35207 -3.58896,0.14135 -8.38984,-4.66399 -4.51659,-8.86729 9.32415,-7.90435 10.96241,-0.41767 22.36104,-14.73478 1.98522,2.96184 4.28469,7.49488 6.42857,11.25 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4155-7-8-6"
d="m 85.476584,307.73621 c -16.34291,-11.68779 -18.73496,-3.00215 -25.89785,-8.42939 -2.63773,-2.43783 -2.63457,-9.23045 3.07641,-9.46383 12.18239,1.00395 8.04693,7.45626 26.23071,5.39258 -0.69058,3.4981 -2.26995,8.32941 -3.40927,12.50064 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4155-7-0-0"
d="m 139.41029,379.1225 c -11.68779,16.34292 -3.00214,18.73496 -8.42939,25.89785 -2.43783,2.63773 -9.23045,2.63458 -9.46383,-3.07641 1.00394,-12.18239 7.45626,-8.04693 5.39257,-26.23071 3.4981,0.69058 8.32942,2.26995 12.50065,3.40927 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<circle
id="path4136-5-5"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
cx="148.58382"
cy="316.89429"
r="61.081726" />
<circle
r="50.376423"
cy="317.04373"
cx="148.79494"
id="path4136-5-6-5"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.40858507;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
<flowRoot
xml:space="preserve"
id="flowRoot4439"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80px;line-height:125%;font-family:Adler;-inkscape-font-specification:Adler;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="matrix(0.96092081,0,0,0.96092081,-26.716999,5.7888348)"><flowRegion
id="flowRegion4441"><rect
id="rect4443"
width="416.00034"
height="148.9232"
x="141.53857"
y="278.20776"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80px;font-family:Adler;-inkscape-font-specification:Adler" /></flowRegion><flowPara
id="flowPara4445"><flowSpan
style="fill:#ff0000"
id="flowSpan4447">Air</flowSpan>cox</flowPara></flowRoot> <g
id="g4492"
clip-path="url(#clipPath4589)"
transform="matrix(0.962985,-0.26955499,0.26955499,0.962985,-82.471435,120.78786)">
<circle
r="61.731167"
cy="313.76675"
cx="403.01682"
id="path4136-5-6"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:7.99999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
r="43.736217"
cy="313.76675"
cx="403.01682"
id="path4136-5-6-4"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:4.99999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB