Minimal support for change forms in the admin

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1683 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-12-16 02:16:59 +00:00
parent f6aa8baf52
commit a01eab5049
6 changed files with 319 additions and 473 deletions

View File

@ -1,8 +1,8 @@
{% load admin_modify adminmedia %} {% load admin_modify adminmedia %}
{% output_all bound_field.form_fields %} {% output_all bound_field.form_fields %}
{% if bound_field.raw_id_admin %} {% if bound_field.raw_id_admin %}
<a href="../../../{{ bound_field.field.rel.to._meta.app_label }}/{{ bound_field.field.rel.to._meta.module_name }}/" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a> FIXURL<a href="" class="related-lookup" id="lookup_{{ bound_field.element_id }}" onclick="return showRelatedObjectLookupPopup(this);"> <img src="{% admin_media_prefix %}img/admin/selector-search.gif" width="16" height="16" alt="Lookup"></a>
{% else %} {% else %}
{% if bound_field.needs_add_label %} {% if bound_field.needs_add_label %}
<a href="../../../{{ bound_field.field.rel.to._meta.app_label }}/{{ bound_field.field.rel.to._meta.module_name }}/add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a> FIXURL<a href="/add/" class="add-another" id="add_{{ bound_field.element_id }}" onclick="return showAddAnotherPopup(this);"> <img src="{% admin_media_prefix %}img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/></a>
{% endif %}{% endif %} {% endif %}{% endif %}

View File

