diff --git a/README.md b/README.md index 5a62d76..c4f3733 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Later we would provide a package, but now we have other priorities. ### settings.py * There must be `BASE_DIR` or `PROJECT_ROOT` defined in order to make liquidsoap working (that must call manage.py using an absolute path). * INSTALLED_APPS: - - dependencies: `'taggit'`, `'easy_thumbnails'` + - dependencies: `'taggit'` (*programs* and *cms* applications), + `'easy_thumbnails'` (*cms*), `'honeypot'` (*cms*) - optional dependencies (in order to make users' life easier): `'autocomplete_light'`, `'suit'` - aircox: `'aircox.programs'`, `'aircox.liquidsoap'`, `'aircox.cms'` diff --git a/cms/models.py b/cms/models.py index 69e8e6a..54cf854 100644 --- a/cms/models.py +++ b/cms/models.py @@ -7,7 +7,8 @@ from django.utils.text import slugify from django.utils.translation import ugettext as _, ugettext_lazy from django.core.urlresolvers import reverse -from django.db.models.signals import post_init, post_save, post_delete + +from django.db.models.signals import Signal, post_save from django.dispatch import receiver from taggit.managers import TaggableManager @@ -43,6 +44,43 @@ class ProxyPost: self.detail_url = thread.detail_url() +class Comment(models.Model): + thread_type = models.ForeignKey( + ContentType, + on_delete=models.SET_NULL, + blank = True, null = True + ) + thread_id = models.PositiveIntegerField( + blank = True, null = True + ) + thread = GenericForeignKey('thread_type', 'thread_id') + + published = models.BooleanField( + verbose_name = _('public'), + default = False + ) + + author = models.CharField( + verbose_name = _('author'), + max_length = 32, + ) + email = models.EmailField( + verbose_name = _('email'), + blank = True, null = True, + ) + url = models.URLField( + verbose_name = _('website'), + blank = True, null = True, + ) + date = models.DateTimeField( + _('date'), + default = timezone.datetime.now + ) + content = models.TextField ( + _('comment'), + ) + + class Post (models.Model): """ Base model that can be used as is if wanted. Represent a generic @@ -84,7 +122,7 @@ class Post (models.Model): ) content = models.TextField ( _('description'), - blank = True, null = True + default = '', ) image = models.ImageField( blank = True, null = True @@ -96,7 +134,7 @@ class Post (models.Model): search_fields = [ 'title', 'content' ] - def get_proxy(self): + def as_proxy(self): """ Return a ProxyPost instance using this post """ @@ -111,14 +149,36 @@ class Post (models.Model): if not queryset: queryset = cl.objects thread_type = ContentType.objects.get_for_model(thread) - qs = queryset.filter(thread_id = thread.pk, - thread_type__pk = thread_type.id) + qs = queryset.filter( + thread_id = thread.pk, + thread_type__pk = thread_type.id + ) + return qs + + def get_comments(self): + """ + Return comments pointing to this post + """ + type = ContentType.objects.get_for_model(self) + qs = Comment.objects.filter( + thread_id = self.pk, + thread_type__pk = type.pk + ) return qs def detail_url(self): return self.route_url(routes.DetailRoute, { 'pk': self.pk, 'slug': slugify(self.title) }) + + def get_object_list(self, request, object, **kwargs): + type = ContentType.objects.get_for_model(object) + qs = Comment.objects.filter( + thread_id = object.pk, + thread_type__pk = type.pk + ) + return qs + @classmethod def route_url(cl, route, kwargs = None): name = cl._website.name_of_model(cl) @@ -198,6 +258,24 @@ class RelatedPostBase (models.base.ModelBase): return rel + @classmethod + def make_auto_create(cl, model): + if not model._relation.rel_to_post: + return + + def handler(sender, instance, created, *args, **kwargs): + rel = model._relation + post = model.objects.filter(related = instance) + if post.count(): + post = post[0] + elif rel.auto_create: + post = model(related = instance) + else: + return + post.rel_to_post() + post.save(avoid_sync = True) + post_save.connect(handler, model._relation.model, False) + def __new__ (cl, name, bases, attrs): # TODO: allow proxy models and better inheritance # TODO: check bindings @@ -213,9 +291,11 @@ class RelatedPostBase (models.base.ModelBase): }.items() if not attrs.get(x) }) model = super().__new__(cl, name, bases, attrs) - print(model, model.related) cl.register(rel.model, model) + # auto create and/or update + cl.make_auto_create(model) + # name clashes name = rel.model._meta.object_name if name == model._meta.object_name: @@ -275,6 +355,9 @@ class RelatedPost (Post, metaclass = RelatedPostBase): 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) + * auto_create: automatically create a RelatedPost for each new item of + the related object and init it with bounded values. Use signals + '', ''. Be careful with post_to_rel! * There is no check of permissions when related object is synchronised @@ -288,6 +371,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase): rel_to_post = False thread_model = None field_args = None + auto_create = False def get_rel_attr(self, attr): attr = self._relation.bindings.get(attr) @@ -306,7 +390,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase): Note: does not check if Relation.post_to_rel is True """ rel = self._relation - if not rel.bindings: + if not self.related or not rel.bindings: return for attr, rel_attr in rel.bindings.items(): @@ -331,7 +415,7 @@ class RelatedPost (Post, metaclass = RelatedPostBase): Note: does not check if Relation.post_to_rel is True """ rel = self._relation - if not rel.bindings: + if not self.related or not rel.bindings: return has_changed = False @@ -362,44 +446,16 @@ class RelatedPost (Post, metaclass = RelatedPostBase): # we use this method for sync, in order to avoid intrusive code on other # applications, e.g. using signals. if self.pk and self._relation.rel_to_post: - self.rel_to_post(save = False) + self.rel_to_post(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(save = True) + def save (self, avoid_sync = False, *args, **kwargs): + """ + If avoid_relation, do not synchronise the post/related object. + """ + if not avoid_sync: + if not self.pk and self._relation.rel_to_post: + self.rel_to_post(False) + if self._relation.post_to_rel: + self.post_to_rel(True) super().save(*args, **kwargs) - -class Comment(models.Model): - thread_type = models.ForeignKey( - ContentType, - on_delete=models.SET_NULL, - blank = True, null = True - ) - thread_id = models.PositiveIntegerField( - blank = True, null = True - ) - thread = GenericForeignKey('thread_type', 'thread_id') - - author = models.TextField( - verbose_name = _('author'), - blank = True, null = True, - ) - email = models.EmailField( - verbose_name = _('email'), - blank = True, null = True, - ) - date = models.DateTimeField( - _('date'), - default = timezone.datetime.now - ) - content = models.TextField ( - _('description'), - blank = True, null = True - ) - - - diff --git a/cms/static/aircox/cms/styles.css b/cms/static/aircox/cms/styles.css index d18f4f5..2a543d6 100644 --- a/cms/static/aircox/cms/styles.css +++ b/cms/static/aircox/cms/styles.css @@ -1,3 +1,4 @@ +/** main layout **/ body { padding: 0; margin: 0; @@ -25,6 +26,7 @@ body { } +/** detail and list content **/ main .section { /* width: calc(50% - 2em); display: inline-block; */ @@ -49,3 +51,36 @@ main .section { } +/** comments **/ +.comment-form label { + display: none; +} + +.comment-form input:not([type=checkbox]), +.comment-form textarea { + display: inline-block; + width: calc(100% - 5em); + max-height: 6em; + margin: 0.2em 0em; +} + +.comment-form input[type=checkbox], +.comment-form button[type=submit] { + max-width: 4em; + vertical-align:bottom; + margin: 0.2em 0em; + text-align: center; +} + +.comment-form .extra { + display: none; +} + +.comment-form input[type="checkbox"]:checked + .extra { + display: block; +} + + + + + diff --git a/cms/templates/aircox/cms/detail.html b/cms/templates/aircox/cms/detail.html index e50fe53..6d39f0e 100644 --- a/cms/templates/aircox/cms/detail.html +++ b/cms/templates/aircox/cms/detail.html @@ -29,6 +29,6 @@ {% endblock %} {% block content %} -{{ content }} +{{ content|safe }} {% endblock %} diff --git a/cms/templates/aircox/cms/section.html b/cms/templates/aircox/cms/section.html index afecc88..672ef50 100644 --- a/cms/templates/aircox/cms/section.html +++ b/cms/templates/aircox/cms/section.html @@ -10,21 +10,21 @@ {% endif %} {% if header %} -
+
{% block section_header %} {{ header }} {% endblock %}
{% endif %} -
+
{% block section_content %} {{ content|safe }} {% endblock %}
{% if footer %} -