Permalink
Browse files

Improved generic views and plugins and docs

  • Loading branch information...
1 parent 974b3bd commit 2685ba87d29e933c2c712a5a0acd8ca9e95ac411 @samluescher committed Jan 27, 2012
View
@@ -0,0 +1,41 @@
+.. _custom-plugins:
+
+Implementing custom plugins
+***************************
+
+Django Media Tree comes with some generic view classes and mixins that make it
+relatively easy to use ``FileNode`` objects with your own applications.
+
+The following pseudo code should give you an idea of how to implement your own
+custom plugin that will render a file listing and work together with some
+3rd-party application. It loosely looks like a Django CMS plugin. Please notice
+that the ``render()`` method is passed an ``options_model_instance``, which
+can be a dictionary or an object with attributes to initialize the generic
+view class we are using, ``ListingView``) (TODO REF to generic views)::
+
+ from media_tree.contrib.views.listing import ListingMixin
+ from 3rd_party_app import YourPluginSuperclass
+
+ class CustomFileNodeListingPlugin(YourPluginSuperclass, ListingMixin):
+
+ def render(self, context, options_model_instance):
+ view = self.get_detail_view(instance.node, opts=options_model_instance)
+ context.update(view.get_context_data())
+ return render_to_response('listing.html', context)
+
+
+.. autoclass:: media_tree.contrib.views.listing.ListingMixin
+ :members:
+ :inherited-members:
+ :exclude-members: get_view
+
+.. autoclass:: media_tree.contrib.views.detail.FileNodeDetailMixin
+ :members:
+ :inherited-members:
+ :exclude-members: get_view
+
+.. autoclass:: media_tree.contrib.views.detail.image.ImageDetailMixin
+ :members:
+ :inherited-members:
+ :exclude-members: get_view
+
View
@@ -33,7 +33,9 @@ Table of Contents
templates
configuration
utils
- extending
+ extending
+ custom_plugins
+ views
bundled
cms_plugins
View
@@ -0,0 +1,17 @@
+.. _generic-views:
+
+Class-based generic views
+*************************
+
+.. automodule:: media_tree.contrib.views
+ :members:
+
+.. autoclass:: media_tree.contrib.views.listing.ListingView
+ :members:
+
+.. autoclass:: media_tree.contrib.views.detail.FileNodeDetailView
+ :members:
+
+.. autoclass:: media_tree.contrib.views.detail.image.ImageDetailView
+ :members:
+
@@ -0,0 +1,12 @@
+"""
+This module contains class-based generic views that enable you to access
+``FileNode`` objects through public URLs. Please see below for specific
+examples.
+
+.. Note::
+ As with any public views, you may want to restrict the objects that should be
+ publicly visible by passing and appropriately filtered ``queryset`` when
+ implementing a view. For instance, you may not want users to see the internal
+ folder structure of your ``FileNode`` objects, hence using a ``ListingView``
+ with a QuerySet such as ``FileNode.objects.all()`` would be a bad idea.
+"""
@@ -0,0 +1,96 @@
+from media_tree.models import FileNode
+from media_tree.contrib.views.base import PluginMixin
+from django.views.generic.detail import DetailView
+from django.http import Http404
+from django.utils.translation import ugettext_lazy as _
+
+
+class FileNodeDetailView(DetailView):
+ """
+ View class for implementing detail views for ``FileNode`` objects. This
+ class is based on Django's generic ``DetailView``. Please refer to the
+ respective Django documentation on subclassing and customizing `Class-based
+ generic views
+ <https://docs.djangoproject.com/en/dev/topics/class-based-views/>`_.
+
+ The following example ``urls.py`` would implement a detail view capable of
+ displaying all files with the extension ``.txt``, with URLs containing the
+ full path of the ``FileNode`` object, for instance
+ ``http://yourdomain.com/files/some/folder/ReadMe.txt``::
+
+ from media_tree.models import FileNode
+ from media_tree.contrib.views.detail import FileNodeDetailView
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^files/(?P<path>.+)/$', FileNodeDetailView.as_view(
+ queryset=FileNode.objects.filter(extension='txt')
+ )),
+ )
+ """
+
+ model = FileNode
+ filter_media_types = None
+ filter_node_types = (FileNode.FILE,)
+ context_object_name = 'object'
+
+ def get_object(self, queryset=None):
+ """
+ Returns the object the view is displaying.
+
+ By default this requires `self.queryset` and a `pk` or `path` argument
+ in the URLconf, but subclasses can override this to return any object.
+ """
+ # Use a custom queryset if provided; this is required for subclasses
+ # like DateDetailView
+ if queryset is None:
+ queryset = self.get_queryset()
+
+ path = self.kwargs.get('path', None)
+
+ # Next, try looking up by path.
+ if path is not None:
+ queryset = queryset.filter(**FileNode.objects.filter_args_for_path(path))
+ try:
+ obj = queryset.get()
+ except FileNode.DoesNotExist:
+ raise Http404(_(u"No %(verbose_name)s found matching the query") %
+ {'verbose_name': queryset.model._meta.verbose_name})
+ return obj
+
+ return super(FileNodeDetailView, self).get_object(queryset)
+
+ def get_queryset(self):
+ queryset = super(FileNodeDetailView, self).get_queryset()
+ kwargs = {}
+ if self.filter_node_types:
+ kwargs['node_type__in'] = self.filter_node_types
+ if self.filter_media_types:
+ kwargs['media_type__in'] = self.filter_media_types
+ return queryset.filter(**kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super(FileNodeDetailView, self).get_context_data(**kwargs)
+ if not 'title' in context:
+ context['title'] = context[self.context_object_name].title or context[self.context_object_name].name
+ return context
+
+
+class FileNodeDetailMixin(PluginMixin):
+ """
+ A mixin that you can use as a superclass for your own custom plugins
+ for interfacing with third-party applications, such as Django CMS. Please
+ take a look at :ref:`custom-plugins` for more information.
+ """
+
+ view_class = FileNodeDetailView
+ """ The view class instantiated by ``get_detail_view()``. """
+
+ def get_detail_view(self, object, opts=None):
+ """
+ Instantiates and returns the view class that will generate the actual
+ context for this plugin.
+ """
+ view = self.get_view(self.view_class, opts)
+ view.object = object
+ return view
@@ -1,16 +1,40 @@
-from media_tree.contrib.views.base import PluginMixin
-from django.views.generic.detail import DetailView
+from media_tree.contrib.views.detail import FileNodeDetailView, FileNodeDetailMixin
from media_tree.utils import widthratio
+from media_tree import media_types
-class ImageDetailView(DetailView):
+class ImageDetailView(FileNodeDetailView):
+ """
+ View class for implementing image detail views for ``FileNode`` objects.
+ This class is based on Django's generic ``DetailView``. Please refer to the
+ respective Django documentation on subclassing and customizing `Class-based
+ generic views
+ <https://docs.djangoproject.com/en/dev/topics/class-based-views/>`_.
+
+ The following example ``urls.py`` would implement a detail view capable of
+ displaying all image files under a folder node with the path
+ ``some/folder``::
+
+ from media_tree.models import FileNode
+ from media_tree.contrib.views.detail.image import ImageDetailView
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^images/(?P<pk>\d+)/$', ImageDetailView.as_view(
+ queryset=FileNode.objects.get_by_path('some/folder').get_descendants()
+ )),
+ )
+ """
width = None
height = None
context_object_name = 'image_node'
+ template_name = "media_tree/image_detail.html"
+ filter_media_types = (media_types.SUPPORTED_IMAGE,)
+
- def get_context_data(self):
- context = super(ImageDetailView, self).get_context_data()
+ def get_context_data(self, **kwargs):
+ context = super(ImageDetailView, self).get_context_data(**kwargs)
if self.width or self.height:
w = self.width or widthratio(self.height, self.object.height, self.object.width)
@@ -20,13 +44,13 @@ def get_context_data(self):
return context
-class ImageDetailMixin(PluginMixin):
+class ImageDetailMixin(FileNodeDetailMixin):
+ """
+ A mixin that you can use as a superclass for your own custom plugins
+ for interfacing with third-party applications, such as Django CMS. Please
+ take a look at :ref:`custom-plugins` for more information.
+ """
+
+ view_class = ImageDetailView
+ """ The view class instantiated by ``get_detail_view()``. """
- def get_detail_view(self, object, opts=None):
- """
- Instantiates and returns the view class that will generate the
- actual context for this plugin.
- """
- view = self.get_view(ImageDetailView, opts)
- view.object = object
- return view
@@ -2,28 +2,42 @@
from media_tree.contrib.views.base import PluginMixin
from media_tree.utils.filenode import get_file_link, get_merged_filenode_list, get_nested_filenode_list
from django.views.generic.list import ListView
+from django.utils.translation import ugettext_lazy as _
LISTING_MERGED = 'M'
LISTING_NESTED = 'N'
class ListingView(ListView):
+ """
+ View class for implementing views that render a file listing. This class is
+ based on Django's generic ``ListView``. Please refer to the respective
+ Django documentation on subclassing and customizing `Class-based generic
+ views <https://docs.djangoproject.com/en/dev/topics/class-based-views/>`_.
+
+ The following example ``urls.py`` would implement a view listing a folder
+ with the path ``some/folder`` and its immediate children, but not
+ descendants deeper down the hierarchy::
+
+ from media_tree.models import FileNode
+ from media_tree.contrib.views.listing import ListingView
+ from django.conf.urls.defaults import *
+
+ urlpatterns = patterns('',
+ (r'^listing/', ListingView.as_view(
+ # notice that queryset can be any iterable, for instance a list:
+ queryset=[FileNode.objects.get_by_path('some/folder')],
+ include_descendants=False
+ )),
+ )
+ """
- selected_nodes = None
list_type = LISTING_NESTED
list_max_depth = None
list_filter_media_types = None
include_descendants = True
-
- def get_queryset(self):
- """
- Get the list of items for this view. This must be an interable, and may
- be a queryset (in which qs-specific behavior will be enabled).
- """
- if self.selected_nodes is not None:
- return self.selected_nodes
- return super(ListingView, self).get_queryset()
+ template_name = 'media_tree/filenode_list.html'
def init_nodes(self):
self.current_folder = None
@@ -59,16 +73,30 @@ def get_context_data(self, **kwargs):
context = super(ListingView, self).get_context_data(**kwargs)
self.init_nodes()
context['object_list'] = self.get_render_object_list(context.pop('object_list'))
+
+ if not 'title' in context:
+ context['title'] = _('media objects')
+
return context
class ListingMixin(PluginMixin):
+ """
+ A mixin that you can use as a superclass for your own custom plugins
+ for interfacing with third-party applications, such as Django CMS. Please
+ take a look at :ref:`custom-plugins` for more information.
+ """
+
+ view_class = ListingView
+ """ The view class instantiated by ``get_listing_view()``. """
- def get_listing_view(self, selected_nodes, opts=None):
+ def get_listing_view(self, queryset, opts=None):
"""
Instantiates and returns the view class that will generate the
actual context for this plugin.
+
+ ``queryset`` can be an actual QuerySet or any iterable.
"""
- view = self.get_view(ListingView, opts)
- view.selected_nodes = selected_nodes
+ view = self.view_class(ListingView, opts)
+ view.queryset = selected_nodes
return view
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>{{ title }}</title>
+</head>
+
+<body>
+ {% block content %}{% endblock %}
+</body>
+</html>
@@ -0,0 +1,7 @@
+{% extends "media_tree/base.html" %}
+{% block content %}
+{% load media_tree_tags %}
+<h2>{{ object.title }}</h2>
+{% if object.description %}<p>{{ object.description }}</p>{% endif %}
+<p>{{ object|file_link:"include_icon include_size include_extension" }}</p>
+{% endblock %}
@@ -1,4 +1,7 @@
+{% extends "media_tree/base.html" %}
+{% block content %}
{% load media_tree_tags %}
<ul class="file-list">
{{ object_list|file_links:"use_metadata include_icon include_size include_extension"|unordered_list }}
</ul>
+{% endblock %}
@@ -0,0 +1,4 @@
+{% extends "media_tree/base.html" %}
+{% block content %}
+{% include "media_tree/filenode/includes/figure.html" %}
+{% endblock %}
Oops, something went wrong.

0 comments on commit 2685ba8

Please sign in to comment.