From 2a9a15ad66eed3e222c99d6d95e62ec2a805cf8f Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 28 Jun 2012 15:11:00 -0400 Subject: [PATCH 01/17] Beginning to port wiki to multicourse. (Unstable) --- .../simplewiki/migrations/__init__.py | 0 lms/djangoapps/simplewiki/models copy.py | 367 ++++++++++++ lms/djangoapps/simplewiki/models.py | 74 ++- lms/djangoapps/simplewiki/views copy.py | 525 ++++++++++++++++++ lms/djangoapps/simplewiki/views.py | 104 ++-- .../{ => simplewiki}/simplewiki_base.html | 0 .../{ => simplewiki}/simplewiki_edit.html | 0 .../{ => simplewiki}/simplewiki_error.html | 0 .../{ => simplewiki}/simplewiki_history.html | 0 .../simplewiki_instructions.html | 0 .../simplewiki_revision_feed.html | 0 .../simplewiki_searchresults.html | 0 .../simplewiki_updateprogressbar.html | 0 .../{ => simplewiki}/simplewiki_view.html | 0 14 files changed, 964 insertions(+), 106 deletions(-) create mode 100644 lms/djangoapps/simplewiki/migrations/__init__.py create mode 100644 lms/djangoapps/simplewiki/models copy.py create mode 100644 lms/djangoapps/simplewiki/views copy.py rename lms/templates/{ => simplewiki}/simplewiki_base.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_edit.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_error.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_history.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_instructions.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_revision_feed.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_searchresults.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_updateprogressbar.html (100%) rename lms/templates/{ => simplewiki}/simplewiki_view.html (100%) 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 From 8002644436c58c786978f96362683af177dbdb54 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Thu, 28 Jun 2012 15:58:11 -0400 Subject: [PATCH 02/17] Added migrations for simplewiki. It is a tricky one to apply. Don't attempt at home. --- .../simplewiki/migrations/0001_initial.py | 218 ++++++++++++++++++ ...article_parent__add_field_article_names.py | 163 +++++++++++++ .../0003_multicourse_data_migration.py | 133 +++++++++++ 3 files changed, 514 insertions(+) create mode 100644 lms/djangoapps/simplewiki/migrations/0001_initial.py create mode 100644 lms/djangoapps/simplewiki/migrations/0002_auto__add_namespace__del_field_article_parent__add_field_article_names.py create mode 100644 lms/djangoapps/simplewiki/migrations/0003_multicourse_data_migration.py diff --git a/lms/djangoapps/simplewiki/migrations/0001_initial.py b/lms/djangoapps/simplewiki/migrations/0001_initial.py new file mode 100644 index 0000000000..542c39248c --- /dev/null +++ b/lms/djangoapps/simplewiki/migrations/0001_initial.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Article' + db.create_table('simplewiki_article', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=512)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=100, blank=True)), + ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('created_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=1, blank=True)), + ('modified_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=1, blank=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'], null=True, blank=True)), + ('locked', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('permissions', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Permission'], null=True, blank=True)), + ('current_revision', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='current_rev', unique=True, null=True, to=orm['simplewiki.Revision'])), + )) + db.send_create_signal('simplewiki', ['Article']) + + # Adding unique constraint on 'Article', fields ['slug', 'parent'] + db.create_unique('simplewiki_article', ['slug', 'parent_id']) + + # Adding M2M table for field related on 'Article' + db.create_table('simplewiki_article_related', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('from_article', models.ForeignKey(orm['simplewiki.article'], null=False)), + ('to_article', models.ForeignKey(orm['simplewiki.article'], null=False)) + )) + db.create_unique('simplewiki_article_related', ['from_article_id', 'to_article_id']) + + # Adding model 'ArticleAttachment' + db.create_table('simplewiki_articleattachment', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'])), + ('file', self.gf('django.db.models.fields.files.FileField')(max_length=255)), + ('uploaded_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), + ('uploaded_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('simplewiki', ['ArticleAttachment']) + + # Adding model 'Revision' + db.create_table('simplewiki_revision', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('article', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'])), + ('revision_text', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('revision_user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='wiki_revision_user', null=True, to=orm['auth.User'])), + ('revision_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('contents', self.gf('django.db.models.fields.TextField')()), + ('contents_parsed', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('counter', self.gf('django.db.models.fields.IntegerField')(default=1)), + ('previous_revision', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Revision'], null=True, blank=True)), + ('deleted', self.gf('django.db.models.fields.IntegerField')(default=0)), + )) + db.send_create_signal('simplewiki', ['Revision']) + + # Adding model 'Permission' + db.create_table('simplewiki_permission', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('permission_name', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('simplewiki', ['Permission']) + + # Adding M2M table for field can_write on 'Permission' + db.create_table('simplewiki_permission_can_write', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('permission', models.ForeignKey(orm['simplewiki.permission'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('simplewiki_permission_can_write', ['permission_id', 'user_id']) + + # Adding M2M table for field can_read on 'Permission' + db.create_table('simplewiki_permission_can_read', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('permission', models.ForeignKey(orm['simplewiki.permission'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('simplewiki_permission_can_read', ['permission_id', 'user_id']) + + + def backwards(self, orm): + # Removing unique constraint on 'Article', fields ['slug', 'parent'] + db.delete_unique('simplewiki_article', ['slug', 'parent_id']) + + # Deleting model 'Article' + db.delete_table('simplewiki_article') + + # Removing M2M table for field related on 'Article' + db.delete_table('simplewiki_article_related') + + # Deleting model 'ArticleAttachment' + db.delete_table('simplewiki_articleattachment') + + # Deleting model 'Revision' + db.delete_table('simplewiki_revision') + + # Deleting model 'Permission' + db.delete_table('simplewiki_permission') + + # Removing M2M table for field can_write on 'Permission' + db.delete_table('simplewiki_permission_can_write') + + # Removing M2M table for field can_read on 'Permission' + db.delete_table('simplewiki_permission_can_read') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'simplewiki.article': { + 'Meta': {'unique_together': "(('slug', 'parent'),)", 'object_name': 'Article'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}), + 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']", 'null': 'True', 'blank': 'True'}), + 'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'simplewiki.articleattachment': { + 'Meta': {'object_name': 'ArticleAttachment'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'simplewiki.permission': { + 'Meta': {'object_name': 'Permission'}, + 'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'simplewiki.revision': { + 'Meta': {'object_name': 'Revision'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}), + 'contents': ('django.db.models.fields.TextField', [], {}), + 'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}), + 'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['simplewiki'] \ No newline at end of file diff --git a/lms/djangoapps/simplewiki/migrations/0002_auto__add_namespace__del_field_article_parent__add_field_article_names.py b/lms/djangoapps/simplewiki/migrations/0002_auto__add_namespace__del_field_article_parent__add_field_article_names.py new file mode 100644 index 0000000000..0ad2bb6cae --- /dev/null +++ b/lms/djangoapps/simplewiki/migrations/0002_auto__add_namespace__del_field_article_parent__add_field_article_names.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Removing unique constraint on 'Article', fields ['slug', 'parent'] + db.delete_unique('simplewiki_article', ['slug', 'parent_id']) + + # Adding model 'Namespace' + db.create_table('simplewiki_namespace', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=30)), + )) + db.send_create_signal('simplewiki', ['Namespace']) + + # Deleting field 'Article.parent' + db.delete_column('simplewiki_article', 'parent_id') + + # Adding field 'Article.namespace' + db.add_column('simplewiki_article', 'namespace', + self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['simplewiki.Namespace']), + keep_default=False) + + # Adding unique constraint on 'Article', fields ['namespace', 'slug'] + db.create_unique('simplewiki_article', ['namespace_id', 'slug']) + + + def backwards(self, orm): + # Removing unique constraint on 'Article', fields ['namespace', 'slug'] + db.delete_unique('simplewiki_article', ['namespace_id', 'slug']) + + # Deleting model 'Namespace' + db.delete_table('simplewiki_namespace') + + # Adding field 'Article.parent' + db.add_column('simplewiki_article', 'parent', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['simplewiki.Article'], null=True, blank=True), + keep_default=False) + + # Deleting field 'Article.namespace' + db.delete_column('simplewiki_article', 'namespace_id') + + # Adding unique constraint on 'Article', fields ['slug', 'parent'] + db.create_unique('simplewiki_article', ['slug', 'parent_id']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'simplewiki.article': { + 'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}), + 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}), + 'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}), + 'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'simplewiki.articleattachment': { + 'Meta': {'object_name': 'ArticleAttachment'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'simplewiki.namespace': { + 'Meta': {'object_name': 'Namespace'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}) + }, + 'simplewiki.permission': { + 'Meta': {'object_name': 'Permission'}, + 'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'simplewiki.revision': { + 'Meta': {'object_name': 'Revision'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}), + 'contents': ('django.db.models.fields.TextField', [], {}), + 'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}), + 'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['simplewiki'] \ No newline at end of file diff --git a/lms/djangoapps/simplewiki/migrations/0003_multicourse_data_migration.py b/lms/djangoapps/simplewiki/migrations/0003_multicourse_data_migration.py new file mode 100644 index 0000000000..5f12d50611 --- /dev/null +++ b/lms/djangoapps/simplewiki/migrations/0003_multicourse_data_migration.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + namespace6002x, created = orm.Namespace.objects.get_or_create(name="6002xS12") + if created: + namespace6002x.save() + + for article in orm.Article.objects.all(): + article.namespace = namespace6002x + article.save() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'simplewiki.article': { + 'Meta': {'unique_together': "(('slug', 'namespace'),)", 'object_name': 'Article'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}), + 'current_revision': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'current_rev'", 'unique': 'True', 'null': 'True', 'to': "orm['simplewiki.Revision']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': '1', 'blank': 'True'}), + 'namespace': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Namespace']"}), + 'permissions': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Permission']", 'null': 'True', 'blank': 'True'}), + 'related': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'related_rel_+'", 'null': 'True', 'to': "orm['simplewiki.Article']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'simplewiki.articleattachment': { + 'Meta': {'object_name': 'ArticleAttachment'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'simplewiki.namespace': { + 'Meta': {'object_name': 'Namespace'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}) + }, + 'simplewiki.permission': { + 'Meta': {'object_name': 'Permission'}, + 'can_read': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'can_write': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'write'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'permission_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'simplewiki.revision': { + 'Meta': {'object_name': 'Revision'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Article']"}), + 'contents': ('django.db.models.fields.TextField', [], {}), + 'contents_parsed': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'counter': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'deleted': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'previous_revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['simplewiki.Revision']", 'null': 'True', 'blank': 'True'}), + 'revision_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'revision_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'revision_user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'wiki_revision_user'", 'null': 'True', 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['simplewiki'] + symmetrical = True From b69deb698d676cc2faad58e8dba4dc6c2c7694b4 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 29 Jun 2012 12:05:55 -0400 Subject: [PATCH 03/17] Got the view page of the wiki rendering. Most of it is a styling mess. --- lms/djangoapps/courseware/courses.py | 1 + lms/djangoapps/simplewiki/admin.py | 2 +- lms/djangoapps/simplewiki/models.py | 10 +---- lms/djangoapps/simplewiki/urls.py | 20 --------- lms/djangoapps/simplewiki/views.py | 18 ++++++-- lms/templates/course_navigation.html | 2 +- lms/templates/simplewiki/simplewiki_base.html | 43 ++++++++----------- lms/urls.py | 20 ++++++++- 8 files changed, 55 insertions(+), 61 deletions(-) delete mode 100644 lms/djangoapps/simplewiki/urls.py diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 983e2f5eaf..a731a08151 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -14,6 +14,7 @@ _FIELDS = ['number', # 6.002x 'path', # /some/absolute/filepath/6.002x --> course.xml is in here. 'instructors', # ['Anant Agarwal'] 'institution', # "MIT" + 'wiki_namespace', 'grader', # a courseware.graders.CourseGrader object #'start', # These should be datetime fields diff --git a/lms/djangoapps/simplewiki/admin.py b/lms/djangoapps/simplewiki/admin.py index b53ace1a7a..8d1094bbc0 100644 --- a/lms/djangoapps/simplewiki/admin.py +++ b/lms/djangoapps/simplewiki/admin.py @@ -34,7 +34,7 @@ class ArticleAdminForm(forms.ModelForm): model = Article class ArticleAdmin(admin.ModelAdmin): - list_display = ('created_by', 'slug', 'modified_on', 'parent') + list_display = ('created_by', 'slug', 'modified_on', 'namespace') search_fields = ('slug',) prepopulated_fields = {'slug': ('title',) } inlines = [RevisionInline] diff --git a/lms/djangoapps/simplewiki/models.py b/lms/djangoapps/simplewiki/models.py index 5fbcb1719c..4c3f393d1d 100644 --- a/lms/djangoapps/simplewiki/models.py +++ b/lms/djangoapps/simplewiki/models.py @@ -56,15 +56,7 @@ class Article(models.Model): try: return Article.objects.filter(slug__exact = "")[0] except: - raise ShouldHaveExactlyOneRootSlug() - - @models.permalink - def get_absolute_url(self): - return ('wiki_view', [self.slug]) - - def get_full_slug(self): - # TODO: Return namespace : slug - return self.slug + raise ShouldHaveExactlyOneRootSlug() # @classmethod # def get_url_reverse(cls, path, article, return_list=[]): diff --git a/lms/djangoapps/simplewiki/urls.py b/lms/djangoapps/simplewiki/urls.py deleted file mode 100644 index a41ce3617b..0000000000 --- a/lms/djangoapps/simplewiki/urls.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.conf.urls.defaults import * - -urlpatterns = patterns('', - url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'), - url(r'^view(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view', name='wiki_view'), - url(r'^view_revision/([0-9]*)(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.view_revision', name='wiki_view_revision'), - url(r'^edit(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.edit', name='wiki_edit'), - url(r'^create(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.create', name='wiki_create'), - url(r'^history(/[a-zA-Z\d/_-]*)/([0-9]*)/?$', 'simplewiki.views.history', name='wiki_history'), - url(r'^search_related(/[a-zA-Z\d/_-]*)/?$', 'simplewiki.views.search_add_related', name='search_related'), - url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'), - url(r'^revision_feed/([0-9]*)/?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'), - url(r'^search/?$', 'simplewiki.views.search_articles', name='wiki_search_articles'), - url(r'^list/?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), #Just an alias for the search, but you usually don't submit a search term -# url(r'^/?([a-zA-Z\d/_-]*)/_related/add/$', 'simplewiki.views.add_related', name='add_related'), -# url(r'^/?([a-zA-Z\d/_-]*)/_related/remove/(\d+)$', 'simplewiki.views.remove_related', name='wiki_remove_relation'), -# url(r'^/?([a-zA-Z\d/_-]*)/_add_attachment/$', 'simplewiki.views_attachments.add_attachment', name='add_attachment'), -# url(r'^/?([a-zA-Z\d/_-]*)/_view_attachment/(.+)?$', 'simplewiki.views_attachments.view_attachment', name='wiki_view_attachment'), -# url(r'^(.*)$', 'simplewiki.views.encode_err', name='wiki_encode_err') -) diff --git a/lms/djangoapps/simplewiki/views.py b/lms/djangoapps/simplewiki/views.py index f3cc06f946..ea4d7e2542 100644 --- a/lms/djangoapps/simplewiki/views.py +++ b/lms/djangoapps/simplewiki/views.py @@ -4,7 +4,7 @@ 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.http import HttpResponse, HttpResponseRedirect, Http404 from django.utils import simplejson from django.utils.translation import ugettext_lazy as _ from mitxmako.shortcuts import render_to_response @@ -15,6 +15,14 @@ from models import Revision, Article, CreateArticleForm, RevisionFormWithTitle, import wiki_settings def view(request, course_id, slug, namespace=None): + try: + course = settings.COURSES_BY_ID[course_id] + if not namespace: + namespace = course.wiki_namespace + except KeyError: + raise Http404("Course not found") + + (article, err) = get_article(request, slug, namespace if namespace else course_id ) if err: return err @@ -27,9 +35,11 @@ def view(request, course_id, slug, namespace=None): '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 %s Wiki" % course_number + 'wiki_title' : article.title + " - edX %s Wiki" % course.title, + 'course' : course, } d.update(csrf(request)) + print d return render_to_response('simplewiki/simplewiki_view.html', d) def view_revision(request, course_id, slug, revision_number, namespace=None): @@ -445,8 +455,8 @@ def get_article(request, slug, namespace): article = None try: - article = Article.objects.get(slug__exact == slug )#, namespace__name__exact = namespace) - except: + article = Article.objects.get( slug__exact = slug )#, namespace__name__exact = namespace) + except Article.DoesNotExist: #TODO: We need to pass a url for creating the article here err = not_found(request, slug) diff --git a/lms/templates/course_navigation.html b/lms/templates/course_navigation.html index 24048dd2b9..8bda22148d 100644 --- a/lms/templates/course_navigation.html +++ b/lms/templates/course_navigation.html @@ -17,7 +17,7 @@ def url_class(url):
  • Textbook
  • Discussion
  • % endif -
  • Wiki
  • +
  • Wiki
  • % if user.is_authenticated():
  • Profile
  • % endif diff --git a/lms/templates/simplewiki/simplewiki_base.html b/lms/templates/simplewiki/simplewiki_base.html index 6a9be3bf08..81ea188088 100644 --- a/lms/templates/simplewiki/simplewiki_base.html +++ b/lms/templates/simplewiki/simplewiki_base.html @@ -1,15 +1,19 @@ ##This file is based on the template from the SimpleWiki source which carries the GPL license -<%inherit file="main.html"/> -<%namespace name='static' file='static_content.html'/> +<%inherit file="../main.html"/> +<%namespace name='static' file='../static_content.html'/> + +<%! + from django.core.urlresolvers import reverse +%> +## A convenience function that calls reverse with the kwargs course_id, namespace, and slug +<%def name="wiki_reverse(wiki_page)"> + ${reverse(wiki_page, kwargs={'course_id': course.id, 'namespace' : wiki_article.namespace.name, 'slug' : wiki_article.slug})} + <%block name="headextra"> - <%! - from django.core.urlresolvers import reverse - %> - + - + + - - + +