from functools import update_wrapper from django.contrib.admin import ModelAdmin, helpers from django.contrib.admin.utils import unquote from django.utils.functional import lazy from django.forms.models import model_to_dict from django.forms.formsets import all_valid from django.urls import reverse, path from django.core.exceptions import PermissionDenied from django.http import Http404, HttpResponse from django.db.models.fields.files import FieldFile, FileField class CloneModelAdmin(ModelAdmin): clone_verbose_name = "Clone" change_form_template = 'admin/admin_change_form.html' cloneable_fields = [] def get_urls(self): def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) wrapper.model_admin = self return update_wrapper(wrapper, view) info = self.opts.app_label, self.opts.model_name new_urlpatterns = [ path( "/clone/", wrap(self.clone_view), name="%s_%s_clone" % info, ), # path("clone/", self.clone_view, name="%s_%s_clone" % info), ] print(new_urlpatterns) original_urlpatterns = super(CloneModelAdmin, self).get_urls() return original_urlpatterns + new_urlpatterns def change_view(self, request, object_id, form_url='', extra_context=None): print(object_id) url = "{0}coord/traveller/{1}/clone/".format(reverse("admin:index"), object_id) url = "/admin/coord/traveller/3/clone/" extra_context = extra_context or {} extra_context.update({ 'clone_verbose_name': self.clone_verbose_name, 'clone_link': url, }) print(extra_context) return super(CloneModelAdmin, self).change_view(request, object_id, form_url, extra_context) def clone_view(self, request, object_id, form_url='', extra_context=None): if not self.has_add_permission(request): raise PermissionDenied original_obj = self.get_object(request, object_id) if original_obj is None: raise Http404 model_form = self.get_form(request) new_obj = self.model() formsets = [] if False and formsets: form = model_form(request.POST, request.FILES) if form.is_valid(): new_obj = self.save_form(request, form, change=False) prefixes = {} for FormSet, inline in self.get_formsets_with_inlines(request): prefix = FormSet.get_default_prefix() prefixes[prefix] = prefixes.get(prefix, 0) + 1 if prefixes[prefix] != 1 or not prefix: prefix = "%s-%s" % (prefix, prefixes[prefix]) request_files = request.FILES filter_params = {'%s__pk' % original_obj.__class__.__name__.lower(): original_obj.pk} inlined_objs = inline.model.objects.filter(**filter_params) for n, inlined_obj in enumerate(inlined_objs.all()): for field in inlined_obj._meta.fields: if isinstance(field, FileField) and field not in request_files: value = field.value_from_object(inlined_obj) file_field_name = '{}-{}-{}'.format(prefix, n, field.name) request_files.setdefault(file_field_name, value) formset = FormSet(data=request.POST, files=request_files, instance=new_obj, save_as_new="_saveasnew" in request.POST, # ???? prefix=prefix) formsets.append(formset) if all_valid(formsets) and form.is_valid(): self.save_model(request, new_obj, form, False) self.save_related(request, form, formsets, False) try: self.log_addition(request, new_obj) except TypeError: # In Django 1.9 we need one more param self.log_addition(request, new_obj, "Cloned object") return self.response_add(request, new_obj, None) else: for field in self.cloneable_fields: setattr(new_obj, field, getattr(original_obj, field)) form = model_form(model_to_dict(new_obj)) admin_form = helpers.AdminForm( form, list(self.get_fieldsets(request)), self.get_prepopulated_fields(request), self.get_readonly_fields(request), model_admin=self ) media = self.media + admin_form.media inline_admin_formsets = self.get_inline_formsets(request, [], []) title = u'{0} {1}'.format(self.clone_verbose_name, original_obj) context = { 'title': title, 'original': title, 'adminform': admin_form, 'is_popup': "_popup" in getattr(request, 'REQUEST', request.GET), 'show_delete': False, 'media': media, 'inline_admin_formsets': inline_admin_formsets, # 'errors': helpers.AdminErrorList(form, self.get_formsets_with_inlines(request)), 'app_label': "coord" } context.update(extra_context or {}) return self.render_change_form( request, context, form_url=form_url, change=False, add=False )