ConstructionConsultationSystem/xadmin/plugins/inline.py
2019-03-18 19:45:47 +08:00

492 lines
17 KiB
Python

import copy
import inspect
from django import forms
from django.forms.formsets import all_valid, DELETION_FIELD_NAME
from django.forms.models import inlineformset_factory, BaseInlineFormSet, modelform_defines_fields
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory
from django.template import loader
from django.template.loader import render_to_string
from django.contrib.auth import get_permission_codename
from django.utils import six
from django.utils.encoding import smart_text
from crispy_forms.utils import TEMPLATE_PACK
from xadmin.layout import FormHelper, Layout, flatatt, Container, Column, Field, Fieldset
from xadmin.plugins.utils import get_context_dict
from xadmin.sites import site
from xadmin.views import BaseAdminPlugin, ModelFormAdminView, DetailAdminView, filter_hook
class ShowField(Field):
template = "xadmin/layout/field_value.html"
def __init__(self, admin_view, *args, **kwargs):
super(ShowField, self).__init__(*args, **kwargs)
self.admin_view = admin_view
if admin_view.style == 'table':
self.template = "xadmin/layout/field_value_td.html"
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
html = ''
detail = form.detail
for field in self.fields:
if not isinstance(form.fields[field].widget, forms.HiddenInput):
result = detail.get_field_result(field)
html += loader.render_to_string(
self.template, context={'field': form[field], 'result': result})
return html
class DeleteField(Field):
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
if form.instance.pk:
self.attrs['type'] = 'hidden'
return super(DeleteField, self).render(form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs)
else:
return ""
class TDField(Field):
template = "xadmin/layout/td-field.html"
class InlineStyleManager(object):
inline_styles = {}
def register_style(self, name, style):
self.inline_styles[name] = style
def get_style(self, name='stacked'):
return self.inline_styles.get(name)
style_manager = InlineStyleManager()
class InlineStyle(object):
template = 'xadmin/edit_inline/stacked.html'
def __init__(self, view, formset):
self.view = view
self.formset = formset
def update_layout(self, helper):
pass
def get_attrs(self):
return {}
style_manager.register_style('stacked', InlineStyle)
class OneInlineStyle(InlineStyle):
template = 'xadmin/edit_inline/one.html'
style_manager.register_style("one", OneInlineStyle)
class AccInlineStyle(InlineStyle):
template = 'xadmin/edit_inline/accordion.html'
style_manager.register_style("accordion", AccInlineStyle)
class TabInlineStyle(InlineStyle):
template = 'xadmin/edit_inline/tab.html'
style_manager.register_style("tab", TabInlineStyle)
class TableInlineStyle(InlineStyle):
template = 'xadmin/edit_inline/tabular.html'
def update_layout(self, helper):
helper.add_layout(
Layout(*[TDField(f) for f in self.formset[0].fields.keys()]))
def get_attrs(self):
fields = []
readonly_fields = []
if len(self.formset):
fields = [f for k, f in self.formset[0].fields.items() if k != DELETION_FIELD_NAME]
readonly_fields = [f for f in getattr(self.formset[0], 'readonly_fields', [])]
return {
'fields': fields,
'readonly_fields': readonly_fields
}
style_manager.register_style("table", TableInlineStyle)
def replace_field_to_value(layout, av):
if layout:
cls_str = str if six.PY3 else basestring
for i, lo in enumerate(layout.fields):
if isinstance(lo, Field) or issubclass(lo.__class__, Field):
layout.fields[i] = ShowField(av, *lo.fields, **lo.attrs)
elif isinstance(lo, cls_str):
layout.fields[i] = ShowField(av, lo)
elif hasattr(lo, 'get_field_names'):
replace_field_to_value(lo, av)
class InlineModelAdmin(ModelFormAdminView):
fk_name = None
formset = BaseInlineFormSet
extra = 3
max_num = None
can_delete = True
fields = []
admin_view = None
style = 'stacked'
def init(self, admin_view):
self.admin_view = admin_view
self.parent_model = admin_view.model
self.org_obj = getattr(admin_view, 'org_obj', None)
self.model_instance = self.org_obj or admin_view.model()
return self
@filter_hook
def get_formset(self, **kwargs):
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(self.get_readonly_fields())
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
# Take the custom ModelForm's Meta.exclude into account only if the
# InlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude)
# if exclude is an empty list we use None, since that's the actual
# default
exclude = exclude or None
can_delete = self.can_delete and self.has_delete_permission()
defaults = {
"form": self.form,
"formset": self.formset,
"fk_name": self.fk_name,
'fields': forms.ALL_FIELDS,
"exclude": exclude,
"formfield_callback": self.formfield_for_dbfield,
"extra": self.extra,
"max_num": self.max_num,
"can_delete": can_delete,
}
defaults.update(kwargs)
return inlineformset_factory(self.parent_model, self.model, **defaults)
@filter_hook
def instance_form(self, **kwargs):
formset = self.get_formset(**kwargs)
attrs = {
'instance': self.model_instance,
'queryset': self.queryset()
}
if self.request_method == 'post':
attrs.update({
'data': self.request.POST, 'files': self.request.FILES,
'save_as_new': "_saveasnew" in self.request.POST
})
instance = formset(**attrs)
instance.view = self
helper = FormHelper()
helper.form_tag = False
helper.include_media = False
# override form method to prevent render csrf_token in inline forms, see template 'bootstrap/whole_uni_form.html'
helper.form_method = 'get'
style = style_manager.get_style(
'one' if self.max_num == 1 else self.style)(self, instance)
style.name = self.style
if len(instance):
layout = copy.deepcopy(self.form_layout)
if layout is None:
layout = Layout(*instance[0].fields.keys())
elif type(layout) in (list, tuple) and len(layout) > 0:
layout = Layout(*layout)
rendered_fields = [i[1] for i in layout.get_field_names()]
layout.extend([f for f in instance[0]
.fields.keys() if f not in rendered_fields])
helper.add_layout(layout)
style.update_layout(helper)
# replace delete field with Dynamic field, for hidden delete field when instance is NEW.
helper[DELETION_FIELD_NAME].wrap(DeleteField)
instance.helper = helper
instance.style = style
readonly_fields = self.get_readonly_fields()
if readonly_fields:
for form in instance:
form.readonly_fields = []
inst = form.save(commit=False)
if inst:
meta_field_names = [field.name for field in inst._meta.get_fields()]
for readonly_field in readonly_fields:
value = None
label = None
if readonly_field in meta_field_names:
label = inst._meta.get_field(readonly_field).verbose_name
value = smart_text(getattr(inst, readonly_field))
elif inspect.ismethod(getattr(inst, readonly_field, None)):
value = getattr(inst, readonly_field)()
label = getattr(getattr(inst, readonly_field), 'short_description', readonly_field)
elif inspect.ismethod(getattr(self, readonly_field, None)):
value = getattr(self, readonly_field)(inst)
label = getattr(getattr(self, readonly_field), 'short_description', readonly_field)
if value:
form.readonly_fields.append({'label': label, 'contents': value})
return instance
def has_auto_field(self, form):
if form._meta.model._meta.has_auto_field:
return True
for parent in form._meta.model._meta.get_parent_list():
if parent._meta.has_auto_field:
return True
return False
def queryset(self):
queryset = super(InlineModelAdmin, self).queryset()
if not self.has_change_permission() and not self.has_view_permission():
queryset = queryset.none()
return queryset
def has_add_permission(self):
if self.opts.auto_created:
return self.has_change_permission()
codename = get_permission_codename('add', self.opts)
return self.user.has_perm("%s.%s" % (self.opts.app_label, codename))
def has_change_permission(self):
opts = self.opts
if opts.auto_created:
for field in opts.fields:
if field.remote_field and field.remote_field.model != self.parent_model:
opts = field.remote_field.model._meta
break
codename = get_permission_codename('change', opts)
return self.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_delete_permission(self):
if self.opts.auto_created:
return self.has_change_permission()
codename = get_permission_codename('delete', self.opts)
return self.user.has_perm("%s.%s" % (self.opts.app_label, codename))
class GenericInlineModelAdmin(InlineModelAdmin):
ct_field = "content_type"
ct_fk_field = "object_id"
formset = BaseGenericInlineFormSet
def get_formset(self, **kwargs):
if self.exclude is None:
exclude = []
else:
exclude = list(self.exclude)
exclude.extend(self.get_readonly_fields())
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
# Take the custom ModelForm's Meta.exclude into account only if the
# GenericInlineModelAdmin doesn't define its own.
exclude.extend(self.form._meta.exclude)
exclude = exclude or None
can_delete = self.can_delete and self.has_delete_permission()
defaults = {
"ct_field": self.ct_field,
"fk_field": self.ct_fk_field,
"form": self.form,
"formfield_callback": self.formfield_for_dbfield,
"formset": self.formset,
"extra": self.extra,
"can_delete": can_delete,
"can_order": False,
"max_num": self.max_num,
"exclude": exclude,
'fields': forms.ALL_FIELDS
}
defaults.update(kwargs)
return generic_inlineformset_factory(self.model, **defaults)
class InlineFormset(Fieldset):
def __init__(self, formset, allow_blank=False, **kwargs):
self.fields = []
self.css_class = kwargs.pop('css_class', '')
self.css_id = "%s-group" % formset.prefix
self.template = formset.style.template
self.inline_style = formset.style.name
if allow_blank and len(formset) == 0:
self.template = 'xadmin/edit_inline/blank.html'
self.inline_style = 'blank'
self.formset = formset
self.model = formset.model
self.opts = formset.model._meta
self.flat_attrs = flatatt(kwargs)
self.extra_attrs = formset.style.get_attrs()
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
context = get_context_dict(context)
context.update(dict(
formset=self,
prefix=self.formset.prefix,
inline_style=self.inline_style,
**self.extra_attrs
))
return render_to_string(self.template, context)
class Inline(Fieldset):
def __init__(self, rel_model):
self.model = rel_model
self.fields = []
super(Inline, self).__init__(legend="")
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
return ""
def get_first_field(layout, clz):
for layout_object in layout.fields:
if issubclass(layout_object.__class__, clz):
return layout_object
elif hasattr(layout_object, 'get_field_names'):
gf = get_first_field(layout_object, clz)
if gf:
return gf
def replace_inline_objects(layout, fs):
if not fs:
return
for i, layout_object in enumerate(layout.fields):
if isinstance(layout_object, Inline) and layout_object.model in fs:
layout.fields[i] = fs.pop(layout_object.model)
elif hasattr(layout_object, 'get_field_names'):
replace_inline_objects(layout_object, fs)
class InlineFormsetPlugin(BaseAdminPlugin):
inlines = []
@property
def inline_instances(self):
if not hasattr(self, '_inline_instances'):
inline_instances = []
for inline_class in self.inlines:
inline = self.admin_view.get_view(
(getattr(inline_class, 'generic_inline', False) and GenericInlineModelAdmin or InlineModelAdmin),
inline_class).init(self.admin_view)
if not (inline.has_add_permission() or
inline.has_change_permission() or
inline.has_delete_permission() or
inline.has_view_permission()):
continue
if not inline.has_add_permission():
inline.max_num = 0
inline_instances.append(inline)
self._inline_instances = inline_instances
return self._inline_instances
def instance_forms(self, ret):
self.formsets = []
for inline in self.inline_instances:
if inline.has_change_permission():
self.formsets.append(inline.instance_form())
else:
self.formsets.append(self._get_detail_formset_instance(inline))
self.admin_view.formsets = self.formsets
def valid_forms(self, result):
return all_valid(self.formsets) and result
def save_related(self):
for formset in self.formsets:
formset.instance = self.admin_view.new_obj
formset.save()
def get_context(self, context):
context['inline_formsets'] = self.formsets
return context
def get_error_list(self, errors):
for fs in self.formsets:
errors.extend(fs.non_form_errors())
for errors_in_inline_form in fs.errors:
errors.extend(errors_in_inline_form.values())
return errors
def get_form_layout(self, layout):
allow_blank = isinstance(self.admin_view, DetailAdminView)
# fixed #176 bug, change dict to list
fs = [(f.model, InlineFormset(f, allow_blank)) for f in self.formsets]
replace_inline_objects(layout, fs)
if fs:
container = get_first_field(layout, Column)
if not container:
container = get_first_field(layout, Container)
if not container:
container = layout
# fixed #176 bug, change dict to list
for key, value in fs:
container.append(value)
return layout
def get_media(self, media):
for fs in self.formsets:
media = media + fs.media
if self.formsets:
media = media + self.vendor(
'xadmin.plugin.formset.js', 'xadmin.plugin.formset.css')
return media
def _get_detail_formset_instance(self, inline):
formset = inline.instance_form(extra=0, max_num=0, can_delete=0)
formset.detail_page = True
if True:
replace_field_to_value(formset.helper.layout, inline)
model = inline.model
opts = model._meta
fake_admin_class = type(str('%s%sFakeAdmin' % (opts.app_label, opts.model_name)), (object, ), {'model': model})
for form in formset.forms:
instance = form.instance
if instance.pk:
form.detail = self.get_view(
DetailAdminUtil, fake_admin_class, instance)
return formset
class DetailAdminUtil(DetailAdminView):
def init_request(self, obj):
self.obj = obj
self.org_obj = obj
class DetailInlineFormsetPlugin(InlineFormsetPlugin):
def get_model_form(self, form, **kwargs):
self.formsets = [self._get_detail_formset_instance(
inline) for inline in self.inline_instances]
return form
site.register_plugin(InlineFormsetPlugin, ModelFormAdminView)
site.register_plugin(DetailInlineFormsetPlugin, DetailAdminView)