diff --git a/cms/admin.py b/cms/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/cms/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/cms/models.py b/cms/models.py new file mode 100644 index 0000000..4d921b5 --- /dev/null +++ b/cms/models.py @@ -0,0 +1,154 @@ +from django.db import models +from django.contrib.auth.models import User +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.utils import timezone +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_save +from django.dispatch import receiver + + +# Using a separate thread helps for routing, by avoiding to specify an +# additional argument to get the second model that implies to find it by +# the name that can be non user-friendly, like /thread/relatedpost/id +class Thread (models.Model): + post_type = models.ForeignKey(ContentType) + post_id = models.PositiveIntegerField() + post = GenericForeignKey('post_type', 'post_id') + + @classmethod + def get (cl, model, **kwargs): + post_type = ContentType.objects.get_for_model(model) + return cl.objects.get(post_type__pk = post_type.id, + **kwargs) + + @classmethod + def filter (cl, model, **kwargs): + post_type = ContentType.objects.get_for_model(model) + return cl.objects.filter(post_type__pk = post_type.id, + **kwargs) + + @classmethod + def exclude (cl, model, **kwargs): + post_type = ContentType.objects.get_for_model(model) + return cl.objects.exclude(post_type__pk = post_type.id, + **kwargs) + + def __str__ (self): + return self.post_type.name + ': ' + str(self.post) + + +class Post (models.Model): + thread = models.ForeignKey( + Thread, + on_delete=models.SET_NULL, + blank = True, null = True, + help_text = _('the publication is posted on this thread'), + ) + author = models.ForeignKey( + User, + verbose_name = _('author'), + blank = True, null = True, + ) + date = models.DateTimeField( + _('date'), + default = timezone.datetime.now + ) + public = models.BooleanField( + verbose_name = _('public'), + default = True + ) + image = models.ImageField( + blank = True, null = True + ) + + title = '' + content = '' + + def detail_url (self): + return reverse(self._meta.verbose_name_plural.lower() + '_detail', + kwargs = { 'pk': self.pk, + 'slug': slugify(self.title) }) + + class Meta: + abstract = True + + +@receiver(post_save) +def on_new_post (sender, instance, created, *args, **kwargs): + """ + Signal handler to create a thread that is attached to the newly post + """ + if not issubclass(sender, Post) or not created: + return + + thread = Thread(post = instance) + thread.save() + + +class Article (Post): + title = models.CharField( + _('title'), + max_length = 128, + blank = False, null = False + ) + content = models.TextField( + _('content'), + blank = False, null = False + ) + static_page = models.BooleanField( + _('static page'), + default = False, + ) + focus = models.BooleanField( + _('article is focus'), + default = False, + ) + + class Meta: + verbose_name = _('Article') + verbose_name_plural = _('Articles') + + +class RelatedPostBase (models.base.ModelBase): + """ + Metaclass for RelatedPost children. + """ + def __new__ (cls, name, bases, attrs): + rel = attrs.get('Relation') + rel = (rel and rel.__dict__) or {} + + related_model = rel.get('related_model') + if related_model: + attrs['related'] = models.ForeignKey(related_model) + + mapping = rel.get('mapping') + if mapping: + def get_prop (name, related_name): + return property(related_name) if callable(related_name) \ + else property(lambda self: + getattr(self.related, related_name)) + + attrs.update({ + name: get_prop(name, related_name) + for name, related_name in mapping.items() + }) + + if not '__str__' in attrs: + attrs['__str__'] = lambda self: str(self.related) + + return super().__new__(cls, name, bases, attrs) + + +class RelatedPost (Post, metaclass = RelatedPostBase): + class Meta: + abstract = True + + class Relation: + related_model = None + mapping = None + + diff --git a/website/routes.py b/cms/routes.py similarity index 82% rename from website/routes.py rename to cms/routes.py index 57f72a4..d890cca 100644 --- a/website/routes.py +++ b/cms/routes.py @@ -42,8 +42,7 @@ class Route: is_list = False # route is for a list url_args = [] # arguments passed from the url [ (name : regex),... ] - def __init__ (self, model, view, view_kwargs = None, - base_name = None): + def __init__ (self, model, view, view_kwargs = None): self.model = model self.view = view self.view_kwargs = view_kwargs @@ -54,11 +53,7 @@ class Route: _meta.update(self.Meta.__dict__) self._meta = _meta - if not base_name: - base_name = model._meta.verbose_name_plural if _meta['is_list'] \ - else model._meta.verbose_name - base_name = base_name.title().lower() - self.base_name = base_name + self.base_name = model._meta.verbose_name_plural.lower() def get_queryset (self, request, **kwargs): """ @@ -75,9 +70,13 @@ class Route: def get_url (self): pattern = '^{}/{}'.format(self.base_name, self.Meta.name) if self._meta['url_args']: - url_args = '/'.join([ '(?P<{}>{})'.format(arg, expr) \ - for arg, expr in self._meta['url_args'] - ]) + url_args = '/'.join([ + '(?P<{}>{}){}'.format( + arg, expr, + (optional and optional[0] and '?') or '' + ) + for arg, expr, *optional in self._meta['url_args'] + ]) pattern += '/' + url_args pattern += '/?$' @@ -87,7 +86,8 @@ class Route: if self.view_kwargs: kwargs.update(self.view_kwargs) - return url(pattern, self.view, kwargs = kwargs, name = '{}') + return url(pattern, self.view, kwargs = kwargs, + name = self.base_name + '_' + self.Meta.name) class DetailRoute (Route): @@ -96,19 +96,28 @@ class DetailRoute (Route): is_list = False url_args = [ ('pk', '[0-9]+'), - ('slug', '(\w|-|_)*'), + ('slug', '(\w|-|_)+', True), ] def get (self, request, **kwargs): return self.model.objects.get(pk = int(kwargs['pk'])) +class AllRoute (Route): + class Meta: + name = 'all' + is_list = True + + def get_queryset (self, request, **kwargs): + return self.model.objects.all() + + class ThreadRoute (Route): class Meta: name = 'thread' is_list = True url_args = [ - ('pk', '[0-9]+') + ('pk', '[0-9]+'), ] def get_queryset (self, request, **kwargs): diff --git a/website/templates/website/list.html b/cms/templates/cms/list.html similarity index 96% rename from website/templates/website/list.html rename to cms/templates/cms/list.html index d5293c3..51204f6 100644 --- a/website/templates/website/list.html +++ b/cms/templates/cms/list.html @@ -9,8 +9,7 @@
{% for post in object_list %} - + href="{{ post.detail_url }}"> {% if 'date' in list.fields or 'time' in list.fields %}