Commit f7f65270 by benjaoming

Deleted article view with purge and restore options

parent 63c7e9f5
......@@ -17,8 +17,24 @@ def json_view(func):
return wrap
def get_article(func=None, can_read=True, can_write=False, deleted_contents=False):
"""Intercepts the keyword args path or article_id and looks up an article,
calling the decorated func with this ID."""
"""View decorator for processing standard url keyword args: Intercepts the
keyword args path or article_id and looks up an article, calling the decorated
func with this ID.
Will accept a func(request, article, *args, **kwargs)
NB! This function will redirect if an article does not exist, permissions
are missing or the article is deleted.
Arguments:
can_read=True and/or can_write=True: Check that the current request.user
has correct permissions.
deleted_contents=True: Do not redirect if the article has been deleted.
Also see: wiki.views.mixins.ArticleMixin
"""
def wrapper(request, *args, **kwargs):
import models
......@@ -78,5 +94,6 @@ def get_article(func=None, can_read=True, can_write=False, deleted_contents=Fals
if func:
return wrapper
else:
return lambda func: get_article(func, can_read=can_read, can_write=can_write)
return lambda func: get_article(func, can_read=can_read, can_write=can_write,
deleted_contents=deleted_contents)
......@@ -236,7 +236,7 @@ class DeleteForm(forms.Form):
super(DeleteForm, self).__init__(*args, **kwargs)
confirm = forms.BooleanField(required=False,
label=_(u'Confirm'))
label=_(u'Yes, I am sure'))
purge = forms.BooleanField(widget=HiddenInput(), required=False,
label=_(u'Purge'),
help_text=_(u'Purge the article: Completely remove it (and all its contents) with no undo. Purging is a good idea if you want to free the slug such that users can create new articles in its place.'))
......
{% extends "wiki/base.html" %}
{% load wiki_tags i18n sekizai_tags %}
{% load url from future %}
{% block pagetitle %}{% trans "Article deleted" %}{% endblock %}
{% block wiki_contents %}
<style type="text/css">
label[for=id_confirm] {
float: left;
margin-right: 10px;
}
</style>
<h1 class="page-header">{% trans "Article Deleted" %}</h1>
<p class="lead">
{% trans "The article you were looking for has been deleted." %}
</p>
<div class="row-fluid">
{% if not article.current_revision.locked or user|is_moderator %}
<div class="span6">
<div class="well">
<h2>{% trans "Restore" %}</h2>
<p>{% trans "You may restore this article and its children by clicking restore. Note that this restores ALL children." %}</p>
<p>
<a href="?restore=1" class="btn">
<span class="icon-repeat"></span>
{% trans "Restore" %}
</a>
</p>
</div>
</div>
{% endif %}
{% if user|is_moderator %}
<div class="span6">
<div class="well">
<h2>{% trans "Purge deletion" %}</h2>
<p>{% trans "You may remove this article and any children permanently and free their slugs by clicking the below button. This action cannot be undone." %}</p>
<form method="POST" class="form form-inline">
{% csrf_token %}
{% wiki_form purge_form %}
<button class="btn">
<span class="icon-remove"></span>
{% trans "Purge" %}
</button>
</form>
</div>
</div>
{% endif %}
</div>
{% endblock %}
......@@ -67,6 +67,9 @@
{% if revision.deleted %}
<span class="badge badge-important">{% trans "deleted" %}</span>
{% endif %}
{% if revision.previous_revision.deleted and not revision.deleted %}
<span class="badge badge-success">{% trans "restored" %}</span>
{% endif %}
<div style="color: #CCC;">
<small>
{% if revision.user_message %}
......
......@@ -27,6 +27,7 @@ urlpatterns += patterns('',
# Paths decided by article_ids
url('^(?P<article_id>\d+)/$', article.ArticleView.as_view(), name='get'),
url('^(?P<article_id>\d+)/delete/$', article.Delete.as_view(), name='delete'),
url('^(?P<article_id>\d+)/deleted/$', article.Deleted.as_view(), name='deleted'),
url('^(?P<article_id>\d+)/edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<article_id>\d+)/preview/$', 'wiki.views.article.preview', name='preview'),
url('^(?P<article_id>\d+)/history/$', article.History.as_view(), name='history'),
......@@ -50,6 +51,7 @@ urlpatterns += patterns('',
# Paths decided by URLs
url('^(?P<path>.+/|)_create/$', article.Create.as_view(), name='create'),
url('^(?P<path>.+/|)_delete/$', article.Delete.as_view(), name='delete'),
url('^(?P<path>.+/|)_deleted/$', article.Deleted.as_view(), name='deleted'),
url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<path>.+/|)_preview/$', 'wiki.views.article.preview', name='preview'),
url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'),
......
......@@ -116,6 +116,11 @@ class Delete(FormView, ArticleMixin):
@method_decorator(get_article(can_write=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 = request.GET.get('next', None)
......@@ -150,17 +155,21 @@ class Delete(FormView, ArticleMixin):
return kwargs
@disable_notify
def delete_children(self, purge=False):
def delete_children(self, purge=False, restore=False):
assert not (restore and purge), "You cannot purge a restore"
if purge:
for child in self.article.get_children(articles__article__current_revision__deleted=False):
child.delete()
else:
for child in self.article.get_children(articles__article__current_revision__deleted=False):
for child in self.article.get_children(articles__article__current_revision__deleted=restore):
revision = models.ArticleRevision()
revision.inherit_predecessor(child.article)
revision.set_from_request(self.request)
revision.automatic_log = _(u'Deleting children of "%s"') % self.article.current_revision.title
revision.deleted = True
if restore:
revision.automatic_log = _(u'Restoring children of "%s"') % self.article.current_revision.title
else:
revision.automatic_log = _(u'Deleting children of "%s"') % self.article.current_revision.title
revision.deleted = not restore
child.article.add_revision(revision)
def form_valid(self, form):
......@@ -246,6 +255,54 @@ class Edit(FormView, ArticleMixin):
# TODO: ...
class Deleted(Delete):
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 not article.current_revision.deleted:
if self.urlpath:
return redirect('wiki:get', path=self.urlpath.path)
else:
return redirect('wiki:get', article_id=article.id)
# Restore
if (request.GET.get('restore', False) and
(not article.current_revision.locked or request.user.has_perm('wiki.moderator'))):
self.delete_children(restore=True)
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, _(u'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_form(self, form_class):
form = super(Delete, self).get_form(form_class)
return form
def get_context_data(self, **kwargs):
kwargs['purge_form'] = kwargs.pop('form', None)
return super(Delete, self).get_context_data(**kwargs)
# TODO: ...
class Source(ArticleMixin, TemplateView):
pass
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment