Add tags support

Set slugify to be global
Update modification date only if text has been changed
Fix a bug in preview (post_id was passed instead of blog_id)
Add Home, Category and Tag links in blog area instead of global area (dynastie template)
Fix a bug in Have_I_right
This commit is contained in:
Grégory Soutadé 2012-11-08 21:23:30 +01:00
parent a2f091a0f5
commit 85488e0838
24 changed files with 289 additions and 67 deletions

View File

@ -1,4 +1,5 @@
from django.forms import ModelForm
from django import forms
from dynastie.models import *
class BlogForm(ModelForm):
@ -6,6 +7,10 @@ class BlogForm(ModelForm):
model = Blog
class PostForm(ModelForm):
description = forms.CharField(widget=forms.Textarea(attrs={'rows':'5', 'cols':'50'}), required=False)
keywords = forms.CharField(widget=forms.Textarea(attrs={'rows':'2', 'cols':'50'}), required=False)
text_tags = forms.CharField(widget=forms.Textarea(attrs={'rows':'2', 'cols':'50'}), required=False)
class Meta:
model = Post
exclude = ('title_slug', 'creation_date', 'modification_date', 'author', 'blog', 'tags')
@ -28,3 +33,8 @@ class CommentForm(ModelForm):
class Meta:
model = Comment
exclude = ('post', 'parent', 'date')
class TagForm(ModelForm):
class Meta:
model = Tag
exclude = ('blog', 'name_slug')

View File

@ -1 +1 @@
__all__ = ["generator", "index", "post", "category", "archive", "rss", "atom"]
__all__ = ["generator", "index", "post", "category", "tag", "archive", "rss", "atom"]

View File

@ -57,9 +57,10 @@ class Archive(Index):
def generate(self, blog, src, output):
from dynastie.models import Post, Blog
hooks = {'posts' : self.createPosts,
self.hooks = {'posts' : self.createPosts,
'navigation' : self.createNavigation,
'archive' : self.createArchive}
'archive' : self.createArchive,
'tags' : self.createTags}
if not os.path.exists(src + '/_archive.html'):
self.addError('No _archive.html found, exiting')
@ -99,7 +100,7 @@ class Archive(Index):
if posts[i].creation_date.year != posts[i+1].creation_date.year:
dom = parse(src + '/_archive.html')
my_post.reverse()
self.createArchives(src, output, dom, hooks, my_post)
self.createArchives(src, output, dom, self.hooks, my_post)
self.cur_year = int(posts[i+1].creation_date.year)
#print 'New year ' + str(self.cur_year)
my_post = []
@ -114,7 +115,7 @@ class Archive(Index):
self.cur_year = int(posts[i].creation_date.year)
if len(my_post) != 0:
self.createArchives(src, output, dom, hooks, my_post)
self.createArchives(src, output, dom, self.hooks, my_post)
if not self.somethingWrote:
self.addReport('Nothing changed')

View File

@ -24,9 +24,10 @@ class Category(Index):
def generate(self, blog, src, output):
from dynastie.models import Post, Blog, Category
hooks = {'posts' : self.createPosts,
self.hooks = {'posts' : self.createPosts,
'navigation' : self.createNavigation,
'category' : self.createCategory}
'category' : self.createCategory,
'tags' : self.createTags}
if not os.path.exists(src + '/_category.html'):
self.addError('No _category.html found, exiting')
@ -73,7 +74,7 @@ class Category(Index):
while self.cur_page <= self.nb_pages:
#print 'Generate ' + filename
nodes = dom.getElementsByTagName("*")
nodes[0] = self.parse(src, hooks, posts, dom, nodes[0])
nodes[0] = self.parse(src, self.hooks, posts, dom, nodes[0])
self.writeIfNotTheSame(output + self.dirname + '/' + filename, nodes[0])
self.cur_page = self.cur_page + 1
filename = self.filename + str(self.cur_page) + '.html'

View File

@ -105,6 +105,9 @@ class Index(DynastieGenerator):
post_elem = self.createElement(dom, '', '<b>No posts yet</b>')
posts_elem.appendChild(post_elem)
# Parse inner HTML
self._parse(self.hooks, posts, dom, post_elem)
self.cur_post = self.cur_post + 1
if self.cur_post == len(posts):
break
@ -139,12 +142,48 @@ class Index(DynastieGenerator):
return recents_elem
def createTags(self, posts, dom, root, node):
from dynastie.models import Post
tags_elem = self.createElement(dom, 'tags')
create_link = (node.getAttribute('link') == '1')
if type(posts) == models.query.QuerySet:
if len(posts) > self.cur_post:
cur_post = posts[self.cur_post]
else:
cur_post = None
elif type(posts) == Post:
cur_post = posts
else:
cur_post = None
if not cur_post is None:
for tag in cur_post.tags.all():
if create_link:
tag_elem = self.createElement(dom, 'tag')
link_elem = self.createLinkElem(dom, '/tag/' + tag.name_slug, '#' + tag.name)
tag_elem.appendChild(link_elem)
else:
tag_elem = self.createElement(dom, 'tag', '#' + tag.name)
tags_elem.appendChild(tag_elem)
if len(cur_post.tags.all()) == 0:
root.removeChild(node)
return None
else:
root.replaceChild(tags_elem, node)
else:
root.removeChild(node)
return None
return tags_elem
def generate(self, blog, src, output):
from dynastie.models import Post, Blog
hooks = {'posts' : self.createPosts,
self.hooks = {'posts' : self.createPosts,
'navigation' : self.createNavigation,
'recents' : self.createRecents}
'recents' : self.createRecents,
'tags' : self.createTags}
if not os.path.exists(src + '/_index.html'):
self.addError('No _index.html found, exiting')
@ -180,7 +219,7 @@ class Index(DynastieGenerator):
while self.cur_page <= self.nb_pages:
#print 'Generate ' + filename
nodes = dom.getElementsByTagName("*")
nodes[0] = self.parse(src, hooks, posts, dom, nodes[0])
nodes[0] = self.parse(src, self.hooks, posts, dom, nodes[0])
self.writeIfNotTheSame(output + '/' + filename, nodes[0])
self.cur_page = self.cur_page + 1
filename = 'index' + str(self.cur_page) + '.html'

View File

@ -138,9 +138,10 @@ class Post(Index):
def _generate(self, blog, src, output, posts):
import xml
self.hooks = {'post' : self._createPost,
'meta' : self.createMetas,
'comments' : self.createComments,
'replace' : self.createReplace}
'meta' : self.createMetas,
'comments' : self.createComments,
'replace' : self.createReplace,
'tags' : self.createTags}
if not os.path.exists(src + '/_post.html'):
self.addError('No _post.html found, exiting')
@ -210,7 +211,8 @@ class Post(Index):
def preview(self, src, values):
from dynastie.models import Blog
self.hooks = {'post' : self.createPreview}
self.hooks = {'post' : self.createPreview,
'tags' : self.createTags}
if not os.path.exists(src + '/_post.html'):
self.addError('No _post.html found, exiting')

125
models.py
View File

@ -12,6 +12,15 @@ from django.db.models.signals import post_init, post_delete, 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)
@ -61,7 +70,10 @@ class Blog(models.Model):
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)
@ -161,29 +173,34 @@ class Category(models.Model):
description = models.TextField(max_length=255, blank=True)
blog = models.ForeignKey(Blog)
def slugify(self):
name = normalize('NFKD', self.name).encode('ascii', 'ignore').replace(' ', '-').lower()
#remove `other` characters
name = sub('[^a-zA-Z0-9_-]', '', name)
#nomalize dashes
name = sub('-+', '-', name)
self.name_slug = name
def save(self):
self.slugify()
self.name_slug = slugify(self.name)
super(Category, self).save()
def remove(self, blog):
blog.create_paths()
output = blog.output_path + '/category/' + self.name_slug
shutil.rmtree(output)
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.create_paths()
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)
@ -204,19 +221,12 @@ class Post(models.Model):
filename = filename + self.title_slug + '.html'
return filename
def slugify(self):
name = normalize('NFKD', self.title).encode('ascii', 'ignore').replace(' ', '-').lower()
#remove `other` characters
name = sub('[^a-zA-Z0-9_-]', '', name)
#nomalize dashes
name = sub('-+', '-', name)
self.title_slug = name
def save(self):
self.slugify()
self.title = self.title.strip()
self.title_slug = slugify(self.title)
super(Post, self).save()
def createPost(self, content):
def createPost(self, content, tags):
b = self.blog
b.create_paths()
output = b.src_path
@ -224,12 +234,75 @@ class Post(models.Model):
os.mkdir(output + '/_post')
filename = output + '/_post/' + str(self.pk)
if os.path.exists(filename):
os.unlink(filename)
f = open(filename, 'wb')
content = unicode(content)
f.write(content.encode('utf-8'))
f.close()
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(','):
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(','):
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
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
@ -267,7 +340,7 @@ class Comment(models.Model):
ip = models.GenericIPAddressField()
@receiver(post_init, sender=Blog)
def delete_blog_signal(sender, **kwargs):
def init_blog_signal(sender, **kwargs):
kwargs['instance'].create_paths()
@receiver(post_delete, sender=Blog)

