Django admin file upload with current model id

前端 未结 5 1027
轮回少年
轮回少年 2020-12-01 03:29

I\'m trying to create a simple photo gallery with the default Django admin. I\'d like to save a sample photo for each gallery, but I don\'t want to keep the filname. Instead

相关标签:
5条回答
  • 2020-12-01 03:35

    The image file gets saved before Gallery instance. So you have to split the saving to two phases by using signals w/ Gallery instance itself carrying the state:

    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

    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. Also, you may want to normalize filename extension (which is unnecessary because of the mimetype) like

    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
    
    0 讨论(0)
  • 2020-12-01 03:37

    For Django 2.2, follow the below code.

    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'.

    P.S.: This will work for DRF as well when you save via views.py. Note that second if (condition) is required for DRF.

    0 讨论(0)
  • 2020-12-01 03:41

    Using Louis's answer, here is recipe to process all FileField in the model:

    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)
    
    0 讨论(0)
  • 2020-12-01 03:45

    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.

    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.

    0 讨论(0)
  • 2020-12-01 03:54

    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.

    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:

    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))
    
    0 讨论(0)
提交回复
热议问题