将 ArrayField 替换为 django-admin 中其他模型的几个字段
Replace ArrayField for several fields from other models in django-admin
我想将模型字段存储为其他原子模型的外键数组。它使模型更加灵活:我可以 add/remove/inherit(如果它是树节点)任何预定义的属性而无需编程。
让我们从原子模型开始。它包含一些机密属性。
class Liquid(models.Model):
volume = models.DecimalField(max_digits=12, decimal_places=6)
class Granular(models.Model):
weight = models.DecimalField(max_digits=16, decimal_places=9)
class Serial(models.Model):
lot_number = models.CharField(max_length=16)
estimated_release_time = models.DateTimeField()
deadline_time = models.DateTimeField())
我想模拟以下代码。无效,因为:
1. 我想存储几个模型的外键(GenericForeignKey 可以帮忙);
2. ArrayField暂不支持外键数组;
class Entity(models.Model):
arr = ArrayField(models.ForeignKey(DifferentAtomicModels))
仿真如下。其中even numbers是django.contrib.contenttypes.models.ContentType的pk,odd numbers是atomic-Model的pk(atomic-model是从前面偶数得到的)。
class Entity(models.Model):
arr = ArrayField(models.IntegerField(), null=True, blank=True)
好吧,在 django-admin changelist_view 和 changeform_view 我想用从它获得的另一个模型的字段替换 arr 字段。
实现这个的方法是什么?哪里最好干预 django-admin 行为(覆盖表单域,制作自定义小部件,smth。其他......)?现在我正在研究重写 ModelAdmin -> changeform_view、get_form、modelform_factory;但是代码中有很多relations/inheritions,我有点困惑......内联没有帮助,因为没有真正的外键。
好吧,我将展示我实现此目标的有点肮脏的方法。我有 overrided/created changeform_view of admin.ModelAdmin 和对应的 functions/templates: get_rel_fieldsets, change_form.html 可能还有别的...
下面的代码是为与类别一起使用而设计的,它可以从祖先那里继承一些参数(显示为 ro)并指定自己的参数(显示为 rw)。
欢迎任何评论或更正,请注意:我是 python/django 的新手。
# myapp/models/py
from django.contrib.postgres.fields import ArrayField
class MultiRelationalArrayField(ArrayField):
pass
class SomeAtomicModel(models.Model):
someproperty = models.CharField(max_length=16)
someproperty2 = ...
someproperty3 = ...
def modeladmin_params(self):
'''ModelAdmin parameters for atomicmodels can optionaly be specified in this method'''
# Both 'fields' and 'readonly_fields' must exist!
return {
'fields': [('someproperty', 'someproperty2'), 'someproperty3')],
'readonly_fields': [],
}
class Category(MPTTModel):
parent = TreeForeignKey('self', null=True, blank=True,
verbose_name=_("parent"), related_name='children')
name = models.CharField(_("name"), max_length=64)
relations = MultiRelationalArrayField(models.PositiveIntegerField(), null=True, blank=True,
help_text=_("List of even:ContentType.id, odd:object.id. It's store relations to various predefined property sets."))
def get_relations(self):
relations = self.relations or []
return relations
def get_ancestors_relations(self):
'''Return list of relations with relations of all ancestors starts from root'''
relations = []
for ct in self.get_ancestors():
relations.extend(ct.get_relations())
return relations
def get_objects_from_relations(self, rel_list=None):
'''
Return list of objects mentioned in self.get_relations() (by default).
If rel_list specified return list of objects from it.
'''
objects = []
if rel_list is None:
rel = self.get_relations()
if rel == []:
return []
rel_list = rel
if len(rel_list) % 2:
# raise ValueError('List length must be even, NOT odd.') # Doesn't work! Dont' know why
raise RuntimeError('List length must be even, NOT odd.')
for i in range(0, len(rel_list)-1, 2):
objects.append(ContentType.objects.get_for_id(rel_list[i]).get_object_for_this_type(pk=rel_list[i+1]))
return objects
@csrf_protect_m
@transaction.atomic
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
'''Customization for MultiRelationalArrayField support'''
# Start main part of customization
add = object_id is None
all_rform_validated = True
if self.rel_array_fields and not add: # Fiedls from self.rel_array_fields will not be shown on obj addition
to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
# ?TODO: to_field check for rel objs
obj = self.get_object(request, unquote(object_id), to_field)
anc_rel = obj.get_ancestors_relations()
rel_anc_objs = obj.get_objects_from_relations(anc_rel)
rel_objs = obj.get_objects_from_relations()
rel_all_objs = rel_anc_objs + rel_objs
# rel_anc_objs will be readonly
anc_rel_len2 = anc_rel.__len__()//2
if request.method == 'POST' and "_saveasnew" in request.POST:
if rel_all_objs != []:
return HttpResponseNotAllowed(_("'_saveasnew' option is prohibited for objects with realtions in array field."))
# Generate ModelAdmin & ModelForm for rel_all_objs on the fly
# rel_O_A_F = [(rel_obj instance, rel_obj_ModelAdmin instance, rel_obj_ModelForm class), ...]
rel_O_A_F = []
for i, o in enumerate(rel_all_objs):
rNAME = o._meta.model.__name__ + 'RelAdmin'
# To avoid batch ModelAdmin creations we can set ModelAdmin parameters in modeladmin_params() obj.method
if hasattr(o, 'modeladmin_params'):
rparams = o.modeladmin_params()
rfields = rparams['fields']
rreadonly_fields = rparams['readonly_fields']
else:
rfields = None
rreadonly_fields = None
rATTRS = {
'formfield_overrides': self.formfield_overrides,
'readonly_fields': rreadonly_fields or (),
}
# Separation of parameters from ancestors and object parameters
if not anc_rel_len2: # No relations from ancestors
rATTRS.update({
'fieldsets': self.get_rel_fieldsets(request, o, False if not i else True, 1, **{'fields': rfields}),
})
elif i in range(0, anc_rel_len2): # Relations from ancestors, all will be readonly
rfieldsets = self.get_rel_fieldsets(request, o, False if not i else True, 0, **{'fields': rfields})
rATTRS.update({
'fieldsets': rfieldsets,
'readonly_fields': flatten_fieldsets(rfieldsets),
})
elif i == anc_rel_len2: # The first relation of obj (rw)
rfieldsets = self.get_rel_fieldsets(request, o, False, 1, **{'fields': rfields})
rATTRS.update({
'fieldsets': rfieldsets,
})
else: # Next relations of obj (rw)
if rfields:
rATTRS.update({
'fieldsets': [(None, {'fields': rfields})],
})
rADM = type(rNAME, (admin.ModelAdmin,), rATTRS)(o._meta.model, self.admin_site)
rel_O_A_F.append((o, rADM, rADM.get_form(request, o)))
# Generate adminFroms for related objects
extra_context = extra_context or {}
extra_context['rel_anc_adminforms'] = []
extra_context['rel_adminforms'] = []
extra_context['media'] = []
errlst = []
rmedia = self.media
for i, (robj, radm, rForm) in enumerate(rel_O_A_F):
if request.method == 'POST':
rform = rForm(request.POST, request.FILES, instance=robj)
if not rform.is_valid():
all_rform_validated = False
errlst.extend(helpers.AdminErrorList(rform, []))
else:
rform = rForm(instance=robj)
radminForm = helpers.AdminForm(
rform,
list(radm.get_fieldsets(request, robj)),
radm.get_prepopulated_fields(request, robj),
radm.get_readonly_fields(request, robj),
model_admin=radm
)
if i < anc_rel_len2:
extra_context['rel_anc_adminforms'].append(radminForm)
else:
extra_context['rel_adminforms'].append(radminForm)
rmedia = rmedia + radminForm.media
extra_context.update({'media': rmedia})
if errlst:
extra_context.update({'errors': errlst})
# return super(ModelAdmin, self).changeform_view(request, object_id, form_url, extra_context=extra_context)
# Interrupt main part of customization. Then some inserts to official code of django 1.8 (see '# custom' at the end fo line)
to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
if to_field and not self.to_field_allowed(request, to_field):
raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
model = self.model
opts = model._meta
add = object_id is None
if add:
if not self.has_add_permission(request):
raise PermissionDenied
obj = None
else:
obj = self.get_object(request, unquote(object_id), to_field)
if not self.has_change_permission(request, obj):
raise PermissionDenied
if obj is None:
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
'name': force_text(opts.verbose_name), 'key': escape(object_id)})
if request.method == 'POST' and "_saveasnew" in request.POST:
return self.add_view(request, form_url=reverse('admin:%s_%s_add' % (
opts.app_label, opts.model_name),
current_app=self.admin_site.name))
ModelForm = self.get_form(request, obj)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
form_validated = True
new_object = self.save_form(request, form, change=not add)
else:
form_validated = False
new_object = form.instance
formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
# if all_valid(formsets) and form_validated: # official
# Also check MultiRelationalArrayField models forms validation # custom
if all_valid(formsets) and form_validated and all_rform_validated: # custom
self.save_model(request, new_object, form, not add)
self.save_related(request, form, formsets, not add)
if add:
self.log_addition(request, new_object)
return self.response_add(request, new_object)
else:
# Also save models from MultiRelationalArrayField # custom
if self.rel_array_fields: # custom
for robj, radm, rForm in rel_O_A_F: # custom
rform = rForm(request.POST, request.FILES, instance=robj) # custom
rnew_object = radm.save_form(request, rform, change=not add) # custom
radm.save_model(request, rnew_object, rform, not add) # custom
change_message = self.construct_change_message(request, form, formsets)
self.log_change(request, new_object, change_message)
return self.response_change(request, new_object)
else:
if add:
initial = self.get_changeform_initial_data(request)
form = ModelForm(initial=initial)
formsets, inline_instances = self._create_formsets(request, self.model(), change=False)
else:
form = ModelForm(instance=obj)
formsets, inline_instances = self._create_formsets(request, obj, change=True)
adminForm = helpers.AdminForm(
form,
list(self.get_fieldsets(request, obj)),
self.get_prepopulated_fields(request, obj),
self.get_readonly_fields(request, obj),
model_admin=self)
media = self.media + adminForm.media
inline_formsets = self.get_inline_formsets(request, formsets, inline_instances, obj)
for inline_formset in inline_formsets:
media = media + inline_formset.media
context = dict(self.admin_site.each_context(request),
title=(_('Add %s') if add else _('Change %s')) % force_text(opts.verbose_name),
adminform=adminForm,
object_id=object_id,
original=obj,
is_popup=(IS_POPUP_VAR in request.POST or
IS_POPUP_VAR in request.GET),
to_field=to_field,
media=media,
inline_admin_formsets=inline_formsets,
errors=helpers.AdminErrorList(form, formsets),
preserved_filters=self.get_preserved_filters(request),
)
# Add MultiRelationalArrayField models forms errors to main form errors # custom
try: # custom
extra_context['errors'].extend(context['errors']) # custom
except: # custom
pass # custom
try: # custom
extra_context['media'] = context['media'] + extra_context['media'] # custom
except: # custom
pass # custom
context.update(extra_context or {})
return self.render_change_form(request, context, add=add, change=not add, obj=obj, form_url=form_url)
# myapp/admin.py
from django.contrib import admin
from django.forms.models import fields_for_model
from django.contrib.admin.options import (csrf_protect_m, transaction, TO_FIELD_VAR, DisallowedModelAdminToField, PermissionDenied, Http404, unquote, force_text, reverse, escape, all_valid, helpers, IS_POPUP_VAR, flatten_fieldsets, partial, forms, FieldError, modelform_defines_fields, modelform_factory)
class CategoryAdmin(TreeEditor, ModelAdmin)::
rel_array_fields = ['get_ancestors_relations', 'relations']
readonly_rel_array_fields = ['get_ancestors_relations']
rel_fieldsets = [
[_('Parameters inherited from ancestors'), {
'fields': ['get_ancestors_relations', ],
# 'classes': ('collapse', ),
'description': _("They also will be inherited by descendants. You can change them only via changing ancestors parameters (higher levels)."),
}],
[_('Parameters'), {
'fields': ['relations', ],
# 'classes': ('collapse', ),
'description': _('They will be added to inherition.'),
}],
]
change_form_template = 'myapp/change_form.html'
def get_rel_fieldsets(self, request, obj, fields_only, fieldset_index=1, **kwargs):
if 'fields' in kwargs and kwargs['fields'] is not None:
fields = kwargs['fields']
else:
fields = list(fields_for_model(obj._meta.model).keys())
if fields_only:
return [(None, {'fields': fields})]
else:
fieldset = self.rel_fieldsets[fieldset_index]
fieldset[1]['fields'] = fields
return [fieldset]
# myapp/templates/myapp/change_form.html
{% extends "admin/change_form.html" %}
{% block field_sets %}
{{ block.super }}
{% if rel_anc_adminforms %}
{% for adminform in rel_anc_adminforms %}
{% for fieldset in adminform %}
{% if fieldset.name %}
<fieldset class="module aligned {{ fieldset.classes }}">
<h2>{{ fieldset.name }}</h2>
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% endif %}
{% for line in fieldset %}
{% include "biokit/includes/line.html" %}
{% endfor %}
{% endfor %}
{% endfor %}
</fieldset>
{% endif %}
{% if rel_adminforms %}
{% for adminform in rel_adminforms %}
THE_SAME_AS_ABOVE
{% endfor %}
</fieldset>
{% endif %}
{% endblock %}
我想将模型字段存储为其他原子模型的外键数组。它使模型更加灵活:我可以 add/remove/inherit(如果它是树节点)任何预定义的属性而无需编程。
让我们从原子模型开始。它包含一些机密属性。
class Liquid(models.Model):
volume = models.DecimalField(max_digits=12, decimal_places=6)
class Granular(models.Model):
weight = models.DecimalField(max_digits=16, decimal_places=9)
class Serial(models.Model):
lot_number = models.CharField(max_length=16)
estimated_release_time = models.DateTimeField()
deadline_time = models.DateTimeField())
我想模拟以下代码。无效,因为:
1. 我想存储几个模型的外键(GenericForeignKey 可以帮忙);
2. ArrayField暂不支持外键数组;
class Entity(models.Model):
arr = ArrayField(models.ForeignKey(DifferentAtomicModels))
仿真如下。其中even numbers是django.contrib.contenttypes.models.ContentType的pk,odd numbers是atomic-Model的pk(atomic-model是从前面偶数得到的)。
class Entity(models.Model):
arr = ArrayField(models.IntegerField(), null=True, blank=True)
好吧,在 django-admin changelist_view 和 changeform_view 我想用从它获得的另一个模型的字段替换 arr 字段。
实现这个的方法是什么?哪里最好干预 django-admin 行为(覆盖表单域,制作自定义小部件,smth。其他......)?现在我正在研究重写 ModelAdmin -> changeform_view、get_form、modelform_factory;但是代码中有很多relations/inheritions,我有点困惑......内联没有帮助,因为没有真正的外键。
好吧,我将展示我实现此目标的有点肮脏的方法。我有 overrided/created changeform_view of admin.ModelAdmin 和对应的 functions/templates: get_rel_fieldsets, change_form.html 可能还有别的... 下面的代码是为与类别一起使用而设计的,它可以从祖先那里继承一些参数(显示为 ro)并指定自己的参数(显示为 rw)。
欢迎任何评论或更正,请注意:我是 python/django 的新手。
# myapp/models/py
from django.contrib.postgres.fields import ArrayField
class MultiRelationalArrayField(ArrayField):
pass
class SomeAtomicModel(models.Model):
someproperty = models.CharField(max_length=16)
someproperty2 = ...
someproperty3 = ...
def modeladmin_params(self):
'''ModelAdmin parameters for atomicmodels can optionaly be specified in this method'''
# Both 'fields' and 'readonly_fields' must exist!
return {
'fields': [('someproperty', 'someproperty2'), 'someproperty3')],
'readonly_fields': [],
}
class Category(MPTTModel):
parent = TreeForeignKey('self', null=True, blank=True,
verbose_name=_("parent"), related_name='children')
name = models.CharField(_("name"), max_length=64)
relations = MultiRelationalArrayField(models.PositiveIntegerField(), null=True, blank=True,
help_text=_("List of even:ContentType.id, odd:object.id. It's store relations to various predefined property sets."))
def get_relations(self):
relations = self.relations or []
return relations
def get_ancestors_relations(self):
'''Return list of relations with relations of all ancestors starts from root'''
relations = []
for ct in self.get_ancestors():
relations.extend(ct.get_relations())
return relations
def get_objects_from_relations(self, rel_list=None):
'''
Return list of objects mentioned in self.get_relations() (by default).
If rel_list specified return list of objects from it.
'''
objects = []
if rel_list is None:
rel = self.get_relations()
if rel == []:
return []
rel_list = rel
if len(rel_list) % 2:
# raise ValueError('List length must be even, NOT odd.') # Doesn't work! Dont' know why
raise RuntimeError('List length must be even, NOT odd.')
for i in range(0, len(rel_list)-1, 2):
objects.append(ContentType.objects.get_for_id(rel_list[i]).get_object_for_this_type(pk=rel_list[i+1]))
return objects
@csrf_protect_m
@transaction.atomic
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
'''Customization for MultiRelationalArrayField support'''
# Start main part of customization
add = object_id is None
all_rform_validated = True
if self.rel_array_fields and not add: # Fiedls from self.rel_array_fields will not be shown on obj addition
to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
# ?TODO: to_field check for rel objs
obj = self.get_object(request, unquote(object_id), to_field)
anc_rel = obj.get_ancestors_relations()
rel_anc_objs = obj.get_objects_from_relations(anc_rel)
rel_objs = obj.get_objects_from_relations()
rel_all_objs = rel_anc_objs + rel_objs
# rel_anc_objs will be readonly
anc_rel_len2 = anc_rel.__len__()//2
if request.method == 'POST' and "_saveasnew" in request.POST:
if rel_all_objs != []:
return HttpResponseNotAllowed(_("'_saveasnew' option is prohibited for objects with realtions in array field."))
# Generate ModelAdmin & ModelForm for rel_all_objs on the fly
# rel_O_A_F = [(rel_obj instance, rel_obj_ModelAdmin instance, rel_obj_ModelForm class), ...]
rel_O_A_F = []
for i, o in enumerate(rel_all_objs):
rNAME = o._meta.model.__name__ + 'RelAdmin'
# To avoid batch ModelAdmin creations we can set ModelAdmin parameters in modeladmin_params() obj.method
if hasattr(o, 'modeladmin_params'):
rparams = o.modeladmin_params()
rfields = rparams['fields']
rreadonly_fields = rparams['readonly_fields']
else:
rfields = None
rreadonly_fields = None
rATTRS = {
'formfield_overrides': self.formfield_overrides,
'readonly_fields': rreadonly_fields or (),
}
# Separation of parameters from ancestors and object parameters
if not anc_rel_len2: # No relations from ancestors
rATTRS.update({
'fieldsets': self.get_rel_fieldsets(request, o, False if not i else True, 1, **{'fields': rfields}),
})
elif i in range(0, anc_rel_len2): # Relations from ancestors, all will be readonly
rfieldsets = self.get_rel_fieldsets(request, o, False if not i else True, 0, **{'fields': rfields})
rATTRS.update({
'fieldsets': rfieldsets,
'readonly_fields': flatten_fieldsets(rfieldsets),
})
elif i == anc_rel_len2: # The first relation of obj (rw)
rfieldsets = self.get_rel_fieldsets(request, o, False, 1, **{'fields': rfields})
rATTRS.update({
'fieldsets': rfieldsets,
})
else: # Next relations of obj (rw)
if rfields:
rATTRS.update({
'fieldsets': [(None, {'fields': rfields})],
})
rADM = type(rNAME, (admin.ModelAdmin,), rATTRS)(o._meta.model, self.admin_site)
rel_O_A_F.append((o, rADM, rADM.get_form(request, o)))
# Generate adminFroms for related objects
extra_context = extra_context or {}
extra_context['rel_anc_adminforms'] = []
extra_context['rel_adminforms'] = []
extra_context['media'] = []
errlst = []
rmedia = self.media
for i, (robj, radm, rForm) in enumerate(rel_O_A_F):
if request.method == 'POST':
rform = rForm(request.POST, request.FILES, instance=robj)
if not rform.is_valid():
all_rform_validated = False
errlst.extend(helpers.AdminErrorList(rform, []))
else:
rform = rForm(instance=robj)
radminForm = helpers.AdminForm(
rform,
list(radm.get_fieldsets(request, robj)),
radm.get_prepopulated_fields(request, robj),
radm.get_readonly_fields(request, robj),
model_admin=radm
)
if i < anc_rel_len2:
extra_context['rel_anc_adminforms'].append(radminForm)
else:
extra_context['rel_adminforms'].append(radminForm)
rmedia = rmedia + radminForm.media
extra_context.update({'media': rmedia})
if errlst:
extra_context.update({'errors': errlst})
# return super(ModelAdmin, self).changeform_view(request, object_id, form_url, extra_context=extra_context)
# Interrupt main part of customization. Then some inserts to official code of django 1.8 (see '# custom' at the end fo line)
to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
if to_field and not self.to_field_allowed(request, to_field):
raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
model = self.model
opts = model._meta
add = object_id is None
if add:
if not self.has_add_permission(request):
raise PermissionDenied
obj = None
else:
obj = self.get_object(request, unquote(object_id), to_field)
if not self.has_change_permission(request, obj):
raise PermissionDenied
if obj is None:
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
'name': force_text(opts.verbose_name), 'key': escape(object_id)})
if request.method == 'POST' and "_saveasnew" in request.POST:
return self.add_view(request, form_url=reverse('admin:%s_%s_add' % (
opts.app_label, opts.model_name),
current_app=self.admin_site.name))
ModelForm = self.get_form(request, obj)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
form_validated = True
new_object = self.save_form(request, form, change=not add)
else:
form_validated = False
new_object = form.instance
formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
# if all_valid(formsets) and form_validated: # official
# Also check MultiRelationalArrayField models forms validation # custom
if all_valid(formsets) and form_validated and all_rform_validated: # custom
self.save_model(request, new_object, form, not add)
self.save_related(request, form, formsets, not add)
if add:
self.log_addition(request, new_object)
return self.response_add(request, new_object)
else:
# Also save models from MultiRelationalArrayField # custom
if self.rel_array_fields: # custom
for robj, radm, rForm in rel_O_A_F: # custom
rform = rForm(request.POST, request.FILES, instance=robj) # custom
rnew_object = radm.save_form(request, rform, change=not add) # custom
radm.save_model(request, rnew_object, rform, not add) # custom
change_message = self.construct_change_message(request, form, formsets)
self.log_change(request, new_object, change_message)
return self.response_change(request, new_object)
else:
if add:
initial = self.get_changeform_initial_data(request)
form = ModelForm(initial=initial)
formsets, inline_instances = self._create_formsets(request, self.model(), change=False)
else:
form = ModelForm(instance=obj)
formsets, inline_instances = self._create_formsets(request, obj, change=True)
adminForm = helpers.AdminForm(
form,
list(self.get_fieldsets(request, obj)),
self.get_prepopulated_fields(request, obj),
self.get_readonly_fields(request, obj),
model_admin=self)
media = self.media + adminForm.media
inline_formsets = self.get_inline_formsets(request, formsets, inline_instances, obj)
for inline_formset in inline_formsets:
media = media + inline_formset.media
context = dict(self.admin_site.each_context(request),
title=(_('Add %s') if add else _('Change %s')) % force_text(opts.verbose_name),
adminform=adminForm,
object_id=object_id,
original=obj,
is_popup=(IS_POPUP_VAR in request.POST or
IS_POPUP_VAR in request.GET),
to_field=to_field,
media=media,
inline_admin_formsets=inline_formsets,
errors=helpers.AdminErrorList(form, formsets),
preserved_filters=self.get_preserved_filters(request),
)
# Add MultiRelationalArrayField models forms errors to main form errors # custom
try: # custom
extra_context['errors'].extend(context['errors']) # custom
except: # custom
pass # custom
try: # custom
extra_context['media'] = context['media'] + extra_context['media'] # custom
except: # custom
pass # custom
context.update(extra_context or {})
return self.render_change_form(request, context, add=add, change=not add, obj=obj, form_url=form_url)
# myapp/admin.py
from django.contrib import admin
from django.forms.models import fields_for_model
from django.contrib.admin.options import (csrf_protect_m, transaction, TO_FIELD_VAR, DisallowedModelAdminToField, PermissionDenied, Http404, unquote, force_text, reverse, escape, all_valid, helpers, IS_POPUP_VAR, flatten_fieldsets, partial, forms, FieldError, modelform_defines_fields, modelform_factory)
class CategoryAdmin(TreeEditor, ModelAdmin)::
rel_array_fields = ['get_ancestors_relations', 'relations']
readonly_rel_array_fields = ['get_ancestors_relations']
rel_fieldsets = [
[_('Parameters inherited from ancestors'), {
'fields': ['get_ancestors_relations', ],
# 'classes': ('collapse', ),
'description': _("They also will be inherited by descendants. You can change them only via changing ancestors parameters (higher levels)."),
}],
[_('Parameters'), {
'fields': ['relations', ],
# 'classes': ('collapse', ),
'description': _('They will be added to inherition.'),
}],
]
change_form_template = 'myapp/change_form.html'
def get_rel_fieldsets(self, request, obj, fields_only, fieldset_index=1, **kwargs):
if 'fields' in kwargs and kwargs['fields'] is not None:
fields = kwargs['fields']
else:
fields = list(fields_for_model(obj._meta.model).keys())
if fields_only:
return [(None, {'fields': fields})]
else:
fieldset = self.rel_fieldsets[fieldset_index]
fieldset[1]['fields'] = fields
return [fieldset]
# myapp/templates/myapp/change_form.html
{% extends "admin/change_form.html" %}
{% block field_sets %}
{{ block.super }}
{% if rel_anc_adminforms %}
{% for adminform in rel_anc_adminforms %}
{% for fieldset in adminform %}
{% if fieldset.name %}
<fieldset class="module aligned {{ fieldset.classes }}">
<h2>{{ fieldset.name }}</h2>
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% endif %}
{% for line in fieldset %}
{% include "biokit/includes/line.html" %}
{% endfor %}
{% endfor %}
{% endfor %}
</fieldset>
{% endif %}
{% if rel_adminforms %}
{% for adminform in rel_adminforms %}
THE_SAME_AS_ABOVE
{% endfor %}
</fieldset>
{% endif %}
{% endblock %}