View File

@ -6,6 +6,7 @@
<div class="post_sub_header">
<dyn:date/> | <div class="author_icon"> Écrit par <dyn:author/> </div>
</div>
<dyn:tags link="1"/>
</div>
<dyn:post_content/>
</dyn:posts>

View File

@ -6,6 +6,7 @@
<div class="post_sub_header">
<dyn:date/> | <div class="author_icon"> Écrit par <dyn:author/> </div>
</div>
<dyn:tags link="1"/>
</div>
<dyn:post_content/>
</dyn:posts>

View File

@ -5,6 +5,7 @@
<div class="post_sub_header">
<dyn:date/> | <div class="author_icon"> Écrit par <dyn:author/> </div>
</div>
<dyn:tags link="1"/>
</div>
<dyn:post_content/>
</dyn:posts>

View File

@ -6,6 +6,7 @@
<div class="post_sub_header">
<dyn:date/> | <div class="author_icon"> Écrit par <dyn:author/> </div>
</div>
<dyn:tags link="1"/>
</div>
<dyn:post_content> </dyn:post_content>
<dyn:comments>

View File

@ -0,0 +1,14 @@
<dyn:base file="_base.html" block="content" xmlns:dyn="http://indefero.soutade.fr/p/dynastie">
<div id="tag_name"><dyn:tag name="1"/></div>
<dyn:posts limit="5">
<div class="post_header">
<dyn:title/>
<div class="post_sub_header">
<dyn:date/> | <div class="author_icon"> Écrit par <dyn:author/> </div>
</div>
<dyn:tags link="1"/>
</div>
<dyn:post_content/>
</dyn:posts>
<dyn:navigation/>
</dyn:base>

View File

