python - Django - 保存图像的多个版本

标签 python django forms image modeling

我的应用程序需要保存上传图像的多个版本。一张高质量图像和另一张仅用于缩略图使用(低质量)。
目前这大部分时间都在工作,但有时保存方法只是失败并且我的所有缩略图图像都被删除,特别是如果我在表单中使用 remove_cover 复选框

raise ValueError("The '%s' attribute has no file associated with it." % self.field.name) app | ValueError: The 'postcover_tn' attribute has no file associated with it.


-> 在此处查看完整跟踪:https://pastebin.com/hgieMGet
模型.py
class Post(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField()
    content = models.TextField(blank=False)
    postcover = models.ImageField(
        verbose_name="Post Cover",
        blank=True,
        null=True,
        upload_to=image_uploads,
    )
    postcover_tn = models.ImageField(
        verbose_name="Post Cover Thumbnail",
        blank=True,
        null=True,
        upload_to=image_uploads,
    )
    published_date = models.DateTimeField(auto_now_add=True, null=True)

    def save(self, *args, **kwargs):
        super(Post, self).save(*args, **kwargs)
        if self.postcover:
            if not (self.postcover_tn and os.path.exists(self.postcover_tn.path)):
                image = Image.open(self.postcover)
                outputIoStream = BytesIO()
                baseheight = 500
                hpercent = baseheight / image.size[1]
                wsize = int(image.size[0] * hpercent)
                imageTemproaryResized = image.resize((wsize, baseheight))
                imageTemproaryResized.save(outputIoStream, format='PNG')
                outputIoStream.seek(0)
                self.postcover = InMemoryUploadedFile(outputIoStream, 'ImageField',
                                                      "%s.png" % self.postcover.name.split('.')[0], 'image/png',
                                                      sys.getsizeof(outputIoStream), None)
                image = Image.open(self.postcover)
                outputIoStream = BytesIO()
                baseheight = 175
                hpercent = baseheight / image.size[1]
                wsize = int(image.size[0] * hpercent)
                imageTemproaryResized = image.resize((wsize, baseheight))
                imageTemproaryResized.save(outputIoStream, format='PNG')
                outputIoStream.seek(0)
                self.postcover_tn = InMemoryUploadedFile(outputIoStream, 'ImageField',
                                                      "%s.png" % self.postcover.name.split('.')[0], 'image/png',
                                                      sys.getsizeof(outputIoStream), None)
        elif self.postcover_tn:
            self.postcover_tn.delete()

        super(Post, self).save(*args, **kwargs)
我似乎也无法正确解决:
  • self.postcover_tn.delete() -> 类“InMemoryUploadedFile”的未解析属性引用“delete”
  • self.postcover_tn.path -> 类“InMemoryUploadedFile”的未解析属性引用“path”

  • 表格.py:
    def save(self, commit=True):
        instance = super(PostForm, self).save(commit=False)
        if self.cleaned_data.get('remove_cover'):
            try:
                os.unlink(instance.postcover.path)
            except OSError:
                pass
            instance.postcover = None
        if commit:
            instance.save()
        return instance
    

    最佳答案

    也许如果我们从另一个角度看问题,我们可以以其他方式解决它,开箱即用。 signals在处理图像(添加、更新和删除)以及我如何设法解决您的问题时非常方便:
    models.py :

    # from django.template.defaultfilters import slugify
    
    
    class Post(models.Model):
        id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
        author = models.ForeignKey(User, on_delete=models.CASCADE)
        title = models.CharField()
        # slug = models.SlugField('slug', max_length=255,
            # unique=True, null=True, blank=True,
            # help_text='If blank, the slug will be generated automatically from the given title.'
        # )
        content = models.TextField(blank=False)
    
        # ------------------------------------------------------------------------------------
    
        # rename images with the current post id/pk (which is UUID) and keep the extension
        # for cover thumbnail we append "_thumbnail" to the name 
    
        # e.g: 
        # img/posts/77b122a3d241461b80c51adc41d719fb.jpg
        # img/posts/77b122a3d241461b80c51adc41d719fb_thumbnail.jpg
    
        def upload_cover(instance, filename):
            ext = filename.split('.')[-1]
            filename = '{}.{}'.format(instance.id, ext)
            path = 'img/posts/'
            return '{}{}'.format(path, filename)
    
        postcover = models.ImageField('Post Cover',
            upload_to=upload_cover,  # callback function
            null=True, blank=True,
            help_text=_('Upload Post Cover.')
        )
    
        def upload_thumbnail(instance, filename):
            ext = filename.split('.')[-1]
            filename = '{}_thumbnail.{}'.format(instance.id, ext)
            path = 'img/posts/'
            return '{}{}'.format(path, filename)
    
        postcover_tn = models.ImageField('Post Cover Thumbnail',
            upload_to=upload_thumbnail,  # callback function
            null=True, blank=True,
            help_text=_('Upload Post Cover Thumbnail.')
        )
    
        # ------------------------------------------------------------------------------------
    
        published_date = models.DateTimeField(auto_now_add=True, null=True)
    
        def save(self, *args, **kwargs):
    
            # i moved the logic to signals
    
            # if not self.slug:
                # self.slug = slugify(self.title)
            super(Post, self).save(*args, **kwargs)
    
    
    创建新文件并重命名 signals.py (靠近 models.py ):
    import io
    import sys
    
    from PIL import Image
    
    from django.core.files.uploadedfile import InMemoryUploadedFile
    from django.dispatch import receiver
    from django.db.models.signals import pre_save, pre_delete
    
    from .models import Post
    
    #  DRY
    def image_resized(image, h):
        name = image.name
        _image = Image.open(image)
        content_type = Image.MIME[_image.format]
        r = h / _image.size[1]  # ratio
        w = int(_image.size[0] * r)
        imageTemproaryResized = _image.resize((w, h))
        file = io.BytesIO()
        imageTemproaryResized.save(file, _image.format)
        file.seek(0)
        size = sys.getsizeof(file)
        return file, name, content_type, size
    
    
    @receiver(pre_save, sender=Post, dispatch_uid='post.save_image')
    def save_image(sender, instance, **kwargs):
    
        # add image (cover | thumbnail)
        if instance._state.adding:
    
            #  postcover
            file, name, content_type, size = image_resized(instance.postcover, 500)
            instance.postcover = InMemoryUploadedFile(file, 'ImageField', name, content_type, size, None)
    
            #  postcover_tn
            file, name, content_type, size = image_resized(instance.postcover_tn, 175)
            instance.postcover_tn = InMemoryUploadedFile(file, 'ImageField', name, content_type, size, None)
    
    
        # update image (cover | thumbnail)
        if not instance._state.adding:
            # we have 2 cases:
            # - replace old with new
            # - delete old (when 'clear' checkbox is checked)
    
            #  postcover
            old = sender.objects.get(pk=instance.pk).postcover
            new = instance.postcover
            if (old and not new) or (old and new and old.url != new.url):
                old.delete(save=False)
    
            #  postcover_tn
            old = sender.objects.get(pk=instance.pk).postcover_tn
            new = instance.postcover_tn
            if (old and not new) or (old and new and old.url != new.url):
                old.delete(save=False)
    
    
    @receiver(pre_delete, sender=Post, dispatch_uid='post.delete_image')
    def delete_image(sender, instance, **kwargs):
        s = sender.objects.get(pk=instance.pk)
    
        if (not s.postcover or s.postcover is not None) and (not s.postcover_tn or s.postcover_tn is not None):
            s.postcover.delete(False)
            s.postcover_tn.delete(False)
    
    apps.py :
    我们需要在 apps.py 中注册信号因为我们使用装饰器 @receiver :
    from django.apps import AppConfig
    from django.utils.translation import ugettext_lazy as _
    
    
    class BlogConfig(AppConfig):  # change to the name of your app
        name = 'blog'  # and here
        verbose_name = _('Blog Entries')
    
        def ready(self):
            from . import signals
    
    这是post的第一个截图管理区
    enter image description here
    由于缩略图是从帖子封面生成的,作为 UI/UX 良好实践,无需显示帖子封面缩略图的第二个输入文件(我将第二个图像字段保留在 admin.py 中只读)。
    下面是我上传图片后的第二个截图
    PS:截图是从我正在开发的另一个应用程序中截取的,所以几乎没有变化,在你的情况下你应该看到
  • 帖子封面相反 特色图片
  • Currently: img/posts/8b0be417db564c53ad06cb493029e2ca.jpg (参见 upload_cover() 中的 models.py )代替 Currently: img/blog/posts/featured/8b0be417db564c53ad06cb493029e2ca.jpg

  • enter image description here
    admin.py
    # "img/posts/default.jpg" and "img/posts/default_thumbnail.jpg" are placeholders
    # grab to 2 image placeholders from internet and put them under "/static" folder
    
    def get_post_cover(obj):
        src = obj.postcover.url if obj.postcover and \
        hasattr(obj.postcover, 'url') else os.path.join(
            settings.STATIC_URL, 'img/posts/default.jpg')
        return mark_safe('<img src="{}" height="500" style="border:1px solid #ccc">'.format(src))
    get_post_cover.short_description = ''
    get_post_cover.allow_tags = True
    
    def get_post_cover_thumbnail(obj):
        src = obj.postcover_tn.url if obj.postcover_tn and \
        hasattr(obj.postcover_tn, 'url') else os.path.join(
            settings.STATIC_URL, 'img/posts/default_thumbnail.jpg')
        return mark_safe('<img src="{}" height="175" style="border:1px solid #ccc">'.format(src))
    get_post_cover_thumbnail.short_description = ''
    get_post_cover_thumbnail.allow_tags = True
    
    
    class PostAdmin(admin.ModelAdmin):
        list_display = ('title', .. )
        fields = (
            'author', 'title', 'content',
            get_post_cover, get_post_cover_thumbnail, 'postcover',
        )
        readonly_fields = (get_post_cover, get_post_cover_thumbnail)
    
    [..]
    
    最后你不需要任何 删除 save() 中的逻辑函数在 forms.py

    关于python - Django - 保存图像的多个版本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62739178/

    相关文章:

    python - Pandas 数据帧 : extract unique component as columns

    python - 如何使用 python 将 3D vtk 渲染场景导出到 paraview?

    python - 如何使用 Python 确定从星期日开始的一年中的第几周?

    django - 更改 django 管理中持续时间字段的格式

    Django ORM "get"转换为 SQL

    java - 如何在 Play 中将动态表单字段绑定(bind)到 HashSet!用Java?

    mysql - 微软 Access : Using Single form to enter query parameters in MS access

    jquery - 想法 - 最好让所有元素都在表格中或一秒钟后将它们添加到那里?

    Python 将 utf-8 字节转换为字符串

    python - Django 缓存 - Pickle 很慢