Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #21386 -- Removed admindocs dependence on sites framework

* Removed ADMIN_FOR setting and warn warning
* Group view functions by namespace instead of site
* Added a test verifying namespaces are listed

Thanks to Claude Paroz for reviewing and ideas for improvement.
  • Loading branch information...
commit a39d672ec7d53637805a61b45a51bc0e7d297a36 1 parent f1b3ab9
Bouke Haarsma authored November 05, 2013 claudep committed December 18, 2013
13  django/contrib/admindocs/templates/admin_doc/template_detail.html
@@ -15,15 +15,12 @@
15 15
 {% block content %}
16 16
 <h1>{% blocktrans %}Template: "{{ name }}"{% endblocktrans %}</h1>
17 17
 
18  
-{% regroup templates|dictsort:"site_id" by site as templates_by_site %}
19  
-{% for group in templates_by_site %}
20  
-    <h2>{% blocktrans with group.grouper as grouper %}Search path for template "{{ name }}" on {{ grouper }}:{% endblocktrans %}</h2>
21  
-    <ol>
22  
-    {% for template in group.list|dictsort:"order" %}
23  
-        <li><code>{{ template.file }}</code>{% if not template.exists %} <em>{% trans '(does not exist)' %}</em>{% endif %}</li>
24  
-    {% endfor %}
25  
-    </ol>
  18
+<h2>{% blocktrans %}Search path for template "{{ name }}":{% endblocktrans %}</h2>
  19
+<ol>
  20
+{% for template in templates|dictsort:"order" %}
  21
+    <li><code>{{ template.file }}</code>{% if not template.exists %} <em>{% trans '(does not exist)' %}</em>{% endif %}</li>
26 22
 {% endfor %}
  23
+</ol>
27 24
 
28 25
 <p class="small"><a href="{% url 'django-admindocs-docroot' %}">&lsaquo; {% trans 'Back to Documentation' %}</a></p>
29 26
 {% endblock %}
31  django/contrib/admindocs/templates/admin_doc/view_index.html
@@ -15,29 +15,40 @@
15 15
 
16 16
 <h1>{% trans 'View documentation' %}</h1>
17 17
 
18  
-{% regroup views|dictsort:"site_id" by site as views_by_site %}
  18
+{% regroup views|dictsort:'namespace' by namespace as views_by_ns %}
19 19
 
20 20
 <div id="content-related" class="sidebar">
21 21
 <div class="module">
22  
-<h2>{% trans 'Jump to site' %}</h2>
  22
+<h2>{% trans 'Jump to namespace' %}</h2>
23 23
 <ul>
24  
-    {% for site_views in views_by_site %}
25  
-    <li><a href="#site{{ site_views.grouper.id }}">{{ site_views.grouper.name }}</a></li>
26  
-    {% endfor %}
  24
+{% for ns_views in views_by_ns %}
  25
+    <li><a href="#ns|{{ ns_views.grouper }}">
  26
+    {% if ns_views.grouper %}{{ ns_views.grouper }}
  27
+    {% else %}{% trans "Empty namespace" %}{% endif %}
  28
+    </a></li>
  29
+{% endfor %}
27 30
 </ul>
28 31
 </div>
29 32
 </div>
30 33
 
31 34
 <div id="content-main">
32 35
 
33  
-{% for site_views in views_by_site %}
  36
+{% for ns_views in views_by_ns %}
34 37
 <div class="module">
35  
-<h2 id="site{{ site_views.grouper.id }}">{% blocktrans with site_views.grouper.name as name %}Views by URL on {{ name }}{% endblocktrans %}</h2>
36  
-
37  
-{% for view in site_views.list|dictsort:"url" %}
  38
+<h2 id="ns|{{ ns_views.grouper }}">
  39
+{% if ns_views.grouper %}
  40
+    {% blocktrans with ns_views.grouper as name %}Views by namespace {{ name }}{% endblocktrans %}
  41
+{% else %}
  42
+    {% blocktrans %}Views by empty namespace{% endblocktrans %}
  43
