diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c6569c7..3070c08 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,18 +46,16 @@ jobs:
- run: python -m pip install -r lint-requirements.txt
- run: ${{ matrix.lint-command }}
-
dist:
runs-on: ubuntu-latest
steps:
- - name: Install gettext
- run: sudo apt install gettext -y
- - uses: actions/setup-python@v3
- - run: python -m pip install --upgrade pip setuptools wheel twine readme-renderer
- uses: actions/checkout@v3
- - run: python setup.py sdist bdist_wheel
+ - uses: actions/setup-python@v3
+ - run: sudo apt install gettext -y
+ - run: python -m pip install --upgrade pip build wheel twine readme-renderer
+ - run: python -m build --sdist --wheel
- run: python -m twine check dist/*
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v3
with:
path: dist/*
@@ -73,11 +71,11 @@ jobs:
- "3.9"
- "3.10"
django-version:
- - "3.2a"
- - "4.0a"
+ - "3.2"
+ - "4.0"
extra:
- - ""
- - "progressbar"
+ - "test"
+ - "test,progressbar"
steps:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
@@ -85,10 +83,9 @@ jobs:
python-version: ${{ matrix.python-version }}
- run: sudo apt install gettext -y
- uses: actions/checkout@v3
- - run: python -m pip install --upgrade pip setuptools codecov wheel
- - run: python -m pip install .[${{ matrix.extra }}]
+ - run: python -m pip install --upgrade pip codecov
+ - run: python -m pip install -e .[${{ matrix.extra }}]
if: ${{ matrix.extra }}
- - run: python -m pip install django~=${{ matrix.django-version }}
- - name: Test with pytest
- run: python setup.py test
+ - run: python -m pip install django~=${{ matrix.django-version }}a
+ - run: python -m pytest
- run: codecov
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ff70343..19f0b4e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,10 +1,12 @@
-name: PyPi Release
+name: Release
-on: [release]
+on:
+ release:
+ types: [published]
jobs:
- build:
+ PyPi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -14,5 +16,5 @@ jobs:
- run: python -m build --sdist --wheel
- run: python -m twine upload dist/*
env:
- TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
+ TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
diff --git a/README.md b/README.md
index cc70313..77fe5f6 100644
--- a/README.md
+++ b/README.md
@@ -4,19 +4,110 @@
# Django Standardized Image Field
-Django Field that implement the following features:
+This package has been deprecated in favor of [django-pictures][django-pictures].
-* Django-Storages compatible (S3)
-* Resize images to different sizes
+## Migration Instructions
+
+First, make sure you understand the differences between the two packages and
+how to serve images in a modern web application via the [picture][picture-tag]-Element.
+
+Next, follow the setup instructions for [django-pictures][django-pictures].
+
+Once you are set up, change your models to use the new `PictureField` and provide the
+ `aspect_ratios` you'd like to serve. Do create migrations just yet.
+
+This step should be followed by changing your templates and frontend.
+The new placeholders feature for local development should help you
+to do this almost effortlessly.
+
+Finally, run `makemigrations` and replace the `AlterField` operation with
+`AlterPictureField`.
+
+We highly recommend to use Django's `image_width` and `image_height` fields, to avoid
+unnecessary IO. If you can add these fields to your model, you can use the following
+snippet to populate them:
+
+```python
+import django.core.files.storage
+from django.db import migrations, models
+import pictures.models
+from pictures.migrations import AlterPictureField
+
+def forward(apps, schema_editor):
+ for obj in apps.get_model("my-app.MyModel").objects.all().iterator():
+ obj.image_width = obj.logo.width
+ obj.image_height = obj.logo.height
+ obj.save(update_fields=["image_height", "image_width"])
+
+def backward(apps, schema_editor):
+ apps.get_model("my-app.MyModel").objects.all().update(
+ image_width=None,
+ image_height=None,
+ )
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('my-app', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="mymodel",
+ name="image_height",
+ field=models.PositiveIntegerField(editable=False, null=True),
+ ),
+ migrations.AddField(
+ model_name="mymodel",
+ name="image_width",
+ field=models.PositiveIntegerField(editable=False, null=True),
+ ),
+ migrations.RunPython(forward, backward),
+ AlterPictureField(
+ model_name="mymodel",
+ name="image",
+ field=pictures.models.PictureField(
+ aspect_ratios=["3/2", "3/1"],
+ breakpoints={"desktop": 1024, "mobile": 576},
+ container_width=1200,
+ file_types=["WEBP"],
+ grid_columns=12,
+ height_field="image_height",
+ pixel_densities=[1, 2],
+ storage=django.core.files.storage.FileSystemStorage(),
+ upload_to="pictures/",
+ verbose_name="image",
+ width_field="image_width",
+ ),
+ ),
+ ]
+```
+
+[django-pictures]: https://github.com/codingjoe/django-pictures
+[picture-tag]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
+
+## Why would I want this?
+
+This is a drop-in replacement for the [Django ImageField](https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.ImageField) that provides a standardized way to handle image uploads.
+It is designed to be as easy to use as possible, and to provide a consistent interface for all image fields.
+It allows images to be presented in various size variants (eg:thumbnails, mid, and hi-res versions)
+and it provides a way to handle images that are too large with validators.
+
+
+## Features
+
+Django Standardized Image Field implements the following features:
+
+* [Django-Storages](https://django-storages.readthedocs.io/en/latest/) compatible (eg: S3, Azure, Google Cloud Storage, etc)
+* Resizes images to different sizes
* Access thumbnails on model level, no template tags required
-* Preserves original image
-* Asynchronous rendering (Celery & Co)
-* Restrict accepted image dimensions
-* Rename files to a standardized name (using a callable upload_to)
+* Preserves original images
+* Can be rendered asynchronously (ie as a [Celery job](https://realpython.com/asynchronous-tasks-with-django-and-celery/))
+* Restricts acceptable image dimensions
+* Renames a file to a standardized name format (using a callable `upload_to` function, see below)
## Installation
-Simply install the latest stable package using the command
+Simply install the latest stable package using the following command:
```bash
pip install django-stdimage
@@ -28,11 +119,13 @@ and add `'stdimage'` to `INSTALLED_APP`s in your settings.py, that's it!
## Usage
+Now it's instally you can use either: `StdImageField` or `JPEGField`.
+
`StdImageField` works just like Django's own
[ImageField](https://docs.djangoproject.com/en/dev/ref/models/fields/#imagefield)
-except that you can specify different sized variations.
+except that you can specify different size variations.
-The `JPEGField` works similar to the `StdImageField` but all size variations are
+The `JPEGField` is identical to the `StdImageField` but all images are
converted to JPEGs, no matter what type the original file is.
### Variations
@@ -58,7 +151,7 @@ class MyModel(models.Model):
# is the same as dictionary-style call
image = StdImageField(upload_to='path/to/img', variations={'thumbnail': (100, 75)})
- # variations are converted to JPEGs
+ # JPEGField variations are converted to JPEGs.
jpeg = JPEGField(
upload_to='path/to/img',
variations={'full': (None, None), 'thumbnail': (100, 75)},
@@ -77,7 +170,7 @@ class MyModel(models.Model):
}, delete_orphans=True)
```
-For using generated variations in templates use `myimagefield.variation_name`.
+To use these variations in templates use `myimagefield.variation_name`.
Example:
@@ -85,15 +178,32 @@ Example:
```
-### Utils
+### Upload to function
-Since version 4 the custom `upload_to` utils have been dropped in favor of
-[Django Dynamic Filenames][dynamic_filenames].
+You can use a function for the `upload_to` argument. Using [Django Dynamic Filenames][dynamic_filenames].[dynamic_filenames]: https://github.com/codingjoe/django-dynamic-filenames
-[dynamic_filenames]: https://github.com/codingjoe/django-dynamic-filenames
+This allows images to be given unique paths and filenames based on the model instance.
+
+Example
+
+```python
+from django.db import models
+from stdimage import StdImageField
+from dynamic_filenames import FilePattern
+
+upload_to_pattern = FilePattern(
+ filename_pattern='my_model/{app_label:.25}/{model_name:.30}/{uuid:base32}{ext}',
+)
+
+
+class MyModel(models.Model):
+ # works just like django's ImageField
+ image = StdImageField(upload_to=upload_to_pattern)
+```
### Validators
-The `StdImageField` doesn't implement any size validation. Validation can be specified using the validator attribute
+The `StdImageField` doesn't implement any size validation out-of-the-box.
+However, Validation can be specified using the validator attribute
and using a set of validators shipped with this package.
Validators can be used for both Forms and Models.
@@ -120,9 +230,9 @@ Django [dropped support](https://docs.djangoproject.com/en/dev/releases/1.3/#del
for automated deletions in version 1.3.
Since version 5, this package supports a `delete_orphans` argument. It will delete
-orphaned files, should a file be delete or replaced via Django form or and object with
-a `StdImageField` be deleted. It will not be deleted if the field value is changed or
-reassigned programatically. In those rare cases, you will need to handle proper deletion
+orphaned files, should a file be deleted or replaced via a Django form and the object with
+the `StdImageField` be deleted. It will not delete files if the field value is changed or
+reassigned programatically. In these rare cases, you will need to handle proper deletion
yourself.
```python
@@ -141,10 +251,10 @@ class MyModel(models.Model):
### Async image processing
Tools like celery allow to execute time-consuming tasks outside of the request. If you don't want
-to wait for your variations to be rendered in request, StdImage provides your the option to pass a
-async keyword and a util.
-Note that the callback is not transaction save, but the file will be there.
-This example is based on celery.
+to wait for your variations to be rendered in request, StdImage provides you the option to pass an
+async keyword and a 'render_variations' function that triggers the async task.
+Note that the callback is not transaction save, but the file variations will be present.
+The example below is based on celery.
`tasks.py`:
```python
@@ -177,19 +287,18 @@ def image_processor(file_name, variations, storage):
class AsyncImageModel(models.Model):
image = StdImageField(
# above task definition can only handle one model object per image filename
- upload_to='path/to/file/',
+ upload_to='path/to/file/', # or use a function
render_variations=image_processor # pass boolean or callable
)
processed = models.BooleanField(default=False) # flag that could be used for view querysets
```
### Re-rendering variations
-You might want to add new variations to a field. That means you need to render new variations for missing fields.
+You might have added or changed variations to an existing field. That means you will need to render new variations.
This can be accomplished using a management command.
```bash
python manage.py rendervariations 'app_name.model_name.field_name' [--replace] [-i/--ignore-missing]
```
The `replace` option will replace all existing files.
-The `ignore-missing` option will suspend missing source file errors and keep
-rendering variations for other files. Othervise command will stop on first
-missing file.
+The `ignore-missing` option will suspend 'missing source file' errors and keep
+rendering variations for other files. Otherwise, the command will stop on first missing file.
diff --git a/setup.cfg b/setup.cfg
index 10e8e2b..5262f42 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,7 +9,7 @@ url = https://github.com/codingjoe/django-stdimage
license = MIT
license_file = LICENSE
classifier =
- Development Status :: 5 - Production/Stable
+ Development Status :: 7 - Inactive
Environment :: Web Environment
Framework :: Django
Topic :: Multimedia :: Graphics :: Graphics Conversion
@@ -38,11 +38,6 @@ install_requires =
setup_requires =
setuptools_scm
- pytest-runner
-tests_require =
- pytest
- pytest-cov
- pytest-django
[options.package_data]
* = *.txt, *.rst, *.html, *.po
@@ -52,20 +47,23 @@ exclude =
tests
[options.extras_require]
+test =
+ pytest
+ pytest-cov
+ pytest-django
progressbar = progressbar2>=3.0.0
[bdist_wheel]
universal = 1
-[aliases]
-test = pytest
-
[tool:pytest]
+testpaths =
+ tests
norecursedirs=venv env .eggs
DJANGO_SETTINGS_MODULE=tests.settings
addopts = --cov=stdimage --nomigrations --tb=short
filterwarnings =
- error
+ ignore::DeprecationWarning
[coverage:run]
source = .
diff --git a/stdimage/models.py b/stdimage/models.py
index 97ff514..bed00d7 100644
--- a/stdimage/models.py
+++ b/stdimage/models.py
@@ -1,5 +1,6 @@
import logging
import os
+import warnings
from io import BytesIO
from django.core.files.base import ContentFile
@@ -11,12 +12,21 @@
ImageFileDescriptor,
)
from PIL import Image, ImageFile, ImageOps
+from PIL.Image import Resampling
from .validators import MinSizeValidator
logger = logging.getLogger()
+warnings.warn(
+ "The django-stdimage is deprecated in favor of django-pictures.\n"
+ "Migration instructions are available in the README:\n"
+ "https://github.com/codingjoe/django-stdimage#migration-instructions",
+ DeprecationWarning,
+)
+
+
class StdImageFileDescriptor(ImageFileDescriptor):
"""The variation property of the field is accessible in instance cases."""
@@ -186,7 +196,7 @@ class StdImageField(ImageField):
"width": None,
"height": None,
"crop": False,
- "resample": Image.ANTIALIAS,
+ "resample": Resampling.LANCZOS,
}
def __init__(
@@ -306,6 +316,19 @@ def save_form_data(self, instance, data):
file.delete(save=False)
super().save_form_data(instance, data)
+ def deconstruct(self):
+ name, path, args, kwargs = super().deconstruct()
+ return (
+ name,
+ path,
+ args,
+ {
+ **kwargs,
+ "variations": self._variations,
+ "force_min_size": self.force_min_size,
+ },
+ )
+
class JPEGFieldFile(StdImageFieldFile):
@classmethod
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 2ab2c68..7180204 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,7 +1,7 @@
import os
import pytest
-from PIL import Image
+from PIL.Image import Resampling
from stdimage.utils import render_variations
from tests.models import ManualVariationsModel
@@ -24,7 +24,7 @@ def test_render_variations(self, image_upload_file):
"width": 150,
"height": 150,
"crop": True,
- "resample": Image.ANTIALIAS,
+ "resample": Resampling.LANCZOS,
}
},
)
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: