“Django”;“另存为新的”;并保留图像字段

“Django”;“另存为新的”;并保留图像字段,django,Django,我有一个Django模型,有多个ImageFields 在ModelAdmin类上,我设置了save_as=True,这意味着管理员页面有一个“另存为新”按钮,允许复制现有项并将其另存为新项 但是,使用此按钮时,不会复制ImageFields,并且在新项目上保留为空 查看POST请求,我发现这些字段在POST数据中是空白的 我考虑过重写模型类的save方法,并自己从旧对象复制图像。但据我所知,我无法判断对象是否保存为“新”。我似乎也没有旧项目的ID,因此无法从中获取旧图像 有没有办法让这些图像字

我有一个Django模型,有多个ImageFields

在ModelAdmin类上,我设置了
save_as=True
,这意味着管理员页面有一个“另存为新”按钮,允许复制现有项并将其另存为新项

但是,使用此按钮时,不会复制ImageFields,并且在新项目上保留为空

查看POST请求,我发现这些字段在POST数据中是空白的

我考虑过重写模型类的save方法,并自己从旧对象复制图像。但据我所知,我无法判断对象是否保存为“新”。我似乎也没有旧项目的ID,因此无法从中获取旧图像

有没有办法让这些图像字段也被复制

编辑: 按请求添加示例代码

创建了一个只有一个模型的极简应用程序。已验证的问题仍然存在

示例models.py:

from django.db import models

class Person(models.Model):
    face_image = models.ImageField(upload_to='images', 
                                   null=False, 
                                   blank=True)
示例admin.py:

from django.contrib import admin
from testapp.models import Person

class PersonAdmin(admin.ModelAdmin):
    save_as = True

admin.site.register(Person, PersonAdmin)

我设法找到了一些解决办法:

我已经覆盖了原始的管理表单,以便在“另存为新”POST请求中也包含旧模型的ID。为此,我为该模型创建了一个特殊的管理员,并在其中添加了一个隐藏的输入:

<input type="hidden" name="my_objectid" value="{{ object_id }}">

这是一张描述同样问题的记录单:

2014年2月9日的pull请求修复了此错误。 希望很快它会被合并。

在响应的基础上,这里有一个更通用的方法来实现相同的结果:

from django.core.urlresolvers import resolve
from django.db.models.fields.files import FieldFile

class PersonAdmin(admin.ModelAdmin):
    save_as = True

    def save_model(self, request, obj, form, change):
        # Django always sends this when "Save as new is clicked"
        if '_saveasnew' in request.POST:
            # Get the ID from the admin URL
            original_pk = resolve(request.path).args[0]
            # Get the original object
            original_obj = obj._meta.concrete_model.objects.get(id=original_pk)

            # Iterate through all it's properties
            for prop, value in vars(original_obj).iteritems():
                # if the property is an Image (don't forget to import ImageFieldFile!)
                if isinstance(getattr(original_obj, prop), FieldFile):
                    setattr(obj,prop,getattr(original_obj, prop)) # Copy it!
        obj.save()

这应该适用于任何模型和任何文件类型。它也不需要编辑表单或模板。这是一种在拉取请求合并后不需要的解决方法:。

如果您的请求在2019年出现。。@nicolaslara的最新答案 这个答案是Django 2+和python 3

要从Django管理员处获取Url,我们应使用:

original_pk = request.resolver_match.kwargs['object_id']
而且iteritems()在python3上不起作用,我们只能使用items()

最终代码:

  def save_model(self, request, obj, form, change):
    # Django always sends this when "Save as new is clicked"
    if '_saveasnew' in request.POST:
        # Get the ID from the admin URL
        original_pk = request.resolver_match.kwargs['object_id']
        print(original_pk)

        # Get the original object
        original_obj = obj._meta.concrete_model.objects.get(id=original_pk)

        # Iterate through all it's properties
        for prop, value in vars(original_obj).items():
            # if the property is an Image (don't forget to import ImageFieldFile!)
            if isinstance(getattr(original_obj, prop), ImageFieldFile):
                setattr(obj, prop, getattr(original_obj, prop))  # Copy it!
    obj.save()

这对我帮助很大,我使用了@ShravaN提供的解决方案,并对其进行了扩展,以便在相关的内联模型中保存图像。我认为代码不是最好的,但它是有效的。如果你有任何改进的想法,请做

