commit b12fc0b42fa64b658033328311beb418abcfd71a Author: James Turk Date: Tue Mar 30 10:43:30 2010 -0400 importing old work diff --git a/markupwiki/__init__.py b/markupwiki/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/markupwiki/forms.py b/markupwiki/forms.py new file mode 100644 index 0000000..836d3b2 --- /dev/null +++ b/markupwiki/forms.py @@ -0,0 +1,19 @@ +from django import forms +from markupwiki.models import Article, ArticleVersion, PUBLIC, PRIVATE + +class ArticleForm(forms.ModelForm): + class Meta: + model = ArticleVersion + fields = ['body', 'body_markup_type'] + +class StaffModerationForm(forms.ModelForm): + class Meta: + model = Article + fields = ['status'] + +class ModerationForm(forms.ModelForm): + class Meta: + model = Article + fields = ['status'] + status = forms.ChoiceField(choices=((PUBLIC, 'Public'), + (PRIVATE, 'Private'))) diff --git a/markupwiki/models.py b/markupwiki/models.py new file mode 100644 index 0000000..f33417e --- /dev/null +++ b/markupwiki/models.py @@ -0,0 +1,41 @@ +from django.db import models +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from markupfield.fields import MarkupField + +PUBLIC, PRIVATE, LOCKED, DELETED = range(4) +ARTICLE_STATUSES = ( + (PUBLIC, 'Public'), # public - no restrictions on viewing/editing + (PRIVATE, 'Private'), # private - only creator / admins can view + (LOCKED, 'Locked'), # locked - only admins can edit + (DELETED, 'Deleted'), # deleted - display deleted page +) + +class Article(models.Model): + title = models.CharField(max_length=50) + creator = models.ForeignKey(User, related_name='wiki_articles') + status = models.IntegerField(choices=ARTICLE_STATUSES, default=PUBLIC) + + def __unicode__(self): + return self.title + + def get_absolute_url(self): + return reverse('view_article', args=[self.title]) + +class ArticleVersion(models.Model): + article = models.ForeignKey(Article, related_name='versions') + author = models.ForeignKey(User, related_name='article_versions') + number = models.PositiveIntegerField() + timestamp = models.DateTimeField(auto_now_add=True) + removed = models.BooleanField(default=False) + body = MarkupField() + + class Meta: + ordering = ['timestamp'] + get_latest_by = 'timestamp' + + def __unicode__(self): + return unicode(self.article) + + def get_absolute_url(self): + return reverse('article_version', args=[self.article.title, self.number]) diff --git a/markupwiki/templates/article.html b/markupwiki/templates/article.html new file mode 100644 index 0000000..415eec8 --- /dev/null +++ b/markupwiki/templates/article.html @@ -0,0 +1,23 @@ +

{{article.title}}

+ +{% if article.locked %} edit article {% endif %} +| view history + +{% if form %} +
+ {{ form.as_ul }} + +
+{% endif %} + +
+ +
+ +
+{% if article.deleted %} + This article has been deleted. +{% else %} + {{version.body}} +{% endif %} +
diff --git a/markupwiki/templates/article_diff.html b/markupwiki/templates/article_diff.html new file mode 100644 index 0000000..2358cfe --- /dev/null +++ b/markupwiki/templates/article_diff.html @@ -0,0 +1,2 @@ + +{{ table|safe }} diff --git a/markupwiki/templates/deleted_article.html b/markupwiki/templates/deleted_article.html new file mode 100644 index 0000000..3824946 --- /dev/null +++ b/markupwiki/templates/deleted_article.html @@ -0,0 +1,13 @@ +

Article "{{article.title}}" has been deleted

+ +edit article + +{% if form %} +
+ {{ form.as_ul }} + +
+{% endif %} + + +

Once an article has been deleted only a moderator can create an article with the same name.

diff --git a/markupwiki/templates/edit_article.html b/markupwiki/templates/edit_article.html new file mode 100644 index 0000000..e85426d --- /dev/null +++ b/markupwiki/templates/edit_article.html @@ -0,0 +1,10 @@ +{% if article %} +

Editing Article "{{title}}"

+{% else %} +

Creating Article "{{title}}"

+{% endif %} + +
+ {{ form.as_ul }} + +
diff --git a/markupwiki/templates/history.html b/markupwiki/templates/history.html new file mode 100644 index 0000000..f7d3358 --- /dev/null +++ b/markupwiki/templates/history.html @@ -0,0 +1,32 @@ + +

History {{article.title}}

+ + + + + + + + + + + +{% for version in versions %} + + + + + + + +{% endfor %} +
VersionAuthorDateCompare FromCompare To
+ {% ifequal version.number 0 %} + Initial Version + {% else %} + Revision {{version.number}} + {% endifequal %} + {{version.author}}{{version.timestamp}}
+ + + diff --git a/markupwiki/templates/private_article.html b/markupwiki/templates/private_article.html new file mode 100644 index 0000000..5245552 --- /dev/null +++ b/markupwiki/templates/private_article.html @@ -0,0 +1,5 @@ +

This Article is Private

+ +
+ Only the author of this article may view it. +
diff --git a/markupwiki/urls.py b/markupwiki/urls.py new file mode 100644 index 0000000..28b85bc --- /dev/null +++ b/markupwiki/urls.py @@ -0,0 +1,13 @@ +from django.conf.urls.defaults import * + +WIKI_REGEX = r'^(?P[\w\s]+)' + +urlpatterns = patterns('markupwiki.views', + url(WIKI_REGEX + '/$', 'view_article', name='view_article'), + url(WIKI_REGEX + '/edit/$', 'edit_article', name='edit_article'), + url(WIKI_REGEX + '/update_status/$', 'article_status', + name='update_article_status'), + url(WIKI_REGEX + '/history/$', 'article_history', name='article_history'), + url(WIKI_REGEX + '/history/(?P<n>\d+)/$', 'view_article', name='article_version'), + url(WIKI_REGEX + '/diff/$', 'article_diff', name='article_diff'), +) diff --git a/markupwiki/views.py b/markupwiki/views.py new file mode 100644 index 0000000..3145676 --- /dev/null +++ b/markupwiki/views.py @@ -0,0 +1,125 @@ +from difflib import HtmlDiff +from django.shortcuts import get_object_or_404, render_to_response, redirect +from django.http import HttpResponseForbidden +from django.views.decorators.http import require_POST +from django.contrib.auth.decorators import login_required +from django.template import RequestContext +from django.utils.functional import wraps +from markupwiki.models import Article, PUBLIC, PRIVATE, DELETED, LOCKED +from markupwiki.forms import ArticleForm, StaffModerationForm, ModerationForm + +def title_check(view): + def new_view(request, title, *args, **kwargs): + newtitle = title.replace(' ', '_') + if newtitle != title: + return redirect(request.path.replace(title, newtitle)) + else: + return view(request, title, *args, **kwargs) + return wraps(view)(new_view) + +@title_check +def view_article(request, title, n=None): + try: + article = Article.objects.get(title=title) + except Article.DoesNotExist: + return redirect('edit_article', title) + + if n: + version = article.versions.get(number=n) + else: + version = article.versions.latest() + + context = {'article':article, 'version': version} + + if request.user.is_staff: + context['form'] = StaffModerationForm(instance=article) + elif request.user == article.creator: + context['form'] = ModerationForm(instance=article) + + if article.status == DELETED: + return render_to_response('deleted_article.html', context, + context_instance=RequestContext(request)) + elif (article.status == PRIVATE and request.user != article.creator + and not request.user.is_staff): + return render_to_response('private_article.html', context, + context_instance=RequestContext(request)) + + return render_to_response('article.html', context, + context_instance=RequestContext(request)) + +@title_check +@login_required +def edit_article(request, title): + try: + article = Article.objects.get(title=title) + except Article.DoesNotExist: + article = None + + if article.locked: + return render_to_response('article_locked.html', {'article': article}) + + if request.method == 'GET': + if article: + version = article.versions.latest() + form = ArticleForm(data={'body':version.body, + 'body_markup_type':version.body_markup_type}) + else: + form = ArticleForm() + elif request.method == 'POST': + form = ArticleForm(request.POST) + if form.is_valid(): + if not article: + article = Article.objects.create(title=title, + creator=request.user) + num = 0 + else: + num = article.versions.latest().number + 1 + version = form.save(False) + version.article = article + version.author = request.user + version.number = num + version.save() + return redirect(article) + + return render_to_response('edit_article.html', {'title':title, + 'article':article, + 'form': form}) + + +@require_POST +@title_check +def article_status(request, title): + article = get_object_or_404(Article, title=title) + status = int(request.POST['status']) + + if article.status in (LOCKED, DELETED) or status in (LOCKED, DELETED): + perm_test = lambda u,a: u.is_staff + elif article.status in (PUBLIC, PRIVATE) or status in (PUBLIC, PRIVATE): + perm_test = lambda u,a: u.is_staff or u == a.creator + + if perm_test(request.user, article): + article.status = status + article.save() + return redirect(article) + else: + return HttpResponseForbidden('access denied') + +@title_check +def article_history(request, title): + article = get_object_or_404(Article, title=title) + versions = article.versions.filter(removed=False) + return render_to_response('history.html', {'article':article, + 'versions':versions}) + +@title_check +def article_diff(request, title): + article = get_object_or_404(Article, title=title) + from_id = int(request.GET['from']) + to_id = int(request.GET['to']) + from_version = article.versions.get(number=from_id) + to_version = article.versions.get(number=to_id) + differ = HtmlDiff() + from_body = from_version.body.raw.split('\n') + to_body = to_version.body.raw.split('\n') + table = differ.make_table(from_body, to_body) + return render_to_response('article_diff.html', {'table':table}) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..06a8274 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django +-e git+git@github.com:jamesturk/django-markupfield.git#egg=django-markupfield