From 226666ca45a3b83996a6515f5fa8784e0ce0f526 Mon Sep 17 00:00:00 2001 From: Marty Alchin Date: Thu, 10 May 2007 00:03:44 +0000 Subject: [PATCH] New API, as suggested on django-developers --- __init__.py | 183 +++++++++++++++++++++++-------- models.py | 6 +- templates/admin/edit_values.html | 24 ++-- views.py | 98 +++++++++-------- 4 files changed, 205 insertions(+), 106 deletions(-) diff --git a/__init__.py b/__init__.py index 033959d..62d5ac9 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -import re, datetime +import datetime, re, sys from bisect import bisect from django.utils.functional import curry @@ -7,33 +7,34 @@ import models -_values, _values_by_model = {}, {} +_values = {} +_value_list = [] # A transaction is necessary for backends like PostgreSQL transaction.enter_transaction_management() try: # Retrieve all stored values once during startup for value in models.Value.objects.all(): - _values[value.app_label, value.model, value.name] = value + _values[value.module_name, value.class_name, value.attribute_name] = value except: # Necessary in case values were used in models # prior to syncdb setting up the value storage transaction.rollback() transaction.leave_transaction_management() -def get_value(app_label, model_name, name): - return _values[app_label, model_name, name].content +def get_value(module_name, class_name, attribute_name): + return _values[module_name, class_name, attribute_name].content -def get_descriptor(app_label, model_name, name): - return _values[app_label, model_name, name].descriptor +def get_descriptor(module_name, class_name, attribute_name): + return _values[module_name, class_name, attribute_name].descriptor -def set_value(app_label, model_name, name, value): - model = _values.get((app_label, model_name, name), None) +def set_value(module_name, class_name, attribute_name, value): + model = _values.get((module_name, class_name, attribute_name), None) if model is None: - _values[app_label, model_name, name] = model = models.Value( - app_label=app_label, - model=model_name, - name=name, + _values[module_name, class_name, attribute_name] = model = models.Value( + module_name=module_name, + class_name=class_name, + attribute_name=attribute_name, ) try: model.content = model.descriptor.get_db_prep_save(value) @@ -41,8 +42,110 @@ def set_value(app_label, model_name, name, value): model.content = str(value) model.save() -def get_values_by_model(app_label, model): - return _values_by_model[app_label, model] +def get_all_values(): + return _value_list + +class OptionsBase(type): + def __init__(cls, name, bases, attrs): + if not bases or bases == (object,): + return + attrs.pop('__module__', None) + for attribute_name, attr in attrs.items(): + if not isinstance(attr, Value): + raise TypeError('The type of %s (%s) is not a valid Value.' % (attribute_name, attr.__class__.__name__)) + cls.add_to_class(attribute_name, attr) + +# FIXME: Add in the rest of the options +class Options(object): + __metaclass__ = OptionsBase + + def __new__(cls): + attrs = [(k, v.copy()) for (k, v) in cls.__dict__.items() if isinstance(v, Value)] + attrs.sort(lambda a, b: cmp(a[1], b[1])) + + for key, attr in attrs: + attr.creation_counter = Value.creation_counter + Value.creation_counter += 1 + if attr.key not in _values: + # This value isn't already stored in the database + _values[attr.key] = models.Value( + module_name=attr.module_name, + class_name=attr.class_name, + attribute_name=attr.attribute_name, + ) + + # Set up cache storage for external access + _values[attr.key].descriptor = attr + _value_list.insert(bisect(_value_list, attr), attr) + + # Make sure the module reflects where it was executed + attrs += (('__module__', sys._getframe(1).f_globals['__name__']),) + + # A new class is created so descriptors work properly + # object.__new__ is necessary here to avoid recursion + return object.__new__(type('Options', (cls,), dict(attrs))) + + def contribute_to_class(self, cls, name): + # Override the class_name of all registered values + for attr in self.__class__.__dict__.values(): + if isinstance(attr, Value): + attr.module_name = cls.__module__ + attr.class_name = cls.__name__ + + if attr.key not in _values: + # This value isn't already stored in the database + _values[attr.key] = models.Value( + module_name=attr.module_name, + class_name=attr.class_name, + attribute_name=attr.attribute_name, + ) + + # Set up cache storage for external access + _values[attr.key].descriptor = attr + + # Create permission for editing values on the model + permission = ( + 'can_edit_%s_values' % cls.__name__.lower(), + 'Can edit %s values' % cls._meta.verbose_name, + ) + if permission not in cls._meta.permissions: + # Add a permission for the value editor + try: + cls._meta.permissions.append(permission) + except AttributeError: + # Permissions were supplied as a tuple, so preserve that + cls._meta.permissions = tuple(cls._meta.permissions + (permission,)) + + # Finally, plaec the attribute on the class + setattr(cls, name, self) + + def add_to_class(cls, attribute_name, value): + value.contribute_to_class(cls, attribute_name) + add_to_class = classmethod(add_to_class) + + def __add__(self, other): + if not isinstance(other, Options): + raise NotImplementedError('Options may only be added to other options.') + + options = type('Options', (Options,), {'__module__': sys._getframe(1).f_globals['__name__']})() + + for attribute_name, attr in self.__class__.__dict__.items(): + if isinstance(attr, Value): + options.__class__.add_to_class(attribute_name, attr) + for attribute_name, attr in other.__class__.__dict__.items(): + if isinstance(attr, Value): + options.__class__.add_to_class(attribute_name, attr) + return options + + @classmethod + def __iter__(cls): + attrs = [v for v in cls.__dict__.values() if isinstance(v, Value)] + attrs.sort(lambda a, b: cmp(a[1], b[1])) + for attr in attrs: + yield attr + return + attrs = [(v.attribute_name, v.copy()) for v in cls if isinstance(v, Value)] + attrs.sort() class Value(object): @@ -58,44 +161,28 @@ def __cmp__(self, other): # This is needed because bisect does not take a comparison function. return cmp(self.creation_counter, other.creation_counter) - def contribute_to_class(self, cls, name): - self.app_label = cls._meta.app_label - self.model = cls.__name__.lower() - self.name = name - self.description = self.description or name.replace('_', ' ') - self.key = (self.app_label, self.model, self.name) - if self.key not in _values: - # This value isn't already stored in the database - _values[self.key] = models.Value( - app_label=self.app_label, - model=self.model, - name=self.name, - ) + def copy(self): + new_value = self.__class__(self.description, self.help_text) + new_value.__dict__ = self.__dict__.copy() + return new_value - permission = ( - 'can_edit_%s_values' % cls._meta.verbose_name, - 'Can edit %s values' % cls._meta.verbose_name, - ) - if permission not in cls._meta.permissions: - # Add a permission for the value editor - try: - cls._meta.permissions.append(permission) - except AttributeError: - # Permissions were supplied as a tuple, so preserve that - cls._meta.permissions = tuple(cls._meta.permissions + (permission,)) + def key(self): + return self.module_name, self.class_name, self.attribute_name + key = property(key) - # Set up cache storage for external access - _values[self.key].descriptor = self - if (self.app_label, self.model) not in _values_by_model: - _values_by_model[self.app_label, self.model] = [] - by_model_list = _values_by_model[self.app_label, self.model] - by_model_list.insert(bisect(by_model_list, self), self) + def contribute_to_class(self, cls, attribute_name): + if not issubclass(cls, Options): + pass#return + self.module_name = cls.__module__ + self.class_name = '' + self.attribute_name = attribute_name + self.description = self.description or attribute_name.replace('_', ' ') - setattr(cls, self.name, self) + setattr(cls, self.attribute_name, self) def __get__(self, instance=None, type=None): - if instance != None: - raise AttributeError, "%s isn't accessible via %s instances" % (self.name, type.__name__) + if instance == None: + raise AttributeError, "%s is only accessible from %s instances." % (self.attribute_name, type.__name__) value = _values.get(self.key, None) if value: return self.to_python(value.content) diff --git a/models.py b/models.py index 9e41632..265e9e3 100644 --- a/models.py +++ b/models.py @@ -1,9 +1,9 @@ from django.db import models class Value(models.Model): - app_label = models.CharField(maxlength=255) - model = models.CharField(maxlength=255) - name = models.CharField(maxlength=255) + module_name = models.CharField(maxlength=255) + class_name = models.CharField(maxlength=255, blank=True) + attribute_name = models.CharField(maxlength=255) content = models.CharField(maxlength=255) def __nonzero__(self): diff --git a/templates/admin/edit_values.html b/templates/admin/edit_values.html index 5f6ab1e..5a5281e 100644 --- a/templates/admin/edit_values.html +++ b/templates/admin/edit_values.html @@ -19,19 +19,21 @@

