简体   繁体   English

Django 使用当前 model id 上传管理文件

[英]Django admin file upload with current model id

I'm trying to create a simple photo gallery with the default Django admin.我正在尝试使用默认的 Django 管理员创建一个简单的照片库。 I'd like to save a sample photo for each gallery, but I don't want to keep the filname.我想为每个画廊保存一张样本照片,但我不想保留电影名。 Instead of the filename, I'd like to save the id of the model ( N.jpg ).我不想保存文件名,而是保存 model ( N.jpg ) 的 ID。 But the first time I want to save the object the id does not exist.但是我第一次想保存的object这个id不存在。 How could I know the next auto increment in the model, or somehow save the model data before the upload with super.save and after upload the file when self.id is exists?我怎么知道 model 中的下一个自动增量,或者在使用super.save上传之前以及在存在self.id的情况下上传文件之后以某种方式保存 model 数据? Is there a cool solution?有很酷的解决方案吗?

Something like this:像这样:

def upload_path_handler(instance, filename):
    ext = filename extension
    return "site_media/images/gallery/{id}.{ext}".format(id=instance.nextincrement, ext=ext)

class Gallery(models.Model):
    name  = models.TextField()
    image = models.FileField(upload_to=upload_path_handler)

And maybe store the filename in a different field.并且可能将文件名存储在不同的字段中。

I ran into the same problem.我遇到了同样的问题。 Okm's answer sent me on the right path but it seems to me it is possible to get the same functionality by just overriding the save() method of your Model. Okm 的回答让我走上了正确的道路,但在我看来,只需覆盖模型的save()方法就可以获得相同的功能。

def save(self, *args, **kwargs):
    if self.pk is None:
        saved_image = self.image
        self.image = None
        super(Material, self).save(*args, **kwargs)
        self.image = saved_image

    super(Material, self).save(*args, **kwargs)

This definitely saves the information correctly.这肯定会正确保存信息。

The image file gets saved before Gallery instance.图像文件在 Gallery 实例之前保存。 So you have to split the saving to two phases by using signals w/ Gallery instance itself carrying the state:因此,您必须使用带有状态的带有 Gallery 实例本身的信号将保存分为两个阶段:

from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

_UNSAVED_FILEFIELD = 'unsaved_filefield'

@receiver(pre_save, sender=Image)
def skip_saving_file(sender, instance, **kwargs):
    if not instance.pk and not hasattr(instance, _UNSAVED_FILEFIELD):
        setattr(instance, _UNSAVED_FILEFIELD, instance.image)
        instance.image = None

@receiver(post_save, sender=Image)
def save_file(sender, instance, created, **kwargs):
    if created and hasattr(instance, _UNSAVED_FILEFIELD):
        instance.image = getattr(instance, _UNSAVED_FILEFIELD)
        instance.save()        
        # delete it if you feel uncomfortable...
        # instance.__dict__.pop(_UNSAVED_FILEFIELD)

The upload_path_handler looks like upload_path_handler 看起来像

def upload_path_handler(instance, filename):
    import os.path
    fn, ext = os.path.splitext(filename)
    return "site_media/images/gallery/{id}{ext}".format(id=instance.pk, ext=ext)

I suggest using ImageField instead of FileField for type-checking if the field is for image uploading only.如果该字段仅用于图像上传,我建议使用 ImageField 而不是 FileField 进行类型检查。 Also, you may want to normalize filename extension (which is unnecessary because of the mimetype) like此外,您可能希望标准化文件扩展名(由于 mimetype,这是不必要的),例如

def normalize_ext(image_field):
    try:
        from PIL import Image
    except ImportError:
        import Image
    ext = Image.open(image_field).format
    if hasattr(image_field, 'seek') and callable(image_field.seek):
       image_field.seek(0)
    ext = ext.lower()
    if ext == 'jpeg':
        ext = 'jpg'
    return '.' + ext

For Django 2.2, follow the below code.对于 Django 2.2,请遵循以下代码。

def save(self, *args, **kwargs):
    if self.pk is None:
        saved_image = self.image
        self.image = None
        super(Gallery, self).save(*args, **kwargs)
        self.image = saved_image
        if 'force_insert' in kwargs:
            kwargs.pop('force_insert')

    super(Gallery, self).save(*args, **kwargs)

Add the above code snippet to your 'class Gallery'.将上面的代码片段添加到您的“类库”中。

PS: This will work for DRF as well when you save via views.py. PS:当您通过 views.py 保存时,这也适用于 DRF。 Note that second if (condition) is required for DRF.请注意,DRF 需要第二个 if (condition)。

Using Louis's answer , here is recipe to process all FileField in the model:使用Louis 的回答,这里是处理模型中所有FileField的方法:

class MyModel(models.Model):

    file_field = models.FileField(upload_to=upload_to, blank=True, null=True)

    def save(self, *args, **kwargs):
        if self.id is None:
            saved = []
            for f in self.__class__._meta.get_fields():
                if isinstance(f, models.FileField):
                    saved.append((f.name, getattr(self, f.name)))
                    setattr(self, f.name, None)

            super(self.__class__, self).save(*args, **kwargs)

            for name, val in saved:
                setattr(self, name, val)
        super(self.__class__, self).save(*args, **kwargs)

In django 1.7, the proposed solutions didn't seem to work for me, so I wrote my FileField subclasses along with a storage subclass which removes the old files.在 django 1.7 中,建议的解决方案似乎对我不起作用,因此我编写了 FileField 子类以及删除旧文件的存储子类。

Storage:贮存:

class OverwriteFileSystemStorage(FileSystemStorage):
    def _save(self, name, content):
        self.delete(name)
        return super()._save(name, content)

    def get_available_name(self, name):
        return name

    def delete(self, name):
        super().delete(name)

        last_dir = os.path.dirname(self.path(name))

        while True:
            try:
                os.rmdir(last_dir)
            except OSError as e:
                if e.errno in {errno.ENOTEMPTY, errno.ENOENT}:
                    break

                raise e

            last_dir = os.path.dirname(last_dir)

FileField:文件字段:

def tweak_field_save(cls, field):
    field_defined_in_this_class = field.name in cls.__dict__ and field.name not in cls.__bases__[0].__dict__

    if field_defined_in_this_class:
        orig_save = cls.save

        if orig_save and callable(orig_save):
            assert isinstance(field.storage, OverwriteFileSystemStorage), "Using other storage than '{0}' may cause unexpected behavior.".format(OverwriteFileSystemStorage.__name__)

            def save(self, *args, **kwargs):
                if self.pk is None:
                    orig_save(self, *args, **kwargs)

                    field_file = getattr(self, field.name)

                    if field_file:
                        old_path = field_file.path
                        new_filename = field.generate_filename(self, os.path.basename(old_path))
                        new_path = field.storage.path(new_filename)
                        os.makedirs(os.path.dirname(new_path), exist_ok=True)
                        os.rename(old_path, new_path)
                        setattr(self, field.name, new_filename)

                    # for next save
                    if len(args) > 0:
                        args = tuple(v if k >= 2 else False for k, v in enumerate(args))

                    kwargs['force_insert'] = False
                    kwargs['force_update'] = False

                orig_save(self, *args, **kwargs)

            cls.save = save


def tweak_field_class(orig_cls):
    orig_init = orig_cls.__init__

    def __init__(self, *args, **kwargs):
        if 'storage' not in kwargs:
            kwargs['storage'] = OverwriteFileSystemStorage()

        if orig_init and callable(orig_init):
            orig_init(self, *args, **kwargs)

    orig_cls.__init__ = __init__

    orig_contribute_to_class = orig_cls.contribute_to_class

    def contribute_to_class(self, cls, name):
        if orig_contribute_to_class and callable(orig_contribute_to_class):
            orig_contribute_to_class(self, cls, name)

        tweak_field_save(cls, self)

    orig_cls.contribute_to_class = contribute_to_class

    return orig_cls


def tweak_file_class(orig_cls):
    """
    Overriding FieldFile.save method to remove the old associated file.
    I'm doing the same thing in OverwriteFileSystemStorage, but it works just when the names match.
    I probably want to preserve both methods if anyone calls Storage.save.
    """

    orig_save = orig_cls.save

    def new_save(self, name, content, save=True):
        self.delete(save=False)

        if orig_save and callable(orig_save):
            orig_save(self, name, content, save=save)

    new_save.__name__ = 'save'
    orig_cls.save = new_save

    return orig_cls


@tweak_file_class
class OverwriteFieldFile(models.FileField.attr_class):
    pass


@tweak_file_class
class OverwriteImageFieldFile(models.ImageField.attr_class):
    pass


@tweak_field_class
class RenamedFileField(models.FileField):
    attr_class = OverwriteFieldFile


@tweak_field_class
class RenamedImageField(models.ImageField):
    attr_class = OverwriteImageFieldFile

and my upload_to callables look like this:我的 upload_to 可调用文件如下所示:

def user_image_path(instance, filename):
    name, ext = 'image', os.path.splitext(filename)[1]

    if instance.pk is not None:
        return os.path.join('users', os.path.join(str(instance.pk), name + ext))

    return os.path.join('users', '{0}_{1}{2}'.format(uuid1(), name, ext))

you can create a model instance by passing cleaned data from your form as **kwargs to django model i did it that way & its much easier than anything else您可以创建一个 model 实例,方法是将表单中的清理数据作为 **kwargs 传递给 django model 我就是这样做的,它比其他任何方法都容易得多

in your views post method add this (this code is from my project not adapted to this question)在你的观点后方法中添加这个(这段代码来自我的项目,不适应这个问题)

    pk = request.session['_auth_user_id']
    user_obj = User.objects.get(pk=pk)


    lab_form_instance = lab_form(request.POST,request.FILES)
    lab_form_instance.save(commit=False)
    # here you can put the form.is_valid() statement
    lab_form_instance.cleaned_data['owner'] =user_obj # here iam adding additional needed data for the model
    obj = lab(**lab_form_instance.cleaned_data)
    obj.save()

*django==4.1 *django==4.1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM