Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added django.contrib.databrowse

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5011 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 30fb62d71af0186c98c7d5208a72609f74943092 1 parent 83c4c53
Adrian Holovaty adrianholovaty authored
Showing with 913 additions and 0 deletions.
  1. +1 −0  django/contrib/databrowse/__init__.py
  2. +188 −0 django/contrib/databrowse/datastructures.py
  3. 0  django/contrib/databrowse/plugins/__init__.py
  4. +84 −0 django/contrib/databrowse/plugins/calendars.py
  5. +72 −0 django/contrib/databrowse/plugins/fieldchoices.py
  6. +14 −0 django/contrib/databrowse/plugins/objects.py
  7. +148 −0 django/contrib/databrowse/sites.py
  8. +58 −0 django/contrib/databrowse/templates/databrowse/base.html
  9. +17 −0 django/contrib/databrowse/templates/databrowse/calendar_day.html
  10. +17 −0 django/contrib/databrowse/templates/databrowse/calendar_homepage.html
  11. +17 −0 django/contrib/databrowse/templates/databrowse/calendar_main.html
  12. +17 −0 django/contrib/databrowse/templates/databrowse/calendar_month.html
  13. +17 −0 django/contrib/databrowse/templates/databrowse/calendar_year.html
  14. +17 −0 django/contrib/databrowse/templates/databrowse/choice_detail.html
  15. +17 −0 django/contrib/databrowse/templates/databrowse/choice_list.html
  16. +17 −0 django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html
  17. +17 −0 django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html
  18. +17 −0 django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
  19. +21 −0 django/contrib/databrowse/templates/databrowse/homepage.html
  20. +19 −0 django/contrib/databrowse/templates/databrowse/model_detail.html
  21. +41 −0 django/contrib/databrowse/templates/databrowse/object_detail.html
  22. +20 −0 django/contrib/databrowse/urls.py
  23. +23 −0 django/contrib/databrowse/views.py
  24. +54 −0 docs/databrowse.txt
