~netlandish/django-wiki

b44dbe932a73e741b821de00c85d236c71b9c8ba — Benjamin Bach 6 years ago 8c585d8 + 4026313
Merge pull request #827 from rsalmaso/sites

Added WikiSite and deprecate wiki.urls.get_pattern
M docs/customization.rst => docs/customization.rst +31 -0
@@ 41,4 41,35 @@ wiki. Add the following as ``wiki/base.html`` somewhere in your
   </ul>
   {% endblock %}

Site
----

You can override default django-wiki ``wiki.sites.site`` urls/views site implementation
with your own: override by setting the :attr:`~.WikiConfig.default_site` attribute
of a custom ``AppConfig`` to the dotted import path of either a ``WikiSite`` subclass
or a callable that returns a site instance.

.. code-block:: python
    # myproject/sites.py

    from wiki.sites import WikiSite

    class MyWikiSite(admin.WikiSite):
        ...

.. code-block:: python
    # myproject/apps.py

    from wiki.apps import WikiConfig

    class MyWikiConfig(WikiConfig):
        default_site = 'myproject.sites.MyWikiSite'

.. code-block:: python
    # myproject/settings.py

    INSTALLED_APPS = [
        ...
        'myproject.apps.MyWikiConfig',  # replaces 'wiki.apps.WikiConfig'
        ...
    ]

M docs/installation.rst => docs/installation.rst +1 -2
@@ 160,10 160,9 @@ following lines at the end of your project's ``urls.py``.

.. code-block:: python

    from wiki.urls import get_pattern as get_wiki_pattern
    urlpatterns += [
        url(r'^notifications/', include('django_nyt.urls')),
        url(r'', get_wiki_pattern())
        url(r'', include('wiki.urls'))
    ]



M docs/release_notes.rst => docs/release_notes.rst +3 -0
@@ 19,12 19,14 @@ Added
~~~~~

 * Django 2 support :url-issue:`755` (Raffaele Salmaso & Mads Jensen)
 * Added ``wiki.sites.WikiSite`` for easy customization :url-issue:`827`

Changed
~~~~~~~

 * Use full path names for ``MARKDOWN_KWARGS['extensions']`` as short names
   support wil be removed in ``Markdown 2.7`` :url-issue:`823`
 * Support for ``include('wiki.urls')`` for urls instantiation :url-issue:`827`

Fixed
~~~~~


@@ 38,6 40,7 @@ Deprecated/Removed

 * Django < 1.11 support is dropped :url-issue:`779`
 * Python < 3.3 support is dropped :url-issue:`779` and :url-issue:`792`
 * Deprecate ``wiki.urls.get_pattern`` :url-issue:`799`


django-wiki 0.3.1

M src/wiki/apps.py => src/wiki/apps.py +1 -0
@@ 7,6 7,7 @@ from . import checks


class WikiConfig(AppConfig):
    default_site = 'wiki.sites.WikiSite'
    name = "wiki"
    verbose_name = _("Wiki")


M src/wiki/conf/settings.py => src/wiki/conf/settings.py +1 -1
@@ 263,7 263,7 @@ USE_BOOTSTRAP_SELECT_WIDGET = getattr(
URL_CONFIG_CLASS = getattr(
    django_settings,
    'WIKI_URL_CONFIG_CLASS',
    'wiki.urls.WikiURLPatterns')
    None)