+{% endif %}
  44
+</h2>
  45
+
  46
+{% for view in ns_views.list|dictsort:"url" %}
38 47
 {% ifchanged %}
39 48
 <h3><a href="{% url 'django-admindocs-views-detail' view=view.full_name %}">{{ view.url }}</a></h3>
40  
-<p class="small quiet">{% blocktrans with view.full_name as name %}View function: {{ name }}{% endblocktrans %}</p>
  49
+<p class="small quiet">{% blocktrans with view.full_name as full_name and view.url_name as url_name %}
  50
+    View function: <code>{{ full_name }}</code>. Name: <code>{{ url_name }}</code>.
  51
+{% endblocktrans %}</p>
41 52
 <p>{{ view.title }}</p>
42 53
 <hr />
43 54
 {% endifchanged %}
74  django/contrib/admindocs/views.py
@@ -2,6 +2,7 @@
2 2
 import inspect
3 3
 import os
4 4
 import re
  5
+import warnings
5 6
 
6 7
 from django import template
7 8
 from django.conf import settings
@@ -13,7 +14,6 @@
13 14
 from django.http import Http404
14 15
 from django.core import urlresolvers
15 16
 from django.contrib.admindocs import utils
16  
-from django.contrib.sites.models import Site
17 17
 from django.utils.decorators import method_decorator
18 18
 from django.utils._os import upath
19 19
 from django.utils import six
@@ -23,10 +23,10 @@
23 23
 # Exclude methods starting with these strings from documentation
24 24
 MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_')
25 25
 
26  
-
27  
-class GenericSite(object):
28  
-    domain = 'example.com'
29  
-    name = 'my site'
  26
+if getattr(settings, 'ADMIN_FOR', None):
  27
+    warnings.warn('The ADMIN_FOR setting has been removed, you can remove '
  28
+                  'this setting from your configuration.', DeprecationWarning,
  29
+                  stacklevel=2)
30 30
 
31 31
 
32 32
 class BaseAdminDocsView(TemplateView):
@@ -129,26 +129,17 @@ class ViewIndexView(BaseAdminDocsView):
129 129
     template_name = 'admin_doc/view_index.html'
130 130
 
131 131
     def get_context_data(self, **kwargs):
132  
-        if settings.ADMIN_FOR:
133  
-            settings_modules = [import_module(m) for m in settings.ADMIN_FOR]
134  
-        else:
135  
-            settings_modules = [settings]
136  
-
137 132
         views = []
138  
-        for settings_mod in settings_modules:
139  
-            urlconf = import_module(settings_mod.ROOT_URLCONF)
140  
-            view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
141  
-            if Site._meta.installed:
142  
-                site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
143  
-            else:
144  
-                site_obj = GenericSite()
145  
-            for (func, regex) in view_functions:
146  
-                views.append({
147  
-                    'full_name': '%s.%s' % (func.__module__, getattr(func, '__name__', func.__class__.__name__)),
148  
-                    'site_id': settings_mod.SITE_ID,
149  
-                    'site': site_obj,
150  
-                    'url': simplify_regex(regex),
151  
-                })
  133
+        urlconf = import_module(settings.ROOT_URLCONF)
  134
+        view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
  135
+        for (func, regex, namespace, name) in view_functions:
  136
+            views.append({
  137
+                'full_name': '%s.%s' % (func.__module__, getattr(func, '__name__', func.__class__.__name__)),
  138
+                'url': simplify_regex(regex),
  139
+                'url_name': ':'.join((namespace or []) + (name and [name] or [])),
  140
+                'namespace': ':'.join((namespace or [])),
  141
+                'name': name,
  142
+            })
152 143
         kwargs.update({'views': views})
153 144
         return super(ViewIndexView, self).get_context_data(**kwargs)
154 145
 
@@ -292,22 +283,14 @@ class TemplateDetailView(BaseAdminDocsView):
292 283
     def get_context_data(self, **kwargs):
293 284
         template = self.kwargs['template']
294 285
         templates = []