@ -1,10 +1,11 @@
from django.core import template, template_loader, meta from django.core import template, template_loader
from django.utils.html import escape from django.utils.html import escape
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.functional import curry from django.utils.functional import curry
from django.contrib.admin.views.main import AdminBoundField from django.contrib.admin.views.main import AdminBoundField
from django.db.models.fields import BoundField, Field from django.db.models.fields import BoundField, Field
from django.db.models import BoundRelatedObject, TABULAR, STACKED from django.db.models import BoundRelatedObject, TABULAR, STACKED
from django.db import models
from django.conf.settings import ADMIN_MEDIA_PREFIX from django.conf.settings import ADMIN_MEDIA_PREFIX
import re import re
@ -42,7 +43,7 @@ submit_row = register.inclusion_tag('admin/submit_line', takes_context=True)(sub
#@register.simple_tag #@register.simple_tag
def field_label(bound_field): def field_label(bound_field):
class_names = [] class_names = []
if isinstance(bound_field.field, meta.BooleanField): if isinstance(bound_field.field, models.BooleanField):
class_names.append("vCheckboxLabel") class_names.append("vCheckboxLabel")
colon = "" colon = ""
else: else:
@ -135,6 +136,8 @@ class StackedBoundRelatedObject(BoundRelatedObject):
def __init__(self, related_object, field_mapping, original): def __init__(self, related_object, field_mapping, original):
super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original) super(StackedBoundRelatedObject, self).__init__(related_object, field_mapping, original)
fields = self.relation.editable_fields() fields = self.relation.editable_fields()
self.field_mappings.fill()
print self.field_mappings.__dict__
self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields) self.form_field_collection_wrappers = [FormFieldCollectionWrapper(field_mapping ,fields)
for field_mapping in self.field_mappings] for field_mapping in self.field_mappings]
self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url') self.show_url = original and hasattr(self.relation.opts, 'get_absolute_url')
@ -241,7 +244,7 @@ def admin_field_line(context, argument_val):
break break
# Assumes BooleanFields won't be stacked next to each other! # Assumes BooleanFields won't be stacked next to each other!
if isinstance(bound_fields[0].field, meta.BooleanField): if isinstance(bound_fields[0].field, models.BooleanField):
class_names.append('checkbox-row') class_names.append('checkbox-row')
return { return {

View File

@ -67,7 +67,6 @@ def find_model(mod, remaining):
if hasattr(mod, '_MODELS'): if hasattr(mod, '_MODELS'):
name = remaining[0] name = remaining[0]
for model in mod._MODELS: for model in mod._MODELS:
print "'%s'" % name, model
if model.__name__.lower() == name: if model.__name__.lower() == name:
return model return model
raise Http404 raise Http404
@ -391,29 +390,32 @@ class AdminBoundFieldSet(BoundFieldSet):
super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine) super(AdminBoundFieldSet, self).__init__(field_set, field_mapping, original, AdminBoundFieldLine)
class BoundManipulator(object): class BoundManipulator(object):
def __init__(self, opts, manipulator, field_mapping): def __init__(self, model, manipulator, field_mapping):
self.inline_related_objects = opts.get_followed_related_objects(manipulator.follow) self.model = model
self.opts = model._meta
self.inline_related_objects = self.opts.get_followed_related_objects(manipulator.follow)
self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None self.original = hasattr(manipulator, 'original_object') and manipulator.original_object or None
self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet) self.bound_field_sets = [field_set.bind(field_mapping, self.original, AdminBoundFieldSet)
for field_set in opts.admin.get_field_sets(opts)] for field_set in self.opts.admin.get_field_sets(self.opts)]
self.ordered_objects = opts.get_ordered_objects()[:] self.ordered_objects = self.opts.get_ordered_objects()[:]
class AdminBoundManipulator(BoundManipulator): class AdminBoundManipulator(BoundManipulator):
def __init__(self, opts, manipulator, field_mapping): def __init__(self, model, manipulator, field_mapping):
super(AdminBoundManipulator, self).__init__(opts, manipulator, field_mapping) super(AdminBoundManipulator, self).__init__(model, manipulator, field_mapping)
field_sets = opts.admin.get_field_sets(opts) field_sets = self.opts.admin.get_field_sets(self.opts)
self.auto_populated_fields = [f for f in opts.fields if f.prepopulate_from] self.auto_populated_fields = [f for f in self.opts.fields if f.prepopulate_from]
self.javascript_imports = get_javascript_imports(opts, self.auto_populated_fields, self.ordered_objects, field_sets); self.javascript_imports = get_javascript_imports(self.opts, self.auto_populated_fields, self.ordered_objects, field_sets);
self.coltype = self.ordered_objects and 'colMS' or 'colM' self.coltype = self.ordered_objects and 'colMS' or 'colM'
self.has_absolute_url = hasattr(opts.get_model_module().Klass, 'get_absolute_url') self.has_absolute_url = hasattr(model, 'get_absolute_url')
self.form_enc_attrib = opts.has_field_type(models.FileField) and \ self.form_enc_attrib = self.opts.has_field_type(models.FileField) and \
'enctype="multipart/form-data" ' or '' 'enctype="multipart/form-data" ' or ''
self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id(); self.first_form_field_id = self.bound_field_sets[0].bound_field_lines[0].bound_fields[0].form_fields[0].get_id();
self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects] self.ordered_object_pk_names = [o.pk.name for o in self.ordered_objects]
opts = self.opts
self.save_on_top = opts.admin.save_on_top self.save_on_top = opts.admin.save_on_top
self.save_as = opts.admin.save_as self.save_as = opts.admin.save_as
@ -428,11 +430,12 @@ class AdminBoundManipulator(BoundManipulator):
return str(getattr(ordered_obj, name)) return str(getattr(ordered_obj, name))
return "" return ""
def render_change_form(opts, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''): def render_change_form(model, manipulator, app_label, context, add=False, change=False, show_delete=False, form_url=''):
opts = model._meta
extra_context = { extra_context = {
'add': add, 'add': add,
'change': change, 'change': change,
'bound_manipulator': AdminBoundManipulator(opts, manipulator, context['form']), 'bound_manipulator': AdminBoundManipulator(model, manipulator, context['form']),
'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()], 'has_delete_permission': context['perms'][app_label][opts.get_delete_permission()],
'form_url': form_url, 'form_url': form_url,
'app_label': app_label, 'app_label': app_label,
@ -527,7 +530,8 @@ def change_stage(request, path, object_id):
if request.POST and request.POST.has_key("_saveasnew"): if request.POST and request.POST.has_key("_saveasnew"):
return add_stage(request, path, form_url='../add/') return add_stage(request, path, form_url='../add/')
try: try:
manipulator = mod.ChangeManipulator(object_id) manipulator_class = model.ChangeManipulator
manipulator = manipulator_class(object_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404 raise Http404
@ -596,7 +600,7 @@ def change_stage(request, path, object_id):
'is_popup' : request.REQUEST.has_key('_popup') 'is_popup' : request.REQUEST.has_key('_popup')
}) })
return render_change_form(opts,manipulator, app_label, c, change=True) return render_change_form(model,manipulator, app_label, c, change=True)
def _nest_help(obj, depth, val): def _nest_help(obj, depth, val):
current = obj current = obj

View File

@ -10,7 +10,7 @@ class EmptyValue(Exception):
"This is raised when empty data is provided" "This is raised when empty data is provided"
pass pass
class Manipulator: class Manipulator(object):
# List of permission strings. User must have at least one to manipulate. # List of permission strings. User must have at least one to manipulate.
# None means everybody has permission. # None means everybody has permission.
required_permission = '' required_permission = ''

View File

