Commit 0cb2ca2c by benjaoming

Merge pull request #188 from yedpodtrzitko/master

create root as a class-based view
parents 9528bf71 10a44575
......@@ -4,7 +4,7 @@
{% block wiki_pagetitle %}{% trans "Create root article" %}{% endblock %}
{% block wiki_contents %}
{% addtoblock "js" %}
{% for js in editor.Media.js %}
<script type="text/javascript" src="{{ STATIC_URL }}{{ js }}"></script>
......@@ -23,17 +23,17 @@
<p class="lead">
{% trans "You have django-wiki installed... but there are no articles. So it's time to create the first one, the root article. In the beginning, it will only be editable by administrators, but you can define permissions after." %}
</p>
<h2 class="page-header">{% trans "Root article" %}</h2>
<form method="POST" class="form-horizontal">
{% wiki_form create_form %}
{% wiki_form form %}
<div class="form-group form-actions">
<div class="col-lg-2"></div>
<div class="col-lg-10">
<input type="submit" name="save_changes" value="{% trans "Create root" %} &raquo;" class="btn btn-primary btn-large" />
</div>
</div>
</div>
</form>
{% endblock %}
......
......@@ -6,10 +6,11 @@ from wiki.core.plugins import registry
from wiki.views import article, accounts
from wiki.core.utils import get_class_from_str
class WikiURLPatterns(object):
'''
configurator for wiki urls.
To customize, you can define your own subclass, either overriding
the view providers, or overriding the functions that collect
views.
......@@ -30,7 +31,6 @@ class WikiURLPatterns(object):
revision_change_view = 'wiki.views.article.change_revision'
revision_merge_view = 'wiki.views.article.merge'
create_root = 'wiki.views.article.root_create'
search_view_class = settings.SEARCH_VIEW
article_diff_view = 'wiki.views.article.diff'
......@@ -38,14 +38,14 @@ class WikiURLPatterns(object):
signup_view_class = accounts.Signup
login_view_class = accounts.Login
logout_view_class = accounts.Logout
def get_urls(self):
urlpatterns = self.get_root_urls()
urlpatterns += self.get_accounts_urls()
urlpatterns += self.get_revision_urls()
urlpatterns += self.get_article_urls()
urlpatterns += self.get_plugin_urls()
# This ALWAYS has to be the last of all the patterns since
# the paths in theory could wrongly match other targets.
urlpatterns += self.get_article_path_urls()
......@@ -54,7 +54,7 @@ class WikiURLPatterns(object):
def get_root_urls(self):
urlpatterns = patterns('',
url('^$', self.article_view_class.as_view(), name='root', kwargs={'path': ''}),
url('^create-root/$', self.create_root, name='root_create'),
url('^create-root/$', article.CreateRootView.as_view(), name='root_create'),
url('^_search/$', get_class_from_str(self.search_view_class).as_view(), name='search'),
url('^_revision/diff/(?P<revision_id>\d+)/$', self.article_diff_view, name='diff'),
)
......@@ -70,7 +70,7 @@ class WikiURLPatterns(object):
def get_revision_urls(self):
urlpatterns = patterns('',
# This one doesn't work because it don't know where to redirect after...
# This one doesn't work because it don't know where to redirect after...
url('^_revision/change/(?P<article_id>\d+)/(?P<revision_id>\d+)/$', self.revision_change_view, name='change_revision'),
url('^_revision/preview/(?P<article_id>\d+)/$', self.article_preview_view_class.as_view(), name='preview_revision'),
url('^_revision/merge/(?P<article_id>\d+)/(?P<revision_id>\d+)/preview/$', self.revision_merge_view, name='merge_revision_preview', kwargs={'preview': True}),
......
......@@ -28,26 +28,26 @@ from django.http import Http404
class ArticleView(ArticleMixin, TemplateView):
template_name="wiki/view.html"
@method_decorator(get_article(can_read=True))
def dispatch(self, request, article, *args, **kwargs):
return super(ArticleView, self).dispatch(request, article, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['selected_tab'] = 'view'
return ArticleMixin.get_context_data(self, **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.
......@@ -57,9 +57,9 @@ class Create(FormView, ArticleMixin):
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)
form.fields['slug'].widget = forms.TextInputPrepend(prepend='/'+self.urlpath.path)
return form
@transaction.commit_manually
def form_valid(self, form):
user=None
......@@ -87,7 +87,7 @@ class Create(FormView, ArticleMixin):
'other_write': self.article.other_write,
})
messages.success(self.request, _(u"New article '%s' created.") % self.newpath.article.current_revision.title)
transaction.commit()
# TODO: Handle individual exceptions better and give good feedback.
except Exception, e:
......@@ -98,36 +98,36 @@ class Create(FormView, ArticleMixin):
messages.error(self.request, _(u"There was an error creating this article."))
transaction.commit()
return redirect('wiki:get', '')
url = self.get_success_url()
transaction.commit()
return url
def get_success_url(self):
return redirect('wiki:get', self.newpath.path)
def get_context_data(self, **kwargs):
kwargs['parent_urlpath'] = self.urlpath
kwargs['parent_article'] = self.article
kwargs['create_form'] = kwargs.pop('form', None)
kwargs['editor'] = editors.getEditor()
return super(Create, 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...
# Where to go after deletion...
self.next = ""
self.cannot_delete_root = False
if urlpath and urlpath.parent:
......@@ -142,29 +142,29 @@ class Delete(FormView, ArticleMixin):
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):
form = super(Delete, self).get_form(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)
......@@ -174,7 +174,7 @@ class Delete(FormView, ArticleMixin):
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.'))
return redirect('wiki:get', article_id=self.article.id)
if can_moderate and purge:
# First, remove children
......@@ -190,15 +190,15 @@ class Delete(FormView, ArticleMixin):
self.article.add_revision(revision)
messages.success(self.request, _(u'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
kwargs['delete_form'] = kwargs.pop('form', None)
kwargs['cannot_delete_root'] = self.cannot_delete_root
kwargs['delete_children'] = self.children_slice[:20]
......@@ -209,19 +209,19 @@ class Delete(FormView, ArticleMixin):
class Edit(FormView, ArticleMixin):
"""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 self.request.session.keys():
......@@ -229,7 +229,7 @@ class Edit(FormView, ArticleMixin):
initial[field_name] = content
del self.request.session[session_key]
return initial
def get_form(self, form_class):
"""
Checks from querystring data that the edit form is actually being saved,
......@@ -241,7 +241,7 @@ class Edit(FormView, ArticleMixin):
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
......@@ -250,7 +250,7 @@ class Edit(FormView, ArticleMixin):
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 = []
......@@ -262,7 +262,7 @@ class Edit(FormView, ArticleMixin):
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 = []
......@@ -277,19 +277,19 @@ class Edit(FormView, ArticleMixin):
messages.success(self.request, usermessage)
else:
messages.success(self.request, _(u'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)
......@@ -297,9 +297,9 @@ class Edit(FormView, ArticleMixin):
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
"""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)
......@@ -311,13 +311,13 @@ class Edit(FormView, ArticleMixin):
self.article.add_revision(revision)
messages.success(self.request, _(u'A new revision of the article was succesfully 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):
kwargs['edit_form'] = kwargs.pop('form', None)
kwargs['editor'] = editors.getEditor()
......@@ -329,16 +329,16 @@ class Edit(FormView, ArticleMixin):
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:
......@@ -347,16 +347,16 @@ class Deleted(Delete):
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)
......@@ -369,9 +369,9 @@ class Deleted(Delete):
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}
......@@ -383,30 +383,30 @@ class Deleted(Delete):
def get_context_data(self, **kwargs):
kwargs['purge_form'] = kwargs.pop('form', None)
return super(Delete, self).get_context_data(**kwargs)
class Source(ArticleMixin, TemplateView):
template_name="wiki/source.html"
@method_decorator(get_article(can_read=True))
def dispatch(self, request, article, *args, **kwargs):
return super(Source, self).dispatch(request, article, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['selected_tab'] = 'source'
return ArticleMixin.get_context_data(self, **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)
......@@ -415,20 +415,20 @@ class History(ListView, ArticleMixin):
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 Dir(ListView, ArticleMixin):
template_name="wiki/dir.html"
allow_empty = True
context_object_name = 'directory'
model = models.URLPath
paginate_by = 30
@method_decorator(get_article(can_read=True))
def dispatch(self, request, article, *args, **kwargs):
self.filter_form = forms.DirFilterForm(request.GET)
......@@ -447,7 +447,7 @@ class Dir(ListView, ArticleMixin):
children = children.active()
children = children.select_related_common().order_by('article__current_revision__title')
return children
def get_context_data(self, **kwargs):
kwargs_article = ArticleMixin.get_context_data(self, **kwargs)
kwargs_listview = ListView.get_context_data(self, **kwargs)
......@@ -455,7 +455,7 @@ class Dir(ListView, ArticleMixin):
kwargs.update(kwargs_listview)
kwargs['filter_query'] = self.query
kwargs['filter_form'] = self.filter_form
# Update each child's ancestor cache so the lookups don't have
# to be repeated.
updated_children = kwargs[self.context_object_name]
......@@ -467,11 +467,11 @@ class Dir(ListView, ArticleMixin):
class SearchView(ListView):
template_name="wiki/search.html"
paginate_by = 25
context_object_name = "articles"
def dispatch(self, request, *args, **kwargs):
# Do not allow anonymous users to search if they cannot read content
if request.user.is_anonymous() and not settings.ANONYMOUS:
......@@ -482,7 +482,7 @@ class SearchView(ListView):
else:
self.query = None
return super(SearchView, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
if not self.query:
return models.Article.objects.get_empty_query_set()
......@@ -491,15 +491,15 @@ class SearchView(ListView):
if not permissions.can_moderate(models.URLPath.root().article, self.request.user):
articles = articles.active().can_read(self.request.user)
return articles
def get_context_data(self, **kwargs):
kwargs = ListView.get_context_data(self, **kwargs)
kwargs['search_form'] = self.search_form
kwargs['search_query'] = self.query
return kwargs
class Plugin(View):
def dispatch(self, request, path=None, slug=None, **kwargs):
kwargs['path'] = path
for plugin in plugin_registry.get_plugins().values():
......@@ -508,15 +508,15 @@ class Plugin(View):
raise Http404()
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
......@@ -532,9 +532,9 @@ class Settings(ArticleMixin, TemplateView):
# 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():
......@@ -552,24 +552,24 @@ class Settings(ArticleMixin, TemplateView):
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
......@@ -590,9 +590,9 @@ def change_revision(request, article, revision_id=None, urlpath=None):
return redirect('wiki:history', article_id=article.id)
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)
......@@ -604,7 +604,7 @@ class Preview(ArticleMixin, TemplateView):
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():
......@@ -612,87 +612,87 @@ class Preview(ArticleMixin, TemplateView):
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)
@json_view
def diff(request, revision_id, other_revision_id=None):
revision = get_object_or_404(models.ArticleRevision, id=revision_id)
if not other_revision_id:
other_revision = revision.previous_revision
baseText = other_revision.content if other_revision else ""
newText = revision.content
differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK)
diff = differ.compare(baseText.splitlines(1), newText.splitlines(1))
other_changes = []
if not other_revision or other_revision.title != revision.title:
other_changes.append((_(u'New title'), revision.title))
return dict(diff=list(diff), other_changes=other_changes)
# TODO: Throw in a class-based view
@get_article(can_write=True)
def merge(request, article, revision_id, urlpath=None, template_file="wiki/preview_inline.html", preview=False):
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 preview:
old_revision = article.current_revision
if revision.deleted:
c = RequestContext(request, {'error_msg': _(u'You cannot merge with a deleted revision'),
'article': article,
'urlpath': urlpath})
return render_to_response("wiki/error.html", context_instance=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 = (_(u'Merge between revision #%(r1)d and revision #%(r2)d') %
{'r1': revision.revision_number,
new_revision.automatic_log = (_(u'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, _(u'A new revision was created: Merge between revision #%(r1)d and revision #%(r2)d') %
messages.success(request, _(u'A new revision was created: Merge between revision #%(r1)d and revision #%(r2)d') %
{'r1': revision.revision_number,
'r2': old_revision.revision_number})
if urlpath:
return redirect('wiki:edit', path=urlpath.path)
else:
return redirect('wiki:edit', article_id=article.id)
c = RequestContext(request, {'article': article,
'title': article.current_revision.title,
'revision': None,
......@@ -702,31 +702,37 @@ def merge(request, article, revision_id, urlpath=None, template_file="wiki/previ
'content': content})
return render_to_response(template_file, context_instance=c)
# TODO: Should be a class-based view
def root_create(request):
try:
root = models.URLPath.root()
if not root.article:
class CreateRootView(FormView):
form_class = forms.CreateRootForm
template_name = 'wiki/create_root.html'
def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser:
return redirect(settings.LOGIN_URL + "?next=" + reverse("wiki:root_create"))
try:
root = models.URLPath.root()
except NoRootURL:
pass
else:
if root.article:
return redirect('wiki:get', path=root.path)
# TODO: This is too dangerous... let's say there is no root.article and we end up here,
# then it might cascade to delete a lot of things on an existing installation.... / benjaoming
root.delete()
raise NoRootURL
return redirect('wiki:get', path=root.path)
except NoRootURL:
pass
if not request.user.is_superuser:
return redirect(settings.LOGIN_URL + "?next=" + reverse("wiki:root_create"))
if request.method == 'POST':
create_form = forms.CreateRootForm(request.POST)
if create_form.is_valid():
models.URLPath.create_root(title=create_form.cleaned_data["title"],
content=create_form.cleaned_data["content"],
request=request)
return redirect("wiki:root")
else:
create_form = forms.CreateRootForm()
c = RequestContext(request, {'create_form': create_form,
'editor': editors.getEditor(),})
return render_to_response("wiki/create_root.html", context_instance=c)
return super(CreateRootView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
models.URLPath.create_root(
title=form.cleaned_data["title"],
content=form.cleaned_data["content"],
request=self.request
)
return redirect("wiki:root")
def get_context_data(self, **kwargs):
data = super(CreateRootView, self).get_context_data(**kwargs)
data['editor'] = editors.getEditor()
return data
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