295  
-        for site_settings_module in settings.ADMIN_FOR:
296  
-            settings_mod = import_module(site_settings_module)
297  
-            if Site._meta.installed:
298  
-                site_obj = Site.objects.get(pk=settings_mod.SITE_ID)
299  
-            else:
300  
-                site_obj = GenericSite()
301  
-            for dir in settings_mod.TEMPLATE_DIRS:
302  
-                template_file = os.path.join(dir, template)
303  
-                templates.append({
304  
-                    'file': template_file,
305  
-                    'exists': os.path.exists(template_file),
306  
-                    'contents': lambda: open(template_file).read() if os.path.exists(template_file) else '',
307  
-                    'site_id': settings_mod.SITE_ID,
308  
-                    'site': site_obj,
309  
-                    'order': list(settings_mod.TEMPLATE_DIRS).index(dir),
310  
-                })
  286
+        for dir in settings.TEMPLATE_DIRS:
  287
+            template_file = os.path.join(dir, template)
  288
+            templates.append({
  289
+                'file': template_file,
  290
+                'exists': os.path.exists(template_file),
  291
+                'contents': lambda: open(template_file).read() if os.path.exists(template_file) else '',
  292
+                'order': list(settings.TEMPLATE_DIRS).index(dir),
  293
+            })
311 294
         kwargs.update({
312 295
             'name': template,
313 296
             'templates': templates,
@@ -356,7 +339,7 @@ def get_readable_field_data_type(field):
356 339
     return field.description % field.__dict__
357 340
 
358 341
 
359  
-def extract_views_from_urlpatterns(urlpatterns, base=''):
  342
+def extract_views_from_urlpatterns(urlpatterns, base='', namespace=None):
360 343
     """
361 344
     Return a list of views from a list of urlpatterns.
362 345
 
@@ -369,10 +352,15 @@ def extract_views_from_urlpatterns(urlpatterns, base=''):
369 352
                 patterns = p.url_patterns
370 353
             except ImportError:
371 354
                 continue
372  
-            views.extend(extract_views_from_urlpatterns(patterns, base + p.regex.pattern))
  355
+            views.extend(extract_views_from_urlpatterns(
  356
+                patterns,
  357
+                base + p.regex.pattern,
  358
+                (namespace or []) + (p.namespace and [p.namespace] or [])
  359
+            ))
373 360
         elif hasattr(p, 'callback'):
374 361
             try:
375  
-                views.append((p.callback, base + p.regex.pattern))
  362
+                views.append((p.callback, base + p.regex.pattern,
  363
+                              namespace, p.name))
376 364
             except ViewDoesNotExist:
377 365
                 continue
378 366
         else:
2  docs/ref/contrib/admin/admindocs.txt
@@ -28,8 +28,6 @@ the following:
28 28
   ``r'^admin/'`` entry, so that requests to ``/admin/doc/`` don't get
29 29
   handled by the latter entry.
30 30
 * Install the docutils Python module (http://docutils.sf.net/).
31  
-* **Optional:** Linking to templates requires the :setting:`ADMIN_FOR`
32  
-  setting to be configured.
33 31
 * **Optional:** Using the admindocs bookmarklets requires
34 32
   ``django.contrib.admindocs.middleware.XViewMiddleware`` to be installed.
35 33
 
19  docs/ref/settings.txt
@@ -2097,25 +2097,6 @@ The default value for the X-Frame-Options header used by
2097 2097
 :doc:`clickjacking protection </ref/clickjacking/>` documentation.
2098 2098
 
2099 2099
 
2100  
-Admindocs
2101  
-=========
2102  
-
2103  
-Settings for :mod:`django.contrib.admindocs`.
2104  
-
2105  
-.. setting:: ADMIN_FOR
2106  
-
2107  
-ADMIN_FOR
2108  
----------
2109  
-
2110  
-Default: ``()`` (Empty tuple)
2111  
-
2112  
-Used for admin-site settings modules, this should be a tuple of settings
2113  
-modules (in the format ``'foo.bar.baz'``) for which this site is an admin.
2114  
-
2115  
-The admin site uses this in its automatically-introspected documentation of
2116  
-models, views and template tags.
2117  
-
2118  
-
2119 2100
 Auth
2120 2101
 ====
2121 2102
 
6  docs/releases/1.7.txt
@@ -880,3 +880,9 @@ Callable arguments were evaluated when a queryset was constructed rather than
880 880
 when it was evaluated, thus this feature didn't offer any benefit compared to
881 881
 evaluating arguments before passing them to queryset and created confusion that
882 882
 the arguments may have been evaluated at query time.
  883
+
  884
+``ADMIN_FOR`` setting
  885
+~~~~~~~~~~~~~~~~~~~~~
  886
+
  887
+The ``ADMIN_FOR`` feature, part of the admindocs, has been removed. You can
  888
+remove the setting from your configuration at your convenience.
31  tests/admin_docs/tests.py
... ...
@@ -1,5 +1,7 @@
1 1
 import unittest
2 2
 
  3
+from django.conf import settings
  4
+from django.contrib.sites.models import Site
3 5
 from django.contrib.admindocs import utils
4 6
 from django.contrib.auth.models import User
5 7
 from django.core.urlresolvers import reverse
@@ -7,6 +9,33 @@
7 9
 from django.test.utils import override_settings
8 10
 
9 11
 
  12
+class MiscTests(TestCase):
  13
+    urls = 'admin_docs.urls'
  14
+
  15
+    def setUp(self):
  16
+        self._old_installed = Site._meta.app_config.installed
  17
+        User.objects.create_superuser('super', None, 'secret')
  18
+        self.client.login(username='super', password='secret')
  19
+
  20
+    def tearDown(self):
  21
+        Site._meta.app_config.installed = self._old_installed
  22
+
  23
+    @override_settings(
  24
+        SITE_ID=None,
  25
+        INSTALLED_APPS=[app for app in settings.INSTALLED_APPS
  26
+                        if app != 'django.contrib.sites'],
  27
+    )
  28
+    def test_no_sites_framework(self):
  29
+        """
  30
+        Without the sites framework, should not access SITE_ID or Site
  31
+        objects. Deleting settings is fine here as UserSettingsHolder is used.
  32
+        """
  33
+        Site._meta.app_config.installed = False
  34
+        Site.objects.all().delete()
  35
+        del settings.SITE_ID
  36
+        self.client.get('/admindocs/views/')  # should not raise
  37
+
  38
+
10 39
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
11 40
 @unittest.skipUnless(utils.docutils_is_available, "no docutils installed.")
12 41
 class AdminDocViewTests(TestCase):
@@ -46,6 +75,8 @@ def test_view_index(self):
46 75
         self.assertContains(response,
47 76
             '<h3><a href="/admindocs/views/django.contrib.admindocs.views.BaseAdminDocsView/">/admindocs/</a></h3>',
48 77
             html=True)
  78
+        self.assertContains(response, 'Views by namespace test')
  79
+        self.assertContains(response, 'Name: <code>test:func</code>.')
49 80
 
50 81
     def test_view_detail(self):
51 82
         response = self.client.get(
7  tests/admin_docs/urls.py
... ...
@@ -1,11 +1,16 @@
1  
-from django.conf.urls import include, patterns
  1
+from django.conf.urls import include, patterns, url
2 2
 from django.contrib import admin
3 3
 
4 4
 from . import views
5 5
 
  6
+ns_patterns = patterns('',
  7
+    url(r'^xview/func/$', views.xview_dec(views.xview), name='func'),
  8
+)
  9
+
6 10
 urlpatterns = patterns('',
7 11
     (r'^admin/', include(admin.site.urls)),
8 12
     (r'^admindocs/', include('django.contrib.admindocs.urls')),
  13
+    (r'^', include(ns_patterns, namespace='test')),
9 14
     (r'^xview/func/$', views.xview_dec(views.xview)),
10 15
     (r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())),
11 16
 )

0 notes on commit a39d672

Please sign in to comment.
Something went wrong with that request. Please try again.