@ -243,9 +243,11 @@ class RelatedObject(object):
def get_manipulator_fields(self, opts, manipulator, change, follow): def get_manipulator_fields(self, opts, manipulator, change, follow):
# TODO: Remove core fields stuff. # TODO: Remove core fields stuff.
if change:
if manipulator.original_object:
meth_name = 'get_%s_count' % self.get_method_name_part() meth_name = 'get_%s_count' % self.get_method_name_part()
count = getattr(manipulator.original_object, meth_name)() count = getattr(manipulator.original_object, meth_name)()
count += self.field.rel.num_extra_on_change count += self.field.rel.num_extra_on_change
if self.field.rel.min_num_in_admin: if self.field.rel.min_num_in_admin:
count = max(count, self.field.rel.min_num_in_admin) count = max(count, self.field.rel.min_num_in_admin)
@ -253,7 +255,6 @@ class RelatedObject(object):
count = min(count, self.field.rel.max_num_in_admin) count = min(count, self.field.rel.max_num_in_admin)
else: else:
count = self.field.rel.num_in_admin count = self.field.rel.num_in_admin
fields = [] fields = []
for i in range(count): for i in range(count):
for f in self.opts.fields + self.opts.many_to_many: for f in self.opts.fields + self.opts.many_to_many:
@ -416,6 +417,9 @@ class Options:
raise AssertionError, "A model can't have more than one AutoField." raise AssertionError, "A model can't have more than one AutoField."
elif is_auto: elif is_auto:
self.has_auto_field = True self.has_auto_field = True
#HACK
self.limit_choices_to = {}
def __repr__(self): def __repr__(self):
return '<Options for %s>' % self.module_name return '<Options for %s>' % self.module_name
@ -426,8 +430,11 @@ class Options:
def get_content_type_id(self): def get_content_type_id(self):
"Returns the content-type ID for this object type." "Returns the content-type ID for this object type."
if not hasattr(self, '_content_type_id'): if not hasattr(self, '_content_type_id'):
mod = get_module('core', 'contenttypes') import django.models.core
self._content_type_id = mod.get_object(python_module_name__exact=self.module_name, package__label__exact=self.app_label).id manager = django.models.core.ContentType.objects
self._content_type_id = \
manager.get_object(python_module_name__exact=self.module_name,
package__label__exact=self.app_label).id
return self._content_type_id return self._content_type_id
def get_field(self, name, many_to_many=True): def get_field(self, name, many_to_many=True):
@ -505,11 +512,12 @@ class Options:
"Returns a list of Options objects that are ordered with respect to this object." "Returns a list of Options objects that are ordered with respect to this object."
if not hasattr(self, '_ordered_objects'): if not hasattr(self, '_ordered_objects'):
objects = [] objects = []
for klass in get_app(self.app_label)._MODELS: #HACK
opts = klass._meta #for klass in get_app(self.app_label)._MODELS:
if opts.order_with_respect_to and opts.order_with_respect_to.rel \ # opts = klass._meta
and self == opts.order_with_respect_to.rel.to._meta: # if opts.order_with_respect_to and opts.order_with_respect_to.rel \
objects.append(opts) # and self == opts.order_with_respect_to.rel.to._meta:
# objects.append(opts)
self._ordered_objects = objects self._ordered_objects = objects
return self._ordered_objects return self._ordered_objects
@ -740,6 +748,268 @@ class Manager(object):
# objects -- MySQL returns the values as strings, instead. # objects -- MySQL returns the values as strings, instead.
return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
class ManipulatorDescriptor(object):
class empty:
pass
def __init__(self, name, base):
self.man = None
self.name = name
self.base = base
def __get__(self, instance, type=None):
if instance != None:
raise "Manipulator accessed via instance"
else:
class Man(self.get_base_manipulator(type), self.base):
pass
Man.classinit(type)
Man.__name__ = self.name
return Man
def get_base_manipulator(self, type):
if hasattr(type, 'MANIPULATOR'):
man = type.MANIPULATOR
else:
man = self.empty
return man
class AutomaticManipulator(formfields.Manipulator):
def classinit(cls, model):
cls.model = model
cls.manager = model._default_manager
cls.opts = model._meta
for field_name_list in cls.opts.unique_together:
setattr(cls, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
for f in cls.opts.fields:
if f.unique_for_date:
setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_date), opts, 'date'))
if f.unique_for_month:
setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_month), opts, 'month'))
if f.unique_for_year:
setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_year), opts, 'year'))
classinit = classmethod(classinit)
def __init__(self, original_object= None, follow=None):
self.follow = self.model._meta.get_follow(follow)
self.fields = []
self.original_object = original_object
for f in self.opts.fields + self.opts.many_to_many:
if self.follow.get(f.name, False):
self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change))
# Add fields for related objects.
for f in self.opts.get_all_related_objects():
if self.follow.get(f.name, False):
print f.name
fol = self.follow[f.name]
fields = f.get_manipulator_fields(self.opts, self, self.change, fol)
print fields
self.fields.extend(fields)
def save(self, new_data):
add, change, opts, klass = self.add, self.change, self.opts, self.model
# TODO: big cleanup when core fields go -> use recursive manipulators.
from django.utils.datastructures import DotExpandedDict
params = {}
for f in opts.fields:
# Fields with auto_now_add should keep their original value in the change stage.
auto_now_add = change and getattr(f, 'auto_now_add', False)
if self.follow.get(f.name, None) and not auto_now_add:
param = f.get_manipulator_new_data(new_data)
else:
if change:
param = getattr(self.original_object, f.attname)
else:
param = f.get_default()
params[f.attname] = param
if change:
params[opts.pk.attname] = self.obj_key
# First, save the basic object itself.
new_object = klass(**params)
new_object.save()
# Now that the object's been saved, save any uploaded files.
for f in opts.fields:
if isinstance(f, FileField):
f.save_file(new_data, new_object, change and self.original_object or None, change, rel=False)
# Calculate which primary fields have changed.
if change:
self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
for f in opts.fields:
if not f.primary_key and str(getattr(self.original_object, f.attname)) != str(getattr(new_object, f.attname)):
self.fields_changed.append(f.verbose_name)
# Save many-to-many objects. Example: Poll.set_sites()
for f in opts.many_to_many:
if self.follow.get(f.name, None):
if not f.rel.edit_inline:
if f.rel.raw_id_admin:
new_vals = new_data.get(f.name, ())
else:
new_vals = new_data.getlist(f.name)
was_changed = getattr(new_object, 'set_%s' % f.name)(new_vals)
if change and was_changed:
self.fields_changed.append(f.verbose_name)
expanded_data = DotExpandedDict(dict(new_data))
# Save many-to-one objects. Example: Add the Choice objects for a Poll.
for related in opts.get_all_related_objects():
# Create obj_list, which is a DotExpandedDict such as this:
# [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
# ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
# ('2', {'id': [''], 'choice': ['']})]
child_follow = self.follow.get(related.name, None)
if child_follow:
obj_list = expanded_data[related.var_name].items()
obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
params = {}
# For each related item...
for _, rel_new_data in obj_list:
# Keep track of which core=True fields were provided.
# If all core fields were given, the related object will be saved.
# If none of the core fields were given, the object will be deleted.
# If some, but not all, of the fields were given, the validator would
# have caught that.
all_cores_given, all_cores_blank = True, True
# Get a reference to the old object. We'll use it to compare the
# old to the new, to see which fields have changed.
old_rel_obj = None
if change:
if rel_new_data[related.opts.pk.name][0]:
try:
old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_method_name_part() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]})
except ObjectDoesNotExist:
pass
for f in related.opts.fields:
if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''):
all_cores_given = False
elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''):
all_cores_blank = False
# If this field isn't editable, give it the same value it had
# previously, according to the given ID. If the ID wasn't
# given, use a default value. FileFields are also a special
# case, because they'll be dealt with later.
if f == related.field:
param = getattr(new_object, related.field.rel.field_name)
elif add and isinstance(f, AutoField):
param = None
elif change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
if old_rel_obj:
param = getattr(old_rel_obj, f.column)
else:
param = f.get_default()
else:
param = f.get_manipulator_new_data(rel_new_data, rel=True)
if param != None:
params[f.attname] = param
# Create the related item.
new_rel_obj = related.opts.get_model_module().Klass(**params)
# If all the core fields were provided (non-empty), save the item.
if all_cores_given:
new_rel_obj.save()
# Save any uploaded files.
for f in related.opts.fields:
if child_follow.get(f.name, None):
if isinstance(f, FileField) and rel_new_data.get(f.name, False):
f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True)
# Calculate whether any fields have changed.
if change:
if not old_rel_obj: # This object didn't exist before.
self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
else:
for f in related.opts.fields:
if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)):
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# Save many-to-many objects.
for f in related.opts.many_to_many:
if child_follow.get(f.name, None) and not f.rel.edit_inline:
was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname])
if change and was_changed:
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# If, in the change stage, all of the core fields were blank and
# the primary key (ID) was provided, delete the item.
if change and all_cores_blank and old_rel_obj:
new_rel_obj.delete()
self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
# Save the order, if applicable.
if change and opts.get_ordered_objects():
order = new_data['order_'] and map(int, new_data['order_'].split(',')) or []
for rel_opts in opts.get_ordered_objects():
getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
return new_object
def get_related_objects(self):
return self.opts.get_followed_related_objects(self.follow)
def flatten_data(self):
new_data = {}
for f in self.opts.get_data_holders(self.follow):
fol = self.follow.get(f.name)
new_data.update(f.flatten_data(fol, self.original_object))
return new_data
class ModelAddManipulator(AutomaticManipulator):
change = False
add = True
def __init__(self, follow=None):
super(ModelAddManipulator, self).__init__(follow=follow)
class ModelChangeManipulator(AutomaticManipulator):
change = False
add = True
def __init__(self, obj_key=None, follow=None):
assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
self.obj_key = obj_key
try:
original_object = self.__class__.manager.get_object(pk=obj_key)
except ObjectDoesNotExist:
# If the object doesn't exist, this might be a manipulator for a
# one-to-one related object that hasn't created its subobject yet.
# For example, this might be a Restaurant for a Place that doesn't
# yet have restaurant information.
if opts.one_to_one_field:
# Sanity check -- Make sure the "parent" object exists.
# For example, make sure the Place exists for the Restaurant.
# Let the ObjectDoesNotExist exception propogate up.
lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
_ = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
params = dict([(f.attname, f.get_default()) for f in opts.fields])
params[opts.pk.attname] = obj_key
original_object = opts.get_model_module().Klass(**params)
else:
raise
print "calling super"
super(ModelChangeManipulator, self).__init__(original_object=original_object, follow=follow)
print "Back"
self.original_object = original_object
if self.opts.get_ordered_objects():
self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
class ModelBase(type): class ModelBase(type):
"Metaclass for all models" "Metaclass for all models"
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
@ -850,11 +1120,16 @@ class ModelBase(type):
related = RelatedObject(other._meta, new_class, field) related = RelatedObject(other._meta, new_class, field)
field.contribute_to_related_class(other, related) field.contribute_to_related_class(other, related)
return new_class return new_class
class Model(object): class Model(object):
__metaclass__ = ModelBase __metaclass__ = ModelBase
AddManipulator = ManipulatorDescriptor('AddManipulator', ModelAddManipulator)
ChangeManipulator = ManipulatorDescriptor('ChangeManipulator', ModelChangeManipulator)
def __repr__(self): def __repr__(self):
return '<%s object>' % self.__class__.__name__ return '<%s object>' % self.__class__.__name__
@ -1051,32 +1326,6 @@ class Model(object):
delete.alters_data = True delete.alters_data = True
def __get_manipulator(cls):
if hasattr(cls, 'MANIPULATOR'):
man = cls.MANIPULATOR
else:
class man:
pass
return man
__get_manipulator=classmethod(__get_manipulator)
def __get_add_manipulator(cls):
class AddManipulator(cls.__get_manipulator(), ModelAddManipulator):
pass
AddManipulator.classinit(cls)
cls.AddManipulator = AddManipulator
return AddManipulator
AddManipulator = property(classmethod(__get_add_manipulator))
def __get_change_manipulator(cls):
class ChangeManipulator(cls.__get_manipulator(), ModelChangeManipulator):
pass
ChangeManipulator.classinit(cls)
cls.ChangeManipulator = ChangeManipulator
return ChangeManipulator
ChangeManipulator = property(classmethod(__get_change_manipulator))
def __get_FIELD_display(self, field): def __get_FIELD_display(self, field):
value = getattr(self, field.attname) value = getattr(self, field.attname)
@ -1288,6 +1537,7 @@ class Model(object):
############################################ ############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) # # HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################ ############################################
@ -1510,6 +1760,8 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
# HELPER FUNCTIONS (MANIPULATORS) # # HELPER FUNCTIONS (MANIPULATORS) #
################################### ###################################
def get_manipulator(opts, klass, extra_methods, add=False, change=False): def get_manipulator(opts, klass, extra_methods, add=False, change=False):
"Returns the custom Manipulator (either add or change) for the given opts." "Returns the custom Manipulator (either add or change) for the given opts."
assert (add == False or change == False) and add != change, "get_manipulator() can be passed add=True or change=True, but not both" assert (add == False or change == False) and add != change, "get_manipulator() can be passed add=True or change=True, but not both"
@ -1522,420 +1774,6 @@ def get_manipulator(opts, klass, extra_methods, add=False, change=False):
return man return man
class AutomaticManipulator(formfields.Manipulator):
def classinit(cls, model):
cls.model = model
cls.manager = model._default_manager
cls.opts = model._meta
for field_name_list in cls.opts.unique_together:
setattr(cls, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts))
for f in cls.opts.fields:
if f.unique_for_date:
setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_date), opts, 'date'))
if f.unique_for_month:
setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_month), opts, 'month'))
if f.unique_for_year:
setattr(man, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, opts.get_field(f.unique_for_year), opts, 'year'))
classinit = classmethod(classinit)
def __init__(self, follow=None):
self.follow = self.model._meta.get_follow(follow)
self.fields = []
for f in opts.fields + opts.many_to_many:
if self.follow.get(f.name, False):
self.fields.extend(f.get_manipulator_fields(opts, self, change))
# Add fields for related objects.
for f in opts.get_all_related_objects():
if self.follow.get(f.name, False):
fol = self.follow[f.name]
self.fields.extend(f.get_manipulator_fields(opts, self, change, fol))
self.original_object = None
def save(self, new_data):
add, change, opts, klass = self.add, self.change, self.opts, self.model
# TODO: big cleanup when core fields go -> use recursive manipulators.
from django.utils.datastructures import DotExpandedDict
params = {}
for f in opts.fields:
# Fields with auto_now_add should keep their original value in the change stage.
auto_now_add = change and getattr(f, 'auto_now_add', False)
if self.follow.get(f.name, None) and not auto_now_add:
param = f.get_manipulator_new_data(new_data)
else:
if change:
param = getattr(self.original_object, f.attname)
else:
param = f.get_default()
params[f.attname] = param
if change:
params[opts.pk.attname] = self.obj_key
# First, save the basic object itself.
new_object = klass(**params)
new_object.save()
# Now that the object's been saved, save any uploaded files.
for f in opts.fields:
if isinstance(f, FileField):
f.save_file(new_data, new_object, change and self.original_object or None, change, rel=False)
# Calculate which primary fields have changed.
if change:
self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
for f in opts.fields:
if not f.primary_key and str(getattr(self.original_object, f.attname)) != str(getattr(new_object, f.attname)):
self.fields_changed.append(f.verbose_name)
# Save many-to-many objects. Example: Poll.set_sites()
for f in opts.many_to_many:
if self.follow.get(f.name, None):
if not f.rel.edit_inline:
if f.rel.raw_id_admin:
new_vals = new_data.get(f.name, ())
else:
new_vals = new_data.getlist(f.name)
was_changed = getattr(new_object, 'set_%s' % f.name)(new_vals)
if change and was_changed:
self.fields_changed.append(f.verbose_name)
expanded_data = DotExpandedDict(dict(new_data))
# Save many-to-one objects. Example: Add the Choice objects for a Poll.
for related in opts.get_all_related_objects():
# Create obj_list, which is a DotExpandedDict such as this:
# [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
# ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
# ('2', {'id': [''], 'choice': ['']})]
child_follow = self.follow.get(related.name, None)
if child_follow:
obj_list = expanded_data[related.var_name].items()
obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
params = {}
# For each related item...
for _, rel_new_data in obj_list:
# Keep track of which core=True fields were provided.
# If all core fields were given, the related object will be saved.
# If none of the core fields were given, the object will be deleted.
# If some, but not all, of the fields were given, the validator would
# have caught that.
all_cores_given, all_cores_blank = True, True
# Get a reference to the old object. We'll use it to compare the
# old to the new, to see which fields have changed.
old_rel_obj = None
if change:
if rel_new_data[related.opts.pk.name][0]:
try:
old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_method_name_part() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]})
except ObjectDoesNotExist:
pass
for f in related.opts.fields:
if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''):
all_cores_given = False
elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''):
all_cores_blank = False
# If this field isn't editable, give it the same value it had
# previously, according to the given ID. If the ID wasn't
# given, use a default value. FileFields are also a special
# case, because they'll be dealt with later.
if f == related.field:
param = getattr(new_object, related.field.rel.field_name)
elif add and isinstance(f, AutoField):
param = None
elif change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
if old_rel_obj:
param = getattr(old_rel_obj, f.column)
else:
param = f.get_default()
else:
param = f.get_manipulator_new_data(rel_new_data, rel=True)
if param != None:
params[f.attname] = param
# Create the related item.
new_rel_obj = related.opts.get_model_module().Klass(**params)
# If all the core fields were provided (non-empty), save the item.
if all_cores_given:
new_rel_obj.save()
# Save any uploaded files.
for f in related.opts.fields:
if child_follow.get(f.name, None):
if isinstance(f, FileField) and rel_new_data.get(f.name, False):
f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True)
# Calculate whether any fields have changed.
if change:
if not old_rel_obj: # This object didn't exist before.
self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
else:
for f in related.opts.fields:
if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)):
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# Save many-to-many objects.
for f in related.opts.many_to_many:
if child_follow.get(f.name, None) and not f.rel.edit_inline:
was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname])
if change and was_changed:
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# If, in the change stage, all of the core fields were blank and
# the primary key (ID) was provided, delete the item.
if change and all_cores_blank and old_rel_obj:
new_rel_obj.delete()
self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
# Save the order, if applicable.
if change and opts.get_ordered_objects():
order = new_data['order_'] and map(int, new_data['order_'].split(',')) or []
for rel_opts in opts.get_ordered_objects():
getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
return new_object
def get_related_objects(opts, klass, add, change, self):
return opts.get_followed_related_objects(self.follow)
def flatten_data(opts, klass, add, change, self):
new_data = {}
for f in opts.get_data_holders(self.follow):
fol = self.follow.get(f.name)
new_data.update(f.flatten_data(fol, self.original_object))
return new_data
class ModelAddManipulator(AutomaticManipulator):
change = False
add = True
class ModelChangeManipulator(AutomaticManipulator):
change = False
add = True
def __init__(self, obj_key=None, follow=None):
assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
self.obj_key = obj_key
try:
original_object = self.manager.get_object(pk=obj_key)
except ObjectDoesNotExist:
# If the object doesn't exist, this might be a manipulator for a
# one-to-one related object that hasn't created its subobject yet.
# For example, this might be a Restaurant for a Place that doesn't
# yet have restaurant information.
if opts.one_to_one_field:
# Sanity check -- Make sure the "parent" object exists.
# For example, make sure the Place exists for the Restaurant.
# Let the ObjectDoesNotExist exception propogate up.
lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
_ = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
params = dict([(f.attname, f.get_default()) for f in opts.fields])
params[opts.pk.attname] = obj_key
original_object = opts.get_model_module().Klass(**params)
else:
raise
super(ModelSaveManipulator, self).__init__(self, follow=follow)
self.original_object = original_object
if opts.get_ordered_objects():
self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
#def manipulator_init(opts, add, change, self, obj_key=None, follow=None):
# self.follow = opts.get_follow(follow)
#
# if change:
# assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
# self.obj_key = obj_key
# try:
# self.original_object = opts.get_model_module().get_object(pk=obj_key)
# except ObjectDoesNotExist:
# # If the object doesn't exist, this might be a manipulator for a
# # one-to-one related object that hasn't created its subobject yet.
# # For example, this might be a Restaurant for a Place that doesn't
# # yet have restaurant information.
# if opts.one_to_one_field:
# # Sanity check -- Make sure the "parent" object exists.
# # For example, make sure the Place exists for the Restaurant.
# # Let the ObjectDoesNotExist exception propogate up.
# lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to
# lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key
# _ = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs)
# params = dict([(f.attname, f.get_default()) for f in opts.fields])
# params[opts.pk.attname] = obj_key
# self.original_object = opts.get_model_module().Klass(**params)
# else:
# raise
# self.fields = []
#
# for f in opts.fields + opts.many_to_many:
# if self.follow.get(f.name, False):
# self.fields.extend(f.get_manipulator_fields(opts, self, change))
#
# # Add fields for related objects.
# for f in opts.get_all_related_objects():
# if self.follow.get(f.name, False):
# fol = self.follow[f.name]
# self.fields.extend(f.get_manipulator_fields(opts, self, change, fol))
#
# # Add field for ordering.
# if change and opts.get_ordered_objects():
# self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_"))
#def manipulator_save(opts, klass, add, change, self, new_data):
# # TODO: big cleanup when core fields go -> use recursive manipulators.
# from django.utils.datastructures import DotExpandedDict
# params = {}
# for f in opts.fields:
# # Fields with auto_now_add should keep their original value in the change stage.
# auto_now_add = change and getattr(f, 'auto_now_add', False)
# if self.follow.get(f.name, None) and not auto_now_add:
# param = f.get_manipulator_new_data(new_data)
# else:
# if change:
# param = getattr(self.original_object, f.attname)
# else:
# param = f.get_default()
# params[f.attname] = param
#
# if change:
# params[opts.pk.attname] = self.obj_key
#
# # First, save the basic object itself.
# new_object = klass(**params)
# new_object.save()
#
# # Now that the object's been saved, save any uploaded files.
# for f in opts.fields:
# if isinstance(f, FileField):
# f.save_file(new_data, new_object, change and self.original_object or None, change, rel=False)
#
# # Calculate which primary fields have changed.
# if change:
# self.fields_added, self.fields_changed, self.fields_deleted = [], [], []
# for f in opts.fields:
# if not f.primary_key and str(getattr(self.original_object, f.attname)) != str(getattr(new_object, f.attname)):
# self.fields_changed.append(f.verbose_name)
#
# # Save many-to-many objects. Example: Poll.set_sites()
# for f in opts.many_to_many:
# if self.follow.get(f.name, None):
# if not f.rel.edit_inline:
# if f.rel.raw_id_admin:
# new_vals = new_data.get(f.name, ())
# else:
# new_vals = new_data.getlist(f.name)
# was_changed = getattr(new_object, 'set_%s' % f.name)(new_vals)
# if change and was_changed:
# self.fields_changed.append(f.verbose_name)
#
# expanded_data = DotExpandedDict(dict(new_data))
# # Save many-to-one objects. Example: Add the Choice objects for a Poll.
# for related in opts.get_all_related_objects():
# # Create obj_list, which is a DotExpandedDict such as this:
# # [('0', {'id': ['940'], 'choice': ['This is the first choice']}),
# # ('1', {'id': ['941'], 'choice': ['This is the second choice']}),
# # ('2', {'id': [''], 'choice': ['']})]
# child_follow = self.follow.get(related.name, None)
#
# if child_follow:
# obj_list = expanded_data[related.var_name].items()
# obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
# params = {}
#
# # For each related item...
# for _, rel_new_data in obj_list:
#
# # Keep track of which core=True fields were provided.
# # If all core fields were given, the related object will be saved.
# # If none of the core fields were given, the object will be deleted.
# # If some, but not all, of the fields were given, the validator would
# # have caught that.
# all_cores_given, all_cores_blank = True, True
#
# # Get a reference to the old object. We'll use it to compare the
# # old to the new, to see which fields have changed.
# old_rel_obj = None
# if change:
# if rel_new_data[related.opts.pk.name][0]:
# try:
# old_rel_obj = getattr(self.original_object, 'get_%s' % related.get_method_name_part() )(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]})
# except ObjectDoesNotExist:
# pass
#
# for f in related.opts.fields:
# if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''):
# all_cores_given = False
# elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''):
# all_cores_blank = False
# # If this field isn't editable, give it the same value it had
# # previously, according to the given ID. If the ID wasn't
# # given, use a default value. FileFields are also a special
# # case, because they'll be dealt with later.
#
# if f == related.field:
# param = getattr(new_object, related.field.rel.field_name)
# elif add and isinstance(f, AutoField):
# param = None
# elif change and (isinstance(f, FileField) or not child_follow.get(f.name, None)):
# if old_rel_obj:
# param = getattr(old_rel_obj, f.column)
# else:
# param = f.get_default()
# else:
# param = f.get_manipulator_new_data(rel_new_data, rel=True)
# if param != None:
# params[f.attname] = param
#
# # Create the related item.
# new_rel_obj = related.opts.get_model_module().Klass(**params)
#
# # If all the core fields were provided (non-empty), save the item.
# if all_cores_given:
# new_rel_obj.save()
#
# # Save any uploaded files.
# for f in related.opts.fields:
# if child_follow.get(f.name, None):
# if isinstance(f, FileField) and rel_new_data.get(f.name, False):
# f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True)
#
# # Calculate whether any fields have changed.
# if change:
# if not old_rel_obj: # This object didn't exist before.
# self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
# else:
# for f in related.opts.fields:
# if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)):
# self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
#
# # Save many-to-many objects.
# for f in related.opts.many_to_many:
# if child_follow.get(f.name, None) and not f.rel.edit_inline:
# was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname])
# if change and was_changed:
# self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
#
# # If, in the change stage, all of the core fields were blank and
# # the primary key (ID) was provided, delete the item.
# if change and all_cores_blank and old_rel_obj:
# new_rel_obj.delete()
# self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
#
# # Save the order, if applicable.
# if change and opts.get_ordered_objects():
# order = new_data['order_'] and map(int, new_data['order_'].split(',')) or []
# for rel_opts in opts.get_ordered_objects():
# getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order)
# return new_object
def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data):

View File

@ -311,9 +311,10 @@ class Field(object):
first_choice = include_blank and blank_choice or [] first_choice = include_blank and blank_choice or []
if self.choices: if self.choices:
return first_choice + list(self.choices) return first_choice + list(self.choices)
rel_model = self.rel.to rel_model = self.rel.to
return first_choice + [(getattr(x, rel_model._meta.pk.attname), str(x)) return first_choice + [(getattr(x, rel_model._meta.pk.attname), str(x))
for x in rel_model._default_manager.get_list(**self.rel._meta.limit_choices_to)] for x in rel_model._default_manager.get_list(**rel_model._meta.limit_choices_to)]
def get_choices_default(self): def get_choices_default(self):
if(self.radio_admin): if(self.radio_admin):