Commits

mikeshantz committed bdd7059

Allow projects to be protected by authentication.

Adds the SPHINX_PROTECTED_PROJECTS setting. Any project with its slug
in the list will require authentication.

Comments (0)

Files changed (8)

docs/quickstart.txt

 Path:
     A file system path to the Sphinx project (where Sphinx’ ``conf.py`` is
     located), e.g.: ``/path/to/app/docs``
+
+
+Protected Projects
+^^^^^^^^^^^^^^^^^^
+
+You can require users to log in to view the documented project by adding the
+project slug as an entry in the :data:`SPHINXDOC_PROTECTED_PROJECTS` setting. ::
+
+    SPHINXDOC_PROTECTED_PROJECTS = [
+        'project-slug',
+    ]
     
 
 Build & import the documentation

docs/ref/decorators.txt

+.. _ref-admin:
+
+Decorators
+==========
+
+.. automodule:: sphinxdoc.decorators
+
+    .. autofunction:: user_allowed_for_project(view)
+

docs/ref/index.txt

     :maxdepth: 1
     
     admin
+    decorators
     forms
     management
     models

docs/ref/views.txt

 
 .. automodule:: sphinxdoc.views
 
+    .. autoclass:: OverviewList
+        :members:
+
     .. autoclass:: ProjectSearchView
         :members:
     

sphinxdoc/decorators.py

+from functools import wraps
+
+from django.contrib.auth.views import redirect_to_login
+from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import PermissionDenied
+from django.shortcuts import get_object_or_404
+from django.utils.decorators import available_attrs
+
+from sphinxdoc.models import Project
+
+
+def user_allowed_for_project(view_func):
+    """
+    Check that the user is allowed for the project.
+
+    If the user is not allowed, the view will be redirected to the standard
+    login page.
+    """
+    @wraps(view_func, assigned=available_attrs(view_func))
+    def _wrapped_view(request, *args, **kwargs):
+        try:
+            slug = kwargs['slug']
+        except IndexError:
+            raise ImproperlyConfigured
+        project = get_object_or_404(Project, slug=slug)
+        if project.is_allowed(request.user):
+            return view_func(request, *args, **kwargs)
+        if request.user.is_authenticated():
+            raise PermissionDenied
+        path = request.build_absolute_uri()
+        return redirect_to_login(path)
+    return _wrapped_view
+
+

sphinxdoc/models.py

 Models for django-sphinxdoc.
 
 """
+from django.conf import settings
 from django.db import models
 
 from sphinxdoc.validators import validate_isdir
     def __unicode__(self):
         return self.name
 
+    def is_allowed(self, user):
+        if user.is_authenticated():
+            return True
+        protected = getattr(settings, 'SPHINXDOC_PROTECTED_PROJECTS', [])
+        if self.slug in protected:
+            return False
+        return True
+
     @models.permalink
     def get_absolute_url(self):
         return ('doc-index', (), {'slug': self.slug})

sphinxdoc/urls.py

 
 """
 from django.conf.urls.defaults import patterns, url
-from django.views.generic import list_detail
 
-from sphinxdoc import models
 from sphinxdoc.views import ProjectSearchView
+from sphinxdoc.views import OverviewList
 
 
-project_info = {
-    'queryset': models.Project.objects.all().order_by('name'),
-    'template_object_name': 'project',
-}
-
 urlpatterns = patterns('sphinxdoc.views',
     url(
         r'^$',
-        list_detail.object_list,
-        project_info,
+        OverviewList.as_view(),
     ),
     url(
         r'^(?P<slug>[\w-]+)/search/$',

sphinxdoc/views.py

 import json
 import os.path
 
+from django.contrib.auth.views import redirect_to_login
 from django.core import urlresolvers
 from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import PermissionDenied
 from django.shortcuts import get_object_or_404, render_to_response
 from django.template import RequestContext
 from django.views.decorators.cache import cache_page
+from django.views.generic import ListView
 from django.views.static import serve
 from haystack.views import SearchView
 
+from sphinxdoc.decorators import user_allowed_for_project
 from sphinxdoc.forms import ProjectSearchForm
 from sphinxdoc.models import Project, Document
 
 CACHE_MINUTES = 5
 
 
+@user_allowed_for_project
 @cache_page(60 * CACHE_MINUTES)
 def documentation(request, slug, path):
     """
             context_instance=RequestContext(request))
 
 
+@user_allowed_for_project
 @cache_page(60 * CACHE_MINUTES)
 def objects_inventory(request, slug):
     """
     return response
 
 
+@user_allowed_for_project
 @cache_page(60 * CACHE_MINUTES)
 def sphinx_serve(request, slug, type_, path):
     """
 
     def __call__(self, request, slug):
         self.slug = slug
-        return SearchView.__call__(self, request)
+        try:
+            return SearchView.__call__(self, request)
+        except PermissionDenied:
+            if request.user.is_authenticated():
+                raise
+            path = request.build_absolute_uri()
+            return redirect_to_login(path)
 
     def build_form(self):
         """
 
         """
         project = Project.objects.get(slug=self.slug)
+        if not project.is_allowed(self.request.user):
+            raise PermissionDenied
 
         try:
             env = json.load(open(os.path.join(project.path, BUILDDIR, 'globalcontext.json'), 'rb'))
             'env': env,
             'update_date': update_date,
         }
+
+
+class OverviewList(ListView):
+    """
+    Listing of all projects available.
+    
+    Extends :class:`django.views.generic.list.ListView`.
+
+    If the user is not authenticated, then projects defined in
+    :data:`SPHINXDOC_PROTECTED_PROJECTS` will not be listed.
+    """
+    queryset = Project.objects.all().order_by('name')
+    template_name = 'sphinxdoc/project_list.html'
+    context_object_name = 'project_list'
+
+    def get_queryset(self):
+        qs = super(OverviewList, self).get_queryset()
+        qs = [project for project in qs if project.is_allowed(self.request.user)]
+        # Note: we are not actually returning a queryset, but a list. This has
+        # some repercussions if there are dependencies elsewhere in the class
+        # on this actually being a queryset.
+        return qs
+