1  django/contrib/databrowse/__init__.py
View
@@ -0,0 +1 @@
+from django.contrib.databrowse.sites import DatabrowsePlugin, ModelDatabrowse, DatabrowseSite, site
188 django/contrib/databrowse/datastructures.py
View
@@ -0,0 +1,188 @@
+"""
+These classes are light wrappers around Django's database API that provide
+convenience functionality and permalink functions for the databrowse app.
+"""
+
+from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
+from django.db import models
+from django.utils import dateformat
+from django.utils.text import capfirst
+from django.utils.translation import get_date_formats
+
+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>' % 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 '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)
+
+ def objects(self, **kwargs):
+ for obj in self.model._default_manager.filter(**kwargs):
+ yield EasyInstance(self, obj)
+
+ 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 '<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 '%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 '%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 '<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name)
+
+ def url(self):
+ return '%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, self.value)
+
+class EasyInstance(object):
+ def __init__(self, easy_model, instance):
+ self.model, self.instance = easy_model, instance
+
+ def __repr__(self):
+ return '<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val())
+
+ def __str__(self):
+ val = str(self.instance)
+ if len(val) > 30:
+ return val[:30] + '...'
+ return val
+
+ def pk(self):
+ return self.instance._get_pk_val()
+
+ def url(self):
+ return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, 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 '<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.
+ """
+ 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_CHANGELIST_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_CHANGELIST_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 = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, value._get_pk_val())
+ lst.append((str(value), url))
+ else:
+ lst = [(value, None) for value in self.values()]
+ elif self.field.choices:
+ lst = []
+ for value in self.values():
+ url = '%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, self.raw_value)
+ lst.append((value, url))
+ elif isinstance(self.field, models.URLField):
+ val = self.values()[0]
+ lst = [(val, val)]
+ else:
+ lst = [(self.values()[0], None)]
+ return lst
0  django/contrib/databrowse/plugins/__init__.py
View
No changes.
84 django/contrib/databrowse/plugins/calendars.py
View
@@ -0,0 +1,84 @@
+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.translation import get_date_formats
+from django.views.generic import date_based
+import datetime
+import time
+
+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 ''
+ return '<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
+ ', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, 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):
+ return ['%s%s/%s/%s/%s/%s/' % (easy_instance_field.model.url(),
+ plugin_name, easy_instance_field.field.name,
+ easy_instance_field.raw_value.year,
+ easy_instance_field.raw_value.strftime('%b').lower(),
+ easy_instance_field.raw_value.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)
+ extra_context = {'root_url': self.site.root_url, 'model': easy_model, 'field': field}
+ if day is not None:
+ # TODO: The objects in this template should be EasyInstances
+ return date_based.archive_day(request, year, month, day, self.model.objects.all(), 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, self.model.objects.all(), 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, self.model.objects.all(), 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, self.model.objects.all(), 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))
72 django/contrib/databrowse/plugins/fieldchoices.py
View
@@ -0,0 +1,72 @@
+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.views.generic import date_based
+import datetime
+import time
+
+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 ''
+ return '<p class="filter"><strong>View by:</strong> %s</p>' % \
+ ', '.join(['<a href="fields/%s/">%s</a>' % (f.name, 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():
+ return ['%s%s/%s/%s/' % (easy_instance_field.model.url(),
+ plugin_name, easy_instance_field.field.name,
+ easy_instance_field.raw_value)]
+
+ 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})
14 django/contrib/databrowse/plugins/objects.py
View
@@ -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})
148 django/contrib/databrowse/sites.py
View
@@ -0,0 +1,148 @@
+from django import http
+from django.db import models
+from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
+from django.shortcuts import render_to_response
+
+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 = '\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()}
58 django/contrib/databrowse/templates/databrowse/base.html
View
@@ -0,0 +1,58 @@
+<!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>
+<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>
+</head>
+<body id="{% block bodyid %}page{% endblock %}">
+<div id="header"><a href="{{ root_url }}">Databrowse</a></div>
+<div id="content">
+{% block content %}{% endblock %}
+</div>
+</body>
+</html>
17 django/contrib/databrowse/templates/databrowse/calendar_day.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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>{{ model.verbose_name_plural|capfirst }} 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 }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
17 django/contrib/databrowse/templates/databrowse/calendar_homepage.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 %}
17 django/contrib/databrowse/templates/databrowse/calendar_main.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 %}
17 django/contrib/databrowse/templates/databrowse/calendar_month.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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>{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ month|date:"F Y" }}</h1>
+
+<ul class="objectlist">
+{% for object in object_list %}
+<li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
17 django/contrib/databrowse/templates/databrowse/calendar_year.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 %}
17 django/contrib/databrowse/templates/databrowse/choice_detail.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
17 django/contrib/databrowse/templates/databrowse/choice_list.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
17 django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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>{{ model.verbose_name_plural|capfirst|escape }} 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 %}
17 django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 %}
17 django/contrib/databrowse/templates/databrowse/fieldchoice_list.html
View
@@ -0,0 +1,17 @@
+{% extends "databrowse/base.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 }}/">{{ object|escape }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
21 django/contrib/databrowse/templates/databrowse/homepage.html
View
@@ -0,0 +1,21 @@
+{% extends "databrowse/base.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 }}</a>,
+ {% endfor %}
+ <a class="more" href="{{ model.url }}">More &rarr;</a>
+ </p>
+ </div>
+{% endfor %}
+
+{% endblock %}
19 django/contrib/databrowse/templates/databrowse/model_detail.html
View
@@ -0,0 +1,19 @@
+{% extends "databrowse/base.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.verbose_name_plural|capfirst }}</h1>
+
+{{ plugin_html }}
+
+<ul class="objectlist">
+{% for object in model.objects %}
+ <li class="{% cycle odd,even %}"><a href="{{ object.url }}">{{ object }}</a></li>
+{% endfor %}
+</ul>
+
+{% endblock %}
41 django/contrib/databrowse/templates/databrowse/object_detail.html
View
@@ -0,0 +1,41 @@
+{% extends "databrowse/base.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 }}</div>
+
+<h1>{{ object.model.verbose_name|capfirst }}: {{ object }}</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 urlvalue in field.urls %}
+{% if urlvalue.1 %}<a href="{{ urlvalue.1 }}">{% endif %}{{ urlvalue.0 }}{% if urlvalue.1 %}</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 }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% else %}
+ <p class="quiet">(None)</p>
+ {% endif %}
+{% endfor %}
+
+{% endblock %}
20 django/contrib/databrowse/urls.py
View
@@ -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),
+)
23 django/contrib/databrowse/views.py
View
@@ -0,0 +1,23 @@
+from django.db.models import FieldDoesNotExist, DateTimeField
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
+import datetime
+import time
+
+###########
+# 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})
54 docs/databrowse.txt
View
@@ -0,0 +1,54 @@
+==========
+Databrowse
+==========
+
+Databrowse is a Django application that lets you browse your data.
+
+As the Django admin dynamically creates an admin interface by introspecting
+your models, Databrowse dynamically creates a rich, browsable Web site by
+introspecting your models.
+
+.. admonition:: Note
+
+ Databrowse is **very** new and is currently under active development. It
+ may change substantially before the next Django release.
+
+ With that said, it's easy to use, and it doesn't require writing any
+ code. So you can play around with it today, with very little investment in
+ time or coding.
+
+How to use Databrowse
+=====================
+
+ 1. Point Django at the default Databrowse templates. There are two ways to
+ do this:
+
+ * Add ``'django.contrib.databrowse'`` to your ``INSTALLED_APPS``
+ setting. This will work if your ``TEMPLATE_LOADERS`` setting includes
+ the ``app_directories`` template loader (which is the case by
+ default). See the `template loader docs`_ for more.
+
+ * Otherwise, determine the full filesystem path to the
+ ``django/contrib/databrowse/templates`` directory, and add that
+ directory to your ``TEMPLATE_DIRS`` setting.
+
+ 2. Register a number of models with the Databrowse site::
+
+ from django.contrib import databrowse
+
+ databrowse.site.register(SomeModel)
+ databrowse.site.register(SomeOtherModel)
+
+ Note that you should register the model *classes*, not instances.
+
+ It doesn't matter where you put this, as long as it gets executed at
+ some point. A good place for it is in your URLconf file (``urls.py``).
+
+ 3. Add the following line to your URLconf::
+
+ (r'^databrowse/(.*)', databrowse.site.root),
+
+ The prefix doesn't matter -- you can use ``databrowse/`` or ``db/`` or
+ whatever you'd like.
+
+ 4. Run the Django server and visit ``/databrowse/`` in your browser.
Please sign in to comment.
Something went wrong with that request. Please try again.