Commit 56d8e574 by benjaoming

Filtering on Directory listings. Issue #27 - never create a merged revision that…

Filtering on Directory listings. Issue #27 - never create a merged revision that inherits the deleted or locked attribute.
parent 2979c984
...@@ -348,3 +348,6 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm): ...@@ -348,3 +348,6 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm):
fields = ('locked', 'owner_username', 'group', 'group_read', 'group_write', 'other_read', 'other_write', fields = ('locked', 'owner_username', 'group', 'group_read', 'group_write', 'other_read', 'other_write',
'recursive') 'recursive')
class DirFilterForm(forms.Form):
query = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _(u'Filter...')}))
...@@ -65,6 +65,9 @@ class URLPath(MPTTModel): ...@@ -65,6 +65,9 @@ class URLPath(MPTTModel):
def cached_ancestors(self, ancestors): def cached_ancestors(self, ancestors):
self._cached_ancestors = ancestors self._cached_ancestors = ancestors
def set_cached_ancestors_from_parent(self, parent):
self.cached_ancestors = parent.cached_ancestors + [parent]
@property @property
def path(self): def path(self):
if not self.parent: return "" if not self.parent: return ""
...@@ -80,7 +83,6 @@ class URLPath(MPTTModel): ...@@ -80,7 +83,6 @@ class URLPath(MPTTModel):
""" """
return self.first_deleted_ancestor() is not None return self.first_deleted_ancestor() is not None
# TODO: Can we move this into is_deleted's logic? It is a very strange method
def first_deleted_ancestor(self): def first_deleted_ancestor(self):
for ancestor in self.cached_ancestors + [self]: for ancestor in self.cached_ancestors + [self]:
if ancestor.article.current_revision.deleted == True: if ancestor.article.current_revision.deleted == True:
...@@ -215,40 +217,41 @@ def on_article_relation_save(instance, *args, **kwargs): ...@@ -215,40 +217,41 @@ def on_article_relation_save(instance, *args, **kwargs):
post_save.connect(on_article_relation_save, ArticleForObject) post_save.connect(on_article_relation_save, ArticleForObject)
# TODO: When a parent all of its children are purged, they get sucked up into the lost and found. It is disabled for now. # TODO: When a parent all of its children are purged, they get
# def on_article_delete(instance, *args, **kwargs): # 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)
...@@ -6,44 +6,66 @@ ...@@ -6,44 +6,66 @@
{% block wiki_contents_tab %} {% block wiki_contents_tab %}
<p class="lead"> <form class="form-search">
{% with paginator.object_list.count as cnt %} <div class="well well-small">
{% blocktrans with article.current_revision.title as title and cnt|pluralize:_("article,articles") as articles_plur and cnt|pluralize:_("is,are") as articles_plur_verb %} <div class="btn-group pull-left">
There {{ articles_plur_verb }} <strong>{{ cnt }} {{ articles_plur }}</strong> in the "{{ title }}" level.
{% endblocktrans %}
{% endwith %}
</p>
<p>
{% if urlpath.parent %} {% if urlpath.parent %}
<a href="{% url 'wiki:dir' path=urlpath.parent.path %}" class="btn"> <a href="{% url 'wiki:dir' path=urlpath.parent.path %}" class="btn">
<span class="icon-arrow-up"></span> <span class="icon-arrow-up"></span>
{% trans "Go up one level" %} {% trans "Go up one level" %}
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'wiki:get' path=urlpath.path %}" class="btn">
<span class="icon-arrow-right"></span>
{% trans "View article" %}
</a>
<a href="{% url 'wiki:create' path=urlpath.path %}" class="btn"> <a href="{% url 'wiki:create' path=urlpath.path %}" class="btn">
<span class="icon-plus"></span> <span class="icon-plus"></span>
{% trans "Add article in this level" %} {% trans "Add article in this level" %}
</a> </a>
</p> </div>
<div class="pull-right">
{{ filter_form.query }}
<button class="btn">
<span class="icon-search"></span>
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% url 'wiki:get' urlpath.path as self_url %}
<p>
{% with paginator.object_list.count as cnt %}
{% blocktrans with urlpath.path as path and cnt|pluralize:_("article,articles") as articles_plur and cnt|pluralize:_("is,are") as articles_plur_verb %}
Browsing <strong><a href="{{ self_url }}">/{{ path }}</a></strong>. There {{ articles_plur_verb }} <strong>{{ cnt }} {{ articles_plur }}</strong> in this level.
{% endblocktrans %}
{% endwith %}
</p>
{% if filter_query %}
<p>{% trans "Filtering by" %} <strong>{{ filter_query }}</strong> (<a href="{{ self_url }}">{% trans "clear" %}</a>).</p>
{% endif %}
<table class="table table-striped"> <table class="table table-striped">
<tr> <tr>
<th>{% trans "Title" %}</th> <th>{% trans "Title" %}</th>
<th>{% trans "Slug" %}</th>
<th>{% trans "Last modified" %}</th> <th>{% trans "Last modified" %}</th>
</tr> </tr>
{% for article in articles %} {% for urlpath in directory %}
<tr> <tr>
<td> <td>
<a href="{% url 'wiki:dir' path=article.path %}"> {{ article.article.current_revision.title }} </a> <a href="{% url 'wiki:dir' path=urlpath.path %}"> {{ urlpath.article.current_revision.title }} </a>
{% if urlpath.article.current_revision.deleted %}
<span class="icon-trash"></span>
{% endif %}
{% if urlpath.article.current_revision.locked %}
<span class="icon-lock"></span>
{% endif %}
</td>
<td>
{{ urlpath.slug }}
</td> </td>
<td> <td>
{{ article.article.current_revision.created|naturaltime }} {{ urlpath.article.current_revision.created|naturaltime }}
</td> </td>
</tr> </tr>
{% empty%} {% empty%}
......
...@@ -23,8 +23,8 @@ ...@@ -23,8 +23,8 @@
{% if revision and revision.deleted %} {% if revision and revision.deleted %}
<div class="warning"> <div class="warning">
<strong>This revision has been deleted.</strong> <strong>{% trans "This revision has been deleted." %}</strong>
<p>Restoring to this revision will mark the article as deleted.</p> <p>{% trans "Restoring to this revision will mark the article as deleted." %}</p>
</div> </div>
{% else %} {% else %}
{% wiki_render article content %} {% wiki_render article content %}
......
...@@ -3,6 +3,7 @@ import difflib ...@@ -3,6 +3,7 @@ import difflib
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.shortcuts import render_to_response, redirect, get_object_or_404 from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template.context import RequestContext from django.template.context import RequestContext
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
...@@ -319,7 +320,6 @@ class Deleted(Delete): ...@@ -319,7 +320,6 @@ class Deleted(Delete):
if self.urlpath: if self.urlpath:
deleted_ancestor = self.urlpath.first_deleted_ancestor() deleted_ancestor = self.urlpath.first_deleted_ancestor()
if deleted_ancestor is None: if deleted_ancestor is None:
# No one is deleted! # No one is deleted!
return redirect('wiki:get', path=self.urlpath.path) return redirect('wiki:get', path=self.urlpath.path)
...@@ -400,22 +400,31 @@ class History(ListView, ArticleMixin): ...@@ -400,22 +400,31 @@ class History(ListView, ArticleMixin):
return super(History, self).dispatch(request, article, *args, **kwargs) return super(History, self).dispatch(request, article, *args, **kwargs)
class Dir(ListView, ArticleMixin): class Dir(ListView, ArticleMixin, FormView):
template_name="wiki/dir.html" template_name="wiki/dir.html"
allow_empty = True allow_empty = True
context_object_name = 'articles' context_object_name = 'directory'
model = models.URLPath model = models.URLPath
paginate_by = 30 paginate_by = 30
@method_decorator(get_article(can_read=True)) @method_decorator(get_article(can_read=True))
def dispatch(self, request, article, *args, **kwargs): def dispatch(self, request, article, *args, **kwargs):
self.filter_form = forms.DirFilterForm(request.GET)
if self.filter_form.is_valid():
self.query = self.filter_form.cleaned_data['query']
else:
self.query = None
return super(Dir, self).dispatch(request, article, *args, **kwargs) return super(Dir, self).dispatch(request, article, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
children = self.urlpath.get_children().can_read(self.request.user).select_related_common().order_by('article__current_revision__title') children = self.urlpath.get_children().can_read(self.request.user)
if self.query:
children = children.filter(Q(article__current_revision__title__contains=self.query) |
Q(slug__contains=self.query))
if not self.article.can_moderate(self.request.user): if not self.article.can_moderate(self.request.user):
children = children.active() children = children.active()
children = children.select_related_common().order_by('article__current_revision__title')
return children return children
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
...@@ -423,13 +432,14 @@ class Dir(ListView, ArticleMixin): ...@@ -423,13 +432,14 @@ class Dir(ListView, ArticleMixin):
kwargs_listview = ListView.get_context_data(self, **kwargs) kwargs_listview = ListView.get_context_data(self, **kwargs)
kwargs.update(kwargs_article) kwargs.update(kwargs_article)
kwargs.update(kwargs_listview) kwargs.update(kwargs_listview)
kwargs['filter_query'] = self.query
kwargs['filter_form'] = self.filter_form
# We take this opportunity to add a bit of caching to each child. # Update each child's ancestor cache so the lookups don't have
# Otherwise, 1 query every time we get a child's path # to be repeated.
updated_children = kwargs[self.context_object_name] updated_children = kwargs[self.context_object_name]
new_ancestors = self.urlpath.cached_ancestors + [self.urlpath]
for child in updated_children: for child in updated_children:
child.cached_ancestors = new_ancestors child.set_cached_ancestors_from_parent(self.urlpath)
kwargs[self.context_object_name] = updated_children kwargs[self.context_object_name] = updated_children
return kwargs return kwargs
...@@ -592,6 +602,8 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ ...@@ -592,6 +602,8 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ
old_revision = article.current_revision old_revision = article.current_revision
new_revision = models.ArticleRevision() new_revision = models.ArticleRevision()
new_revision.inherit_predecessor(article) new_revision.inherit_predecessor(article)
new_revision.deleted = False
new_revision.locked = False
new_revision.title=article.current_revision.title new_revision.title=article.current_revision.title
new_revision.content=content new_revision.content=content
new_revision.automatic_log = (_(u'Merge between Revision #%(r1)d and Revision #%(r2)d') % new_revision.automatic_log = (_(u'Merge between Revision #%(r1)d and Revision #%(r2)d') %
......
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