| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- 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)
|