Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
42 changes: 3 additions & 39 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
name: CI

on:
push:
branches:
- main
pull_request:

jobs:

lint:
runs-on: ubuntu-latest
strategy:
matrix:
lint-command:
- bandit -r . -x ./tests
- black --check --diff .
- flake8 .
- isort --check-only --diff .
- pydocstyle .
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.x"
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- run: python -m pip install -e .[lint]
- run: ${{ matrix.lint-command }}

dist:
runs-on: ubuntu-latest
steps:
Expand All @@ -43,11 +20,8 @@ jobs:
- uses: actions/upload-artifact@v5
with:
path: dist/*

pytest-os:
name: PyTest
needs:
- lint
strategy:
matrix:
os:
Expand All @@ -67,11 +41,8 @@ jobs:
with:
flags: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}

pytest-python:
name: PyTest
needs:
- lint
strategy:
matrix:
python-version:
Expand All @@ -80,7 +51,7 @@ jobs:
- "3.12"
- "3.13"
django-version:
- "4.2" # LTS
- "4.2" # LTS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -95,11 +66,8 @@ jobs:
with:
flags: py${{ matrix.python-version }}
token: ${{ secrets.CODECOV_TOKEN }}

pytest-django:
name: PyTest
needs:
- lint
strategy:
matrix:
python-version:
Expand All @@ -122,11 +90,8 @@ jobs:
with:
flags: dj${{ matrix.django-version }}
token: ${{ secrets.CODECOV_TOKEN }}

pytest-extras:
name: PyTest
needs:
- lint
strategy:
matrix:
extras:
Expand All @@ -151,10 +116,9 @@ jobs:
with:
flags: ${{ matrix.extras }}
token: ${{ secrets.CODECOV_TOKEN }}

codeql:
name: CodeQL
needs: [ dist, pytest-os, pytest-python, pytest-django, pytest-extras ]
needs: [dist, pytest-os, pytest-python, pytest-django, pytest-extras]
runs-on: ubuntu-latest
permissions:
actions: read
Expand All @@ -163,7 +127,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ python ]
language: [python]
steps:
- name: Checkout
uses: actions/checkout@v5
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
name: Release

on:
release:
types: [published]
workflow_dispatch:

jobs:
pypi-build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
Expand All @@ -20,14 +17,12 @@ jobs:
with:
name: release-dists
path: dist/

pypi-publish:
runs-on: ubuntu-latest
needs:
- pypi-build
permissions:
id-token: write

steps:
- uses: actions/download-artifact@v6
with:
Expand Down
48 changes: 48 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-ast
- id: check-toml
- id: check-yaml
- id: check-symlinks
- id: debug-statements
- id: end-of-file-fixer
exclude: "pictures/templates/pictures/attrs.html"
- id: no-commit-to-branch
args: [--branch, main]
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.29.0
hooks:
- id: django-upgrade
- repo: https://github.com/hukkin/mdformat
rev: 0.7.22
hooks:
- id: mdformat
additional_dependencies:
- mdformat-ruff
- mdformat-deflist
- mdformat-footnote
- mdformat-gfm
- mdformat-gfm-alerts
- mdformat-tables
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.3
hooks:
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/google/yamlfmt
rev: v0.19.0
hooks:
- id: yamlfmt
ci:
autoupdate_schedule: weekly
skip:
- no-commit-to-branch
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ and are ready to adopt in your project :)
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")
Expand Down Expand Up @@ -77,7 +78,7 @@ python3 -m pip install django-pictures
# settings.py
INSTALLED_APPS = [
# ...
'pictures',
"pictures",
]

# the following are defaults, but you can override them
Expand All @@ -96,7 +97,6 @@ PICTURES = {
"USE_PLACEHOLDERS": True,
"QUEUE_NAME": "pictures",
"PROCESSOR": "pictures.tasks.process_picture",

}
```

Expand Down Expand Up @@ -155,8 +155,8 @@ 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"],
upload_to="avatars",
aspect_ratios=[None, "1/1", "3/2", "16/9"],
)
```

Expand Down Expand Up @@ -263,6 +263,7 @@ 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()
```
Expand All @@ -274,6 +275,7 @@ 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=["AVIF"])
```
Expand Down Expand Up @@ -366,5 +368,4 @@ class MyPicture(Picture):
[django-rq]: https://github.com/rq/django-rq
[dramatiq]: https://dramatiq.io/
[drf]: https://www.django-rest-framework.org/
[libavif-install]: https://pillow.readthedocs.io/en/latest/installation/building-from-source.html#external-libraries
[migration]: tests/testapp/migrations/0002_alter_profile_picture.py
10 changes: 4 additions & 6 deletions pictures/migrations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Type

from django.db import models
from django.db.migrations import AlterField
from django.db.models import Q
Expand Down Expand Up @@ -28,7 +26,7 @@ def database_backwards(self, app_label, schema_editor, from_state, to_state):
self.alter_picture_field(from_model, to_model)

def alter_picture_field(
self, from_model: Type[models.Model], to_model: Type[models.Model]
self, from_model: type[models.Model], to_model: type[models.Model]
):
from_field = from_model._meta.get_field(self.name)
to_field = to_model._meta.get_field(self.name)
Expand All @@ -46,7 +44,7 @@ def alter_picture_field(
):
self.update_pictures(from_field, to_model)

def update_pictures(self, from_field: PictureField, to_model: Type[models.Model]):
def update_pictures(self, from_field: PictureField, to_model: type[models.Model]):
"""Remove obsolete pictures and create new ones."""
for obj in to_model._default_manager.exclude(
Q(**{self.name: ""}) | Q(**{self.name: None})
Expand All @@ -57,13 +55,13 @@ def update_pictures(self, from_field: PictureField, to_model: Type[models.Model]
)
new_field_file.update_all(old_field_file)

def from_picture_field(self, from_model: Type[models.Model]):
def from_picture_field(self, from_model: type[models.Model]):
for obj in from_model._default_manager.all().iterator():
field_file = getattr(obj, self.name)
field_file.delete_all()

def to_picture_field(
self, from_model: Type[models.Model], to_model: Type[models.Model]
self, from_model: type[models.Model], to_model: type[models.Model]
):
from_field = from_model._meta.get_field(self.name)
if hasattr(from_field.attr_class, "delete_variations"):
Expand Down
6 changes: 3 additions & 3 deletions pictures/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def url(self) -> str:
def height(self) -> int | None:
if self.aspect_ratio:
return math.floor(self.width / self.aspect_ratio)
return None

@property
def name(self) -> str:
Expand Down Expand Up @@ -129,7 +130,6 @@ def delete(self):


class PictureFieldFile(ImageFieldFile):

def __xor__(self, other) -> tuple[set[Picture], set[Picture]]:
"""Return the new and obsolete :class:`Picture` instances."""
if not isinstance(other, PictureFieldFile):
Expand Down Expand Up @@ -176,7 +176,7 @@ def update_all(self, other: PictureFieldFile | None = None):
@property
def width(self):
self._require_file()
if self._committed and self.field.width_field:
if self._committed and self.field.width_field: # NoQA SIM102
if width := getattr(self.instance, self.field.width_field, None):
# get width from width field, to avoid loading image
return width
Expand All @@ -185,7 +185,7 @@ def width(self):
@property
def height(self):
self._require_file()
if self._committed and self.field.height_field:
if self._committed and self.field.height_field: # NoQA SIM102
if height := getattr(self.instance, self.field.height_field, None):
# get height from height field, to avoid loading image
return height
Expand Down
10 changes: 4 additions & 6 deletions pictures/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def noop(*args, **kwargs) -> None:


class PictureProcessor(Protocol):

def __call__(
self,
storage: tuple[str, list, dict],
Expand All @@ -33,11 +32,10 @@ def _process_picture(
old = old or []
storage = utils.reconstruct(*storage)
if new:
with storage.open(file_name) as fs:
with Image.open(fs) as img:
for picture in new:
picture = utils.reconstruct(*picture)
picture.save(img)
with storage.open(file_name) as fs, Image.open(fs) as img:
for picture in new:
picture = utils.reconstruct(*picture)
picture.save(img)

for picture in old:
picture = utils.reconstruct(*picture)
Expand Down
22 changes: 10 additions & 12 deletions pictures/templatetags/pictures.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,16 @@ def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
img_attrs[key[4:]] = value
else:
raise TypeError(f"Invalid keyword argument: {key}")
return tmpl.render(
{
"field_file": field_file,
"alt": img_alt,
"ratio": (ratio or "3/2").replace("/", "x"),
"sources": sources,
"media": utils.sizes(field=field, container_width=container, **breakpoints),
"picture_attrs": picture_attrs,
"img_attrs": img_attrs,
"use_placeholders": settings.USE_PLACEHOLDERS,
}
)
return tmpl.render({
"field_file": field_file,
"alt": img_alt,
"ratio": (ratio or "3/2").replace("/", "x"),
"sources": sources,
"media": utils.sizes(field=field, container_width=container, **breakpoints),
"picture_attrs": picture_attrs,
"img_attrs": img_attrs,
"use_placeholders": settings.USE_PLACEHOLDERS,
})


@register.simple_tag()
Expand Down
Loading