{% endif %} {% if form.fields %} -{% regroup form by app_label as apps %} +{% regroup form by module_name as modules %}
- {% for app in apps %} + {% for module in modules %}
- - - {% regroup app.list by model_name as models %} - {% for model in models %} - - - - - {% for field in model.list %} +
{% blocktrans with app.grouper|capfirst as name %}{{ name }}{% endblocktrans %}
{% blocktrans with model.grouper|capfirst as name %}{{ name }}{% endblocktrans %} 
+ + {% regroup module.list by class_name as classes %} + {% for class in classes %} + {% if class.grouper %} + + + + + {% endif %} + {% for field in class.list %} {% if field.errors %} diff --git a/views.py b/views.py index b23b376..fe8fd82 100644 --- a/views.py +++ b/views.py @@ -1,54 +1,69 @@ import re -from django.db import models +from django.db.models import get_model from django.http import HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext from django.utils.text import capfirst from django import newforms as forms +from django.newforms.forms import SortedDictFromList from django.contrib.admin.views.decorators import staff_member_required from django.contrib import values -regex = re.compile(r'^(.+)__(.+)__(.+)$') -value_list = [] +regex = re.compile(r'^(.+)__(.*)__(.+)$') + +# This is just a placeholder for now, fields will be added dynamically on each request +class ValueEditor(forms.BaseForm): + + def __iter__(self): + for field in super(ValueEditor, self).__iter__(): + yield self.specialize(field) + + def __getitem__(self, name): + field = super(ValueEditor, self).__getitem__(name) + return self.specialize(field) + + def specialize(self, field): + "Wrapper to add module_name and class_name for regrouping" + field.label = capfirst(field.label) + module_name, class_name, x = regex.match(field.name).groups() + + app_label = module_name.split('.')[-2]; + field.module_name = capfirst(app_label) + + if class_name: + if get_model(app_label, class_name): + class_name = capfirst(get_model(app_label, class_name)._meta.verbose_name) + else: + class_name = capfirst(class_name) + field.class_name = class_name + + return field + +def get_fields(user): + # Retrieves all value fields available for the given user + fields = SortedDictFromList() + for value in values.get_all_values(): + app_label = value.module_name.split('.')[-2] + perm = '%s.can_edit_%s_values' % (app_label, value.class_name.lower()) + initial = '' + if user.has_perm(perm): + # Provide current values for initializing the form + current = values.get_value(*value.key) + initial = current and value.to_editor(current) + + # Add the field to the customized field list + fields['%s__%s__%s' % value.key] = value.field(label=value.description, initial=initial) + return fields def editor(request): + # Create an editor customized for the current user + editor = type('ValueEditor', (ValueEditor,), {'base_fields': get_fields(request.user)}) - # This is just a placeholder for now, fields will be added dynamically below - class ValueEditor(forms.Form): - - def __iter__(self): - for field in super(ValueEditor, self).__iter__(): - yield self.specialize(field) - - def __getitem__(self, name): - field = super(ValueEditor, self).__getitem__(name) - return self.specialize(field) - - def specialize(self, field): - " Wrapper to add app_label and model_name for regrouping" - field.label = capfirst(field.label) - field.app_label, field.model_name, dummy = regex.match(field.name).groups() - return field - - # Cycle through all apps and models, adding value fields to the form - for app in models.get_apps(): - for model in models.get_models(app): - app_label, model_name = model._meta.app_label, model.__name__.lower() - try: - for value in values.get_values_by_model(app_label, model_name): - if request.user.has_perm('%s.can_edit_%s_values' % (app_label, model_name)): - value_list.append((model, value.name)) - key = '%s__%s__%s' % (app_label, model_name, value.name) - field = value.field(label=value.description) - ValueEditor.base_fields[key] = field - except KeyError: - continue - if request.method == 'POST': # Populate the form with user-submitted data - form = ValueEditor(request.POST.copy()) + form = editor(request.POST.copy()) if form.is_valid(): form.full_clean() for name, value in form.clean_data.items(): @@ -56,21 +71,16 @@ def specialize(self, field): descriptor = values.get_descriptor(*key) try: current_value = descriptor.to_python(values.get_value(*key)) - except ValueError: + except: current_value = None if current_value != descriptor.to_python(value): args = key + (value,) values.set_value(*args) - request.user.message_set.create(message='Updated %s' % descriptor.description) + request.user.message_set.create(message='Updated %s on %s' % (descriptor.description, descriptor.class_name)) return HttpResponseRedirect(request.path) else: - # Populate the form with values currently in use - value_dict = {} - for name in ValueEditor.base_fields: - key = regex.match(name).groups() - descriptor = values.get_descriptor(*key) - value_dict[name] = descriptor.to_editor(values.get_value(*key)) - form = ValueEditor(value_dict) + # Leave the form populated with current values + form = editor() return render_to_response('admin/edit_values.html', { 'form': form,
{% blocktrans with module.grouper as name %}{{ name }}{% endblocktrans %}
{% blocktrans with class.grouper as name %}{{ name }}{% endblocktrans %} 
{{ field.errors }}