~netlandish/django-wiki

650ceee4940d0b84e3b8325be065dc75af4bf4bd — Benjamin Bach 6 years ago c0bbf03 + 5df639b
Merge pull request #792 from rsalmaso/py3

Python3 only support
84 files changed, 80 insertions(+), 200 deletions(-)

M src/wiki/__init__.py
M src/wiki/admin.py
M src/wiki/apps.py
M src/wiki/conf/__init__.py
M src/wiki/conf/settings.py
M src/wiki/core/plugins/base.py
M src/wiki/core/plugins/loader.py
M src/wiki/core/plugins/registry.py
M src/wiki/core/utils.py
M src/wiki/decorators.py
M src/wiki/editors/markitup.py
M src/wiki/forms.py
M src/wiki/migrations/0001_initial.py
M src/wiki/migrations/0002_urlpath_moved_to.py
M src/wiki/models/__init__.py
M src/wiki/models/article.py
M src/wiki/models/pluginbase.py
M src/wiki/models/urlpath.py
M src/wiki/plugins/attachments/apps.py
M src/wiki/plugins/attachments/forms.py
M src/wiki/plugins/attachments/migrations/0001_initial.py
M src/wiki/plugins/attachments/migrations/0002_auto_20151118_1816.py
M src/wiki/plugins/attachments/models.py
M src/wiki/plugins/attachments/views.py
M src/wiki/plugins/attachments/wiki_plugin.py
M src/wiki/plugins/globalhistory/apps.py
M src/wiki/plugins/globalhistory/models.py
M src/wiki/plugins/help/apps.py
M src/wiki/plugins/help/wiki_plugin.py
M src/wiki/plugins/images/apps.py
M src/wiki/plugins/images/forms.py
M src/wiki/plugins/images/markdown_extensions.py
M src/wiki/plugins/images/migrations/0001_initial.py
M src/wiki/plugins/images/migrations/0002_auto_20151118_1811.py
M src/wiki/plugins/images/models.py
M src/wiki/plugins/images/views.py
M src/wiki/plugins/images/wiki_plugin.py
M src/wiki/plugins/links/apps.py
M src/wiki/plugins/links/mdx/urlize.py
M src/wiki/plugins/links/wiki_plugin.py
M src/wiki/plugins/macros/apps.py
M src/wiki/plugins/macros/mdx/macro.py
M src/wiki/plugins/macros/wiki_plugin.py
M src/wiki/plugins/notifications/apps.py
M src/wiki/plugins/notifications/forms.py
M src/wiki/plugins/notifications/migrations/0001_initial.py
M src/wiki/plugins/notifications/migrations/0002_auto_20151118_1811.py
M src/wiki/plugins/notifications/models.py
M src/wiki/plugins/notifications/util.py
M src/wiki/plugins/notifications/views.py
M src/wiki/templatetags/wiki_tags.py
M src/wiki/urls.py
M src/wiki/views/accounts.py
M src/wiki/views/article.py
M testproject/manage.py
M testproject/testproject/settings/__init__.py
M testproject/testproject/settings/base.py
M testproject/testproject/settings/codehilite.py
M testproject/testproject/settings/customauthuser.py
M testproject/testproject/settings/haystack.py
M testproject/testproject/settings/sendfile.py
M testproject/testproject/urls.py
M testproject/testproject/wsgi.py
M tests/base.py
M tests/core/test_accounts.py
M tests/core/test_basic.py
M tests/core/test_commands.py
M tests/core/test_managers.py
M tests/core/test_markdown.py
M tests/core/test_models.py
M tests/core/test_template_filters.py
M tests/core/test_template_tags.py
M tests/core/test_urls.py
M tests/core/test_views.py
M tests/plugins/attachments/test_commands.py
M tests/plugins/attachments/test_views.py
M tests/plugins/globalhistory/__init__.py
M tests/plugins/globalhistory/test_globalhistory.py
M tests/plugins/images/__init__.py
M tests/plugins/images/test_views.py
M tests/plugins/links/test_links.py
M tests/plugins/macros/test_toc.py
M tests/testdata/models.py
M tests/testdata/urls.py
M src/wiki/__init__.py => src/wiki/__init__.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
# This package and all its sub-packages are part of django-wiki,
# except where otherwise stated.
#

M src/wiki/admin.py => src/wiki/admin.py +1 -1
@@ 1,7 1,7 @@
from django import forms
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from mptt.admin import MPTTModelAdmin

from . import editors, models

M src/wiki/apps.py => src/wiki/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class WikiConfig(AppConfig):

M src/wiki/conf/__init__.py => src/wiki/conf/__init__.py +0 -1
@@ 1,1 0,0 @@
# -*- coding: utf-8 -*-

M src/wiki/conf/settings.py => src/wiki/conf/settings.py +1 -1
@@ 5,7 5,7 @@ from django.conf import settings as django_settings
from django.contrib.messages import constants as messages
from django.core.files.storage import default_storage
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _

#: Should urls be case sensitive?
URL_CASE_SENSITIVE = getattr(django_settings, 'WIKI_URL_CASE_SENSITIVE', False)

M src/wiki/core/plugins/base.py => src/wiki/core/plugins/base.py +1 -1
@@ 1,5 1,5 @@
from django import forms
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _


"""Base classes for different plugin objects.

M src/wiki/core/plugins/loader.py => src/wiki/core/plugins/loader.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.utils.module_loading import autodiscover_modules



M src/wiki/core/plugins/registry.py => src/wiki/core/plugins/registry.py +1 -4
@@ 1,8 1,5 @@
# -*- coding: utf-8 -*-
from importlib import import_module

from django.utils.six import string_types

_cache = {}
_settings_forms = []
_markdown_extensions = []


@@ 24,7 21,7 @@ def register(PluginClass):

    settings_form = getattr(PluginClass, 'settings_form', None)
    if settings_form:
        if isinstance(settings_form, string_types):
        if isinstance(settings_form, str):
            klassname = settings_form.split(".")[-1]
            modulename = ".".join(settings_form.split(".")[:-1])
            form_module = import_module(modulename)

M src/wiki/core/utils.py => src/wiki/core/utils.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from importlib import import_module

from django.http.response import JsonResponse

M src/wiki/decorators.py => src/wiki/decorators.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from functools import wraps

from django.core.urlresolvers import reverse

M src/wiki/editors/markitup.py => src/wiki/editors/markitup.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8
from django import forms
from django.forms.utils import flatatt
from django.utils.encoding import force_text

M src/wiki/forms.py => src/wiki/forms.py +15 -16
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import random
import string
from datetime import timedelta


@@ 19,8 18,8 @@ from django.utils.encoding import force_text
from django.utils.html import conditional_escape, escape
from django.utils.safestring import mark_safe
from django.utils.translation import pgettext_lazy
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _

from wiki import models
from wiki.conf import settings


@@ 59,10 58,10 @@ class WikiSlugField(forms.SlugField):
def _clean_slug(slug, urlpath):
    if slug.startswith("_"):
        raise forms.ValidationError(
            ugettext('A slug may not begin with an underscore.'))
            gettext('A slug may not begin with an underscore.'))
    if slug == 'admin':
        raise forms.ValidationError(
            ugettext("'admin' is not a permitted slug name."))
            gettext("'admin' is not a permitted slug name."))

    if settings.URL_CASE_SENSITIVE:
        already_existing_slug = models.URLPath.objects.filter(


@@ 77,11 76,11 @@ def _clean_slug(slug, urlpath):
        already_urlpath = already_existing_slug[0]
        if already_urlpath.article and already_urlpath.article.current_revision.deleted:
            raise forms.ValidationError(
                ugettext('A deleted article with slug "%s" already exists.') %
                gettext('A deleted article with slug "%s" already exists.') %
                already_urlpath.slug)
        else:
            raise forms.ValidationError(
                ugettext('A slug named "%s" already exists.') %
                gettext('A slug named "%s" already exists.') %
                already_urlpath.slug)

    if settings.CHECK_SLUG_URL_AVAILABLE:


@@ 90,7 89,7 @@ def _clean_slug(slug, urlpath):
            match = resolve(urlpath.path + '/' + slug + '/')
            if match.app_name != 'wiki':
                raise forms.ValidationError(
                    ugettext('This slug conflicts with an existing URL.'))
                    gettext('This slug conflicts with an existing URL.'))
        except Resolver404:
            pass



@@ 124,7 123,7 @@ class SpamProtectionMixin(object):

        if not (user or ip_address):
            raise forms.ValidationError(
                ugettext(
                gettext(
                    'Spam protection failed to find both a logged in user and an IP address.'))

        def check_interval(from_time, max_count, interval_name):


@@ 140,7 139,7 @@ class SpamProtectionMixin(object):
            revisions = revisions.count()
            if revisions >= max_count:
                raise forms.ValidationError(
                    ugettext('Spam protection: You are only allowed to create or edit %(revisions)d article(s) per %(interval_name)s.') % {
                    gettext('Spam protection: You are only allowed to create or edit %(revisions)d article(s) per %(interval_name)s.') % {
                        'revisions': max_count,
                        'interval_name': interval_name,
                    })


@@ 267,7 266,7 @@ class EditForm(forms.Form, SpamProtectionMixin):
        title = self.cleaned_data.get('title', None)
        title = (title or "").strip()
        if not title:
            raise forms.ValidationError(ugettext('Article is missing title or has an invalid title'))
            raise forms.ValidationError(gettext('Article is missing title or has an invalid title'))
        return title

    def clean(self):


@@ 280,11 279,11 @@ class EditForm(forms.Form, SpamProtectionMixin):
            return cd
        if not str(self.initial_revision.id) == str(self.presumed_revision):
            raise forms.ValidationError(
                ugettext(
                gettext(
                    'While you were editing, someone else changed the revision. Your contents have been automatically merged with the new contents. Please review the text below.'))
        if ('title' in cd) and cd['title'] == self.initial_revision.title and cd[
                'content'] == self.initial_revision.content:
            raise forms.ValidationError(ugettext('No changes made. Nothing to save.'))
            raise forms.ValidationError(gettext('No changes made. Nothing to save.'))
        self.check_spam()
        return cd



@@ 422,10 421,10 @@ class DeleteForm(forms.Form):
    def clean(self):
        cd = super().clean()
        if not cd['confirm']:
            raise forms.ValidationError(ugettext('You are not sure enough!'))
            raise forms.ValidationError(gettext('You are not sure enough!'))
        if cd['revision'] != self.article.current_revision:
            raise forms.ValidationError(
                ugettext(
                gettext(
                    'While you tried to delete this article, it was modified. TAKE CARE!'))
        return cd



@@ 532,7 531,7 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm):
                    user = User.objects.get(**kwargs)
                except User.DoesNotExist:
                    raise forms.ValidationError(
                        ugettext('No user with that username'))
                        gettext('No user with that username'))
            else:
                user = None
        else:

M src/wiki/migrations/0001_initial.py => src/wiki/migrations/0001_initial.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import django.db.models.deletion
import mptt.fields
from django.conf import settings

M src/wiki/migrations/0002_urlpath_moved_to.py => src/wiki/migrations/0002_urlpath_moved_to.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-06-06 23:18
import django.db.models.deletion
import mptt.fields

M src/wiki/models/__init__.py => src/wiki/models/__init__.py +2 -4
@@ 1,8 1,6 @@
# -*- coding: utf-8 -*-
from django.apps import apps
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.six import string_types, text_type

# TODO: Don't use wildcards
from .article import *  # noqa


@@ 77,7 75,7 @@ def reverse(*args, **kwargs):
    return the result of calling reverse._transform_url(reversed_url)
    for every url in the wiki namespace.
    """
    if isinstance(args[0], string_types) and args[0].startswith('wiki:'):
    if isinstance(args[0], str) and args[0].startswith('wiki:'):
        url_kwargs = kwargs.get('kwargs', {})
        path = url_kwargs.get('path', False)
        # If a path is supplied then discard the article_id


@@ 96,7 94,7 @@ def reverse(*args, **kwargs):


# Now we redefine reverse method
reverse_lazy = lazy(reverse, text_type)
reverse_lazy = lazy(reverse, str)
urlresolvers.reverse = reverse
urlresolvers.reverse_lazy = reverse_lazy


M src/wiki/models/article.py => src/wiki/models/article.py +1 -6
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache


@@ 7,9 6,8 @@ from django.db import models
from django.db.models.fields import GenericIPAddressField as IPAddressField
from django.db.models.signals import post_save, pre_delete, pre_save
from django.utils import translation
from django.utils.encoding import python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel
from wiki import managers
from wiki.conf import settings


@@ 18,7 16,6 @@ from wiki.core.markdown import article_markdown
from wiki.decorators import disable_signal_for_loaddata


@python_2_unicode_compatible
class Article(models.Model):

    objects = managers.ArticleManager()


@@ 248,7 245,6 @@ class Article(models.Model):
            return reverse('wiki:get', kwargs={'article_id': self.id})


@python_2_unicode_compatible
class ArticleForObject(models.Model):

    objects = managers.ArticleFkManager()


@@ 343,7 339,6 @@ class BaseRevisionMixin(models.Model):
        abstract = True


@python_2_unicode_compatible
class ArticleRevision(BaseRevisionMixin, models.Model):

    """This is where main revision data is stored. To make it easier to

M src/wiki/models/pluginbase.py => src/wiki/models/pluginbase.py +1 -2
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
"""
There are three kinds of plugin base models:



@@ 21,7 20,7 @@ There are three kinds of plugin base models:
"""
from django.db import models
from django.db.models import signals
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from wiki.decorators import disable_signal_for_loaddata

from .article import ArticleRevision, BaseRevisionMixin

M src/wiki/models/urlpath.py => src/wiki/models/urlpath.py +3 -6
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import logging
import warnings



@@ 10,9 9,8 @@ from django.core.urlresolvers import reverse
from django.db import models, transaction
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
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel
from wiki import managers


@@ 24,7 22,6 @@ from wiki.models.article import Article, ArticleForObject, ArticleRevision
log = logging.getLogger(__name__)


@python_2_unicode_compatible
class URLPath(MPTTModel):

    """


@@ 177,7 174,7 @@ class URLPath(MPTTModel):

    def __str__(self):
        path = self.path
        return path if path else ugettext("(root)")
        return path if path else gettext("(root)")

    def delete(self, *args, **kwargs):
        assert not (self.parent and self.get_children()

M src/wiki/plugins/attachments/apps.py => src/wiki/plugins/attachments/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class AttachmentsConfig(AppConfig):

M src/wiki/plugins/attachments/forms.py => src/wiki/plugins/attachments/forms.py +5 -6
@@ 1,11 1,10 @@
# -*- coding: utf-8 -*-
import tempfile
import zipfile

from django import forms
from django.core.files.uploadedfile import File
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from wiki.core.permissions import can_moderate
from wiki.plugins.attachments import models
from wiki.plugins.attachments.models import IllegalFileExtension


@@ 102,7 101,7 @@ class AttachmentArchiveForm(AttachmentForm):
                    except IllegalFileExtension as e:
                        raise forms.ValidationError(e)
            except zipfile.BadZipfile:
                raise forms.ValidationError(ugettext("Not a zip file"))
                raise forms.ValidationError(gettext("Not a zip file"))
        else:
            return super().clean_file()
        return uploaded_file


@@ 111,7 110,7 @@ class AttachmentArchiveForm(AttachmentForm):
        super().clean()
        if not can_moderate(self.article, self.request.user):
            raise forms.ValidationError(
                ugettext("User not allowed to moderate this article"))
                gettext("User not allowed to moderate this article"))
        return self.cleaned_data

    def save(self, *args, **kwargs):


@@ 162,7 161,7 @@ class DeleteForm(forms.Form):

    def clean_confirm(self):
        if not self.cleaned_data['confirm']:
            raise forms.ValidationError(ugettext('You are not sure enough!'))
            raise forms.ValidationError(gettext('You are not sure enough!'))
        return True



M src/wiki/plugins/attachments/migrations/0001_initial.py => src/wiki/plugins/attachments/migrations/0001_initial.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

M src/wiki/plugins/attachments/migrations/0002_auto_20151118_1816.py => src/wiki/plugins/attachments/migrations/0002_auto_20151118_1816.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models



M src/wiki/plugins/attachments/models.py => src/wiki/plugins/attachments/models.py +4 -8
@@ 1,12 1,10 @@
# -*- coding: utf-8 -*-
import os

from django.conf import settings as django_settings
from django.db import models
from django.db.models import signals
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from wiki import managers
from wiki.decorators import disable_signal_for_loaddata
from wiki.models.article import BaseRevisionMixin


@@ 21,7 19,6 @@ class IllegalFileExtension(Exception):
    pass


@python_2_unicode_compatible
class Attachment(ReusablePlugin):

    objects = managers.ArticleFkManager()


