M wiki/decorators.py => wiki/decorators.py +12 -2
@@ 84,13 84,20 @@ def get_article(func=None, can_read=True, can_write=False, # noqa
path = kwargs.pop('path', None)
article_id = kwargs.pop('article_id', None)
+ org = request.organization
urlpath = None
# fetch by urlpath.path
if path is not None:
try:
- urlpath = models.URLPath.get_by_path(path, select_related=True)
+ urlpath = models.URLPath.get_by_path(
+ path, org, select_related=True)
+ if urlpath.article.organization != org:
+ if urlpath.is_root_node():
+ raise NoRootURL
+ else:
+ raise models.URLPath.DoesNotExist
except NoRootURL:
return redirect('wiki:root_create')
except models.URLPath.DoesNotExist:
@@ 101,7 108,7 @@ def get_article(func=None, can_read=True, can_write=False, # noqa
path.split("/"),
))
path = "/".join(pathlist[:-1])
- parent = models.URLPath.get_by_path(path)
+ parent = models.URLPath.get_by_path(path, org)
return HttpResponseRedirect(
reverse(
"wiki:create", kwargs={'path': parent.path, }) +
@@ 153,6 160,9 @@ def get_article(func=None, can_read=True, can_write=False, # noqa
if article.current_revision and article.current_revision.deleted:
return redirect('wiki:deleted', article_id=article.id)
+ if article.organization != org:
+ return response_forbidden(request, article, urlpath)
+
if article.current_revision.locked and not_locked:
return response_forbidden(request, article, urlpath)
A wiki/migrations/0002_auto_20170118_0635.py => wiki/migrations/0002_auto_20170118_0635.py +21 -0
@@ 0,0 1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2017-01-18 14:35
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wiki', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='urlpath',
+ name='article',
+ field=models.ForeignKey(help_text='This field is automatically updated, but you need to populate it when creating a new URL path.', on_delete=django.db.models.deletion.CASCADE, to='wiki.Article', verbose_name='article'),
+ ),
+ ]
A wiki/migrations/0003_article_organization.py => wiki/migrations/0003_article_organization.py +22 -0
@@ 0,0 1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2017-01-18 14:38
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('grinch', '0016_team_organization'),
+ ('wiki', '0002_auto_20170118_0635'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='article',
+ name='organization',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='grinch.Organization'),
+ ),
+ ]
M wiki/models/article.py => wiki/models/article.py +4 -0
@@ 77,6 77,10 @@ class Article(models.Model):
default=True,
verbose_name=_('others write access'))
+ organization = models.ForeignKey(
+ 'grinch.Organization', related_name='articles',
+ null=True, blank=True)
+
# PERMISSIONS
def can_read(self, user):
return permissions.can_read(self, user)
M wiki/models/urlpath.py => wiki/models/urlpath.py +14 -7
@@ 8,6 8,7 @@ from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models, transaction
+from django.db.models import Q
from django.db.models.signals import post_save, pre_delete
# Django 1.6 transaction API, required for 1.8+
from django.utils.encoding import python_2_unicode_compatible
@@ 167,11 168,13 @@ class URLPath(MPTTModel):
log.exception("Exception deleting article subtree.")
@classmethod
- def root(cls):
+ def root(cls, organization=None):
site = Site.objects.get_current()
- root_nodes = list(
- cls.objects.root_nodes().filter(site=site).select_related_common()
- )
+ query = Q(site=site)
+ if organization:
+ query &= Q(article__organization=organization)
+ qs = cls.objects.root_nodes().filter(query).select_related_common()
+ root_nodes = list(qs)
# We fetch the nodes as a list and use len(), not count() because we need
# to get the result out anyway. This only takes one sql query
no_paths = len(root_nodes)
@@ 219,7 222,7 @@ class URLPath(MPTTModel):
super(URLPath, self).clean(*args, **kwargs)
@classmethod
- def get_by_path(cls, path, select_related=False):
+ def get_by_path(cls, path, organization, select_related=False):
"""
Strategy: Don't handle all kinds of weird cases. Be strict.
Accepts paths both starting with and without '/'
@@ 234,11 237,11 @@ class URLPath(MPTTModel):
# Root page requested
if not path:
- return cls.root()
+ return cls.root(organization)
slugs = path.split('/')
level = 1
- parent = cls.root()
+ parent = cls.root(organization)
for slug in slugs:
if settings.URL_CASE_SENSITIVE:
child = parent.get_children().select_related_common().get(
@@ 262,12 265,16 @@ class URLPath(MPTTModel):
if not site:
site = Site.objects.get_current()
root_nodes = cls.objects.root_nodes().filter(site=site)
+ if request:
+ root_nodes = root_nodes.filter(
+ article__organization=request.organization)
if not root_nodes:
# (get_or_create does not work for MPTT models??)
article = Article()
revision = ArticleRevision(title=title, **kwargs)
if request:
revision.set_from_request(request)
+ article.organization = request.organization
article.add_revision(revision, save=True)
article.save()
root = cls.objects.create(site=site, article=article)
M wiki/views/article.py => wiki/views/article.py +15 -8
@@ 82,9 82,11 @@ class Create(FormView, ArticleMixin):
def form_valid(self, form):
user = None
+ organization = None
ip_address = None
if not self.request.user.is_anonymous():
user = self.request.user
+ organization = self.request.organization
if settings.LOG_IPS_USERS:
ip_address = self.request.META.get('REMOTE_ADDR', None)
elif settings.LOG_IPS_ANONYMOUS:
@@ 105,6 107,7 @@ class Create(FormView, ArticleMixin):
'group_write': self.article.group_write,
'other_read': self.article.other_read,
'other_write': self.article.other_write,
+ 'organization': organization,
})
messages.success(
self.request,
@@ 883,22 886,26 @@ class CreateRootView(FormView):
template_name = 'wiki/create_root.html'
def dispatch(self, request, *args, **kwargs):
-
if not request.user.is_superuser:
return redirect("wiki:root_missing")
+ org = request.organization
try:
- root = models.URLPath.root()
+ root = models.URLPath.root(org)
+ if request.method == "GET" and root.article.organization != org:
+ raise NoRootURL
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()
+ if root.article.organization == org:
+ return redirect('wiki:get', path=root.path)
+ else:
+ # 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()
return super(CreateRootView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):