summaryrefslogtreecommitdiffstats
path: root/webapp/django/contrib/databrowse
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/django/contrib/databrowse')
-rw-r--r--webapp/django/contrib/databrowse/__init__.py1
-rw-r--r--webapp/django/contrib/databrowse/datastructures.py217
-rw-r--r--webapp/django/contrib/databrowse/plugins/__init__.py0
-rw-r--r--webapp/django/contrib/databrowse/plugins/calendars.py86
-rw-r--r--webapp/django/contrib/databrowse/plugins/fieldchoices.py74
-rw-r--r--webapp/django/contrib/databrowse/plugins/objects.py14
-rw-r--r--webapp/django/contrib/databrowse/sites.py149
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/base.html61
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/base_site.html1
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/calendar_day.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/calendar_homepage.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/calendar_main.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/calendar_month.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/calendar_year.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/choice_detail.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/choice_list.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html17
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/homepage.html21
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/model_detail.html19
-rw-r--r--webapp/django/contrib/databrowse/templates/databrowse/object_detail.html41
-rw-r--r--webapp/django/contrib/databrowse/urls.py20
-rw-r--r--webapp/django/contrib/databrowse/views.py19
24 files changed, 893 insertions, 0 deletions
diff --git a/webapp/django/contrib/databrowse/__init__.py b/webapp/django/contrib/databrowse/__init__.py
new file mode 100644
index 0000000000..e2f48ac822
--- /dev/null
+++ b/webapp/django/contrib/databrowse/__init__.py
@@ -0,0 +1 @@
+from django.contrib.databrowse.sites import DatabrowsePlugin, ModelDatabrowse, DatabrowseSite, site
diff --git a/webapp/django/contrib/databrowse/datastructures.py b/webapp/django/contrib/databrowse/datastructures.py
new file mode 100644
index 0000000000..5fdbdbe134
--- /dev/null
+++ b/webapp/django/contrib/databrowse/datastructures.py
@@ -0,0 +1,217 @@
+"""
+These classes are light wrappers around Django's database API that provide
+convenience functionality and permalink functions for the databrowse app.
+"""
+
+from django.db import models
+from django.utils import dateformat
+from django.utils.text import capfirst
+from django.utils.translation import get_date_formats
+from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
+from django.utils.safestring import mark_safe
+from django.db.models.query import QuerySet
+
+EMPTY_VALUE = '(None)'
+DISPLAY_SIZE = 100
+
+class EasyModel(object):
+ def __init__(self, site, model):
+ self.site = site
+ self.model = model
+ self.model_list = site.registry.keys()
+ self.verbose_name = model._meta.verbose_name
+ self.verbose_name_plural = model._meta.verbose_name_plural
+
+ def __repr__(self):
+ return '<EasyModel for %s>' % smart_str(self.model._meta.object_name)
+
+ def model_databrowse(self):
+ "Returns the ModelDatabrowse class for this model."
+ return self.site.registry[self.model]
+
+ def url(self):
+ return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name))
+
+ def objects(self, **kwargs):
+ return self.get_query_set().filter(**kwargs)
+
+ def get_query_set(self):
+ easy_qs = self.model._default_manager.get_query_set()._clone(klass=EasyQuerySet)
+ easy_qs._easymodel = self
+ return easy_qs
+
+ def object_by_pk(self, pk):
+ return EasyInstance(self, self.model._default_manager.get(pk=pk))
+
+ def sample_objects(self):
+ for obj in self.model._default_manager.all()[:3]:
+ yield EasyInstance(self, obj)
+
+ def field(self, name):
+ try:
+ f = self.model._meta.get_field(name)
+ except models.FieldDoesNotExist:
+ return None
+ return EasyField(self, f)
+
+ def fields(self):
+ return [EasyField(self, f) for f in (self.model._meta.fields + self.model._meta.many_to_many)]
+
+class EasyField(object):
+ def __init__(self, easy_model, field):
+ self.model, self.field = easy_model, field
+
+ def __repr__(self):
+ return smart_str(u'<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+
+ def choices(self):
+ for value, label in self.field.choices:
+ yield EasyChoice(self.model, self, value, label)
+
+ def url(self):
+ if self.field.choices:
+ return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name))
+ elif self.field.rel:
+ return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name))
+
+class EasyChoice(object):
+ def __init__(self, easy_model, field, value, label):
+ self.model, self.field = easy_model, field
+ self.value, self.label = value, label
+
+ def __repr__(self):
+ return smart_str(u'<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+
+ def url(self):
+ return mark_safe('%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)))
+
+class EasyInstance(object):
+ def __init__(self, easy_model, instance):
+ self.model, self.instance = easy_model, instance
+
+ def __repr__(self):
+ return smart_str(u'<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val()))
+
+ def __unicode__(self):
+ val = smart_unicode(self.instance)
+ if len(val) > DISPLAY_SIZE:
+ return val[:DISPLAY_SIZE] + u'...'
+ return val
+
+ def __str__(self):
+ return self.__unicode__().encode('utf-8')
+
+ def pk(self):
+ return self.instance._get_pk_val()
+
+ def url(self):
+ return mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk())))
+
+ def fields(self):
+ """
+ Generator that yields EasyInstanceFields for each field in this
+ EasyInstance's model.
+ """
+ for f in self.model.model._meta.fields + self.model.model._meta.many_to_many:
+ yield EasyInstanceField(self.model, self, f)
+
+ def related_objects(self):
+ """
+ Generator that yields dictionaries of all models that have this
+ EasyInstance's model as a ForeignKey or ManyToManyField, along with
+ lists of related objects.
+ """
+ for rel_object in self.model.model._meta.get_all_related_objects() + self.model.model._meta.get_all_related_many_to_many_objects():
+ if rel_object.model not in self.model.model_list:
+ continue # Skip models that aren't in the model_list
+ em = EasyModel(self.model.site, rel_object.model)
+ yield {
+ 'model': em,
+ 'related_field': rel_object.field.verbose_name,
+ 'object_list': [EasyInstance(em, i) for i in getattr(self.instance, rel_object.get_accessor_name()).all()],
+ }
+
+class EasyInstanceField(object):
+ def __init__(self, easy_model, instance, field):
+ self.model, self.field, self.instance = easy_model, field, instance
+ self.raw_value = getattr(instance.instance, field.name)
+
+ def __repr__(self):
+ return smart_str(u'<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
+
+ def values(self):
+ """
+ Returns a list of values for this field for this instance. It's a list
+ so we can accomodate many-to-many fields.
+ """
+ # This import is deliberately inside the function because it causes
+ # some settings to be imported, and we don't want to do that at the
+ # module level.
+ if self.field.rel:
+ if isinstance(self.field.rel, models.ManyToOneRel):
+ objs = getattr(self.instance.instance, self.field.name)
+ elif isinstance(self.field.rel, models.ManyToManyRel): # ManyToManyRel
+ return list(getattr(self.instance.instance, self.field.name).all())
+ elif self.field.choices:
+ objs = dict(self.field.choices).get(self.raw_value, EMPTY_VALUE)
+ elif isinstance(self.field, models.DateField) or isinstance(self.field, models.TimeField):
+ if self.raw_value:
+ date_format, datetime_format, time_format = get_date_formats()
+ if isinstance(self.field, models.DateTimeField):
+ objs = capfirst(dateformat.format(self.raw_value, datetime_format))
+ elif isinstance(self.field, models.TimeField):
+ objs = capfirst(dateformat.time_format(self.raw_value, time_format))
+ else:
+ objs = capfirst(dateformat.format(self.raw_value, date_format))
+ else:
+ objs = EMPTY_VALUE
+ elif isinstance(self.field, models.BooleanField) or isinstance(self.field, models.NullBooleanField):
+ objs = {True: 'Yes', False: 'No', None: 'Unknown'}[self.raw_value]
+ else:
+ objs = self.raw_value
+ return [objs]
+
+ def urls(self):
+ "Returns a list of (value, URL) tuples."
+ # First, check the urls() method for each plugin.
+ plugin_urls = []
+ for plugin_name, plugin in self.model.model_databrowse().plugins.items():
+ urls = plugin.urls(plugin_name, self)
+ if urls is not None:
+ #plugin_urls.append(urls)
+ values = self.values()
+ return zip(self.values(), urls)
+ if self.field.rel:
+ m = EasyModel(self.model.site, self.field.rel.to)
+ if self.field.rel.to in self.model.model_list:
+ lst = []
+ for value in self.values():
+ url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())))
+ lst.append((smart_unicode(value), url))
+ else:
+ lst = [(value, None) for value in self.values()]
+ elif self.field.choices:
+ lst = []
+ for value in self.values():
+ url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)))
+ lst.append((value, url))
+ elif isinstance(self.field, models.URLField):
+ val = self.values()[0]
+ lst = [(val, iri_to_uri(val))]
+ else:
+ lst = [(self.values()[0], None)]
+ return lst
+
+class EasyQuerySet(QuerySet):
+ """
+ When creating (or cloning to) an `EasyQuerySet`, make sure to set the
+ `_easymodel` variable to the related `EasyModel`.
+ """
+ def iterator(self, *args, **kwargs):
+ for obj in super(EasyQuerySet, self).iterator(*args, **kwargs):
+ yield EasyInstance(self._easymodel, obj)
+
+ def _clone(self, *args, **kwargs):
+ c = super(EasyQuerySet, self)._clone(*args, **kwargs)
+ c._easymodel = self._easymodel
+ return c
diff --git a/webapp/django/contrib/databrowse/plugins/__init__.py b/webapp/django/contrib/databrowse/plugins/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/webapp/django/contrib/databrowse/plugins/__init__.py
diff --git a/webapp/django/contrib/databrowse/plugins/calendars.py b/webapp/django/contrib/databrowse/plugins/calendars.py
new file mode 100644
index 0000000000..ac2f522148
--- /dev/null
+++ b/webapp/django/contrib/databrowse/plugins/calendars.py
@@ -0,0 +1,86 @@
+from django import http
+from django.db import models
+from django.contrib.databrowse.datastructures import EasyModel
+from django.contrib.databrowse.sites import DatabrowsePlugin
+from django.shortcuts import render_to_response
+from django.utils.text import capfirst
+from django.utils.encoding import force_unicode
+from django.utils.safestring import mark_safe
+from django.views.generic import date_based
+from django.utils import datetime_safe
+
+class CalendarPlugin(DatabrowsePlugin):
+ def __init__(self, field_names=None):
+ self.field_names = field_names
+
+ def field_dict(self, model):
+ """
+ Helper function that returns a dictionary of all DateFields or
+ DateTimeFields in the given model. If self.field_names is set, it takes
+ take that into account when building the dictionary.
+ """
+ if self.field_names is None:
+ return dict([(f.name, f) for f in model._meta.fields if isinstance(f, models.DateField)])
+ else:
+ return dict([(f.name, f) for f in model._meta.fields if isinstance(f, models.DateField) and f.name in self.field_names])
+
+ def model_index_html(self, request, model, site):
+ fields = self.field_dict(model)
+ if not fields:
+ return u''
+ return mark_safe(u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
+ u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
+
+ def urls(self, plugin_name, easy_instance_field):
+ if isinstance(easy_instance_field.field, models.DateField):
+ d = easy_instance_field.raw_value
+ return [mark_safe(u'%s%s/%s/%s/%s/%s/' % (
+ easy_instance_field.model.url(),
+ plugin_name, easy_instance_field.field.name,
+ d.year,
+ datetime_safe.new_date(d).strftime('%b').lower(),
+ d.day))]
+
+ def model_view(self, request, model_databrowse, url):
+ self.model, self.site = model_databrowse.model, model_databrowse.site
+ self.fields = self.field_dict(self.model)
+
+ # If the model has no DateFields, there's no point in going further.
+ if not self.fields:
+ raise http.Http404('The requested model has no calendars.')
+
+ if url is None:
+ return self.homepage_view(request)
+ url_bits = url.split('/')
+ if self.fields.has_key(url_bits[0]):
+ return self.calendar_view(request, self.fields[url_bits[0]], *url_bits[1:])
+
+ raise http.Http404('The requested page does not exist.')
+
+ def homepage_view(self, request):
+ easy_model = EasyModel(self.site, self.model)
+ field_list = self.fields.values()
+ field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
+ return render_to_response('databrowse/calendar_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list})
+
+ def calendar_view(self, request, field, year=None, month=None, day=None):
+ easy_model = EasyModel(self.site, self.model)
+ queryset = easy_model.get_query_set()
+ extra_context = {'root_url': self.site.root_url, 'model': easy_model, 'field': field}
+ if day is not None:
+ return date_based.archive_day(request, year, month, day, queryset, field.name,
+ template_name='databrowse/calendar_day.html', allow_empty=False, allow_future=True,
+ extra_context=extra_context)
+ elif month is not None:
+ return date_based.archive_month(request, year, month, queryset, field.name,
+ template_name='databrowse/calendar_month.html', allow_empty=False, allow_future=True,
+ extra_context=extra_context)
+ elif year is not None:
+ return date_based.archive_year(request, year, queryset, field.name,
+ template_name='databrowse/calendar_year.html', allow_empty=False, allow_future=True,
+ extra_context=extra_context)
+ else:
+ return date_based.archive_index(request, queryset, field.name,
+ template_name='databrowse/calendar_main.html', allow_empty=True, allow_future=True,
+ extra_context=extra_context)
+ assert False, ('%s, %s, %s, %s' % (field, year, month, day))
diff --git a/webapp/django/contrib/databrowse/plugins/fieldchoices.py b/webapp/django/contrib/databrowse/plugins/fieldchoices.py
new file mode 100644
index 0000000000..8f77792579
--- /dev/null
+++ b/webapp/django/contrib/databrowse/plugins/fieldchoices.py
@@ -0,0 +1,74 @@
+from django import http
+from django.db import models
+from django.contrib.databrowse.datastructures import EasyModel
+from django.contrib.databrowse.sites import DatabrowsePlugin
+from django.shortcuts import render_to_response
+from django.utils.text import capfirst
+from django.utils.encoding import smart_str, force_unicode
+from django.utils.safestring import mark_safe
+import urllib
+
+class FieldChoicePlugin(DatabrowsePlugin):
+ def __init__(self, field_filter=None):
+ # If field_filter is given, it should be a callable that takes a
+ # Django database Field instance and returns True if that field should
+ # be included. If field_filter is None, that all fields will be used.
+ self.field_filter = field_filter
+
+ def field_dict(self, model):
+ """
+ Helper function that returns a dictionary of all fields in the given
+ model. If self.field_filter is set, it only includes the fields that
+ match the filter.
+ """
+ if self.field_filter:
+ return dict([(f.name, f) for f in model._meta.fields if self.field_filter(f)])
+ else:
+ return dict([(f.name, f) for f in model._meta.fields if not f.rel and not f.primary_key and not f.unique and not isinstance(f, (models.AutoField, models.TextField))])
+
+ def model_index_html(self, request, model, site):
+ fields = self.field_dict(model)
+ if not fields:
+ return u''
+ return mark_safe(u'<p class="filter"><strong>View by:</strong> %s</p>' % \
+ u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
+
+ def urls(self, plugin_name, easy_instance_field):
+ if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
+ field_value = smart_str(easy_instance_field.raw_value)
+ return [mark_safe(u'%s%s/%s/%s/' % (
+ easy_instance_field.model.url(),
+ plugin_name, easy_instance_field.field.name,
+ urllib.quote(field_value, safe='')))]
+
+ def model_view(self, request, model_databrowse, url):
+ self.model, self.site = model_databrowse.model, model_databrowse.site
+ self.fields = self.field_dict(self.model)
+
+ # If the model has no fields with choices, there's no point in going
+ # further.
+ if not self.fields:
+ raise http.Http404('The requested model has no fields.')
+
+ if url is None:
+ return self.homepage_view(request)
+ url_bits = url.split('/', 1)
+ if self.fields.has_key(url_bits[0]):
+ return self.field_view(request, self.fields[url_bits[0]], *url_bits[1:])
+
+ raise http.Http404('The requested page does not exist.')
+
+ def homepage_view(self, request):
+ easy_model = EasyModel(self.site, self.model)
+ field_list = self.fields.values()
+ field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name))
+ return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list})
+
+ def field_view(self, request, field, value=None):
+ easy_model = EasyModel(self.site, self.model)
+ easy_field = easy_model.field(field.name)
+ if value is not None:
+ obj_list = easy_model.objects(**{field.name: value})
+ return render_to_response('databrowse/fieldchoice_detail.html', {'root_url': self.site.root_url, 'model': easy_model, 'field': easy_field, 'value': value, 'object_list': obj_list})
+ obj_list = [v[field.name] for v in self.model._default_manager.distinct().order_by(field.name).values(field.name)]
+ return render_to_response('databrowse/fieldchoice_list.html', {'root_url': self.site.root_url, 'model': easy_model, 'field': easy_field, 'object_list': obj_list})
diff --git a/webapp/django/contrib/databrowse/plugins/objects.py b/webapp/django/contrib/databrowse/plugins/objects.py
new file mode 100644
index 0000000000..7326566655
--- /dev/null
+++ b/webapp/django/contrib/databrowse/plugins/objects.py
@@ -0,0 +1,14 @@
+from django import http
+from django.contrib.databrowse.datastructures import EasyModel
+from django.contrib.databrowse.sites import DatabrowsePlugin
+from django.shortcuts import render_to_response
+import urlparse
+
+class ObjectDetailPlugin(DatabrowsePlugin):
+ def model_view(self, request, model_databrowse, url):
+ # If the object ID wasn't provided, redirect to the model page, which is one level up.
+ if url is None:
+ return http.HttpResponseRedirect(urlparse.urljoin(request.path, '../'))
+ easy_model = EasyModel(model_databrowse.site, model_databrowse.model)
+ obj = easy_model.object_by_pk(url)
+ return render_to_response('databrowse/object_detail.html', {'object': obj, 'root_url': model_databrowse.site.root_url})
diff --git a/webapp/django/contrib/databrowse/sites.py b/webapp/django/contrib/databrowse/sites.py
new file mode 100644
index 0000000000..9ef35dbc1c
--- /dev/null
+++ b/webapp/django/contrib/databrowse/sites.py
@@ -0,0 +1,149 @@
+from django import http
+from django.db import models
+from django.contrib.databrowse.datastructures import EasyModel
+from django.shortcuts import render_to_response
+from django.utils.safestring import mark_safe
+
+class AlreadyRegistered(Exception):
+ pass
+
+class NotRegistered(Exception):
+ pass
+
+class DatabrowsePlugin(object):
+ def urls(self, plugin_name, easy_instance_field):
+ """
+ Given an EasyInstanceField object, returns a list of URLs for this
+ plugin's views of this object. These URLs should be absolute.
+
+ Returns None if the EasyInstanceField object doesn't get a
+ list of plugin-specific URLs.
+ """
+ return None
+
+ def model_index_html(self, request, model, site):
+ """
+ Returns a snippet of HTML to include on the model index page.
+ """
+ return ''
+
+ def model_view(self, request, model_databrowse, url):
+ """
+ Handles main URL routing for a plugin's model-specific pages.
+ """
+ raise NotImplementedError
+
+class ModelDatabrowse(object):
+ plugins = {}
+
+ def __init__(self, model, site):
+ self.model = model
+ self.site = site
+
+ def root(self, request, url):
+ """
+ Handles main URL routing for the databrowse app.
+
+ `url` is the remainder of the URL -- e.g. 'objects/3'.
+ """
+ # Delegate to the appropriate method, based on the URL.
+ if url is None:
+ return self.main_view(request)
+ try:
+ plugin_name, rest_of_url = url.split('/', 1)
+ except ValueError: # need more than 1 value to unpack
+ plugin_name, rest_of_url = url, None
+ try:
+ plugin = self.plugins[plugin_name]
+ except KeyError:
+ raise http.Http404('A plugin with the requested name does not exist.')
+ return plugin.model_view(request, self, rest_of_url)
+
+ def main_view(self, request):
+ easy_model = EasyModel(self.site, self.model)
+ html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()]))
+ return render_to_response('databrowse/model_detail.html', {
+ 'model': easy_model,
+ 'root_url': self.site.root_url,
+ 'plugin_html': html_snippets,
+ })
+
+class DatabrowseSite(object):
+ def __init__(self):
+ self.registry = {} # model_class -> databrowse_class
+ self.root_url = None
+
+ def register(self, model_or_iterable, databrowse_class=None, **options):
+ """
+ Registers the given model(s) with the given databrowse site.
+
+ The model(s) should be Model classes, not instances.
+
+ If a databrowse class isn't given, it will use DefaultModelDatabrowse
+ (the default databrowse options).
+
+ If a model is already registered, this will raise AlreadyRegistered.
+ """
+ databrowse_class = databrowse_class or DefaultModelDatabrowse
+ if issubclass(model_or_iterable, models.Model):
+ model_or_iterable = [model_or_iterable]
+ for model in model_or_iterable:
+ if model in self.registry:
+ raise AlreadyRegistered('The model %s is already registered' % model.__class__.__name__)
+ self.registry[model] = databrowse_class
+
+ def unregister(self, model_or_iterable):
+ """
+ Unregisters the given model(s).
+
+ If a model isn't already registered, this will raise NotRegistered.
+ """
+ if issubclass(model_or_iterable, models.Model):
+ model_or_iterable = [model_or_iterable]
+ for model in model_or_iterable:
+ if model not in self.registry:
+ raise NotRegistered('The model %s is not registered' % model.__class__.__name__)
+ del self.registry[model]
+
+ def root(self, request, url):
+ """
+ Handles main URL routing for the databrowse app.
+
+ `url` is the remainder of the URL -- e.g. 'comments/comment/'.
+ """
+ self.root_url = request.path[:len(request.path) - len(url)]
+ url = url.rstrip('/') # Trim trailing slash, if it exists.
+
+ if url == '':
+ return self.index(request)
+ elif '/' in url:
+ return self.model_page(request, *url.split('/', 2))
+
+ raise http.Http404('The requested databrowse page does not exist.')
+
+ def index(self, request):
+ m_list = [EasyModel(self, m) for m in self.registry.keys()]
+ return render_to_response('databrowse/homepage.html', {'model_list': m_list, 'root_url': self.root_url})
+
+ def model_page(self, request, app_label, model_name, rest_of_url=None):
+ """
+ Handles the model-specific functionality of the databrowse site, delegating
+ to the appropriate ModelDatabrowse class.
+ """
+ model = models.get_model(app_label, model_name)
+ if model is None:
+ raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
+ try:
+ databrowse_class = self.registry[model]
+ except KeyError:
+ raise http.Http404("This model exists but has not been registered with databrowse.")
+ return databrowse_class(model, self).root(request, rest_of_url)
+
+site = DatabrowseSite()
+
+from django.contrib.databrowse.plugins.calendars import CalendarPlugin
+from django.contrib.databrowse.plugins.objects import ObjectDetailPlugin
+from django.contrib.databrowse.plugins.fieldchoices import FieldChoicePlugin
+
+class DefaultModelDatabrowse(ModelDatabrowse):
+ plugins = {'objects': ObjectDetailPlugin(), 'calendars': CalendarPlugin(), 'fields': FieldChoicePlugin()}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/base.html b/webapp/django/contrib/databrowse/templates/databrowse/base.html
new file mode 100644
index 0000000000..a3419851c4
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/base.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+<head>
+<title>{% block title %}{% endblock %}</title>
+{% block style %}
+<style type="text/css">
+* { margin:0; padding:0; }
+body { background:#eee; color:#333; font:76%/1.6 "Lucida Grande","Bitstream Vera Sans",Verdana,sans-serif; }
+a { color: #5b80b2; text-decoration:none; }
+a:hover { text-decoration:underline; }
+a img { border:none; }
+h1 { font-size:1.8em; color:#666; margin:0.4em 0 0.2em 0; }
+h2 { font-size:1.5em; color:#666; margin:1em 0 0.2em 0; }
+p { margin:0.5em 0 1em 0; }
+.odd { background-color:#EDF3FE; }
+.quiet { color:#666; }
+/* FILTERS */
+.filter { color:#999; font-size:0.9em; float:left; margin-bottom:10px; margin-right:20px; }
+.filter strong { color:#666; }
+/* OBJECT LISTS */
+.objectlist { clear:both; margin:0 -20px; color:#666; }
+.objectlist li a { display:block; padding:1em 20px; }
+.objectlist li a:hover { background:#5b80b2; color:#3B5572; color:#fff; text-decoration:none; }
+.related h2 { font-size: 1em; margin-bottom: 0.6em; }
+.related .objectlist li a { padding: 0.6em 20px; }
+.related .objectlist li.odd { background:#eee; }
+/* OBJECT DETAIL */
+.objectinfo { border-collapse:collapse; color:#666; margin:0 -20px; }
+.objectinfo td, .objectinfo th { padding:1em 20px; vertical-align:top; }
+.objectinfo td { width:100%; }
+.objectinfo th { text-align:left; white-space:nowrap; }
+/* MODEL GROUPS */
+.modelgroup { color:#999; font-size:0.9em; margin:0 -20px; }
+.modelgroup h2 { font-size:1.2em; margin:0; }
+.modelgroup h2 a { display: block; padding: 0.83em 20px; }
+.modelgroup h2 a:hover { text-decoration: none; color: #fff; }
+.modelgroup p { float:left; margin:-2.65em 0 0 14em; position:relative; }
+.modelgroup p a { white-space:nowrap; }
+.modelgroup a.more { color:#999; }
+.modelgroup:hover { background:#5b80b2; color:#becfe5; }
+.modelgroup:hover p a { color:#becfe5; }
+.modelgroup:hover a { color:#fff; }
+.modelgroup:hover a.more { color:#fff; }
+/* BREADCRUMBS */
+#breadcrumbs { padding:10px 0; color:#999; font-size:0.9em; }
+/* HEADER */
+#header a { display:block; background:#eee; color:#676868; padding:10px 20px; font-weight:bold; font-size:1em; text-decoration:none; border-bottom:1px solid #ddd; }
+#header a:hover { text-decoration:underline; }
+/* CONTENT */
+#content { background:#fff; border-bottom:1px solid #ddd; padding:0 20px; }
+</style>
+{% endblock %}
+{% block extrahead %}{% endblock %}
+</head>
+<body id="{% block bodyid %}page{% endblock %}">
+<div id="header"><a href="{{ root_url }}">{% block databrowse_title %}Databrowse{% endblock %}</a></div>
+<div id="content">
+{% block content %}{% endblock %}
+</div>
+</body>
+</html>
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/base_site.html b/webapp/django/contrib/databrowse/templates/databrowse/base_site.html
new file mode 100644
index 0000000000..b577ab8427
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/base_site.html
@@ -0,0 +1 @@
+{% extends "databrowse/base.html" %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/calendar_day.html b/webapp/django/contrib/databrowse/templates/databrowse/calendar_day.html
new file mode 100644
index 0000000000..bbb62cc814
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/calendar_day.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} {{ day|date:"F j, Y" }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../../../">Calendars</a> / <a href="../../../">By {{ field.verbose_name }}</a> / <a href="../../">{{ day.year }}</a> / <a href="../">{{ day|date:"F" }}</a> / {{ day.day }}</div>
+
+<h1>{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural|escape }}{% else %}{{ model.verbose_name|escape }}{% endif %} with {{ field.verbose_name }} on {{ day|date:"F j, Y" }}</h1>
+
+<ul class="objectlist">
+{% for object in object_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/calendar_homepage.html b/webapp/django/contrib/databrowse/templates/databrowse/calendar_homepage.html
new file mode 100644
index 0000000000..85eb8af9eb
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/calendar_homepage.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}Calendars{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / Calendars</div>
+
+<h1>Calendars</h1>
+
+<ul class="objectlist">
+{% for field in field_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ field.name }}/">{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/calendar_main.html b/webapp/django/contrib/databrowse/templates/databrowse/calendar_main.html
new file mode 100644
index 0000000000..b22a44d321
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/calendar_main.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ field.verbose_name|capfirst }} calendar{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../">Calendars</a> / By {{ field.verbose_name }}</div>
+
+<h1>{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</h1>
+
+<ul class="objectlist">
+{% for year in date_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ year.year }}/">{{ year.year }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/calendar_month.html b/webapp/django/contrib/databrowse/templates/databrowse/calendar_month.html
new file mode 100644
index 0000000000..70b7deb75a
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/calendar_month.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ month|date:"F Y" }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../../">Calendars</a> / <a href="../../">By {{ field.verbose_name }}</a> / <a href="../">{{ month.year }}</a> / {{ month|date:"F" }}</div>
+
+<h1>{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural|escape }}{% else %}{{ model.verbose_name|escape }}{% endif %} with {{ field.verbose_name }} on {{ day|date:"F Y" }}</h1>
+
+<ul class="objectlist">
+{% for object in object_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/calendar_year.html b/webapp/django/contrib/databrowse/templates/databrowse/calendar_year.html
new file mode 100644
index 0000000000..a6e6f53ba3
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/calendar_year.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ year }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../">Calendars</a> / <a href="../">By {{ field.verbose_name }}</a> / {{ year }}</div>
+
+<h1>{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ year }}</h1>
+
+<ul class="objectlist">
+{% for month in date_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ month|date:"M"|lower }}/">{{ month|date:"F" }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/choice_detail.html b/webapp/django/contrib/databrowse/templates/databrowse/choice_detail.html
new file mode 100644
index 0000000000..6cb73e73ec
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/choice_detail.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst }} by {{ field.field.verbose_name }}: {{ value|escape }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="{{ field.url }}">By {{ field.field.verbose_name }}</a> / {{ value|escape }}</div>
+
+<h1>{{ model.verbose_name_plural|capfirst }} by {{ field.field.verbose_name }}: {{ value|escape }}</h1>
+
+<ul class="objectlist">
+{% for object in object_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/choice_list.html b/webapp/django/contrib/databrowse/templates/databrowse/choice_list.html
new file mode 100644
index 0000000000..95cd88b0bf
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/choice_list.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst }} by {{ field.field.verbose_name }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / By {{ field.field.verbose_name }}</div>
+
+<h1>{{ model.verbose_name_plural|capfirst }} by {{ field.field.verbose_name }}</h1>
+
+<ul class="objectlist">
+{% for choice in field.choices %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ choice.url }}">{{ choice.label|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html b/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html
new file mode 100644
index 0000000000..7801f3f631
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst|escape }} with {{ field.field.verbose_name|escape }} {{ value|escape }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../">Fields</a> / <a href="../">By {{ field.field.verbose_name|escape }}</a> / {{ value|escape }}</div>
+
+<h1>{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural|escape }}{% else %}{{ model.verbose_name|escape }}{% endif %} with {{ field.field.verbose_name|escape }} {{ value|escape }}</h1>
+
+<ul class="objectlist">
+{% for object in object_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html b/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html
new file mode 100644
index 0000000000..3259824061
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}Browsable fields in {{ model.verbose_name_plural|escape }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / Fields</div>
+
+<h1>Browsable fields in {{ model.verbose_name_plural }}</h1>
+
+<ul class="objectlist">
+{% for field in field_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ field.name }}/">{{ model.verbose_name_plural|capfirst }} by {{ field.verbose_name }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html b/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
new file mode 100644
index 0000000000..71b906d3e7
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
@@ -0,0 +1,17 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst|escape }} by {{ field.field.verbose_name|escape }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../">Fields</a> / By {{ field.field.verbose_name|escape }}</div>
+
+<h1>{{ model.verbose_name_plural|capfirst|escape }} by {{ field.field.verbose_name|escape }}</h1>
+
+<ul class="objectlist">
+{% for object in object_list %}
+<li class="{% cycle 'odd' 'even' %}"><a href="{{ object|iriencode }}/">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/homepage.html b/webapp/django/contrib/databrowse/templates/databrowse/homepage.html
new file mode 100644
index 0000000000..718e577416
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/homepage.html
@@ -0,0 +1,21 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}Databrowse{% endblock %}
+
+{% block bodyid %}homepage{% endblock %}
+
+{% block content %}
+
+{% for model in model_list %}
+ <div class="modelgroup {% cycle 'even' 'odd' %}">
+ <h2><a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a></h2>
+ <p>
+ {% for object in model.sample_objects %}
+ <a href="{{ object.url }}">{{ object|escape }}</a>,
+ {% endfor %}
+ <a class="more" href="{{ model.url }}">More &rarr;</a>
+ </p>
+ </div>
+{% endfor %}
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/model_detail.html b/webapp/django/contrib/databrowse/templates/databrowse/model_detail.html
new file mode 100644
index 0000000000..b66bc093dd
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/model_detail.html
@@ -0,0 +1,19 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ model.verbose_name_plural|capfirst }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / {{ model.verbose_name_plural|capfirst }}</div>
+
+<h1>{{ model.objects.count }} {% if model.objects.count|pluralize %}{{ model.verbose_name_plural }}{% else %}{{ model.verbose_name }}{% endif %}</h1>
+
+{{ plugin_html }}
+
+<ul class="objectlist">
+{% for object in model.objects %}
+ <li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/templates/databrowse/object_detail.html b/webapp/django/contrib/databrowse/templates/databrowse/object_detail.html
new file mode 100644
index 0000000000..7c1bd3e16d
--- /dev/null
+++ b/webapp/django/contrib/databrowse/templates/databrowse/object_detail.html
@@ -0,0 +1,41 @@
+{% extends "databrowse/base_site.html" %}
+
+{% block title %}{{ object.model.verbose_name|capfirst }}: {{ object }}{% endblock %}
+
+{% block content %}
+
+<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ object.model.url }}">{{ object.model.verbose_name_plural|capfirst }}</a> / {{ object|escape }}</div>
+
+<h1>{{ object.model.verbose_name|capfirst }}: {{ object|escape }}</h1>
+
+<table class="objectinfo">
+{% for field in object.fields %}
+<tr class="{% cycle 'odd' 'even' %}">
+<th>{{ field.field.verbose_name|capfirst }}</th>
+<td>
+{% if field.urls %}
+{% for value, url in field.urls %}
+{% if url %}<a href="{{ url }}">{% endif %}{{ value|escape }}{% if url %}</a>{% endif %}{% if not forloop.last %}, {% endif %}
+{% endfor %}
+{% else %}None{% endif %}
+</td>
+</tr>
+{% endfor %}
+</table>
+
+{% for related_object in object.related_objects %}
+ <div class="related">
+ <h2>Appears in "{{ related_object.related_field }}" in the following {{ related_object.model.verbose_name_plural }}:</h2>
+ {% if related_object.object_list %}
+ <ul class="objectlist">
+ {% for object in related_object.object_list %}
+ <li class="{% cycle 'odd' 'even' %}"><a href="{{ object.url }}">{{ object|escape }}</a></li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <p class="quiet">(None)</p>
+ {% endif %}
+ </div>
+{% endfor %}
+
+{% endblock %}
diff --git a/webapp/django/contrib/databrowse/urls.py b/webapp/django/contrib/databrowse/urls.py
new file mode 100644
index 0000000000..9b85d142a2
--- /dev/null
+++ b/webapp/django/contrib/databrowse/urls.py
@@ -0,0 +1,20 @@
+from django.conf.urls.defaults import *
+from django.contrib.databrowse import views
+
+# Note: The views in this URLconf all require a 'models' argument,
+# which is a list of model classes (*not* instances).
+
+urlpatterns = patterns('',
+ #(r'^$', views.homepage),
+ #(r'^([^/]+)/([^/]+)/$', views.model_detail),
+
+ (r'^([^/]+)/([^/]+)/fields/(\w+)/$', views.choice_list),
+ (r'^([^/]+)/([^/]+)/fields/(\w+)/(.*)/$', views.choice_detail),
+
+ #(r'^([^/]+)/([^/]+)/calendars/(\w+)/$', views.calendar_main),
+ #(r'^([^/]+)/([^/]+)/calendars/(\w+)/(\d{4})/$', views.calendar_year),
+ #(r'^([^/]+)/([^/]+)/calendars/(\w+)/(\d{4})/(\w{3})/$', views.calendar_month),
+ #(r'^([^/]+)/([^/]+)/calendars/(\w+)/(\d{4})/(\w{3})/(\d{1,2})/$', views.calendar_day),
+
+ #(r'^([^/]+)/([^/]+)/objects/(.*)/$', views.object_detail),
+)
diff --git a/webapp/django/contrib/databrowse/views.py b/webapp/django/contrib/databrowse/views.py
new file mode 100644
index 0000000000..4543e95780
--- /dev/null
+++ b/webapp/django/contrib/databrowse/views.py
@@ -0,0 +1,19 @@
+from django.http import Http404
+from django.shortcuts import render_to_response
+
+###########
+# CHOICES #
+###########
+
+def choice_list(request, app_label, module_name, field_name, models):
+ m, f = lookup_field(app_label, module_name, field_name, models)
+ return render_to_response('databrowse/choice_list.html', {'model': m, 'field': f})
+
+def choice_detail(request, app_label, module_name, field_name, field_val, models):
+ m, f = lookup_field(app_label, module_name, field_name, models)
+ try:
+ label = dict(f.field.choices)[field_val]
+ except KeyError:
+ raise Http404('Invalid choice value given')
+ obj_list = m.objects(**{f.field.name: field_val})
+ return render_to_response('databrowse/choice_detail.html', {'model': m, 'field': f, 'value': label, 'object_list': obj_list})