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 %}