Fixed the errors that were breaking tests from unused simplewiki files and a half-migrated certificates module.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import json
|
||||
import logging
|
||||
import settings
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
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)
|
||||
@@ -1,525 +0,0 @@
|
||||
# -*- 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
|
||||
@@ -1,153 +0,0 @@
|
||||
import os
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.servers.basehttp import FileWrapper
|
||||
from django.db.models.fields.files import FieldFile
|
||||
from django.http import HttpResponse, HttpResponseForbidden, Http404
|
||||
from django.template import loader, Context
|
||||
|
||||
from models import ArticleAttachment, get_attachment_filepath
|
||||
from views import check_permissions, fetch_from_url
|
||||
|
||||
from wiki_settings import (
|
||||
WIKI_ALLOW_ANON_ATTACHMENTS,
|
||||
WIKI_ALLOW_ATTACHMENTS,
|
||||
WIKI_ATTACHMENTS_MAX,
|
||||
WIKI_ATTACHMENTS_ROOT,
|
||||
WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS,
|
||||
WIKI_REQUIRE_LOGIN_VIEW,
|
||||
WIKI_REQUIRE_LOGIN_EDIT,
|
||||
)
|
||||
|
||||
|
||||
def add_attachment(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
|
||||
|
||||
if not WIKI_ALLOW_ATTACHMENTS or (not WIKI_ALLOW_ANON_ATTACHMENTS and request.user.is_anonymous()):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.FILES.__contains__('attachment'):
|
||||
attachment = ArticleAttachment()
|
||||
if not request.user.is_anonymous():
|
||||
attachment.uploaded_by = request.user
|
||||
attachment.article = article
|
||||
|
||||
file = request.FILES['attachment']
|
||||
file_rel_path = get_attachment_filepath(attachment, file.name)
|
||||
chunk_size = request.upload_handlers[0].chunk_size
|
||||
|
||||
filefield = FieldFile(attachment, attachment.file, file_rel_path)
|
||||
attachment.file = filefield
|
||||
|
||||
file_path = WIKI_ATTACHMENTS_ROOT + attachment.file.name
|
||||
|
||||
if not request.POST.__contains__('overwrite') and os.path.exists(file_path):
|
||||
c = Context({'overwrite_warning' : True,
|
||||
'wiki_article': article,
|
||||
'filename': file.name})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
if file.size > WIKI_ATTACHMENTS_MAX:
|
||||
c = Context({'too_big' : True,
|
||||
'max_size': WIKI_ATTACHMENTS_MAX,
|
||||
'wiki_article': article,
|
||||
'file': file})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
def get_extension(fname):
|
||||
return attachment.file.name.split('.')[-2]
|
||||
if WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS and not \
|
||||
get_extension(attachment.file.name) in WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS:
|
||||
c = Context({'extension_err' : True,
|
||||
'extensions': WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS,
|
||||
'wiki_article': article,
|
||||
'file': file})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
# Remove existing attachments
|
||||
# TODO: Move this until AFTER having removed file.
|
||||
# Current problem is that Django's FileField delete() method
|
||||
# automatically deletes files
|
||||
for a in article.attachments():
|
||||
if file_rel_path == a.file.name:
|
||||
a.delete()
|
||||
def receive_file():
|
||||
destination = open(file_path, 'wb+')
|
||||
size = file.size
|
||||
cnt = 0
|
||||
c = Context({'started' : True,})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
yield t.render(c)
|
||||
for chunk in file.chunks():
|
||||
cnt += 1
|
||||
destination.write(chunk)
|
||||
c = Context({'progress_width' : (cnt*chunk_size) / size,
|
||||
'wiki_article': article,})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
yield t.render(c)
|
||||
c = Context({'finished' : True,
|
||||
'wiki_article': article,})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
destination.close()
|
||||
attachment.save()
|
||||
yield t.render(c)
|
||||
|
||||
return HttpResponse(receive_file())
|
||||
|
||||
return HttpResponse('')
|
||||
|
||||
# Taken from http://www.djangosnippets.org/snippets/365/
|
||||
def send_file(request, filepath):
|
||||
"""
|
||||
Send a file through Django without loading the whole file into
|
||||
memory at once. The FileWrapper will turn the file object into an
|
||||
iterator for chunks of 8KB.
|
||||
"""
|
||||
filename = filepath
|
||||
wrapper = FileWrapper(file(filename))
|
||||
response = HttpResponse(wrapper, content_type='text/plain')
|
||||
response['Content-Length'] = os.path.getsize(filename)
|
||||
return response
|
||||
|
||||
def view_attachment(request, wiki_url, file_name):
|
||||
|
||||
(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
|
||||
|
||||
attachment = None
|
||||
for a in article.attachments():
|
||||
if get_attachment_filepath(a, file_name) == a.file.name:
|
||||
attachment = a
|
||||
|
||||
if attachment:
|
||||
filepath = WIKI_ATTACHMENTS_ROOT + attachment.file.name
|
||||
if os.path.exists(filepath):
|
||||
return send_file(request, filepath)
|
||||
|
||||
raise Http404()
|
||||
|
||||
####################
|
||||
# LOGIN PROTECTION #
|
||||
####################
|
||||
|
||||
if WIKI_REQUIRE_LOGIN_VIEW:
|
||||
view_attachment = login_required(view_attachment)
|
||||
|
||||
if WIKI_REQUIRE_LOGIN_EDIT or not WIKI_ALLOW_ANON_ATTACHMENTS:
|
||||
add_attachment = login_required(add_attachment)
|
||||
Reference in New Issue
Block a user