@@ 68,12 65,12 @@ def extension_allowed(filename):
    except IndexError:
        # No extension
        raise IllegalFileExtension(
            ugettext("No file extension found in filename. That's not okay!"))
            gettext("No file extension found in filename. That's not okay!"))
    if not extension.lower() in map(
            lambda x: x.lower(),
            settings.FILE_EXTENSIONS):
        raise IllegalFileExtension(
            ugettext(
            gettext(
                "The following filename is illegal: {filename:s}. Extension "
                "has to be one of {extensions:s}"
            ).format(


@@ 115,7 112,6 @@ def upload_path(instance, filename):
    return os.path.join(upload_path, filename)


@python_2_unicode_compatible
class AttachmentRevision(BaseRevisionMixin, models.Model):

    attachment = models.ForeignKey('Attachment', on_delete=models.CASCADE)

M src/wiki/plugins/attachments/views.py => src/wiki/plugins/attachments/views.py +1 -2
@@ 1,11 1,10 @@
# -*- coding: utf-8 -*-
from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.views.generic.base import TemplateView, View
from django.views.generic.edit import FormView
from django.views.generic.list import ListView

M src/wiki/plugins/attachments/wiki_plugin.py => src/wiki/plugins/attachments/wiki_plugin.py +1 -2
@@ 1,6 1,5 @@
# -*- coding: utf-8 -*-
from django.conf.urls import include, url
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin
from wiki.plugins.attachments import models, settings, views

M src/wiki/plugins/globalhistory/apps.py => src/wiki/plugins/globalhistory/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class GlobalHistoryConfig(AppConfig):

M src/wiki/plugins/globalhistory/models.py => src/wiki/plugins/globalhistory/models.py +0 -1
@@ 1,1 0,0 @@
# -*- coding: utf-8 -*-

M src/wiki/plugins/help/apps.py => src/wiki/plugins/help/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class HelpConfig(AppConfig):

M src/wiki/plugins/help/wiki_plugin.py => src/wiki/plugins/help/wiki_plugin.py +1 -1
@@ 1,4 1,4 @@
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin


M src/wiki/plugins/images/apps.py => src/wiki/plugins/images/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class ImagesConfig(AppConfig):

M src/wiki/plugins/images/forms.py => src/wiki/plugins/images/forms.py +4 -4
@@ 1,6 1,6 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from wiki.core.plugins.base import PluginSidebarFormMixin
from wiki.plugins.images import models



@@ 14,7 14,7 @@ class SidebarForm(PluginSidebarFormMixin):
        self.fields['image'].required = True

    def get_usermessage(self):
        return ugettext(
        return gettext(
            "New image %s was successfully uploaded. You can use it by selecting it from the list of available images.") % self.instance.get_filename()

    def save(self, *args, **kwargs):


@@ 64,5 64,5 @@ class PurgeForm(forms.Form):
    def clean_confirm(self):
        confirm = self.cleaned_data['confirm']
        if not confirm:
            raise forms.ValidationError(ugettext('You are not sure enough!'))
            raise forms.ValidationError(gettext('You are not sure enough!'))
        return confirm

M src/wiki/plugins/images/markdown_extensions.py => src/wiki/plugins/images/markdown_extensions.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import re

import markdown

M src/wiki/plugins/images/migrations/0001_initial.py => src/wiki/plugins/images/migrations/0001_initial.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import wiki.plugins.images.models
from django.db import migrations, models


M src/wiki/plugins/images/migrations/0002_auto_20151118_1811.py => src/wiki/plugins/images/migrations/0002_auto_20151118_1811.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models



M src/wiki/plugins/images/models.py => src/wiki/plugins/images/models.py +5 -8
@@ 5,9 5,8 @@ from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models import signals
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from wiki.models.pluginbase import RevisionPlugin, RevisionPluginRevision

from . import settings


@@ 29,7 28,6 @@ def upload_path(instance, filename):
    return os.path.join(upload_path, filename)


@python_2_unicode_compatible
class Image(RevisionPlugin):

    # The plugin system is so awesome that the inheritor doesn't need to do


@@ 50,12 48,11 @@ class Image(RevisionPlugin):

    def __str__(self):
        if self.current_revision:
            return ugettext('Image: %s') % self.current_revision.imagerevision.get_filename()
            return gettext('Image: %s') % self.current_revision.imagerevision.get_filename()
        else:
            return ugettext('Current revision not set!!')
            return gettext('Current revision not set!!')


@python_2_unicode_compatible
class ImageRevision(RevisionPluginRevision):

    image = models.ImageField(upload_to=upload_path,


@@ 111,7 108,7 @@ class ImageRevision(RevisionPluginRevision):
        ordering = ('-created',)

    def __str__(self):
        return ugettext('Image Revision: %d') % self.revision_number
        return gettext('Image Revision: %d') % self.revision_number


def on_image_revision_delete(instance, *args, **kwargs):

M src/wiki/plugins/images/views.py => src/wiki/plugins/images/views.py +1 -1
@@ 4,7 4,7 @@ from django.contrib import messages
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django.views.generic.list import ListView

M src/wiki/plugins/images/wiki_plugin.py => src/wiki/plugins/images/wiki_plugin.py +1 -2
@@ 1,6 1,5 @@
# -*- coding: utf-8 -*-
from django.conf.urls import url
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin
from wiki.plugins.images import forms, models, settings, views

M src/wiki/plugins/links/apps.py => src/wiki/plugins/links/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class LinksConfig(AppConfig):

M src/wiki/plugins/links/mdx/urlize.py => src/wiki/plugins/links/mdx/urlize.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import re

import markdown

M src/wiki/plugins/links/wiki_plugin.py => src/wiki/plugins/links/wiki_plugin.py +1 -2
@@ 1,7 1,6 @@
# -*- coding: utf-8 -*-
from django.conf.urls import url
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin
from wiki.plugins.links import settings, views

M src/wiki/plugins/macros/apps.py => src/wiki/plugins/macros/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class MacrosConfig(AppConfig):

M src/wiki/plugins/macros/mdx/macro.py => src/wiki/plugins/macros/mdx/macro.py +2 -4
@@ 1,10 1,8 @@
# -*- coding: utf-8 -*-
import re

import markdown
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.utils.six import string_types
from django.utils.translation import gettext as _
from wiki.plugins.macros import settings

# See:


@@ 52,7 50,7 @@ class MacroPreprocessor(markdown.preprocessors.Preprocessor):
                            value = kwarg.group('value')
                            if value is None:
                                value = True
                            if isinstance(value, string_types):
                            if isinstance(value, str):
                                # If value is enclosed with ': Remove and
                                # remove escape sequences
                                if value.startswith("'") and len(value) > 2:

M src/wiki/plugins/macros/wiki_plugin.py => src/wiki/plugins/macros/wiki_plugin.py +1 -1
@@ 1,4 1,4 @@
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin
from wiki.plugins.macros import settings

M src/wiki/plugins/notifications/apps.py => src/wiki/plugins/notifications/apps.py +1 -1
@@ 1,5 1,5 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _


class NotificationsConfig(AppConfig):

M src/wiki/plugins/notifications/forms.py => src/wiki/plugins/notifications/forms.py +9 -9
@@ 2,8 2,8 @@ from django import forms
from django.contrib.contenttypes.models import ContentType
from django.forms.models import BaseModelFormSet, modelformset_factory
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from django_nyt.models import NotificationType, Settings, Subscription
from wiki.core.plugins.base import PluginSettingsFormMixin
from wiki.plugins.notifications import models


@@ 13,7 13,7 @@ from wiki.plugins.notifications.settings import ARTICLE_EDIT
class SettingsModelChoiceField(forms.ModelChoiceField):

    def label_from_instance(self, obj):
        return ugettext(
        return gettext(
            "Receive notifications %(interval)s"
        ) % {
            'interval': obj.get_interval_display()


@@ 24,7 24,7 @@ class ArticleSubscriptionModelMultipleChoiceField(
        forms.ModelMultipleChoiceField):

    def label_from_instance(self, obj):
        return ugettext("%(title)s - %(url)s") % {
        return gettext("%(title)s - %(url)s") % {
            'title': obj.article.current_revision.title,
            'url': obj.article.get_absolute_url()
        }


@@ 41,17 41,17 @@ class SettingsModelForm(forms.ModelForm):
            self.fields['delete_subscriptions'] = ArticleSubscriptionModelMultipleChoiceField(
                models.ArticleSubscription.objects.filter(
                    subscription__settings=instance),
                label=ugettext("Remove subscriptions"),
                label=gettext("Remove subscriptions"),
                required=False,
                help_text=ugettext("Select article subscriptions to remove from notifications"),
                help_text=gettext("Select article subscriptions to remove from notifications"),
                initial=models.ArticleSubscription.objects.none(),
            )
            self.fields['email'] = forms.TypedChoiceField(
                label=_("Email digests"),
                choices=(
                    (0, ugettext('Unchanged (selected on each article)')),
                    (1, ugettext('No emails')),
                    (2, ugettext('Email on any change')),
                    (0, gettext('Unchanged (selected on each article)')),
                    (1, gettext('No emails')),
                    (2, gettext('Email on any change')),
                ),
                coerce=lambda x: int(x) if x is not None else None,
                widget=forms.RadioSelect(),

M src/wiki/plugins/notifications/migrations/0001_initial.py => src/wiki/plugins/notifications/migrations/0001_initial.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models



M src/wiki/plugins/notifications/migrations/0002_auto_20151118_1811.py => src/wiki/plugins/notifications/migrations/0002_auto_20151118_1811.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models



M src/wiki/plugins/notifications/models.py => src/wiki/plugins/notifications/models.py +1 -4
@@ 1,9 1,7 @@
# -*- coding: utf-8 -*-
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import signals
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django_nyt.models import Subscription
from django_nyt.utils import notify
from wiki import models as wiki_models


@@ 14,7 12,6 @@ from wiki.plugins.notifications import settings
from wiki.plugins.notifications.util import get_title


@python_2_unicode_compatible
class ArticleSubscription(ArticlePlugin):

    subscription = models.OneToOneField(Subscription, on_delete=models.CASCADE)

M src/wiki/plugins/notifications/util.py => src/wiki/plugins/notifications/util.py +1 -1
@@ 1,4 1,4 @@
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _


def get_title(article):

M src/wiki/plugins/notifications/views.py => src/wiki/plugins/notifications/views.py +1 -1
@@ 2,7 2,7 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.views.generic.edit import FormView

from . import forms, models

M src/wiki/templatetags/wiki_tags.py => src/wiki/templatetags/wiki_tags.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import re

from django.apps import apps

M src/wiki/urls.py => src/wiki/urls.py +0 -1
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
from django.conf.urls import include, url
from wiki.conf import settings
from wiki.core.plugins import registry

M src/wiki/views/accounts.py => src/wiki/views/accounts.py +1 -2
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
"""Here is a very basic handling of accounts.
If you have your own account handling, don't worry,
just switch off account handling in


@@ 17,7 16,7 @@ from django.contrib.auth import logout as auth_logout
from django.contrib.auth.forms import AuthenticationForm
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.views.generic.base import View
from django.views.generic.edit import CreateView, FormView, UpdateView
from wiki import forms

M src/wiki/views/article.py => src/wiki/views/article.py +3 -4
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
import difflib
import logging



@@ 10,8 9,8 @@ from django.db.models import Q
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from django.views.generic.base import RedirectView, TemplateView, View
from django.views.generic.edit import FormView
from django.views.generic.list import ListView


@@ 489,7 488,7 @@ class Move(ArticleMixin, FormView):

            messages.success(
                self.request,
                ungettext(
                ngettext(
                    "Article successfully moved! Created {n} redirect.",
                    "Article successfully moved! Created {n} redirects.",
                    len(descendants)

M testproject/manage.py => testproject/manage.py +0 -2
@@ 1,6 1,4 @@
#!/usr/bin/env python
from __future__ import unicode_literals

import os
import sys


M testproject/testproject/settings/__init__.py => testproject/testproject/settings/__init__.py +0 -3
@@ 1,6 1,3 @@
from __future__ import unicode_literals, absolute_import


try:
    from .local import *
except ImportError:

M testproject/testproject/settings/base.py => testproject/testproject/settings/base.py +0 -3
@@ 1,4 1,3 @@
# -*- coding: utf-8 -*-
"""
Generated by 'django-admin startproject' using Django 1.9.5.



@@ 9,8 8,6 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""

from __future__ import unicode_literals

import os

from django.core.urlresolvers import reverse_lazy

M testproject/testproject/settings/codehilite.py => testproject/testproject/settings/codehilite.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from testproject.settings import *
from testproject.settings.local import *


M testproject/testproject/settings/customauthuser.py => testproject/testproject/settings/customauthuser.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

import os  # noqa @UnusedImport

from .base import *  # noqa @UnusedWildImport

M testproject/testproject/settings/haystack.py => testproject/testproject/settings/haystack.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from .base import *  # noqa @UnusedWildImport



M testproject/testproject/settings/sendfile.py => testproject/testproject/settings/sendfile.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from .base import *  # noqa @UnusedWildImport



M testproject/testproject/urls.py => testproject/testproject/urls.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin

M testproject/testproject/wsgi.py => testproject/testproject/wsgi.py +0 -2
@@ 13,8 13,6 @@ middleware here, or combine a Django application with an application of another
framework.

"""
from __future__ import unicode_literals

import os
import sys


M tests/base.py => tests/base.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

import os
import unittest


M tests/core/test_accounts.py => tests/core/test_accounts.py +0 -3
@@ 1,6 1,3 @@
from __future__ import print_function, unicode_literals


from django.conf import settings as django_settings
from django.contrib.auth import authenticate
from django.shortcuts import resolve_url

M tests/core/test_basic.py => tests/core/test_basic.py +0 -2
@@ 1,5 1,3 @@
from __future__ import print_function, unicode_literals

from django.test import TestCase

from wiki.conf import settings as wiki_settings

M tests/core/test_commands.py => tests/core/test_commands.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

import os
import sys
import tempfile

M tests/core/test_managers.py => tests/core/test_managers.py +0 -2
@@ 4,8 4,6 @@ because the pattern of building them is different from Django
1.5 to 1.6 to 1.7 so there will be 3 patterns in play at the
same time.
"""
from __future__ import print_function, unicode_literals

from wiki.models import Article, URLPath
from wiki.plugins.attachments.models import Attachment


M tests/core/test_markdown.py => tests/core/test_markdown.py +0 -3
@@ 1,6 1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import markdown
from django.test import TestCase
from mock import patch

M tests/core/test_models.py => tests/core/test_models.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from django.apps import apps
from django.conf.urls import url
from django.contrib.auth import get_user_model

M tests/core/test_template_filters.py => tests/core/test_template_filters.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from django.contrib.auth import get_user_model
from wiki.models import Article, ArticleRevision
from wiki.templatetags.wiki_tags import (can_delete, can_moderate, can_read,

M tests/core/test_template_tags.py => tests/core/test_template_tags.py +3 -7
@@ 1,13 1,9 @@
"""
Almost all test cases covers both tag calling and template using.
"""

from __future__ import print_function, unicode_literals

from django.conf import settings as django_settings
from django.contrib.contenttypes.models import ContentType
from django.http import HttpRequest
from django.utils.six import assertCountEqual

from wiki.conf import settings
from wiki.forms import CreateRootForm


@@ 171,7 167,7 @@ class WikiRenderTest(TemplateTestCase):

        output = wiki_render({}, article)

        assertCountEqual(self, self.keys, output)
        self.assertCountEqual(self.keys, output)

        self.assertEqual(output['article'], article)
        self.assertIsNone(output['content'])


@@ 210,7 206,7 @@ class WikiRenderTest(TemplateTestCase):
        registry._cache = {'spam': 'eggs'}

        output = wiki_render({}, article, preview_content=content)
        assertCountEqual(self, self.keys, output)
        self.assertCountEqual(self.keys, output)
        self.assertEqual(output['article'], article)
        self.assertMultiLineEqual(output['content'], expected_markdown)
        self.assertIs(output['preview'], True)


@@ 239,7 235,7 @@ class WikiRenderTest(TemplateTestCase):

        output = wiki_render({}, article, preview_content=content)

        assertCountEqual(self, self.keys, output)
        self.assertCountEqual(self.keys, output)

        self.assertEqual(output['article'], article)


M tests/core/test_urls.py => tests/core/test_urls.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from django.conf.urls import url
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site

M tests/core/test_views.py => tests/core/test_views.py +0 -2
@@ 1,5 1,3 @@
from __future__ import print_function, unicode_literals

import pprint

from django.contrib.auth import authenticate

M tests/plugins/attachments/test_commands.py => tests/plugins/attachments/test_commands.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

import os
import tempfile


M tests/plugins/attachments/test_views.py => tests/plugins/attachments/test_views.py +0 -2
@@ 1,5 1,3 @@
from __future__ import print_function, unicode_literals

from io import BytesIO

from django.core.files.uploadedfile import InMemoryUploadedFile

M tests/plugins/globalhistory/__init__.py => tests/plugins/globalhistory/__init__.py +0 -1
@@ 1,1 0,0 @@
from __future__ import absolute_import

M tests/plugins/globalhistory/test_globalhistory.py => tests/plugins/globalhistory/test_globalhistory.py +0 -2
@@ 1,5 1,3 @@
from __future__ import print_function, unicode_literals

from django.core.urlresolvers import reverse
from wiki.models import URLPath


M tests/plugins/images/__init__.py => tests/plugins/images/__init__.py +0 -1
@@ 1,1 0,0 @@
from __future__ import absolute_import

M tests/plugins/images/test_views.py => tests/plugins/images/test_views.py +0 -2
@@ 1,5 1,3 @@
from __future__ import print_function, unicode_literals

import base64
from io import BytesIO


M tests/plugins/links/test_links.py => tests/plugins/links/test_links.py +0 -3
@@ 1,6 1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import markdown
from django.core.urlresolvers import reverse_lazy
from django.test import TestCase

M tests/plugins/macros/test_toc.py => tests/plugins/macros/test_toc.py +0 -4
@@ 1,7 1,3 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals

import markdown
from django.test import TestCase
from wiki.plugins.macros.mdx.toc import WikiTocExtension

M tests/testdata/models.py => tests/testdata/models.py +0 -2
@@ 1,5 1,3 @@
from __future__ import absolute_import

from django.contrib.auth.models import AbstractUser
from django.db import models


M tests/testdata/urls.py => tests/testdata/urls.py +0 -2
@@ 1,5 1,3 @@
from __future__ import unicode_literals

from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin