class SpaceEdit(NotificationMixin, WikiContextMixin, wiki_article.Edit, SpaceArticleMixin): template_name = "spaces_wiki/edit.html" notification_label = 'spaces_wiki_modify' @method_decorator(get_article(can_write=True, not_locked=True)) @method_decorator(permission_required_or_403('access_space')) def dispatch(self, request, article, *args, **kwargs): # hackish, see SpaceArticleView.dispatch kwargs['article_id'] = article.id kwargs['path'] = kwargs['urlpath'].path return super(SpaceEdit, self).dispatch(request, article, *args, **kwargs) def form_valid(self, form): # set title and link for notification mail self.notification_object_title = self.article self.notification_object_link = self.article.get_absolute_url() ret = super(SpaceEdit, self).form_valid(form) if isinstance(ret, HttpResponseRedirect): # super().form_valid successfully saved the article # create dashboard notification actstream_action.send(sender=self.request.user, verb=_("was modified"), target=self.request.SPACE, action_object=self.article.wikiarticle) return ret def get_context_data(self, **kwargs): context = super(SpaceEdit, self).get_context_data(**kwargs) # most probably the same bug as this: # https://github.com/django-wiki/django-wiki/issues/497 # if bug gets fixed in a proper way, the next line can be removed context['edit_form'] = self.get_form(EditForm) # set title and link for n12n mails context["object_title"] = self.article context["object_link"] = self.article.get_absolute_url() return context
class AttachmentChangeRevisionView(ArticleMixin, View): form_class = forms.AttachmentForm template_name = "wiki/plugins/attachments/replace.html" @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, attachment_id, revision_id, *args, **kwargs): if article.can_moderate(request.user): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) else: self.attachment = get_object_or_404( models.Attachment.objects.active(), id=attachment_id, articles=article) self.revision = get_object_or_404(models.AttachmentRevision, id=revision_id, attachment__articles=article) return super(AttachmentChangeRevisionView, self).dispatch(request, article, *args, **kwargs) def post(self, request, *args, **kwargs): self.attachment.current_revision = self.revision self.attachment.save() self.article.clear_cache() messages.success( self.request, _('Current revision changed for %s.') % self.attachment.original_filename) return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'attachments' return ArticleMixin.get_context_data(self, **kwargs)
class AttachmentAddView(ArticleMixin, View): @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): self.attachment = get_object_or_404( models.Attachment.objects.active().can_write(request.user), id=attachment_id) return super(AttachmentAddView, self).dispatch(request, article, *args, **kwargs) def post(self, request, *args, **kwargs): if self.attachment.articles.filter(id=self.article.id): self.attachment.articles.add(self.article) self.attachment.save() messages.success( self.request, _(u'Added a reference to "%(att)s" from "%(art)s".') % { 'att': self.attachment.original_filename, 'art': self.article.current_revision.title }) return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id)
class AttachmentSearchView(ArticleMixin, ListView): template_name = "wiki/plugins/attachments/search.html" allow_empty = True context_object_name = 'attachments' paginate_by = 10 @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, *args, **kwargs): return super(AttachmentSearchView, self).dispatch( request, article, *args, **kwargs ) def get_queryset(self): self.query = self.request.GET.get('query', None) if not self.query: qs = models.Attachment.objects.none() else: qs = models.Attachment.objects.active().can_read(self.request.user) qs = qs.filter( Q(original_filename__contains=self.query) | Q(current_revision__description__contains=self.query) | Q(article__current_revision__title__contains=self.query)) return qs def get_context_data(self, **kwargs): # Is this a bit of a hack? Use better inheritance? kwargs_article = ArticleMixin.get_context_data(self, **kwargs) kwargs_listview = ListView.get_context_data(self, **kwargs) kwargs['search_form'] = forms.SearchForm(self.request.GET) kwargs['query'] = self.query kwargs.update(kwargs_article) kwargs.update(kwargs_listview) kwargs['selected_tab'] = 'attachments' return kwargs
class AttachmentHistoryView(ArticleMixin, TemplateView): template_name = "wiki/plugins/attachments/history.html" @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): if article.can_moderate(request.user): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) else: self.attachment = get_object_or_404( models.Attachment.objects.active(), id=attachment_id, articles=article) return super().dispatch(request, article, *args, **kwargs) def get_context_data(self, **kwargs): kwargs["attachment"] = self.attachment kwargs["revisions"] = self.attachment.attachmentrevision_set.all( ).order_by("-revision_number") kwargs["selected_tab"] = "attachments" return super().get_context_data(**kwargs)
class History(ListView, ArticleMixin): template_name = "wiki/history.html" allow_empty = True context_object_name = 'revisions' paginate_by = 10 def get_queryset(self): return models.ArticleRevision.objects.filter( article=self.article).order_by('-created') def get_context_data(self, **kwargs): # Is this a bit of a hack? Use better inheritance? kwargs_article = ArticleMixin.get_context_data(self, **kwargs) kwargs_listview = ListView.get_context_data(self, **kwargs) kwargs.update(kwargs_article) kwargs.update(kwargs_listview) kwargs['selected_tab'] = 'history' return kwargs @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): return super(History, self).dispatch(request, article, *args, **kwargs)
class QueryUrlPath(View): # TODO: get_article does not actually support JSON responses @method_decorator(json_view) @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): max_num = kwargs.pop('max_num', 20) # TODO: Move this import when circularity issue is resolved # https://github.com/benjaoming/django-wiki/issues/23 query = request.GET.get('query', None) if query: matches = models.URLPath.objects.can_read( request.user).active().filter( article__current_revision__title__contains=query, article__current_revision__deleted=False, ) matches = matches.select_related_common() return [("[%s](wiki:%s)") % (m.article.current_revision.title, '/' + m.path.strip("/")) for m in matches[:max_num]] return []
class MetadataView(LoginRequiredMixin, ArticleMixin, TemplateView): template_name = "metadata.html" login_url = 'wiki:login' @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): self.request = request # TODO: find out why this is necessary return super(MetadataView, self).dispatch(request, article, *args, **kwargs) def get_context_data(self, **kwargs): try: models.SimpleMetadata.objects.get(article=Article.objects.get( urlpath=URLPath.objects.get(slug='supersenses'))) models.SimpleMetadata.objects.get(article=Article.objects.get( urlpath=URLPath.objects.get(slug='construals'))) kwargs['install'] = '' except (URLPath.DoesNotExist, Article.DoesNotExist, models.SimpleMetadata.DoesNotExist): kwargs['install'] = mark_safe( '<h2><a href="installmetadata">Install metadata</a></h2>') return super().get_context_data(**kwargs)
class EventListView(ListView): template_name = "trojsten/events/event_list.html" model = EventType context_object_name = "event_types" # Hodnoty sa pridaju v dispatch() article = None urlpath = None def get_queryset(self): return EventType.objects.current_site_only().prefetch_related( "event_set") @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): self.article = article self.urlpath = kwargs.get("urlpath", None) return super(EventListView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super(EventListView, self).get_context_data(**kwargs) context.update({"article": self.article, "urlpath": self.urlpath}) return context
class Preview(ArticleMixin, TemplateView): template_name="wiki/preview_inline.html" @method_decorator(get_article(can_read=True, deleted_contents=True)) def dispatch(self, request, article, *args, **kwargs): revision_id = request.GET.get('r', None) self.title = None self.content = None self.preview = False if revision_id: self.revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) else: self.revision = None return super(Preview, self).dispatch(request, article, *args, **kwargs) def post(self, request, *args, **kwargs): edit_form = forms.EditForm(request, self.article.current_revision, request.POST, preview=True) if edit_form.is_valid(): self.title = edit_form.cleaned_data['title'] self.content = edit_form.cleaned_data['content'] self.preview = True return super(Preview, self).get(request, *args, **kwargs) def get(self, request, *args, **kwargs): if self.revision and not self.title: self.title = self.revision.title if self.revision and not self.content: self.content = self.revision.content return super(Preview, self).get( request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs['title'] = self.title kwargs['revision'] = self.revision kwargs['content'] = self.content kwargs['preview'] = self.preview return ArticleMixin.get_context_data(self, **kwargs)
class QueryUrlPath(View): @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): max_num = kwargs.pop('max_num', 20) query = request.GET.get('query', None) matches = [] if query: matches = models.URLPath.objects.can_read( request.user).active().filter( article__current_revision__title__contains=query, article__current_revision__deleted=False, ) matches = matches.select_related_common() matches = [ "[{title:s}](wiki:{url:s})".format( title=m.article.current_revision.title, url='/' + m.path.strip("/") ) for m in matches[:max_num] ] return object_to_json_response(matches)
class RevisionAddView(ArticleMixin, FormView): template_name = "wiki/plugins/images/revision_add.html" form_class = forms.RevisionForm @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, *args, **kwargs): self.image = get_object_or_404(models.Image, article=article, id=kwargs.get('image_id', None)) if not self.image.can_write(request.user): return redirect(wiki_settings.LOGIN_URL) return ArticleMixin.dispatch(self, request, article, *args, **kwargs) def get_form_kwargs(self, **kwargs): kwargs = super(RevisionAddView, self).get_form_kwargs(**kwargs) kwargs['image'] = self.image kwargs['request'] = self.request return kwargs def get_context_data(self, **kwargs): kwargs = super(RevisionAddView, self).get_context_data(**kwargs) kwargs['image'] = self.image return kwargs def form_valid(self, form, **kwargs): form.save() messages.info( self.request, _('%(file)s has been saved.') % { 'file': self.image.current_revision.imagerevision.get_filename(), }) if self.urlpath: return redirect('wiki:edit', path=self.urlpath.path) return redirect('wiki:edit', article_id=self.article.id)
class AttachmentDownloadView(ArticleMixin, View): @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): if article.can_moderate(request.user): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) else: self.attachment = get_object_or_404( models.Attachment.objects.active(), id=attachment_id, articles=article) revision_id = kwargs.get("revision_id", None) if revision_id: self.revision = get_object_or_404(models.AttachmentRevision, id=revision_id, attachment__articles=article) else: self.revision = self.attachment.current_revision return super().dispatch(request, article, *args, **kwargs) def get(self, request, *args, **kwargs): if self.revision: if settings.USE_LOCAL_PATH: try: return send_file( request, self.revision.file.path, self.revision.created, self.attachment.original_filename, ) except OSError: pass else: return HttpResponseRedirect(self.revision.file.url) raise Http404
class Edit(ArticleMixin, FormView): """Edit an article and process sidebar plugins.""" form_class = forms.EditForm template_name = "wiki/edit.html" @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, *args, **kwargs): self.sidebar_plugins = plugin_registry.get_sidebar() self.sidebar = [] return super(Edit, self).dispatch(request, article, *args, **kwargs) def get_initial(self): initial = FormView.get_initial(self) for field_name in ['title', 'content']: session_key = 'unsaved_article_%s_%d' % (field_name, self.article.id) if session_key in list(self.request.session.keys()): content = self.request.session[session_key] initial[field_name] = content del self.request.session[session_key] return initial def get_form(self, form_class=None): """ Checks from querystring data that the edit form is actually being saved, otherwise removes the 'data' and 'files' kwargs from form initialisation. """ if form_class is None: form_class = self.get_form_class() kwargs = self.get_form_kwargs() if self.request.POST.get( 'save', '') != '1' and self.request.POST.get('preview') != '1': kwargs['data'] = None kwargs['files'] = None kwargs['no_clean'] = True return form_class(self.request, self.article.current_revision, **kwargs) def get_sidebar_form_classes(self): """Returns dictionary of form classes for the sidebar. If no form class is specified, puts None in dictionary. Keys in the dictionary are used to identify which form is being saved.""" form_classes = {} for cnt, plugin in enumerate(self.sidebar_plugins): form_classes['form%d' % cnt] = (plugin, plugin.sidebar.get( 'form_class', None)) return form_classes def get(self, request, *args, **kwargs): # Generate sidebar forms self.sidebar_forms = [] for form_id, (plugin, Form) in list(self.get_sidebar_form_classes().items()): if Form: form = Form(self.article, self.request.user) setattr(form, 'form_id', form_id) else: form = None self.sidebar.append((plugin, form)) return super(Edit, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): # Generate sidebar forms self.sidebar_forms = [] for form_id, (plugin, Form) in list(self.get_sidebar_form_classes().items()): if Form: if form_id == self.request.GET.get('f', None): form = Form(self.article, self.request, data=self.request.POST, files=self.request.FILES) if form.is_valid(): form.save() usermessage = form.get_usermessage() if usermessage: messages.success(self.request, usermessage) else: messages.success(self.request, _('Your changes were saved.')) title = form.cleaned_data['unsaved_article_title'] content = form.cleaned_data['unsaved_article_content'] if title != self.article.current_revision.title or content != self.article.current_revision.content: request.session['unsaved_article_title_%d' % self.article.id] = title request.session['unsaved_article_content_%d' % self.article.id] = content messages.warning( request, _('Please note that your article text has not yet been saved!' )) if self.urlpath: return redirect('wiki:edit', path=self.urlpath.path) return redirect('wiki:edit', article_id=self.article.id) else: form = Form(self.article, self.request) setattr(form, 'form_id', form_id) else: form = None self.sidebar.append((plugin, form)) return super(Edit, self).post(request, *args, **kwargs) def form_valid(self, form): """Create a new article revision when the edit form is valid (does not concern any sidebar forms!).""" revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.title = form.cleaned_data['title'] revision.content = form.cleaned_data['content'] revision.user_message = form.cleaned_data['summary'] revision.deleted = False revision.set_from_request(self.request) self.article.add_revision(revision) messages.success( self.request, _('A new revision of the article was successfully added.')) return self.get_success_url() def get_success_url(self): """Go to the article view page when the article has been saved""" if self.urlpath: return redirect("wiki:get", path=self.urlpath.path) return redirect('wiki:get', article_id=self.article.id) def get_context_data(self, **kwargs): # Needed for Django 1.9 because get_context_data is no longer called # with the form instance if 'form' not in kwargs: kwargs['form'] = self.get_form() kwargs['edit_form'] = kwargs['form'] kwargs['editor'] = editors.getEditor() kwargs['selected_tab'] = 'edit' kwargs['sidebar'] = self.sidebar return super(Edit, self).get_context_data(**kwargs)
class Delete(FormView, ArticleMixin): form_class = forms.DeleteForm template_name = "wiki/delete.html" @method_decorator( get_article(can_write=True, not_locked=True, can_delete=True)) def dispatch(self, request, article, *args, **kwargs): return self.dispatch1(request, article, *args, **kwargs) def dispatch1(self, request, article, *args, **kwargs): """Deleted view needs to access this method without a decorator, therefore it is separate.""" urlpath = kwargs.get('urlpath', None) # Where to go after deletion... self.next = "" self.cannot_delete_root = False if urlpath and urlpath.parent: self.next = reverse('wiki:get', kwargs={'path': urlpath.parent.path}) elif urlpath: # We are a urlpath with no parent. This is the root self.cannot_delete_root = True else: # We have no urlpath. Get it if a urlpath exists for art_obj in article.articleforobject_set.filter(is_mptt=True): if art_obj.content_object.parent: self.next = reverse( 'wiki:get', kwargs={ 'article_id': art_obj.content_object.parent.article.id }) else: self.cannot_delete_root = True return super(Delete, self).dispatch(request, article, *args, **kwargs) def get_initial(self): return {'revision': self.article.current_revision} def get_form(self, form_class=None): form = super(Delete, self).get_form(form_class=form_class) if self.article.can_moderate(self.request.user): form.fields['purge'].widget = forms.forms.CheckboxInput() return form def get_form_kwargs(self): kwargs = FormView.get_form_kwargs(self) kwargs['article'] = self.article kwargs['has_children'] = bool(self.children_slice) return kwargs def form_valid(self, form): cd = form.cleaned_data purge = cd['purge'] # If we are purging, only moderators can delete articles with children cannot_delete_children = False can_moderate = self.article.can_moderate(self.request.user) if purge and self.children_slice and not can_moderate: cannot_delete_children = True if self.cannot_delete_root or cannot_delete_children: messages.error( self.request, _('This article cannot be deleted because it has children or is a root article.' )) return redirect('wiki:get', article_id=self.article.id) if can_moderate and purge: # First, remove children if self.urlpath: self.urlpath.delete_subtree() self.article.delete() messages.success( self.request, _('This article together with all its contents are now completely gone! Thanks!' )) else: revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.set_from_request(self.request) revision.deleted = True self.article.add_revision(revision) messages.success( self.request, _('The article "%s" is now marked as deleted! Thanks for keeping the site free from unwanted material!' ) % revision.title) return self.get_success_url() def get_success_url(self): return redirect(self.next) def get_context_data(self, **kwargs): cannot_delete_children = False if self.children_slice and not self.article.can_moderate( self.request.user): cannot_delete_children = True # Needed since Django 1.9 because get_context_data is no longer called # with the form instance if 'form' not in kwargs: kwargs['form'] = self.get_form() kwargs['delete_form'] = kwargs.pop('form', None) kwargs['cannot_delete_root'] = self.cannot_delete_root kwargs['delete_children'] = self.children_slice[:20] kwargs['delete_children_more'] = len(self.children_slice) > 20 kwargs['cannot_delete_children'] = cannot_delete_children return super(Delete, self).get_context_data(**kwargs)
class AttachmentView(ArticleMixin, FormView): form_class = forms.AttachmentForm template_name = "wiki/plugins/attachments/index.html" @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): if article.can_moderate(request.user): self.attachments = models.Attachment.objects.filter( articles=article, current_revision__deleted=False ).exclude( current_revision__file=None ).order_by('original_filename') self.form_class = forms.AttachmentArchiveForm else: self.attachments = models.Attachment.objects.active().filter( articles=article) # Fixing some weird transaction issue caused by adding commit_manually # to form_valid return super( AttachmentView, self).dispatch( request, article, *args, **kwargs) def form_valid(self, form): if (self.request.user.is_anonymous() and not settings.ANONYMOUS or not self.article.can_write(self.request.user) or self.article.current_revision.locked): return response_forbidden(self.request, self.article, self.urlpath) attachment_revision = form.save() if isinstance(attachment_revision, list): messages.success( self.request, _('Successfully added: %s') % (", ".join( [ar.get_filename() for ar in attachment_revision]))) else: messages.success( self.request, _('%s was successfully added.') % attachment_revision.get_filename()) self.article.clear_cache() return redirect( "wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) def get_form_kwargs(self): kwargs = super(AttachmentView, self).get_form_kwargs() kwargs['article'] = self.article kwargs['request'] = self.request return kwargs def get_context_data(self, **kwargs): kwargs['attachments'] = self.attachments kwargs['deleted_attachments'] = models.Attachment.objects.filter( articles=self.article, current_revision__deleted=True) kwargs['search_form'] = forms.SearchForm() kwargs['selected_tab'] = 'attachments' kwargs['anonymous_disallowed'] = self.request.user.is_anonymous( ) and not settings.ANONYMOUS return super(AttachmentView, self).get_context_data(**kwargs)
class AttachmentReplaceView(ArticleMixin, FormView): form_class = forms.AttachmentForm template_name = "wiki/plugins/attachments/replace.html" @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): if request.user.is_anonymous() and not settings.ANONYMOUS: return response_forbidden( request, article, kwargs.get( 'urlpath', None)) if article.can_moderate(request.user): self.attachment = get_object_or_404( models.Attachment, id=attachment_id, articles=article) self.can_moderate = True else: self.attachment = get_object_or_404( models.Attachment.objects.active(), id=attachment_id, articles=article) self.can_moderate = False return super( AttachmentReplaceView, self).dispatch( request, article, *args, **kwargs) def get_form_class(self): if self.can_moderate: return forms.AttachmentReplaceForm else: return forms.AttachmentForm def form_valid(self, form): try: attachment_revision = form.save(commit=True) attachment_revision.set_from_request(self.request) attachment_revision.previous_revision = self.attachment.current_revision attachment_revision.save() self.attachment.current_revision = attachment_revision self.attachment.save() messages.success( self.request, _('%s uploaded and replaces old attachment.') % attachment_revision.get_filename()) self.article.clear_cache() except models.IllegalFileExtension as e: messages.error( self.request, _('Your file could not be saved: %s') % e) return redirect( "wiki:attachments_replace", attachment_id=self.attachment.id, path=self.urlpath.path, article_id=self.article.id) if self.can_moderate: if form.cleaned_data['replace']: # form has no cleaned_data field unless self.can_moderate is True try: most_recent_revision = self.attachment.attachmentrevision_set.exclude( id=attachment_revision.id, created__lte=attachment_revision.created).latest() most_recent_revision.delete() except ObjectDoesNotExist: msg = "{attachment} does not contain any revisions.".format(\ attachment=str(self.attachment.original_filename)) messages.error(self.request, msg) return redirect( "wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id) def get_form(self, form_class=None): form = super(AttachmentReplaceView, self).get_form(form_class=form_class) form.fields['file'].help_text = _( 'Your new file will automatically be renamed to match the file already present. Files with different extensions are not allowed.') return form def get_form_kwargs(self): kwargs = super(AttachmentReplaceView, self).get_form_kwargs() kwargs['article'] = self.article kwargs['request'] = self.request kwargs['attachment'] = self.attachment return kwargs def get_initial(self, **kwargs): return {'description': self.attachment.current_revision.description} def get_context_data(self, **kwargs): kwargs['attachment'] = self.attachment kwargs['selected_tab'] = 'attachments' return super(AttachmentReplaceView, self).get_context_data(**kwargs)
class MergeView(View): preview = False template_name = "wiki/preview_inline.html" template_error_name = "wiki/error.html" urlpath = None @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, revision_id, *args, **kwargs): return super().dispatch(request, article, revision_id, *args, **kwargs) def get(self, request, article, revision_id, *args, **kwargs): revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) current_text = article.current_revision.content if article.current_revision else "" new_text = revision.content content = simple_merge(current_text, new_text) # Save new revision if not self.preview: old_revision = article.current_revision if revision.deleted: c = { 'error_msg': _('You cannot merge with a deleted revision'), 'article': article, 'urlpath': self.urlpath } return render(request, self.template_error_name, context=c) new_revision = models.ArticleRevision() new_revision.inherit_predecessor(article) new_revision.deleted = False new_revision.locked = False new_revision.title = article.current_revision.title new_revision.content = content new_revision.automatic_log = ( _('Merge between revision #%(r1)d and revision #%(r2)d') % { 'r1': revision.revision_number, 'r2': old_revision.revision_number }) article.add_revision(new_revision, save=True) old_revision.simpleplugin_set.all().update( article_revision=new_revision) revision.simpleplugin_set.all().update( article_revision=new_revision) messages.success( request, _('A new revision was created: Merge between revision #%(r1)d and revision #%(r2)d' ) % { 'r1': revision.revision_number, 'r2': old_revision.revision_number }) if self.urlpath: return redirect('wiki:edit', path=self.urlpath.path) else: return redirect('wiki:edit', article_id=article.id) c = { 'article': article, 'title': article.current_revision.title, 'revision': None, 'merge1': revision, 'merge2': article.current_revision, 'merge': True, 'content': content } return render(request, self.template_name, c)
class Deleted(Delete): """Tell a user that an article has been deleted. If user has permissions, let user restore and possibly purge the deleted article and children.""" template_name = "wiki/deleted.html" form_class = forms.DeleteForm @method_decorator(get_article(can_read=True, deleted_contents=True)) def dispatch(self, request, article, *args, **kwargs): self.urlpath = kwargs.get("urlpath", None) self.article = article if self.urlpath: deleted_ancestor = self.urlpath.first_deleted_ancestor() if deleted_ancestor is None: # No one is deleted! return redirect("wiki:get", path=self.urlpath.path) elif deleted_ancestor != self.urlpath: # An ancestor was deleted, so redirect to that deleted page return redirect("wiki:deleted", path=deleted_ancestor.path) else: if not article.current_revision.deleted: return redirect("wiki:get", article_id=article.id) # Restore if request.GET.get("restore", False): can_restore = not article.current_revision.locked and article.can_delete( request.user) can_restore = can_restore or article.can_moderate(request.user) if can_restore: revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.set_from_request(request) revision.deleted = False revision.automatic_log = _("Restoring article") self.article.add_revision(revision) messages.success( request, _('The article "%s" and its children are now restored.') % revision.title, ) if self.urlpath: return redirect("wiki:get", path=self.urlpath.path) else: return redirect("wiki:get", article_id=article.id) return super().dispatch1(request, article, *args, **kwargs) def get_initial(self): return { "revision": self.article.current_revision, "purge": True, } def get_context_data(self, **kwargs): kwargs["purge_form"] = self.get_form() kwargs["form"] = kwargs["purge_form"] return super().get_context_data(**kwargs)
class Create(FormView, ArticleMixin): form_class = forms.CreateForm template_name = "wiki/create.html" @method_decorator(get_article(can_write=True, can_create=True)) def dispatch(self, request, article, *args, **kwargs): return super().dispatch(request, article, *args, **kwargs) def get_form(self, form_class=None): """ Returns an instance of the form to be used in this view. """ if form_class is None: form_class = self.get_form_class() kwargs = self.get_form_kwargs() initial = kwargs.get("initial", {}) initial["slug"] = self.request.GET.get("slug", None) kwargs["initial"] = initial form = form_class(self.request, self.urlpath, **kwargs) form.fields["slug"].widget = forms.TextInputPrepend( prepend="/" + self.urlpath.path, attrs={ # Make patterns force lowercase if we are case insensitive to bless the user with a # bit of strictness, anyways "pattern": "[a-z0-9_-]+" if not settings.URL_CASE_SENSITIVE else "[a-zA-Z0-9_-]+", "title": "Lowercase letters, numbers, hyphens and underscores" if not settings.URL_CASE_SENSITIVE else "Letters, numbers, hyphens and underscores", }, ) return form def form_valid(self, form): try: self.newpath = models.URLPath._create_urlpath_from_request( self.request, self.article, self.urlpath, form.cleaned_data["slug"], form.cleaned_data["title"], form.cleaned_data["content"], form.cleaned_data["summary"], ) messages.success( self.request, _("New article '%s' created.") % self.newpath.article.current_revision.title, ) # TODO: Handle individual exceptions better and give good feedback. except Exception as e: log.exception("Exception creating article.") if self.request.user.is_superuser: messages.error( self.request, _("There was an error creating this article: %s") % str(e), ) else: messages.error(self.request, _("There was an error creating this article.")) return redirect("wiki:get", "") return self.get_success_url() def get_success_url(self): return redirect("wiki:get", self.newpath.path) def get_context_data(self, **kwargs): c = ArticleMixin.get_context_data(self, **kwargs) c["form"] = self.get_form() c["parent_urlpath"] = self.urlpath c["parent_article"] = self.article c["create_form"] = c.pop("form", None) c["editor"] = editors.getEditor() return c
class AttachmentReplaceView(ArticleMixin, FormView): form_class = forms.AttachmentForm template_name = "wiki/plugins/attachments/replace.html" @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): if request.user.is_anonymous() and not settings.ANONYMOUS: return response_forbidden(request, article, kwargs.get('urlpath', None)) if article.can_moderate(request.user): self.attachment = get_object_or_404(models.Attachment, id=attachment_id, articles=article) self.can_moderate = True else: self.attachment = get_object_or_404( models.Attachment.objects.active(), id=attachment_id, articles=article) self.can_moderate = False return super(AttachmentReplaceView, self).dispatch(request, article, *args, **kwargs) def get_form_class(self): if self.can_moderate: return forms.AttachmentReplaceForm else: return forms.AttachmentForm def form_valid(self, form): try: attachment_revision = form.save(commit=True) attachment_revision.set_from_request(self.request) attachment_revision.previous_revision = self.attachment.current_revision attachment_revision.save() self.attachment.current_revision = attachment_revision self.attachment.save() messages.success( self.request, _(u'%s uploaded and replaces old attachment.') % attachment_revision.get_filename()) except models.IllegalFileExtension, e: messages.error(self.request, _(u'Your file could not be saved: %s') % e) return redirect("wiki:attachments_replace", attachment_id=self.attachment.id, path=self.urlpath.path, article_id=self.article.id) if self.can_moderate: older_revisions = self.attachment.attachmentrevision_set.exclude( id=attachment_revision.id, created__lte=attachment_revision.created, ) # Because of signalling, the files are automatically removed... older_revisions.delete() return redirect("wiki:attachments_index", path=self.urlpath.path, article_id=self.article.id)
class Create(FormView, ArticleMixin): form_class = forms.CreateForm template_name = "wiki/create.html" @method_decorator(get_article(can_write=True, can_create=True)) def dispatch(self, request, article, *args, **kwargs): return super(Create, self).dispatch(request, article, *args, **kwargs) def get_form(self, form_class): """ Returns an instance of the form to be used in this view. """ kwargs = self.get_form_kwargs() initial = kwargs.get('initial', {}) initial['slug'] = self.request.GET.get('slug', None) kwargs['initial'] = initial form = form_class(self.request, self.urlpath, **kwargs) form.fields['slug'].widget = forms.TextInputPrepend( prepend='/' + self.urlpath.path, attrs={ # Make patterns force lowercase if we are case insensitive to bless the user with a # bit of strictness, anyways 'pattern': '[a-z0-9_]+' if not settings.URL_CASE_SENSITIVE else '[a-zA-Z0-9_]+', 'title': 'Lowercase letters, numbers, and underscores' if not settings.URL_CASE_SENSITIVE else 'Letters, numbers, and underscores', }) return form def form_valid(self, form): user = None ip_address = None if not self.request.user.is_anonymous(): user = self.request.user if settings.LOG_IPS_USERS: ip_address = self.request.META.get('REMOTE_ADDR', None) elif settings.LOG_IPS_ANONYMOUS: ip_address = self.request.META.get('REMOTE_ADDR', None) try: self.newpath = models.URLPath.create_article( self.urlpath, form.cleaned_data['slug'], title=form.cleaned_data['title'], content=form.cleaned_data['content'], user_message=form.cleaned_data['summary'], user=user, ip_address=ip_address, article_kwargs={ 'owner': user, 'group': self.article.group, 'group_read': self.article.group_read, 'group_write': self.article.group_write, 'other_read': self.article.other_read, 'other_write': self.article.other_write, }) messages.success( self.request, _("New article '%s' created.") % self.newpath.article.current_revision.title) transaction.commit() # TODO: Handle individual exceptions better and give good feedback. except Exception as e: log.exception("Exception creating article.") transaction.rollback() if self.request.user.is_superuser: messages.error( self.request, _("There was an error creating this article: %s") % str(e)) else: messages.error(self.request, _("There was an error creating this article.")) return redirect('wiki:get', '') url = self.get_success_url() return url def get_success_url(self): return redirect('wiki:get', self.newpath.path) def get_context_data(self, **kwargs): c = ArticleMixin.get_context_data(self, **kwargs) c['parent_urlpath'] = self.urlpath c['parent_article'] = self.article c['create_form'] = kwargs.pop('form', None) c['editor'] = editors.getEditor() return c
class MergeView(View): preview = False template_name = "wiki/preview_inline.html" template_error_name = "wiki/error.html" urlpath = None @method_decorator(get_article(can_write=True)) def dispatch(self, request, article, revision_id, *args, **kwargs): return super().dispatch(request, article, revision_id, *args, **kwargs) def get(self, request, article, revision_id, *args, **kwargs): revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id) current_text = (article.current_revision.content if article.current_revision else "") new_text = revision.content content = simple_merge(current_text, new_text) # Save new revision if not self.preview: old_revision = article.current_revision if revision.deleted: c = { "error_msg": _("You cannot merge with a deleted revision"), "article": article, "urlpath": self.urlpath, } return render(request, self.template_error_name, context=c) new_revision = models.ArticleRevision() new_revision.inherit_predecessor(article) new_revision.deleted = False new_revision.locked = False new_revision.title = article.current_revision.title new_revision.content = content new_revision.automatic_log = _( "Merge between revision #%(r1)d and revision #%(r2)d") % { "r1": revision.revision_number, "r2": old_revision.revision_number } article.add_revision(new_revision, save=True) old_revision.simpleplugin_set.all().update( article_revision=new_revision) revision.simpleplugin_set.all().update( article_revision=new_revision) messages.success( request, _("A new revision was created: Merge between revision #%(r1)d and revision #%(r2)d" ) % { "r1": revision.revision_number, "r2": old_revision.revision_number }, ) if self.urlpath: return redirect("wiki:edit", path=self.urlpath.path) else: return redirect("wiki:edit", article_id=article.id) c = { "article": article, "title": article.current_revision.title, "revision": None, "merge1": revision, "merge2": article.current_revision, "merge": True, "content": content, } return render(request, self.template_name, c)
class Deleted(Delete): """Tell a user that an article has been deleted. If user has permissions, let user restore and possibly purge the deleted article and children.""" template_name = "wiki/deleted.html" form_class = forms.DeleteForm @method_decorator(get_article(can_read=True, deleted_contents=True)) def dispatch(self, request, article, *args, **kwargs): self.urlpath = kwargs.get('urlpath', None) self.article = article if self.urlpath: deleted_ancestor = self.urlpath.first_deleted_ancestor() if deleted_ancestor is None: # No one is deleted! return redirect('wiki:get', path=self.urlpath.path) elif deleted_ancestor != self.urlpath: # An ancestor was deleted, so redirect to that deleted page return redirect('wiki:deleted', path=deleted_ancestor.path) else: if not article.current_revision.deleted: return redirect('wiki:get', article_id=article.id) # Restore if request.GET.get('restore', False): can_restore = not article.current_revision.locked and article.can_delete( request.user) can_restore = can_restore or article.can_moderate(request.user) if can_restore: revision = models.ArticleRevision() revision.inherit_predecessor(self.article) revision.set_from_request(request) revision.deleted = False revision.automatic_log = _('Restoring article') self.article.add_revision(revision) messages.success( request, _('The article "%s" and its children are now restored.') % revision.title) if self.urlpath: return redirect('wiki:get', path=self.urlpath.path) else: return redirect('wiki:get', article_id=article.id) return super(Deleted, self).dispatch1(request, article, *args, **kwargs) def get_initial(self): return {'revision': self.article.current_revision, 'purge': True} def get_context_data(self, **kwargs): # Needed since Django 1.9 because get_context_data is no longer called # with the form instance if 'form' not in kwargs: kwargs['form'] = self.get_form() kwargs['purge_form'] = kwargs.pop('form', None) return super(Delete, self).get_context_data(**kwargs)
class Move(ArticleMixin, FormView): form_class = forms.MoveForm template_name = "wiki/move.html" @method_decorator(login_required) @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, *args, **kwargs): return super(Move, self).dispatch(request, article, *args, **kwargs) def get_initial(self): initial = FormView.get_initial(self) return initial def get_form(self, form_class=None): if form_class is None: form_class = self.get_form_class() kwargs = self.get_form_kwargs() return form_class(**kwargs) def get_context_data(self, **kwargs): if 'form' not in kwargs: kwargs['form'] = self.get_form() kwargs['root_path'] = models.URLPath.root() return super(Move, self).get_context_data(**kwargs) @atomic @transaction_commit_on_success def form_valid(self, form): if not self.urlpath.parent: messages.error( self.request, _('This article cannot be moved because it is a root article.') ) return redirect('wiki:get', article_id=self.article.id) dest_path = get_object_or_404(models.URLPath, pk=form.cleaned_data['destination']) tmp_path = dest_path while tmp_path.parent: if tmp_path == self.urlpath: messages.error( self.request, _('This article cannot be moved to a child of itself.')) return redirect('wiki:move', article_id=self.article.id) tmp_path = tmp_path.parent # Clear cache to update article lists (Old links) for ancestor in self.article.ancestor_objects(): ancestor.article.clear_cache() # Save the old path for later old_path = self.urlpath.path self.urlpath.parent = dest_path self.urlpath.slug = form.cleaned_data['slug'] self.urlpath.save() # Reload url path form database self.urlpath = models.URLPath.objects.get(pk=self.urlpath.pk) # Use a copy of ourself (to avoid cache) and update article links again for ancestor in models.Article.objects.get( pk=self.article.pk).ancestor_objects(): ancestor.article.clear_cache() # Create a redirect page for every moved article # /old-slug # /old-slug/child # /old-slug/child/grand-child if form.cleaned_data['redirect']: # NB! Includes self! descendants = list( self.urlpath.get_descendants( include_self=True).order_by("level")) root_len = len(descendants[0].path) for descendant in descendants: # Without this descendant.get_ancestors() and as a result # descendant.path is wrong after the first create_article() due # to path caching descendant.refresh_from_db() dst_path = descendant.path src_path = urljoin(old_path, dst_path[root_len:]) src_len = len(src_path) pos = src_path.rfind("/", 0, src_len - 1) slug = src_path[pos + 1:src_len - 1] parent_urlpath = models.URLPath.get_by_path( src_path[0:max(pos, 0)]) link = "[wiki:/{path}](wiki:/{path})".format(path=dst_path) urlpath_new = models.URLPath._create_urlpath_from_request( self.request, self.article, parent_urlpath, slug, _("Moved: {title}").format(title=descendant.article), _("Article moved to {link}").format(link=link), _("Created redirect (auto)"), ) urlpath_new.moved_to = descendant urlpath_new.save() messages.success( self.request, _("Article successfully moved! Created {n} redirects.").format( n=len(descendants))) else: messages.success(self.request, _('Article successfully moved!')) return redirect("wiki:get", path=self.urlpath.path)
class CategoryView(ArticleMixin, FormView): ''' This view manages the creation of new categories ''' form_class = forms.ArticleCategoryForm template_name = "category_detail.html" @method_decorator( get_article(can_read=True, can_create=True), ) def dispatch(self, request, article, *args, **kwargs): categories = Category.objects.filter(slug="maincat") return super(CategoryView, self).dispatch(request, article, *args, **kwargs) def get_form_kwargs(self, **kwargs): kwargs = super(CategoryView, self).get_form_kwargs(**kwargs) #kwargs['article'] = self.article #kwargs['request'] = self.request return kwargs # Processing of category creation form goes here, modify the category creation process below def form_valid(self, form): clean_data = form.cleaned_data print(clean_data) slug = clean_data['slug'] title = clean_data['name'] content = clean_data['description'] # creates an article for the category and then associates them by having equaling titles and slugs self.landing_article_urlpath = URLPath.create_article( URLPath.root(), slug, title=title, content=content, user_message=" ", user=self.request.user, article_kwargs={ 'owner': self.request.user, 'group': self.article.group, 'group_read': self.article.group_read, 'group_write': self.article.group_write, 'other_read': self.article.other_read, 'other_write': self.article.other_write, }) landing_article = Article.objects.get( urlpath=self.landing_article_urlpath) form.instance.article = landing_article form.save() category = Category.objects.get(slug=slug) return redirect("wiki:get", path=self.landing_article_urlpath.path, article_id=self.article.id) def get_form(self): form = super(CategoryView, self).get_form(form_class=forms.ArticleCategoryForm) return form # Insert form and category list into context for retrieval in template def get_context_data(self, **kwargs): kwargs['categories'] = Category.objects.all() kwargs['form'] = self.get_form() kwargs = super(CategoryView, self).get_context_data(**kwargs) kwargs['article'] = self.article return kwargs
class Create(FormView, ArticleMixin): form_class = forms.CreateForm template_name = "wiki/create.html" @method_decorator(get_article(can_write=True, can_create=True)) def dispatch(self, request, article, *args, **kwargs): return super(Create, self).dispatch(request, article, *args, **kwargs) def get_form(self, form_class=None): """ Returns an instance of the form to be used in this view. """ if form_class is None: form_class = self.get_form_class() kwargs = self.get_form_kwargs() initial = kwargs.get('initial', {}) initial['slug'] = self.request.GET.get('slug', None) kwargs['initial'] = initial form = form_class(self.request, self.urlpath, **kwargs) form.fields['slug'].widget = forms.TextInputPrepend( prepend='/' + self.urlpath.path, attrs={ # Make patterns force lowercase if we are case insensitive to bless the user with a # bit of strictness, anyways 'pattern': '[a-z0-9_-]+' if not settings.URL_CASE_SENSITIVE else '[a-zA-Z0-9_-]+', 'title': 'Lowercase letters, numbers, hyphens and underscores' if not settings.URL_CASE_SENSITIVE else 'Letters, numbers, hyphens and underscores', }) return form def form_valid(self, form): try: self.newpath = models.URLPath._create_urlpath_from_request( self.request, self.article, self.urlpath, form.cleaned_data['slug'], form.cleaned_data['title'], form.cleaned_data['content'], form.cleaned_data['summary']) messages.success( self.request, _("New article '%s' created.") % self.newpath.article.current_revision.title) # TODO: Handle individual exceptions better and give good feedback. except Exception as e: log.exception("Exception creating article.") if self.request.user.is_superuser: messages.error( self.request, _("There was an error creating this article: %s") % str(e)) else: messages.error(self.request, _("There was an error creating this article.")) return redirect('wiki:get', '') url = self.get_success_url() return url def get_success_url(self): return redirect('wiki:get', self.newpath.path) def get_context_data(self, **kwargs): c = ArticleMixin.get_context_data(self, **kwargs) # Needed since Django 1.9 because get_context_data is no longer called # with the form instance if 'form' not in c: c['form'] = self.get_form() c['parent_urlpath'] = self.urlpath c['parent_article'] = self.article c['create_form'] = c.pop('form', None) c['editor'] = editors.getEditor() return c
class AttachmentDeleteView(ArticleMixin, FormView): form_class = forms.DeleteForm template_name = "wiki/plugins/attachments/delete.html" @method_decorator(get_article(can_write=True, not_locked=True)) def dispatch(self, request, article, attachment_id, *args, **kwargs): self.attachment = get_object_or_404( models.Attachment, id=attachment_id, articles=article) if not self.attachment.can_delete(request.user): return response_forbidden( request, article, kwargs.get( 'urlpath', None)) return super( AttachmentDeleteView, self).dispatch( request, article, *args, **kwargs) def form_valid(self, form): if self.attachment.article == self.article: revision = models.AttachmentRevision() revision.attachment = self.attachment revision.set_from_request(self.request) revision.deleted = True revision.file = self.attachment.current_revision.file if self.attachment.current_revision else None revision.description = self.attachment.current_revision.description if self.attachment.current_revision else "" revision.save() self.attachment.current_revision = revision self.attachment.save() self.article.clear_cache() messages.info( self.request, _('The file %s was deleted.') % self.attachment.original_filename) else: self.attachment.articles.remove(self.article) messages.info( self.request, _('This article is no longer related to the file %s.') % self.attachment.original_filename) self.article.clear_cache() return redirect( "wiki:get", path=self.urlpath.path, article_id=self.article.id) def get_context_data(self, **kwargs): kwargs['attachment'] = self.attachment kwargs['selected_tab'] = 'attachments' if 'form' not in kwargs: kwargs['form'] = self.get_form() return super(AttachmentDeleteView, self).get_context_data(**kwargs)
class Settings(ArticleMixin, TemplateView): permission_form_class = forms.PermissionsForm template_name = "wiki/settings.html" @method_decorator(login_required) @method_decorator(get_article(can_read=True)) def dispatch(self, request, article, *args, **kwargs): return super(Settings, self).dispatch(request, article, *args, **kwargs) def get_form_classes(self, ): """ Return all settings forms that can be filled in """ settings_forms = [] if permissions.can_change_permissions(self.article, self.request.user): settings_forms.append(self.permission_form_class) plugin_forms = [F for F in plugin_registry.get_settings_forms()] plugin_forms.sort(key=lambda form: form.settings_order) settings_forms += plugin_forms for i in range(len(settings_forms)): # TODO: Do not set an attribute on a form class - this # could be mixed up with a different instance # Use strategy from Edit view... setattr(settings_forms[i], 'action', 'form%d' % i) return settings_forms def post(self, *args, **kwargs): self.forms = [] for Form in self.get_form_classes(): if Form.action == self.request.GET.get('f', None): form = Form(self.article, self.request, self.request.POST) if form.is_valid(): form.save() usermessage = form.get_usermessage() if usermessage: messages.success(self.request, usermessage) if self.urlpath: return redirect('wiki:settings', path=self.urlpath.path) return redirect('wiki:settings', article_id=self.article.id) else: form = Form(self.article, self.request) self.forms.append(form) return super(Settings, self).get(*args, **kwargs) def get(self, *args, **kwargs): self.forms = [] # There is a bug where articles fetched with select_related have bad boolean field https://code.djangoproject.com/ticket/15040 # We fetch a fresh new article for this reason new_article = models.Article.objects.get(id=self.article.id) for Form in self.get_form_classes(): self.forms.append(Form(new_article, self.request)) return super(Settings, self).get(*args, **kwargs) def get_success_url(self): if self.urlpath: return redirect('wiki:settings', path=self.urlpath.path) return redirect('wiki:settings', article_id=self.article.id) def get_context_data(self, **kwargs): kwargs['selected_tab'] = 'settings' kwargs['forms'] = self.forms return super(Settings, self).get_context_data(**kwargs)
class Create(FormView, ArticleMixin): form_class = forms.CreateForm template_name = "wiki/create.html" @method_decorator(get_article(can_write=True, can_create=True)) def dispatch(self, request, article, *args, **kwargs): return super(Create, self).dispatch(request, article, *args, **kwargs) def get_form(self, form_class): """ Returns an instance of the form to be used in this view. """ kwargs = self.get_form_kwargs() initial = kwargs.get('initial', {}) initial['slug'] = self.request.GET.get('slug', None) kwargs['initial'] = initial form = form_class(self.request, self.urlpath, **kwargs) form.fields['slug'].widget = forms.TextInputPrepend(prepend='/' + self.urlpath.path) return form def form_valid(self, form): user = None ip_address = None if not self.request.user.is_anonymous(): user = self.request.user if settings.LOG_IPS_USERS: ip_address = self.request.META.get('REMOTE_ADDR', None) elif settings.LOG_IPS_ANONYMOUS: ip_address = self.request.META.get('REMOTE_ADDR', None) try: self.newpath = models.URLPath.create_article( self.urlpath, form.cleaned_data['slug'], title=form.cleaned_data['title'], content=form.cleaned_data['content'], user_message=form.cleaned_data['summary'], user=user, ip_address=ip_address, article_kwargs={ 'owner': user, 'group': self.article.group, 'group_read': self.article.group_read, 'group_write': self.article.group_write, 'other_read': self.article.other_read, 'other_write': self.article.other_write, }) messages.success( self.request, _(u"New article '%s' created.") % self.newpath.article.current_revision.title) except Exception, e: log.exception("Exception creating article.") if self.request.user.is_superuser: messages.error( self.request, _(u"There was an error creating this article: %s") % str(e)) else: messages.error(self.request, _(u"There was an error creating this article.")) return redirect('wiki:get', '') url = self.get_success_url() return url