def save_model(self, request, obj, form, change):
    # Django always sends this when "Save as new is clicked"
    if '_saveasnew' in request.POST:
        # Get the ID from the admin URL
        original_pk = request.resolver_match.kwargs['object_id']
        # Get the original object
        original_obj = obj._meta.concrete_model.objects.get(id=original_pk)
        # Iterate through all it's properties
        self._copy_image_fields(obj, original_obj)

    obj.save()

def _copy_image_fields(self, obj, original_obj):
    for prop, value in vars(original_obj).items():
        # if the property is an Image
        # (don't forget to import ImageFieldFile!)
        if isinstance(getattr(original_obj, prop), ImageFieldFile):
            setattr(obj, prop, getattr(original_obj, prop))  # Copy it!

def save_related(self, request, form, formsets, change):
    if '_saveasnew' in request.POST:
        # Get the ID from the admin URL
        original_pk = request.resolver_match.kwargs['object_id']

        # Get the original object
        original_obj = form.instance._meta.concrete_model.objects.get(
            id=original_pk
        )
        form.save_m2m()
        for formset in formsets:
            instances = formset.save(commit=False)

            if instances:
                related = list(filter(lambda r: r.related_model == formset.model, original_obj._meta.related_objects))
                related = related[0] if related else None
                # related: ManyToOneRel
                if related:
                    field_name = f"{related.name}_set" if not related.related_name else related.related_name
                    related_set = getattr(original_obj, field_name)
                else:
                    # TODO: warning?
                    continue
                for ori, ni in zip(related_set.all(), instances):
                    # instance: Model
                    # we need to figure out which field is in the original
                    # object
                    self._copy_image_fields(ni, ori)
                    ni.save()
            formset.save_m2m()
    else:
        super(LiveEventAdmin, self).save_related(request, form, formsets, change)
    

你能分享一下你的代码吗。我没有发布任何代码,因为它非常琐碎。这只是一个简单的问题。不要以为我写的自定义代码破坏了“另存为新”功能(因为我看到数据甚至没有被发布)。它应该是开箱即用的吗?这并不是那么简单,因为您描述的行为在文档中似乎没有提到,所以它要么是django中的问题,要么是您的代码中的问题,我真的无法帮助您看到至少部分添加了最基本的代码示例。仍然发生。我意识到Django中缺少此功能(至少在我使用的1.4中)。我想知道是否有什么方法可以解决或破解它。正确的缩进在编辑过程中丢失了,以至于它无法编译,也没有任何意义,恢复为原始版本。同样,从今天起,情况明显好转。引用的拉取请求已被拒绝,并且在两年内未触及票证,所以我怀疑我们不太可能很快看到这个问题得到解决。感谢您提供的这个代码片段,它可能会提供一些有限的短期帮助。通过说明为什么这是一个很好的问题解决方案来正确解释它的长期价值,并将使它对未来有其他类似问题的读者更有用。请在您的回答中添加一些解释,包括您所做的假设。
def save_model(self, request, obj, form, change):
    # Django always sends this when "Save as new is clicked"
    if '_saveasnew' in request.POST:
        # Get the ID from the admin URL
        original_pk = request.resolver_match.kwargs['object_id']
        # Get the original object
        original_obj = obj._meta.concrete_model.objects.get(id=original_pk)
        # Iterate through all it's properties
        self._copy_image_fields(obj, original_obj)

    obj.save()

def _copy_image_fields(self, obj, original_obj):
    for prop, value in vars(original_obj).items():
        # if the property is an Image
        # (don't forget to import ImageFieldFile!)
        if isinstance(getattr(original_obj, prop), ImageFieldFile):
            setattr(obj, prop, getattr(original_obj, prop))  # Copy it!

def save_related(self, request, form, formsets, change):
    if '_saveasnew' in request.POST:
        # Get the ID from the admin URL
        original_pk = request.resolver_match.kwargs['object_id']

        # Get the original object
        original_obj = form.instance._meta.concrete_model.objects.get(
            id=original_pk
        )
        form.save_m2m()
        for formset in formsets:
            instances = formset.save(commit=False)

            if instances:
                related = list(filter(lambda r: r.related_model == formset.model, original_obj._meta.related_objects))
                related = related[0] if related else None
                # related: ManyToOneRel
                if related:
                    field_name = f"{related.name}_set" if not related.related_name else related.related_name
                    related_set = getattr(original_obj, field_name)
                else:
                    # TODO: warning?
                    continue
                for ori, ni in zip(related_set.all(), instances):
                    # instance: Model
                    # we need to figure out which field is in the original
                    # object
                    self._copy_image_fields(ni, ori)
                    ni.save()
            formset.save_m2m()
    else:
        super(LiveEventAdmin, self).save_related(request, form, formsets, change)