diff --git a/lms/djangoapps/simplewiki/migrations/__init__.py b/lms/djangoapps/simplewiki/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lms/djangoapps/simplewiki/models copy.py b/lms/djangoapps/simplewiki/models copy.py
new file mode 100644
index 0000000000..ade95ed491
--- /dev/null
+++ b/lms/djangoapps/simplewiki/models copy.py
@@ -0,0 +1,367 @@
+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)
diff --git a/lms/djangoapps/simplewiki/models.py b/lms/djangoapps/simplewiki/models.py
index ade95ed491..5fbcb1719c 100644
--- a/lms/djangoapps/simplewiki/models.py
+++ b/lms/djangoapps/simplewiki/models.py
@@ -16,9 +16,13 @@ from util.cache import cache
class ShouldHaveExactlyOneRootSlug(Exception):
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):
"""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.
"""
@@ -27,12 +31,10 @@ class Article(models.Model):
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
help_text=_('Letters, numbers, underscore and hyphen.'),
blank=True)
+ namespace = models.ForeignKey(Namespace, verbose_name=_('Namespace'))
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,
@@ -54,44 +56,28 @@ class Article(models.Model):
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()
+ raise ShouldHaveExactlyOneRootSlug()
@models.permalink
def get_absolute_url(self):
- return ('wiki_view', [self.get_url()])
+ return ('wiki_view', [self.slug])
+
+ def get_full_slug(self):
+ # TODO: Return namespace : slug
+ return self.slug
- @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
+ # @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."""
@@ -101,7 +87,8 @@ class Article(models.Model):
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
+ # TODO: We can inherit namespace permissions here
+ return True
def can_write(self, user):
""" Check write permissions and return True/False."""
@@ -111,7 +98,8 @@ class Article(models.Model):
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
+ # TODO: We can inherit namespace permissions here
+ return True
def can_write_l(self, user):
"""Check write permissions and locked status"""
@@ -123,13 +111,13 @@ class Article(models.Model):
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:
+ if self.slug == '':
return unicode(_('Root article'))
else:
- return self.get_url()
+ return self.slug
class Meta:
- unique_together = (('slug', 'parent'),)
+ unique_together = (('slug', 'namespace'),)
verbose_name = _('Article')
verbose_name_plural = _('Articles')
diff --git a/lms/djangoapps/simplewiki/views copy.py b/lms/djangoapps/simplewiki/views copy.py
new file mode 100644
index 0000000000..553e6e8d19
--- /dev/null
+++ b/lms/djangoapps/simplewiki/views copy.py
@@ -0,0 +1,525 @@
+# -*- 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)
+ #
+ #
+ #
+ #
+ #
+ # %else:
+ #
+ #
+
+ 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
diff --git a/lms/djangoapps/simplewiki/views.py b/lms/djangoapps/simplewiki/views.py
index 167ce27d95..f3cc06f946 100644
--- a/lms/djangoapps/simplewiki/views.py
+++ b/lms/djangoapps/simplewiki/views.py
@@ -14,16 +14,11 @@ from multicourse import multicourse_settings
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)
+def view(request, course_id, slug, namespace=None):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
if 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)
if perm_err:
return perm_err
@@ -35,10 +30,10 @@ def view(request, wiki_url):
'wiki_title' : article.title + " - MITX %s Wiki" % course_number
}
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):
- (article, path, err) = fetch_from_url(request, wiki_url)
+def view_revision(request, course_id, slug, revision_number, namespace=None):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
@@ -48,7 +43,7 @@ def view_revision(request, revision_number, wiki_url, revision=None):
d = {'wiki_article': article,
'wiki_err_norevision': revision_number,}
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)
@@ -62,10 +57,11 @@ def view_revision(request, revision_number, wiki_url, revision=None):
'wiki_current_revision_deleted' : not (revision.deleted == 0),
}
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):
+ # TODO: What is going on here? Why don't we just return the redirect?
try:
root = Article.get_root()
except:
@@ -82,7 +78,7 @@ def create(request, wiki_url):
d = {'wiki_err_keyword': True,
'wiki_url': '/'.join(url_path) }
d.update(csrf(request))
- return render_to_response('simplewiki_error.html', d)
+ return render_to_response('simplewiki/simplewiki_error.html', d)
# Lookup path
try:
@@ -97,7 +93,7 @@ def create(request, wiki_url):
d = {'wiki_err_noparent': True,
'wiki_url_parent': '/'.join(url_path[:-1]) }
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)
if perm_err:
@@ -144,10 +140,11 @@ def create(request, wiki_url):
}
d.update(csrf(request))
- return render_to_response('simplewiki_edit.html', d)
+ return render_to_response('simplewiki/simplewiki_edit.html', d)
-def edit(request, wiki_url):
- (article, path, err) = fetch_from_url(request, wiki_url)
+def edit(request, course_id, slug, namespace = None):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
+
if err:
return err
@@ -195,10 +192,10 @@ def edit(request, wiki_url):
}
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):
- (article, path, err) = fetch_from_url(request, wiki_url)
+def history(request, course_id, slug, namespace = None, page=1):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
@@ -278,7 +275,7 @@ def history(request, wiki_url, page=1):
'show_delete_revision' : request.user.is_superuser,}
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):
@@ -306,9 +303,9 @@ def revision_feed(request, page=1):
'show_delete_revision' : request.user.is_superuser,}
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
# 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.
@@ -321,7 +318,7 @@ def search_articles(request):
querystring = ""
- results = Article.objects.all()
+ results = Article.objects.filter(namespace__exact = course_id)
if request.user.is_superuser:
results = results.order_by('current_revision__deleted')
@@ -351,11 +348,11 @@ def search_articles(request):
d = {'wiki_search_results': results,
'wiki_search_query': querystring,}
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):
- (article, path, err) = fetch_from_url(request, wiki_url)
+def search_add_related(request, course_id, slug, namespace=None):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
@@ -384,9 +381,8 @@ def search_add_related(request, wiki_url):
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)
+def add_related(request, course_id, slug, namespace=None):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
@@ -406,8 +402,9 @@ def add_related(request, wiki_url):
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)
+def remove_related(request, course_id, slug, related_id, namespace=None,):
+ (article, err) = get_article(request, slug, namespace if namespace else course_id )
+
if err:
return err
@@ -425,7 +422,7 @@ def remove_related(request, wiki_url, related_id):
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
-def random_article(request):
+def random_article(request, course_id):
from random import randint
num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)]
@@ -434,46 +431,26 @@ def random_article(request):
def encode_err(request, url):
d = {'wiki_err_encode': True}
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):
"""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"""
-
+ return render_to_response('simplewiki/simplewiki_error.html', d)
+
+def get_article(request, slug, namespace):
err = None
article = None
- path = None
- url_path = get_url_path(url)
-
try:
- root = Article.get_root()
+ article = Article.objects.get(slug__exact == slug )#, namespace__name__exact = namespace)
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)
-
+ #TODO: We need to pass a url for creating the article here
+ err = not_found(request, slug)
+
+ return (article, 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)
@@ -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)
# 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)
+ return render_to_response('simplewiki/simplewiki_error.html', d)
else:
return None
@@ -508,6 +485,7 @@ def check_permissions(request, article, check_read=False, check_write=False, che
# LOGIN PROTECTION #
####################
+
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
view = login_required(view)
history = login_required(history)
diff --git a/lms/templates/simplewiki_base.html b/lms/templates/simplewiki/simplewiki_base.html
similarity index 100%
rename from lms/templates/simplewiki_base.html
rename to lms/templates/simplewiki/simplewiki_base.html
diff --git a/lms/templates/simplewiki_edit.html b/lms/templates/simplewiki/simplewiki_edit.html
similarity index 100%
rename from lms/templates/simplewiki_edit.html
rename to lms/templates/simplewiki/simplewiki_edit.html
diff --git a/lms/templates/simplewiki_error.html b/lms/templates/simplewiki/simplewiki_error.html
similarity index 100%
rename from lms/templates/simplewiki_error.html
rename to lms/templates/simplewiki/simplewiki_error.html
diff --git a/lms/templates/simplewiki_history.html b/lms/templates/simplewiki/simplewiki_history.html
similarity index 100%
rename from lms/templates/simplewiki_history.html
rename to lms/templates/simplewiki/simplewiki_history.html
diff --git a/lms/templates/simplewiki_instructions.html b/lms/templates/simplewiki/simplewiki_instructions.html
similarity index 100%
rename from lms/templates/simplewiki_instructions.html
rename to lms/templates/simplewiki/simplewiki_instructions.html
diff --git a/lms/templates/simplewiki_revision_feed.html b/lms/templates/simplewiki/simplewiki_revision_feed.html
similarity index 100%
rename from lms/templates/simplewiki_revision_feed.html
rename to lms/templates/simplewiki/simplewiki_revision_feed.html
diff --git a/lms/templates/simplewiki_searchresults.html b/lms/templates/simplewiki/simplewiki_searchresults.html
similarity index 100%
rename from lms/templates/simplewiki_searchresults.html
rename to lms/templates/simplewiki/simplewiki_searchresults.html
diff --git a/lms/templates/simplewiki_updateprogressbar.html b/lms/templates/simplewiki/simplewiki_updateprogressbar.html
similarity index 100%
rename from lms/templates/simplewiki_updateprogressbar.html
rename to lms/templates/simplewiki/simplewiki_updateprogressbar.html
diff --git a/lms/templates/simplewiki_view.html b/lms/templates/simplewiki/simplewiki_view.html
similarity index 100%
rename from lms/templates/simplewiki_view.html
rename to lms/templates/simplewiki/simplewiki_view.html