def merge(modeladmin, request, queryset): """ Merge two model instances. Move all foreign keys. """ opts = modeladmin.model._meta perm = "{0}.{1}".format( opts.app_label.lower(), get_permission_codename('adminactions_merge', opts)) if not request.user.has_perm(perm): messages.error( request, _('Sorry you do not have rights to execute this action (%s)' % perm)) return def raw_widget(field, **kwargs): """ force all fields as not required""" kwargs['widget'] = TextInput({'class': 'raw-value'}) return field.formfield(**kwargs) merge_form = getattr(modeladmin, 'merge_form', MergeForm) MForm = modelform_factory(modeladmin.model, form=merge_form, exclude=('pk', ), formfield_callback=raw_widget) OForm = modelform_factory(modeladmin.model, exclude=('pk', ), formfield_callback=raw_widget) tpl = 'adminactions/merge.html' # transaction_supported = model_supports_transactions(modeladmin.model) ctx = { '_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'transaction_supported': 'Un', 'select_across': request.POST.get('select_across') == '1', 'action': request.POST.get('action'), 'fields': [ f for f in queryset.model._meta.fields if not f.primary_key and f.editable ], 'app_label': queryset.model._meta.app_label, 'result': '', 'opts': queryset.model._meta } if 'preview' in request.POST: master = queryset.get(pk=request.POST.get('master_pk')) original = clone_instance(master) other = queryset.get(pk=request.POST.get('other_pk')) formset = formset_factory(OForm)( initial=[model_to_dict(master), model_to_dict(other)]) with transaction.nocommit(): form = MForm(request.POST, instance=master) other.delete() form_is_valid = form.is_valid() if form_is_valid: ctx.update({'original': original}) tpl = 'adminactions/merge_preview.html' else: master = queryset.get(pk=request.POST.get('master_pk')) other = queryset.get(pk=request.POST.get('other_pk')) elif 'apply' in request.POST: master = queryset.get(pk=request.POST.get('master_pk')) other = queryset.get(pk=request.POST.get('other_pk')) formset = formset_factory(OForm)( initial=[model_to_dict(master), model_to_dict(other)]) with transaction.nocommit(): form = MForm(request.POST, instance=master) stored_pk = other.pk other.delete() ok = form.is_valid() other.pk = stored_pk if ok: if form.cleaned_data['dependencies'] == MergeForm.DEP_MOVE: related = api.ALL_FIELDS else: related = None fields = form.cleaned_data['field_names'] api.merge(master, other, fields=fields, commit=True, related=related) return HttpResponseRedirect(request.path) else: messages.error(request, form.errors) else: try: master, other = queryset.all() # django 1.4 need to remove the trailing milliseconds for field in master._meta.fields: if isinstance(field, models.DateTimeField): for target in (master, other): raw_value = getattr(target, field.name) fixed_value = datetime(raw_value.year, raw_value.month, raw_value.day, raw_value.hour, raw_value.minute, raw_value.second) setattr(target, field.name, fixed_value) except ValueError: messages.error(request, _('Please select exactly 2 records')) return initial = { '_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'select_across': 0, 'generic': MergeForm.GEN_IGNORE, 'dependencies': MergeForm.DEP_MOVE, 'action': 'merge', 'master_pk': master.pk, 'other_pk': other.pk } formset = formset_factory(OForm)( initial=[model_to_dict(master), model_to_dict(other)]) form = MForm(initial=initial, instance=master) adminForm = helpers.AdminForm(form, modeladmin.get_fieldsets(request), {}, [], model_admin=modeladmin) media = modeladmin.media + adminForm.media ctx.update({ 'adminform': adminForm, 'formset': formset, 'media': mark_safe(media), 'title': u"Merge %s" % force_unicode(modeladmin.opts.verbose_name_plural), 'master': master, 'other': other }) return render_to_response(tpl, RequestContext(request, ctx))
def merge(master, other, fields=None, commit=False, m2m=None, related=None): # noqa """ Merge 'other' into master. `fields` is a list of fieldnames that must be readed from ``other`` to put into master. If ``fields`` is None ``master`` will get all the ``other`` values except primary_key. Finally ``other`` will be deleted and master will be preserved @param master: Model instance @param other: Model instance @param fields: list of fieldnames to merge @param m2m: list of m2m fields to merge. If empty will be removed @param related: list of related fieldnames to merge. If empty will be removed @return: """ fields = fields or [f.name for f in master._meta.fields] all_m2m = {} all_related = {} if related == ALL_FIELDS: related = [rel.get_accessor_name() for rel in compat.get_all_related_objects(master)] # for rel in master._meta.get_all_related_objects(False, False, False)] if m2m == ALL_FIELDS: m2m = [field.name for field in master._meta.many_to_many] if m2m and not commit: raise ValueError('Cannot save related with `commit=False`') with compat.atomic(): result = clone_instance(master) for fieldname in fields: f = get_field_by_path(master, fieldname) if f and not f.primary_key: setattr(result, fieldname, getattr(other, fieldname)) if m2m: for fieldname in set(m2m): all_m2m[fieldname] = [] field_object = get_field_by_path(master, fieldname) if not isinstance(field_object, ManyToManyField): raise ValueError('{0} is not a ManyToManyField field'.format(fieldname)) source_m2m = getattr(other, field_object.name) for r in source_m2m.all(): all_m2m[fieldname].append(r) if related: for name in set(related): related_object = get_field_by_path(master, name) all_related[name] = [] if related_object and isinstance(related_object.field, OneToOneField): try: accessor = getattr(other, name) all_related[name] = [(related_object.field.name, accessor)] except ObjectDoesNotExist: pass else: accessor = getattr(other, name, None) if accessor: rel_fieldname = list(accessor.core_filters.keys())[0].split('__')[0] for r in accessor.all(): all_related[name].append((rel_fieldname, r)) if commit: for name, elements in list(all_related.items()): for rel_fieldname, element in elements: setattr(element, rel_fieldname, master) element.save() other.delete() result.save() for fieldname, elements in list(all_m2m.items()): dest_m2m = getattr(result, fieldname) for element in elements: dest_m2m.add(element) return result
def merge(modeladmin, request, queryset): # noqa """ Merge two model instances. Move all foreign keys. """ opts = modeladmin.model._meta perm = "{0}.{1}".format(opts.app_label, get_permission_codename('adminactions_merge', opts)) if not request.user.has_perm(perm): messages.error(request, _('Sorry you do not have rights to execute this action (%s)' % perm)) return def raw_widget(field, **kwargs): """ force all fields as not required""" kwargs['widget'] = TextInput({'class': 'raw-value'}) return field.formfield(**kwargs) merge_form = getattr(modeladmin, 'merge_form', MergeForm) MForm = modelform_factory(modeladmin.model, form=merge_form, exclude=('pk', ), formfield_callback=raw_widget) OForm = modelform_factory(modeladmin.model, exclude=('pk', ), formfield_callback=raw_widget) tpl = 'adminactions/merge.html' # transaction_supported = model_supports_transactions(modeladmin.model) ctx = { '_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'transaction_supported': 'Un', 'select_across': request.POST.get('select_across') == '1', 'action': request.POST.get('action'), 'fields': [f for f in queryset.model._meta.fields if not f.primary_key and f.editable], 'app_label': queryset.model._meta.app_label, 'result': '', 'opts': queryset.model._meta} if 'preview' in request.POST: master = queryset.get(pk=request.POST.get('master_pk')) original = clone_instance(master) other = queryset.get(pk=request.POST.get('other_pk')) formset = formset_factory(OForm)(initial=[model_to_dict(master), model_to_dict(other)]) with transaction.nocommit(): form = MForm(request.POST, instance=master) other.delete() form_is_valid = form.is_valid() if form_is_valid: ctx.update({'original': original}) tpl = 'adminactions/merge_preview.html' else: master = queryset.get(pk=request.POST.get('master_pk')) other = queryset.get(pk=request.POST.get('other_pk')) elif 'apply' in request.POST: master = queryset.get(pk=request.POST.get('master_pk')) other = queryset.get(pk=request.POST.get('other_pk')) formset = formset_factory(OForm)(initial=[model_to_dict(master), model_to_dict(other)]) with transaction.nocommit(): form = MForm(request.POST, instance=master) stored_pk = other.pk other.delete() ok = form.is_valid() other.pk = stored_pk if ok: if form.cleaned_data['dependencies'] == MergeForm.DEP_MOVE: related = api.ALL_FIELDS else: related = None fields = form.cleaned_data['field_names'] api.merge(master, other, fields=fields, commit=True, related=related) return HttpResponseRedirect(request.path) else: messages.error(request, form.errors) else: try: master, other = queryset.all() # django 1.4 need to remove the trailing milliseconds for field in master._meta.fields: if isinstance(field, models.DateTimeField): for target in (master, other): raw_value = getattr(target, field.name) fixed_value = datetime(raw_value.year, raw_value.month, raw_value.day, raw_value.hour, raw_value.minute, raw_value.second) setattr(target, field.name, fixed_value) except ValueError: messages.error(request, _('Please select exactly 2 records')) return initial = {'_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'select_across': 0, 'generic': MergeForm.GEN_IGNORE, 'dependencies': MergeForm.DEP_MOVE, 'action': 'merge', 'master_pk': master.pk, 'other_pk': other.pk} formset = formset_factory(OForm)(initial=[model_to_dict(master), model_to_dict(other)]) form = MForm(initial=initial, instance=master) adminForm = helpers.AdminForm(form, modeladmin.get_fieldsets(request), {}, [], model_admin=modeladmin) media = modeladmin.media + adminForm.media ctx.update({'adminform': adminForm, 'formset': formset, 'media': mark_safe(media), 'title': u"Merge %s" % smart_text(modeladmin.opts.verbose_name_plural), 'master': master, 'other': other}) return render_to_response(tpl, RequestContext(request, ctx))
def merge(modeladmin, request, queryset): """ Merge two model instances. Move all foreign keys. """ def raw_widget(field, **kwargs): """ force all fields as not required""" kwargs['widget'] = TextInput({'class': 'raw-value', 'readonly': 'readonly'}) kwargs['widget'] = TextInput({'class': 'raw-value', 'size': '30'}) return field.formfield(**kwargs) # Allows to specified a custom Form in the ModelAdmin # MForm = getattr(modeladmin, 'merge_form', MergeForm) merge_form = getattr(modeladmin, 'merge_form', MergeForm) MForm = modelform_factory(modeladmin.model, form=merge_form, formfield_callback=raw_widget) OForm = modelform_factory(modeladmin.model, formfield_callback=raw_widget) tpl = 'adminactions/merge.html' ctx = { '_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'select_across': request.POST.get('select_across') == '1', 'action': request.POST.get('action'), 'fields': [f for f in queryset.model._meta.fields if not f.primary_key and f.editable], 'app_label': queryset.model._meta.app_label, 'result': '', 'opts': queryset.model._meta} if 'preview' in request.POST: master = queryset.get(pk=request.POST.get('master_pk')) original = clone_instance(master) other = queryset.get(pk=request.POST.get('other_pk')) formset = formset_factory(OForm)(initial=[model_to_dict(master), model_to_dict(other)]) with transaction.commit_manually(): form = MForm(request.POST, instance=master) other.delete() form_is_valid = form.is_valid() transaction.rollback() if form_is_valid: ctx.update({'original': original}) tpl = 'adminactions/merge_preview.html' elif 'apply' in request.POST: master = queryset.get(pk=request.POST.get('master_pk')) other = queryset.get(pk=request.POST.get('other_pk')) formset = formset_factory(OForm)(initial=[model_to_dict(master), model_to_dict(other)]) with transaction.commit_manually(): form = MForm(request.POST, instance=master) other.delete() if form.is_valid(): form.save() transaction.commit() return HttpResponseRedirect(request.path) else: messages.error(request, form.errors) transaction.rollback() else: try: master, other = queryset.all() except ValueError: messages.error(request, _('Please select exactly 2 records')) return initial = {'_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'select_across': 0, 'generic': MergeForm.GEN_IGNORE, 'dependencies': MergeForm.DEP_MOVE, 'action': 'merge', 'master_pk': master.pk, 'other_pk': other.pk} formset = formset_factory(OForm)(initial=[model_to_dict(master), model_to_dict(other)]) form = MForm(initial=initial, instance=master) adminForm = helpers.AdminForm(form, modeladmin.get_fieldsets(request), {}, [], model_admin=modeladmin) media = modeladmin.media + adminForm.media ctx.update({'adminform': adminForm, 'formset': formset, 'media': mark_safe(media), 'master': master, 'other': other}) return render_to_response(tpl, RequestContext(request, ctx))