Giter VIP home page Giter VIP logo

django-pictures's Introduction

Django Pictures Logo

Django Pictures

Responsive cross-browser image library using modern codes like AVIF & WebP.

  • responsive web images using the picture tag
  • native grid system support
  • serve files with or without a CDN
  • placeholders for local development
  • migration support
  • async image processing for Celery, Dramatiq or Django RQ
  • DRF support

PyPi Version Test Coverage GitHub License

Usage

Before you start, it can be a good idea to understand the fundamentals of responsive images.

Once you get a feeling how complicated things can get with all device types, you'll probably find a new appreciation for this package and are ready to adopt in you project :)

# models.py
from django.db import models
from pictures.models import PictureField

class Profile(models.Model):
    title = models.CharField(max_length=255)
    picture = PictureField(upload_to="avatars")
<!-- template.html -->
{% load pictures %}
{% picture profile.picture img_alt="Spiderman" img_loading="lazy" picture_class="my-picture" m=6 l=4 %}

The keyword arguments m=6 l=4 define the columns the image should take up in a grid at a given breakpoint. So in this example, the image will take up 6 columns on medium screens and 4 columns on large screens. You can define your grid and breakpoints as you want, refer to the grid columns and breakpoints sections.

The template above will render into:

<picture class="my-picture">
  <source type="image/webp"
          srcset="/media/testapp/profile/image/800w.webp 800w, /media/testapp/profile/image/100w.webp 100w, /media/testapp/profile/image/200w.webp 200w, /media/testapp/profile/image/300w.webp 300w, /media/testapp/profile/image/400w.webp 400w, /media/testapp/profile/image/500w.webp 500w, /media/testapp/profile/image/600w.webp 600w, /media/testapp/profile/image/700w.webp 700w"
          sizes="(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 33vw, 600px">
  <img src="/media/testapp/profile/image.jpg" alt="Spiderman" width="800" height="800" loading="lazy">
</picture>

Note that arbitrary attributes can be passed to either the <picture> or <img> element by prefixing parameters to the {% picture %} tag with picture_ or img_ respectively.

Setup

Installation

python3 -m pip install django-pictures

Settings

# settings.py
INSTALLED_APPS = [
    # ...
    'pictures',
]

# the following are defaults, but you can override them
PICTURES = {
    "BREAKPOINTS": {
        "xs": 576,
        "s": 768,
        "m": 992,
        "l": 1200,
        "xl": 1400,
    },
    "GRID_COLUMNS": 12,
    "CONTAINER_WIDTH": 1200,
    "FILE_TYPES": ["WEBP"],
    "PIXEL_DENSITIES": [1, 2],
    "USE_PLACEHOLDERS": True,
    "QUEUE_NAME": "pictures",
    "PROCESSOR": "pictures.tasks.process_picture",

}

If you have either Dramatiq or Celery installed, we will default to async image processing. You will need workers to listen to the pictures queue.

Placeholders

This library comes with dynamically created placeholders to simplify local development. To enable them, add the following to enable the PICTURES["USE_PLACEHOLDERS"] setting and add the following URL configuration:

# urls.py
from django.urls import include, path
from pictures.conf import get_settings

urlpatterns = [
    # ...
]

if get_settings().USE_PLACEHOLDERS:
    urlpatterns += [
        path("_pictures/", include("pictures.urls")),
    ]

Legacy use-cases (email)

Although the picture-tag is adequate for most use-cases, some remain, where a single img tag is necessary. Notably in email, where most clients do support WebP but not srcset. The template tag img_url returns a single size image URL. In addition to the ratio you will need to define the file_type as well as the width (absolute width in pixels).

{% load pictures %}
<img src="{% img_url profile.picture ratio="3/2" file_type="webp" width=800 %}" alt="profile picture">

Config

Aspect ratios

You can specify the aspect ratios of your images. Images will be cropped to the specified aspect ratio. Aspect ratios are specified as a string with a slash between the width and height. For example, 16/9 will crop the image to 16:9.

# models.py
from django.db import models
from pictures.models import PictureField


class Profile(models.Model):
    title = models.CharField(max_length=255)
    picture = PictureField(
      upload_to="avatars",
      aspect_ratios=[None, "1/1", "3/2", "16/9"],
    )
# template.html
{% load pictures %}
{% picture profile.picture img_alt="Spiderman" ratio="16/9" m=6 l=4 %}

If you don't specify an aspect ratio or None in your template, the image will be served with the original aspect ratio of the file.

You may only use aspect ratios in templates, that have been defined on the model. The model aspect_ratios will default to [None], if other value is provided.

Breakpoints

You may define your own breakpoints, they should be identical to the ones used in your css library. Simply override the PICTURES["BREAKPOINTS"] setting.

Grid columns

Grids are so common in web design, that they even made it into CSS. We default to 12 columns, but you can override this setting, via the PICTURES["GRID_COLUMNS"] setting.

Container width

Containers are commonly used to limit the maximum width of layouts, to promote better readability on larger screens. We default to 1200px, but you can override this setting, via the PICTURES["CONTAINER_WIDTH"] setting.

You may also set it to None, should you not use a container.

File types

Unless you still services IE11 clients, you should be fine serving just WebP. Sadly, AVIF (WebP's successor) is not yet supported by Pillow.

If you are serving IE11 use add JPEG to the list. Beware though, that this may drastically increase you storage needs.

Pixel densities

Unless you really care that your images hold of if you hold your UHD phone very close to your eyeballs, you should be fine, serving at the default 1x and 2x densities.

Async image processing

If you have either Dramatiq or Celery installed, we will default to async image processing. You will need workers to listen to the pictures queue. You can override the queue name, via the PICTURES["QUEUE_NAME"] setting.

You can also override the processor, via the PICTURES["PROCESSOR"] setting. The default processor is pictures.tasks.process_picture. It takes a single argument, the PictureFileFile instance. You can use this to override the processor, should you need to do some custom processing.

Migrations

Django doesn't support file field migrations, but we do. You can simply auto create the migration and replace Django's AlterField operation with AlterPictureField. That's it.

You can follow the example in our test app, to see how it works.

Contrib

Django Rest Framework (DRF)

We do ship with a read-only PictureField that can be used to include all available picture sizes in a DRF serializer.

from rest_framework import serializers
from pictures.contrib.rest_framework import PictureField

class PictureSerializer(serializers.Serializer):
    picture = PictureField()

The response can be restricted to a fewer aspect ratios and file types, by providing the aspect_ratios and file_types arguments to the DRF field.

from rest_framework import serializers
from pictures.contrib.rest_framework import PictureField

class PictureSerializer(serializers.Serializer):
    picture = PictureField(aspect_ratios=["16/9"], file_types=["WEBP"])

You also may provide optional GET parameters to the serializer, to specify the aspect ratio and breakpoints you want to include in the response. The parameters are prefixed with the fieldname_ to avoid conflicts with other fields.

curl http://localhost:8000/api/path/?picture_ratio=16%2F9&picture_m=6&picture_l=4
# %2F is the url encoded slash
{
  "other_fields": "",
  "picture": {
    "url": "/path/to/image.jpg",
    "width": 800,
    "height": 800,
    "ratios": {
      "1/1": {
        "sources": {
          "image/webp": {
            "100": "/path/to/image/1/100w.webp",
            "200": ""
          }
        },
        "media": "(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 33vw, 25vw"
      }
    }
  }
}

Note that the media keys are only included, if you have specified breakpoints.

Django Cleanup

PictureField is compatible with Django Cleanup, which automatically deletes its file and corresponding SimplePicture files.

django-pictures's People

Contributors

1r00t avatar amureki avatar codingjoe avatar dependabot[bot] avatar jnns avatar krtko1 avatar marcorichetta avatar nekidev avatar schubisu avatar truongvan avatar umerf52 avatar vchrisb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

django-pictures's Issues

delete orphaned images

django-stdimage has an option to delete orphaned images (delete_orphans=True), when updating an image or deleting the associated image.
It would be great if django-pictures would have a similar capability.

Additionality when removing an aspect_ratio from a model, the obsolete images are not deleted.

I/O operation on closed file.

When saving an instance of m model, I'm getting the following error:

Internal Server Error: /admin/gifs/gif/add/
Traceback (most recent call last):
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\asgiref\sync.py", line 
486, in thread_handler
    raise exc_info[1]
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\core\handlers\exception.py", line 42, in inner
    response = await get_response(request)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\core\handlers\base.py", line 253, in _get_response_async
    response = await wrapped_callback(
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\asgiref\sync.py", line 
448, in __call__
    ret = await asyncio.wait_for(future, timeout=None)
  File "C:\Users\bradl\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 408, in wait_for
    return await fut
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\asgiref\current_thread_executor.py", line 22, in run
    result = self.fn(*self.args, **self.kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\asgiref\sync.py", line 
490, in thread_handler
    return func(*args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\contrib\admin\options.py", line 688, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\utils\decorators.py", line 134, in _wrapper_view
    response = view_func(request, *args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\views\decorators\cache.py", line 62, in _wrapper_view_func
    response = view_func(request, *args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\contrib\admin\sites.py", line 242, in inner
    return view(request, *args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\contrib\admin\options.py", line 1886, in add_view
    return self.changeform_view(request, None, form_url, extra_context)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\utils\decorators.py", line 46, in _wrapper
    return bound_method(*args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\utils\decorators.py", line 134, in _wrapper_view
    response = view_func(request, *args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\contrib\admin\options.py", line 1747, in changeform_view
    return self._changeform_view(request, object_id, form_url, extra_context)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\contrib\admin\options.py", line 1798, in _changeform_view
    self.save_model(request, new_object, form, not add)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\contrib\admin\options.py", line 1227, in save_model
    obj.save()
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\base.py", line 814, in save
    self.save_base(
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\base.py", line 877, in save_base
    updated = self._save_table(
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\base.py", line 1020, in _save_table
    results = self._do_insert(
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\base.py", line 1061, in _do_insert
    return manager._insert(
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\query.py", line 1805, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\sql\compiler.py", line 1819, in execute_sql
    for sql, params in self.as_sql():
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\sql\compiler.py", line 1743, in as_sql
    value_rows = [
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\sql\compiler.py", line 1744, in <listcomp>
    [
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\sql\compiler.py", line 1745, in <listcomp>
    self.prepare_value(field, self.pre_save_val(field, obj))
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\sql\compiler.py", line 1693, in pre_save_val
    return field.pre_save(obj, add=True)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\django\db\models\fields\files.py", line 317, in pre_save
    file.save(file.name, file.file, save=False)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\pictures\models.py", line 97, in save
    self.save_all()
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\pictures\models.py", line 103, in save_all
    tasks.process_picture(self)
  File "C:\Users\bradl\Documents\GitHub\Nekos-API\api\.venv\lib\site-packages\pictures\tasks.py", line 17, in _process_picture
    with field_file.storage.open(field_file.name) as fs:
ValueError: I/O operation on closed file.

This is my model:

import uuid

from django.db import models

from pictures.models import PictureField

from dynamic_filenames import FilePattern

# Create your models here.


class Gif(models.Model):
    """
    Gif model.
    """

    class JSONAPIMeta:
        resource_name = "gif"

    id = models.UUIDField(
        default=uuid.uuid4, null=False, editable=False, primary_key=True
    )

    file = PictureField(
        upload_to=FilePattern(filename_pattern="uploads/gifs/{uuid:base32}{ext}"),
        aspect_ratios=[None, "16/9"],
        file_types=["WEBP", "GIF"],
    )

When creating instances from the admin site, that error is raised. I don't really know where to start.

django-pictures using file JPG type format throws an error 'JPG'

I'm trying to save an instance in Django admin UI but it throws an error

KeyError at /admin/general/sitesetting/1/change/

'JPG'

The error had faded away after I changed the FILE_TYPES variable settings from
"FILE_TYPES": ["WEBP","JPG"]
to
"FILE_TYPES": ["WEBP"],

Is the format JPG not supported or it's a Bug !?

Exception while saving

Hey, I use this module in a project with django-rest-framwork, gunicorn and nginx.
When I try to upload a new file from my client/frontend in production I get this:

Exception ignored in: <function _TemporaryFileCloser.__del__ at 0x7fa1d196a680>
Traceback (most recent call last):
  File "/usr/lib/python3.10/tempfile.py", line 589, in __del__
    self.close()
  File "/usr/lib/python3.10/tempfile.py", line 585, in close
    unlink(self.name)
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmph9zpjln8.upload.jpg'

Locally with runserver I didn't have any problems.

Any ideas what I can do about that? I don't quite understand what is going on.
I found this: random issue
But it doen't really help me.

DRF Serializer Support

This is a long-shot suggestion.

How do you feel about including serializer support like it "was" provided here.

I'd be happy to contribute this in the near future.

With dramatiq only the initial picture is being created

Hi, thanks for the amazing package. I'm new to both django-pictures and dramatiq.

I added django-dramatiq to my project and when I upload a picture, only the original is being uploaded.

It was working fine before installing django-dramatiq and dramatiq, and it is working again after uninstalled dramatiq.

This is my settings.py file with django-pictures and django-dramatiq:

# settings.py

INSTALLED_APPS = [
    ...
    "django_dramatiq",
    "pictures",
    ...
    "myapp",
]

DRAMATIQ_BROKER = {
    "BROKER": "dramatiq.brokers.redis.RedisBroker",
    "OPTIONS": {
        "url": env("REDIS_URL", "redis://127.0.0.1:6379"),
    },
    "MIDDLEWARE": [
        "dramatiq.middleware.Prometheus",
        "dramatiq.middleware.AgeLimit",
        "dramatiq.middleware.TimeLimit",
        "dramatiq.middleware.Callbacks",
        "dramatiq.middleware.Retries",
        "django_dramatiq.middleware.DbConnectionsMiddleware",
        "django_dramatiq.middleware.AdminMiddleware",
    ],
}

DRAMATIQ_RESULT_BACKEND = {
    "BACKEND": "dramatiq.results.backends.redis.RedisBackend",
    "BACKEND_OPTIONS": {
        "url": env("REDIS_URL", "redis://127.0.0.1:6379"),
    },
    "MIDDLEWARE_OPTIONS": {"result_ttl": 1000 * 60 * 10},
}

My model file:

# myapp/models.py

from django.db import models
from pictures.models import PictureField


class MyModel(models.Model):
    pictures = PictureField(
        upload_to="myapp/pictures/%Y/%m/%d/",
        aspect_ratios=[None, "1/1", "3/2", "16/9"],
        null=True,
        blank=True,
    )

Checked list:

  • Confirmed redis is up and running
  • Django can connect to redis
  • Found 3 keys of dramatiq in redis
key: dramatiq:pictures
value: ["c9166cce-e49b-4efd-81be-f62c1ad5dbbe"]

key: dramatiq:__heartbeats__
value: [{"value":"174e8865-67b4-4672-8202-ee16ca39c08f","score":"1672767537326"}]

key: dramatiq:pictures.msgs
value: {"c9166cce-e49b-4efd-81be-f62c1ad5dbbe":"{\"queue_name\":\"pictures\",\"actor_name\":\"process_picture_with_dramatiq\",\"args\":[],\"kwargs\":{\"app_name\":\"myapp\",\"model_name\":\"mymodel\",\"field_name\":\"pictures\",\"file_name\":\"myapp\/pictures\/2023\/01\/03\/mypicture.png\",\"storage_construct\":[\"django.core.files.storage.FileSystemStorage\",[],{}]},\"options\":{\"redis_message_id\":\"c9166cce-e49b-4efd-81be-f62c1ad5dbbe\"},\"message_id\":\"e39f3496-0957-4e1f-b6bf-4e0a8fb909de\",\"message_timestamp\":1672767537326}"}

Versions:

Django==4.1.4
django-dramatiq==0.11.2
django-pictures==1.0.0
djangorestframework==3.14.0
dramatiq==1.13.0

Am I doing something wrong in settings.py?

Similar to #66

ParamValidationError: Parameter validation failed

ParamValidationError: Parameter validation failed:
Invalid length for parameter Key, value: 0, valid min length: 1
(8 additional frame(s) were not displayed)
...
  File "boto3/resources/action.py", line 88, in __call__
    response = getattr(parent.meta.client, operation_name)(*args, **params)
  File "botocore/client.py", line 508, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "botocore/client.py", line 874, in _make_api_call
    request_dict = self._convert_to_request_dict(
  File "botocore/client.py", line 935, in _convert_to_request_dict
    request_dict = self._serializer.serialize_to_request(
  File "botocore/validate.py", line 381, in serialize_to_request
    raise ParamValidationError(report=report.generate_report())

Failed to process message <dramatiq.broker.MessageProxy object at 0x7f65d77dadd0> with unhandled exception.

The following error happens if the models gets saved with a blank picture field.

avif

hello
thanks for you great work
since
https://pypi.org/project/pillow-avif-plugin/
now exists
you think maybe we could have avif support as an optional installation?
like
pip install django-pictures[avif] to install the plugin as well

Can not install django-pictures

Hi,
Can not install using this command: python3 -m pip install django-pictures

Here is the error in my terminal:

ERROR: Ignored the following versions that require a different python version: 1.0a1 Requires-Python >=3.10; 1.0a2 Requires-Python >=3.10; 1.0a3 Requires-Python >=3.10; 1.0a4 Requires-Python >=3.10; 1.0a5 Requires-Python >=3.10
ERROR: Could not find a version that satisfies the requirement django-pictures (from versions: none)
ERROR: No matching distribution found for django-picture

Do you have a way to install it? I'm using Python 3.7

images are converted incorrect

First, thank you for you efforts! A test migration from django-stdimage did work for me.

My model is configured with aspect_ratios=[None, '4/3'], file_types=['WEBP'], pixel_densities=[1, 2], but all images are converted wrongly.

e.g. an original image has a dimension of 4288x2848 and is a JPG.

for the "none" ratio, there are images scaled from 100w to 2400w, but all (beside 800w,1600w and 2400w) are identical and have a dimension of 99x66.
800w, 1600w and 2400w are also identical but 799x531 and in good quality.

for the "4/3" ratio, all images have the correct image dimension, e.g. 800x600 for 800w, but are in a very poor quality.

I looked at the sources, but did not figure out what leads to this outcome.

Async image processing with Django Celery not working

The task is loaded, but when I sent the image through the admin site, nothing happened.
Another task I implemented will be executed without problems, and there seems to be no problem with Celery.

 -------------- celery@*** v5.3.6 (emerald-rush)
--- ***** ----- 
-- ******* ---- macOS-14.0-arm64-arm-64bit 2024-01-30 13:31:43
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         ***:**********
- ** ---------- .> transport:   redis://localhost:6379/0
- ** ---------- .> results:     
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . pictures.tasks.process_picture_with_celery

[2024-01-30 13:31:44,321: INFO/MainProcess] Connected to redis://localhost:6379/0
[2024-01-30 13:31:44,324: INFO/MainProcess] mingle: searching for neighbors
[2024-01-30 13:31:45,349: INFO/MainProcess] mingle: all alone
[2024-01-30 13:31:45,367: INFO/MainProcess] celery@*** ready.

Image gets rotated

When uploading an image it gets automatically rotated sometimes. Maybe based on EXIF data.
There is no option to prevent that from happening.

djanog-pictures not compatible wiht django-storages

I finally did test django-pictures with django-storages (using s3) and they are not compatible.
When creating or updating an image I do get The file cannot be reopened.

I think this is due to https://github.com/codingjoe/django-pictures/blob/main/pictures/tasks.py#L15 trying to open the (closed) file again. From my understanding django-storages is using in-memory for file processing, which does not allow it to be reopened.

I also looked at django-stdimage and this implementation is opening the file from storage:
https://github.com/codingjoe/django-stdimage/blob/4c7194a88fc970cb0d910f444a2de29911d4a630/stdimage/models.py#L72

❓ Maybe adopting this would fix this?
❓ Or not closing it in the first place before processing finished?

HINT: Set both the width_field and height_field attribute to avoid storage IO

Console INFO:
product.Product.thumbnail: (fields.E101) width_field and height_field attributes are missing
HINT: Set both the width_field and height_field attribute to avoid storage IO

    thumbnail = PictureField(
        _('Thumbnail'),
        upload_to="img/thumbnail",
    )

try save in django admin... img save more, return exception
image

WEBP thumbnails with text are blurred compared to the original PNG

Hello,

thank you for an excellent package.

I noticed that the WEBP thumbnails are somehow more blurred than images resized previously by other means (e.g., manually in GIMP). This is visible with a lossless (PNG) image containing text.

Since WEBP also seems lossless, I suspect that the image.thumbnail(size) call in pictures/models.py causes the issue. The thumbnail method uses a bicubic resampling filter by default.

Does it make sense to take the resampling filter from the configuration?

image.thumbnail(size, resample=conf.get_settings().RESAMPLING_FILTER)

Or set the filter to Resampling.LANCZOS?

image.thumbnail(size, resample=Resampling.LANCZOS)

Wrong width and height

I don't now if I'm doing wrong.
Here is the model settings:

image_picture_width = models.PositiveIntegerField(default=266)
image_picture_height = models.PositiveIntegerField(default=150)
image = PictureField(verbose_name = _("Category Image"), upload_to='tag_images', blank=True, width_field="image_picture_width", height_field="image_picture_height", aspect_ratios=[None, None, None, "16/9"])

In my template:

                {% picture tag.image alt="tag_image" m=2 l=2 style="max-height: 98px;object-fit: cover;" width="266" height="150" %}

I uploaded the image, but image_picture_width and image_picture_height were overridden (800px, 600px)

In Chrome inspector got this:

<picture>
  <source type="image/webp" srcset="/_pictures/tag_image/3x2/800w.WEBP 800w, /_pictures/tag_image/3x2/100w.WEBP 100w, /_pictures/tag_image/3x2/200w.WEBP 200w, /_pictures/tag_image/3x2/300w.WEBP 300w, /_pictures/tag_image/3x2/400w.WEBP 400w, /_pictures/tag_image/3x2/500w.WEBP 500w, /_pictures/tag_image/3x2/600w.WEBP 600w, /_pictures/tag_image/3x2/700w.WEBP 700w" sizes="(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 16vw, 200px">
  <img src="https://trivaty-dev.s3.amazonaws.com/media/tag_images/languages.jpg" alt="tag_image" style="max-height: 98px;object-fit: cover;" width="800" height="600">
</picture>

why the image width are not set correctly? how to fix that?

Thank your to clarify as no information are available in the docs.

Placeholder URL pattern doesn't support alt text containing slashes

When working with image placeholders and alt texts that contain slashes, the respective URLpattern can not be found because the slash character is interpreted as a delimiter between alt, width, ratio etc.

Passing the alt text as URL-encoded string and unquoting it in the utility function that generates the placeholder image should fix this.

django-storages compatibility

Hi, does anyone know if this plugin works with django-storages? I'm not able to make it work. With django-storages images are uploaded to S3 and with django-pictures I would like to generate WEBP images from originals on S3 ... Is this possible and does anyone have an example? Thank you. Best regards.

Error in documentation?

The readme says that the default settings are the following:

PICTURES = {
    "BREAKPOINTS": {
        "xs": 576,
        "s": 768,
        "m": 992,
        "l": 1200,
        "xl": 1400,
    },
    "GRID_COLUMNS": 12,
    "CONTAINER_WIDTH": 1200,
    "FILE_TYPES": ["WEBP"],
    "PIXEL_DENSITIES": [1, 2],
}

But if you run a Django project without adding the placeholder URLs an error is raised. Shouldn't it then be

PICTURES = {
    "BREAKPOINTS": {
        "xs": 576,
        "s": 768,
        "m": 992,
        "l": 1200,
        "xl": 1400,
    },
    "GRID_COLUMNS": 12,
    "CONTAINER_WIDTH": 1200,
    "FILE_TYPES": ["WEBP"],
    "PIXEL_DENSITIES": [1, 2],
    "USE_PLACEHOLDERS": True
}

?

Styling / Class on picture tag

In order to fully support customizability in styling for the images, it should be possible to add style or class to each of those. The two tags that I see are: img and picture. For picture it's currently not possible.

What does m=6 l=4 do?

What does m=6 l=4 do? I don't get it...

{% picture profile.picture img_alt="Spiderman" img_loading="lazy" picture_class="my-picture" m=6 l=4 %}

thanks

How to work with static files?

Might be a silly question (as in out of the context) but is there a canonical way to work with static files? I mean files manually placed during development, and then served using the staticfiles app.

`contrib.rest_framework.PictureField` serialization is slow

Greetings Johannes!

While doing bulk operations with images that involved this library, I noticed a noticeable difference in the serialization time compared to the standard DRF library.
Here is a quick snippet that I am trying:

import time
from django.db import models

from pictures.contrib.rest_framework import PictureField as PictureFieldRest
from rest_framework.fields import ImageField as DRFImageField

class MyPicture(models.Model):
    image = PictureField(
        aspect_ratios=["3/2", "1/1", "15/2"],
        file_types=["WEBP", "JPEG"],
        width_field="image_width",
        height_field="image_height",
    )
    image_width = models.PositiveSmallIntegerField(editable=False)
    image_height = models.PositiveSmallIntegerField(editable=False)


def test_serialization_speed():
    items_count = 5000

    start = time.time()
    serialized = []
    for item in MyPicture.objects.all()[:items_count]:
        serialized.append(
            DRFImageField().to_representation(item.image)
        )
    print("DRF ImageField serialization TIME: ", time.time() - start)

    start = time.time()
    serialized = []
    for item in MyPicture.objects.all()[:items_count]:
        serialized.append(
            PictureFieldRest().to_representation(item.image)
        )
    print("PictureField serialization TIME: ", time.time() - start)

The results on my dev machine (MacBook M1, 16 Gb) are the following:

DRF ImageField serialization TIME:  0.11259627342224121
PictureField serialization TIME:  11.82898998260498

I believe that the issue comes from the serialization of this part:
https://github.com/codingjoe/django-pictures/blob/main/pictures/contrib/rest_framework.py#L25-L38

We have plenty of combinations of aspect ratios and containers, each of them has to be serialized from SimplePicture object to its url, which leads to a heavy CPU-bound operation.
However, I cannot yet come up with the solution to that problem.
Would you have any ideas on this?

Best,
Rust

unable to setup

I got stuck on setup process, can you directed me to correct way to setup?

my model.py:

from django.db import models
from pictures.models import PictureField

class Gallery(models.Model):
    title = models.CharField(max_length=256)
    competition = models.ForeignKey(
        "competitions.Competition",
        on_delete=models.CASCADE,
        related_name="galleries",
    )

class Photo(models.Model):
    gallery = models.ForeignKey(
        "gallery.Gallery",
        on_delete=models.CASCADE,
        related_name="photos",
    )
    image = PictureField(upload_to='photos')

when I run makemigrations I get this warning and no migration is created

gallery.Photo.image: (fields.E101) width_field and height_field attributes are missing
	HINT: Please add two positive integer fields to 'gallery.Photo' and add their field names as the 'width_field' and 'height_field' attribute for your picture field. Otherwise Django will not be able to cache the image aspect size causing disk IO and potential response time increases.

Allow posting Pictures with DRF

In the short documentation it mentions this:

We do ship with a read-only PictureField that can be used to include all available picture sizes in a DRF serializer.

So there is currently no way to create a Picture through a DRF serializer?
Or how would you go about doing that?

With Celery only the initial image is being created

I added Celery to my project and when I upload a picture, only the original is being uploaded.

kroko@wedding:~/wedding-spa$ ll /var/www/media/pictures/
total 12256
drwxr-xr-x 2 root root    4096 Nov 18 18:56 ./
drwxr-xr-x 3 root root    4096 Nov 18 18:53 ../
-rw-r--r-- 1 root root 6248960 Nov 18 18:53 P1110218.JPG
-rw-r--r-- 1 root root 6290432 Nov 18 18:56 P1110225.JPG

No aspect ratio directories get created.

Maybe I'm doing something wrong, I'm new to Celery.

Migration to PictureField fails with blank=True

The migration process from StdImageField to PictureField fails when the former is allowed to be blank. This happens because the regular PictureFieldFile.save() method has a check for an existing field value in place but the migration calls PictureFieldFile.save_all() directly (skipping the check and tasks.process_picture() starts off by trying to open a non-existing file).

Should `aspect_ratios` default to `None`?

Greetings @codingjoe

Trying out your package, looks great so far!
I noticed one thing, that I am not sure about. aspect_ratios is a non-required attribute, so I left it out:

# models.py
class Picture(models.Model):
    image = PictureField(
        _("image"), upload_to=attached_to_id_pattern, blank=True, null=True
    )

# serializers.py

class PictureSerializer(serializers.ModelSerializer):
    image = PictureField()

    class Meta:
        model = models.Picture
        fields = ("id", "image")

After I did set the serializer with my fresh field and uploaded a picture, I ended up with the next payload:

"pictures": [
        {
            "id": 1,
            "image": {
                "null": {
                    "WEBP": {
                        "100": "/media/picture/74/XSWP/100w.webp",
                        "200": "/media/picture/74/XSWP/200w.webp",
                        "300": "/media/picture/74/XSWP/300w.webp",
                        "400": "/media/picture/74/XSWP/400w.webp",
                        "500": "/media/picture/74/XSWP/500w.webp",
                        "600": "/media/picture/74/XSWP/600w.webp"
                    }
                }
            }
        }
    ]

As you can see, there is null value that does not look right in the payload. And it comes from the empty aspect_ratios.
Should it default to something more meaningful, like "1/1"? Also, I am missing out a bit on the documentation where and how aspect_ratios would be used and affect the result.

Cheers,
Rust

NoReverseMatch : 'pictures' is not a registered namespace

In the last 1.0rc2 update I came across the error "NoReverseMatch : 'pictures' is not a registered namespace", after comparing the last version with the previous one I noticed that a default configuration for the USE_PLACEHOLDERS was missing, so I was able to fix this by adding to the PICTURES in the file settings.py

PICTURES = {
"BREAKPOINTS": {
"xs": 576,
"s": 768,
"m": 992,
"l": 1200,
"xl": 1400,
},
"GRID_COLUMNS": 12,
"CONTAINER_WIDTH": 1200,
"FILE_TYPES": ["WEBP"],
"PIXEL_DENSITIES": [1, 2],
"USE_PLACEHOLDERS": False, <- fixed the problem adding this line
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.