# -*- coding: utf-8 -*-
"""
Copyright 2012-2013 Grégory Soutadé
This file is part of Dynastie.
Dynastie is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Dynastie is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Dynastie. If not, see .
"""
import os
import shutil
import hashlib
import inspect
import gzip
from unicodedata import normalize
from re import sub
from datetime import datetime
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import pre_init, post_init, pre_delete, post_delete
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from dynastie.generators import *
def slugify(name):
name = name.strip()
name = normalize('NFKD', name).encode('ascii', 'ignore').replace(' ', '-').lower()
#remove `other` characters
name = sub('[^a-zA-Z0-9_-]', '', name)
#nomalize dashes
name = sub('-+', '-', name)
return name
class Blog(models.Model):
name = models.CharField(max_length=255, unique=True)
title = models.CharField(max_length=255)
description = models.TextField(max_length=255, blank=True)
keywords = models.TextField(blank=True)
writers = models.ManyToManyField(User)
engines = list()
src_path = ''
output_path = ''
report = ''
def create_paths(self):
self.src_path = os.environ['DYNASTIE_ROOT'] + 'sites/' + self.name
self.output_path = os.environ['DYNASTIE_ROOT'] + 'sites/' + self.name + '_output'
def create(self):
self.create_paths()
if not os.path.exists('sites'):
os.mkdir('sites')
if not os.path.exists(self.src_path):
os.mkdir(self.src_path)
if not os.path.exists(self.output_path):
os.mkdir(self.output_path)
def remove(self):
if os.path.exists(self.src_path):
shutil.rmtree(self.src_path)
if os.path.exists(self.output_path):
shutil.rmtree(self.output_path)
def load_generators(self):
if os.path.exists(self.src_path + '/_generators'):
f = open(self.src_path + '/_generators', 'r')
for line in f:
if line.startswith("#"):
continue
engine = line.strip()
if not engine in globals():
print 'Engine ' + engine + ' doesn\'t exists'
self.engines.append(globals()[engine])
f.close()
else:
self.engines.append(globals()['post'])
self.engines.append(globals()['index'])
self.engines.append(globals()['category'])
self.engines.append(globals()['tag'])
self.engines.append(globals()['archive'])
self.engines.append(globals()['atom'])
self.engines.append(globals()['rss'])
def copytree(self, src, dst):
names = os.listdir(src)
errors = []
for name in names:
if name.startswith('_') or name.endswith('~'):
continue
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
try:
if os.path.islink(srcname) and not os.path.exists(dstname):
linkto = os.readlink(srcname)
os.symlink(linkto, dstname)
if os.path.isdir(srcname):
if not os.path.exists(dstname):
os.makedirs(dstname)
self.copytree(srcname, dstname)
else:
copied = False
if os.path.exists(dstname):
src_md5 = hashlib.md5()
f = open(srcname,'rb')
src_md5.update(f.read())
f.close()
dst_md5 = hashlib.md5()
f = open(dstname,'rb')
dst_md5.update(f.read())
f.close()
if src_md5.digest() != dst_md5.digest():
self.report = self.report + 'Update ' + dstname + '
\n'
shutil.copy2(srcname, dstname)
copied = True
else:
self.report = self.report + 'Add ' + dstname + '
\n'
shutil.copy2(srcname, dstname)
copied = True
if copied:
exts = ('css', 'html', 'htm', 'xhtml', 'js')
found = False
for ext in exts:
if srcname.endswith(ext):
found = True
break
if found:
dstname = dstname + '.gz'
if os.path.exists(dstname):
os.unlink(dstname)
f = open(srcname)
content = f.read()
f.close()
f = gzip.open(dstname, 'wb')
f.write(content)
f.close()
# XXX What about devices, sockets etc.?
except (IOError, os.error), why:
errors.append((srcname, dstname, str(why)))
# catch the Error from the recursive copytree so that we can
# continue with other files
except Exception, err:
errors.extend(err.args[0])
if errors:
raise Exception(errors)
def generate(self):
self.report = '
Generation of ' + datetime.now().strftime("%d/%m/%Y at %H:%M:%S") + '
\n'
self.load_generators()
self.copytree(self.src_path, self.output_path)
generated = []
hash_posts = {}
hash_posts_content = {}
for engine in self.engines:
if not inspect.ismodule(engine):
continue
for name, obj in inspect.getmembers(engine):
if inspect.isclass(obj) and obj.__module__.startswith("dynastie.generators"):
if obj.__module__ in generated: continue
e = obj(hash_posts, hash_posts_content)
r = e.generate(self, self.src_path, self.output_path)
generated.append(obj.__module__)
if not r is None:
self.report = self.report + '
\n' + r
return self.report
class Editor(models.Model):
name = models.CharField(max_length=255, unique=True)
class Category(models.Model):
name = models.CharField(max_length=255, unique=True)
name_slug = models.CharField(max_length=255)
parent = models.ForeignKey('self', blank=True, null=True)
description = models.TextField(max_length=255, blank=True)
blog = models.ForeignKey(Blog)
def save(self):
self.name_slug = slugify(self.name)
super(Category, self).save()
def remove(self):
blog = Blog.objects.get(pk=self.blog.id)
output = blog.output_path + '/category/' + self.name_slug
if os.path.exists(output):
shutil.rmtree(output)
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
name_slug = models.CharField(max_length=255)
blog = models.ForeignKey(Blog)
def save(self):
self.name_slug = slugify(self.name)
super(Tag, self).save()
def remove(self):
blog = Blog.objects.get(pk=self.blog.id)
output = blog.output_path + '/tag/' + self.name_slug
if os.path.exists(output):
shutil.rmtree(output)
class Post(models.Model):
title = models.CharField(max_length=255)
title_slug = models.CharField(max_length=255)
category = models.ForeignKey(Category, blank=True, null=True, on_delete=models.SET_NULL)
published = models.BooleanField()
creation_date = models.DateTimeField()
modification_date = models.DateTimeField()
front_page = models.BooleanField()
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
description = models.TextField(max_length=255, blank=True)
keywords = models.TextField(blank=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
blog = models.ForeignKey(Blog)
def getPath(self):
filename = '/post/'
filename = filename + self.creation_date.strftime("%Y") + '/' + self.creation_date.strftime("%m") + '/'
filename = filename + self.title_slug + '.html'
return filename
def save(self):
self.title = self.title.strip()
self.title_slug = slugify(self.title)
super(Post, self).save()
def createPost(self, content, tags):
b = self.blog
output = b.src_path
if not os.path.exists(output + '/_post'):
os.mkdir(output + '/_post')
filename = output + '/_post/' + str(self.pk)
content = unicode(content)
content = content.encode('utf-8')
modif = True
if os.path.exists(filename):
f = open(filename, 'rb')
src_md5 = hashlib.md5()
src_md5.update(f.read())
f.close()
dst_md5 = hashlib.md5()
dst_md5.update(content)
if src_md5.digest() == dst_md5.digest():
modif = False
else:
os.unlink(filename)
if modif:
f = open(filename, 'wb')
f.write(content)
f.close()
self.modification_date=datetime.now()
tags_list = Tag.objects.filter(blog_id=self.blog.id)
my_tags = []
# Create new tags
for tag in tags.split(','):
if tag == '': continue
tag_slug = slugify(tag)
found = False
for t in tags_list:
if t.name_slug == tag_slug:
found = True
break
if not found and not tag in my_tags:
t = Tag(blog=self.blog, name=tag.strip(), name_slug=tag_slug)
t.save()
# print 'Create ' + tag_slug
my_tags.append(tag)
# Add new tags
post_tags_list = Tag.objects.filter(post=self.id)
for tag in tags.split(','):
if tag == '': continue
tag_slug = slugify(tag)
found = False
for t in post_tags_list:
if t.name_slug == tag_slug:
found = True
break
if not found:
for t in tags_list:
if t.name_slug == tag_slug:
self.tags.add(t)
# print 'Add ' + tag_slug
break
# Remove old tags
if tags == '':
for t in post_tags_list:
self.tags.remove(t)
else:
for t in post_tags_list:
found = False
for tag in tags.split(','):
tag_slug = slugify(tag)
if t.name_slug == tag_slug:
found = True
break
if not found:
# print 'Remove ' + t.name_slug
self.tags.remove(t)
self.save()
def remove(self):
b = self.blog
output = b.src_path
filename = output + '/_post/' + str(self.pk)
if os.path.exists(filename):
os.unlink(filename)
output = b.output_path + self.getPath()
if os.path.exists(filename):
os.unlink(filename)
filename = filename + '.gz'
if os.path.exists(filename):
os.unlink(filename)
filename = b.src_path + '/post/'
filename = filename + self.creation_date.strftime("%Y") + '/' + self.creation_date.strftime("%m") + '/'
if os.path.exists(filename) and len(os.listdir(filename)) == 0:
os.rmdir(filename)
filename = b.output_path + '/post/'
filename = filename + self.creation_date.strftime("%Y") + '/'
if os.path.exists(filename) and len(os.listdir(filename)) == 0:
os.rmdir(filename)
class Comment(models.Model):
post = models.ForeignKey(Post)
parent = models.ForeignKey('self', null=True, blank=True)
date = models.DateTimeField()
author = models.CharField(max_length=255)
email = models.EmailField(max_length=255, blank=True)
the_comment = models.TextField(max_length=255)
ip = models.GenericIPAddressField()
def _update_line_returns(self):
self.the_comment = self.the_comment.replace('\n', '
')
def _remove_br(self):
self.the_comment = self.the_comment.replace('
', '\n')
@receiver(post_init, sender=Blog)
def init_blog_signal(sender, **kwargs):
kwargs['instance'].create_paths()
@receiver(post_delete, sender=Blog)
def delete_blog_signal(sender, **kwargs):
kwargs['instance'].remove()
@receiver(pre_delete, sender=Category)
def delete_category_signal(sender, **kwargs):
kwargs['instance'].remove()
@receiver(pre_delete, sender=Tag)
def delete_tag_signal(sender, **kwargs):
kwargs['instance'].remove()
@receiver(post_delete, sender=Post)
def delete_post_signal(sender, **kwargs):
kwargs['instance'].remove()
@receiver(pre_delete, sender=Post)
def pre_delete_post_signal(sender, **kwargs):
post = kwargs['instance']
comments = Comment.objects.filter(post=post.id).delete()
# Replace line returns by
for generation
@receiver(pre_save, sender=Comment)
def pre_save_comment_signal(sender, **kwargs):
kwargs['instance']._update_line_returns()