@ -257,7 +257,7 @@ div.recents
margin-left:20px;
}
#archive_year, #category_name
#archive_year, #category_name, #tag_name
{
font-size:50;
padding-bottom:20px;
@ -363,4 +363,14 @@ ul li
{
display:none;
padding:5px;
}
.tags, .tag
{
display:inline;
}
.tag
{
margin-left:10px;
}

View File

@ -25,12 +25,7 @@ tinyMCE.init({
});
// From http://planetozh.com/blog/2008/04/javascript-basename-and-dirname/
function basename(path) {
return path.replace(/\\/g,'/').replace( /.*\//, '' );
}
function previewPost()
function previewPost(blog_id)
{
var w = (screen.width * 80)/100;
var h = (screen.height * 80)/100;
@ -39,7 +34,6 @@ function previewPost()
var form = document.getElementById("previewForm");
var action = form.action;
var target = form.target;
var blog_id = basename(action);
form.action="/preview/" + blog_id;
form.target="PreviewPost";

View File

@ -12,6 +12,6 @@
<form id="previewForm" action="/post/add/{{ blog_id }}" method="post">{% csrf_token %}
{{ form.as_p }}
<textarea name="content" class="mceAdvanced"></textarea><br/><br/>
<input type="submit" name="add" value="Add" /><input type="button" name="preview" value="Preview" onClick="previewPost();"/><input type="submit" name="cancel" value="Cancel" />
<input type="submit" name="add" value="Add" /><input type="button" name="preview" value="Preview" onClick="previewPost({{ blog_id }});"/><input type="submit" name="cancel" value="Cancel" />
</form>
{% endblock %}

View File

@ -1,7 +1,6 @@
{% extends "templates/base.html" %}
{% block content %}
<table>
{% if categories|length == 0 %}
<b>Any category available</b><br/><br/>
{% else %}
@ -12,5 +11,4 @@
</table>
{% endif %}
<li><a href="/category/add">Add a category</a></li>
</table>
{% endblock %}

View File

@ -12,7 +12,7 @@
<form id="previewForm" action="/post/edit/{{ post_id }}" method="post">{% csrf_token %}
{{ form.as_p }}
<textarea name="content" class="mceAdvanced">{{ content }}</textarea>
<input type="submit" name="edit" value="Edit" /><input type="button" name="preview" value="Preview" onClick="previewPost();"/><input type="submit" name="cancel" value="Cancel" />
<input type="submit" name="edit" value="Edit" /><input type="button" name="preview" value="Preview" onClick="previewPost({{ blog_id }});"/><input type="submit" name="cancel" value="Cancel" />
</form>
<div class="comments">
{% for comment in comments %}

9
templates/edit_tag.html Normal file
View File

@ -0,0 +1,9 @@
{% extends "templates/base.html" %}
{% block content %}
<form action="/tag/edit/{{ tag.id }}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="edit" value="Edit" /> <input type="submit" name="cancel" value="Cancel" />
</form>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "templates/base.html" %}
{% block content %}
<a href="/category/{{ blog.id }}">Categories</a>
<a href="/blog/{{ blog.id }}">Home</a> <a href="/category/{{ blog.id }}">Categories</a> <a href="/tag/{{ blog.id }}">Tags</a>
{% if user.is_superuser %}
<form action="/blog/edit/{{ blog.id }}" method="post">
{% csrf_token %}

13
templates/tag.html Normal file
View File

@ -0,0 +1,13 @@
{% extends "templates/base.html" %}
{% block content %}
{% if tags|length == 0 %}
<b>Any tag available</b><br/><br/>
{% else %}
<table>
{% for tag in tags %}
<tr><td>{{ tag.id }}</td><td>{{ tag.name }}</td><td><a href="/tag/edit/{{ tag.id }}">Edit</a></td><td><a href="/tag/delete/{{ tag.id }}">Delete</a></td></tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "templates/base.html" %}
{% block content %}
<a href="/category/{{ blog.id }}">Categories</a>
<a href="/blog/{{ blog.id }}">Home</a> <a href="/category/{{ blog.id }}">Categories</a> <a href="/tag/{{ blog.id }}">Tags</a>
{% if user.is_superuser %}
<form action="/blog/edit/{{ blog.id }}" method="post">
{% csrf_token %}

View File

@ -31,6 +31,9 @@ urlpatterns = patterns('',
url(r'^comment/add/(\d+)/(\d+)$', 'dynastie.views.add_comment', name='add_comment'),
url(r'^comment/edit/(\d+)$', 'dynastie.views.edit_comment', name='edit_comment'),
url(r'^comment/delete/(\d+)$','dynastie.views.delete_comment',name='delete_comment'),
url(r'^tag/(\d+)$', 'dynastie.views.tag', name='tag'),
url(r'^tag/edit/(\d+)$', 'dynastie.views.edit_tag', name='edit_tag'),
url(r'^tag/delete/(\d+)$', 'dynastie.views.delete_tag', name='delete_tag'),
# url(r'^dynastie/', include('dynastie.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:

View File

@ -43,12 +43,12 @@ def have_I_right(request, blog_id=None, post_id=None, must_be_superuser=False):
if not blog_id is None:
if not request.user.is_superuser:
b = Blog.objects.filter(pk=blog_id).filter(writers=request.user.id)
b = Blog.objects.filter(pk=blog_id, writers=request.user.id)
if len(b) == 0:
raise Http404
b = b[0]
else:
b = Blog.objects.get(pk=post.blog.id)
b = Blog.objects.get(pk=blog_id)
if b is None:
raise Http404
@ -194,6 +194,7 @@ def edit_category(request, category_id):
return HttpResponseRedirect('/category' + str(b.id))
if 'edit' in request.POST:
name = category.name
name = name.strip()
form = CategoryForm(request.POST, instance=category) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
if request.POST['name'] != name:
@ -221,6 +222,57 @@ def delete_category(request, category_id):
return HttpResponseRedirect('/category/' + str(b.id))
@login_required
def tag(request, blog_id):
b = have_I_right(request, blog_id)
tags = Tag.objects.filter(blog_id=blog_id)
c = {'tags' : tags}
return render(request, 'templates/tag.html', c)
@login_required
def edit_tag(request, tag_id):
tag = Tag.objects.get(pk=tag_id)
if tag is None:
raise Http404
b = have_I_right(request, tag.blog.id)[0]
if request.method == 'POST': # If the form has been submitted...
if 'cancel' in request.POST:
return HttpResponseRedirect('/blog/' + str(b.id))
if 'edit' in request.POST:
name = tag.name
form = TagForm(request.POST, instance=tag) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
if request.POST['name'] != name:
tag.remove()
form.save()
return HttpResponseRedirect('/blog/' + str(b.id))
else:
form = TagForm(instance=tag) # An unbound form
c = {'tag' : tag, 'form' : form}
return render(request, 'templates/edit_tag.html', c)
@login_required
def delete_tag(request, tag_id):
tag = Tag.objects.get(pk=tag_id)
if tag is None:
raise Http404
b = have_I_right(request, tag.blog.id)[0]
tag.remove(b)
tag.delete()
return HttpResponseRedirect('/blog/' + str(b.id))
@login_required
def blog(request):
if request.user.is_superuser:
@ -311,7 +363,7 @@ def edit_blog(request, blog_id):
@login_required
def add_post(request, blog_id):
(b,) = have_I_right(request, blog_id)
(b,p) = have_I_right(request, blog_id)
if request.method == 'POST': # If the form has been submitted...
if 'add' in request.POST:
@ -321,7 +373,7 @@ def add_post(request, blog_id):
form = PostForm(request.POST, instance=post) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
form = form.save()
form.createPost(content)
form.createPost(content, request.POST['text_tags'])
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/blog/' + blog_id) # Redirect after POST
@ -344,13 +396,12 @@ def edit_post(request, post_id):
if request.method == 'POST': # If the form has been submitted...
if 'edit' in request.POST:
post.modification_date = datetime.now()
form = PostForm(request.POST, instance=post) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
if title != request.POST['title']:
post.remove()
form.save()
post.createPost(request.POST['content'])
post.createPost(request.POST['content'], request.POST['text_tags'])
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/blog/' + str(blog_id)) # Redirect after POST
@ -358,7 +409,7 @@ def edit_post(request, post_id):
if 'cancel' in request.POST:
return HttpResponseRedirect('/blog/' + str(blog_id)) # Redirect after POST
else:
form = PostForm(instance=post) # An unbound form
form = PostForm(instance=post, initial={'text_tags':', '.join((tag.name) for tag in post.tags.all())}) # An unbound form
b.create_paths()
filename = b.src_path + '/_post/' + str(post.pk)
@ -422,7 +473,7 @@ def preview(request, blog_id):
'content' : request.POST['content']
}
(b, ) = have_I_right(request, blog_id)
(b, p) = have_I_right(request, blog_id)
b.create_paths()
@ -482,7 +533,7 @@ def tinymcelist_add(request, blog_id):
month = now.month
try:
(b, ) = have_I_right(request, blog_id)
(b, p) = have_I_right(request, blog_id)
except Http404:
return HttpResponse('', content_type='application/x-javascript')
@ -545,8 +596,8 @@ def add_comment(request, post_id, parent_id):
the_comment = the_comment.replace('<', '&lt;')
the_comment = the_comment.replace('>', '&gt;')
comment = Comment(post=post, parent=parentComment, date=datetime.now(), author=request.POST['author'],\
email=request.POST['email'], the_comment=the_comment, ip=ip)
comment = Comment(post=post, parent=parentComment, date=datetime.now(), author=request.POST['author'].strip(),\
email=request.POST['email'].strip(), the_comment=the_comment, ip=ip)
comment.save()
engine = globals()['post']

View File

@ -18,7 +18,7 @@ import sys
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dynastie.settings")
dynastie_root = '/home/soutade/dynastie/'
dynastie_root = '/home/soutade/Projets_Perso/dynastie2/dynastie/'
if dynastie_root not in sys.path:
sys.path.append(dynastie_root)
dynastie_root += 'dynastie/'