Commit 2a9a15ad by Bridger Maxwell

Beginning to port wiki to multicourse. (Unstable)

parent ef3afda2
import difflib
import os
from django import forms
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import signals
from django.utils.translation import ugettext_lazy as _
from markdown import markdown
from wiki_settings import *
from util.cache import cache
class ShouldHaveExactlyOneRootSlug(Exception):
pass
class Article(models.Model):
"""Wiki article referring to Revision model for actual content.
'slug' and 'parent' field should be maintained centrally, since users
aren't allowed to change them, anyways.
"""
title = models.CharField(max_length=512, verbose_name=_('Article title'),
blank=False)
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
help_text=_('Letters, numbers, underscore and hyphen.'),
blank=True)
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
created_on = models.DateTimeField(auto_now_add = 1)
modified_on = models.DateTimeField(auto_now_add = 1)
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
help_text=_('Affects URL structure and possibly inherits permissions'),
null=True, blank=True)
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
blank=True, null=True,
help_text=_('Permission group'))
current_revision = models.OneToOneField('Revision', related_name='current_rev',
blank=True, null=True, editable=True)
related = models.ManyToManyField('self', verbose_name=_('Related articles'), symmetrical=True,
help_text=_('Sets a symmetrical relation other articles'),
blank=True, null=True)
def attachments(self):
return ArticleAttachment.objects.filter(article__exact = self)
@classmethod
def get_root(cls):
"""Return the root article, which should ALWAYS exist..
except the very first time the wiki is loaded, in which
case the user is prompted to create this article."""
try:
return Article.objects.filter(slug__exact = "")[0]
except:
raise ShouldHaveExactlyOneRootSlug()
def get_url(self):
"""Return the Wiki URL for an article"""
url = self.slug + "/"
if self.parent_id:
parent_url = cache.get("wiki_url-" + str(self.parent_id))
if parent_url is None:
parent_url = self.parent.get_url()
url = parent_url + url
cache.set("wiki_url-" + str(self.id), url, 60*60)
return url
def get_abs_url(self):
"""Return the absolute path for an article. This is necessary in cases
where the template system isn't used for generating URLs..."""
# TODO: Remove and create a reverse() lookup.
return WIKI_BASE + self.get_url()
@models.permalink
def get_absolute_url(self):
return ('wiki_view', [self.get_url()])
@classmethod
def get_url_reverse(cls, path, article, return_list=[]):
"""Lookup a URL and return the corresponding set of articles
in the path."""
if path == []:
return return_list + [article]
# Lookup next child in path
try:
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
return cls.get_url_reverse(path[1:], a, return_list+[article])
except Exception, e:
return None
def can_read(self, user):
""" Check read permissions and return True/False."""
if user.is_superuser:
return True
if self.permissions:
perms = self.permissions.can_read.all()
return perms.count() == 0 or (user in perms)
else:
return self.parent.can_read(user) if self.parent else True
def can_write(self, user):
""" Check write permissions and return True/False."""
if user.is_superuser:
return True
if self.permissions:
perms = self.permissions.can_write.all()
return perms.count() == 0 or (user in perms)
else:
return self.parent.can_write(user) if self.parent else True
def can_write_l(self, user):
"""Check write permissions and locked status"""
if user.is_superuser:
return True
return not self.locked and self.can_write(user)
def can_attach(self, user):
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
def __unicode__(self):
if self.slug == '' and not self.parent:
return unicode(_('Root article'))
else:
return self.get_url()
class Meta:
unique_together = (('slug', 'parent'),)
verbose_name = _('Article')
verbose_name_plural = _('Articles')
def get_attachment_filepath(instance, filename):
"""Store file, appending new extension for added security"""
dir_ = WIKI_ATTACHMENTS + instance.article.get_url()
dir_ = '/'.join(filter(lambda x: x!='', dir_.split('/')))
if not os.path.exists(WIKI_ATTACHMENTS_ROOT + dir_):
os.makedirs(WIKI_ATTACHMENTS_ROOT + dir_)
return dir_ + '/' + filename + '.upload'
class ArticleAttachment(models.Model):
article = models.ForeignKey(Article, verbose_name=_('Article'))
file = models.FileField(max_length=255, upload_to=get_attachment_filepath, verbose_name=_('Attachment'))
uploaded_by = models.ForeignKey(User, blank=True, verbose_name=_('Uploaded by'), null=True)
uploaded_on = models.DateTimeField(auto_now_add = True, verbose_name=_('Upload date'))
def download_url(self):
return reverse('wiki_view_attachment', args=(self.article.get_url(), self.filename()))
def filename(self):
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
def get_size(self):
try:
size = self.file.size
except OSError:
size = 0
return size
def filename(self):
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
def is_image(self):
fname = self.filename().split('.')
if len(fname) > 1 and fname[-1].lower() in WIKI_IMAGE_EXTENSIONS:
return True
return False
def get_thumb(self):
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE)
def get_thumb_small(self):
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE_SMALL)
def mk_thumbs(self):
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE, **{'force':True})
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE_SMALL, **{'force':True})
def mk_thumb(self, width, height, force=False):
"""Requires Python Imaging Library (PIL)"""
if not self.get_size():
return False
if not self.is_image():
return False
base_path = os.path.dirname(self.file.path)
orig_name = self.filename().split('.')
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
thumb_filepath = "%s%s%s" % (base_path, os.sep, thumb_filename)
if force or not os.path.exists(thumb_filepath):
try:
import Image
img = Image.open(self.file.path)
img.thumbnail((width,height), Image.ANTIALIAS)
img.save(thumb_filepath)
except IOError:
return False
return True
def get_thumb_impl(self, width, height):
"""Requires Python Imaging Library (PIL)"""
if not self.get_size():
return False
if not self.is_image():
return False
self.mk_thumb(width, height)
orig_name = self.filename().split('.')
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
thumb_url = settings.MEDIA_URL + WIKI_ATTACHMENTS + self.article.get_url() +'/' + thumb_filename
return thumb_url
def __unicode__(self):
return self.filename()
class Revision(models.Model):
article = models.ForeignKey(Article, verbose_name=_('Article'))
revision_text = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('Description of change'))
revision_user = models.ForeignKey(User, verbose_name=_('Modified by'),
blank=True, null=True, related_name='wiki_revision_user')
revision_date = models.DateTimeField(auto_now_add = True, verbose_name=_('Revision date'))
contents = models.TextField(verbose_name=_('Contents (Use MarkDown format)'))
contents_parsed = models.TextField(editable=False, blank=True, null=True)
counter = models.IntegerField(verbose_name=_('Revision#'), default=1, editable=False)
previous_revision = models.ForeignKey('self', blank=True, null=True, editable=False)
# Deleted has three values. 0 is normal, non-deleted. 1 is if it was deleted by a normal user. It should
# be a NEW revision, so that it appears in the history. 2 is a special flag that can be applied or removed
# from a normal revision. It means it has been admin-deleted, and can only been seen by an admin. It doesn't
# show up in the history.
deleted = models.IntegerField(verbose_name=_('Deleted group'), default=0)
def get_user(self):
return self.revision_user if self.revision_user else _('Anonymous')
# Called after the deleted fied has been changed (between 0 and 2). This bypasses the normal checks put in
# save that update the revision or reject the save if contents haven't changed
def adminSetDeleted(self, deleted):
self.deleted = deleted
super(Revision, self).save()
def save(self, **kwargs):
# Check if contents have changed... if not, silently ignore save
if self.article and self.article.current_revision:
if self.deleted == 0 and self.article.current_revision.contents == self.contents:
return
else:
import datetime
self.article.modified_on = datetime.datetime.now()
self.article.save()
# Increment counter according to previous revision
previous_revision = Revision.objects.filter(article=self.article).order_by('-counter')
if previous_revision.count() > 0:
if previous_revision.count() > previous_revision[0].counter:
self.counter = previous_revision.count() + 1
else:
self.counter = previous_revision[0].counter + 1
else:
self.counter = 1
if (self.article.current_revision and self.article.current_revision.deleted == 0):
self.previous_revision = self.article.current_revision
# Create pre-parsed contents - no need to parse on-the-fly
ext = WIKI_MARKDOWN_EXTENSIONS
ext += ["wikipath(base_url=%s)" % reverse('wiki_view', args=('/',))]
self.contents_parsed = markdown(self.contents,
extensions=ext,
safe_mode='escape',)
super(Revision, self).save(**kwargs)
def delete(self, **kwargs):
"""If a current revision is deleted, then regress to the previous
revision or insert a stub, if no other revisions are available"""
article = self.article
if article.current_revision == self:
prev_revision = Revision.objects.filter(article__exact = article,
pk__not = self.pk).order_by('-counter')
if prev_revision:
article.current_revision = prev_revision[0]
article.save()
else:
r = Revision(article=article,
revision_user = article.created_by)
r.contents = unicode(_('Auto-generated stub'))
r.revision_text= unicode(_('Auto-generated stub'))
r.save()
article.current_revision = r
article.save()
super(Revision, self).delete(**kwargs)
def get_diff(self):
if (self.deleted == 1):
yield "Article Deletion"
return
if self.previous_revision:
previous = self.previous_revision.contents.splitlines(1)
else:
previous = []
# Todo: difflib.HtmlDiff would look pretty for our history pages!
diff = difflib.unified_diff(previous, self.contents.splitlines(1))
# let's skip the preamble
diff.next(); diff.next(); diff.next()
for d in diff:
yield d
def __unicode__(self):
return "r%d" % self.counter
class Meta:
verbose_name = _('article revision')
verbose_name_plural = _('article revisions')
class Permission(models.Model):
permission_name = models.CharField(max_length = 255, verbose_name=_('Permission name'))
can_write = models.ManyToManyField(User, blank=True, null=True, related_name='write',
help_text=_('Select none to grant anonymous access.'))
can_read = models.ManyToManyField(User, blank=True, null=True, related_name='read',
help_text=_('Select none to grant anonymous access.'))
def __unicode__(self):
return self.permission_name
class Meta:
verbose_name = _('Article permission')
verbose_name_plural = _('Article permissions')
class RevisionForm(forms.ModelForm):
contents = forms.CharField(label=_('Contents'), widget=forms.Textarea(attrs={'rows':8, 'cols':50}))
class Meta:
model = Revision
fields = ['contents', 'revision_text']
class RevisionFormWithTitle(forms.ModelForm):
title = forms.CharField(label=_('Title'))
class Meta:
model = Revision
fields = ['title', 'contents', 'revision_text']
class CreateArticleForm(RevisionForm):
title = forms.CharField(label=_('Title'))
class Meta:
model = Revision
fields = ['title', 'contents',]
def set_revision(sender, *args, **kwargs):
"""Signal handler to ensure that a new revision is always chosen as the
current revision - automatically. It simplifies stuff greatly. Also
stores previous revision for diff-purposes"""
instance = kwargs['instance']
created = kwargs['created']
if created and instance.article:
instance.article.current_revision = instance
instance.article.save()
signals.post_save.connect(set_revision, Revision)
...@@ -16,9 +16,13 @@ from util.cache import cache ...@@ -16,9 +16,13 @@ from util.cache import cache
class ShouldHaveExactlyOneRootSlug(Exception): class ShouldHaveExactlyOneRootSlug(Exception):
pass pass
class Namespace(models.Model):
name = models.CharField(max_length=30, verbose_name=_('namespace'))
# TODO: We may want to add permissions, etc later
class Article(models.Model): class Article(models.Model):
"""Wiki article referring to Revision model for actual content. """Wiki article referring to Revision model for actual content.
'slug' and 'parent' field should be maintained centrally, since users 'slug' and 'title' field should be maintained centrally, since users
aren't allowed to change them, anyways. aren't allowed to change them, anyways.
""" """
...@@ -27,12 +31,10 @@ class Article(models.Model): ...@@ -27,12 +31,10 @@ class Article(models.Model):
slug = models.SlugField(max_length=100, verbose_name=_('slug'), slug = models.SlugField(max_length=100, verbose_name=_('slug'),
help_text=_('Letters, numbers, underscore and hyphen.'), help_text=_('Letters, numbers, underscore and hyphen.'),
blank=True) blank=True)
namespace = models.ForeignKey(Namespace, verbose_name=_('Namespace'))
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True) created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
created_on = models.DateTimeField(auto_now_add = 1) created_on = models.DateTimeField(auto_now_add = 1)
modified_on = models.DateTimeField(auto_now_add = 1) modified_on = models.DateTimeField(auto_now_add = 1)
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
help_text=_('Affects URL structure and possibly inherits permissions'),
null=True, blank=True)
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing')) locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'), permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
blank=True, null=True, blank=True, null=True,
...@@ -56,42 +58,26 @@ class Article(models.Model): ...@@ -56,42 +58,26 @@ class Article(models.Model):
except: except:
raise ShouldHaveExactlyOneRootSlug() raise ShouldHaveExactlyOneRootSlug()
def get_url(self):
"""Return the Wiki URL for an article"""
url = self.slug + "/"
if self.parent_id:
parent_url = cache.get("wiki_url-" + str(self.parent_id))
if parent_url is None:
parent_url = self.parent.get_url()
url = parent_url + url
cache.set("wiki_url-" + str(self.id), url, 60*60)
return url
def get_abs_url(self):
"""Return the absolute path for an article. This is necessary in cases
where the template system isn't used for generating URLs..."""
# TODO: Remove and create a reverse() lookup.
return WIKI_BASE + self.get_url()
@models.permalink @models.permalink
def get_absolute_url(self): def get_absolute_url(self):
return ('wiki_view', [self.get_url()]) return ('wiki_view', [self.slug])
@classmethod def get_full_slug(self):
def get_url_reverse(cls, path, article, return_list=[]): # TODO: Return namespace : slug
"""Lookup a URL and return the corresponding set of articles return self.slug
in the path."""
if path == []: # @classmethod
return return_list + [article] # def get_url_reverse(cls, path, article, return_list=[]):
# Lookup next child in path # """Lookup a URL and return the corresponding set of articles
try: # in the path."""
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0])) # if path == []:
return cls.get_url_reverse(path[1:], a, return_list+[article]) # return return_list + [article]
except Exception, e: # # Lookup next child in path
return None # try:
# a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
# return cls.get_url_reverse(path[1:], a, return_list+[article])
# except Exception, e:
# return None
def can_read(self, user): def can_read(self, user):
""" Check read permissions and return True/False.""" """ Check read permissions and return True/False."""
...@@ -101,7 +87,8 @@ class Article(models.Model): ...@@ -101,7 +87,8 @@ class Article(models.Model):
perms = self.permissions.can_read.all() perms = self.permissions.can_read.all()
return perms.count() == 0 or (user in perms) return perms.count() == 0 or (user in perms)
else: else:
return self.parent.can_read(user) if self.parent else True # TODO: We can inherit namespace permissions here
return True
def can_write(self, user): def can_write(self, user):
""" Check write permissions and return True/False.""" """ Check write permissions and return True/False."""
...@@ -111,7 +98,8 @@ class Article(models.Model): ...@@ -111,7 +98,8 @@ class Article(models.Model):
perms = self.permissions.can_write.all() perms = self.permissions.can_write.all()
return perms.count() == 0 or (user in perms) return perms.count() == 0 or (user in perms)
else: else:
return self.parent.can_write(user) if self.parent else True # TODO: We can inherit namespace permissions here
return True
def can_write_l(self, user): def can_write_l(self, user):
"""Check write permissions and locked status""" """Check write permissions and locked status"""
...@@ -123,13 +111,13 @@ class Article(models.Model): ...@@ -123,13 +111,13 @@ class Article(models.Model):
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous()) return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
def __unicode__(self): def __unicode__(self):
if self.slug == '' and not self.parent: if self.slug == '':
return unicode(_('Root article')) return unicode(_('Root article'))
else: else:
return self.get_url() return self.slug
class Meta: class Meta:
unique_together = (('slug', 'parent'),) unique_together = (('slug', 'namespace'),)
verbose_name = _('Article') verbose_name = _('Article')
verbose_name_plural = _('Articles') verbose_name_plural = _('Articles')
......
# -*- coding: utf-8 -*-
from django.conf import settings as settings
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _
from mitxmako.shortcuts import render_to_response
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings
def view(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
if perm_err:
return perm_err
d = {'wiki_article': article,
'wiki_article_revision':article.current_revision,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (article.current_revision.deleted == 0),
'wiki_title' : article.title + " - MITX 6.002x Wiki"
}
d.update(csrf(request))
return render_to_response('simplewiki_view.html', d)
def view_revision(request, revision_number, wiki_url, revision=None):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
try:
revision = Revision.objects.get(counter=int(revision_number), article=article)
except:
d = {'wiki_article': article,
'wiki_err_norevision': revision_number,}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision)
if perm_err:
return perm_err
d = {'wiki_article': article,
'wiki_article_revision':revision,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_current_revision_deleted' : not (revision.deleted == 0),
}
d.update(csrf(request))
return render_to_response('simplewiki_view.html', d)
def root_redirect(request):
try:
root = Article.get_root()
except:
err = not_found(request, '/')
return err
return HttpResponseRedirect(reverse('wiki_view', args=(root.get_url())))
def create(request, wiki_url):
url_path = get_url_path(wiki_url)
if url_path != [] and url_path[0].startswith('_'):
d = {'wiki_err_keyword': True,
'wiki_url': '/'.join(url_path) }
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
# Lookup path
try:
# Ensure that the path exists...
root = Article.get_root()
# Remove root slug if present in path
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path[:-1], root)
if not path:
d = {'wiki_err_noparent': True,
'wiki_url_parent': '/'.join(url_path[:-1]) }
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True)
if perm_err:
return perm_err
# Ensure doesn't already exist
article = Article.get_url_reverse(url_path, root)
if article:
return HttpResponseRedirect(reverse('wiki_view', args=(article[-1].get_url(),)))
# TODO: Somehow this doesnt work...
#except ShouldHaveExactlyOneRootSlug, (e):
except:
if Article.objects.filter(parent=None).count() > 0:
return HttpResponseRedirect(reverse('wiki_view', args=('/',)))
# Root not found...
path = []
url_path = [""]
if request.method == 'POST':
f = CreateArticleForm(request.POST)
if f.is_valid():
article = Article()
article.slug = url_path[-1]
if not request.user.is_anonymous():
article.created_by = request.user
article.title = f.cleaned_data.get('title')
if path != []:
article.parent = path[-1]
a = article.save()
new_revision = f.save(commit=False)
if not request.user.is_anonymous():
new_revision.revision_user = request.user
new_revision.article = article
new_revision.save()
import django.db as db
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
else:
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', url_path[-1]),
'contents':_('Headline\n===\n\n')})
d = {'wiki_form': f,
'wiki_write': True,
'create_article' : True,
}
d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d)
def edit(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
# Check write permissions
perm_err = check_permissions(request, article, check_write=True, check_locked=True, check_deleted=False)
if perm_err:
return perm_err
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
EditForm = RevisionFormWithTitle
else:
EditForm = RevisionForm
if request.method == 'POST':
f = EditForm(request.POST)
if f.is_valid():
new_revision = f.save(commit=False)
new_revision.article = article
if request.POST.__contains__('delete'):
if (article.current_revision.deleted == 1): #This article has already been deleted. Redirect
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
new_revision.contents = ""
new_revision.deleted = 1
elif not new_revision.get_diff():
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
if not request.user.is_anonymous():
new_revision.revision_user = request.user
new_revision.save()
if wiki_settings.WIKI_ALLOW_TITLE_EDIT:
new_revision.article.title = f.cleaned_data['title']
new_revision.article.save()
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
else:
startContents = article.current_revision.contents if (article.current_revision.deleted == 0) else 'Headline\n===\n\n'
f = EditForm({'contents': startContents, 'title': article.title})
d = {'wiki_form': f,
'wiki_write': True,
'wiki_article': article,
'wiki_title' : article.title,
'wiki_attachments_write': article.can_attach(request.user),
'create_article' : False,
}
d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d)
def history(request, wiki_url, page=1):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_read=True, check_deleted=False)
if perm_err:
print "returned error " , perm_err
return perm_err
page_size = 10
try:
p = int(page)
except ValueError:
p = 1
history = Revision.objects.filter(article__exact = article).order_by('-counter').select_related('previous_revision__counter', 'revision_user', 'wiki_article')
if request.method == 'POST':
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT and not request.user.is_authenticated():
return HttpResponseRedirect('/')
if request.POST.__contains__('revision'): #They selected a version, but they can be either deleting or changing the version
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
if perm_err:
return perm_err
redirectURL = reverse('wiki_view', args=(article.get_url(),))
try:
r = int(request.POST['revision'])
revision = Revision.objects.get(id=r)
if request.POST.__contains__('change'):
article.current_revision = revision
article.save()
elif request.POST.__contains__('view'):
redirectURL = reverse('wiki_view_revision', args=(revision.counter, article.get_url(),))
#The rese of these are admin functions
elif request.POST.__contains__('delete') and request.user.is_superuser:
if (revision.deleted == 0):
revision.adminSetDeleted(2)
elif request.POST.__contains__('restore') and request.user.is_superuser:
if (revision.deleted == 2):
revision.adminSetDeleted(0)
elif request.POST.__contains__('delete_all') and request.user.is_superuser:
Revision.objects.filter(article__exact = article, deleted = 0).update(deleted = 2)
elif request.POST.__contains__('lock_article'):
print "changing locked article " , article.locked
article.locked = not article.locked
print "changed locked article " , article.locked
article.save()
except:
pass
finally:
return HttpResponseRedirect(redirectURL)
#
#
# <input type="submit" name="delete" value="Delete revision"/>
# <input type="submit" name="restore" value="Restore revision"/>
# <input type="submit" name="delete_all" value="Delete all revisions">
# %else:
# <input type="submit" name="delete_article" value="Delete all revisions">
#
page_count = (history.count()+(page_size-1)) / page_size
if p > page_count:
p = 1
beginItem = (p-1) * page_size
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
d = {'wiki_page': p,
'wiki_next_page': next_page,
'wiki_prev_page': prev_page,
'wiki_write': article.can_write_l(request.user),
'wiki_attachments_write': article.can_attach(request.user),
'wiki_article': article,
'wiki_title': article.title,
'wiki_history': history[beginItem:beginItem+page_size],
'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request))
return render_to_response('simplewiki_history.html', d)
def revision_feed(request, page=1):
page_size = 10
try:
p = int(page)
except ValueError:
p = 1
history = Revision.objects.order_by('-revision_date').select_related('revision_user', 'article', 'previous_revision')
page_count = (history.count()+(page_size-1)) / page_size
if p > page_count:
p = 1
beginItem = (p-1) * page_size
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
d = {'wiki_page': p,
'wiki_next_page': next_page,
'wiki_prev_page': prev_page,
'wiki_history': history[beginItem:beginItem+page_size],
'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request))
return render_to_response('simplewiki_revision_feed.html', d)
def search_articles(request):
# blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort.
# Adding some context to results (eg where matches were) would also be nice.
# todo: maybe do some perm checking here
if request.method == 'POST':
querystring = request.POST['value'].strip()
else:
querystring = ""
results = Article.objects.all()
if request.user.is_superuser:
results = results.order_by('current_revision__deleted')
else:
results = results.filter(current_revision__deleted = 0)
if querystring:
for queryword in querystring.split():
# Basic negation is as fancy as we get right now
if queryword[0] == '-' and len(queryword) > 1:
results._search = lambda x: results.exclude(x)
queryword = queryword[1:]
else:
results._search = lambda x: results.filter(x)
results = results._search(Q(current_revision__contents__icontains = queryword) | \
Q(title__icontains = queryword))
results = results.select_related('current_revision__deleted')
results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_url().lower()) )
if len(results) == 1 and querystring:
return HttpResponseRedirect(reverse('wiki_view', args=(results[0].get_url(),)))
else:
d = {'wiki_search_results': results,
'wiki_search_query': querystring,}
d.update(csrf(request))
return render_to_response('simplewiki_searchresults.html', d)
def search_add_related(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_read=True)
if perm_err:
return perm_err
search_string = request.GET.get('query', None)
self_pk = request.GET.get('self', None)
if search_string:
results = []
related = Article.objects.filter(title__istartswith = search_string)
others = article.related.all()
if self_pk:
related = related.exclude(pk=self_pk)
if others:
related = related.exclude(related__in = others)
related = related.order_by('title')[:10]
for item in related:
results.append({'id': str(item.id),
'value': item.title,
'info': item.get_url()})
else:
results = []
json = simplejson.dumps({'results': results})
return HttpResponse(json, mimetype='application/json')
def add_related(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
if perm_err:
return perm_err
try:
related_id = request.POST['id']
rel = Article.objects.get(id=related_id)
has_already = article.related.filter(id=related_id).count()
if has_already == 0 and not rel == article:
article.related.add(rel)
article.save()
except:
pass
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def remove_related(request, wiki_url, related_id):
(article, path, err) = fetch_from_url(request, wiki_url)
if err:
return err
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
if perm_err:
return perm_err
try:
rel_id = int(related_id)
rel = Article.objects.get(id=rel_id)
article.related.remove(rel)
article.save()
except:
pass
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def random_article(request):
from random import randint
num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)]
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def encode_err(request, url):
d = {'wiki_err_encode': True}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
def not_found(request, wiki_url):
"""Generate a NOT FOUND message for some URL"""
d = {'wiki_err_notfound': True,
'wiki_url': wiki_url}
d.update(csrf(request))
return render_to_response('simplewiki_error.html', d)
def get_url_path(url):
"""Return a list of all actual elements of a url, safely ignoring
double-slashes (//) """
return filter(lambda x: x!='', url.split('/'))
def fetch_from_url(request, url):
"""Analyze URL, returning the article and the articles in its path
If something goes wrong, return an error HTTP response"""
err = None
article = None
path = None
url_path = get_url_path(url)
try:
root = Article.get_root()
except:
err = not_found(request, '/')
return (article, path, err)
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path, root)
if not path:
err = not_found(request, '/' + '/'.join(url_path))
else:
article = path[-1]
return (article, path, err)
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
read_err = check_read and not article.can_read(request.user)
write_err = check_write and not article.can_write(request.user)
locked_err = check_locked and article.locked
if revision == None:
revision = article.current_revision
deleted_err = check_deleted and not (revision.deleted == 0)
if (request.user.is_superuser):
deleted_err = False
locked_err = False
if read_err or write_err or locked_err or deleted_err:
d = {'wiki_article': article,
'wiki_err_noread': read_err,
'wiki_err_nowrite': write_err,
'wiki_err_locked': locked_err,
'wiki_err_deleted': deleted_err,}
d.update(csrf(request))
# TODO: Make this a little less jarring by just displaying an error
# on the current page? (no such redirect happens for an anon upload yet)
# benjaoming: I think this is the nicest way of displaying an error, but
# these errors shouldn't occur, but rather be prevented on the other pages.
return render_to_response('simplewiki_error.html', d)
else:
return None
####################
# LOGIN PROTECTION #
####################
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
view = login_required(view)
history = login_required(history)
search_articles = login_required(search_articles)
root_redirect = login_required(root_redirect)
revision_feed = login_required(revision_feed)
random_article = login_required(random_article)
search_add_related = login_required(search_add_related)
not_found = login_required(not_found)
view_revision = login_required(view_revision)
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT:
create = login_required(create)
edit = login_required(edit)
add_related = login_required(add_related)
remove_related = login_required(remove_related)
if wiki_settings.WIKI_CONTEXT_PREPROCESSORS:
settings.TEMPLATE_CONTEXT_PROCESSORS += wiki_settings.WIKI_CONTEXT_PREPROCESSORS
...@@ -14,16 +14,11 @@ from multicourse import multicourse_settings ...@@ -14,16 +14,11 @@ from multicourse import multicourse_settings
from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings import wiki_settings
def view(request, wiki_url): def view(request, course_id, slug, namespace=None):
(article, path, err) = fetch_from_url(request, wiki_url) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
if 'coursename' in request.session: coursename = request.session['coursename']
else: coursename = None
course_number = multicourse_settings.get_course_number(coursename)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True) perm_err = check_permissions(request, article, check_read=True, check_deleted=True)
if perm_err: if perm_err:
return perm_err return perm_err
...@@ -35,10 +30,10 @@ def view(request, wiki_url): ...@@ -35,10 +30,10 @@ def view(request, wiki_url):
'wiki_title' : article.title + " - MITX %s Wiki" % course_number 'wiki_title' : article.title + " - MITX %s Wiki" % course_number
} }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_view.html', d) return render_to_response('simplewiki/simplewiki_view.html', d)
def view_revision(request, revision_number, wiki_url, revision=None): def view_revision(request, course_id, slug, revision_number, namespace=None):
(article, path, err) = fetch_from_url(request, wiki_url) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
...@@ -48,7 +43,7 @@ def view_revision(request, revision_number, wiki_url, revision=None): ...@@ -48,7 +43,7 @@ def view_revision(request, revision_number, wiki_url, revision=None):
d = {'wiki_article': article, d = {'wiki_article': article,
'wiki_err_norevision': revision_number,} 'wiki_err_norevision': revision_number,}
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_error.html', d) return render_to_response('simplewiki/simplewiki_error.html', d)
perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision) perm_err = check_permissions(request, article, check_read=True, check_deleted=True, revision=revision)
...@@ -62,10 +57,11 @@ def view_revision(request, revision_number, wiki_url, revision=None): ...@@ -62,10 +57,11 @@ def view_revision(request, revision_number, wiki_url, revision=None):
'wiki_current_revision_deleted' : not (revision.deleted == 0), 'wiki_current_revision_deleted' : not (revision.deleted == 0),
} }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_view.html', d) return render_to_response('simplewiki/simplewiki_view.html', d)
def root_redirect(request): def root_redirect(request):
# TODO: What is going on here? Why don't we just return the redirect?
try: try:
root = Article.get_root() root = Article.get_root()
except: except:
...@@ -82,7 +78,7 @@ def create(request, wiki_url): ...@@ -82,7 +78,7 @@ def create(request, wiki_url):
d = {'wiki_err_keyword': True, d = {'wiki_err_keyword': True,
'wiki_url': '/'.join(url_path) } 'wiki_url': '/'.join(url_path) }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_error.html', d) return render_to_response('simplewiki/simplewiki_error.html', d)
# Lookup path # Lookup path
try: try:
...@@ -97,7 +93,7 @@ def create(request, wiki_url): ...@@ -97,7 +93,7 @@ def create(request, wiki_url):
d = {'wiki_err_noparent': True, d = {'wiki_err_noparent': True,
'wiki_url_parent': '/'.join(url_path[:-1]) } 'wiki_url_parent': '/'.join(url_path[:-1]) }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_error.html', d) return render_to_response('simplewiki/simplewiki_error.html', d)
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True) perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True, check_deleted=True)
if perm_err: if perm_err:
...@@ -144,10 +140,11 @@ def create(request, wiki_url): ...@@ -144,10 +140,11 @@ def create(request, wiki_url):
} }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d) return render_to_response('simplewiki/simplewiki_edit.html', d)
def edit(request, course_id, slug, namespace = None):
(article, err) = get_article(request, slug, namespace if namespace else course_id )
def edit(request, wiki_url):
(article, path, err) = fetch_from_url(request, wiki_url)
if err: if err:
return err return err
...@@ -195,10 +192,10 @@ def edit(request, wiki_url): ...@@ -195,10 +192,10 @@ def edit(request, wiki_url):
} }
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_edit.html', d) return render_to_response('simplewiki/simplewiki_edit.html', d)
def history(request, wiki_url, page=1): def history(request, course_id, slug, namespace = None, page=1):
(article, path, err) = fetch_from_url(request, wiki_url) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
...@@ -278,7 +275,7 @@ def history(request, wiki_url, page=1): ...@@ -278,7 +275,7 @@ def history(request, wiki_url, page=1):
'show_delete_revision' : request.user.is_superuser,} 'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_history.html', d) return render_to_response('simplewiki/simplewiki_history.html', d)
def revision_feed(request, page=1): def revision_feed(request, page=1):
...@@ -306,9 +303,9 @@ def revision_feed(request, page=1): ...@@ -306,9 +303,9 @@ def revision_feed(request, page=1):
'show_delete_revision' : request.user.is_superuser,} 'show_delete_revision' : request.user.is_superuser,}
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_revision_feed.html', d) return render_to_response('simplewiki/simplewiki_revision_feed.html', d)
def search_articles(request): def search_articles(request, course_id):
# blampe: We should check for the presence of other popular django search # blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort. # apps and use those if possible. Only fall back on this as a last resort.
# Adding some context to results (eg where matches were) would also be nice. # Adding some context to results (eg where matches were) would also be nice.
...@@ -321,7 +318,7 @@ def search_articles(request): ...@@ -321,7 +318,7 @@ def search_articles(request):
querystring = "" querystring = ""
results = Article.objects.all() results = Article.objects.filter(namespace__exact = course_id)
if request.user.is_superuser: if request.user.is_superuser:
results = results.order_by('current_revision__deleted') results = results.order_by('current_revision__deleted')
...@@ -351,11 +348,11 @@ def search_articles(request): ...@@ -351,11 +348,11 @@ def search_articles(request):
d = {'wiki_search_results': results, d = {'wiki_search_results': results,
'wiki_search_query': querystring,} 'wiki_search_query': querystring,}
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_searchresults.html', d) return render_to_response('simplewiki/simplewiki_searchresults.html', d)
def search_add_related(request, wiki_url): def search_add_related(request, course_id, slug, namespace=None):
(article, path, err) = fetch_from_url(request, wiki_url) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
...@@ -384,9 +381,8 @@ def search_add_related(request, wiki_url): ...@@ -384,9 +381,8 @@ def search_add_related(request, wiki_url):
json = simplejson.dumps({'results': results}) json = simplejson.dumps({'results': results})
return HttpResponse(json, mimetype='application/json') return HttpResponse(json, mimetype='application/json')
def add_related(request, wiki_url): def add_related(request, course_id, slug, namespace=None):
(article, err) = get_article(request, slug, namespace if namespace else course_id )
(article, path, err) = fetch_from_url(request, wiki_url)
if err: if err:
return err return err
...@@ -406,8 +402,9 @@ def add_related(request, wiki_url): ...@@ -406,8 +402,9 @@ def add_related(request, wiki_url):
finally: finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),))) return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def remove_related(request, wiki_url, related_id): def remove_related(request, course_id, slug, related_id, namespace=None,):
(article, path, err) = fetch_from_url(request, wiki_url) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
...@@ -425,7 +422,7 @@ def remove_related(request, wiki_url, related_id): ...@@ -425,7 +422,7 @@ def remove_related(request, wiki_url, related_id):
finally: finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),))) return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def random_article(request): def random_article(request, course_id):
from random import randint from random import randint
num_arts = Article.objects.count() num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)] article = Article.objects.all()[randint(0, num_arts-1)]
...@@ -434,46 +431,26 @@ def random_article(request): ...@@ -434,46 +431,26 @@ def random_article(request):
def encode_err(request, url): def encode_err(request, url):
d = {'wiki_err_encode': True} d = {'wiki_err_encode': True}
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_error.html', d) return render_to_response('simplewiki/simplewiki_error.html', d)
def not_found(request, wiki_url): def not_found(request, wiki_url):
"""Generate a NOT FOUND message for some URL""" """Generate a NOT FOUND message for some URL"""
d = {'wiki_err_notfound': True, d = {'wiki_err_notfound': True,
'wiki_url': wiki_url} 'wiki_url': wiki_url}
d.update(csrf(request)) d.update(csrf(request))
return render_to_response('simplewiki_error.html', d) return render_to_response('simplewiki/simplewiki_error.html', d)
def get_url_path(url):
"""Return a list of all actual elements of a url, safely ignoring
double-slashes (//) """
return filter(lambda x: x!='', url.split('/'))
def fetch_from_url(request, url):
"""Analyze URL, returning the article and the articles in its path
If something goes wrong, return an error HTTP response"""
def get_article(request, slug, namespace):
err = None err = None
article = None article = None
path = None
url_path = get_url_path(url)
try: try:
root = Article.get_root() article = Article.objects.get(slug__exact == slug )#, namespace__name__exact = namespace)
except: except:
err = not_found(request, '/') #TODO: We need to pass a url for creating the article here
return (article, path, err) err = not_found(request, slug)
if url_path and root.slug == url_path[0]:
url_path = url_path[1:]
path = Article.get_url_reverse(url_path, root)
if not path:
err = not_found(request, '/' + '/'.join(url_path))
else:
article = path[-1]
return (article, path, err)
return (article, err)
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None): def check_permissions(request, article, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
read_err = check_read and not article.can_read(request.user) read_err = check_read and not article.can_read(request.user)
...@@ -500,7 +477,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che ...@@ -500,7 +477,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che
# on the current page? (no such redirect happens for an anon upload yet) # on the current page? (no such redirect happens for an anon upload yet)
# benjaoming: I think this is the nicest way of displaying an error, but # benjaoming: I think this is the nicest way of displaying an error, but
# these errors shouldn't occur, but rather be prevented on the other pages. # these errors shouldn't occur, but rather be prevented on the other pages.
return render_to_response('simplewiki_error.html', d) return render_to_response('simplewiki/simplewiki_error.html', d)
else: else:
return None return None
...@@ -508,6 +485,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che ...@@ -508,6 +485,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che
# LOGIN PROTECTION # # LOGIN PROTECTION #
#################### ####################
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW: if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
view = login_required(view) view = login_required(view)
history = login_required(history) history = login_required(history)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment