Skip to content
Open
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
6 changes: 6 additions & 0 deletions pictures/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ def delete_all(self):
self.name,
[],
[i.deconstruct() for i in self.get_picture_files_list()],
self.instance_name,
)

def update_all(self, other: PictureFieldFile | None = None):
Expand All @@ -171,8 +172,13 @@ def update_all(self, other: PictureFieldFile | None = None):
self.name,
[i.deconstruct() for i in new],
[i.deconstruct() for i in old],
self.instance_name,
)

@property
def instance_name(self):
return f"{self.instance._meta.app_label}.{self.instance._meta.model_name}.{self.field.name}"

@property
def width(self):
self._require_file()
Expand Down
3 changes: 3 additions & 0 deletions pictures/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import django.dispatch

process_picture_done = django.dispatch.Signal()
35 changes: 31 additions & 4 deletions pictures/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from typing import Protocol

from django.apps import apps
from django.db import transaction
from PIL import Image

from pictures import conf, utils
from pictures import conf, signals, utils


def noop(*args, **kwargs) -> None:
Expand All @@ -19,6 +20,7 @@ def __call__(
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None: ...


Expand All @@ -27,6 +29,7 @@ def _process_picture(
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
new = new or []
old = old or []
Expand All @@ -41,6 +44,21 @@ def _process_picture(
picture = utils.reconstruct(*picture)
picture.delete()

if field:
app_label, model_name, _ = field.split(".")
sender = apps.get_model(app_label=app_label, model_name=model_name)
else:
sender = _process_picture

signals.process_picture_done.send(
sender=sender,
storage=storage.deconstruct(),
file_name=file_name,
new=new,
old=old,
field=field,
)


process_picture: PictureProcessor = _process_picture

Expand All @@ -57,21 +75,24 @@ def process_picture_with_dramatiq(
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
_process_picture(storage, file_name, new, old)
_process_picture(storage, file_name, new, old, field)

def process_picture( # noqa: F811
storage: tuple[str, list, dict],
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
transaction.on_commit(
lambda: process_picture_with_dramatiq.send(
storage=storage,
file_name=file_name,
new=new,
old=old,
field=field,
)
)

Expand All @@ -91,14 +112,16 @@ def process_picture_with_celery(
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
_process_picture(storage, file_name, new, old)
_process_picture(storage, file_name, new, old, field)

def process_picture( # noqa: F811
storage: tuple[str, list, dict],
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
transaction.on_commit(
lambda: process_picture_with_celery.apply_async(
Expand All @@ -107,6 +130,7 @@ def process_picture( # noqa: F811
file_name=file_name,
new=new,
old=old,
field=field,
),
queue=conf.get_settings().QUEUE_NAME,
)
Expand All @@ -125,20 +149,23 @@ def process_picture_with_django_rq(
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
_process_picture(storage, file_name, new, old)
_process_picture(storage, file_name, new, old, field)

def process_picture( # noqa: F811
storage: tuple[str, list, dict],
file_name: str,
new: list[tuple[str, list, dict]] | None = None,
old: list[tuple[str, list, dict]] | None = None,
field: str = "",
) -> None:
transaction.on_commit(
lambda: process_picture_with_django_rq.delay(
storage=storage,
file_name=file_name,
new=new,
old=old,
field=field,
)
)
71 changes: 71 additions & 0 deletions tests/test_signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from unittest.mock import Mock

import pytest
from django.apps import apps
from django.dispatch import receiver

from pictures import signals, tasks
from tests.testapp.models import SimpleModel


@pytest.mark.django_db
def test_process_picture_sends_process_picture_done(image_upload_file):
obj = SimpleModel.objects.create(picture=image_upload_file)

handler = Mock()
signals.process_picture_done.connect(handler)

tasks._process_picture(
obj.picture.storage.deconstruct(),
obj.picture.name,
new=[i.deconstruct() for i in obj.picture.get_picture_files_list()],
)

handler.assert_called_once_with(
signal=signals.process_picture_done,
sender=tasks._process_picture,
storage=obj.picture.storage.deconstruct(),
file_name=obj.picture.name,
new=[i.deconstruct() for i in obj.picture.get_picture_files_list()],
old=[],
field="",
)


@pytest.mark.django_db
def test_process_picture_sends_process_picture_done_on_create(image_upload_file):
handler = Mock()
signals.process_picture_done.connect(handler)

obj = SimpleModel.objects.create(picture=image_upload_file)

handler.assert_called_once_with(
signal=signals.process_picture_done,
sender=SimpleModel,
storage=obj.picture.storage.deconstruct(),
file_name=obj.picture.name,
new=[i.deconstruct() for i in obj.picture.get_picture_files_list()],
old=[],
field="testapp.simplemodel.picture",
)


@pytest.mark.django_db
def test_processed_object_found(image_upload_file):
obj = SimpleModel.objects.create()

found_object = None

@receiver(signals.process_picture_done, sender=SimpleModel)
def handler(*, file_name, field, **__):
nonlocal found_object
app_label, model_name, field_name = field.split(".")
model = apps.get_model(app_label=app_label, model_name=model_name)

# Users can now modify the object that process_picture_done
# corresponds to
found_object = model.objects.get(**{field_name: file_name})

obj.picture.save("image.png", image_upload_file)

assert obj == found_object