#: Search view - dotted path denoting where the search view Class is located.
SEARCH_VIEW = getattr(

A src/wiki/sites.py => src/wiki/sites.py +183 -0
@@ 0,0 1,183 @@
from django.apps import apps
from django.utils.functional import LazyObject
from django.utils.module_loading import import_string
from wiki.compat import include, url
from wiki.core.plugins import registry


class WikiSite:

    """
    Wiki site configurator.

    To customize, you can define your own subclass, either overriding
    the view providers, or overriding the functions that collect
    views and set update WikiConfig.default_site to the dotted import path
    of your customized site.
    """

    def __init__(self, name='wiki'):
        from wiki.conf import settings
        from wiki.views import accounts, article, deleted_list

        self.name = name

        # root view
        self.root_view = getattr(self, "root_view", article.CreateRootView.as_view())
        self.root_missing_view = getattr(self, "root_missing_view", article.MissingRootView.as_view())

        # basic views
        self.article_view = getattr(self, "article_view", article.ArticleView.as_view())
        self.article_create_view = getattr(self, "article_create_view", article.Create.as_view())
        self.article_delete_view = getattr(self, "article_delete_view", article.Delete.as_view())
        self.article_deleted_view = getattr(self, "article_deleted_view", article.Deleted.as_view())
        self.article_dir_view = getattr(self, "article_dir_view", article.Dir.as_view())
        self.article_edit_view = getattr(self, "article_edit_view", article.Edit.as_view())
        self.article_move_view = getattr(self, "article_move_view", article.Move.as_view())
        self.article_preview_view = getattr(self, "article_preview_view", article.Preview.as_view())
        self.article_history_view = getattr(self, "article_history_view", article.History.as_view())
        self.article_settings_view = getattr(self, "article_settings_view", article.Settings.as_view())
        self.article_source_view = getattr(self, "article_source_view", article.Source.as_view())
        self.article_plugin_view = getattr(self, "article_plugin_view", article.Plugin.as_view())
        self.revision_change_view = getattr(self, "revision_change_view", article.ChangeRevisionView.as_view())
        self.revision_merge_view = getattr(self, "revision_merge_view", article.MergeView.as_view())
        self.revision_preview_merge_view = getattr(self, "revision_preview_merge_view", article.MergeView.as_view(preview=True))

        self.search_view = import_string(getattr(self, "search_view", settings.SEARCH_VIEW)).as_view()
        self.article_diff_view = getattr(self, "article_diff_view", article.DiffView.as_view())

        # account views
        self.signup_view = getattr(self, "signup_view", accounts.Signup.as_view())
        self.login_view = getattr(self, "login_view", accounts.Login.as_view())
        self.logout_view = getattr(self, "logout_view", accounts.Logout.as_view())
        self.profile_update_view = getattr(self, "profile_update_view", accounts.Update.as_view())

        # deleted list view
        self.deleted_list_view = getattr(self, "deleted_list_view", deleted_list.DeletedListView.as_view())

    def get_urls(self):
        urlpatterns = self.get_root_urls()
        urlpatterns += self.get_accounts_urls()
        urlpatterns += self.get_deleted_list_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()
        return urlpatterns

    @property
    def urls(self):
        return self.get_urls(), 'wiki', self.name

    def get_root_urls(self):
        urlpatterns = [
            url(r'^$', self.article_view, name='root', kwargs={'path': ''}),
            url(r'^create-root/$', self.root_view, name='root_create'),
            url(r'^missing-root/$', self.root_missing_view, name='root_missing'),
            url(r'^_search/$', self.search_view, name='search'),
            url(r'^_revision/diff/(?P<revision_id>[0-9]+)/$', self.article_diff_view, name='diff'),
        ]
        return urlpatterns

    def get_deleted_list_urls(self):
        urlpatterns = [
            url('^_admin/$', self.deleted_list_view, name="deleted_list"),
        ]
        return urlpatterns

    def get_accounts_urls(self):
        from wiki.conf import settings
        if settings.ACCOUNT_HANDLING:
            urlpatterns = [
                url(r'^_accounts/sign-up/$', self.signup_view, name='signup'),
                url(r'^_accounts/logout/$', self.logout_view, name='logout'),
                url(r'^_accounts/login/$', self.login_view, name='login'),
                url(r'^_accounts/settings/$', self.profile_update_view, name='profile_update'),
            ]
        else:
            urlpatterns = []
        return urlpatterns

    def get_revision_urls(self):
        urlpatterns = [
            # This one doesn't work because it don't know
            # where to redirect after...
            url(r'^change/(?P<revision_id>[0-9]+)/$', self.revision_change_view, name='change_revision'),
            url(r'^preview/$', self.article_preview_view, name='preview_revision'),
            url(r'^merge/(?P<revision_id>[0-9]+)/preview/$', self.revision_preview_merge_view, name='merge_revision_preview'),
        ]
        return [
            url(r'^_revision/(?P<article_id>[0-9]+)/', include(urlpatterns)),
        ]

    def get_article_urls(self):
        urlpatterns = [
            # Paths decided by article_ids
            url(r'^$', self.article_view, name='get'),
            url(r'^delete/$', self.article_delete_view, name='delete'),
            url(r'^deleted/$', self.article_deleted_view, name='deleted'),
            url(r'^edit/$', self.article_edit_view, name='edit'),
            url(r'^move/$', self.article_move_view, name='move'),
            url(r'^preview/$', self.article_preview_view, name='preview'),
            url(r'^history/$', self.article_history_view, name='history'),
            url(r'^settings/$', self.article_settings_view, name='settings'),
            url(r'^source/$', self.article_source_view, name='source'),
            url(r'^revision/change/(?P<revision_id>[0-9]+)/$', self.revision_change_view, name='change_revision'),
            url(r'^revision/merge/(?P<revision_id>[0-9]+)/$', self.revision_merge_view, name='merge_revision'),
            url(r'^plugin/(?P<slug>\w+)/$', self.article_plugin_view, name='plugin'),
        ]
        return [
            url(r'^(?P<article_id>[0-9]+)/', include(urlpatterns)),
        ]

    def get_article_path_urls(self):
        urlpatterns = [
            # Paths decided by URLs
            url(r'^(?P<path>.+/|)_create/$', self.article_create_view, name='create'),
            url(r'^(?P<path>.+/|)_delete/$', self.article_delete_view, name='delete'),
            url(r'^(?P<path>.+/|)_deleted/$', self.article_deleted_view, name='deleted'),
            url(r'^(?P<path>.+/|)_edit/$', self.article_edit_view, name='edit'),
            url(r'^(?P<path>.+/|)_move/$', self.article_move_view, name='move'),
            url(r'^(?P<path>.+/|)_preview/$', self.article_preview_view, name='preview'),
            url(r'^(?P<path>.+/|)_history/$', self.article_history_view, name='history'),
            url(r'^(?P<path>.+/|)_dir/$', self.article_dir_view, name='dir'),
            url(r'^(?P<path>.+/|)_search/$', self.search_view, name='search'),
            url(r'^(?P<path>.+/|)_settings/$', self.article_settings_view, name='settings'),
            url(r'^(?P<path>.+/|)_source/$', self.article_source_view, name='source'),
            url(r'^(?P<path>.+/|)_revision/change/(?P<revision_id>[0-9]+)/$', self.revision_change_view, name='change_revision'),
            url(r'^(?P<path>.+/|)_revision/merge/(?P<revision_id>[0-9]+)/$', self.revision_merge_view, name='merge_revision'),
            url(r'^(?P<path>.+/|)_plugin/(?P<slug>\w+)/$', self.article_plugin_view, name='plugin'),
            # This should always go last!
            url(r'^(?P<path>.+/|)$', self.article_view, name='get'),
        ]
        return urlpatterns

    def get_plugin_urls(self):
        urlpatterns = []
        for plugin in registry.get_plugins().values():
            slug = getattr(plugin, 'slug', None)
            if slug:
                article_urlpatterns = plugin.urlpatterns.get('article', [])
                urlpatterns += [
                    url(r'^(?P<article_id>[0-9]+)/plugin/' + slug + '/',
                        include(article_urlpatterns)),
                    url(r'^(?P<path>.+/|)_plugin/' + slug + '/',
                        include(article_urlpatterns)),
                ]
                root_urlpatterns = plugin.urlpatterns.get('root', [])
                urlpatterns += [
                    url(r'^_plugin/' + slug + '/', include(root_urlpatterns)),
                ]
        return urlpatterns


class DefaultWikiSite(LazyObject):
    def _setup(self):
        WikiSiteClass = import_string(apps.get_app_config('wiki').default_site)
        self._wrapped = WikiSiteClass()


site = DefaultWikiSite()

M src/wiki/urls.py => src/wiki/urls.py +15 -0
@@ 2,9 2,15 @@ from django.utils.module_loading import import_string
from wiki.compat import include, url
from wiki.conf import settings
from wiki.core.plugins import registry
from wiki import sites
from wiki.views import accounts, article, deleted_list


urlpatterns = [
    url(r'^', sites.site.urls),
]


class WikiURLPatterns:

    """


@@ 247,11 253,20 @@ def get_pattern(app_name="wiki", namespace="wiki", url_config_class=None):
       single Django project.
       https://docs.djangoproject.com/en/dev/topics/http/urls/#topics-http-reversing-url-namespaces
    """
    import warnings
    warnings.warn(
        "wiki.urls.get_pattern is deprecated and will be removed in next version, just `include('wiki.urls')` in your urlconf",
        DeprecationWarning
    )
    if url_config_class is None:
        url_config_classname = getattr(settings, 'URL_CONFIG_CLASS', None)
        if url_config_classname is None:
            url_config_class = WikiURLPatterns
        else:
            warnings.warn(
                "URL_CONFIG_CLASS is deprecated and will be removed in next version, override `wiki.sites.WikiSite` instead",
                DeprecationWarning
            )
            url_config_class = import_string(url_config_classname)
    urlpatterns = url_config_class().get_urls()


M testproject/testproject/urls.py => testproject/testproject/urls.py +1 -2
@@ 4,7 4,6 @@ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.http.response import HttpResponse
from django.views.static import serve as static_serve
from wiki.compat import include, url
from wiki.urls import get_pattern as get_wiki_pattern

admin.autodiscover()



@@ 22,7 21,7 @@ if settings.DEBUG:

urlpatterns += [
    url(r'^notify/', include('django_nyt.urls')),
    url(r'', get_wiki_pattern())
    url(r'', include('wiki.urls')),
]

handler500 = 'testproject.views.server_error'

A tests/core/test_sites.py => tests/core/test_sites.py +92 -0
@@ 0,0 1,92 @@
from importlib import reload

from django.contrib.sites.models import Site
from django.test.testcases import TestCase
from wiki import sites, urls
from wiki.apps import WikiConfig
from wiki.compat import include, url
from wiki.models import Article, URLPath

from ..base import wiki_override_settings


class WikiCustomSite(sites.WikiSite):
    def get_article_urls(self):
        urlpatterns = [
            url('^some-prefix/(?P<article_id>[0-9]+)/$', self.article_view, name='get'),
        ]
        return urlpatterns

    def get_article_path_urls(self):
        urlpatterns = [
            url('^some-other-prefix/(?P<path>.+/|)$', self.article_view, name='get'),
        ]
        return urlpatterns


class WikiCustomConfig(WikiConfig):
    default_site = "tests.core.test_sites.WikiCustomSite"


urlpatterns = [
    url(r'^notify/', include('django_nyt.urls')),
    url(r'', include('wiki.urls')),
]


@wiki_override_settings(
    INSTALLED_APPS=[
        'tests.testdata',
        'django.contrib.auth.apps.AuthConfig',
        'django.contrib.contenttypes.apps.ContentTypesConfig',
        'django.contrib.sessions.apps.SessionsConfig',
        'django.contrib.admin.apps.AdminConfig',
        'django.contrib.humanize.apps.HumanizeConfig',
        'django.contrib.sites.apps.SitesConfig',
        'django_nyt.apps.DjangoNytConfig',
        'mptt',
        'sekizai',
        'sorl.thumbnail',
        'tests.core.test_sites.WikiCustomConfig',
        'wiki.plugins.attachments.apps.AttachmentsConfig',
        'wiki.plugins.notifications.apps.NotificationsConfig',
        'wiki.plugins.images.apps.ImagesConfig',
        'wiki.plugins.macros.apps.MacrosConfig',
        'wiki.plugins.globalhistory.apps.GlobalHistoryConfig',
    ],
    ROOT_URLCONF='tests.core.test_sites',
)
class CustomWikiSiteTest(TestCase):
    def setUp(self):
        # Reload wiki.urls since it may have already been instantiated by another test app.
        self._old_site = sites.site
        sites.site = sites.DefaultWikiSite()
        reload(urls)

    def tearDown(self):
        sites.site = self._old_site
        reload(urls)

    def test_use_custom_wiki_site(self):
        self.assertEqual(sites.site.__class__.__name__, 'WikiCustomSite')

    def test_get_absolute_url_if_urlpath_set_is_not_exists__no_root_urlconf(self):
        a = Article.objects.create()

        self.assertEqual(a.get_absolute_url(), '/some-prefix/1/')

    def test_get_absolute_url_if_urlpath_set_is_exists__no_root_urlconf(self):
        a1 = Article.objects.create()
        s1 = Site.objects.create(domain="something.com", name="something.com")
        u1 = URLPath.objects.create(article=a1, site=s1)

        a2 = Article.objects.create()
        s2 = Site.objects.create(domain="somethingelse.com", name="somethingelse.com")
        URLPath.objects.create(
            article=a2,
            site=s2,
            parent=u1,
            slug='test_slug'
        )

        self.assertEqual(a2.get_absolute_url(), '/some-other-prefix/test_slug/')

M tests/testdata/urls.py => tests/testdata/urls.py +1 -2
@@ 2,7 2,6 @@ from django.conf import settings
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from wiki.compat import include, url
from wiki.urls import get_pattern as get_wiki_pattern


urlpatterns = [


@@ 22,5 21,5 @@ if settings.DEBUG:
urlpatterns += [
    url(r'^django_functest/', include('django_functest.urls')),
    url(r'^notify/', include('django_nyt.urls')),
    url(r'', get_wiki_pattern())
    url(r'', include('wiki.urls')),
]