Commit 6be6a0c1 by benjaoming

Merge branch 'master' of github.com:benjaoming/django-wiki

parents 915260d6 32416cd1
...@@ -102,12 +102,15 @@ def get_article(func=None, can_read=True, can_write=False, ...@@ -102,12 +102,15 @@ def get_article(func=None, can_read=True, can_write=False,
else: else:
raise TypeError('You should specify either article_id or path') raise TypeError('You should specify either article_id or path')
# If the article has been deleted, show a special page. if not deleted_contents:
if not deleted_contents and article.current_revision and article.current_revision.deleted: # If the article has been deleted, show a special page.
if urlpath: if urlpath:
return redirect('wiki:deleted', path=urlpath.path) if urlpath.is_deleted(): # This also checks all ancestors
return redirect('wiki:deleted', path=urlpath.path)
else: else:
return redirect('wiki:deleted', article_id=article.id) if article.current_revision and article.current_revision.deleted:
return redirect('wiki:deleted', article_id=article.id)
if article.current_revision.locked and not_locked: if article.current_revision.locked and not_locked:
return response_forbidden(request, article, urlpath) return response_forbidden(request, article, urlpath)
......
...@@ -74,6 +74,18 @@ class URLPath(MPTTModel): ...@@ -74,6 +74,18 @@ class URLPath(MPTTModel):
return "/".join(slugs) + "/" return "/".join(slugs) + "/"
def is_deleted(self):
"""
Returns True if this article or any of its ancestors have been deleted
"""
return self.first_deleted_ancestor() is not None
def first_deleted_ancestor(self):
for ancestor in self.cached_ancestors + [self]:
if ancestor.article.current_revision.deleted == True:
return ancestor
return None
@classmethod @classmethod
def root(cls): def root(cls):
site = Site.objects.get_current() site = Site.objects.get_current()
...@@ -202,39 +214,40 @@ def on_article_relation_save(instance, *args, **kwargs): ...@@ -202,39 +214,40 @@ def on_article_relation_save(instance, *args, **kwargs):
post_save.connect(on_article_relation_save, ArticleForObject) post_save.connect(on_article_relation_save, ArticleForObject)
def on_article_delete(instance, *args, **kwargs): # TODO: When a parent all of its children are purged, they get sucked up into the lost and found. It is disabled for now.
# If an article is deleted, then throw out its URLPaths # def on_article_delete(instance, *args, **kwargs):
# But move all descendants to a lost-and-found node. # # If an article is deleted, then throw out its URLPaths
site = Site.objects.get_current() # # But move all descendants to a lost-and-found node.
# site = Site.objects.get_current()
# Get the Lost-and-found path or create a new one #
try: # # Get the Lost-and-found path or create a new one
lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG, # try:
parent=URLPath.root(), # lost_and_found = URLPath.objects.get(slug=settings.LOST_AND_FOUND_SLUG,
site=site) # parent=URLPath.root(),
except URLPath.DoesNotExist: # site=site)
article = Article(group_read = True, # except URLPath.DoesNotExist:
group_write = False, # article = Article(group_read = True,
other_read = False, # group_write = False,
other_write = False) # other_read = False,
article.add_revision(ArticleRevision( # other_write = False)
content=_(u'Articles who lost their parents\n' # article.add_revision(ArticleRevision(
'===============================\n\n' # content=_(u'Articles who lost their parents\n'
'The children of this article have had their parents deleted. You should probably find a new home for them.'), # '===============================\n\n'
title=_(u"Lost and found"))) # 'The children of this article have had their parents deleted. You should probably find a new home for them.'),
lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG, # title=_(u"Lost and found")))
parent=URLPath.root(), # lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG,
site=site, # parent=URLPath.root(),
article=article) # site=site,
article.add_object_relation(lost_and_found) # article=article)
# article.add_object_relation(lost_and_found)
#
for urlpath in URLPath.objects.filter(articles__article=instance, site=site): #
# Delete the children # for urlpath in URLPath.objects.filter(articles__article=instance, site=site):
for child in urlpath.get_children(): # # Delete the children
child.move_to(lost_and_found) # for child in urlpath.get_children():
# ...and finally delete the path itself # child.move_to(lost_and_found)
# TODO: This should be unnecessary because of URLPath.article(...ondelete=models.CASCADE) # # ...and finally delete the path itself
urlpath.delete() # # TODO: This should be unnecessary because of URLPath.article(...ondelete=models.CASCADE)
# urlpath.delete()
pre_delete.connect(on_article_delete, Article) #
# pre_delete.connect(on_article_delete, Article)
...@@ -7,11 +7,12 @@ from django_notify import notify ...@@ -7,11 +7,12 @@ from django_notify import notify
from django_notify.models import Subscription from django_notify.models import Subscription
from wiki import models as wiki_models from wiki import models as wiki_models
from wiki.models.pluginbase import ArticlePlugin
from wiki.core.plugins import registry from wiki.core.plugins import registry
from wiki.plugins.notifications import ARTICLE_EDIT #TODO: Is this bad practice? from wiki.plugins.notifications import ARTICLE_EDIT #TODO: Is this bad practice?
from wiki.plugins.notifications import settings from wiki.plugins.notifications import settings
class ArticleSubscription(wiki_models.pluginbase.ArticlePlugin, Subscription): class ArticleSubscription(ArticlePlugin, Subscription):
def __unicode__(self): def __unicode__(self):
return (_(u"%(user)s subscribing to %(article)s (%(type)s)") % return (_(u"%(user)s subscribing to %(article)s (%(type)s)") %
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<div class="span6"> <div class="span6">
<div class="well"> <div class="well">
<h2>{% trans "Restore" %}</h2> <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>{% trans "You may restore this article and its children by clicking restore." %}</p>
<p> <p>
<a href="?restore=1" class="btn"> <a href="?restore=1" class="btn">
<span class="icon-repeat"></span> <span class="icon-repeat"></span>
......
...@@ -130,8 +130,10 @@ class Delete(FormView, ArticleMixin): ...@@ -130,8 +130,10 @@ class Delete(FormView, ArticleMixin):
if urlpath and urlpath.parent: if urlpath and urlpath.parent:
self.next = reverse('wiki:get', kwargs={'path': urlpath.parent.path}) self.next = reverse('wiki:get', kwargs={'path': urlpath.parent.path})
elif urlpath: elif urlpath:
# We are a urlpath with no parent. This is the root
self.cannot_delete_root = True self.cannot_delete_root = True
else: else:
# We have no urlpath. Get it if a urlpath exists
for art_obj in article.articleforobject_set.filter(is_mptt=True): for art_obj in article.articleforobject_set.filter(is_mptt=True):
if art_obj.content_object.parent: if art_obj.content_object.parent:
self.next = reverse('wiki:get', kwargs={'article_id': art_obj.content_object.parent.article.id}) self.next = reverse('wiki:get', kwargs={'article_id': art_obj.content_object.parent.article.id})
...@@ -154,42 +156,31 @@ class Delete(FormView, ArticleMixin): ...@@ -154,42 +156,31 @@ class Delete(FormView, ArticleMixin):
kwargs['article'] = self.article kwargs['article'] = self.article
kwargs['has_children'] = bool(self.children_slice) kwargs['has_children'] = bool(self.children_slice)
return kwargs return kwargs
@disable_notify
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=restore):
revision = models.ArticleRevision()
revision.inherit_predecessor(child.article)
revision.set_from_request(self.request)
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): def form_valid(self, form):
cd = form.cleaned_data cd = form.cleaned_data
purge = cd['purge']
#If we are purging, only moderators can delete articles with children
cannot_delete_children = False cannot_delete_children = False
can_moderate = self.article.can_moderate(self.request.user) can_moderate = self.article.can_moderate(self.request.user)
if self.children_slice and not can_moderate: if purge and self.children_slice and not can_moderate:
cannot_delete_children = True cannot_delete_children = True
if self.cannot_delete_root or cannot_delete_children: if self.cannot_delete_root or cannot_delete_children:
messages.error(self.request, _(u'This article cannot be deleted because it has children or is a root article.')) messages.error(self.request, _(u'This article cannot be deleted because it has children or is a root article.'))
return redirect('wiki:get', article_id=self.article.id) return redirect('wiki:get', article_id=self.article.id)
# First, remove children
self.delete_children(purge=cd['purge']) if can_moderate and purge:
# First, remove children
if can_moderate and cd['purge']: if self.urlpath:
self.article.delete() for descendant in self.urlpath.get_descendants(include_self=True):
descendant.article.delete()
else:
self.article.delete()
messages.success(self.request, _(u'This article together with all its contents are now completely gone! Thanks!')) messages.success(self.request, _(u'This article together with all its contents are now completely gone! Thanks!'))
else: else:
revision = models.ArticleRevision() revision = models.ArticleRevision()
...@@ -327,17 +318,24 @@ class Deleted(Delete): ...@@ -327,17 +318,24 @@ class Deleted(Delete):
self.urlpath = kwargs.get('urlpath', None) self.urlpath = kwargs.get('urlpath', None)
self.article = article self.article = article
if not article.current_revision.deleted: if self.urlpath:
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) return redirect('wiki:get', path=self.urlpath.path)
else: 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) return redirect('wiki:get', article_id=article.id)
# Restore # Restore
if (request.GET.get('restore', False) and if (request.GET.get('restore', False) and
(not article.current_revision.locked and article.can_delete(request.user)) or (not article.current_revision.locked and article.can_delete(request.user)) or
article.can_moderate(request.user)): article.can_moderate(request.user)):
self.delete_children(restore=True)
revision = models.ArticleRevision() revision = models.ArticleRevision()
revision.inherit_predecessor(self.article) revision.inherit_predecessor(self.article)
revision.set_from_request(request) revision.set_from_request(request)
......
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