diff options
Diffstat (limited to 'webapp/django/contrib/databrowse')
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 →</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}) |