~netlandish/django-wiki

1b92f9284935dc085573b00e809f0ecc36e4b6ca — Gustavo Andres Morero 7 years ago c01e5ac
adding Article.organization and changes to handle articles by organization.
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):