{"id":15295826,"url":"https://github.com/rcrowther/django-img","last_synced_at":"2026-02-08T19:06:30.108Z","repository":{"id":57420450,"uuid":"259554117","full_name":"rcrowther/django-img","owner":"rcrowther","description":"Image handling for Django. Includes upload code, repository and filter creation. Low on features, fast setup, modular.","archived":false,"fork":false,"pushed_at":"2021-09-04T07:46:57.000Z","size":1009,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-22T19:18:59.385Z","etag":null,"topics":["django-application","image-filters","image-processing","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rcrowther.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-28T06:42:56.000Z","updated_at":"2021-10-01T03:23:51.000Z","dependencies_parsed_at":"2022-09-16T09:11:35.514Z","dependency_job_id":null,"html_url":"https://github.com/rcrowther/django-img","commit_stats":null,"previous_names":["rcrowther/django-image"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rcrowther/django-img","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-img","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-img/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-img/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-img/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcrowther","download_url":"https://codeload.github.com/rcrowther/django-img/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcrowther%2Fdjango-img/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265500539,"owners_count":23777506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django-application","image-filters","image-processing","python3"],"created_at":"2024-09-30T18:08:19.517Z","updated_at":"2026-02-08T19:06:26.558Z","avatar_url":"https://github.com/rcrowther.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Django-img\n\u003e :warning: **Module may in future be deprecated** I'm developing [a similar app with much awkward code removed]( https://github.com/rcrowther/django-image-lite)\n\nAn app to handle upload and display of images.\n\nThe app needs at least one repository declaring then a migration. After declaring an admin, upload is possible. To show images, only a few filter classes are needed (some are builtin), and to place the main template tag. That's all.\n\nThe base code is concise and levers Django recommendations and facilities where possible. It may provide a base for others wishing to build their own app.\n\nThe distribution is called 'django-img', but internally the module is called 'image'.\n\nThis is [a rewrite of Wagtail's Image app](#credits).\n\n## Why you may or may not want this app\nThis may not be the app for you,\n\nPro\n- Abstract base allows any number of repositories with custom configurations\n- Filter declarations can travel with apps (like CSS)\n- Auto-generates filtered images\n- One (primary) template tag\n\nCon\n- Only images, not any file\n- Filters can not be applied to individual images\n- No effort to present filters to users, either admin or visitors\n- No builtin categorisation and/or tagging\n\nYou can, by targeting in templates, apply a special filter to one image, but the system is geared towards handling images in classes.\n \nThe idea is to step back from flexible chain presentation and APIs. All most sites need is to upload images, crop to size, then run some general image enhancement. By dropping APIs and configuration the app is small, concise, and fits good CSS/template-practice. \n\nAlso, I have not,\n- considered SVG images or movie files\n- tested cloud storage\n\n\n## Overview\n![overview diagram](screenshots/overview.svg?raw=true\u0026sanitize=true)\n\nImages are tracked in the database. The base model is called 'AbstractImage'. \n\nEach original image can generate derivative images. These are tracked by models based in 'AbstractReform'. Reforms are generated by filters. Filters can be defined in apps or centrally. \n\nImage delivery is by template tag. The presence of a tag with a reference to an image and a filter will generate a reform automatically. The tags deliver images by URL.\n\nFile-based storage is in 'media/' with paths adjustable through attribute settings.\n\nThe app includes code to upload images. Some custom Django admin is provided, which is optional and easy to modify/replace.\n\n\n\n## If you have done this before\n- [subclass the models for Image and Reform](#custom-image-repositories) to create a repository.\n- Migrate custom repository tables\n- [Add fields](#model-fields) to models that need them\n- Migrate models carrying image fields\n- Create 'image_filters.py' files in the apps, then [subclass a few filters](#filters)\n- Insert [template tags](#template-tags) into relevant templates\n\n\n\n## Quickstart\n### Depemdancies\nUnidecode,\n\n    pip install unidecode\n\n[Unidecode](https://pypi.org/project/Unidecode/)\n\nPillow,\n\n    pip install pillow\n\n[Pillow](https://pillow.readthedocs.io/en/stable/index.html)\n\n\n#### Optional\nTo use Wand filters, on Debian-based distros,\n\n    sudo apt-get install libmagickwand-dev\n\nThen,\n\n    pip install wand\n\n\n\n\n\n### Install\nPyPi,\n\n    pip install django-img\n\nOr download the app code to Django.\n\nDeclare in Django settings,\n\n        INSTALLED_APPS = [\n            ...\n            'image.apps.ImageConfig',\n        ]\n\nMigrate,\n\n    ./manage.py makemigrations image\n    ./manage.py migrate image\n\nNow you need to [declare a repository](#custom-image-repositories). Further examples assume the example repository created there.\n\n\n\n### Upload some images\nIn Django admin, go to Image upload and upload a few images.\n\nI don't know about you, but if I have a new app I like to try with real data. If you have a collection of test images somewhere, try this management command,\n\n    ./manage.py image_create_bulk news_article_images.NewsArticleImage pathToMyDirectory\n\nNote you need to give a path to a Model. You can create, meaning upload and register, fifteen or twenty images in a few seconds.\n\n\n\n### View some images\nOk, let's see an image. Two ways,\n\n#### Use a view \nFind a web view template. Nearly any template will do (maybe not a JSON REST interface, something visible).\n\nAdd this tag to the template,\n\n    {% load img_tags %}\n    ...\n    {% imagequery image.Image \"pk=1\" image.Thumb %}\n\n'image.Thumb' is a predefined filter. It makes a 64x64 pixel thumbnail. The tag we use here searches for an image by a very low method, \"pk=1\". This will do for now. \n\nVisit the page. The app will generate the filtered 'reform' image automatically.\n\n\n#### Don't have a view?\nYeh, new or experimental site, I know. Image has a builtin template. [Make a view](#the-view). Now visit (probably) http://localhost:8000/image/1/ To see some *real* web code.\n\n\n\n### (optional) See a broken image\nUse the management command to remove reforms,\n\n    ./manage.py reform_delete\n\nNo better way to make a truly broken file... Goto '/media/originals' and delete a file (maybe the file you are currently viewing as a reform). \n\nNow refresh the view. The app will try to find the reform. When it fails, it will attempt to make a new reform. But the original file is missing, so it will fail to do that too. It will then display a generic 'broken' image.\n \n### (aside) Filters\nPerhaps your first request will be how to make a new filter.\n\nMake a new file called 'image_filters'. Put it in the top level of any app (not in the site directory, that can be done but must be configured in a [settings.py](#settings). Put something in like this (adapt if you wish e.g. ResizeSmart, Crop...),\n\n    from image import CropSmart, registry\n\n    class MediumImage(CropSmart):\n        width=260\n        height=350\n        format='png'\n\n    registry.register(MediumImage)\n \nNow adapt the template tag (or the tag in 'image/templates/image/image_detail.html') to point at the new filter,\n\n    {% imagequery image.Image \"pk=1\" pathToImageFilterFile.MediumImage %}\n\nVisit the page again. Image uses the new filter definition to generate a new reform (filters the image) then displays it.\n\nOk, you changed the image size, and maybe the format. If you want to continue, you probably have questions. Goto the main documentation.\n\n\n\n## QuickStop\nDon't like what you see?\n\n- Remove any temporary code.\n- Migrate backwards ('./manage.py migrate image zero')\n- Remove from 'apps.py'\n- Remove the two directories '/media/originals/', '/media/reforms/'\n- Remove the app folder, or uninstall\n\nThat's it, gone.\n\n\n\n## Full documentation\nIndex, \n\n- [Model Fields](#model-fields)\n- [New Image Repositories](#custom-image-repositories)\n- [Auto Delete](#auto-delete)\n- [Filters](#filters)\n- [Admin](#admin)\n- [Forms](#forms)\n- [Template Tags](#template-tags)\n- [Admin-generated Reforms](#admin-generated-reforms)\n- [Management Commands](#management-commands)\n- [Settings](#settings)\n- [Utils](#utilities)\n- [Tests](#tests)\n\n\n## Model Fields\nTwo ways,\n\n#### Custom ImageRelationFieldMixin fields\nThere are two, ImageOneToOneField and ImageManyToOneField. An image class name is passed as a paremeter.\n\nImageOneToOneField (for images with only one placement),\n\n    from image.model_fields import ImageOneToOneField\n\n\n    class Page(models.Model):\n        ...\n        img = ImageOneToOneField(\n            'page.Image'\n            )\n\nand ImageManyToOneField (for image pools),\n\n    from image.model_fields import ImageManyToOneField\n\n\n    class Page(models.Model):\n        ...\n        img = ImageManyToOneField(\n            'page.Image'\n            )\n\n#### Inherited models\nhttps://docs.djangoproject.com/en/3.2/topics/db/models/#be-careful-with-related-name-and-related-query-name\n\n##### Auto-delete images\nAn ImageOneToOneField field can auto-delete associated Image models and files (and their reforms) with the model. See [Auto Delete](#auto-delete) \n\n\n#### Stock Django\nYou can also use a Django foreign key declaration,\n\n    from image.models import Image\n\n\n    class Page(models.Model):\n\n        img = models.ForeignKey(\n            'page.Image',\n            null=True,\n            blank=True,\n            on_delete=models.SET_NULL,\n            related_name='+'\n            )\n\n         etc.\n\nnull=True and blank=True means users can delay adding an image until later. And related_name='*' means that Images will not track the models you are creating. See Django documentation of model fields for more details.\n\nOnly use models.CASCADE if you are sure this is what you want. It means, if an image is deleted, the model that carries the image is deleted too. This is probably the wrong order of dependancy, and not usually what you want!\n\n\n#### Choosing between the two\nImageOneToOneField/ImageManyToOneField\n- tidy\n- Works with preconfigured admin and auto-delete\n\nForeign Field\n- Django stock\n- explicit\n- flexible configuration\n\n\n## Image Repositories\n### Overview\nNew repositories can be made by subclassing the the core models.\n\nReasons you may want to customise repositories,\n\n#### Repository behaviour\nCustom repositories have new DB tables, and can operate with new configurations such as storing files in different directories, auto-deleting original files, not limiting filename sizes and more.\n\n#### Associate data with images\nYou may want to associate data with an image. Many people's first thought would be to add a title, as the app does not provide titles by default. But other kinds of information can be attached to an image such as captions, credits, dates, and/or data for semantic/SEO rendering.\n\n#### Split needs\nFor example, you may want a repository attached to a main Article model, and also an image pool for general site use such as banners or icons. \n \n\n### Subclassing Image/Reform \nCustom Image repository code is placed in 'models.py' files and migrated. You decide how you want your namespacing to work. The code can be placed in an app handling one kind of model or, for general use, in a separate app. For a seperate app,\n\n    ./manage.py startapp news_article_images\n\nDeclare the app in settings.py,\n\n        INSTALLED_APPS = [\n            ...\n            'image.apps.ImageConfig',\n            'news_article_images.apps.NewsArticleImagesConfig',\n        ]\n\nHere is a minimal subclass. In a 'models.py' file, do this,\n\n    from image.models import AbstractImage, AbstractReform\n\n    class NewsArticleImage(AbstractImage):\n        reform_model = 'NewsArticleReform'\n        upload_dir='news_originals'\n\n        # AbstractImage has a file and upload_date\n        caption = models.CharField(_('Caption'),\n            max_length=255,\n        )\n\n        author = models.CharField(_('Author'),\n            max_length=255,\n            db_index=True\n        )\n\n        etc.\n\n\n\n    class NewsArticleReform(AbstractReform):\n        image_model = NewsArticleImage\n        upload_dir='news_reforms'\n\n        # exactly the same in every subclass\n        image = models.ForeignKey(image_model, related_name='+', on_delete=models.CASCADE)\n\n\nNot the last word in DRY coding, but you should be able to work out what the code is for. Note that 'image_model' and 'reform_model' are explicitly declared. Note also that 'reform_model' is declared as a string, but 'image_model' is declared as a class.\n\nMigrate,\n\n    ./manage.py makemigrations news_article_images\n    ./manage.py migrate news_article_images\n\nYou now have a new image upload app. It has it's own DB tables. Change it's configuration (see next section). Refer to it in other models,\n\n    class NewsArticle(models.Model):\n\n        img = ImageManyToOneField(\n            \"news_article.NewssArticleImage\"\n            )\n\n        etc.\n\n### Attributes\nSubclasses accept some attributes. Note that some of these settings are radical alterations to a model class. To be sure a model setting will take effect, it is best to migrate the class.\n\nAn expanded version of the above,\n\n    from image.models import AbstractImage, AbstractReform\n\n    class NewsArticleImage(AbstractImage):\n        reform_model = 'NewsArticleReform'\n        upload_dir='news_originals'\n        accept_formats = ['png']\n        filepath_length=55\n        max_upload_size=2\n        form_limit_filepath_length=True\n        auto_delete_files=True\n\n        ...\n\n\n\n    class NewsArticleReform(AbstractReform):\n        image_model = NewsArticleImage\n\n        # relative to MEDIA_ROOT\n        upload_dir='news_reforms'\n\n        # Add the appname to the filename. This is good id, but\n        # in practice makes long filenames e.g. \n        # site_images_thumbnail-washing_machine.png\n        app_namespace = False\n\n        # lowercase the filename. Default is True.\n        lowercase = True\n        file_format='png'\n        jpeg_quality=28\n\n        # exactly the same in every subclass\n        image = models.ForeignKey(image_model, related_name='+', on_delete=models.CASCADE)\n\nSome of these attributes introduce checks ('max_upload_size'), some set defaults('file_format'), some can be overridden ('file_format', 'jpeg_quality' can be overridden by filter settings) (the configuration above is odd, and for illustration e.g. if reforms are set to 'file_format'='png', 'jpeg_quality' will never be used). See [Settings](#settings) for more details.\n\nMigrate, and you are up and running.\n\n\n### Inheritance! Can I build repositories using OOP techniques?\nNo! Python has been cautious about this kind of programming, and Django's solutions are a workround. Try stacking models of any kind and, unless you know the code line by line, the classes will create unusable migrations. In the current situation, for stability and maintainability, create models directly from the two abstract bases.\n\n### Can I create different repositories, then point them at the same storage paths?\nThe app tracks through the database tables, and the [management commands](#management-commands) work from them, so yes, you can. That said, when code offers opportunities for namespacing/encapsulation, you need a good reason to ignore it.\n\n### Things to consider when subclassing models\n\n#### Auto delete of files\nMay be good to set up your deletion policy from the start. See [Auto Delete](#auto-delete)\n \n#### Add Meta information\nYou may want to configure a Meta class. If you added titles or slugs, for example, you may be interested in making them into unique constrained groups, or adding indexes,\n\n    class NewssArticleImage(AbstractImage):\n        upload_dir='news_originals'\n        filepath_length=100\n\n        etc.\n\n        class Meta:\n            verbose_name = _('news_image')\n            verbose_name_plural = _('news_images')\n            indexes = [\n                models.Index(fields=['author']),\n            ]\n            constraints = [\n                models.UniqueConstraint(\n                    fields=['title', 'author'], \n                    name='unique_newsarticle_reform_src'\n                )\n            indexes = [\n                models.Index(fields=['upload_time']),\n            ]\n\nNote that the base Image does not apply an index to upload_time, so if you want that, you must specify it.\n\n## Auto-delete\n### Overview\nI read somewhere that a long time ago, Django would auto-delete files. This probably happened in model fields. This behaviour is not true now. If objects and fields are deleted, files are left in the host system. However, it suits this application, and some of it's intended uses, to auto-delete mod els and files. If you would like this behaviour, the app provides some solutions.\n\nThere are aspects to this. First, the files for Reforms are always deleted with the reform. And reforms are always deleted when the image model is deleted. However, the choices arrive with the deletion of the Image. Should the file be deleted with the Image?  Finally, should an Image be deleted when a model using that image is deleted?\n\n\n#### Auto-delete of Reforms\nReform files are deleted with the reform. There is nothing to do.\n\nReforms are treated as objects generated automatically, so automatic deletion is not controversial. Reform models are deleted by the CASCADE in the foreign key, and so are the files.\n\n\n#### Auto-delete Image files\nImage file deletion is optional. To auto-delete, set the Image model attribute 'auto_delete_files=True'.\n\n\n### Automatic deletion of image models when a supporting object is deleted\nPlace this in the ready() method of the application,\n\n    from image.signals import register_image_delete_handler\n\n\n    class NewsConfig(AppConfig):\n        ...\n\n        def ready(self):\n            super().ready()\n            from news_article.models import NewsArticle\n            register_image_delete_handler(NewsArticle)\n\nThe image fields must be ImageOneToOneFields, and the field attribute must be 'auto_delete=True'.\n\n#### Why must the code run on the custom field, and why not on a ImageOneToManyField?\nThe custom field means lightweight field identification.\n \nAn ImageOneToManyField implies an image pool. Many connections to one image. This is a  classic computing problem of reference counting. Best avoided.\n\n\n\n\n\n\n\n### Behaviour of the default repository\nBy default, the default repository will not auto-delete the original files associated with Images. It will auto-delete reform models and their files.\n\n\n\n## Filters\n### Overview\nFilters are used to describe how an uploaded image should be modified for display. In the background, the app will automatically adjust the image to the filter specification (or use a cached version).\n \nA few filters are predefined. One utility/test filter,\n\n\u003cdl\u003e\n\u003cdt\u003eThumb\u003c/dt\u003e\n    \u003cdd\u003eA 64x64 pixel square\u003c/dd\u003e\n\u003c/dl\u003e\n\nAnd some base filters, which you can configure. These are centre-anchored, \n\n- Crop\n- Resize\n- SmartCrop\n- SmartResize\n\nIf you only need different image sizes, you only need to configure these. But if you want to pass some time with image-processing code, you can add filters to generate ''PuddingColour' and other effects.\n\n\n### Filter placement and registration\nFiles of filter definitions can be placed in any app. Create a file called 'image_filters.py' and off you go.\n\nIf you would prefer to gather all filters in one place, define the settings to include,\n\n    IMAGES = [\n        {\n            'SEARCH_MODULES': [\n                        \"siteName\",\n            ],\n        },\n    ]\n\nThen put a file 'image_filters.py' in the 'sitename' directory. If you use a central file, you should namespace the filters,\n\n    BlogPostLarge:\n        width : 256\n        height 256\n\n\n### Filter declarations\nAll builtin filter bases accept these attributes,\n\n- width\n- height\n- format\n\nMost filter code demands 'width' and 'height', but 'format' is optional. Without a stated format, the image format stays as it was (unless another setting is in place). Formats accepted are conservative,\n\n    bmp, gif, ico, jpg, png, rgb, tiff, webp \n\nwhich should be written as above (lowercase, and 'jpg', not 'jpeg'). So,\n\n    from image import Resize, registry\n\n    class MediumImage(Resize)\n        width=260\n        height=350\n        format='png'\n        #fill_color=\"Coral\"\n        #jpeg_quality=28\n        # optional effects\n\n    registry.register(MediumImage)\n\n\nCrop and Resize can/often result in images narrower in one dimension. \n\nThe Smart filters do a background fill in a chosen colour. By usung a fill, they can maintain aspect rattio, but return the requested size,\n\n    from image import ResizeSmart, registry\n\n    class MediumImage(ResizeSmart):\n        width=260\n        height=350\n        format='jpg'\n        fill_color=\"Coral\"\n\n    registry.register(MediumImage)\n\nFill color is defined however the image library handles it. Both Pillow and Wand can handle CSS style hex e.g. '#00FF00' (green), and HTML colour-names e.g. 'AliceWhite'.\n\n\n### Registering filters\nFilters need to be registered. Registration style is like ModelAdmin, templates etc. Registration is to 'image.registry' (this is how templatetags find them).\n\nYou can use an explicit declaration,\n\n    from image import ResizeSmart, registry\n\n    ...\n\n    registry.register(single_or_list_of_filters)\n\nOr use the decorator,\n\n    from image import register, ResizeSmart\n\n    @register()\n    class MediumImage(ResizeSmart):\n        width=260\n        height=350\n        format='jpg'\n        fill_color=\"Coral\"\n\n\n### Wand filters\nThe base filters in the Wand filter set have more attributes available. The 'wand' code needs Wand to be installed on the host computer. Assuming that, you gain these effects,\n\n    from image import filters_wand, register\n\n    @register()\n    class Medium(filters_wand.ResizeSmart):\n        width=260\n        height=350\n        format='jpg'\n        pop=False\n        greyscale=False\n        night=False\n        warm=False\n        strong=False\n        no=False\n        watermark='image/watermark.png'\n\n\n\nIf you enable more than one effect, they will chain, though you have no control over order.\n\nI lost my way with the Wand effects. There is no 'blur', no 'rotate', no 'waves'. But there is,\n\n\u003cdl\u003e\n    \u003cdt\u003epop\u003c/dt\u003e\n    \u003cdd\u003e\n        Tightens leveling of black and white\n    \u003c/dd\u003e\n    \u003cdt\u003egreyscale\u003c/dt\u003e\n    \u003cdd\u003e\n        A fast imitation\n    \u003c/dd\u003e\n    \u003cdt\u003enight\u003c/dt\u003e\n    \u003cdd\u003e\n        Pretend the picture is from a movie\n    \u003c/dd\u003e\n    \u003cdt\u003ewarm\u003c/dt\u003e\n    \u003cdd\u003e\n        A small shift in hue to compensate for a common photography white-balance issue. \n    \u003c/dd\u003e\n    \u003cdt\u003estrong\u003c/dt\u003e\n    \u003cdd\u003e\n        Oversaturate image colors (like everyone does on the web). Unlike 'pop' this will not stress contrast so flatten blacks and whites. You may or may not prefer this. \n    \u003c/dd\u003e\n    \u003cdt\u003eno\u003c/dt\u003e\n    \u003cdd\u003e\n        Draw a red cross over the image\n    \u003c/dd\u003e\n    \u003cdt\u003ewatermark\u003c/dt\u003e\n    \u003cdd\u003eAccepts a URL to a watermark image template.\n    \u003c/dd\u003e\n\u003c/dl\u003e\n\nWatermark deserves some explanation. This does not draw on the image, as text metrics are tricky to handle. You configure a URL stub to an image, here's a builtin,\n\n    watermark = 'image/watermark.png'\n\nThe URL is Django static-aware, but will pass untouched if you give it a web-scheme URL (like the URLs in Django Media).\n \nThe template is scaled to the image-to-be-watermarked, then composited over the main image by 'dissolve'. So the watermark is customisable, can be used on most sizes of image, and is usually readable since aspect ratio is preserved.\n\nIt is probably worth saying again that you can not change the parameters, so the strengths of these effects, without creating a new filter.\n\n\n### Writing custom filter code\nFirst bear in mind that Image uses fixed parameters. So your filter must work with fixed parameters across a broad range of uploaded images. I don't want anyone to dive into code, put in hours of work, then ask me how they can create an online image-editing app. Not going to happen.\n\nHowever, while I can't make a case for 'waves' or 'pudding-colour' filters, I can see uses. For example, Wagtail CMS uses the OpenCV library to generate images that auto-focus on facial imagery (i.e. not centrally crop). There are uses for that.\n\nSecond, bear in mind that image editing is lunging into another world, rather like creating Django Forms without using models and classes. It will take time. But there is help available. Inherit from 'image.Filter'. You will need to provide a 'process' method, which takes an open Python File and returns a ByteBufferIO and a file extension.\n\nIf you want the filter to work with the Pillow or Wand libraries, you can inherit from the PillowMixin or WandMixin. These cover filehandling for those libraries. Then you can provide a 'modify' method, which alters then returns an image in the format of those libraries.\n\nSee the code for details.\n\n\n### Why can filters not be chained or given parameters?\nThis app only enables creation of fixed filters intended for a broad range of images. You write a filter with parameters, the processing order is fixed, and it is set.\n\nThis is a deliberate decision. It makes life easy. If you want to produce a front-end that can adjust the filters, or chain them, that is another step. This is not that app.\n\n\n## Admin\n### Overview\nImage ships with stock Django admin. However, this is not always suited to the app, it's intended or possible uses. So there are some additions.\n\nThe admin provided has an attitude about how to use the app. It assumes that each model instance is locked to one file. If a model exists, then the file exists. If the admin is given the same file, it duplicates the file and model.\n\nIn this system, models that refer to Image models can be null and blank, which represents 'image not yet uploaded'. And it is possible to build systems that reuse images. It is the Image_instance-\u003efile connection that is locked.\n\n\n### Package solutions\n#### ImageCoreAdmin\nFor administration and maintenance of image collections. This is a specialised use, which would only be visible to end users if trusted.\n\nSignificant changes from stock admin,\n\n- changelist is tidier and includes 'view' and 'delete' links\n- changelist has searchable filenames\n- change form has 'readonly' file data\n\n\n##### Remove ImageCoreAdmin from central repository\nWant Django stock admin? Change the comments in 'image/admin.py' from,\n\n    # Custom admin interface disallows deletion of files from models.\n    class ImageAdmin(ImageCoreAdmin):\n        \n    # Stock admin interface.\n    #class ImageAdmin(admin.ModelAdmin):\n        pass\n            \n            \n    admin.site.register(Image, ImageAdmin)\n\nto,\n\n    # Custom admin interface disalows deletion of files from models.\n    #class ImageAdmin(ImageCoreAdmin):\n        \n    # Stock admin interface.\n    class ImageAdmin(admin.ModelAdmin):\n        pass\n        \n        \n    admin.site.register(Image, ImageAdmin)\n\n\n\n##### Notes and alternatives for the core admin\nYou may provide no core admin at all. You can use the './manage.py' commands to do maintenance. The stock admin is provided to get you started.\n\nIf you prefer your own core admin, have a look at the code for ImageCoreAdmin in '/image/admins.py'. It provides some clues about how to do formfield overrides and other customisation.\n\nYou may find it more maintainable to duplicate and modify the admin code, rather than import and override.\n\n\n#### LinkedImageAdmin\nFor administration of models that contain foreign key links to images.\n\nThis is a small override that should not interfere with other admin code. It disallows image editing once an image has been connected to a field (by upload or selection). e.g.\n\n    from image.addmins import LinkedImageAdmin\n\n        class NewsArticleAdmin(LinkedImageAdmin, admin.ModelAdmin):\n            pass\n            \n            \n        admin.site.register(NewsArticle, NewsArticleAdmin)\n\n\n\n## Forms\n### Overview\nThere's nothing special about using this app in forms. Even the custom fields are mostly renames, and create namespaces for custom admin configurations.\n\nThat said, the fields used internally are not standard. \n\n### ImageFileField\nIf you are interested in the internals, this does a few extra jobs beyond an ImageFile,\n\n\u003cdl\u003e\n    \u003cdt\u003eContains extra config attributes,\u003c/dt\u003e\n    \u003cdd\u003eMost of which are gathered from the class configuration (e.g. max_upload size). The class can deconstruct these for migrations.\u003c/dd\u003e\n    \u003cdt\u003eextra validators\u003cddt\u003e\n    \u003cdd\u003eBeyond the standard Django field, this field and it's formfield actively check filesizes and extensions. Calls to is_valid() will run these validations.\u003c/dd\u003e\n\u003c/dl\u003e\n\n### For references to images\nWhen images are referenced from another model, this would usually [use a Foreign Key on the referring model](#model-fields). \n\n\n## Template Tags\n### Overview\nFor most uses, the app has only one template tag. There is another, for testing and edge cases.\n\n### The 'image' tag\nLet's say there is a filter named ''Large'. Add this to template code,\n\n    {% load img_tags %}\n\n    {% image report.img my_app.Large %}\n \nthen visit the page. The app will generate a ''Large' reform of 'report.image', to the spec given in the filter.\n\nThe tag can guess the app from the context. So,\n\n     {% image report.img Large %}\n\nWill assume the filter is in the app the context says the view comes from.\n\nThe tag accepts keyword parameters which become HTML attributes,\n\n     {% image report.img Large class=\"report-image\" %}\n\nThis renders similar to,\n\n    \u003cimg src=\"/media/reforms/eoy-report_large.png\" alt=\"eoy-report image\" class=\"report-image\"\u003e\n\n\n### The 'query' tag\nThere is a tag to find images by a database query. Sometimes this will be useful, for fixed or temporary decoration/banners etc. It must be given the app/model path in full,\n\n        {{ imagequery some_app_name.some_image_model_name \"some_query\" image.Large  }}\n\ne.g.\n\n        {{ imagequery image.Image \"pk=1\" image.Large  }}\n\nor,\n\n        {{ imagequery \"src=\"taunton-skyscraper\"\" image.Large  }}\n\nWhile this may be useful, especially for fixed logos or banners, if you are passing a model through a context it is unnecessary. \n\n\n### Filters from other apps\nYou can borrow filter collections from other apps. Use the module path and filter classname,\n\n    {% image \"urban_decay\" different_app.filter_name  %} \n\nBut try not to create a tangle between your apps. You would not do that with CSS or other similar resources. Store general filters in a central location, and namespace them.\n\n\n## Admin-generated Reforms\nGenrerate reforms directly using an admin-like form.\n\nThe template tags work well for highly-structured content. For example, a product review will nearly always start with an image of the product in question. It can probaly be configured with template tags for a streamfield (untried). But I felt the app should also be usable for less structured content, i.e. markup.\n\nRather than build some image selector, which has never finally worked for me, I have added a form that can generate reforms. It's between you and your deplyment where you place the reforms, and how you reference them in markup. But the form will generate reforms.\n\nTo implement, create a view, then specialise the builtin view to your image model,\n\n\n    from image.views import ImageReformsView\n    from .models import NewsArticleImage\n\n\n    class NewsArticleReformsView(ImageReformsView):\n        model = NewsArticleImage\n\nNow you need to access the view. A URL is builtin to the model, so you can use this unusual declaration in ''urls.py',\n\n    urlpatterns = [\n        ...\n        NewsArticleReformsView.url(), \n        ...\n    ]\n\nAnd now admin needs to access the URL. Modify your Image admin like this, adding an extra column of links. This example assumes use of ImageCoreAdmin, but it is not necessary. Again, the URL is builtin,\n\n    from django.utils.html import format_html\n\n    class NewsArticleImageAdmin(ImageCoreAdmin):\n        list_display = ('filename', 'upload_day', 'image_delete', 'image_view', 'add_reform',)\n\n        def add_reform(self, obj):\n            opts = obj._meta\n            return format_html('\u003ca href=\"{}\" class=\"button\"\u003eAdd\u003c/a\u003e',\n                obj.url_form_reform_add()\n            )\n        add_reform.short_description = 'Add Reform'\n\nNow, if you follow the 'AddReform' button displayed next to every image in the image model list, you should see a form like the following, and can generate reforms based on whatever filters are available,\n\n![Generate Reform Form](screenshots/generate_reforms.png?raw=true)\n\n\n\n## Management Commands\nThey are,\n\n- image_create_bulk\n- image_sync\n- image_list\n- reform_delete\n\nAll management commands can be pointed at subclasses of Image and Reform, as well as the default app models.\n\nThey do what they say. 'image_sync' is particularly useful, it will attempt to make models for orphaned files, or delete orphaned files, or delete models with missing files. These commands are useful for dev, too.\n\n\n\n\n\n## Settings\n### Overview\nImage accepts settings in several places. The app has moved away from site-wide settings towards other placements. Here is a summary, in order of last placement wins,\n\n### Image\n\u003cdl\u003e\n    \u003cdt\u003ereform_model\u003c/dt\u003e\n    \u003cdd\u003e\n        default=AbstractReform\n    \u003c/dd\u003e\n    \u003cdt\u003eupload_dir\u003c/dt\u003e\n    \u003cdd\u003e\n        default='originals'\n    \u003c/dd\u003e\n    \u003cdt\u003efilepath_length\u003c/dt\u003e\n    \u003cdd\u003e\n        default=100\n    \u003c/dd\u003e\n    \u003cdt\u003eform_limit_filepath_length\u003c/dt\u003e\n    \u003cdd\u003e\n        default=True\n    \u003c/dd\u003e\n    \u003cdt\u003eaccept_formats\u003c/dt\u003e\n    \u003cdd\u003e\n        default=None\n    \u003c/dd\u003e\n    \u003cdt\u003emax_upload_size\u003c/dt\u003e\n    \u003cdd\u003e\n        default=2MB\n    \u003c/dd\u003e\n    \u003cdt\u003eauto_delete_files\u003c/dt\u003e\n    \u003cdd\u003e\n        (if the Image is deleted, the file is deleted too) default=False  \n    \u003c/dd\u003e\n\u003c/dl\u003e\n\n### Reform\n\u003cdl\u003e\n    \u003cdt\u003eupload_dir\u003c/dt\u003e\n    \u003cdd\u003e\n        default='reforms'\n    \u003c/dd\u003e\n    \u003cdt\u003eimage_model\u003c/dt\u003e\n    \u003cdd\u003e\n        default='image.Image'\n    \u003c/dd\u003e\n    \u003cdt\u003efile_format\u003c/dt\u003e\n    \u003cdd\u003e\n        (set the default format of reforms) default=original format, Reform attribute, filter attribute\n    \u003c/dd\u003e\n    \u003cdt\u003ejpeg_quality\u003c/dt\u003e\n    \u003cdd\u003e\n        (set the quality of JPEG reforms) default=80, Reform attribute, filter attribute\n    \u003c/dd\u003e\n\u003c/dl\u003e\n\n### ImageOneToOneField\n\u003cdl\u003e\n    \u003cdt\u003eauto_delete\u003c/dt\u003e\n    \u003cdd\u003e\n        if True, and signals are enabled, deletion of the model will delete image models.\n    \u003c/dd\u003e\n\u003c/dl\u003e\n(the field naturally has many other settings, this setting is the only one related to this app)\n\n### Site-wide settings\nImages accepts some site-wide settings,\n\n    IMAGES = [\n        {\n            'BROKEN': 'myapp/lonely.png',\n            'SEARCH_APP_DIRS': True,\n            'SEARCH_MODULES': [\n                        \"someSiteName\",\n            ],\n        },\n    ]\n\n\u003cdl\u003e\n    \u003cdt\u003eBROKEN\u003c/dt\u003e\n    \u003cdd\u003e\n    URL of a static file displayed in place of unreadable files. Takes a static-aware URL to a file. URLs with a web-based scheme pass untouched, relative URLs are assumed in an app's static folder. Default is 'image/unfound.png'.\n    \u003c/dd\u003e\n    \u003cdt\u003eSEARCH_APP_DIRS\u003c/dt\u003e\n    \u003cdd\u003e\n    Find 'image_filters.py' files in apps. If False, the app only uses filters defined in the core app and SEARCH_MODULES setting.\n    \u003c/dd\u003e\n    \u003cdt\u003eSEARCH_MODULES\u003c/dt\u003e\n    \u003cdd\u003e\n    Defines extra places to find 'image_filter.py' files. The above example suggests a site-wide filter collection in the site directory (most page-based Django sites have central collections of templates and CSS in the same directory). The setting takes module paths, not filepaths, because 'image_filter.py' files are live code.\n    \u003c/dd\u003e\n\u003c/dl\u003e\n\n\n\n## Broken Images\nThe app throws a special error if images are broken i.e. files are missing or unreadable. In this case a stock 'broken' image is returned. Using the standard tags there is no need to configure or change in any way for this. The image can be [redefined](#site-wide-settings).\n\n\n    \n        \n## Utilities\n### The View\nImage has a builtin template for a view. It's main purpose is to test filter code. As a test and trial device, it is not enabled by default.\n\nMake a view,\n\n    from django.views.generic import DetailView\n    from NewsArticleImage.models import NewsArticleImage\n\n    class NewsArticleImageDetailView(DetailView):\n        template_name='image/image_detail.html'\n        model = NewsArticleImage\n        context_object_name = 'image'\n\nGoto urls.py, add this,\n\n    from test_image.views import NewsArticleImageDetailView\n\n    path('newsarticleimage/\u003cint:pk\u003e/', NewsArticleImageDetailView.as_view(), name='news-article-image-detail'),\n\nNow visit (probably),\n\n    http://localhost:8000/image/1/\n\nIn the template you can edit the tag to point at your own configurations. With visible results and basic image data, the view is often easier to use than the shell.\n\n\n## Tests\nI've not found a way to test this app without Python ducktape. There are tests. They are in a directory 'test_image'. This is a full app.\n\nTo run the tests you must install django-img, then move 'test_image' to the top level of a project. Install,\n\n        INSTALLED_APPS = [\n            ...\n            'test_image.apps.TestImageConfig',\n            'image.apps.ImageConfig',\n            ...\n        ]\n\n...and migrate. The tests are in the ''tests' sub-folder of 'test_image'.\n\n\n## Notes\nNo SVG support: would require shadow code, Pillow especially can't handle them.\n\nWidths, heights and bytesize of original images are recorded: in case the storage media is not local files but cloud provision.\n\nThe app generally uses URLs, not the filepaths: this can be confusing, but means the app is 'static' aware, and it should keep working if you swap storage backends.\n\n\n## Credits\nThis is a rewrite of the Image app from Wagtail CMS. Some core ideas are from Wagtail, such as the broken image handling and replicable repositories. Some patches of code are also similar, or only lightly modified, such as the upload and storage code. However, the configuration options and actions have been changed, and the filtering actions are entirely new.\n\n[Wagtail documentation](https://docs.wagtail.io/en/v2.8.1/advanced_topics/images/index.html)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcrowther%2Fdjango-img","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcrowther%2Fdjango-img","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcrowther%2Fdjango-img/lists"}