Compare commits

...

891 commits

Author SHA1 Message Date
Benedikt Willi
238573051e Fix build, remove unmaintained Django & Python versions.
Update .travis.yml

Update tox.ini

Update test_cachefiles.py

Update test_optimistic_strategy.py

Update test_no_extra_queries.py

Update test_cachefiles.py

Update .travis.yml

Update tox.ini

Update .travis.yml
2020-05-11 09:36:36 +02:00
Karthikeyan Singaravelan
417e33ff5a Fix deprecation warning regarding invalid escape sequences. 2020-05-11 09:35:48 +02:00
Tim Gates
9d450a78b8
docs: Fix simple typo, assigment -> assignment
There is a small typo in tests/test_generateimage_tag.py, tests/test_thumbnail_tag.py.

Should read `assignment` rather than `assigment`.
2020-03-08 13:04:21 +11:00
Venelin Stoykov
bc12a319b3
Merge pull request #496 from nthall/documentation-links
fix broken links in documentation (#319)
2020-02-21 23:34:17 +02:00
Noah Hall
85f0741594 fix broken links in documentation (#319) 2020-02-20 22:25:29 -05:00
Venelin Stoykov
3317273401
Merge pull request #477 from vstoykov/fix/django-master
Do not check for existence if name is None
2018-10-12 23:32:50 +03:00
Venelin Stoykov
94cc8ed9e4
Merge pull request #478 from vstoykov/fix/warnings
Pass features to BeautifulSoup constructor
2018-10-12 23:31:54 +03:00
Venelin Stoykov
60f35b0af5 Pass features to BeautifulSoup constructor
This will remove a warning durring tests
2018-10-12 23:08:20 +03:00
Venelin Stoykov
2c85d5aafe Do not check for existence if name is None
This will fix tests for Django master
2018-10-12 23:06:28 +03:00
Venelin Stoykov
f3c5f7cb16
Merge pull request #475 from matthewwithanm/modernize-testing-config
Python 3.6 and Django 2.1
2018-10-12 22:41:40 +03:00
Venelin Stoykov
66db460c24 Python 3.6 and Django 2.1
Stop testing some configurations of older Django versions.
2018-09-25 00:37:25 +03:00
Venelin Stoykov
6f7de35f79
Merge pull request #469 from matthewwithanm/fix-image-cachefile-serializtion
Fix pickle serialization for ImageCacheFile
2018-06-03 18:21:48 +03:00
Roman Gorbil
de991d4048 Fix pickle serialization for ImageCacheFile
When Celery CachedFileBackend used with filesystem storage (django.core.files.storage.FileSystemStorage), everything works fine.
But there are issues with storages.backends.s3boto3.S3Boto3Storage (and it's fix from #391), as well as with django_s3_storage.storage.S3Storage.

Exception was:

```
Traceback (most recent call last):
  ...

  File "/src/django-imagekit/imagekit/cachefiles/__init__.py", line 131, in __bool__
    existence_required.send(sender=self, file=self)
  ...
  File "/libs/utils.py", line 380, in on_existence_required
    file.generate()
  File "/src/django-imagekit/imagekit/cachefiles/__init__.py", line 94, in generate
    self.cachefile_backend.generate(self, force)
  File "/src/django-imagekit/imagekit/cachefiles/backends.py", line 136, in generate
    self.schedule_generation(file, force=force)
  File "/src/django-imagekit/imagekit/cachefiles/backends.py", line 165, in schedule_generation
    _celery_task.delay(self, file.generator, force=force)
  ...
  File "/lib/python3.6/site-packages/kombu/serialization.py", line 221, in dumps
    payload = encoder(data)
  File "/lib/python3.6/site-packages/kombu/serialization.py", line 350, in pickle_dumps
    return dumper(obj, protocol=pickle_protocol)
kombu.exceptions.EncodeError: can't pickle _thread._local objects
```
2018-06-03 18:06:57 +03:00
Leonardo
595f7b35ef Enhance condition in _get_size (#463)
This fix the issue #326.
2018-04-24 15:48:43 +03:00
Venelin Stoykov
fc221335b7
Merge pull request #448 from matthewwithanm/feature/django2.0
Test against Django 2.0
2017-12-06 19:54:26 +02:00
Venelin Stoykov
58e44975c7 Test against Django 2.0 2017-12-06 00:13:26 +02:00
Venelin Stoykov
115b596a8d Merge branch 'release/4.0.2' into develop
* release/4.0.2:
  Bump version to 4.0.2
2017-12-05 22:27:15 +02:00
Venelin Stoykov
ea66e3d10d Bump version to 4.0.2 2017-11-20 10:24:12 +02:00
Venelin Stoykov
6319891697
Merge pull request #440 from matthewwithanm/fix/open-files-leak
Fixed #429 Do not leak open files after generation
2017-11-20 10:02:01 +02:00
Venelin Stoykov
6ee931398f Do not leak open files after generation 2017-11-17 18:37:54 +02:00
Venelin Stoykov
7e23384145 Merge pull request #435 from Saritasa/fix-async-with-existance-required
Fix `ImageCacheFile.__repr__` to not send signals
2017-10-11 14:39:10 +03:00
Roman Gorbil
d80f426d3c Fix ImageCacheFile.__repr__ to not send signals
Cachefile strategy may be configured to generate file when file existance required.

To generate images, async backends passes `ImageCacheFile` instance to worker.
Both celery and RQ calls `__repr__` method for each argument to enque call.
And if `__repr__` of object will send `existnace_required` signal, we will get endless recursion.

Issue: #434
2017-10-10 17:39:46 +07:00
Venelin Stoykov
c95542ee2a Merge pull request #431 from x-yuri/generateimages
generateimages: fix taking arguments
2017-09-13 02:22:57 +03:00
Yuri Kanivetsky
de3047e73d Make generateimages support pre Django 1.8 versions 2017-09-12 20:46:24 +03:00
Yuri Kanivetsky
a153812add generateimages: fix taking arguments 2017-08-29 11:27:10 +03:00
Venelin Stoykov
364cd49278 Merge pull request #428 from adamchainz/patch-1
README - use Python 3 print function
2017-07-24 15:55:15 +03:00
Adam Johnson
2e1b574486 README - use Python 3 print function
It's 2017!!!
2017-07-24 13:41:54 +01:00
Venelin Stoykov
3819e61fdb Merge pull request #419 from vstoykov/fix/368-processedimagefield-with-spec
Fixed #368 use specs directly in ProcessedImageField
2017-06-01 20:05:47 +03:00
Venelin Stoykov
845eeab3ce Merge pull request #422 from vstoykov/fix/documentation-for-python3
Improve docs for Python 3 - files should be opened as binary
2017-06-01 20:02:59 +03:00
Venelin Stoykov
755bd34c3e In Python 3 files should be opened as binary 2017-05-31 11:07:37 +03:00
Venelin Stoykov
2b04099dc4 Fixed #368 use specs directly in ProcessedImageField
Thanks to @xcono for pointing to solution to the problem
2017-05-18 23:38:32 +03:00
Venelin Stoykov
c3dbb1edf0 Merge branch 'release/4.0.1' into develop
* release/4.0.1:
  stylling and linting fixes to setup.py
  Bump version to 4.0.1
2017-05-17 18:20:52 +03:00
Venelin Stoykov
681b85d7bf stylling and linting fixes to setup.py 2017-05-17 18:12:57 +03:00
Venelin Stoykov
f96dadbfe0 Bump version to 4.0.1 2017-05-17 17:45:22 +03:00
Venelin Stoykov
499e9e1e07 Merge pull request #417 from Proper-Job/performance_improvement
Performance improvement
2017-05-17 12:27:07 +03:00
Moritz Pfeiffer
36fa53e249 Cleaned up _autodiscovered flag handling. 2017-05-17 09:51:24 +02:00
Moritz Pfeiffer
c74d8424b8 Added huge performance improvement by running imagekit.utils.autodiscover() only once on Django > 1.7 as it was intended. 2017-05-16 14:19:05 +02:00
Venelin Stoykov
3d37fb3d3a Merge pull request #414 from mikob/develop
Improved docs to include example on how to use plain ImageSpec (defin…
2017-05-10 17:00:24 +03:00
mikob
c24455ef36 Update README.st change model->instance for clarity in defining specs outside of models. 2017-05-10 18:03:28 +09:00
mikob
934a5283ad Improved docs to include example on how to use plain ImageSpec (defined outside of model, not ImageSpecField) with AdminThumbnail. 2017-04-10 17:46:29 +09:00
Venelin Stoykov
5281859d60 Merge branch 'release/4.0' into develop
* release/4.0:
  Bump version to 4.0
2017-02-22 15:58:12 +02:00
Venelin Stoykov
46d2a9e663 Bump version to 4.0 2017-02-22 15:35:20 +02:00
Venelin Stoykov
855c9a32b1 Merge pull request #411 from vstoykov/feature/wheel
Add universal wheels support
2017-02-22 15:28:11 +02:00
Venelin Stoykov
16ab0d2c99 Merge pull request #412 from vstoykov/feature/appconfigs-module-loading
Replaces #301 autodiscover works with AppConfig
2017-02-22 15:13:14 +02:00
Venelin Stoykov
96383451a0 Universal wheels! 2017-02-22 15:11:07 +02:00
Venelin Stoykov
755193699b Replaces #301 autodiscover works with AppConfig 2017-02-22 14:21:09 +02:00
Venelin Stoykov
d3369eec89 Merge pull request #410 from vstoykov/feature/ignore-ctags
Ignore autogenerated CTags file
2017-02-22 13:53:21 +02:00
Venelin Stoykov
12fdee81dd Ignore autogenerated CTags file
It used by some IDEs to index all Symbols in the project
2017-02-22 13:28:11 +02:00
Venelin Stoykov
dee14b6c22 Merge pull request #409 from matthewwithanm/fix/south
Fixed #408 Do not try south modelinspector when not needed
2017-02-21 18:42:13 +02:00
Venelin Stoykov
2bc6241f55 Do not try south modelinspector when not needed
Fixed #408
2017-02-21 15:19:40 +02:00
Venelin Stoykov
3546c39178 Merge pull request #403 from vstoykov/improve/caching
Improve caching (By default cache files state "forever")
2017-02-17 14:35:05 +02:00
Venelin Stoykov
4d1ee41f2e Make it possible to configure IMAGEKIT_CACHE_TIMEOUT
By default cache forever
2017-02-17 02:47:19 +02:00
Venelin Stoykov
f6d3cbe4a1 Merge pull request #406 from matthewwithanm/feature/dajngo-1.11
Test against Django 1.11
2017-02-16 17:25:59 +02:00
Venelin Stoykov
48cf03b482 Test against Django 1.11 2017-02-16 14:01:30 +02:00
Venelin Stoykov
175904617e Merge pull request #405 from calmyoga/file-opened-locally
Close the file only if it has been opened locally

Fixed #404
2017-02-16 09:26:17 +02:00
rohit suri
732f7045e4 Close the file only if it has been opened locally 2017-02-15 21:24:32 -08:00
Venelin Stoykov
95e484d073 Cleanup caching configuration
Requires Django 1.3+
2017-02-09 00:43:08 +02:00
Venelin Stoykov
47ff56cfe2 Merge pull request #392 from sobolevn/patch-1
updated readme.rst with a svg badge
2016-11-09 23:47:05 +02:00
Sobolev Nikita
c354bb365a updated readme.rst with a svg badge 2016-11-05 20:47:32 +03:00
Venelin Stoykov
7e4bf0e3d8 Merge pull request #390 from papercapp/develop
honor post_save's update_fields and only fire the source_saved signal…
2016-10-26 14:57:56 +03:00
Hannes Tismer
f1f295e054 honor post_save's update_fields and only fire the source_saved signal when needed 2016-10-24 16:21:53 +02:00
Venelin Stoykov
6457cf0c55 Merge pull request #384 from vstoykov/fix-350
Fixed #350: Error when trying to access width/height after url
2016-08-02 01:53:34 +03:00
Venelin Stoykov
6a8fe5f83c Merge pull request #385 from vstoykov/ignore/vscode
Ignore VSCode workspace config files
2016-07-17 05:28:56 +03:00
Venelin Stoykov
3c0c47d8ed Ignore VSCode workspace config files 2016-07-17 05:23:13 +03:00
Venelin Stoykov
d86ec082f1 Fixed #350: Error when trying to access width/height after url
If the file is closed and something is calling `open` now the file will be opened correctly event if it was already closed
2016-07-17 05:08:01 +03:00
Venelin Stoykov
23a243c51e Merge pull request #383 from vstoykov/cleanup/tests
Fixes #382: Tests no longer leave junk
2016-07-17 05:00:56 +03:00
Venelin Stoykov
07d29b3bf7 Fixes #382: Tests no longer leave junk 2016-07-17 04:54:03 +03:00
Venelin Stoykov
5061679b17 Merge pull request #380 from vstoykov/update-django
Fixes #379 Support for Django 1.10
2016-07-14 13:55:40 +03:00
Venelin Stoykov
5cde74e3e2 Fixes #379 Support for Django 1.10 2016-07-11 01:59:32 +03:00
Venelin Stoykov
e9425df833 Merge pull request #378 from vstoykov/ignore-idea
Ignore .idea from git
2016-07-09 14:24:34 +03:00
Venelin Stoykov
f98ee822a4 Ignore .idea from git 2016-07-09 14:23:42 +03:00
Venelin Stoykov
52ad8a0ace Merge pull request #363 from fladi/include_tests_in_source
Include the test suite in the sourcetarball but do not install it.
2016-02-25 09:56:07 +02:00
Michael Fladischer
f2255a5d3a Include the test suite in the sourcetarball but do not install it.
I reworked the `MANIFEST.in` to include the whole test suite so it can be used
by distribution packages during build time. It is excluded from the installed
packages automatically.

The inclusion rules for the documentation were also made more verbose to prevent
build artifacts from entering the source tarball (think .pyc files).
2016-02-25 08:40:18 +01:00
Venelin Stoykov
03a8d0d443 Merge pull request #367 from vstoykov/drop-older-support
Fix travis configuration to Include Python 3.5 and remove old Django versions
2016-02-25 02:28:17 +02:00
Venelin Stoykov
b460a66874 Make travis happy 2016-02-25 02:23:01 +02:00
Venelin Stoykov
124b23ccc8 Merge pull request #366 from vstoykov/drop-older-support
Drop support for Django 1.2 and 1.3 and Python 2.6 and 3.2
2016-02-25 01:42:54 +02:00
Venelin Stoykov
371a3bb376 Drop support for older Django and Python versions
This change drop official support for Django 1.2 and 1.3
and Python 2.6 and 3.2

In this commit test requirements are not so strict to allow versions
available in Debian sid.

Fixes #362

Many thanks to @fladi
2016-02-25 01:23:39 +02:00
Matthew Dapena-Tretter
b3084b43b2 Merge pull request #364 from fladi/non_free_lenna
Replace Lenna image in tests with a truly free alternative.
2016-02-09 18:36:43 -08:00
Michael Fladischer
4e370fdc59 Replace Lenna image in tests with a truly free alternative.
The Lenna image used in image processing tests is considered problematic due to
its unclear copyright status. Right now it is considered to be "overlooked" by
the copyright holder Playboy.

A suitable replacement image is already provided in the libav sources which is
licensed under the Expat (MIT) license and thus truly free.

This replaces the Lenna image with the one from the libav project.
2016-02-09 11:43:05 +01:00
Venelin Stoykov
7ddca36712 Merge pull request #354 from vstoykov/compat
Move compat module outside of templatetags package
2015-12-26 18:06:31 +02:00
Venelin Stoykov
340e26cd67 Move compat module outside of templatetags package 2015-12-26 17:58:22 +02:00
Venelin Stoykov
5ce8b9f072 Merge pull request #353 from vstoykov/improve-tox
Fix test requirements for django 1.9 and Python3.5
2015-12-26 17:57:18 +02:00
Venelin Stoykov
d280ad8989 Fix test requirements for django 1.9 and Python3.5 2015-12-24 23:37:53 +02:00
Bryan Veloso
0c435539df Merge branch 'release/3.3' into develop
* release/3.3:
  Add @vstoykov to the author list.
  Bump version number.
2015-12-08 11:41:50 -08:00
Bryan Veloso
7903efd9b7 Add @vstoykov to the author list. 2015-12-08 11:39:03 -08:00
Bryan Veloso
53fb3a8722 Bump version number. 2015-12-08 11:38:39 -08:00
Venelin Stoykov
d1e877f07d Merge pull request #345 from Photonomie/fix-django19
Django 1.9 compatibility

Fixes #347 
Fixes #340 
Fixes #321 
Fixes #317
2015-12-03 23:56:08 +02:00
Pierre Dulac
cec8cd7780 Update django-nose version to work with Django 1.9 2015-10-31 19:14:32 +01:00
Pierre Dulac
e79d2ba60e Add a missing env to the tox matrix 2015-10-31 19:02:02 +01:00
Pierre Dulac
97dc4b6cb2 Work a compatibility implementation for Django 1.2 2015-10-31 18:51:07 +01:00
Pierre Dulac
6fabad9749 Tells tox to only run the designated env 2015-10-31 11:43:17 +01:00
Pierre Dulac
b475de7b48 Enable the new travis architecture for speed and reliability 2015-10-31 11:39:46 +01:00
Pierre Dulac
820d2f00eb Allow the test to fail fast 2015-10-31 11:39:06 +01:00
Pierre Dulac
c89a63edbe Allow travis to fail for the python3.5 interpreter not yet available 2015-10-31 11:38:40 +01:00
Pierre Dulac
ecf5e892e2 Use the env conf for travis to split the test builds 2015-10-31 11:37:44 +01:00
Pierre Dulac
c858936e0c Add tox env for django 1.9
supported python versions can be found at
https://docs.djangoproject.com/en/1.9/releases/1.9/
2015-10-31 11:36:01 +01:00
Pierre Dulac
7f36f897f8 Update the doc to reflect the new IMAGEKIT_CACHE_BACKEND behavior 2015-10-31 00:22:55 +01:00
Pierre Dulac
5855e97997 Cleaner implementation thanks to @vstoykov explanation 2015-10-30 16:23:28 +01:00
Pierre Dulac
e155b632cd Handle cases where DEFAULT_CACHE_ALIAS is None in old Django versions 2015-10-30 00:24:50 +01:00
Pierre Dulac
fbf15befb8 Do not take a decision on which cache to use in DEBUG mode
maybe the developer wants to test his cache configuration locally, or
maybe he has to test different types of caches, we just don't know
2015-10-29 23:27:02 +01:00
Pierre Dulac
0a0708d2d6 Use a compat method to wrap the new way of retrieving the cache engine 2015-10-29 23:03:48 +01:00
Bryan Veloso
673b95b4c4 Merge branch 'release/3.2.7' into develop
* release/3.2.7:
  Bump the version to 3.2.7.
2015-08-23 18:02:55 -07:00
Bryan Veloso
75763b80f8 Bump the version to 3.2.7. 2015-08-23 17:58:46 -07:00
Matthew Dapena-Tretter
71e2a5b802 Merge pull request #335 from mrigor/close-files
Close files
2015-08-21 16:25:42 -04:00
Igor
7cdda46070 Fixes open cache file never getting closed
In a processes that generates many images, you could run into a
cituation with too man files being open. This results in:
IOError: [Errno 24] Too many open files
2015-08-02 00:05:43 -07:00
Igor
eb81b9c88c Fixes open source file never getting closed
In a processes that generates many images, you could run into a
cituation with too man files being open. This results in:
IOError: [Errno 24] Too many open files
2015-08-02 00:01:30 -07:00
Matthew Dapena-Tretter
9e5ef330fa Merge pull request #324 from vstoykov/python34-django18
Add test environments for Python3.4 and Django1.7 and Django1.8
2015-07-10 09:01:32 -04:00
Venelin Stoykov
458f80050c Do not use progressive when we are not running in terminal 2015-06-05 03:01:07 +03:00
Venelin Stoykov
e455768352 Add test environments for Python3.4 and Django1.7 and Django1.8 2015-06-05 03:01:00 +03:00
Matthew Dapena-Tretter
db70f810ad Merge pull request #323 from vegaro/develop
Fixes imports in README example for ProcessedImageField
2015-05-10 20:40:05 -04:00
Cesar de la Vega
561b5d7ab7 Fixes imports in README example for ProcessedImageField 2015-05-10 23:59:26 +01:00
Bryan Veloso
6bb45bc532 Merge branch 'release/3.2.6' into develop
* release/3.2.6:
  Bump the version to 3.2.6.
2015-02-26 10:28:58 -08:00
Bryan Veloso
d6bbff47f0 Bump the version to 3.2.6. 2015-02-26 10:28:38 -08:00
ILYA
41f45a4fe7 Updated importlib import to fix DeprecationWarning (for django 1.8) 2015-02-26 10:28:11 -05:00
Matthew Dapena-Tretter
8ad3d1e8be Merge pull request #311 from tino/feature/docs-async-optimistic
Add note about usage of optimistic strategy with async backend
2015-02-21 08:45:01 -05:00
Tino de Bruijn
f6e0033aae Add note about usage of optimistic strategy with async backend 2015-02-20 17:58:53 +01:00
David Ray
207849e48e Fix typo 2015-01-27 09:36:16 -05:00
Bryan Veloso
c5738740fb Merge branch 'release/3.2.5' into develop
* release/3.2.5:
  Bump the version to 3.2.5.
2015-01-05 15:58:54 -08:00
Bryan Veloso
a159e7c75b Bump the version to 3.2.5. 2015-01-05 15:58:38 -08:00
Niklas A Emanuelsson
d9fe8d24b2 Explicitly setting serializer for celery task 2015-01-02 15:40:40 +01:00
Bryan Veloso
5275d613e6 Merge branch 'release/3.2.4' into develop
* release/3.2.4:
  Bump the version to 3.2.4.
2014-09-28 13:24:00 -07:00
Bryan Veloso
1d5606b3d7 Bump the version to 3.2.4. 2014-09-28 13:23:32 -07:00
Matthew Dapena-Tretter
7f40d4fd4b Merge pull request #291 from danxshap/import_fix
Fix a Django 1.7 issue with importing INSTALLED_APPS modules
2014-09-28 16:06:16 -04:00
Matthew Dapena-Tretter
94255855db Merge branch 'processors-deprecation' into develop
* processors-deprecation:
  Deprecate `imagekit.processors` submodules
2014-09-28 12:12:54 -04:00
Venelin Stoykov
1ac3399737 Deprecate imagekit.processors submodules
- `base`, `crop`, `resize`, and `utils` are now placed in `pilkit` app
- remove magic compatibility between `imagekit.processors` and `pilkit.procesors`
2014-09-28 18:31:33 +03:00
Bryan Veloso
e56f8c5925 Merge branch 'release/3.2.3' into develop
* release/3.2.3:
  Bump the version to 3.2.3.
2014-09-27 22:21:24 -07:00
Bryan Veloso
5f4f7070f4 Bump the version to 3.2.3. 2014-09-27 22:20:40 -07:00
Matthew Dapena-Tretter
3a2150e515 Exclude tests from dist
Related: matthewwithanm/pilkit#14
2014-09-27 18:03:07 -04:00
Matthew Dapena-Tretter
e2ae850866 Revert "Remove test dir __init__.py"
This reverts commit f5b23a67bd.

I forgot we were using 'tests.settings' as a settings module path.
2014-09-27 17:52:21 -04:00
Matthew Dapena-Tretter
b9b95717c6 Merge branch 'no-extra-queries' into develop
* no-extra-queries:
  Only include fetched fields in initial hash of sources
  Add test to illustrate GH-295
2014-09-27 15:29:52 -04:00
Matthew Dapena-Tretter
78a1ccaf2f Only include fetched fields in initial hash of sources
Should avoid unnecessary queries, as detailed in GH-295.
2014-09-26 22:33:16 -04:00
Matthew Dapena-Tretter
8d35dad5fc Add test to illustrate GH-295 2014-09-26 21:32:01 -04:00
Matthew Dapena-Tretter
002b5bdac8 Merge pull request #292 from njamaleddine/develop
Fixed minor spelling error in README.rst
2014-09-23 22:11:01 -04:00
Matthew Dapena-Tretter
bbf48a7953 Test that there isn't IO done when you get a URL 2014-09-23 18:41:09 -04:00
Matthew Dapena-Tretter
00b4388245 Support should_verify_existence on strategies
This prevents extra IO. Different defaults are used for async backends
since we can’t assume that `existence_required` resulted in existence
synchronously.
2014-09-23 18:41:09 -04:00
Matthew Dapena-Tretter
c92f53c1b0 Test that Optimistic strategy doesn't cause reads
Using the Optimistic strategy should prevent IO ops when you cast the
file as a boolean.
2014-09-23 18:40:58 -04:00
Matthew Dapena-Tretter
9f4192a7c6 Ignore my Python3 virtualenv 2014-09-23 18:29:52 -04:00
Matthew Dapena-Tretter
f5b23a67bd Remove test dir __init__.py
Related: matthewwithanm/pilkit#14
2014-09-23 14:59:20 -04:00
Nabil
945a5623ef Fixed minor spelling error in README.rst 2014-09-21 18:50:44 -04:00
danxshap
06b06dbced Catch autodiscover module import error 2014-09-08 18:28:49 -04:00
Bryan Veloso
89b5666b02 Merge branch 'release/3.2.2' into develop
* release/3.2.2:
  Bump version to 3.2.2.
2014-07-14 12:24:46 -07:00
Bryan Veloso
d013b82c7f Bump version to 3.2.2. 2014-07-14 12:24:05 -07:00
Colin Wood
2f7bfe5dc7 Make sure image files has a name associated.
The generate image command will run into issues if the ImageSpecField does not
have any image file source associated wit hti. Like a Optional image field. So
we can not generate the images for that. So this should check to make sure that
it has one.
2014-07-11 10:07:43 -04:00
Bryan Veloso
5bb41bdccd Merge branch 'release/3.2.1' into develop
* release/3.2.1:
  Bump the version to 3.2.1.
2014-04-04 09:27:48 -07:00
Bryan Veloso
1d80e83732 Bump the version to 3.2.1. 2014-04-04 09:26:54 -07:00
Matthew Dapena-Tretter
1448e7dddd Merge pull request #279 from mkai/_rq_async_refactor
Simplified RQ cache file backed by using the job decorator
2014-04-04 10:33:27 -04:00
Markus Kaiserswerth
3056b3efc0 Simplified RQ cache file backed by using the job decorator 2014-04-04 14:24:21 +02:00
Matthew Dapena-Tretter
f45491bddb Merge pull request #278 from mkai/_rq_async_cleanup
Cleaned up RQ cache file backend code, added async_rq setup extra
2014-03-23 18:50:45 -04:00
Matthew Dapena-Tretter
b869f78b35 Merge pull request #277 from mkai/_async_deprecation
Added a DeprecationWarning if (old) Async cache file backend is used
2014-03-23 18:48:10 -04:00
Matthew Dapena-Tretter
6aa99adf1c Add note about open()
Hopefully this prevents people from going through acrobatics to get a File when
they've already got one!
2014-03-19 11:27:03 -04:00
Matthew Dapena-Tretter
9be8507ebd Always call variable "source_file"
This emphasizes that it's a file object and not a PIL image and also connects it
to the earlier example which creates that variable.
2014-03-19 11:26:10 -04:00
Markus Kaiserswerth
5b0c789f6b setup.py: added 'async_rq' extra, django-rq dependency
django-rq v0.6.0 is the first version with Python 3 support.
2014-03-18 17:03:16 +01:00
Markus Kaiserswerth
c5a1be3b8e Removed unneeded django_rq import 2014-03-18 17:02:23 +01:00
Markus Kaiserswerth
85d8cb15bb Added a DeprecationWarning if Async cache file backend is used 2014-03-18 16:37:18 +01:00
Matthew Dapena-Tretter
d3aabb0db3 Merge pull request #267 from bmispelon/issue-266
Fixed #266 -- Simplified (and renamed) StrategyWrapper.
2014-01-21 08:53:56 -08:00
Matthew Dapena-Tretter
169b594b28 Merge pull request #268 from bmispelon/tox-conf-update
Updated tox.ini to test against Django 1.6.
2014-01-21 08:53:32 -08:00
Baptiste Mispelon
df8d798551 Updated tox.ini to test against Django 1.6. 2014-01-21 17:50:58 +01:00
Baptiste Mispelon
3799f3c2f4 Fixed #266 -- Simplified (and renamed) StrategyWrapper.
StrategyWrapper was unnecessarily implemented as a LazyObject
and it triggered a bug in Django (issue 21840).

Changing the lazy object to a function works just as well and
bypasses the bug.
2014-01-21 17:46:19 +01:00
Matthew Dapena-Tretter
ffd3ba384e Link to Instakit.
Related: fish2000/instakit#2
2014-01-21 10:22:10 -05:00
Bryan Veloso
14c7979e4c Merge branch 'release/3.2' into develop
* release/3.2:
  Bump the version to 3.2.
2014-01-01 15:18:51 -08:00
Bryan Veloso
1ac1a44fc5 Bump the version to 3.2. 2014-01-01 15:17:33 -08:00
Matthew Dapena-Tretter
f113fc7517 Use signal.connect for backwards compat
The receiver decorator isn't available until Django 1.3.
2014-01-01 17:47:36 -05:00
Bryan Veloso
ce9a62c02c Merge branch 'python3' of https://github.com/vstoykov/django-imagekit into vstoykov-python3
* 'python3' of https://github.com/vstoykov/django-imagekit:
  Add Venelin Stoykov to AUTHORS
  Improve logic of contributing ImageSpecFields to Models
  Use force_bytes from imagekit.lib in test_cachefiles
  Remove @vstoykov's note. Seems like the right place to me (:
  Move force_bytes into lib module
  Don't use a raw string with \u escapes
  Fix sanitizing cache key under Python 3
  Add module to sys.modules
  Test for Python 3
  Insert importer at beginning of list
  Delay Django import until needed
  Add Python 3 suport and drop support for Python 2.5

Conflicts:
	imagekit/cachefiles/__init__.py
2013-12-26 17:30:42 -08:00
Bryan Veloso
2ff015a89a Merge branch 'release/3.1' into develop
* release/3.1:
  Tagging 3.1.
2013-12-20 09:20:28 -08:00
Bryan Veloso
452a9c1b31 Tagging 3.1. 2013-12-20 09:18:57 -08:00
Venelin Stoykov
3667c09d82 Add Venelin Stoykov to AUTHORS 2013-12-16 22:38:05 +02:00
Venelin Stoykov
26aa19eeef Improve logic of contributing ImageSpecFields to Models
- If `source` is defined then register source group immediately
- If `source` is not defined then create a signal handler and attach it to
`class_prepared` signal which will see if there is only one ImageField in
the given model.

This will fix problems coused in Python 3 about using ImageSpecField
without providing a `source`.
2013-12-16 10:48:57 +02:00
Venelin Stoykov
8a600d30b3 Use force_bytes from imagekit.lib in test_cachefiles 2013-12-15 01:58:22 +02:00
Matthew Dapena-Tretter
45f10075b6 Remove @vstoykov's note. Seems like the right place to me (: 2013-12-14 13:03:02 -05:00
Matthew Dapena-Tretter
87983c5e6d Move force_bytes into lib module 2013-12-14 13:02:21 -05:00
Matthew Dapena-Tretter
c1e16696b1 Don't use a raw string with \u escapes
Apparently, Python 3.2 doesn't process these in raw strings.
See https://mail.python.org/pipermail/python-list/2012-May/624756.html
and https://mail.python.org/pipermail/python-dev/2007-May/073042.html
2013-12-14 12:54:26 -05:00
Venelin Stoykov
fb947b1937 Fix sanitizing cache key under Python 3 2013-12-14 18:56:47 +02:00
Matthew Dapena-Tretter
68cfcce3f1 Correct reference to generateimage tag
Closes GH-261
2013-12-14 10:50:58 -05:00
Matthew Dapena-Tretter
43afb7c33d Fix celery backend 2013-11-28 00:39:57 -05:00
Matthew Dapena-Tretter
687884224c Merge pull request #258 from mkai/_rq_async_cachefile_backend
Added RQ-based async cache file backend
2013-11-27 20:50:56 -08:00
Markus Kaiserswerth
af3316278d Cache file backends: dropped the "Async" in class names 2013-11-04 13:12:02 +01:00
Markus Kaiserswerth
260c6f5a10 Added RQ-based async cache file backend 2013-10-27 13:27:21 +01:00
Bryan Veloso
2ed1855aa1 Merge branch 'release/3.0.4' into develop
* release/3.0.4:
  Bump the version to 3.0.4.
2013-09-26 10:56:40 -07:00
Bryan Veloso
d6a024ed2d Bump the version to 3.0.4. 2013-09-26 10:56:22 -07:00
Matthew Tretter
5a218e1465 Merge pull request #255 from nex2hex/develop
Add __getstate__ method to ImageCacheFile
2013-09-24 06:24:33 -07:00
nex2hex
1c26a2ea5c Add __getstate__ method to ImageCacheFile 2013-09-24 15:18:59 +04:00
Matthew Tretter
857b1e160e Update IMAGEKIT_DEFAULT_FILE_STORAGE description 2013-09-05 09:42:09 -05:00
Matthew Tretter
bf1b45c943 Add module to sys.modules 2013-09-01 21:56:42 -04:00
Matthew Tretter
2e4d435f4f Test for Python 3 2013-09-01 21:56:28 -04:00
Matthew Tretter
3732b2ee09 Insert importer at beginning of list 2013-09-01 21:55:58 -04:00
Matthew Tretter
183efabca7 Delay Django import until needed 2013-09-01 21:55:37 -04:00
Venelin Stoykov
0575011529 Add Python 3 suport and drop support for Python 2.5 2013-08-19 21:27:50 +03:00
Matthew Tretter
453efac553 Merge branch 'nex2hex/develop' into develop
* nex2hex/develop:
  Update fields.py
2013-08-14 23:40:02 -04:00
Matthew Tretter
c78cbfc089 Discover image generators during lookup
Fixes GH-241
2013-08-14 23:08:40 -04:00
Sean Hayes
d647678c2e Added global boolean to prevent autodiscover() from being called more than once. 2013-08-14 22:55:28 -04:00
nex2hex
1a33c2be51 Update fields.py
Don't overwrite existing image when form saved
2013-08-13 18:52:21 +06:00
Matthew Tretter
bc8fdd7ada Run tests with Django 1.5 2013-07-27 20:50:16 -04:00
Bryan Veloso
144c388689 Merge branch 'release/3.0.3' into develop
* release/3.0.3:
  Bump to 3.0.3.
2013-07-22 10:01:53 -07:00
Bryan Veloso
3be774bbf6 Bump to 3.0.3. 2013-07-22 10:01:20 -07:00
Bryan Veloso
532188bd51 Merge branch 'fix-pending-state' into develop
* fix-pending-state:
  Remove PENDING state
2013-07-22 09:59:08 -07:00
Matthew Tretter
e36290b4ee Woops. It uses the cache if DEBUG is False. 2013-07-17 16:09:02 -04:00
Bryan Veloso
8a709a845c Merge branch 'release/3.0.2' into develop
* release/3.0.2:
  Removing the changelog. Changelogs are hard.
  Bump to 3.0.2.
2013-07-17 11:46:18 -07:00
Bryan Veloso
05ec0c1b33 Removing the changelog. Changelogs are hard. 2013-07-17 11:45:42 -07:00
Bryan Veloso
cae6677994 Bump to 3.0.2. 2013-07-17 11:44:31 -07:00
Matthew Tretter
3444626084 Remove PENDING state
Re: #227
2013-07-16 21:41:10 -04:00
Matthew Tretter
a0c7b3f274 Use == for comparison 2013-07-16 20:40:48 -04:00
Matthew Tretter
f684b4e4e0 Merge branch 'fix-234' into develop 2013-07-16 20:36:36 -04:00
Matthew Tretter
14939faef6 Don't mutate __dict__ 2013-07-13 17:13:58 -04:00
Matthew Tretter
3c04cb852f Don't require source in __setstate__; fixes #234 2013-07-13 17:12:55 -04:00
Matthew Tretter
db6cfcb6ce Add (failing) test for #234 2013-07-13 16:38:49 -04:00
Matthew Tretter
637af70921 Don't include cache in serialization of backend
Fixes #227
2013-06-20 09:17:12 -04:00
Rich Leland
ada883c99f Fixed name of spec id attr 2013-06-13 17:41:30 -04:00
Bryan Veloso
ef05e23b66 Merge branch 'release/3.0.1'
* release/3.0.1:
  Bump version to 3.0.1.
  Fix cache backend fallback
  Fix LazyImageCacheFile.__repr__ and __str__
  Test stringification of LazyImageCacheFiles
  PEP 3110 compat
  Remove unused imports
  Update installation directions; closes #223
  I, for one, welcome our new @matthewwithanm overlords.
  Add import to example
2013-06-11 14:08:38 -07:00
Bryan Veloso
90e9d314a6 Bump version to 3.0.1. 2013-06-11 14:08:23 -07:00
Matthew Tretter
bb7e9e5891 Fix cache backend fallback
Closes #225
Closes #226
2013-06-10 18:33:22 -04:00
Matthew Tretter
01a6c555a1 Fix LazyImageCacheFile.__repr__ and __str__
Using SimpleLazyObject also lets us clean things up a bit.

Closes #218
2013-06-04 23:35:46 -04:00
Matthew Tretter
3001069254 Test stringification of LazyImageCacheFiles 2013-06-04 23:35:46 -04:00
Jacob Magnusson
654867c3cf PEP 3110 compat 2013-06-04 21:42:03 -04:00
Jacob Magnusson
6582794408 Remove unused imports 2013-06-04 21:41:52 -04:00
Matthew Tretter
d3882c34b2 Update installation directions; closes #223 2013-06-04 21:37:06 -04:00
Bryan Veloso
02cd1bf7ff I, for one, welcome our new @matthewwithanm overlords. 2013-06-03 12:50:20 -07:00
Matthew Tretter
af73a93953 Add import to example
Closes #224
2013-06-03 10:31:57 -03:00
Bryan Veloso
0e41675d1c Merge branch 'release/3.0.0'
* release/3.0.0: (302 commits)
  Bump the version number to 3.0.0.
  Fixed thumbnail template tag treating provided sizes as strings, not integers
  Remove source_deleted signal
  Don't send source_saved when no source
  Add failing test for source_save signal w/o source
  Test that source_saved is dispatched for new instance
  Make test less verbose
  Separate source group tests; signal counting util
  Combine source_created and source_changed
  Define NullHandler for Python <= 2.6
  Improve default cache backend handling
  pass in upscale keyword to thumbnail template tag
  Fix None checks
  Split before_access into two signals
  Reset content pointer
  Reduce calls to backend
  Store the file after we generate it
  Store cachefile strategy on file; not generator
  Add cachefile backend fallback
  Use pilkit's process_image utility
  ...
2013-05-30 00:36:46 -07:00
Bryan Veloso
0e0240085f Bump the version number to 3.0.0. 2013-05-30 00:33:10 -07:00
Matthew Tretter
09eea6edf6 Merge pull request #222 from IlyaSemenov/develop
Fixed thumbnail template tag treating provided sizes as strings, not integers
2013-05-29 18:43:04 -07:00
Matthew Tretter
33b8913031 Merge branch 'source-signal-cleanup' into develop 2013-05-29 21:41:44 -04:00
Ilya Semenov
761dcd20ae Fixed thumbnail template tag treating provided sizes as strings, not integers 2013-05-25 16:45:40 +07:00
Matthew Tretter
f9d91c7c4d Remove source_deleted signal
...for now. Eventually, we will want a signal that tells us when sources
are no longer used, however that isn't just limited to when they're
deleted! This new signal should also be dispatched, for example, when
a source image field is set to `None`. Since none of the built-in
strategies are currently using the source_deleted signal, I've decided
to remove it until we have a more complete solution.
2013-05-25 00:50:59 -04:00
Matthew Tretter
535e68aea6 Don't send source_saved when no source
Fixes #214 and the failing test from 404fed5
2013-05-25 00:19:14 -04:00
Matthew Tretter
404fed58ec Add failing test for source_save signal w/o source
This is like the test contributed by @saulshanabrook in #214, but
catches the bug closer to its source. (A more unit-y unit test.)
2013-05-25 00:07:58 -04:00
Matthew Tretter
c6a0a13c45 Test that source_saved is dispatched for new instance 2013-05-25 00:04:26 -04:00
Matthew Tretter
a6ef72027b Make test less verbose 2013-05-25 00:04:26 -04:00
Matthew Tretter
4ff55724dc Separate source group tests; signal counting util 2013-05-25 00:04:22 -04:00
Matthew Tretter
397a79ba56 Combine source_created and source_changed
As discussed in #214, source_created and source_changed didn't really
have clear definitions. In truth, their names and separation betray
their origins as model receivers in earlier versions. The "source group"
abstraction helped us get away from thinking about things exclusively in
terms of models, but these remained as an artifact.
2013-05-24 23:21:30 -04:00
Matthew Tretter
0d5bfe3751 Define NullHandler for Python <= 2.6
Closes #219
2013-05-20 19:19:13 -04:00
Matthew Tretter
80008aee11 Merge pull request #215 from saulshanabrook/patch-5
pass in upscale keyword to thumbnail template tag
2013-05-20 16:09:05 -07:00
Matthew Tretter
32522114db Improve default cache backend handling 2013-05-13 21:28:41 -04:00
Saul Shanabrook
6023e9216a pass in upscale keyword to thumbnail template tag 2013-05-11 09:12:29 -03:00
Matthew Tretter
34739819aa Fix None checks
Files can be falsy
2013-05-10 04:43:01 -04:00
Matthew Tretter
c89b18aa95 Split before_access into two signals
Differentiating between when the generated file content is required and
when the generated file is just required to exist gives us more
flexibility with strategies.
2013-05-10 04:39:46 -04:00
Matthew Tretter
6db082bca2 Reset content pointer 2013-05-10 03:08:43 -04:00
Matthew Tretter
906fbbd463 Reduce calls to backend
Reading from an `ImageCacheFile`, will result in accessing its `file`
attribute repeatedly which would result in the `before_access` signal
being dispatched, which in turn would result in many unnecessary calls
to the image cache backend. With this change, we don't send
`before_access` if the file has already been created.

Similarly, we don't need to try to generate the image if we know for
certain that it's already been generated (because we have a reference
to it).
2013-05-10 02:56:44 -04:00
Matthew Tretter
bc49f9cf8b Store the file after we generate it
This means reading ImageCacheFiles won't result in an additional storage
operation.
2013-05-10 02:42:28 -04:00
Matthew Tretter
06dd238993 Store cachefile strategy on file; not generator
The file can appeal to the generator for the value, but it shouldn't
require it; it just does that as a convenience.
2013-05-10 00:51:47 -04:00
Matthew Tretter
d5d5fc0550 Add cachefile backend fallback
The generator shouldn't have to implement `cachefile_backend`
2013-05-10 00:26:46 -04:00
Matthew Tretter
4efa05099d Use pilkit's process_image utility 2013-05-09 22:02:03 -04:00
Matthew Tretter
82eb69b3be Merge pull request #212 from saulshanabrook/patch-2
No makefile, so changed testing to run tox
2013-05-02 19:26:13 -07:00
Saul Shanabrook
92a9184ed3 Use tox to run all tests 2013-05-01 00:19:51 -03:00
Saul Shanabrook
0de80cf59c No makefile, so changed testing to run setup.py test 2013-05-01 00:06:55 -03:00
Saul Shanabrook
2304381b3d Changed contributing guidelines link to branch develop 2013-05-01 00:05:10 -03:00
Matthew Tretter
6b75822eb3 Show build status of develop branch 2013-04-30 22:52:23 -04:00
Matthew Tretter
84dcebbefe Add @saulshanabrook to AUTHORS 2013-04-30 22:06:09 -04:00
Matthew Tretter
676d49c605 A few corrections; more about caching file data 2013-04-30 21:56:55 -04:00
Matthew Tretter
2064434042 Merge branch 'saulshanabrook/revise-docs' into develop 2013-04-30 20:52:58 -04:00
Matthew Tretter
d94c4bb0a9 Merge branch 'canon-pickle' into develop 2013-04-30 20:50:29 -04:00
Saul Shanabrook
4a608caf3a moved models in front of source groups in the advanced docs 2013-04-30 18:34:42 -04:00
Saul Shanabrook
deed81b963 Added caching docs 2013-04-30 18:30:51 -04:00
Matthew Tretter
eb36ae399e Add test for memcached key sanitation 2013-04-30 09:32:47 -04:00
Matthew Tretter
f173861b53 Fix invalid char range 2013-04-30 09:32:47 -04:00
Matthew Tretter
c704db2da5 Sanitize cache keys for memcached
Closes #208
2013-04-30 09:32:47 -04:00
Bryan Veloso
bc0c17010e Bryan, we use reST. >_<; Refs #206. 2013-04-25 11:33:29 -07:00
Bryan Veloso
a3498c5aa1 Add a build status image to the README. Fixes #206. 2013-04-25 11:29:27 -07:00
Matthew Tretter
f5b171979b Canonicalize sets and dicts before hashing 2013-04-25 02:30:37 -04:00
Matthew Tretter
3e019f5dac Merge pull request #204 from danxshap/close_file_fix
Re-open source file on ValueError in ImageSpec.generate()
2013-04-23 10:59:14 -07:00
Matthew Tretter
0a98eb0e79 Use DEFAULT_FILE_STORAGE by default
As raised in #203, if `IMAGEKIT_DEFAULT_FILE_STORAGE` isn't set,
`DEFAULT_FILE_STORAGE` should be used.
2013-04-22 09:05:49 -04:00
danxshap
13c92db760 Re-open source file on ValueError in ImageSpec.generate() 2013-04-21 16:06:13 -04:00
Eric Brelsford
7946fe54b5 processors in PILKit: module
Help those looking in the code find processors.
2013-04-10 14:24:02 -04:00
Eric Brelsford
90c4529d26 processors in PILKit: documentation 2013-04-10 14:19:25 -04:00
Matthew Tretter
261f164127 Bump version to 3.0b1 2013-04-05 16:43:26 -04:00
Matthew Tretter
c4431fe296 Don't require generator to unregister 2013-04-05 16:41:53 -04:00
Matthew Tretter
4b2e6341f9 Merge branch 'south-fix' into develop 2013-04-05 16:38:28 -04:00
Matthew Tretter
6f9f99e86c Fields shouldn't cause AlreadyRegistered exceptions 2013-04-05 15:28:36 -04:00
Matthew Tretter
091b2137d0 Always call set_spec_id 2013-04-05 14:57:41 -04:00
Matthew Tretter
41ab2c0fa3 Use different name for different signature 2013-04-05 14:57:11 -04:00
Matthew Tretter
dafebc9a4d Don't call register without spec 2013-04-05 14:38:29 -04:00
Matthew Tretter
9891314b8e Don't error if same generator is registered 2013-04-05 12:25:02 -04:00
Matthew Tretter
681527fa9d Remove implicit source logic from descriptor
This needs to be known earlier, when registering the source group, so
we do it in `contribute_to_class` instead.

Closes #188.
2013-04-03 00:21:04 -04:00
Matthew Tretter
c0ce3b5209 Add "Deferred Generation" section to docs 2013-04-02 23:31:15 -04:00
Matthew Tretter
28ffd444d6 Relax Pillow testing requirement 2013-04-02 23:01:44 -04:00
Matthew Tretter
fbf052ce5f Fix test: update DummyAsyncCacheFileBackend API 2013-04-02 23:00:54 -04:00
Matthew Tretter
2b10e85813 Clarify "force" kwarg 2013-04-02 22:55:44 -04:00
Matthew Tretter
3ca8c53698 Also defer existance check 2013-04-02 22:37:52 -04:00
Matthew Tretter
728368abf6 Add async cachefile backend 2013-04-02 22:29:53 -04:00
Matthew Tretter
86cd23e906 Remove actions module 2013-03-15 01:06:28 -04:00
Matthew Tretter
3d6a9de9aa Add existence check timeout 2013-03-15 01:05:53 -04:00
Matthew Tretter
b061e135c2 Add tests for cachefile truthiness 2013-03-15 00:49:44 -04:00
Matthew Tretter
1e129c5b70 Convert to boolean 2013-03-15 00:49:24 -04:00
Matthew Tretter
70ff6dc788 Store state when force-generated 2013-03-15 00:30:58 -04:00
Matthew Tretter
c48c720f8a Add __nonzero__ method that will work for async 2013-03-15 00:27:23 -04:00
Matthew Tretter
fc87c0497c Add abstract cachefile backend for illustration 2013-03-15 00:20:15 -04:00
Matthew Tretter
54dda25adb Generation goes through backend; use states
We might as well account for asynchronous generation in our
CachedFileBackend.
2013-03-15 00:19:19 -04:00
Matthew Tretter
ac92b77709 Fix loopy existence check
Sometimes two wrongs do make a right I guess. But better to have two
rights.
2013-03-14 23:04:30 -04:00
Matthew Tretter
f181d30008 Rename file_exists to exists 2013-03-14 22:58:28 -04:00
Matthew Tretter
044a3625f9 Correct error message 2013-03-14 22:55:40 -04:00
Matthew Tretter
aae6aeb142 Add truthy/falsy file assertion utils 2013-03-14 22:24:33 -04:00
Matthew Tretter
d62d8a824e Rename test_specs 2013-03-14 22:17:37 -04:00
Matthew Tretter
14be033b7f Add SECRET_KEY for tests 2013-03-13 23:41:28 -04:00
Matthew Tretter
c64eee40b3 Bump version to 3.0a5 2013-02-25 22:43:37 -05:00
Matthew Tretter
92a3c2688c Error when attempting to generate image w/o source 2013-02-25 22:42:47 -05:00
Matthew Tretter
61aa1c32e7 Bump version to 3.0a4 2013-02-25 22:22:27 -05:00
Matthew Tretter
b33869a9f8 Sourceless specs are falsy
Passes test added in 190153d. Related to #187
2013-02-25 22:18:33 -05:00
Matthew Tretter
190153d068 Add test to ensure sourceless specs are falsy
Currently failing; related to #187
2013-02-25 22:15:57 -05:00
Matthew Tretter
74ae51f164 Add venv and .env to gitignore 2013-02-25 21:56:51 -05:00
Matthew Tretter
dff0182310 Remove another old util 2013-02-12 21:40:15 -05:00
Matthew Tretter
f5a078b68f Remove old util 2013-02-12 21:40:15 -05:00
Matthew Tretter
e456e0fd99 Remove util tests
These are part of PILKit
2013-02-12 21:40:15 -05:00
Matthew Tretter
896e289884 Add @seanbell to AUTHORS 2013-02-12 20:46:35 -05:00
Matthew Tretter
5c944c5efb Bump version to 3.0a3 2013-02-10 16:05:21 -05:00
Matthew Tretter
b180bbef09 Merge branch 'abstract-model-fixes' into develop 2013-02-10 16:04:53 -05:00
Matthew Tretter
b53b7c3cae Clarify ImageFieldSourceGroup docstrings 2013-02-10 16:01:50 -05:00
Matthew Tretter
90ae9e8339 Fix get_nonabstract_descendants
Previously, function was only returning the first non-abstract
descendant. (It should also return concrete descendants of concrete
descendants, which it now does.)

Also, converted to generator.
2013-02-10 15:58:22 -05:00
Matthew Tretter
7bf3e4e7a3 Add test for @seanbell's utility
Currently failing
2013-02-10 15:54:40 -05:00
Matthew Tretter
de550b71df Add test for abstract model signals 2013-02-10 15:42:39 -05:00
Matthew Tretter
df41459e65 Fix signals for abstract models
Includes a fix for undispatched signals, as well as signals being
handled twice.

A regression of #126
Related: #185
2013-02-10 15:42:10 -05:00
Sean Bell
af6ebcb469 Fixing iteration over objects for abstract models 2013-02-09 03:02:18 -05:00
Matthew Tretter
969275bbc9 Rename GeneratedImageFile to ImageCacheFile 2013-02-08 18:15:00 -05:00
Matthew Tretter
9f3550a4f4 Merge branch 'pilkit' into develop 2013-02-08 18:08:42 -05:00
Matthew Tretter
98552d7f75 Relax PILKit version requirement 2013-02-08 18:08:29 -05:00
Matthew Tretter
ec5d4ed324 Remove processor tests; these are in PILKit 2013-02-07 23:15:08 -05:00
Matthew Tretter
36313194ac Remove PILKit functionality
This commit removes the functionality now in the PILKit project, and
adds PILKit as a dependency. Import hooks have been used to expose the
processors under "imagekit.processors".
2013-02-07 23:10:05 -05:00
Matthew Tretter
d0ba353be3 Typo fix! 2013-02-07 16:11:02 -05:00
Bryan Veloso
aa2dcb8e34 Merge pull request #183 from jdriscoll/ik-next
IK Next
2013-02-06 19:28:24 -08:00
Matthew Tretter
8fabbae86c Extract version from pkgmeta 2013-02-06 22:21:20 -05:00
Matthew Tretter
561856abd7 Don't use assert_not_in
It's not available in Python 2.6
2013-02-06 22:04:13 -05:00
Matthew Tretter
51dcf283fd Fix default cache backend for Django < 1.3 2013-02-06 21:59:26 -05:00
Matthew Tretter
80a1f0b4ee Merge branch 'ik3-testing-fixes' into ik-next 2013-02-06 21:11:08 -05:00
Matthew Tretter
4ac6565bec Use Pillow for testing 2013-02-06 21:01:51 -05:00
Matthew Tretter
7759394df7 Remove Pillow (PIL is specified in setup.py) 2013-02-06 20:46:14 -05:00
Matthew Tretter
c90e6d637c Simplify tox command 2013-02-06 20:46:06 -05:00
Matthew Tretter
5982e1e549 Remove Makefile 2013-02-06 20:41:18 -05:00
Matthew Tretter
f6ce251e13 Fix setup.py test 2013-02-06 20:40:54 -05:00
Matthew Tretter
a906555c9f Merge branch 'develop' into ik-next
Conflicts:
	imagekit/__init__.py
	imagekit/conf.py
	imagekit/generators.py
	imagekit/models/fields/files.py
	imagekit/utils.py
	tests/core/tests.py
2013-02-05 23:34:05 -05:00
Matthew Tretter
088b84627b Add section about management commands 2013-02-05 21:19:35 -05:00
Matthew Tretter
1f86e33d64 Doc fixes 2013-02-05 21:12:39 -05:00
Matthew Tretter
0ea497261b Update credits 2013-02-05 19:24:36 -05:00
Matthew Tretter
34a7ab9751 Remove requirements.txt
What was this doing in here?
2013-02-05 19:21:26 -05:00
Matthew Tretter
55a2a5fc9d Typo fix 2013-02-04 19:57:00 -05:00
Matthew Tretter
301adc2087 Let's call em cachefiles
Changed my mind about 04aa72c1f9. It's
just a better description, even if different strategies can change the
behavior so it isn't really very cache-like.
2013-02-04 19:52:38 -05:00
Matthew Tretter
59971b6cd4 More docs 2013-02-04 19:48:42 -05:00
Matthew Tretter
5f8f651def More advanced docs! 2013-02-04 19:48:42 -05:00
Matthew Tretter
d22c49a465 Don't delete the file when source is deleted
We can't be sure another spec isn't using this file.
2013-02-04 19:48:42 -05:00
Matthew Tretter
1a7c0627df Starting advanced usage docs 2013-02-04 19:48:32 -05:00
Eric Eldredge
36c0757417 Some upgrading docs 2013-02-02 20:47:05 -05:00
Matthew Tretter
218f569005 Don't assign processors, so properties will work
This way, a subclass can add a @property without a setter and not worry
about an error.
2013-02-02 19:21:32 -05:00
Matthew Tretter
c9205e588e More docs 2013-02-01 23:27:36 -05:00
Matthew Tretter
58e1c7f7e0 Some docs 2013-02-01 01:37:49 -05:00
Matthew Tretter
f94b7276b3 Use "imagekit" instead of "ik" for built-in generator prefix 2013-02-01 01:31:51 -05:00
Matthew Tretter
50d83745bc Remove unnecessary complexity of kwarg mapping 2013-02-01 01:02:20 -05:00
Matthew Tretter
08ebcbcbf3 Change html attrs delimiter to -- 2013-02-01 00:56:29 -05:00
Matthew Tretter
92b11f8349 Use imagegenerators module, not imagespecs 2013-02-01 00:30:15 -05:00
Matthew Tretter
0947c1403f Organize settings 2013-01-31 22:40:54 -05:00
Matthew Tretter
933ff79ac1 Make settings consistent 2013-01-31 22:38:48 -05:00
Matthew Tretter
e1c819e9b4 Allow default generatedfile name configuration w/namers 2013-01-31 22:37:09 -05:00
Matthew Tretter
bf1685dbfb Generalize get_class util 2013-01-31 22:01:01 -05:00
Matthew Tretter
2ca4e4b6c2 Merge branch 'rename-cache-things' into ik-next 2013-01-31 19:43:06 -05:00
Matthew Tretter
75962976d0 Add stringify methods to LazyGeneratedImageFile 2013-01-31 19:41:54 -05:00
Matthew Tretter
d6b73b8da7 Renaming/repackaging of generated file related classes 2013-01-31 19:24:08 -05:00
Matthew Tretter
8e6abc1e65 Remove ensure_exists
`generate()` now plays double duty
2013-01-31 10:07:27 -05:00
Matthew Tretter
01fad6e4c6 Fix registration bug 2013-01-31 10:07:27 -05:00
Matthew Tretter
04aa72c1f9 Rename cache things (it isn't cachine)
https://twitter.com/alex_gaynor/statuses/257558176965206016
2013-01-31 10:07:20 -05:00
Matthew Tretter
7f6188623c Merge branch 'cacheables' into ik-next 2013-01-29 02:27:24 -05:00
Matthew Tretter
f0dbe32f7a Fix pickling error 2013-01-29 02:27:03 -05:00
Matthew Tretter
54ca5da15d Improve generator id pattern matching
This behavior allows users to easy generate images by app, model, or
field.
2013-01-29 02:22:00 -05:00
Matthew Tretter
e0ffb246ae Always use colon as segment separator 2013-01-29 02:17:52 -05:00
Matthew Tretter
e48817a5ec Update warmimagecache to use new cacheable registry 2013-01-29 01:53:23 -05:00
Matthew Tretter
ca4f090e63 Fix source callbacks on strategies 2013-01-29 01:48:06 -05:00
Matthew Tretter
3931b552a0 Separate source groups and cacheables.
This allows a sensible specialized interface for source groups, but also
for ImageKit to interact with specs using the generalized image
generator interface.
2013-01-29 01:40:00 -05:00
Matthew Tretter
5b44564318 Add LazyGeneratedImageCacheFile 2013-01-28 21:46:04 -05:00
Matthew Tretter
a3e9a080d4 Revert signal names 2013-01-28 21:45:58 -05:00
Matthew Tretter
cef3a41d86 Merge branch 'ik-next' into cacheables
Conflicts:
	imagekit/management/commands/warmimagecache.py
	imagekit/registry.py
2013-01-28 21:45:37 -05:00
Eric Eldredge
eb9089e0c8 Register cacheables as generators instead of items 2013-01-24 00:04:43 -05:00
Matthew Tretter
c202234e82 Fix imagekit.forms.fields.ProcessedImageField 2013-01-23 22:54:25 -05:00
Matthew Tretter
b45a22abe6 Add test for imagekit.forms.fields.ProcessedImageField 2013-01-23 22:54:08 -05:00
Eric Eldredge
a8855d4c27 Change spec/source registry to generator/cacheable 2013-01-23 22:46:57 -05:00
Matthew Tretter
f4917ab7ca Clean up util method 2013-01-23 22:41:36 -05:00
Matthew Tretter
84b30e990f Fix imagekit.models.fields.ProcessedImageField 2013-01-23 22:41:23 -05:00
Matthew Tretter
c6f2c2e7a7 Add test for ProcessedImageField 2013-01-23 22:40:57 -05:00
Matthew Tretter
234082e63c Extract generate() util, to make files Django likes 2013-01-23 22:34:29 -05:00
Matthew Tretter
9dd7bef709 Simplify import 2013-01-23 22:07:31 -05:00
Matthew Tretter
d52b9c8100 Add utility for extracting field info 2013-01-23 21:47:54 -05:00
Matthew Tretter
4737ac64c4 Specs no longer accept arbitrary kwargs
Only the source.
2013-01-23 21:35:38 -05:00
Matthew Tretter
6ff1d35fbe Remove unused import 2013-01-23 21:31:53 -05:00
Matthew Tretter
eef1e41448 Remove code that used old filename kwarg 2013-01-23 21:28:23 -05:00
Matthew Tretter
d632fc70fa Copy contents to NamedTemporaryFile if generated file has no name 2013-01-23 21:27:21 -05:00
Matthew Tretter
4ecfa5d35e Don't rely on source filename being relative path
Closes #180
2013-01-13 23:40:26 -05:00
Matthew Tretter
8c5a571293 Remove unused import
Fixes flake8 error
2013-01-13 23:35:19 -05:00
Matthew Tretter
e5b15d09bd Remove _generateimage utility. 2013-01-09 00:28:29 -05:00
Matthew Tretter
5acce98223 Remove extra space 2013-01-09 00:26:47 -05:00
Matthew Tretter
219b8507ad Add thumbnail tag tests 2013-01-09 00:25:28 -05:00
Matthew Tretter
43a1f49498 New thumbnail tag syntax! Closes #177 2013-01-09 00:25:08 -05:00
Matthew Tretter
3177eb8e19 Extract utils for use in other modules 2013-01-08 23:36:22 -05:00
Matthew Tretter
11d511f9cc Extract util for parsing common bits.
In preparation for new thumbnail and placeholder tag syntaxes (#177 and
#176) which share some (but not all) syntax with the generateimage tag.
2013-01-08 22:42:42 -05:00
Matthew Tretter
658bb22c78 Special case serialization of ImageFieldFiles
Closes #168
2013-01-08 21:52:56 -05:00
Matthew Tretter
c2dedaa2b8 Use file name; not file, which can't be pickled 2013-01-08 20:57:19 -05:00
Matthew Tretter
faee0fa537 Correct typo 2013-01-08 20:36:17 -05:00
Matthew Tretter
d80f2f26a9 "source" now refers to the file itself 2012-12-11 22:53:13 -05:00
Matthew Tretter
184c13dd4e More source_group renaming 2012-12-11 22:41:10 -05:00
Matthew Tretter
52fb4e24be Add thumbnail templatetag
Finally!
2012-12-07 00:42:11 -05:00
Matthew Tretter
30e40b4916 Add TODO for unregistration 2012-12-07 00:17:35 -05:00
Matthew Tretter
c69c2d087e Create Thumbnail spec; closes #175 2012-12-06 23:48:09 -05:00
Matthew Tretter
1fb1d83c56 Add Thumbnail processor 2012-12-06 23:22:02 -05:00
Matthew Tretter
8c80ba3b4f GeneratedImageCacheFile stores file manipulation attributes
Everything for dealing with files should be part of
GeneratedImageCacheFile--not the generator. The fact that
GeneratedImageCacheFile can get this information (storage, filename,
etc.) is a convenience so that the user only has to define one class
(the generator) to fully specify their functionality, but handling the
cache file is not part of the core responsibility of the generator.

This is also the reason for the renaming of `get_filename` and `storage`
to `cache_file_name` and `cache_file_storage`: the generator is just as
useful for those who want to generate persistent files. But the original
attribute names didn't indicate that they were used only for cache
files. The new ones do, and don't preclude the addition of other
versions that would be used by another `File` subclass for specifying
file names or storage classes.
2012-12-06 19:54:26 -05:00
Matthew Tretter
12307c97aa Use state--not constructor args--to recreate dynamic specs
Previously, we were relying on `__init__`'s arguments to recreate specs.
Now we do it the proper way, using the dict returned by `__getstate__`
(which may or may not include those arguments).
2012-12-05 23:51:30 -05:00
Matthew Tretter
042bdcefb6 Simplify dynamic spec definitions
Use a closure instead of an attribute to store the class attrs.
2012-12-05 23:38:10 -05:00
Matthew Tretter
c45876f95c Ignore some style errors 2012-12-05 23:16:34 -05:00
Matthew Tretter
2a6199b804 Simplify get_hash implementation 2012-12-05 23:16:07 -05:00
Matthew Tretter
7578903307 Fix test 2012-12-05 21:10:36 -05:00
Matthew Tretter
0ec6067c8d Correct pickling/unpickling of dynamic specs
Previously, __reduce__ was returning a reduction of the class, not the
instance.
2012-12-05 21:10:36 -05:00
Matthew Tretter
938e2e178b Clean up test utils; write to media dir 2012-12-05 21:10:36 -05:00
Matthew Tretter
7f11f44c67 Special case source_file for specs
It was already special. Why hide it? Closes #173
2012-12-05 21:10:31 -05:00
Matthew Tretter
ea962b6259 Correct argument order
Related: 2cc72cd
2012-12-05 21:09:10 -05:00
Matthew Tretter
afc5900db6 Support decorator syntax for register.spec 2012-12-05 21:09:10 -05:00
Matthew Tretter
956601b5d0 Revert register.spec argument order
Since we got rid of inner Config classes, we can put the order back and
support decorators.
2012-12-05 21:09:10 -05:00
Matthew Tretter
a07bc49a25 Remove inner Config classes 2012-12-05 21:09:10 -05:00
Matthew Tretter
a5c33a4925 Add tests for generateimage template tag
Currently, these will fail because the temporary file cannot be pickled
in order to generate a hash.
2012-12-03 22:25:12 -05:00
Matthew Tretter
a499f5fbe6 Add util for generating named image file 2012-12-03 22:24:55 -05:00
Matthew Tretter
4f7ce68904 Add documentation for generateimage 2012-12-03 21:11:52 -05:00
Matthew Tretter
4f81e14f58 Re-add html attribute handling 2012-12-03 21:06:15 -05:00
Matthew Tretter
848d7d7fa3 Add TODOs 2012-12-01 23:19:45 -05:00
Matthew Tretter
14d2193f8d Remove unused args 2012-12-01 22:23:25 -05:00
Matthew Tretter
db777594ce Merge branch 'generator-supreme' into ik-next 2012-12-01 22:12:00 -05:00
Matthew Tretter
236eea8459 Move filename generation to generator 2012-12-01 22:09:34 -05:00
Matthew Tretter
20c900df4a Remove unused imports 2012-12-01 21:52:23 -05:00
Matthew Tretter
7bc82d3624 Remove arguments from generate() method
Previously, we had two places where we were passing kwargs that affected
the image generation: the ImageSpec constructor and the generate method.
These were essentially partial applications. With this commit, there's
only one partial application (when the spec is instantiated), and the
generate method is called without arguments. Therefore, specs can now
be treated as generic generators whose constructors just happen to
accept a source_file.
2012-12-01 21:20:33 -05:00
Matthew Tretter
5ecb491e65 Remove unused import 2012-12-01 20:47:55 -05:00
Matthew Tretter
7ed404f096 Switch args back to old order 2012-12-01 20:45:34 -05:00
Matthew Tretter
1f06c9ac70 Remove ImageSpecCacheFile 2012-12-01 20:41:08 -05:00
Matthew Tretter
9188499965 Rework template tag for generators 2012-12-01 20:36:31 -05:00
Eric Eldredge
e0567e8fa7 Remove specs.SpecRegistry; add registry module
The registry module splits the work that specs.SpecRegistry
used to do into two classes: GeneratorRegistry and
SourceGroupRegistry. These two registries are wrapped in
Register and Unregister utilities for API convenience.
2012-12-01 17:16:09 -05:00
Matthew Tretter
d253fe281a Rename image_field argument; closes #158 2012-12-01 16:46:10 -05:00
Matthew Tretter
54baa44900 Require spec id for form fields
Closes #163
2012-12-01 15:56:37 -05:00
Matthew Tretter
4ead0b3002 Add note about nested config classes 2012-12-01 15:51:40 -05:00
Matthew Tretter
5b1c5f7b4e Rename source objects to source groups 2012-12-01 15:51:28 -05:00
Matthew Tretter
49a55d4763 Try adding __reduce__ 2012-12-01 14:11:04 -05:00
Matthew Tretter
5a414a3644 Rename spec_args to spec_attrs 2012-11-06 23:50:23 -05:00
Matthew Tretter
7532e5040b Add contributing guidelines 2012-11-06 00:40:14 -05:00
Matthew Tretter
aaa823afd6 Add flake8 linting 2012-11-05 23:34:32 -05:00
Matthew Tretter
9d310ba57e Remove old class-based tests 2012-11-05 22:45:10 -05:00
Matthew Tretter
c3f7aeca54 Separate serialization test 2012-11-05 22:44:00 -05:00
Matthew Tretter
c752eea6a0 Fix bug with black mat_color 2012-11-05 22:23:29 -05:00
Matthew Tretter
6255b93b78 Add some processor tests 2012-11-05 22:23:25 -05:00
Matthew Tretter
56f1ccb8a8 Separate test_utils module 2012-11-05 21:54:15 -05:00
Matthew Tretter
8266099ae8 Clean up tests dir 2012-11-05 21:33:05 -05:00
Matthew Tretter
f9e2ce8649 Add TODO 2012-11-03 00:27:03 -04:00
Matthew Tretter
5494ee7fc1 Clarify relationship between BaseImageSpec and ImageSpec 2012-11-03 00:08:47 -04:00
Matthew Tretter
e56d687bb0 Name can be set explicitly on GeneratedImageCacheFile 2012-11-02 22:17:25 -04:00
Matthew Tretter
64d95768f8 Extract GeneratedImageCacheFile
As mentioned in #167, we want to be forward thinking and allow for a
hypothetical spec supertype which has the same functionality as an image
spec but doesn't require a source file: a generator. To this end, I've
renamed `ImageSpec.apply()` to `ImageSpec.generate()` and extracted
a `GeneratedImageCacheFile` base class from `ImageSpecCacheFile`, which
supports the more general interface of a generator--namely, a
`generate()` method with arbitrary args and kwargs.
2012-11-02 00:33:33 -04:00
Matthew Tretter
56f8d1b8bc Create form field class; re: #163 2012-10-25 22:46:28 -04:00
Matthew Tretter
6377f89e85 IKContentFile must have name attr 2012-10-25 22:43:10 -04:00
Matthew Tretter
570e7bd640 Simplify SpecHost creation 2012-10-25 22:31:37 -04:00
Matthew Tretter
76b9ebbab4 Omit missing kwargs so as not to override defaults
The default image cache strategy was being overridden, which prevented
images from being generated.
2012-10-25 22:25:18 -04:00
Matthew Tretter
006ff54fa8 Clean up version meta. 2012-10-25 20:01:26 -04:00
Eric Eldredge
9973e80a37 Add test requirements to setup 2012-10-24 23:45:51 -04:00
Matthew Tretter
fb8c411f75 Create new cache warming command
Replaces ikcachevalidate and ikcacheinvalidate, and uses the "sources"
abstraction. Closes #165
2012-10-24 23:41:05 -04:00
Matthew Tretter
84f3b6475b Add files() generator. Re: #165 2012-10-24 23:41:05 -04:00
Eric Eldredge
d27836983a Use nose to run tests
Closes #160
2012-10-24 23:30:55 -04:00
Matthew Tretter
adf143edc5 add_source -> add_sources; switch argument order 2012-10-24 22:30:10 -04:00
Matthew Tretter
bdec396180 Support inner Config class for id, sources
Closes #164
2012-10-24 22:23:53 -04:00
Matthew Tretter
8d3fcafcd9 Swap argument order for specs.register
This will allow us to put the id in the inner Meta class.
2012-10-24 22:14:45 -04:00
Matthew Tretter
606f59a102 Add docs page about configuration & optimization 2012-10-21 21:52:59 -04:00
Matthew Tretter
d110b82347 Rename imagekit_tags to imagekit 2012-10-21 17:59:56 -04:00
Matthew Tretter
3dbb96ea40 Remove unused imports 2012-10-21 17:57:53 -04:00
Matthew Tretter
fa54b9b6ef Document file-generation aspect of specs 2012-10-21 17:55:18 -04:00
Matthew Tretter
3e2c3803ff No need to call spec; the registry does that 2012-10-20 23:44:26 -04:00
Matthew Tretter
0c4d9738c6 Add TODO 2012-10-20 23:44:13 -04:00
Matthew Tretter
12493b3a0d Spec host must support kwarg "hints"
The registry's `get_spec()` was already supporting kwargs as a means to
provide information about the source to the spec constructor/factory
function, but the ``SpecHost`` class wasn't capable of accepting any.
This commit rectifies that. The main goal purpose of this is to allow a
bound field (the file attached by ``ImageSpecFileDescriptor``)--and the
attached model instance--to be taken into account during the spec
instance creation.

Related: #156
2012-10-20 23:29:07 -04:00
Matthew Tretter
aa91a70e46 Make specs know less about source files 2012-10-20 22:53:55 -04:00
Matthew Tretter
e796b4cc61 Create ImageSpec subclasses
Since the `ImageSpec` constructor will be accepting keyword arg hints,
it can no longer accept the properties.
2012-10-20 22:15:25 -04:00
Matthew Tretter
b0b466618f Separate two forms of tag; support additional html attrs
Closes #154
2012-10-17 23:41:34 -04:00
Matthew Tretter
770a8cebf4 Fix iteration error 2012-10-17 21:35:19 -04:00
Matthew Tretter
e300ce36a4 Use new registry name 2012-10-17 21:25:19 -04:00
Matthew Tretter
4f52e401d2 Handle null format 2012-10-17 21:24:57 -04:00
Matthew Tretter
98a6fff62d Add DEFAULT_FILE_STORAGE Setting; Closes #153 2012-10-17 21:09:04 -04:00
Matthew Tretter
ca1db05c4e Use named logger 2012-10-17 21:00:32 -04:00
Matthew Tretter
a265fd79e1 Correct example image URLs
Generated files are stored in the media folder, not static
2012-10-17 11:00:59 -04:00
Matthew Tretter
9df856339b Add IMAGEKIT_DEFAULT_FILE_STORAGE setting 2012-10-17 10:43:01 -04:00
Matthew Tretter
d8ce11e86e Log warning when filename doesn't match expected value 2012-10-17 01:11:05 -04:00
Matthew Tretter
806ebd75b6 Don't return file from generate()
The file is `self`
2012-10-17 00:37:02 -04:00
Matthew Tretter
ca324b7f52 Remove unused import 2012-10-17 00:32:31 -04:00
Matthew Tretter
41fa197212 Remove save kwarg--that's what generate() does! 2012-10-17 00:31:38 -04:00
Matthew Tretter
a08edaca56 Handle storage in BaseIKFile 2012-10-17 00:29:51 -04:00
Matthew Tretter
97d47c9c6c Remove generate_file. apply() does it all!
There was a lot of garbage in that method and I don't know why.
2012-10-17 00:23:38 -04:00
Matthew Tretter
77b33f757c Correct example 2012-10-17 00:07:29 -04:00
Matthew Tretter
4c4727fa9f Remove unused import 2012-10-17 00:07:29 -04:00
Matthew Tretter
63ad9e4421 Remove registration methods from template tag
The registry isn't just for template tags anymore.
2012-10-17 00:07:28 -04:00
Matthew Tretter
a93832626a Return string from render method
...instead of wrapping the file with an object that has a __unicode__
method. If you want the actual file, you should use the assignment form.
2012-10-17 00:07:23 -04:00
Matthew Tretter
9b81acd10c Don't pass removed argument 2012-10-16 23:52:31 -04:00
Matthew Tretter
5c6d1aef5d Rename ImageSpecFile
You can generate other "spec" files (using apply will get you one). This
one is for saving cache files and its name should reflect that.
2012-10-16 23:51:26 -04:00
Matthew Tretter
df8905f7e4 Remove unused imports 2012-10-16 23:39:10 -04:00
Matthew Tretter
738bbfa9a1 Move cache file naming into ImageSpecFile 2012-10-16 23:38:44 -04:00
Matthew Tretter
5ca8b7f4ba Rename process_file; don't return image object
This function will just be used to apply the spec and create a new file;
useful for transforming images in views, etc.
2012-10-16 22:59:40 -04:00
Matthew Tretter
fdc08aeeb0 Don't extend ImageFieldFile
This file isn't just for fields anymore, so we want to get rid of all
the ORM stuff.
2012-10-16 22:52:01 -04:00
Matthew Tretter
13b59ef85e Reorder methods 2012-10-16 22:33:17 -04:00
Matthew Tretter
a4ef8aa681 Add before_access signal 2012-10-16 22:30:36 -04:00
Matthew Tretter
3308c92a71 Remove SpecWrapper class
We don't need it now that we have an ImageSpec class
2012-10-16 22:23:14 -04:00
Matthew Tretter
37e0de3069 Move signals module 2012-10-16 22:10:52 -04:00
Matthew Tretter
8ef1437bea Remove apply() method 2012-10-16 22:06:24 -04:00
Matthew Tretter
1452f04cda Remove unused parameter 2012-10-16 22:03:50 -04:00
Matthew Tretter
7f6e97a37a Rename Pessimistic to JustInTime 2012-10-16 21:46:23 -04:00
Matthew Tretter
ca391fbf0a Change cache prefix 2012-10-16 21:43:17 -04:00
Matthew Tretter
80b723b510 Move IKContentFile to imagekit.files 2012-10-16 21:31:47 -04:00
Eric Eldredge
c0b79a227d Remove ImageSpecFieldFile in favor of ImageSpecFile 2012-10-15 23:53:05 -04:00
Eric Eldredge
2a33a2ad88 Fix circular import utils > imagecache.backends 2012-10-15 21:17:58 -04:00
Matthew Tretter
5fe5a73cb1 Update docs
This will be great when 3.0 is ready, but it'll
also serve as a nice guide for us as we develop.
2012-10-14 22:29:59 -04:00
Eric Eldredge
93409c8f05 SpecRegistry's spec can be callable (spec factory) 2012-10-14 21:35:07 -04:00
Eric Eldredge
461fbaef1a Processors no longer callable 2012-10-14 18:48:17 -04:00
Matthew Tretter
dc84144d6b Pass additional info with source signal 2012-10-14 16:08:48 -04:00
Matthew Tretter
7ce05f468a Pass attname to dispatch_signal
This allows any file object to be used--even those that don't have a
`field` attribute. For example, you could theoretically use one spec
field as a source for another.
2012-10-14 15:41:35 -04:00
Matthew Tretter
7447d147d4 Wire up source events to specs (and image cache strategies)
Also change signal names to past tense to match convention.
2012-10-13 01:16:05 -04:00
Matthew Tretter
2222451712 Merge branch 'sources' into ik-next 2012-10-13 00:23:00 -04:00
Matthew Tretter
cedd744e32 Add description of registry 2012-10-13 00:22:30 -04:00
Matthew Tretter
440fcb19ef Correct signal relaying 2012-10-12 23:43:51 -04:00
Matthew Tretter
a59330cf2c Add class description 2012-10-12 23:12:05 -04:00
Matthew Tretter
8a35b3a3dd Registration and connection of sources 2012-10-12 23:08:26 -04:00
Matthew Tretter
fe803f8981 Beginning to move functionality into "sources"
Before this is applied, we're going to have to make it so that the
image cache strategies are passed the source file, not the other file.
2012-10-10 00:18:54 -04:00
Matthew Tretter
06e5f45904 Remove unused imports 2012-10-04 23:43:02 -04:00
Matthew Tretter
667f0cc08e Simplify IMAGEKIT_CACHE_BACKEND setting 2012-10-04 23:41:20 -04:00
Matthew Tretter
436a73dc9a Remove image cache backend field registration 2012-10-04 23:28:55 -04:00
Matthew Tretter
5a1dd0c459 Don't require spec to be created up-front 2012-10-04 23:27:19 -04:00
Matthew Tretter
ce08448207 Rename register_spec() to set_spec_id() 2012-10-04 23:22:25 -04:00
Matthew Tretter
56c66f4883 Back ProcessedImageField with spec registry 2012-10-04 23:15:16 -04:00
Matthew Tretter
82d0e4be73 Remove unused imports 2012-10-04 23:03:22 -04:00
Matthew Tretter
f289ff3199 Back ImageSpecFields with spec registry
This marks a major step towards centralizing some of the "spec" logic
and creating a single access point for them. Because `ImageSpecFields`
are just alternative interfaces for defining and registering specs,
they can be accessed and overridden in the same manner as other specs
(like those used by template tags): via the spec registry.
2012-10-04 22:56:26 -04:00
Matthew Tretter
99ba61d605 Move spec registry 2012-10-04 22:02:29 -04:00
Matthew Tretter
116b0bc0c5 Move spec classes to specs module 2012-10-04 21:58:22 -04:00
Matthew Tretter
30ba1d890e Move exceptions 2012-10-04 21:57:33 -04:00
Matthew Tretter
0cc7938400 Re-integrate receivers module
Somewhere along the line, a change got merged that stopped using the
receivers module. This re-integrates it and moves changes made to the
old receivers (static methods on ImageSpecField) to them.
2012-10-04 21:44:55 -04:00
Matthew Tretter
d2087aa168 Create ImageSpecs; remove generators 2012-10-04 21:37:20 -04:00
Matthew Tretter
675fd02afb Merge branch 'tt' into ik-next 2012-10-04 18:55:25 -04:00
Eric Eldredge
2e5489eb56 Merge branch 'ik-next' into templatetags
Conflicts:
	imagekit/models/fields/__init__.py
	imagekit/utils.py
2012-10-04 15:46:50 -04:00
Matthew Tretter
c8778b9cfb Remove print statement 2012-10-03 22:31:45 -04:00
Matthew Tretter
ba9bf1f877 Add image cache strategies
This new feature gives the user more control over *when* their images
are validated. Image cache backends are now exclusively for controlling
the *how*. This means you won't have to write a lot of code when you
just want to change one or the other.
2012-10-03 22:23:11 -04:00
Matthew Tretter
f43bd4ec28 Include source filename in hash 2012-09-15 15:09:58 -04:00
Bryan Veloso
30db3c5bbd Merge branch 'release/2.0.2' into develop
* release/2.0.2:
  Changelog for 2.0.2.
  Bumping the version number.
2012-09-14 11:26:26 -07:00
Bryan Veloso
c33b6f2d98 Merge branch 'release/2.0.2'
* release/2.0.2: (25 commits)
  Changelog for 2.0.2.
  Bumping the version number.
  Change assertRaises for Python 2.6 compatibility
  Test correct versions of Django
  Fix docs typo; closes #147
  fix API documentation
  Change how signals are used
  Add __init__ to Reflection processor; closes #141
  Add Google Group to README
  Use django-appconf
  Allow callables for AdminThumbnail image_field arg
  Whoops! Messed that up.
  Code blocks.
  Fix pickling of ImageSpecFieldFile
  Create failing test to illustrate #97
  Remove unused stuff
  Derp, forgot to change the tox command.
  add irc channel to docs and README
  Adding a travis-ci configuration file.
  Adding basepython to the tox directives.
  ...

Conflicts:
	docs/changelog.rst
	docs/conf.py
	imagekit/__init__.py
2012-09-14 11:24:21 -07:00
Bryan Veloso
664b7d4cf4 Changelog for 2.0.2. 2012-09-14 11:22:38 -07:00
Bryan Veloso
81db33e63a Bumping the version number. 2012-09-14 11:14:46 -07:00
Eric Eldredge
ec9a1f1fda Spec templatetag returns html by default
...if no 'as var' is provided or if the var is printed directly.
2012-09-07 00:25:03 -04:00
Matthew Tretter
eb0558e144 Change assertRaises for Python 2.6 compatibility 2012-09-06 23:08:50 -04:00
Matthew Tretter
7c0511bd81 Test correct versions of Django
It looks like our versions ranges weren't correct.
2012-09-06 22:40:20 -04:00
Matthew Tretter
8a2738ca8a Add backend for caching image state 2012-09-06 22:11:23 -04:00
Matthew Tretter
2ad3791d9d Reorganize image cache backends 2012-09-06 09:11:18 -04:00
Matthew Tretter
3103ab29bd Remove "non-validating" backend
It's been superseded by the VALIDATE_ON_ACCESS setting
2012-09-06 09:10:05 -04:00
Matthew Tretter
0fc29ee7cf Extract useful backend utils 2012-09-06 09:09:30 -04:00
Matthew Tretter
197dfb3485 Add VALIDATE_ON_ACCESS setting 2012-09-06 09:08:45 -04:00
Eric Eldredge
bdecf75e0a Merge branch 'ik-next' into templatetags 2012-09-05 21:47:39 -04:00
Eric Eldredge
c50e6cea3b Merge branch 'develop' into templatetags
Conflicts:
	imagekit/utils.py
2012-09-05 21:46:59 -04:00
Matthew Tretter
e136957fc0 Fix docs typo; closes #147 2012-08-28 10:25:12 -04:00
Bryan Veloso
50cf317509 Merge pull request #144 from cyberdelia/api-docs
Fix API documentation
2012-08-13 07:52:29 -07:00
Timothée Peignier
9cc86d597e fix API documentation 2012-08-13 11:47:20 +02:00
Matthew Tretter
c8733c4707 Change how signals are used
Signals are now connected without specifying the class and non-IK
models are filtered out in the receivers. This is necessary because of
a bug with how Django handles abstract models.

Closes #126
2012-07-25 23:04:21 -04:00
Matthew Tretter
23c6e9a70e Add __init__ to Reflection processor; closes #141 2012-07-25 22:20:15 -04:00
Matthew Tretter
f3008f68b8 Add Google Group to README 2012-07-23 10:22:23 -03:00
Matthew Tretter
7ad5cf4db5 Use hashes for generated image filenames
While this change means users can no longer specify their own filenames,
changing a property of a processor, for example, will now result in a
new image. This solves a lot of the previous invalidation issues.
2012-07-19 23:48:28 -04:00
Matthew Tretter
a196e00059 Use django-appconf 2012-07-19 21:03:15 -04:00
Matthew Tretter
548fb65618 Allow callables for AdminThumbnail image_field arg
This allows images from related models to be displayed. Closes #138.
2012-07-19 20:08:03 -04:00
Bryan Veloso
70ab4a0cc0 Whoops! Messed that up. 2012-07-19 15:31:08 -07:00
Bryan Veloso
c24746ea1a Code blocks. 2012-07-19 15:29:53 -07:00
Matthew Tretter
becee54c03 Fix pickling of ImageSpecFieldFile
Code now passes the test added in 118f6e4. Hopefully this will
address #97.
2012-07-18 22:25:26 -04:00
Matthew Tretter
118f6e4206 Create failing test to illustrate #97 2012-07-18 17:21:47 -04:00
Matthew Tretter
5e1757c1ee Remove unused stuff 2012-07-18 17:21:21 -04:00
Bryan Veloso
5ae961733c Derp, forgot to change the tox command. 2012-05-29 13:15:09 -07:00
Bryan Veloso
028fb29d8d Merge pull request #132 from cyberdelia/community-docs
Add IRC channel to docs and README.
2012-05-29 13:13:54 -07:00
Timothée Peignier
f2f6766b86 add irc channel to docs and README 2012-05-29 22:05:37 +02:00
Bryan Veloso
6009bd418a Adding a travis-ci configuration file. 2012-05-29 12:58:28 -07:00
Bryan Veloso
35343eaa9d Adding basepython to the tox directives. 2012-05-29 12:57:06 -07:00
Bryan Veloso
82c7d5e475 Adding Django 1.4 to tox. 2012-05-29 12:55:51 -07:00
Matthew Tretter
c1b4c9bf71 Use cStringIO if available 2012-05-12 15:45:08 -04:00
German M. Bravo
89f2aa7a7d Reflections on images using RGBA 2012-05-12 15:39:36 -04:00
German M. Bravo
784afcc95d Simplified path join for the cache 2012-05-12 15:39:12 -04:00
Bryan Veloso
322c96eec2 Merge branch 'release/2.0.1' into develop
* release/2.0.1:
  Bumping the version number.
  Changelog update.
2012-04-28 11:32:22 -07:00
Bryan Veloso
5e4dc79f1f Merge branch 'release/2.0.1'
* release/2.0.1:
  Bumping the version number.
  Changelog update.
  Do not leak file descriptor
  Changelog link.
  Adding @jezdez to AUTHORS.
2012-04-28 11:31:34 -07:00
Bryan Veloso
78a9018337 Bumping the version number. 2012-04-28 11:31:25 -07:00
Bryan Veloso
f438f63bf8 Changelog update. 2012-04-28 11:30:09 -07:00
Matthew Tretter
ceadfb9712 Merge pull request #128 from dekkers/fix-file-descriptor-leak
Do not leak file descriptor
2012-04-28 07:24:30 -07:00
Jeroen Dekkers
69b590257d Do not leak file descriptor 2012-04-28 16:13:50 +02:00
Bryan Veloso
ea5277e7fc Changelog link. 2012-04-24 22:52:06 -07:00
Bryan Veloso
a071549ed0 Adding @jezdez to AUTHORS. 2012-04-24 14:26:57 -07:00
Bryan Veloso
74e5d1f2ba Merge branch 'release/2.0' into develop
* release/2.0:
  Introduction to image cache backends
  Some changelog corrections
  Spelling error.
  Intial pass of the 2.0 release notes.
  Bumping an version number.
2012-04-24 14:04:13 -07:00
Bryan Veloso
e68906ef56 Merge branch 'release/2.0'
* release/2.0: (127 commits)
  IKContentFile accepts format hint
  Additional mimetype utils
  Don't get extension of empty filename
  Tell people to import fields from the models module
  Refactored AutoConvert into prepare_image
  Docstring for save_image
  Kill PIL's chattiness; fixes #91
  PIL bug workaround
  Use StringIO instead of temp file
  Extract reusable save_image function
  Rename SpecFile and move it to utils
  Extract suggest_extension util from generator
  Woah, globals
  Add SpecFile.__unicode__
  Introduction to image cache backends
  Some changelog corrections
  Spelling error.
  Intial pass of the 2.0 release notes.
  Bumping an version number.
  delete file before re-generating it to avoid ill named file
  ...
2012-04-24 14:01:59 -07:00
Bryan Veloso
209afac9e3 Merge branch 'develop' into release/2.0
* develop:
  IKContentFile accepts format hint
  Additional mimetype utils
  Don't get extension of empty filename
  Tell people to import fields from the models module
  Refactored AutoConvert into prepare_image
  Docstring for save_image
  Kill PIL's chattiness; fixes #91
  PIL bug workaround
  Use StringIO instead of temp file
  Extract reusable save_image function
  Rename SpecFile and move it to utils
  Extract suggest_extension util from generator
  Woah, globals
  Add SpecFile.__unicode__
2012-04-24 13:59:28 -07:00
Matthew Tretter
89eb05668e IKContentFile accepts format hint 2012-04-20 23:30:30 -04:00
Matthew Tretter
b466ff3723 Additional mimetype utils 2012-04-20 23:27:35 -04:00
Matthew Tretter
7b34716d9e Don't get extension of empty filename 2012-04-20 23:26:58 -04:00
Matthew Tretter
af7c12cb68 Tell people to import fields from the models module 2012-04-20 21:43:59 -04:00
Matthew Tretter
667e265c94 Refactored AutoConvert into prepare_image
Because of its need to return kwargs for ``Image.save()``, it never
really fit the mold of a processor.
2012-04-20 21:37:43 -04:00
Matthew Tretter
222c9ba22a Docstring for save_image 2012-04-20 21:23:06 -04:00
Matthew Tretter
b6f629d644 Kill PIL's chattiness; fixes #91 2012-04-20 01:55:00 -04:00
Matthew Tretter
5fdf9d6a91 PIL bug workaround 2012-04-20 00:46:35 -04:00
Matthew Tretter
6f8a22c5bf Use StringIO instead of temp file 2012-04-20 00:46:33 -04:00
Matthew Tretter
e0c9708e63 Extract reusable save_image function 2012-04-20 00:46:08 -04:00
Matthew Tretter
7d5937ebe6 Rename SpecFile and move it to utils 2012-04-19 21:33:56 -04:00
Matthew Tretter
8044b97a33 Extract suggest_extension util from generator 2012-04-19 21:33:52 -04:00
Matthew Tretter
6e4a8d1b58 Woah, globals 2012-04-09 21:27:09 -04:00
Bryan Veloso
3fe63daef3 Merge pull request #123 from claymation/develop
Add SpecFile.__unicode__
2012-03-27 17:22:36 -07:00
Clay McClure
db4df4f82c Add SpecFile.__unicode__
SpecFile is based after django.core.files.base.ContentFile, which lacks a __unicode__
method. This leads to an AttributeError when SpecFile.__repr__ is called. This is
easily resolved by giving SpecFile a proper __unicode__ method.
2012-03-27 17:17:54 -04:00
Matthew Tretter
479270e498 Introduction to image cache backends 2012-03-24 19:03:24 -04:00
Matthew Tretter
8aafc3681a Some changelog corrections 2012-03-24 18:40:00 -04:00
Bryan Veloso
e7fe6d1d98 Spelling error. 2012-03-23 16:47:46 -07:00
Bryan Veloso
2d02e02b8c Intial pass of the 2.0 release notes. 2012-03-23 16:42:14 -07:00
Bryan Veloso
70f80ba7fa Bumping an version number. 2012-03-23 14:32:52 -07:00
Bryan Veloso
1c8987e353 Merge pull request #121 from cyberdelia/celery-improvements
Delete file before re-generating it
2012-03-23 13:04:22 -07:00
Timothée Peignier
39b2feda5a delete file before re-generating it to avoid ill named file 2012-03-23 12:13:53 +01:00
Timothée Peignier
bc7852be21 fix typo in celery backend 2012-03-20 20:06:41 +01:00
Bryan Veloso
c46a403baa Using Django's method of versioning. Also updating the docs' version. 2012-03-20 02:44:53 -07:00
Bryan Veloso
cad5d94661 I think this is fitting now. 2012-03-20 02:39:36 -07:00
Bryan Veloso
3d14800049 Getting rid of versiontools. 2012-03-20 02:39:28 -07:00
Bryan Veloso
f54ff8796e Merge pull request #114 from cyberdelia/cache-backends
Add celery backend and separate backends.
2012-03-15 12:46:07 -07:00
Timothée Peignier
d1af56ba3e ensure task is correctly created 2012-03-15 10:37:28 +01:00
Matthew Tretter
8ee53b9fa2 Merge pull request #117 from cyberdelia/fixing-docs
Fix documentation build
2012-03-10 12:20:09 -08:00
Timothée Peignier
9700a10d4b fix documentation build. close #56 2012-03-10 12:02:32 +01:00
Timothée Peignier
1e4ac109f7 rename async to celery 2012-03-08 19:58:36 +01:00
Timothée Peignier
7af4940914 try to import celery on init 2012-03-05 17:47:19 +01:00
Timothée Peignier
f3976a5c68 change import path 2012-03-05 17:36:08 +01:00
Timothée Peignier
983bceff62 rename CeleryCacheStateBackend as CeleryImageCacheBackend 2012-03-05 17:00:35 +01:00
Timothée Peignier
9fbdd7bef4 import base backends in imagecache module 2012-03-05 16:55:40 +01:00
Timothée Peignier
0fa86f7da8 rename celery module to async 2012-03-05 13:58:09 +01:00
Timothée Peignier
42c79d7bb2 declare celery backends in a separate module 2012-03-05 12:27:53 +01:00
Clay McClure
2cf425d8a3 Wrap ContentFile in a file-like object with a filename and content_type.
This extra layer of indirection allows us to tack some attributes
(name and content_type) onto the underlying file, which we cannot
do with a StringIO since it's a native ctype.

These attributes are used by various third-party software that expects
to work with django.core.files.File instances, and not directly with
StringIO instances. By way of example, the django-storages mosso
backend (CloudFilesStorage) looks for a content_type attribute, and
the cloudfiles Object backend looks for a name attribute.
2012-02-29 05:01:31 -05:00
Matthew Tretter
3a5d7da0d8 Validate the image any time the file is required
This means that accessing `path` or `size` will also validate,
closing #109
2012-02-23 17:46:57 -05:00
Matthew Tretter
24a2d772a6 Organized tests 2012-02-18 01:56:17 -05:00
Matthew Tretter
8fc71e689a Cover renamed to ResizeToCover 2012-02-18 01:38:19 -05:00
Matthew Tretter
2431aa2d2e Contributing note 2012-02-18 00:48:08 -05:00
Matthew Tretter
3b4e74c58b Add @version2 to contributors 2012-02-18 00:35:34 -05:00
Matthew Tretter
00c1cd3f9e Merge branch 'processor_updates' into develop 2012-02-18 00:20:59 -05:00
Matthew Tretter
e67934852d Rename processors and clean up packages 2012-02-18 00:20:44 -05:00
Matthew Tretter
3912003f02 Rename Fit and Fill to ResizeToFit and ResizeToFill 2012-02-18 00:05:18 -05:00
Matthew Tretter
5a8564d039 Rename BasicResize to Resize 2012-02-17 23:55:59 -05:00
Matthew Tretter
3fad906305 Remove crop.Crop to avoid confusion with resize.Crop 2012-02-17 23:54:39 -05:00
Matthew Tretter
3e2bd2f21f Crop processor consolidation
BasicCrop is absorbed into Crop and Crop uses ResizeCanvas
2012-02-17 23:22:56 -05:00
Matthew Tretter
a164427074 Don't use Anchor internals; allow any anchor tuple
ResizeCanvas now uses the anchor behavior of the Crop processor
2012-02-17 23:22:49 -05:00
Matthew Tretter
568c3d29a1 Default ResizeCanvas color 2012-02-17 22:39:43 -05:00
Matthew Tretter
6cca16ef99 Allow negative coordinates when using an anchor 2012-02-17 22:38:04 -05:00
Matthew Tretter
dd5efac0eb A little AddBorer cleanup 2012-02-17 22:38:00 -05:00
Matthew Tretter
441266a6d7 Small ResizeCanvas fixes 2012-02-17 22:34:40 -05:00
Jan Sagemueller
b073868bb7 AddBorder, Anchor, and ResizeCanvas processors
[NEW] Processors: AddBorder
[NEW] Processors: Anchor has now its own class, taken from Crop
[CHG] Processors: Renamed Mat => ResizeCanvas, and will now use either
an anchor from Anchor or a user defined pixel offset
2012-02-17 21:48:32 -05:00
Jan Sagemueller
09ecbae143 Resize processor: Externalized transparency padding into new class Mat
Conflicts:

	imagekit/processors/resize.py
2012-02-17 21:47:24 -05:00
Matthew Tretter
b016065807 Fix inheritance of ImageKitMeta objects
Fixes #100
2012-02-17 21:10:09 -05:00
Matthew Tretter
35b04aeb16 Abstract inheritance test; illustrates #100 2012-02-17 21:10:05 -05:00
Matthew Tretter
310fd86c21 Extract utility functions 2012-02-17 20:44:47 -05:00
Matthew Tretter
84d36791b8 Pass the pickle test; should fix #97 2012-02-17 20:07:50 -05:00
Matthew Tretter
280d01f07e Failing test to illustrate #97 2012-02-17 19:22:49 -05:00
Matthew Tretter
1bd3035f2c Extract image/model generation utils 2012-02-17 18:53:04 -05:00
Matthew Tretter
677c52b730 Rename descriptor and file to match Django 2012-02-17 18:39:51 -05:00
Matthew Tretter
6816196da7 Use "super" 2012-02-17 18:22:05 -05:00
Matthew Tretter
60c78e7424 Clarifying documentation 2012-02-17 16:08:58 -05:00
Bryan Veloso
854716aea6 Merge pull request #104 from toidi/patch-1
fix unicode error
2012-02-16 12:37:26 -08:00
Eduard Iskandarov
fa244cdcc9 fix unicode error 2012-02-16 19:47:22 +06:00
Bryan Veloso
dafbe2040d The serial must be greater than 0 for an alpha release. Fixes #101. Thanks @vesterbaek! 2012-02-15 12:57:36 -08:00
Bryan Veloso
a004071e27 We're in 2.0alpha now, really. 2012-02-14 11:18:36 -08:00
Matthew Tretter
082cf6a1bb Merge pull request #99 from cyberdelia/pep8-import-fixes
Fix import and formatting
2012-02-14 08:44:50 -08:00
Timothée Peignier
98066eea93 fix import and formatting 2012-02-14 17:34:51 +01:00
Matthew Tretter
938acedcc7 Decorator syntax for registering specs 2012-02-13 22:13:32 -05:00
Matthew Tretter
722a553501 Automatically autodiscover 2012-02-13 22:12:34 -05:00
Matthew Tretter
d275aaa3f7 A little reorganization 2012-02-13 22:12:07 -05:00
Matthew Tretter
807beef418 Centralized spec properies 2012-02-13 21:49:09 -05:00
Matthew Tretter
6a1a22825b Working proof of concept of spec tag 2012-02-13 21:49:09 -05:00
Matthew Tretter
3a3f941e2c Pull field utilities out into their own module 2012-02-13 21:47:53 -05:00
Matthew Tretter
8cdbd96a1f Add files module 2012-02-13 21:44:29 -05:00
Matthew Tretter
c2891090d1 Remove old handlers
They somehow got re-added along the way
2012-02-13 21:38:40 -05:00
Matthew Tretter
b71102e7e8 Correct indentation error 2012-02-13 21:16:45 -05:00
Matthew Tretter
88cc692568 Merge branch 'refactor_generation' into develop 2012-02-12 17:27:07 -05:00
Matthew Tretter
e71432d8ed Undo "return content" removal
I changed my mind. For now, at least.
2012-02-12 17:23:55 -05:00
Matthew Tretter
e31080ff4a Moved SpecFileGenerator to new module 2012-02-12 17:23:53 -05:00
Matthew Tretter
d9abd75d8a Pass storage to generator 2012-02-12 17:23:40 -05:00
Matthew Tretter
ff1b76f923 Remove image cache backend from generator 2012-02-12 17:23:40 -05:00
Matthew Tretter
a668b28257 Fix ProcessedImageField bug
Was using an old method signature
2012-02-12 17:23:40 -05:00
Matthew Tretter
b24fe7b8e0 Cleanup 2012-02-12 17:23:40 -05:00
Matthew Tretter
2a422f5cb6 Decouple generator from model 2012-02-12 17:23:39 -05:00
Matthew Tretter
1054d055fb Rename generate_content to process_content 2012-02-12 17:23:39 -05:00
Matthew Tretter
a7cf79290c Don't return content 2012-02-12 17:23:39 -05:00
Matthew Tretter
4ab5aadec6 Remove process method 2012-02-12 17:23:39 -05:00
Matthew Tretter
146a5ee01c Access generator through field 2012-02-12 17:23:39 -05:00
Matthew Tretter
7d6036aaac Remove cache_to from generator 2012-02-12 17:23:39 -05:00
Matthew Tretter
71c56c7d6a Separate generator 2012-02-12 17:23:39 -05:00
Bryan Veloso
8c22d11b91 Adding @madisvain to AUTHORS. 2012-02-12 14:16:38 -08:00
Matthew Tretter
fe1bfa4d1a Fix conflict resolution issues 2012-02-12 17:06:37 -05:00
Bryan Veloso
424659bd07 Merge pull request #95 from jdriscoll/new_processors
New processors
2012-02-12 13:54:12 -08:00
Bryan Veloso
5a31e98b2f Merge remote-tracking branch 'matthewwithanm/whencontrol' into develop
* matthewwithanm/whencontrol:
  Rename cache_state_backend to image_cache_backend
  Add NonValidatingCacheStateBackend
  Some documentation
  Only invalidate spec file if source changes
  Rename DefaultCacheStateBackend
  generate method (optionally) saves file
  Rename force flag to force-revalidation
  Add clear method for when future validation is unwanted
  Commands for validating and invalidating the cache
  Fix var name typo
  Spec files now accessible through _ik attr
  Remove unused import
  First shot at cache state backend implementation

Conflicts:
	imagekit/models/fields.py
2012-02-12 13:48:02 -08:00
Matthew Tretter
e9e364eedd Rename cache_state_backend to image_cache_backend
Related names (like the package and class names) have also been
updated.
2012-02-12 16:18:34 -05:00
Matthew Tretter
2838fa47cd Created "fields" module
This should give us a little more structure as IK grows.
2012-02-12 14:53:49 -05:00
Matthew Tretter
0b02a7158e Add link to readthedocs
I think people don't know we have more documentation than the
README that github is showing.
2012-02-11 16:00:11 -05:00
Matthew Tretter
cf821cfecd Documentation 2012-02-11 15:40:16 -05:00
Matthew Tretter
80a26a4f09 Rename crop function to show it's internal 2012-02-11 15:33:25 -05:00
Matthew Tretter
4278a95001 Use BasicCrop in other processors 2012-02-11 15:32:23 -05:00
Matthew Tretter
51212749e9 Improve BasicCrop API as discussed in #94 2012-02-11 15:28:47 -05:00
Matthew Tretter
15e0981835 Use BasicResize for resizing 2012-02-11 15:26:27 -05:00
Matthew Tretter
1a999f23da @madisvain's BasicResize from #94 2012-02-11 15:25:10 -05:00
Matthew Tretter
75db5db073 Default anchor to CENTER
This was accidentally lost in 88cda1b0b3.
2012-02-11 15:16:28 -05:00
Matthew Tretter
57056c021d Add SmartFill processor
The simplicity is what makes it so attractive.
2012-02-11 15:12:24 -05:00
Matthew Tretter
ce7353b0bd Extract Cover processor from Fill
This will also be used by `SmartFill`, so we might as well centralize it.
2012-02-11 15:01:59 -05:00
Matthew Tretter
88cda1b0b3 Clean up Crop processor
Also, finally allowing arbitrary anchors (specified in percentages).
2012-02-11 14:52:43 -05:00
Matthew Tretter
b4b64a4863 Avoid duplication of cropping code
`Fill` is just a resize followed by a crop, so we might as well make
use of the processors we already have.
2012-02-11 14:25:06 -05:00
Matthew Tretter
16110a0f1c Remove print statement 2012-02-11 14:13:57 -05:00
Matthew Tretter
1ffc8ca81e Remove duplicate resize.Crop
`resize.Crop` actually hasn't been moved to `crop.Crop` (as the removed
class claimed). In fact, what used to be called `resize.Crop` is now
`resize.Fill`. The confusion is understandable, since it's what
motivated the change in the first place!
2012-02-11 14:11:07 -05:00
Madis Väin
6dd0d2272f write the processors talked about in issue #93 2012-02-11 13:50:12 -05:00
Matthew Tretter
2467dfe912 Rename resize.Crop to resize.Fill 2012-02-11 13:36:30 -05:00
Matthew Tretter
c3ef5172c3 Rename ImageSpec to ImageSpecField
The ImageSpec class remains for now, but using it throws
deprecation warnings.
2012-02-11 13:06:48 -05:00
Matthew Tretter
15b15afe2c Add NonValidatingCacheStateBackend 2012-02-11 00:42:56 -05:00
Matthew Tretter
164a4e11fc Some documentation 2012-02-11 00:15:58 -05:00
Matthew Tretter
1956e16b4b Only invalidate spec file if source changes 2012-02-11 00:04:56 -05:00
Matthew Tretter
60a8c06336 Rename DefaultCacheStateBackend
PessimisticCacheStateBackend more accurately describes what is is,
instead of how it's used.
2012-02-10 22:08:13 -05:00
Matthew Tretter
c2a4d01b7c Only one return 2012-02-10 22:04:20 -05:00
Matthew Tretter
09b97ee62f Move SmartCrop to crop module
Addresses one of the issues raised by in @madisvain in #93
2012-02-10 19:42:36 -05:00
Jan Sagemueller
a041302c96 Resize processor: Add mat_color option to enforce the targeted image size 2012-02-08 09:25:37 -05:00
Matthew Tretter
35b807cfa9 generate method (optionally) saves file
This way, there's a creation counterpart to `delete()`. The user
shouldn't have to deal with storage backends to create and delete the
files, and now they don't.
2012-02-03 09:16:50 -05:00
Matthew Tretter
513b23b169 Rename force flag to force-revalidation 2012-02-02 23:23:20 -05:00
Matthew Tretter
011c0c2e5f Add clear method for when future validation is unwanted 2012-02-02 23:21:00 -05:00
Matthew Tretter
8b6ba687e4 Commands for validating and invalidating the cache 2012-02-02 00:19:46 -05:00
Matthew Tretter
c4fc09c688 Fix var name typo 2012-02-02 00:08:22 -05:00
Matthew Tretter
a668df1c37 Spec files now accessible through _ik attr 2012-02-01 23:26:39 -05:00
Matthew Tretter
a1638127cf Remove unused import 2012-02-01 22:41:33 -05:00
Matthew Tretter
62ff07bf1f First shot at cache state backend implementation 2012-02-01 22:37:58 -05:00
Timothée Peignier
eb801d75f5 small pep8 formatting fix 2012-01-27 13:45:22 +01:00
Matthew Tretter
343f14ad48 Use old-style string formatting 2012-01-26 18:19:39 -05:00
Bryan Veloso
8447064936 Adding @cyberdelia to AUTHORS. 2012-01-26 13:34:54 -08:00
Timothée Peignier
3bac472561 ImageSpec accepts callable processors arg 2012-01-26 11:01:58 -05:00
Bryan Veloso
eca968599d Merge pull request #83 from richleland/tox
Added config file for tox. Closes #31.
2012-01-06 18:16:19 -08:00
Rich Leland
c384672f5d Added tox config file. 2012-01-06 21:08:41 -05:00
Rich Leland
833af52734 Ignore tox files, test media folder. 2012-01-06 21:03:16 -05:00
Bryan Veloso
f882e226a6 Wrong theme name? 2012-01-06 02:07:25 -08:00
Bryan Veloso
49da2b22f6 Adding @kennethreitz's Sphinx style. 2012-01-06 01:52:56 -08:00
Bryan Veloso
cf8ec71e00 reST slays me. :( 2012-01-06 01:52:56 -08:00
Matthew Tretter
6333ee5d05 Makes Adjust transparency-compatible
And closes #64!
2012-01-03 18:57:32 -05:00
Bryan Veloso
c5c0402c89 Merge branch 'release/1.1.0' into develop
* release/1.1.0:
  Adding (crude) changelog information for 1.1.0.
  Bumping the version number.
  Removing Pillow from requirements.txt and adding a note about PIL/Pillow to the README. References #72.
  Being a bit friendlier to Python 2.5 users... or is it 2.4. Fixes #82.
2011-12-22 22:18:51 -08:00
Bryan Veloso
0656292bef Merge branch 'release/1.1.0'
* release/1.1.0: (28 commits)
  Adding (crude) changelog information for 1.1.0.
  Bumping the version number.
  Removing Pillow from requirements.txt and adding a note about PIL/Pillow to the README. References #72.
  Being a bit friendlier to Python 2.5 users... or is it 2.4. Fixes #82.
  Change handling of spec deletion
  No need to check for None
  Catch KeyError on image.info dictionary
  Using difference instead of subtract
  Indentation tweak
  Makes evaluation of `source_file` lazy
  Test illustrating #75, #74
  Separate create_photo method
  avoid Transpose to crash when exif data doesn't exists
  Allows `None` value for `processors` argument
  Remove _Resize class
  Adds crop module to docs
  Explicitly import crop module
  Renames processor to `TrimBorderColor`
  Gives precedence to user options
  Replaces `quality` argument with `options` dict
  ...
2011-12-22 22:18:31 -08:00
Bryan Veloso
68763fa5b1 Adding (crude) changelog information for 1.1.0. 2011-12-22 22:14:32 -08:00
Bryan Veloso
3bacd51ba5 Bumping the version number. 2011-12-22 22:14:14 -08:00
Bryan Veloso
2a275f094c Removing Pillow from requirements.txt and adding a note about PIL/Pillow to the README. References #72. 2011-12-22 21:58:42 -08:00
Bryan Veloso
c16b008a34 Being a bit friendlier to Python 2.5 users... or is it 2.4. Fixes #82. 2011-12-22 21:48:15 -08:00
Matthew Tretter
e574f1190f Merge pull request #73 from cyberdelia/fix-transpose-exif
Catch error when exif data doesn't exist
2011-12-07 21:37:48 -08:00
Matthew Tretter
dd28557323 Change handling of spec deletion
Though it's not the case with `FileSystemStorage`, attempting to delete
a nonexistent file can cause an exception (as in #80). We want to avoid
these errors but don't want to suppress others raised in the process of
deleting the file, so we first check to see if the file exists.
2011-12-07 00:52:06 -05:00
Matthew Tretter
081a0a0d0f No need to check for None
`get_spec_files` will never return it.
2011-12-07 00:37:52 -05:00
Kevin Postal
eda3fc3c91 Catch KeyError on image.info dictionary 2011-12-06 23:29:30 -05:00
Matthew Tretter
8bab75b331 Merge pull request #66 from matthewwithanm/trimcolor-processor
Processor for trimming solid-color borders
2011-12-06 10:04:39 -08:00
Matthew Tretter
358bb1f6b0 Using difference instead of subtract
This corrects the border removal, however, I'm not certain whether the
interpretation of "tolerance" gels with expectations.
2011-12-06 12:56:50 -05:00
Matthew Tretter
96878105e9 Indentation tweak 2011-12-02 02:46:27 -05:00
Matthew Tretter
eb425fbae9 Makes evaluation of source_file lazy
This paves the way for future updates that will fix bugs related to
specs not reflecting updates to their source image fields.
2011-12-02 02:28:08 -05:00
Matthew Tretter
d036343adc Test illustrating #75, #74 2011-12-02 02:19:27 -05:00
Matthew Tretter
1137487689 Separate create_photo method 2011-12-02 02:19:12 -05:00
Timothée Peignier
54d5237adc avoid Transpose to crash when exif data doesn't exists 2011-11-28 16:00:34 +01:00
Matthew Tretter
ee1fb7f943 Allows None value for processors argument
Fixes #70
2011-11-23 19:34:59 -05:00
Matthew Tretter
5ecdeb1209 Remove _Resize class
It once had a purpose, but it hasn't for a while.
2011-11-22 23:41:18 -05:00
Matthew Tretter
5579c8db3c Adds crop module to docs 2011-11-22 23:12:54 -05:00
Matthew Tretter
25be1f66ca Explicitly import crop module
So you can `import crop from imagekit.processors`
2011-11-16 15:17:11 -05:00
Matthew Tretter
ed64032762 Renames processor to TrimBorderColor 2011-11-16 15:16:24 -05:00
Chris Drackett
b89bc96e4b Merge pull request #68 from matthewwithanm/options-arg
Replaces `quality` argument with `options` dict
2011-11-16 08:36:37 -08:00
Matthew Tretter
62d39ccf9e Gives precedence to user options
Previously, options set by `AutoConvert` had higher precedence than
those explicitly passed by the user. This corrects that.
2011-11-16 10:27:18 -05:00
Matthew Tretter
788365d6db Replaces quality argument with options dict
The `options` argument for `ImageSpec` and `ProcessedImageField`
replaces `quality` and provides a more general solution, allowing the
user access to PIL's format-specific options (including "quality",
"progressive", and "optimize" for JPEGs).
2011-11-16 10:00:35 -05:00
Matthew Tretter
cfd503a2eb TrimColor processor 2011-11-16 00:11:05 -05:00
Bryan Veloso
8c64307e5a Moving Matt to maintainers. 2011-11-11 16:20:13 +09:00
Bryan Veloso
25a7536a26 Adding Alexander Bohn (@fish2000) to AUTHORS. 2011-11-11 16:19:28 +09:00
FI$H 2000
7ce43309ad Adds SmartCrop resize processor, with tests. 2011-11-10 10:54:58 -05:00
Bryan Veloso
2dad6fb88d Adding a requirements.txt file for ReadTheDocs' sake. 2011-11-10 16:05:09 +09:00
Bryan Veloso
882cf87ec3 No longer need this and nothing's pointing to it anyway. 2011-11-10 16:04:49 +09:00
Bryan Veloso
3bd7b59046 Fixing some spelling errors, thanks @ptone. 2011-11-10 15:51:48 +09:00
Bryan Veloso
78709be23c We're at 1.1.0dev, for real this time. 2011-11-10 15:39:47 +09:00
Bryan Veloso
99120ef63b Merge branch 'release/1.0.3' into develop
* release/1.0.3:
  Bumping version numbers.
  Changelog for 1.0.3.
2011-11-10 15:30:53 +09:00
Bryan Veloso
c16292e372 Merge branch 'release/1.0.3'
* release/1.0.3:
  Bumping version numbers.
  Changelog for 1.0.3.
  Reset file pointer before opening image. Fixes #47
  Rename `_create()` to `generate()`
  AutoConvert processor
  Smarter transparency handling
  Change how we set `ImageFile.MAXBLOCK`
  RGBA is a transparent image mode too . When saving RGBA, you cannot specify 'transparency' color - you get IOError wrong mode.
  Increase MAXBLOCK on save failure
  avoid Transpose to crash when there is no exif data
  Try to better handle being passed a open file with write mode enabled
  `format_to_extension` correctly handles `None` arg
  Proper handling of empty images and missing fields
  Fixes error message
2011-11-10 15:30:48 +09:00
Bryan Veloso
83cc7f4cc3 Bumping version numbers. 2011-11-10 15:30:01 +09:00
Bryan Veloso
6f8ea4911c Changelog for 1.0.3. 2011-11-10 15:28:40 +09:00
Matthew Tretter
7e5491cc3e Reset file pointer before opening image. Fixes #47 2011-11-08 20:53:48 -05:00
Matthew Tretter
cfa3d01f54 Rename _create() to generate()
After #51 presented a good use case, we decided to make this part of
the API (i.e. remove the underscore). The default value for the `lazy`
kwarg is changed to `True` to reduce the likelihood of accidental
regeneration of images. Closes #54.
2011-11-07 23:07:09 -05:00
Matthew Tretter
8c9a9b8d5c AutoConvert processor
At first, we only had a small amount of code for preserving
transparency in `img_to_fobj`, but as that code grew it became apparent
that it really didn't belong in the utility function. This commit
creates an `AutoConvert` processor which is automatically run before
saving unless you disable it by passing `autoconvert=False` to your
`ImageSpec` or `ProcessedImageField` constructor. `img_to_fobj` is once
again a simple utility function for creating a file object from a PIL
image.
2011-11-06 21:44:31 -05:00
Matthew Tretter
f4b9d74335 Smarter transparency handling
The in-code comments explain it pretty thoroughly but, in short, this
handles a lot more of the situations in which you're converting to or
from formats that support transparency.
2011-11-06 01:19:32 -05:00
Matthew Tretter
8911836b53 Change how we set ImageFile.MAXBLOCK
Previously, we set it to an arbitrary large number (which turned out
not to be large enough in some cases). Since we may have been
overriding something the user explicitly chose (and our value may be
overridden by another app), that probably wasn't good. After this
change, `MAXBLOCK` is only increased when it needs to be--and even
then, only temporarily.
2011-11-05 21:20:36 -04:00
Matthew Tretter
7ab1d08e49 Merge pull request #52 from islavov/patch-1
FIX: RGBA mode breaks transparency check
2011-11-05 11:55:10 -07:00
iu_long
ae8a3b8ef8 RGBA is a transparent image mode too . When saving RGBA, you cannot specify 'transparency' color - you get IOError wrong mode. 2011-11-05 18:25:17 +02:00
Matthew Tretter
dd7ae5a8e4 Increase MAXBLOCK on save failure
Under certain circumstances, saving a JPEG may fail if MAXBLOCK isn't
large enough, so we (temporarily) increase it. This should fix #50.
2011-11-04 22:43:41 -04:00
Bryan Veloso
486fb51a1e Merge pull request #49 from cyberdelia/fix-empty-exif-data
Avoid Transpose to crash when there is no exif data.
2011-11-04 04:50:19 -07:00
Bryan Veloso
e2796b5984 Merge pull request #48 from ptone/open_image_improvement
Open image improvement. Closes #47.
2011-11-04 04:49:27 -07:00
Timothée Peignier
a0f2db4d29 avoid Transpose to crash when there is no exif data 2011-11-04 12:00:19 +01:00
ptone
18349a053f Try to better handle being passed a open file with write mode enabled
such as would be the case with tempfile.mkstemp etc
2011-11-04 00:11:22 -07:00
ptone
115baa9a97 Merge branch 'develop' of git://github.com/jdriscoll/django-imagekit into develop
* 'develop' of git://github.com/jdriscoll/django-imagekit:
  `format_to_extension` correctly handles `None` arg
  Proper handling of empty images and missing fields
  Fixes error message
2011-11-03 23:36:32 -07:00
Matthew Tretter
dd642fd05b format_to_extension correctly handles None arg 2011-11-03 15:00:40 -04:00
Matthew Tretter
3022eb037d Proper handling of empty images and missing fields
Fixes #42
2011-11-03 12:33:22 -04:00
Matthew Tretter
bbc0bffd88 Fixes error message
Arguments were in the wrong order. Also, shows class name in string
representation of instance isn't helpful.
2011-11-03 12:26:23 -04:00
Bryan Veloso
2cb8d5d407 Merge branch 'release/1.0.2' into develop
* release/1.0.2:
  Fixing some reST syntax.
  Bumping version numbers.
2011-11-03 14:20:01 +09:00
Bryan Veloso
3fd2450e8d Merge branch 'release/1.0.2'
* release/1.0.2:
  Fixing some reST syntax.
  Bumping version numbers.
  Adding a changelog.
  Basic tests for extension_to_format and format_to_extension. References #45.
  Fix in `format_to_extension` mapping
  On-demand loading of PIL plugins
  Fix conversion of PNG "palette" or "P" mode images to JPEG. "P" mode images need to be converted to 'RGB' if target image format is not PNG or GIF.
  Back to dev.
2011-11-03 14:19:20 +09:00
Bryan Veloso
5666a640f0 Fixing some reST syntax. 2011-11-03 14:16:08 +09:00
Bryan Veloso
44392b7b24 Bumping version numbers. 2011-11-03 14:15:03 +09:00
Bryan Veloso
026955711e Adding a changelog. 2011-11-03 14:12:18 +09:00
Bryan Veloso
0f7c400239 Basic tests for extension_to_format and format_to_extension. References #45. 2011-11-03 02:57:01 +09:00
Matthew Tretter
f1b1f2ec71 Fix in format_to_extension mapping 2011-11-02 12:21:45 -04:00
Matthew Tretter
14bac58373 On-demand loading of PIL plugins 2011-11-02 11:26:24 -04:00
Bryan Veloso
dbc225466a Merge pull request #43 from deepakprakash/develop
Fix conversion of PNG "palette" or "P" mode images to JPEG. Fixes #41.
2011-11-02 02:24:48 -07:00
deepakprakash
d36e624acb Fix conversion of PNG "palette" or "P" mode images to JPEG.
"P" mode images need to be converted to 'RGB' if target image format is not PNG or GIF.
2011-11-02 14:25:22 +05:30
Bryan Veloso
5985def06b Back to dev. 2011-11-02 13:49:17 +09:00
Bryan Veloso
58a1f35425 Merge branch 'release/1.0.1' into develop
* release/1.0.1:
  Bumping docs to 1.0.1.
  Bumping version to 1.0.1.
2011-11-02 13:48:06 +09:00
Bryan Veloso
d9a7640d56 Merge branch 'release/1.0.1'
* release/1.0.1:
  Bumping docs to 1.0.1.
  Bumping version to 1.0.1.
  Making sure the templates make it via pip. Fixes #39.
  Generated filename is cached
  Fixes extension guessing based on image format
  No longer an alpha.
  Including README.rst and the docs in MANIFEST.in. Fixes #40.
  Develop is now v1.1.0.dev.
  Slightly updated README to correctly render as reStructuredText.
2011-11-02 13:47:32 +09:00
Bryan Veloso
3985629b9a Bumping docs to 1.0.1. 2011-11-02 13:46:23 +09:00
Bryan Veloso
2e27e82832 Bumping version to 1.0.1. 2011-11-02 13:45:10 +09:00
Bryan Veloso
023f8592d7 Making sure the templates make it via pip. Fixes #39. 2011-11-02 13:42:35 +09:00
Matthew Tretter
8885325bfc Generated filename is cached 2011-11-01 19:39:54 -04:00
Matthew Tretter
892cce7c7f Fixes extension guessing based on image format
IK relies on PIL's EXTENSION list to map formats to extensions.
However, this list normally isn't populated until an image is loaded.
This change forces the population of the list before examining it.
2011-11-01 18:54:18 -04:00
Bryan Veloso
4b86e37f93 No longer an alpha. 2011-11-01 01:54:31 +09:00
Bryan Veloso
a2296a382b Including README.rst and the docs in MANIFEST.in. Fixes #40. 2011-11-01 01:37:29 +09:00
Bryan Veloso
28f4dd0103 Develop is now v1.1.0.dev. 2011-11-01 00:44:29 +09:00
Matthew Tretter
72ba73a431 Merge pull request #38 from jezdez/develop
Slightly updated README to correctly render as reStructuredText.
2011-10-31 08:27:03 -07:00
Jannis Leidel
4555df03d0 Slightly updated README to correctly render as reStructuredText. 2011-10-31 16:24:29 +01:00
Bryan Veloso
11c1259ba3 Merge branch 'release/1.0.0' into develop
* release/1.0.0: (68 commits)
  Tests now run again.
  PEP8-ing and whitespacing.
  Requiring versiontools and patching up our setup.py.
  adding introspection rule for users with south
  PEP8 and import tweaks.
  fixing typo
  ImageSpecFile is a proper File
  Typo fix
  A list of ImageSpec names are now stored on the model.
  AdminThumbnailView is now AdminThumbnail
  Docs typo fix
  Adds explicit import of resize module to processors
  fixing bad import in docs
  adding name to AUTHORS
  adding test for new api
  Moved Crop and Fit to resize module.
  More docs edits.
  Typo fix.
  Installation instructions.
  Embracing duck typing.
  ...

Conflicts:
	README.rst
2011-10-31 23:32:39 +09:00
Bryan Veloso
7e55f5e087 Merge branch 'release/1.0.0'
* release/1.0.0: (68 commits)
  Tests now run again.
  PEP8-ing and whitespacing.
  Requiring versiontools and patching up our setup.py.
  adding introspection rule for users with south
  PEP8 and import tweaks.
  fixing typo
  ImageSpecFile is a proper File
  Typo fix
  A list of ImageSpec names are now stored on the model.
  AdminThumbnailView is now AdminThumbnail
  Docs typo fix
  Adds explicit import of resize module to processors
  fixing bad import in docs
  adding name to AUTHORS
  adding test for new api
  Moved Crop and Fit to resize module.
  More docs edits.
  Typo fix.
  Installation instructions.
  Embracing duck typing.
  ...

Conflicts:
	README.rst
2011-10-31 23:30:09 +09:00
Bryan Veloso
f47c4f9e8b Tests now run again. 2011-10-31 23:23:50 +09:00
Bryan Veloso
06c1c678b6 PEP8-ing and whitespacing. 2011-10-31 23:12:03 +09:00
Bryan Veloso
a45f3af2a5 Requiring versiontools and patching up our setup.py. 2011-10-31 23:01:41 +09:00
Bryan Veloso
6c87d7deac Merge branch 'release/0.4.1' into develop
* release/0.4.1:
  Discontinuation notice.
2011-10-31 22:43:13 +09:00
Bryan Veloso
1900248842 Merge branch 'release/0.4.1'
* release/0.4.1:
  Discontinuation notice.
  Future-proofing our PIL imports, fixes #37.
  Whitespacing and PEP8-ing.
  Clearing out some unused imports.
  We no longer import *.
  Prefer cStringIO.
  Killing off some stray tabs.
  Changed id to pk in case the primary key isn't called 'id'
  Adding Ryan to AUTHORS.
  dynamically getting colors, instead of setting it to 255
  adding transparency patch to preserve transparency when saving the image
  Making versioning a little easier on myself.
  error fix: https://github.com/jdriscoll/django-imagekit/issues/25
  Fixing some spelling. Fixes #29. Thanks Nai!
  Tests now pass and don't leave any directory cruft. Fixes #28.
  Adding a test runner script a la #daniellindsleyrocksdahouse.
  Creating a "core" test module.
  Creating a dedicated tests directory.
  Let's get these docs started.
  Added support for preserving transparency when converting from color to greyscale
2011-10-31 22:42:24 +09:00
Bryan Veloso
47fac1cb07 Discontinuation notice. 2011-10-31 22:41:43 +09:00
Chris McKenzie
714ff8ae1d adding introspection rule for users with south 2011-10-24 18:18:25 -04:00
Bryan Veloso
6adadafc74 PEP8 and import tweaks. 2011-10-20 12:12:47 +09:00
Bryan Veloso
302399d837 Merge branch 'new_api' of https://github.com/matthewwithanm/django-imagekit into release/1.0.0
* 'new_api' of https://github.com/matthewwithanm/django-imagekit: (63 commits)
  fixing typo
  ImageSpecFile is a proper File
  Typo fix
  A list of ImageSpec names are now stored on the model.
  AdminThumbnailView is now AdminThumbnail
  Docs typo fix
  Adds explicit import of resize module to processors
  fixing bad import in docs
  adding name to AUTHORS
  adding test for new api
  Moved Crop and Fit to resize module.
  More docs edits.
  Typo fix.
  Installation instructions.
  Embracing duck typing.
  Documentation!
  Fix bug
  Bound fields are now cached on the model instance.
  Transpose processor now supports auto EXIF orientation.
  Fix filename formatting.
  ...

Conflicts:
	AUTHORS
	README.rst
	docs/Makefile
	docs/conf.py
	docs/index.rst
	imagekit/__init__.py
	imagekit/defaults.py
	imagekit/management/commands/ikflush.py
	imagekit/models.py
	imagekit/options.py
	imagekit/processors.py
	imagekit/specs.py
	tests/core/tests.py
2011-10-20 12:06:10 +09:00
Chris McKenzie
79a6e8f1ab fixing typo 2011-10-17 10:27:29 -04:00
Bryan Veloso
56d8b80a12 Future-proofing our PIL imports, fixes #37. 2011-10-13 14:35:16 +09:00
Matthew Tretter
8147cb85df ImageSpecFile is a proper File
Previously, ImageSpecFile was a file-like object, but didn't actually
extend any of the file classes. Because of this, some of Django's file-
handling code was duplicated. More importantly, instances didn't always
behave as one might expect (if one were familiar with ImageFields),
particularly when the source image was empty. This could have been
especially confusing in templates. (For example, because
ImageSpecFields whose source images didn't exist were still truthy.)
2011-10-10 17:22:06 -04:00
Matthew Tretter
7f7141ef27 Typo fix 2011-10-10 17:21:59 -04:00
Bryan Veloso
2d4f116e57 Whitespacing and PEP8-ing. 2011-10-11 02:16:22 +09:00
Bryan Veloso
f0269831fd Clearing out some unused imports. 2011-10-11 02:13:31 +09:00
Bryan Veloso
0ddff78068 We no longer import *. 2011-10-11 02:10:38 +09:00
Bryan Veloso
5e72d9f5e4 Prefer cStringIO. 2011-10-11 02:08:05 +09:00
Bryan Veloso
db86d7ed66 Killing off some stray tabs. 2011-10-11 01:12:30 +09:00
Bryan Veloso
00049abc95 Merge pull request #36 from jammons/develop
Changed id to pk in case the primary key isn't called 'id'
2011-10-10 00:37:18 -07:00
Jeff Ammons
2a37d7bfdc Changed id to pk in case the primary key isn't called 'id' 2011-10-06 16:15:26 -07:00
Eric Eldredge
b8e57dccd6 A list of ImageSpec names are now stored on the model.
Previously, ImageSpecFile instances were retrieved (for saving and deleting,
among other possibilities) by iterating over the model instance's attributes.
This change adds ImageSpecFile names to a list (spec_file_names) on an
imagekit meta object on the model (_ik), making later retrieval much cheaper
and more straightforward.
2011-10-06 16:08:14 -04:00
Matthew Tretter
b9aa69e0c0 AdminThumbnailView is now AdminThumbnail
I never liked that the "AdminThumbnailView" was supposed to be defined
on the model, but never looked into it.

This commit puts the definition back where it belongs: in the admin.
Instead of requiring you to add a field (with view logic) to your
model, you now just add a property to your admin class and specify
that property in the `list_display` list.
2011-10-02 21:58:08 -04:00
Matthew Tretter
067217e40e Docs typo fix 2011-09-26 16:52:35 -04:00
Eric Eldredge
67477a6e15 Adds explicit import of resize module to processors
This way users can write 'from imagekit.processors import *' and also use the
resize processors like so: 'resize.Crop(50, 50)'
2011-09-26 16:45:45 -04:00
Chris McKenzie
492febf7ec fixing bad import in docs 2011-09-26 16:40:38 -04:00
Chris McKenzie
9e9665f626 adding name to AUTHORS 2011-09-26 16:39:45 -04:00
Chris McKenzie
6412e40c69 adding test for new api 2011-09-26 16:38:55 -04:00
Matthew Tretter
da00e2a5da Moved Crop and Fit to resize module.
Crop doesn't necessarily imply the any scaling is taking place. Several
ideas were discussed, from renaming Crop to combining both processors
into a single Resize processor (as they were in the original IK), but
those solutions were felt to either precluded future extension
(alternative resize modes) or make the API too verbose.
2011-09-26 14:40:48 -04:00
Matthew Tretter
6751d78504 More docs edits.
Added us to the authors. Reorganized some of the documentation so that
sphinx and the project landing page on github can share it.
2011-09-26 11:36:08 -04:00
Bryan Veloso
6efd21e9b4 Adding Ryan to AUTHORS. 2011-09-26 12:07:37 +02:00
Bryan Veloso
cec46b3176 Merge pull request #32 from ryanbagwell/develop
Trasparency preservation patch
2011-09-26 03:06:04 -07:00
Matthew Tretter
9b9d9ec1c4 Typo fix. 2011-09-26 00:06:05 -04:00
Matthew Tretter
32c6f0c05f Installation instructions. 2011-09-25 23:38:23 -04:00
Matthew Tretter
6adbef3475 Embracing duck typing.
ImageProcessor didn't do anything. I'd rather get it out of there to reduce the
temptation for future IK contributors to do type checking and mess up peoples'
custom processors.
2011-09-25 23:24:44 -04:00
Matthew Tretter
d6632c95f5 Documentation! 2011-09-25 21:04:11 -04:00
Ryan Bagwell
ff07e4e247 dynamically getting colors, instead of setting it to 255 2011-09-25 17:53:51 -05:00
Ryan Bagwell
b96a3f53a5 adding transparency patch to preserve transparency when saving the image 2011-09-25 15:49:32 -05:00
Matthew Tretter
51144daeb6 Fix bug
Eric's fixes in c00ea10b0a meant that
some of the code that already existed to reuse objects would actually
be run for the first time. Naturally, there were some bugs; namely, I
was storing a filename (instead of a File object) in `_file` and a bad
else clause.
2011-09-25 16:18:52 -04:00
Eric Eldredge
c00ea10b0a Bound fields are now cached on the model instance.
ImageSpecFile and AdminThumbnailView are created on the first access, and
then assigned as properties of the model instance for subsequent access.
2011-09-25 12:42:27 -04:00
Eric Eldredge
f570bd0d7f Transpose processor now supports auto EXIF orientation.
Replaced calls to Image.open with an open_image utility function.
The open_image utility calls Image.open, but then wraps the opened Image's
copy method with a version that preserves EXIF data. This allows an
ImageSpec to copy the original image, yet still provide all the original
image's exif data to the processor pipeline.
2011-09-24 23:09:49 -04:00
Matthew Tretter
c694ba644f Fix filename formatting. 2011-09-24 02:04:23 -04:00
Matthew Tretter
6ae4f56ed7 Eliminate repetition in File objects. 2011-09-24 02:04:23 -04:00
Matthew Tretter
f9c4d6b500 Cleanup.
Centralized access of _img, tried to reduce re-calculation of some
properties, renamed _imgfield to source_file to reflect the fact that
it's an ImageFieldFile and not an ImageField.
2011-09-24 02:04:23 -04:00
Matthew Tretter
cc96ba5198 Moved _get_imgfile() into _create()
That was the only place it was being called and it just led to (my)
confusion with the image property.
2011-09-24 02:04:23 -04:00
Eric Eldredge
8a0bc084fe Processors no longer take a file argument.
They only get the image to process now.
2011-09-23 21:25:47 -04:00
Eric Eldredge
d99bf5327b Transpose processor has a new API.
Transpose now takes transposition constants as arguments. Multiple
transpositions can be sequenced together in one Transpose processor.
Auto transposition is not yet supported (PIL strips the EXIF data, so need to
find a workaround for getting that data to the processor).
2011-09-23 21:23:21 -04:00
Matthew Tretter
7e20b75ced Processors don't care about format.
The process of choosing an image format has been cleaned up and
Processors' role in determining the format has been removed.

Previously, processors would return a tuple containing the modified
image and the format. Other parts of IK overrode PIL's Image.format
with the target format, although that had no effect on PIL and the fact
that it didn't throw an error was just lucky.
2011-09-23 20:18:51 -04:00
Matthew Tretter
15e49be719 Extracted ProcessorPipeline
Pulled some functionality out of _ImageSpecMixin into the ProcessorPipeline
class so it could be used independently of the model-related stuff.
2011-09-23 19:12:04 -04:00
Matthew Tretter
e190e78df5 Rename fields module to models. 2011-09-23 18:06:28 -04:00
Matthew Tretter
3f7ca512af Extension argument to cache_to includes dot.
It seems that's how we do it in python world. Who am I to argue?
2011-09-23 12:44:53 -04:00
Eric Eldredge
77459eae73 Pared down the _post_save_handler.
The original handler implementation ported code from the old ImageModel's
save method, but ended up duplicating the efforts of the ImageSpecFile's
_create method.
2011-09-22 19:44:47 -04:00
Matthew Tretter
3d810e7be5 Rename ImageSpecFile properties
`_obj` and `_spec` are now `instance` and `field`, to match FieldFile.
2011-09-22 18:13:32 -04:00
Matthew Tretter
0ef56e1aaa process() accepts file
In the old IK API, processors (like `Transpose`) were able to access
the file by inspecting the model instance (which carried an options
object that specified the attribute name of the ImageField from which
the file could be extracted). Since the new API allows for multiple
ImageFields (and because IKOptions have been removed), it became
necessary to provide more information. Initially, this was accomplished
by passing the spec to `process()`, however with the addition of
ProcessedImageField, it became clear the a cleaner solution was to pass
only the field file (ImageSpecFile or ProcessedImageFieldFile).

This keeps the ORM stuff (fields, etc.) out of the `ImageProcessor` API
but (because field files, not just regular files, are passed) the
average hacker can still have their processor make use of model
information by accessing the model through the file's `instance`
property.
2011-09-22 17:58:33 -04:00
Matthew Tretter
bd7eb7284b BoundImageSpec is now ImageSpecFile
In preparation for unifying the ImageSpecFile and
ProcessedImageFieldFile interfaces.
2011-09-22 09:20:37 -04:00
Matthew Tretter
5718c304cf ProcessedImageField replaces preprocessor_spec 2011-09-22 00:24:13 -04:00
Matthew Tretter
98a5ca32b4 Fix bug when imgfield doesn't exist. 2011-09-21 23:10:06 -04:00
Matthew Tretter
a60dfba31d IKOptions no longer exists. 2011-09-21 21:15:16 -04:00
Matthew Tretter
788257b819 property_name is now attname
That's what Django calls it; that's what we'll call it.
2011-09-21 21:15:16 -04:00
Matthew Tretter
fb53981ec8 No need to extend ImageModel.
In fact, ImageModel doesn't exist anymore. Most of IKOptions have also been
removed.
2011-09-21 21:15:09 -04:00
Matthew Tretter
7167016237 Removed specs list from opts. 2011-09-21 20:21:48 -04:00
Matthew Tretter
305d20569c Moved get_bound_specs to utils. 2011-09-21 20:17:23 -04:00
Matthew Tretter
4e23254e73 Crop processor accepts anchor argument. 2011-09-21 19:02:18 -04:00
Matthew Tretter
e4c4fe02b3 Separated Crop.process() and Fit.process()
They didn't have enough in common to warrant them being branches of the
same method.
2011-09-21 18:24:02 -04:00
Matthew Tretter
b5616d2f75 Move ImageSpec to fields module. 2011-09-21 11:37:29 -04:00
Eric Eldredge
fe2fb844af Changed the way post_save and post_delete signals are being handled.
One handler is created per model instead of per bound image spec.
This cuts down on the number of handlers created, and also offloads the
policing of the handlers in memory to the signal framework. Since they are no
longer being created per spec, the handlers can be weakly referenced.
2011-09-21 10:44:05 -04:00
Eric Eldredge
b1c5432310 Implemented post_save and post_delete handlers for ImageSpecs.
Removed the save and clear_cache methods from ImageModel (along with helpers).
Now, whenever an ImageSpec is contributed to a model, handlers are created for
the post_save and post_delete signals. The post_save handler does the work of
running the ImageSpec processors and caching the resulting file, while the
post_delete handler does the work cleaning up the cached files.
2011-09-21 09:56:27 -04:00
Matthew Tretter
34e475885b Unbound fields are accessible from class. 2011-09-21 09:52:38 -04:00
Matthew Tretter
2770be23ea Naming & implementation of bound fields are more consistent.
Bound fields are now named as such (BoundBlah), extend their unbound
counterparts, and their constructors accept an unbound instance.
2011-09-21 09:39:06 -04:00
Matthew Tretter
a71b3ca337 Removed Format processor
The Format processor was really a special case and didn't do any
processing at all. Instead, ImageSpec just knew to look for it and
responded accordingly. Therefore, it's been replaced with a `format`
property on ImageSpec. This warranted a deeper look at how the format
and extension were being deduced (when not explicitly provided); the
results are documented in-code, though the goal was "no surprises."
2011-09-20 21:34:00 -04:00
Matthew Tretter
80c785f2e5 No need for this. 2011-09-20 21:03:34 -04:00
Matthew Tretter
544d5b874a Added AdminThumbnailView field.
You're no longer restricted to just one, special-case admin thumbnail. Make as
many as you want by adding AdminThumbnailView properties to your model and
including them in your admin class's `list_display` tuple. You can also provide
a custom template. Note that (because this change introduces templates to
imagekit), imagekit is now required in INSTALLED_APPS.

Ideally we could get this stuff out of the model, but we'll have to look into
whether that's possible without making things really complicated.
2011-09-20 19:37:04 -04:00
Matthew Tretter
501d3c7ad3 Now using contribute_to_class.
By creating the Descriptor using contribute_to_class (instead of in
ImageModelBase's __init__), we take the first step towards eliminating the need
to extend ImageModel at all.
2011-09-20 15:44:54 -04:00
Matthew Tretter
82348d4931 Changed how cache files are named.
Removed the cache_dir, cache_filename_fields and cache_filename_format
properties of IKOptions. While these were very powerful, I felt that it was
unnecessarily confusing to have two properties (cache_dir and
cache_filename_format) that determine the filename. The new cache_to property is
modeled after ImageField's upload_to and behaves almost identically (the only
exception being that a callable value receives different arguments). In
addition, I felt that the interpolation of model properties provided by
cache_filename_fields, though useful, would be better handled by a utility
function outside of this library.
2011-09-18 21:08:49 -04:00
Bryan Veloso
df93146049 Making versioning a little easier on myself. 2011-09-12 14:17:12 -07:00
unknown
e295c9c039 error fix: https://github.com/jdriscoll/django-imagekit/issues/25 2011-09-12 17:43:53 +08:00
Bryan Veloso
a3ed700117 Fixing some spelling. Fixes #29. Thanks Nai! 2011-09-12 02:23:48 -07:00
Matthew Tretter
8cfe485a5a Storage can be specified on a per-spec basis.
If not defined on the spec, IKOptions.default_storage will be used. If that's
not defined, it will fall back to the image field's storage.
2011-09-10 00:25:34 -04:00
Matthew Tretter
57a28091c5 Added default_image_field
This works kind of like Django's models' _default_manager. If your specs don't
specify an image_field, and your IKOptions don't specify a default_image_field,
the first ImageField your model defines will be used.
2011-09-10 00:25:27 -04:00
Matthew Tretter
4c78f2d24c _imgfield is now a property of ImageSpec
Moved _imgfield from ImageModel to ImageSpec. Theoretically, this will allow you
to have specs that use different image fields on the same model.
2011-09-10 00:24:37 -04:00
Matthew Tretter
5e00de5204 Admin thumbnails. 2011-09-10 00:23:47 -04:00
Bryan Veloso
f9b79aa67f Tests now pass and don't leave any directory cruft. Fixes #28.
Tests were failing because they assumed that the file was deleted upon
teardown. This isn't the case after Django 1.3, where this no longer
happens. This fix removes any orphaned files (and directories) that were
created via the testing process.
2011-09-09 17:57:18 -07:00
Bryan Veloso
49df1caf63 Adding a test runner script a la #daniellindsleyrocksdahouse. 2011-09-09 17:53:00 -07:00
Bryan Veloso
e251b8903b Creating a "core" test module. 2011-09-09 17:51:22 -07:00
Bryan Veloso
9af96b4098 Creating a dedicated tests directory.
Moves tests.py out of the main module. Still a work in progress.
2011-09-09 16:33:42 -07:00
Bryan Veloso
6082b2b782 Let's get these docs started. 2011-09-09 15:53:49 -07:00
Matthew Tretter
a1f11facbe Processors now use static properties. 2011-09-08 16:49:44 -04:00
Matthew Tretter
db4d704f71 Changed ImageSpec constructor so you can use static properties.
For example:

    class MyImageSpec(ImageSpec):
        quality = 100

    class Photo(ImageModel):
        display = MyImageSpec()
2011-09-08 16:48:53 -04:00
Matthew Tretter
0b0942921b Updated README to reflect the new API. 2011-09-08 15:52:03 -04:00
Matthew Tretter
def8dea23f Storage moved onto ImageSpec. 2011-09-08 10:28:54 -04:00
Matthew Tretter
e32ccb617d Resize split into Crop and Fit. 2011-09-08 10:28:54 -04:00
Matthew Tretter
cd3395b68f Processors are now instance-based. 2011-09-08 10:28:50 -04:00
Matthew Tretter
a9895f335a Using spec properties from ImageModel. 2011-09-08 10:00:22 -04:00
Bryan Veloso
7dc8b9159e Merge branch 'release/0.4.0' into develop
* release/0.4.0:
  Think it's time to bump to 0.4.0.
2011-09-07 16:00:37 -07:00
BaBaBags
db95e63a63 Added support for preserving transparency when converting from color to greyscale 2011-08-23 10:20:37 -04:00
88 changed files with 6187 additions and 842 deletions

16
.gitignore vendored
View file

@ -1,7 +1,17 @@
*.pyc
*.db
*.egg*
*.orig
*.pyc
.DS_Store
dist
build
.tox
.idea
.vscode
MANIFEST
build
dist
/tests/media/*
!/tests/media/reference.png
/venv
/venv3
/.env
/tags

35
.travis.yml Normal file
View file

@ -0,0 +1,35 @@
sudo: false
language: python
python:
- "3.8"
- "3.7"
- "3.6"
- "3.5"
env:
- DJANGO="master"
- DJANGO="30"
- DJANGO="22"
- DJANGO="21"
- DJANGO="21"
- DJANGO="20"
- DJANGO="111"
install:
- pip install tox
script:
- tox -e py$(python -c 'import sys;print("".join(map(str, sys.version_info[:2])))')-django${DJANGO}
jobs:
fast_finish: true
allow_failures:
- env: DJANGO="master"
exclude:
- python: "3.5"
env: DJANGO="30"
- python: "3.5"
env: DJANGO="master"
notifications:
irc: "irc.freenode.org#imagekit"

60
AUTHORS
View file

@ -1,19 +1,53 @@
Original Author:
ImageKit was originally written by `Justin Driscoll`_.
* Justin Driscoll (jdriscoll)
The field-based API and other post-1.0 stuff was written by the bright people at
HZDG_.
Maintainers
-----------
Maintainers:
* `Matthew Tretter`_
* `Bryan Veloso`_
* `Chris Drackett`_
* `Greg Newman`_
* Bryan Veloso (bryanveloso)
* Chris Drackett (chrisdrackett)
* Greg Newman (gregnewman)
Contributors
------------
* `Josh Ourisman`_
* `Jonathan Slenders`_
* `Eric Eldredge`_
* `Chris McKenzie`_
* `Markus Kaiserswerth`_
* `Ryan Bagwell`_
* `Alexander Bohn`_
* `Timothée Peignier`_
* `Madis Väin`_
* `Jan Sagemüller`_
* `Clay McClure`_
* `Jannis Leidel`_
* `Sean Bell`_
* `Saul Shanabrook`_
* `Venelin Stoykov`_
Contributors:
* Josh Ourisman (joshourisman)
* Jonathan Slenders (jonathanslenders)
* Matthew Tretter (matthewwithanm)
* Markus Kaiserswerth (mkai)
.. _Justin Driscoll: http://github.com/jdriscoll
.. _HZDG: http://hzdg.com
.. _Bryan Veloso: http://github.com/bryanveloso
.. _Chris Drackett: http://github.com/chrisdrackett
.. _Greg Newman: http://github.com/gregnewman
.. _Josh Ourisman: http://github.com/joshourisman
.. _Jonathan Slenders: http://github.com/jonathanslenders
.. _Matthew Tretter: http://github.com/matthewwithanm
.. _Eric Eldredge: http://github.com/lettertwo
.. _Chris McKenzie: http://github.com/kenzic
.. _Ryan Bagwell: http://github.com/ryanbagwell
.. _Markus Kaiserswerth: http://github.com/mkai
.. _Alexander Bohn: http://github.com/fish2000
.. _Timothée Peignier: http://github.com/cyberdelia
.. _Madis Väin: http://github.com/madisvain
.. _Jan Sagemüller: https://github.com/version2
.. _Clay McClure: https://github.com/claymation
.. _Jannis Leidel: https://github.com/jezdez
.. _Sean Bell: https://github.com/seanbell
.. _Saul Shanabrook: https://github.com/saulshanabrook
.. _Venelin Stoykov: https://github.com/vstoykov

24
CONTRIBUTING.rst Normal file
View file

@ -0,0 +1,24 @@
Contributing
------------
We love contributions! These guidelines will help make sure we can get your
contributions merged as quickly as possible:
1. Write `good commit messages`__!
2. If you want to add a new feature, talk to us on the `mailing list`__ or
`IRC`__ first. We might already have plans, or be able to offer some advice.
3. Make sure your code passes the tests that ImageKit already has. To run the
tests, first install tox, ``pip install tox``, then use ``tox``. This will let you know about any errors or style
issues.
4. While we're talking about tests, creating new ones for your code makes it
much easier for us to merge your code quickly. ImageKit uses nose_, so
writing tests is painless. Check out `ours`__ for examples.
5. It's a good idea to do your work in a branch; that way, you can work on more
than one contribution at a time without making them interdependent.
__ http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
__ https://groups.google.com/forum/#!forum/django-imagekit
__ irc://irc.freenode.net/imagekit
.. _nose: https://nose.readthedocs.org/en/latest/
__ https://github.com/matthewwithanm/django-imagekit/tree/develop/tests

View file

@ -1,2 +1,18 @@
include AUTHORS
include LICENSE
include README.rst
include testrunner.py
include setup.cfg
include tests/*.py
include tests/assets/Lenna.png
include tests/assets/lenna-*.jpg
include tests/media/lenna.png
prune tests/media/CACHE
prune tests/media/b
prune tests/media/photos
include docs/Makefile
include docs/conf.py
include docs/make.bat
include docs/*.rst
recursive-include docs/_themes LICENSE README.rst flask_theme_support.py theme.conf *.css_t *.css *.html
recursive-include imagekit/templates *.html

View file

@ -1,145 +1,481 @@
===============
django-imagekit
===============
|Build Status|_
ImageKit In 7 Steps
===================
.. |Build Status| image:: https://travis-ci.org/matthewwithanm/django-imagekit.svg?branch=develop
.. _Build Status: https://travis-ci.org/matthewwithanm/django-imagekit
Step 1
******
ImageKit is a Django app for processing images. Need a thumbnail? A
black-and-white version of a user-uploaded image? ImageKit will make them for
you. If you need to programatically generate one image from another, you need
ImageKit.
::
ImageKit comes with a bunch of image processors for common tasks like resizing
and cropping, but you can also create your own. For an idea of what's possible,
check out the `Instakit`__ project.
$ pip install django-imagekit
**For the complete documentation on the latest stable version of ImageKit, see**
`ImageKit on RTD`_.
(or clone the source and put the imagekit module on your path)
.. _`ImageKit on RTD`: http://django-imagekit.readthedocs.org
__ https://github.com/fish2000/instakit
Step 2
******
Add ImageKit to your models.
Installation
============
::
1. Install `PIL`_ or `Pillow`_. (If you're using an ``ImageField`` in Django,
you should have already done this.)
2. ``pip install django-imagekit``
3. Add ``'imagekit'`` to your ``INSTALLED_APPS`` list in your project's settings.py
# myapp/models.py
.. note:: If you've never seen Pillow before, it considers itself a
more-frequently updated "friendly" fork of PIL that's compatible with
setuptools. As such, it shares the same namespace as PIL does and is a
drop-in replacement.
.. _`PIL`: http://pypi.python.org/pypi/PIL
.. _`Pillow`: http://pypi.python.org/pypi/Pillow
Usage Overview
==============
.. _specs:
Specs
-----
You have one image and you want to do something to it to create another image.
But how do you tell ImageKit what to do? By defining an image spec.
An **image spec** is a type of **image generator** that generates a new image
from a source image.
Defining Specs In Models
^^^^^^^^^^^^^^^^^^^^^^^^
The easiest way to use define an image spec is by using an ImageSpecField on
your model class:
.. code-block:: python
from django.db import models
from imagekit.models import ImageModel
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Photo(ImageModel):
name = models.CharField(max_length=100)
original_image = models.ImageField(upload_to='photos')
num_views = models.PositiveIntegerField(editable=False, default=0)
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
class IKOptions:
# This inner class is where we define the ImageKit options for the model
spec_module = 'myapp.specs'
cache_dir = 'photos'
image_field = 'original_image'
save_count_as = 'num_views'
profile = Profile.objects.all()[0]
print(profile.avatar_thumbnail.url) # > /media/CACHE/images/982d5af84cddddfd0fbf70892b4431e4.jpg
print(profile.avatar_thumbnail.width) # > 100
Step 3
******
As you can probably tell, ImageSpecFields work a lot like Django's
ImageFields. The difference is that they're automatically generated by
ImageKit based on the instructions you give. In the example above, the avatar
thumbnail is a resized version of the avatar image, saved as a JPEG with a
quality of 60.
Create your specifications.
Sometimes, however, you don't need to keep the original image (the avatar in
the above example); when the user uploads an image, you just want to process it
and save the result. In those cases, you can use the ``ProcessedImageField``
class:
::
.. code-block:: python
# myapp/specs.py
from django.db import models
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
from imagekit.specs import ImageSpec
from imagekit import processors
class Profile(models.Model):
avatar_thumbnail = ProcessedImageField(upload_to='avatars',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
# first we define our thumbnail resize processor
class ResizeThumb(processors.Resize):
width = 100
height = 75
crop = True
profile = Profile.objects.all()[0]
print(profile.avatar_thumbnail.url) # > /media/avatars/MY-avatar.jpg
print(profile.avatar_thumbnail.width) # > 100
# now we define a display size resize processor
class ResizeDisplay(processors.Resize):
width = 600
This is pretty similar to our previous example. We don't need to specify a
"source" any more since we're not processing another image field, but we do need
to pass an "upload_to" argument. This behaves exactly as it does for Django
ImageFields.
# now let's create an adjustment processor to enhance the image at small sizes
class EnchanceThumb(processors.Adjustment):
contrast = 1.2
sharpness = 1.1
.. note::
You might be wondering why we didn't need an "upload_to" argument for our
ImageSpecField. The reason is that ProcessedImageFields really are just like
ImageFields—they save the file path in the database and you need to run
syncdb (or create a migration) when you add one to your model.
ImageSpecFields, on the other hand, are virtual—they add no fields to your
database and don't require a database. This is handy for a lot of reasons,
but it means that the path to the image file needs to be programmatically
constructed based on the source image and the spec.
Defining Specs Outside of Models
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Defining specs as models fields is one very convenient way to process images,
but it isn't the only way. Sometimes you can't (or don't want to) add fields to
your models, and that's okay. You can define image spec classes and use them
directly. This can be especially useful for doing image processing in views—
particularly when the processing being done depends on user input.
.. code-block:: python
from imagekit import ImageSpec
from imagekit.processors import ResizeToFill
# now we can define our thumbnail spec
class Thumbnail(ImageSpec):
quality = 90 # defaults to 70
access_as = 'thumbnail_image'
pre_cache = True
processors = [ResizeThumb, EnchanceThumb]
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
# and our display spec
class Display(ImageSpec):
quality = 90 # defaults to 70
increment_count = True
processors = [ResizeDisplay]
It's probably not surprising that this class is capable of processing an image
in the exact same way as our ImageSpecField above. However, unlike with the
image spec model field, this class doesn't define what source the spec is acting
on, or what should be done with the result; that's up to you:
Step 4
******
.. code-block:: python
Flush the cache and pre-generate thumbnails (ImageKit has to be added to ``INSTALLED_APPS`` for management command to work).
source_file = open('/path/to/myimage.jpg', 'rb')
image_generator = Thumbnail(source=source_file)
result = image_generator.generate()
::
.. note::
$ python manage.py ikflush myapp
You don't have to use ``open``! You can use whatever File-like object you
want—including a model's ``ImageField``.
Step 5
******
The result of calling ``generate()`` on an image spec is a file-like object
containing our resized image, with which you can do whatever you want. For
example, if you wanted to save it to disk:
Use your new model in templates.
.. code-block:: python
::
<div class="original">
<img src="{{ photo.original_image.url }}" alt="{{ photo.name }}">
</div>
<div class="display">
<img src="{{ photo.display.url }}" alt="{{ photo.name }}">
</div>
<div class="thumbs">
{% for p in photos %}
<img src="{{ p.thumbnail_image.url }}" alt="{{ p.name }}">
{% endfor %}
</div>
Step 6
******
Play with the API.
::
>>> from myapp.models import Photo
>>> p = Photo.objects.all()[0]
<Photo: MyPhoto>
>>> p.display.url
u'/static/photos/myphoto_display.jpg'
>>> p.display.width
600
>>> p.display.height
420
>>> p.display.image
<JpegImagePlugin.JpegImageFile instance at 0xf18990>
>>> p.display.file
<File: /path/to/media/photos/myphoto_display.jpg>
>>> p.display.spec
<class 'myapp.specs.Display'>
Step 7
******
Enjoy a nice beverage.
::
from refrigerator import beer
beer.enjoy()
dest = open('/path/to/dest.jpg', 'wb')
dest.write(result.read())
dest.close()
Using Specs In Templates
^^^^^^^^^^^^^^^^^^^^^^^^
If you have a model with an ImageSpecField or ProcessedImageField, you can
easily use those processed image just as you would a normal image field:
.. code-block:: html
<img src="{{ profile.avatar_thumbnail.url }}" />
(This is assuming you have a view that's setting a context variable named
"profile" to an instance of our Profile model.)
But you can also generate processed image files directly in your template—from
any image—without adding anything to your model. In order to do this, you'll
first have to define an image generator class (remember, specs are a type of
generator) in your app somewhere, just as we did in the last section. You'll
also need a way of referring to the generator in your template, so you'll need
to register it.
.. code-block:: python
from imagekit import ImageSpec, register
from imagekit.processors import ResizeToFill
class Thumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
register.generator('myapp:thumbnail', Thumbnail)
.. note::
You can register your generator with any id you want, but choose wisely!
If you pick something too generic, you could have a conflict with another
third-party app you're using. For this reason, it's a good idea to prefix
your generator ids with the name of your app. Also, ImageKit recognizes
colons as separators when doing pattern matching (e.g. in the generateimages
management command), so it's a good idea to use those too!
.. warning::
This code can go in any file you want—but you need to make sure it's loaded!
In order to keep things simple, ImageKit will automatically try to load an
module named "imagegenerators" in each of your installed apps. So why don't
you just save yourself the headache and put your image specs in there?
Now that we've created an image generator class and registered it with ImageKit,
we can use it in our templates!
generateimage
"""""""""""""
The most generic template tag that ImageKit gives you is called "generateimage".
It requires at least one argument: the id of a registered image generator.
Additional keyword-style arguments are passed to the registered generator class.
As we saw above, image spec constructors expect a source keyword argument, so
that's what we need to pass to use our thumbnail spec:
.. code-block:: html
{% load imagekit %}
{% generateimage 'myapp:thumbnail' source=source_file %}
This will output the following HTML:
.. code-block:: html
<img src="/media/CACHE/images/982d5af84cddddfd0fbf70892b4431e4.jpg" width="100" height="50" />
You can also add additional HTML attributes; just separate them from your
keyword args using two dashes:
.. code-block:: html
{% load imagekit %}
{% generateimage 'myapp:thumbnail' source=source_file -- alt="A picture of Me" id="mypicture" %}
Not generating HTML image tags? No problem. The tag also functions as an
assignment tag, providing access to the underlying file object:
.. code-block:: html
{% load imagekit %}
{% generateimage 'myapp:thumbnail' source=source_file as th %}
<a href="{{ th.url }}">Click to download a cool {{ th.width }} x {{ th.height }} image!</a>
thumbnail
"""""""""
Because it's such a common use case, ImageKit also provides a "thumbnail"
template tag:
.. code-block:: html
{% load imagekit %}
{% thumbnail '100x50' source_file %}
Like the generateimage tag, the thumbnail tag outputs an <img> tag:
.. code-block:: html
<img src="/media/CACHE/images/982d5af84cddddfd0fbf70892b4431e4.jpg" width="100" height="50" />
Comparing this syntax to the generateimage tag above, you'll notice a few
differences.
First, we didn't have to specify an image generator id; unless we tell it
otherwise, thumbnail tag uses the generator registered with the id
"imagekit:thumbnail". **It's important to note that this tag is *not* using the
Thumbnail spec class we defined earlier**; it's using the generator registered
with the id "imagekit:thumbnail" which, by default, is
``imagekit.generatorlibrary.Thumbnail``.
Second, we're passing two positional arguments (the dimensions and the source
image) as opposed to the keyword arguments we used with the generateimage tag.
Like with the generateimage tag, you can also specify additional HTML attributes
for the thumbnail tag, or use it as an assignment tag:
.. code-block:: html
{% load imagekit %}
{% thumbnail '100x50' source_file -- alt="A picture of Me" id="mypicture" %}
{% thumbnail '100x50' source_file as th %}
Using Specs in Forms
^^^^^^^^^^^^^^^^^^^^
In addition to the model field above, there's also a form field version of the
``ProcessedImageField`` class. The functionality is basically the same (it
processes an image once and saves the result), but it's used in a form class:
.. code-block:: python
from django import forms
from imagekit.forms import ProcessedImageField
from imagekit.processors import ResizeToFill
class ProfileForm(forms.Form):
avatar_thumbnail = ProcessedImageField(spec_id='myapp:profile:avatar_thumbnail',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
The benefit of using ``imagekit.forms.ProcessedImageField`` (as opposed to
``imagekit.models.ProcessedImageField`` above) is that it keeps the logic for
creating the image outside of your model (in which you would use a normal Django
ImageField). You can even create multiple forms, each with their own
ProcessedImageField, that all store their results in the same image field.
Processors
----------
So far, we've only seen one processor: ``imagekit.processors.ResizeToFill``. But
ImageKit is capable of far more than just resizing images, and that power comes
from its processors.
Processors take a PIL image object, do something to it, and return a new one.
A spec can make use of as many processors as you'd like, which will all be run
in order.
.. code-block:: python
from imagekit import ImageSpec
from imagekit.processors import TrimBorderColor, Adjust
class MySpec(ImageSpec):
processors = [
TrimBorderColor(),
Adjust(contrast=1.2, sharpness=1.1),
]
format = 'JPEG'
options = {'quality': 60}
The ``imagekit.processors`` module contains processors for many common
image manipulations, like resizing, rotating, and color adjustments. However,
if they aren't up to the task, you can create your own. All you have to do is
define a class that implements a ``process()`` method:
.. code-block:: python
class Watermark(object):
def process(self, image):
# Code for adding the watermark goes here.
return image
That's all there is to it! To use your fancy new custom processor, just include
it in your spec's ``processors`` list:
.. code-block:: python
from imagekit import ImageSpec
from imagekit.processors import TrimBorderColor, Adjust
from myapp.processors import Watermark
class MySpec(ImageSpec):
processors = [
TrimBorderColor(),
Adjust(contrast=1.2, sharpness=1.1),
Watermark(),
]
format = 'JPEG'
options = {'quality': 60}
Note that when you import a processor from ``imagekit.processors``, imagekit
in turn imports the processor from `PILKit`_. So if you are looking for
available processors, look at PILKit.
.. _`PILKit`: https://github.com/matthewwithanm/pilkit
Admin
-----
ImageKit also contains a class named ``imagekit.admin.AdminThumbnail``
for displaying specs (or even regular ImageFields) in the
`Django admin change list`_. AdminThumbnail is used as a property on
Django admin classes:
.. code-block:: python
from django.contrib import admin
from imagekit.admin import AdminThumbnail
from .models import Photo
class PhotoAdmin(admin.ModelAdmin):
list_display = ('__str__', 'admin_thumbnail')
admin_thumbnail = AdminThumbnail(image_field='thumbnail')
admin.site.register(Photo, PhotoAdmin)
To use specs defined outside of models:
.. code-block:: python
from django.contrib import admin
from imagekit.admin import AdminThumbnail
from imagekit import ImageSpec
from imagekit.processors import ResizeToFill
from imagekit.cachefiles import ImageCacheFile
from .models import Photo
class AdminThumbnailSpec(ImageSpec):
processors = [ResizeToFill(100, 30)]
format = 'JPEG'
options = {'quality': 60 }
def cached_admin_thumb(instance):
# `image` is the name of the image field on the model
cached = ImageCacheFile(AdminThumbnailSpec(instance.image))
# only generates the first time, subsequent calls use cache
cached.generate()
return cached
class PhotoAdmin(admin.ModelAdmin):
list_display = ('__str__', 'admin_thumbnail')
admin_thumbnail = AdminThumbnail(image_field=cached_admin_thumb)
admin.site.register(Photo, PhotoAdmin)
AdminThumbnail can even use a custom template. For more information, see
``imagekit.admin.AdminThumbnail``.
.. _`Django admin change list`: https://docs.djangoproject.com/en/dev/intro/tutorial02/#customize-the-admin-change-list
Management Commands
-------------------
ImageKit has one management command—`generateimages`—which will generate cache
files for all of your registered image generators. You can also pass it a list
of generator ids in order to generate images selectively.
Community
=========
Please use `the GitHub issue tracker <https://github.com/matthewwithanm/django-imagekit/issues>`_
to report bugs with django-imagekit. `A mailing list <https://groups.google.com/forum/#!forum/django-imagekit>`_
also exists to discuss the project and ask questions, as well as the official
`#imagekit <irc://irc.freenode.net/imagekit>`_ channel on Freenode.
Contributing
============
We love contributions! And you don't have to be an expert with the library—or
even Django—to contribute either: ImageKit's processors are standalone classes
that are completely separate from the more intimidating internals of Django's
ORM. If you've written a processor that you think might be useful to other
people, open a pull request so we can take a look!
You can also check out our list of `open, contributor-friendly issues`__ for
ideas.
Check out our `contributing guidelines`__ for more information about pitching in
with ImageKit.
__ https://github.com/matthewwithanm/django-imagekit/issues?labels=contributor-friendly&state=open
__ https://github.com/matthewwithanm/django-imagekit/blob/develop/CONTRIBUTING.rst

153
docs/Makefile Normal file
View file

@ -0,0 +1,153 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = python $(shell which sphinx-build)
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ImageKit.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ImageKit.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/ImageKit"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ImageKit"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

45
docs/_themes/LICENSE vendored Executable file
View file

@ -0,0 +1,45 @@
Modifications:
Copyright (c) 2010 Kenneth Reitz.
Original Project:
Copyright (c) 2010 by Armin Ronacher.
Some rights reserved.
Redistribution and use in source and binary forms of the theme, with or
without modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
We kindly ask you to only use these themes in an unmodified manner just
for Flask and Flask-related products, not for unrelated projects. If you
like the visual style and want to use it for your own projects, please
consider making some larger changes to the themes (such as changing
font faces, sizes, colors or margins).
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

25
docs/_themes/README.rst vendored Executable file
View file

@ -0,0 +1,25 @@
krTheme Sphinx Style
====================
This repository contains sphinx styles Kenneth Reitz uses in most of
his projects. It is a drivative of Mitsuhiko's themes for Flask and Flask related
projects. To use this style in your Sphinx documentation, follow
this guide:
1. put this folder as _themes into your docs folder. Alternatively
you can also use git submodules to check out the contents there.
2. add this to your conf.py: ::
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
html_theme = 'kr'
The following themes exist:
**kr**
the standard flask documentation theme for large projects
**kr_small**
small one-page theme. Intended to be used by very small addon libraries.

86
docs/_themes/flask_theme_support.py vendored Executable file
View file

@ -0,0 +1,86 @@
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}

16
docs/_themes/kr/layout.html vendored Executable file
View file

@ -0,0 +1,16 @@
{%- extends "basic/layout.html" %}
{%- block extrahead %}
{{ super() }}
{% if theme_touch_icon %}
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
{% endif %}
<link media="only screen and (max-device-width: 480px)" href="{{
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9">
{% endblock %}
{%- block relbar2 %}{% endblock %}
{%- block footer %}
<div class="footer">
&copy; Copyright {{ copyright }}.
</div>
{%- endblock %}

19
docs/_themes/kr/relations.html vendored Executable file
View file

@ -0,0 +1,19 @@
<h3>Related Topics</h3>
<ul>
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
{%- for parent in parents %}
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
{%- endfor %}
{%- if prev %}
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
}}">{{ prev.title }}</a></li>
{%- endif %}
{%- if next %}
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
}}">{{ next.title }}</a></li>
{%- endif %}
{%- for parent in parents %}
</ul></li>
{%- endfor %}
</ul></li>
</ul>

480
docs/_themes/kr/static/flasky.css_t vendored Executable file
View file

@ -0,0 +1,480 @@
/*
* flasky.css_t
* ~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
* :license: Flask Design License, see LICENSE for details.
*/
{% set page_width = '940px' %}
{% set sidebar_width = '220px' %}
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
font-size: 17px;
background-color: white;
color: #000;
margin: 0;
padding: 0;
}
div.document {
width: {{ page_width }};
margin: 30px auto 0 auto;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 {{ sidebar_width }};
}
div.sphinxsidebar {
width: {{ sidebar_width }};
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 0 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
font-size: 14px;
color: #888;
text-align: right;
}
div.footer a {
color: #888;
}
div.related {
display: none;
}
div.sphinxsidebar a {
color: #444;
text-decoration: none;
border-bottom: 1px dotted #999;
}
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}
div.sphinxsidebar {
font-size: 14px;
line-height: 1.5;
}
div.sphinxsidebarwrapper {
padding: 18px 10px;
}
div.sphinxsidebarwrapper p.logo {
padding: 0;
margin: -10px 0 0 -20px;
text-align: center;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: 'Garamond', 'Georgia', serif;
color: #444;
font-size: 24px;
font-weight: normal;
margin: 0 0 5px 0;
padding: 0;
}
div.sphinxsidebar h4 {
font-size: 20px;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p.logo a,
div.sphinxsidebar h3 a,
div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}
div.sphinxsidebar p {
color: #555;
margin: 10px 0;
}
div.sphinxsidebar ul {
margin: 10px 0;
padding: 0;
color: #000;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: 'Georgia', serif;
font-size: 1em;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition tt.xref, div.admonition a tt {
border-bottom: 1px solid #fafafa;
}
dd div.admonition {
margin-left: -60px;
padding-left: 60px;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight {
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.9em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
background: #fdfdfd;
font-size: 0.9em;
}
table.footnote + table.footnote {
margin-top: -15px;
border-top: none;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td.label {
width: 0px;
padding: 0.3em 0 0.3em 0.5em;
}
table.footnote td {
padding: 0.3em 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
blockquote {
margin: 0 0 0 30px;
padding: 0;
}
ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}
pre {
background: #eee;
padding: 7px 30px;
margin: 15px -30px;
line-height: 1.3em;
}
dl pre, blockquote pre, li pre {
margin-left: -60px;
padding-left: 60px;
}
dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
border-bottom: 1px solid white;
}
a.reference {
text-decoration: none;
border-bottom: 1px dotted #004B6B;
}
a.reference:hover {
border-bottom: 1px solid #6D4100;
}
a.footnote-reference {
text-decoration: none;
font-size: 0.7em;
vertical-align: top;
border-bottom: 1px dotted #004B6B;
}
a.footnote-reference:hover {
border-bottom: 1px solid #6D4100;
}
a:hover tt {
background: #EEE;
}
@media screen and (max-width: 600px) {
div.sphinxsidebar {
display: none;
}
div.document {
width: 100%;
}
div.documentwrapper {
margin-left: 0;
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
}
div.bodywrapper {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
ul {
margin-left: 0;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.bodywrapper {
margin: 0;
}
.footer {
width: auto;
}
.github {
display: none;
}
}
/* scrollbars */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-button:start:decrement,
::-webkit-scrollbar-button:end:increment {
display: block;
height: 10px;
}
::-webkit-scrollbar-button:vertical:increment {
background-color: #fff;
}
::-webkit-scrollbar-track-piece {
background-color: #eee;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-thumb:vertical {
height: 50px;
background-color: #ccc;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-thumb:horizontal {
width: 50px;
background-color: #ccc;
-webkit-border-radius: 3px;
}
/* misc. */
.revsys-inline {
display: none!important;
}

90
docs/_themes/kr/static/small_flask.css vendored Executable file
View file

@ -0,0 +1,90 @@
/*
* small_flask.css_t
* ~~~~~~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher.
* :license: Flask Design License, see LICENSE for details.
*/
body {
margin: 0;
padding: 20px 30px;
}
div.documentwrapper {
float: none;
background: white;
}
div.sphinxsidebar {
display: block;
float: none;
width: 102.5%;
margin: 50px -30px -20px -30px;
padding: 10px 20px;
background: #333;
color: white;
}
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar a {
color: #aaa;
}
div.sphinxsidebar p.logo {
display: none;
}
div.document {
width: 100%;
margin: 0;
}
div.related {
display: block;
margin: 0;
padding: 10px 0 20px 0;
}
div.related ul,
div.related ul li {
margin: 0;
padding: 0;
}
div.footer {
display: none;
}
div.bodywrapper {
margin: 0;
}
div.body {
min-height: 0;
padding: 0;
}
.rtd_doc_footer {
display: none;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.footer {
width: auto;
}
.github {
display: none;
}

7
docs/_themes/kr/theme.conf vendored Executable file
View file

@ -0,0 +1,7 @@
[theme]
inherit = basic
stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
touch_icon =

22
docs/_themes/kr_small/layout.html vendored Executable file
View file

@ -0,0 +1,22 @@
{% extends "basic/layout.html" %}
{% block header %}
{{ super() }}
{% if pagename == 'index' %}
<div class=indexwrapper>
{% endif %}
{% endblock %}
{% block footer %}
{% if pagename == 'index' %}
</div>
{% endif %}
{% endblock %}
{# do not display relbars #}
{% block relbar1 %}{% endblock %}
{% block relbar2 %}
{% if theme_github_fork %}
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;"
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
{% endif %}
{% endblock %}
{% block sidebar1 %}{% endblock %}
{% block sidebar2 %}{% endblock %}

287
docs/_themes/kr_small/static/flasky.css_t vendored Executable file
View file

@ -0,0 +1,287 @@
/*
* flasky.css_t
* ~~~~~~~~~~~~
*
* Sphinx stylesheet -- flasky theme based on nature theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
color: #000;
background: white;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 40px auto 0 auto;
width: 700px;
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
}
img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
text-align: right;
color: #888;
padding: 10px;
font-size: 14px;
width: 650px;
margin: 0 auto 40px auto;
}
div.footer a {
color: #888;
text-decoration: underline;
}
div.related {
line-height: 32px;
color: #888;
}
div.related ul {
padding: 0 0 0 10px;
}
div.related a {
color: #444;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body {
padding-bottom: 40px; /* saved for footer */
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
margin: 30px 0px 10px 0px;
padding: 0;
}
{% if theme_index_logo %}
div.indexwrapper h1 {
text-indent: -999999px;
background: url({{ theme_index_logo }}) no-repeat center center;
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: white;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
div.admonition {
background: #fafafa;
margin: 20px -30px;
padding: 10px 30px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
div.admonition p.admonition-title {
font-family: 'Garamond', 'Georgia', serif;
font-weight: normal;
font-size: 24px;
margin: 0 0 10px 0;
padding: 0;
line-height: 1;
}
div.admonition p.last {
margin-bottom: 0;
}
div.highlight{
background-color: white;
}
dt:target, .highlight {
background: #FAF3E8;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre, tt {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.85em;
}
img.screenshot {
}
tt.descname, tt.descclassname {
font-size: 0.95em;
}
tt.descname {
padding-right: 0.08em;
}
img.screenshot {
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils {
border: 1px solid #888;
-moz-box-shadow: 2px 2px 4px #eee;
-webkit-box-shadow: 2px 2px 4px #eee;
box-shadow: 2px 2px 4px #eee;
}
table.docutils td, table.docutils th {
border: 1px solid #888;
padding: 0.25em 0.7em;
}
table.field-list, table.footnote {
border: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
table.footnote {
margin: 15px 0;
width: 100%;
border: 1px solid #eee;
}
table.field-list th {
padding: 0 0.8em 0 0;
}
table.field-list td {
padding: 0;
}
table.footnote td {
padding: 0.5em;
}
dl {
margin: 0;
padding: 0;
}
dl dd {
margin-left: 30px;
}
pre {
padding: 0;
margin: 15px -30px;
padding: 8px;
line-height: 1.3em;
padding: 7px 30px;
background: #eee;
border-radius: 2px;
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
}
dl pre {
margin-left: -60px;
padding-left: 60px;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
}
tt.xref, a tt {
background-color: #FBFBFB;
}
a:hover tt {
background: #EEE;
}

10
docs/_themes/kr_small/theme.conf vendored Executable file
View file

@ -0,0 +1,10 @@
[theme]
inherit = basic
stylesheet = flasky.css
nosidebar = true
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo = ''
index_logo_height = 120px
github_fork = ''

182
docs/advanced_usage.rst Normal file
View file

@ -0,0 +1,182 @@
Advanced Usage
**************
Models
======
The ``ImageSpecField`` Shorthand Syntax
---------------------------------------
If you've read the README, you already know what an ``ImageSpecField`` is and
the basics of defining one:
.. code-block:: python
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
This will create an ``avatar_thumbnail`` field which is a resized version of the
image stored in the ``avatar`` image field. But this is actually just shorthand
for creating an ``ImageSpec``, registering it, and associating it with an
``ImageSpecField``:
.. code-block:: python
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class AvatarThumbnail(ImageSpec):
processors = [ResizeToFill(100, 50)]
format = 'JPEG'
options = {'quality': 60}
register.generator('myapp:profile:avatar_thumbnail', AvatarThumbnail)
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
id='myapp:profile:avatar_thumbnail')
Obviously, the shorthand version is a lot, well…shorter. So why would you ever
want to go through the trouble of using the long form? The answer is that the
long form—creating an image spec class and registering it—gives you a lot more
power over the generated image.
.. _dynamic-specs:
Specs That Change
-----------------
As you'll remember from the README, an image spec is just a type of image
generator that generates a new image from a source image. How does the image
spec get access to the source image? Simple! It's passed to the constructor as
a keyword argument and stored as an attribute of the spec. Normally, we don't
have to concern ourselves with this; the ``ImageSpec`` knows what to do with the
source image and we're happy to let it do its thing. However, having access to
the source image in our spec class can be very useful…
Often, when using an ``ImageSpecField``, you may want the spec to vary based on
properties of a model. (For example, you might want to store image dimensions on
the model and then use them to generate your thumbnail.) Now that we know how to
access the source image from our spec, it's a simple matter to extract its model
and use it to create our processors list. In fact, ImageKit includes a utility
for getting this information.
.. code-block:: python
:emphasize-lines: 11-14
from django.db import models
from imagekit import ImageSpec, register
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from imagekit.utils import get_field_info
class AvatarThumbnail(ImageSpec):
format = 'JPEG'
options = {'quality': 60}
@property
def processors(self):
model, field_name = get_field_info(self.source)
return [ResizeToFill(model.thumbnail_width, thumbnail.avatar_height)]
register.generator('myapp:profile:avatar_thumbnail', AvatarThumbnail)
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
id='myapp:profile:avatar_thumbnail')
thumbnail_width = models.PositiveIntegerField()
thumbnail_height = models.PositiveIntegerField()
Now each avatar thumbnail will be resized according to the dimensions stored on
the model!
Of course, processors aren't the only thing that can vary based on the model of
the source image; spec behavior can change in any way you want.
.. _source-groups:
Source Groups
=============
When you run the ``generateimages`` management command, how does ImageKit know
which source images to use with which specs? Obviously, when you define an
ImageSpecField, the source image is being connected to a spec, but what's going
on underneath the hood?
The answer is that, when you define an ImageSpecField, ImageKit automatically
creates and registers an object called a *source group*. Source groups are
responsible for two things:
1. They dispatch signals when a source is saved, and
2. They expose a generator method that enumerates source files.
When these objects are registered (using ``imagekit.register.source_group()``),
their signals will trigger callbacks on the cache file strategies associated
with image specs that use the source. (So, for example, you can chose to
generate a file every time the source image changes.) In addition, the generator
method is used (indirectly) to create the list of files to generate with the
``generateimages`` management command.
Currently, there is only one source group class bundled with ImageKit—the one
used by ImageSpecFields. This source group
(``imagekit.specs.sourcegroups.ImageFieldSourceGroup``) represents an ImageField
on every instance of a particular model. In terms of the above description, the
instance ``ImageFieldSourceGroup(Profile, 'avatar')`` 1) dispatches a signal
every time the image in Profile's avatar ImageField changes, and 2) exposes a
generator method that iterates over every Profile's "avatar" image.
Chances are, this is the only source group you will ever need to use, however,
ImageKit lets you define and register custom source groups easily. This may be
useful, for example, if you're using the template tags "generateimage" and
"thumbnail" and the optimistic cache file strategy. Again, the purpose is
to tell ImageKit which specs are used with which sources (so the
"generateimages" management command can generate those files) and when the
source image has been created or changed (so that the strategy has the
opportunity to act on it).
A simple example of a custom source group class is as follows:
.. code-block:: python
import glob
import os
class JpegsInADirectory(object):
def __init__(self, dir):
self.dir = dir
def files(self):
os.chdir(self.dir)
for name in glob.glob('*.jpg'):
yield open(name, 'rb')
Instances of this class could then be registered with one or more spec id:
.. code-block:: python
from imagekit import register
register.source_group('myapp:profile:avatar_thumbnail', JpegsInADirectory('/path/to/some/pics'))
Running the "generateimages" management command would now cause thumbnails to be
generated (using the "myapp:profile:avatar_thumbnail" spec) for each of the
JPEGs in `/path/to/some/pics`.
Note that, since this source group doesnt send the `source_saved` signal, the
corresponding cache file strategy callbacks would not be called for them.

256
docs/caching.rst Normal file
View file

@ -0,0 +1,256 @@
Caching
*******
Default Backend Workflow
========================
``ImageSpec``
-------------
At the heart of ImageKit are image generators. These are classes with a
``generate()`` method which returns an image file. An image spec is a type of
image generator. The thing that makes specs special is that they accept a source
image. So an image spec is just an image generator that makes an image from some
other image.
``ImageCacheFile``
------------------
However, an image spec by itself would be vastly inefficient. Every time an
an image was accessed in some way, it would have be regenerated and saved.
Most of the time, you want to re-use a previously generated image, based on the
input image and spec, instead of generating a new one. That's where
``ImageCacheFile`` comes in. ``ImageCacheFile`` is a File-like object that
wraps an image generator. They look and feel just like regular file
objects, but they've got a little trick up their sleeve: they represent files
that may not actually exist!
.. _cache-file-strategy:
Cache File Strategy
-------------------
Each ``ImageCacheFile`` has a cache file strategy, which abstracts away when
image is actually generated. It can implement the following three methods:
* ``on_content_required`` - called by ``ImageCacheFile`` when it requires the
contents of the generated image. For example, when you call ``read()`` or
try to access information contained in the file.
* ``on_existence_required`` - called by ``ImageCacheFile`` when it requires the
generated image to exist but may not be concerned with its contents. For
example, when you access its ``url`` or ``path`` attribute.
* ``on_source_saved`` - called when the source of a spec is saved
The default strategy only defines the first two of these, as follows:
.. code-block:: python
class JustInTime(object):
def on_content_required(self, file):
file.generate()
def on_existence_required(self, file):
file.generate()
.. _cache-file-backend:
Cache File Backend
------------------
The ``generate`` method on the ``ImageCacheFile`` is further delegated to the
cache file backend, which abstracts away how an image is generated.
The cache file backend defaults to the setting
``IMAGEKIT_DEFAULT_CACHEFILE_BACKEND`` and can be set explicitly on a spec with
the ``cachefile_backend`` attribute.
The default works like this:
* Checks the file storage to see if a file exists
* If not, caches that information for 5 seconds
* If it does, caches that information in the ``IMAGEKIT_CACHE_BACKEND``
If file doesn't exist, generates it immediately and synchronously
That pretty much covers the architecture of the caching layer, and its default
behavior. I like the default behavior. When will an image be regenerated?
Whenever it needs to be! When will your storage backend get hit? Depending on
your ``IMAGEKIT_CACHE_BACKEND`` settings, as little as twice per file (once for the
existence check and once to save the generated file). What if you want to change
a spec? The generated file name (which is used as part of the cache keys) vary
with the source file name and spec attributes, so if you change any of those, a
new file will be generated. The default behavior is easy!
.. note::
Like regular Django ImageFields, IK doesn't currently cache width and height
values, so accessing those will always result in a read. That will probably
change soon though.
Optimizing
==========
There are several ways to improve the performance (reduce I/O operations) of
ImageKit. Each has its own pros and cons.
Caching Data About Generated Files
----------------------------------
Generally, once a file is generated, you will never be removing it, so by
default ImageKit will use default cache to cache the state of generated
files "forever" (or only 5 minutes when ``DEBUG = True``).
The time for which ImageKit will cache state is configured with
``IMAGEKIT_CACHE_TIMEOUT``. If set to ``None`` this means "never expire"
(default when ``DEBUG = False``). You can reduce this timeout if you want
or set it to some numeric value in seconds if your cache backend behaves
differently and for example do not cache values if timeout is ``None``.
If you clear your cache durring deployment or some other reason probably
you do not want to lose the cache for generated images especcialy if you
are using some slow remote storage (like Amazon S3). Then you can configure
seprate cache (for example redis) in your ``CACHES`` config and tell ImageKit
to use it instead of the default cache by setting ``IMAGEKIT_CACHE_BACKEND``.
Pre-Generating Images
---------------------
The default cache file backend generates images immediately and synchronously.
If you don't do anything special, that will be when they are first requested—as
part of request-response cycle. This means that the first visitor to your page
will have to wait for the file to be created before they see any HTML.
This can be mitigated, though, by simply generating the images ahead of time, by
running the ``generateimages`` management command.
.. note::
If using with template tags, be sure to read :ref:`source-groups`.
Deferring Image Generation
--------------------------
As mentioned above, image generation is normally done synchronously. through
the default cache file backend. However, you can also take advantage of
deferred generation. In order to do this, you'll need to do two things:
1) install `celery`__ (or `django-celery`__ if you are bound to Celery<3.1)
2) tell ImageKit to use the async cachefile backend.
To do this for all specs, set the ``IMAGEKIT_DEFAULT_CACHEFILE_BACKEND`` in
your settings
.. code-block:: python
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'imagekit.cachefiles.backends.Async'
Images will now be generated asynchronously. But watch out! Asynchrounous
generation means you'll have to account for images that haven't been generated
yet. You can do this by checking the truthiness of your files; if an image
hasn't been generated, it will be falsy:
.. code-block:: html
{% if not profile.avatar_thumbnail %}
<img src="/path/to/placeholder.jpg" />
{% else %}
<img src="{{ profile.avatar_thumbnail.url }}" />
{% endif %}
Or, in Python:
.. code-block:: python
profile = Profile.objects.all()[0]
if profile.avatar_thumbnail:
url = profile.avatar_thumbnail.url
else:
url = '/path/to/placeholder.jpg'
.. note::
If you are using an "async" backend in combination with the "optimistic"
cache file strategy (see `Removing Safeguards`_ below), checking for
thruthiness as described above will not work. The "optimistic" backend is
very optimistic so to say, and removes the check. Create and use the
following strategy to a) have images only created on save, and b) retain
the ability to check whether the images have already been created::
class ImagekitOnSaveStrategy(object):
def on_source_saved(self, file):
file.generate()
.. note::
If you use custom storage backend for some specs,
(storage passed to the field different than configured one)
it's required the storage to be pickleable
__ https://pypi.python.org/pypi/django-celery
__ http://www.celeryproject.org
Removing Safeguards
-------------------
Even with pre-generating images, ImageKit will still try to ensure that your
image exists when you access it by default. This is for your benefit: if you
forget to generate your images, ImageKit will see that and generate it for you.
If the state of the file is cached (see above), this is a pretty cheap
operation. However, if the state isn't cached, ImageKit will need to query the
storage backend.
For those who aren't willing to accept that cost (and who never want ImageKit
to generate images in the request-responce cycle), there's the "optimistic"
cache file strategy. This strategy only generates a new image when a spec's
source image is created or changed. Unlike with the "just in time" strategy,
accessing the file won't cause it to be generated, ImageKit will just assume
that it already exists.
To use this cache file strategy for all specs, set the
``IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY`` in your settings:
.. code-block:: python
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.Optimistic'
If you have specs that :ref:`change based on attributes of the source
<dynamic-specs>`, that's not going to cut it, though; the file will also need to
be generated when those attributes change. Likewise, image generators that don't
have sources (i.e. generators that aren't specs) won't cause files to be
generated automatically when using the optimistic strategy. (ImageKit can't know
when those need to be generated, if not on access.) In both cases, you'll have
to trigger the file generation yourself—either by generating the file in code
when necessary, or by periodically running the ``generateimages`` management
command. Luckily, ImageKit makes this pretty easy:
.. code-block:: python
from imagekit.cachefiles import LazyImageCacheFile
file = LazyImageCacheFile('myapp:profile:avatar_thumbnail', source=source_file)
file.generate()
One final situation in which images won't be generated automatically when using
the optimistic strategy is when you use a spec with a source that hasn't been
registered with it. Unlike the previous two examples, this situation cannot be
rectified by running the ``generateimages`` management command, for the simple
reason that the command has no way of knowing it needs to generate a file for
that spec from that source. Typically, this situation would arise when using the
template tags. Unlike ImageSpecFields, which automatically register all the
possible source images with the spec you define, the template tags
("generateimage" and "thumbnail") let you use any spec with any source.
Therefore, in order to generate the appropriate files using the
``generateimages`` management command, you'll need to first register a source
group that represents all of the sources you wish to use with the corresponding
specs. See :ref:`source-groups` for more information.

249
docs/conf.py Normal file
View file

@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
#
# ImageKit documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 25 17:05:55 2011.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import re, sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
sys.path.append(os.path.abspath('_themes'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'ImageKit'
copyright = u'2011, Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'
pkgmeta = {}
execfile(os.path.join(os.path.dirname(__file__), '..', 'imagekit',
'pkgmeta.py'), pkgmeta)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = re.match(r'\d+\.\d+', pkgmeta['__version__']).group()
# The full version, including alpha/beta/rc tags.
release = pkgmeta['__version__']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'kr'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = False
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'ImageKitdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'ImageKit.tex', u'ImageKit Documentation',
u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett \\& Matthew Tretter', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'imagekit', u'ImageKit Documentation',
[u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'ImageKit', u'ImageKit Documentation', u'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett & Matthew Tretter',
'ImageKit', 'One line description of project.', 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
autoclass_content = 'both'

90
docs/configuration.rst Normal file
View file

@ -0,0 +1,90 @@
.. _settings:
Configuration
=============
Settings
--------
.. currentmodule:: django.conf.settings
.. attribute:: IMAGEKIT_CACHEFILE_DIR
:default: ``'CACHE/images'``
The directory to which image files will be cached.
.. attribute:: IMAGEKIT_DEFAULT_FILE_STORAGE
:default: ``None``
The qualified class name of a Django storage backend to use to save the
cached images. If no value is provided for ``IMAGEKIT_DEFAULT_FILE_STORAGE``,
and none is specified by the spec definition, `your default file storage`__
will be used.
.. attribute:: IMAGEKIT_DEFAULT_CACHEFILE_BACKEND
:default: ``'imagekit.cachefiles.backends.Simple'``
Specifies the class that will be used to validate cached image files.
.. attribute:: IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY
:default: ``'imagekit.cachefiles.strategies.JustInTime'``
The class responsible for specifying how and when cache files are
generated.
.. attribute:: IMAGEKIT_CACHE_BACKEND
:default: ``'default'``
The Django cache backend alias to retrieve the shared cache instance defined
in your settings, as described in the `Django cache section`_.
The cache is then used to store information like the state of cached
images (i.e. validated or not).
.. _`Django cache section`: https://docs.djangoproject.com/en/1.8/topics/cache/#accessing-the-cache
.. attribute:: IMAGEKIT_CACHE_TIMEOUT
:default: ``None``
Use when you need to override the timeout used to cache file state.
By default it is "cache forever".
It's highly recommended that you use a very high timeout.
.. attribute:: IMAGEKIT_CACHE_PREFIX
:default: ``'imagekit:'``
A cache prefix to be used when values are stored in ``IMAGEKIT_CACHE_BACKEND``
.. attribute:: IMAGEKIT_CACHEFILE_NAMER
:default: ``'imagekit.cachefiles.namers.hash'``
A function responsible for generating file names for non-spec cache files.
.. attribute:: IMAGEKIT_SPEC_CACHEFILE_NAMER
:default: ``'imagekit.cachefiles.namers.source_name_as_path'``
A function responsible for generating file names for cache files that
correspond to image specs. Since you will likely want to base the name of
your cache files on the name of the source, this extra setting is provided.
__ https://docs.djangoproject.com/en/dev/ref/settings/#default-file-storage

24
docs/index.rst Normal file
View file

@ -0,0 +1,24 @@
.. include:: ../README.rst
Authors
=======
.. include:: ../AUTHORS
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. toctree::
:glob:
:maxdepth: 2
configuration
advanced_usage
caching
upgrading

190
docs/make.bat Normal file
View file

@ -0,0 +1,190 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ImageKit.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ImageKit.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

117
docs/upgrading.rst Normal file
View file

@ -0,0 +1,117 @@
Upgrading from 2.x
==================
ImageKit 3.0 introduces new APIs and tools that augment, improve, and in some
cases entirely replace old IK workflows. Below, you'll find some useful guides
for migrating your ImageKit 2.0 apps over to the shiny new IK3.
Model Specs
-----------
IK3 is chock full of new features and better tools for even the most
sophisticated use cases. Despite this, not too much has changed when it
comes to the most common of use cases: processing an ``ImageField`` on a model.
In IK2, you may have used an ``ImageSpecField`` on a model to process an
existing ``ImageField``:
.. code-block:: python
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(image_field='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
In IK3, things look much the same:
.. code-block:: python
class Profile(models.Model):
avatar = models.ImageField(upload_to='avatars')
avatar_thumbnail = ImageSpecField(source='avatar',
processors=[ResizeToFill(100, 50)],
format='JPEG',
options={'quality': 60})
The major difference is that ``ImageSpecField`` no longer takes an
``image_field`` kwarg. Instead, you define a ``source``.
Image Cache Backends
--------------------
In IK2, you could gain some control over how your cached images were generated
by providing an ``image_cache_backend``:
.. code-block:: python
class Photo(models.Model):
...
thumbnail = ImageSpecField(..., image_cache_backend=MyImageCacheBackend())
This gave you great control over *how* your images are generated and stored,
but it could be difficult to control *when* they were generated and stored.
IK3 retains the image cache backend concept (now called cache file backends),
but separates the 'when' control out to cache file strategies:
.. code-block:: python
class Photo(models.Model):
...
thumbnail = ImageSpecField(...,
cachefile_backend=MyCacheFileBackend(),
cachefile_strategy=MyCacheFileStrategy())
If you are using the IK2 default image cache backend setting:
.. code-block:: python
IMAGEKIT_DEFAULT_IMAGE_CACHE_BACKEND = 'path.to.MyImageCacheBackend'
IK3 provides analogous settings for cache file backends and strategies:
.. code-block:: python
IMAGEKIT_DEFAULT_CACHEFILE_BACKEND = 'path.to.MyCacheFileBackend'
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'path.to.MyCacheFileStrategy'
See the documentation on :ref:`cache file backends <cache-file-backend>` and :ref:`cache file strategies <cache-file-strategy>`
for more details.
Conditional model ``processors``
--------------------------------
In IK2, an ``ImageSpecField`` could take a ``processors`` callable instead of
an iterable, which allowed processing decisions to made based on other
properties of the model. IK3 does away with this feature for consistency's sake
(if one kwarg could be callable, why not all?), but provides a much more robust
solution: the custom ``spec``. See the :doc:`advanced usage <advanced_usage>` documentation for more.
Conditonal ``cache_to`` file names
----------------------------------
IK2 provided a means of specifying custom cache file names for your
image specs by passing a ``cache_to`` callable to an ``ImageSpecField``.
IK3 does away with this feature, again, for consistency.
There is a way to achieve custom file names by overriding your spec's
``cachefile_name``, but it is not recommended, as the spec's default
behavior is to hash the combination of ``source``, ``processors``, ``format``,
and other spec options to ensure that changes to the spec always result in
unique file names. See the documentation on :ref:`specs` for more.
Processors have moved to PILKit
-------------------------------
Processors have moved to a separate project: `PILKit`_. You should not have to
make any changes to an IK2 project to use PILKit--it should be installed with
IK3, and importing from ``imagekit.processors`` will still work.
.. _`PILKit`: https://github.com/matthewwithanm/pilkit

View file

@ -1,11 +1,6 @@
"""
Django ImageKit
Author: Justin Driscoll <justin.driscoll@gmail.com>
Version: 0.4.0
"""
__author__ = 'Justin Driscoll, Bryan Veloso, Greg Newman, Chris Drackett'
__version__ = (0, 4, 0)
# flake8: noqa
from . import conf
from . import generatorlibrary
from .specs import ImageSpec
from .pkgmeta import *
from .registry import register, unregister

40
imagekit/admin.py Normal file
View file

@ -0,0 +1,40 @@
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
class AdminThumbnail(object):
"""
A convenience utility for adding thumbnails to Django's admin change list.
"""
short_description = _('Thumbnail')
allow_tags = True
def __init__(self, image_field, template=None):
"""
:param image_field: The name of the ImageField or ImageSpecField on the
model to use for the thumbnail.
:param template: The template with which to render the thumbnail
"""
self.image_field = image_field
self.template = template
def __call__(self, obj):
if callable(self.image_field):
thumbnail = self.image_field(obj)
else:
try:
thumbnail = getattr(obj, self.image_field)
except AttributeError:
raise Exception('The property %s is not defined on %s.' %
(self.image_field, obj.__class__.__name__))
original_image = getattr(thumbnail, 'source', None) or thumbnail
template = self.template or 'imagekit/admin/thumbnail.html'
return render_to_string(template, {
'model': obj,
'thumbnail': thumbnail,
'original_image': original_image,
})

View file

@ -0,0 +1,183 @@
from copy import copy
from django.conf import settings
from django.core.files import File
from django.core.files.images import ImageFile
from django.utils.functional import SimpleLazyObject
from django.utils.encoding import smart_str
from ..files import BaseIKFile
from ..registry import generator_registry
from ..signals import content_required, existence_required
from ..utils import get_logger, get_singleton, generate, get_by_qname
class ImageCacheFile(BaseIKFile, ImageFile):
"""
A file that represents the result of a generator. Creating an instance of
this class is not enough to trigger the generation of the file. In fact,
one of the main points of this class is to allow the creation of the file
to be deferred until the time that the cache file strategy requires it.
"""
def __init__(self, generator, name=None, storage=None, cachefile_backend=None, cachefile_strategy=None):
"""
:param generator: The object responsible for generating a new image.
:param name: The filename
:param storage: A Django storage object that will be used to save the
file.
:param cachefile_backend: The object responsible for managing the
state of the file.
:param cachefile_strategy: The object responsible for handling events
for this file.
"""
self.generator = generator
if not name:
try:
name = generator.cachefile_name
except AttributeError:
fn = get_by_qname(settings.IMAGEKIT_CACHEFILE_NAMER, 'namer')
name = fn(generator)
self.name = name
storage = storage or getattr(generator, 'cachefile_storage',
None) or get_singleton(settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
'file storage backend')
self.cachefile_backend = (
cachefile_backend
or getattr(generator, 'cachefile_backend', None)
or get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_BACKEND,
'cache file backend'))
self.cachefile_strategy = (
cachefile_strategy
or getattr(generator, 'cachefile_strategy', None)
or get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY,
'cache file strategy')
)
super(ImageCacheFile, self).__init__(storage=storage)
def _require_file(self):
if getattr(self, '_file', None) is None:
content_required.send(sender=self, file=self)
self._file = self.storage.open(self.name, 'rb')
# The ``path`` and ``url`` properties are overridden so as to not call
# ``_require_file``, which is only meant to be called when the file object
# will be directly interacted with (e.g. when using ``read()``). These only
# require the file to exist; they do not need its contents to work. This
# distinction gives the user the flexibility to create a cache file
# strategy that assumes the existence of a file, but can still make the file
# available when its contents are required.
def _storage_attr(self, attr):
if getattr(self, '_file', None) is None:
existence_required.send(sender=self, file=self)
fn = getattr(self.storage, attr)
return fn(self.name)
@property
def path(self):
return self._storage_attr('path')
@property
def url(self):
return self._storage_attr('url')
def generate(self, force=False):
"""
Generate the file. If ``force`` is ``True``, the file will be generated
whether the file already exists or not.
"""
if force or getattr(self, '_file', None) is None:
self.cachefile_backend.generate(self, force)
def _generate(self):
# Generate the file
content = generate(self.generator)
actual_name = self.storage.save(self.name, content)
# We're going to reuse the generated file, so we need to reset the pointer.
content.seek(0)
# Store the generated file. If we don't do this, the next time the
# "file" attribute is accessed, it will result in a call to the storage
# backend (in ``BaseIKFile._get_file``). Since we already have the
# contents of the file, what would the point of that be?
self.file = File(content)
if actual_name != self.name:
get_logger().warning(
'The storage backend %s did not save the file with the'
' requested name ("%s") and instead used "%s". This may be'
' because a file already existed with the requested name. If'
' so, you may have meant to call generate() instead of'
' generate(force=True), or there may be a race condition in the'
' file backend %s. The saved file will not be used.' % (
self.storage,
self.name, actual_name,
self.cachefile_backend
)
)
def __bool__(self):
if not self.name:
return False
# Dispatch the existence_required signal before checking to see if the
# file exists. This gives the strategy a chance to create the file.
existence_required.send(sender=self, file=self)
try:
check = self.cachefile_strategy.should_verify_existence(self)
except AttributeError:
# All synchronous backends should have created the file as part of
# `existence_required` if they wanted to.
check = getattr(self.cachefile_backend, 'is_async', False)
return self.cachefile_backend.exists(self) if check else True
def __getstate__(self):
state = copy(self.__dict__)
# file is hidden link to "file" attribute
state.pop('_file', None)
# remove storage from state as some non-FileSystemStorage can't be
# pickled
settings_storage = get_singleton(
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
'file storage backend'
)
if state['storage'] == settings_storage:
state.pop('storage')
return state
def __setstate__(self, state):
if 'storage' not in state:
state['storage'] = get_singleton(
settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
'file storage backend'
)
self.__dict__.update(state)
def __nonzero__(self):
# Python 2 compatibility
return self.__bool__()
def __repr__(self):
return smart_str("<%s: %s>" % (
self.__class__.__name__, self if self.name else "None")
)
class LazyImageCacheFile(SimpleLazyObject):
def __init__(self, generator_id, *args, **kwargs):
def setup():
generator = generator_registry.get(generator_id, *args, **kwargs)
return ImageCacheFile(generator)
super(LazyImageCacheFile, self).__init__(setup)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, str(self) or 'None')

View file

@ -0,0 +1,195 @@
from ..utils import get_singleton, get_cache, sanitize_cache_key
import warnings
from copy import copy
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
class CacheFileState(object):
EXISTS = 'exists'
GENERATING = 'generating'
DOES_NOT_EXIST = 'does_not_exist'
def get_default_cachefile_backend():
"""
Get the default file backend.
"""
from django.conf import settings
return get_singleton(settings.IMAGEKIT_DEFAULT_CACHEFILE_BACKEND,
'file backend')
class InvalidFileBackendError(ImproperlyConfigured):
pass
class AbstractCacheFileBackend(object):
"""
An abstract cache file backend. This isn't used by any internal classes and
is included simply to illustrate the minimum interface of a cache file
backend for users who wish to implement their own.
"""
def generate(self, file, force=False):
raise NotImplementedError
def exists(self, file):
raise NotImplementedError
class CachedFileBackend(object):
existence_check_timeout = 5
"""
The number of seconds to wait before rechecking to see if the file exists.
If the image is found to exist, that information will be cached using the
timeout specified in your CACHES setting (which should be very high).
However, when the file does not exist, you probably want to check again
in a relatively short amount of time. This attribute allows you to do that.
"""
@property
def cache(self):
if not getattr(self, '_cache', None):
self._cache = get_cache()
return self._cache
def get_key(self, file):
from django.conf import settings
return sanitize_cache_key('%s%s-state' %
(settings.IMAGEKIT_CACHE_PREFIX, file.name))
def get_state(self, file, check_if_unknown=True):
key = self.get_key(file)
state = self.cache.get(key)
if state is None and check_if_unknown:
exists = self._exists(file)
state = CacheFileState.EXISTS if exists else CacheFileState.DOES_NOT_EXIST
self.set_state(file, state)
return state
def set_state(self, file, state):
key = self.get_key(file)
if state == CacheFileState.DOES_NOT_EXIST:
self.cache.set(key, state, self.existence_check_timeout)
else:
self.cache.set(key, state, settings.IMAGEKIT_CACHE_TIMEOUT)
def __getstate__(self):
state = copy(self.__dict__)
# Don't include the cache when pickling. It'll be reconstituted based
# on the settings.
state.pop('_cache', None)
return state
def exists(self, file):
return self.get_state(file) == CacheFileState.EXISTS
def generate(self, file, force=False):
raise NotImplementedError
def generate_now(self, file, force=False):
if force or self.get_state(file) not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
self.set_state(file, CacheFileState.GENERATING)
file._generate()
self.set_state(file, CacheFileState.EXISTS)
file.close()
class Simple(CachedFileBackend):
"""
The most basic file backend. The storage is consulted to see if the file
exists. Files are generated synchronously.
"""
def generate(self, file, force=False):
self.generate_now(file, force=force)
def _exists(self, file):
return bool(getattr(file, '_file', None)
or (file.name and file.storage.exists(file.name)))
def _generate_file(backend, file, force=False):
backend.generate_now(file, force=force)
class BaseAsync(Simple):
"""
Base class for cache file backends that generate files asynchronously.
"""
is_async = True
def generate(self, file, force=False):
# Schedule the file for generation, unless we know for sure we don't
# need to. If an already-generated file sneaks through, that's okay;
# ``generate_now`` will catch it. We just want to make sure we don't
# schedule anything we know is unnecessary--but we also don't want to
# force a costly existence check.
state = self.get_state(file, check_if_unknown=False)
if state not in (CacheFileState.GENERATING, CacheFileState.EXISTS):
self.schedule_generation(file, force=force)
def schedule_generation(self, file, force=False):
# overwrite this to have the file generated in the background,
# e. g. in a worker queue.
raise NotImplementedError
try:
from celery import task
except ImportError:
pass
else:
_celery_task = task(ignore_result=True, serializer='pickle')(_generate_file)
class Celery(BaseAsync):
"""
A backend that uses Celery to generate the images.
"""
def __init__(self, *args, **kwargs):
try:
import celery # noqa
except ImportError:
raise ImproperlyConfigured('You must install celery to use'
' imagekit.cachefiles.backends.Celery.')
super(Celery, self).__init__(*args, **kwargs)
def schedule_generation(self, file, force=False):
_celery_task.delay(self, file, force=force)
# Stub class to preserve backwards compatibility and issue a warning
class Async(Celery):
def __init__(self, *args, **kwargs):
message = '{path}.Async is deprecated. Use {path}.Celery instead.'
warnings.warn(message.format(path=__name__), DeprecationWarning)
super(Async, self).__init__(*args, **kwargs)
try:
from django_rq import job
except ImportError:
pass
else:
_rq_job = job('default', result_ttl=0)(_generate_file)
class RQ(BaseAsync):
"""
A backend that uses RQ to generate the images.
"""
def __init__(self, *args, **kwargs):
try:
import django_rq # noqa
except ImportError:
raise ImproperlyConfigured('You must install django-rq to use'
' imagekit.cachefiles.backends.RQ.')
super(RQ, self).__init__(*args, **kwargs)
def schedule_generation(self, file, force=False):
_rq_job.delay(self, file, force=force)

View file

@ -0,0 +1,91 @@
"""
Functions responsible for returning filenames for the given image generator.
Users are free to define their own functions; these are just some some sensible
choices.
"""
from django.conf import settings
import os
from ..utils import format_to_extension, suggest_extension
def source_name_as_path(generator):
"""
A namer that, given the following source file name::
photos/thumbnails/bulldog.jpg
will generate a name like this::
/path/to/generated/images/photos/thumbnails/bulldog/5ff3233527c5ac3e4b596343b440ff67.jpg
where "/path/to/generated/images/" is the value specified by the
``IMAGEKIT_CACHEFILE_DIR`` setting.
"""
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
# Generally, we put the file right in the cache file directory.
dir = settings.IMAGEKIT_CACHEFILE_DIR
else:
# For source files with relative names (like Django media files),
# use the source's name to create the new filename.
dir = os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
os.path.splitext(source_filename)[0])
ext = suggest_extension(source_filename or '', generator.format)
return os.path.normpath(os.path.join(dir,
'%s%s' % (generator.get_hash(), ext)))
def source_name_dot_hash(generator):
"""
A namer that, given the following source file name::
photos/thumbnails/bulldog.jpg
will generate a name like this::
/path/to/generated/images/photos/thumbnails/bulldog.5ff3233527c5.jpg
where "/path/to/generated/images/" is the value specified by the
``IMAGEKIT_CACHEFILE_DIR`` setting.
"""
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
# Generally, we put the file right in the cache file directory.
dir = settings.IMAGEKIT_CACHEFILE_DIR
else:
# For source files with relative names (like Django media files),
# use the source's name to create the new filename.
dir = os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
os.path.dirname(source_filename))
ext = suggest_extension(source_filename or '', generator.format)
basename = os.path.basename(source_filename)
return os.path.normpath(os.path.join(dir, '%s.%s%s' % (
os.path.splitext(basename)[0], generator.get_hash()[:12], ext)))
def hash(generator):
"""
A namer that, given the following source file name::
photos/thumbnails/bulldog.jpg
will generate a name like this::
/path/to/generated/images/5ff3233527c5ac3e4b596343b440ff67.jpg
where "/path/to/generated/images/" is the value specified by the
``IMAGEKIT_CACHEFILE_DIR`` setting.
"""
format = getattr(generator, 'format', None)
ext = format_to_extension(format) if format else ''
return os.path.normpath(os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
'%s%s' % (generator.get_hash(), ext)))

View file

@ -0,0 +1,49 @@
import six
from django.utils.functional import LazyObject
from ..lib import force_text
from ..utils import get_singleton
class JustInTime(object):
"""
A strategy that ensures the file exists right before it's needed.
"""
def on_existence_required(self, file):
file.generate()
def on_content_required(self, file):
file.generate()
class Optimistic(object):
"""
A strategy that acts immediately when the source file changes and assumes
that the cache files will not be removed (i.e. it doesn't ensure the
cache file exists when it's accessed).
"""
def on_source_saved(self, file):
file.generate()
def should_verify_existence(self, file):
return False
class DictStrategy(object):
def __init__(self, callbacks):
for k, v in callbacks.items():
setattr(self, k, v)
def load_strategy(strategy):
if isinstance(strategy, six.string_types):
strategy = get_singleton(strategy, 'cache file strategy')
elif isinstance(strategy, dict):
strategy = DictStrategy(strategy)
elif callable(strategy):
strategy = strategy()
return strategy

161
imagekit/compat.py Normal file
View file

@ -0,0 +1,161 @@
# flake8: noqa
"""
This module contains code from django.template.base
(sha 90d3af380e8efec0301dd91600c6686232de3943). Bundling this code allows us to
support older versions of Django that did not contain it (< 1.4).
Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
from django.template import TemplateSyntaxError
import re
# Regex for token keyword arguments
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
def token_kwargs(bits, parser, support_legacy=False):
"""
A utility method for parsing token keyword arguments.
:param bits: A list containing remainder of the token (split by spaces)
that is to be checked for arguments. Valid arguments will be removed
from this list.
:param support_legacy: If set to true ``True``, the legacy format
``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
format is allowed.
:returns: A dictionary of the arguments retrieved from the ``bits`` token
list.
There is no requirement for all remaining token ``bits`` to be keyword
arguments, so the dictionary will be returned as soon as an invalid
argument format is reached.
"""
if not bits:
return {}
match = kwarg_re.match(bits[0])
kwarg_format = match and match.group(1)
if not kwarg_format:
if not support_legacy:
return {}
if len(bits) < 3 or bits[1] != 'as':
return {}
kwargs = {}
while bits:
if kwarg_format:
match = kwarg_re.match(bits[0])
if not match or not match.group(1):
return kwargs
key, value = match.groups()
del bits[:1]
else:
if len(bits) < 3 or bits[1] != 'as':
return kwargs
key, value = bits[2], bits[0]
del bits[:3]
kwargs[key] = parser.compile_filter(value)
if bits and not kwarg_format:
if bits[0] != 'and':
return kwargs
del bits[:1]
return kwargs
def parse_bits(parser, bits, params, varargs, varkw, defaults,
takes_context, name):
"""
Parses bits for template tag helpers (simple_tag, include_tag and
assignment_tag), in particular by detecting syntax errors and by
extracting positional and keyword arguments.
"""
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError(
"'%s' is decorated with takes_context=True so it must "
"have a first argument of 'context'" % name)
args = []
kwargs = {}
unhandled_params = list(params)
for bit in bits:
# First we try to extract a potential kwarg from the bit
kwarg = token_kwargs([bit], parser)
if kwarg:
# The kwarg was successfully extracted
param, value = list(kwarg.items())[0]
if param not in params and varkw is None:
# An unexpected keyword argument was supplied
raise TemplateSyntaxError(
"'%s' received unexpected keyword argument '%s'" %
(name, param))
elif param in kwargs:
# The keyword argument has already been supplied once
raise TemplateSyntaxError(
"'%s' received multiple values for keyword argument '%s'" %
(name, param))
else:
# All good, record the keyword argument
kwargs[str(param)] = value
if param in unhandled_params:
# If using the keyword syntax for a positional arg, then
# consume it.
unhandled_params.remove(param)
else:
if kwargs:
raise TemplateSyntaxError(
"'%s' received some positional argument(s) after some "
"keyword argument(s)" % name)
else:
# Record the positional argument
args.append(parser.compile_filter(bit))
try:
# Consume from the list of expected positional arguments
unhandled_params.pop(0)
except IndexError:
if varargs is None:
raise TemplateSyntaxError(
"'%s' received too many positional arguments" %
name)
if defaults is not None:
# Consider the last n params handled, where n is the
# number of defaults.
unhandled_params = unhandled_params[:-len(defaults)]
if unhandled_params:
# Some positional arguments were not supplied
raise TemplateSyntaxError(
"'%s' did not receive value(s) for the argument(s): %s" %
(name, ", ".join(["'%s'" % p for p in unhandled_params])))
return args, kwargs

40
imagekit/conf.py Normal file
View file

@ -0,0 +1,40 @@
from appconf import AppConf
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
class ImageKitConf(AppConf):
CACHEFILE_NAMER = 'imagekit.cachefiles.namers.hash'
SPEC_CACHEFILE_NAMER = 'imagekit.cachefiles.namers.source_name_as_path'
CACHEFILE_DIR = 'CACHE/images'
DEFAULT_CACHEFILE_BACKEND = 'imagekit.cachefiles.backends.Simple'
DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.JustInTime'
DEFAULT_FILE_STORAGE = None
CACHE_BACKEND = None
CACHE_PREFIX = 'imagekit:'
CACHE_TIMEOUT = None
USE_MEMCACHED_SAFE_CACHE_KEY = True
def configure_cache_backend(self, value):
if value is None:
from django.core.cache import DEFAULT_CACHE_ALIAS
return DEFAULT_CACHE_ALIAS
if value not in settings.CACHES:
raise ImproperlyConfigured("{0} is not present in settings.CACHES".format(value))
return value
def configure_cache_timeout(self, value):
if value is None and settings.DEBUG:
# If value is not configured and is DEBUG set it to 5 minutes
return 300
# Otherwise leave it as is. If it is None then valies will never expire
return value
def configure_default_file_storage(self, value):
if value is None:
value = settings.DEFAULT_FILE_STORAGE
return value

View file

@ -1,25 +0,0 @@
""" Default ImageKit configuration """
from imagekit.specs import ImageSpec
from imagekit import processors
class ResizeThumbnail(processors.Resize):
width = 100
height = 50
crop = True
class EnhanceSmall(processors.Adjustment):
contrast = 1.2
sharpness = 1.1
class SampleReflection(processors.Reflection):
size = 0.5
background_color = "#000000"
class PNGFormat(processors.Format):
format = 'PNG'
extension = 'png'
class DjangoAdminThumbnail(ImageSpec):
access_as = 'admin_thumbnail'
processors = [ResizeThumbnail, EnhanceSmall, SampleReflection, PNGFormat]

22
imagekit/exceptions.py Normal file
View file

@ -0,0 +1,22 @@
from pilkit.exceptions import UnknownExtension, UnknownFormat
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class MissingGeneratorId(Exception):
pass
class MissingSource(ValueError):
pass
# Aliases for backwards compatibility
UnknownExtensionError = UnknownExtension
UnknownFormatError = UnknownFormat

110
imagekit/files.py Normal file
View file

@ -0,0 +1,110 @@
from __future__ import unicode_literals
import os
from django.core.files.base import File, ContentFile
from django.utils.encoding import smart_str
from .lib import smart_text
from .utils import format_to_mimetype, extension_to_mimetype
class BaseIKFile(File):
"""
This class contains all of the methods we need from
django.db.models.fields.files.FieldFile, but with the model stuff ripped
out. It's only extended by one class, but we keep it separate for
organizational reasons.
"""
def __init__(self, storage):
self.storage = storage
def _require_file(self):
if not self:
raise ValueError()
def _get_file(self):
self._require_file()
if not hasattr(self, '_file') or self._file is None:
self._file = self.storage.open(self.name, 'rb')
return self._file
def _set_file(self, file):
self._file = file
def _del_file(self):
del self._file
file = property(_get_file, _set_file, _del_file)
def _get_path(self):
self._require_file()
return self.storage.path(self.name)
path = property(_get_path)
def _get_url(self):
self._require_file()
return self.storage.url(self.name)
url = property(_get_url)
def _get_size(self):
self._require_file()
if not getattr(self, '_committed', False):
return self.file.size
return self.storage.size(self.name)
size = property(_get_size)
def open(self, mode='rb'):
self._require_file()
try:
self.file.open(mode)
except ValueError:
# if the underlaying file can't be reopened
# then we will use the storage to try to open it again
if self.file.closed:
# clear cached file instance
del self.file
# Because file is a property we can acces it after
# we deleted it
return self.file.open(mode)
raise
def _get_closed(self):
file = getattr(self, '_file', None)
return file is None or file.closed
closed = property(_get_closed)
def close(self):
file = getattr(self, '_file', None)
if file is not None:
file.close()
class IKContentFile(ContentFile):
"""
Wraps a ContentFile in a file-like object with a filename and a
content_type. A PIL image format can be optionally be provided as a content
type hint.
"""
def __init__(self, filename, content, format=None):
self.file = ContentFile(content)
self.file.name = filename
mimetype = getattr(self.file, 'content_type', None)
if format and not mimetype:
mimetype = format_to_mimetype(format)
if not mimetype:
ext = os.path.splitext(filename or '')[1]
mimetype = extension_to_mimetype(ext)
self.file.content_type = mimetype
@property
def name(self):
return self.file.name
def __str__(self):
return smart_str(self.file.name or '')
def __unicode__(self):
# Python 2
return smart_text(self.file.name or '')

View file

@ -0,0 +1,3 @@
# flake8: noqa
from .fields import ProcessedImageField

33
imagekit/forms/fields.py Normal file
View file

@ -0,0 +1,33 @@
from django.forms import ImageField
from ..specs import SpecHost
from ..utils import generate
class ProcessedImageField(ImageField, SpecHost):
def __init__(self, processors=None, format=None, options=None,
autoconvert=True, spec_id=None, spec=None, *args, **kwargs):
if spec_id is None:
# Unlike model fields, form fields are never told their field name.
# (Model fields are done so via `contribute_to_class()`.) Therefore
# we can't really generate a good spec id automatically.
raise TypeError('You must provide a spec_id')
SpecHost.__init__(self, processors=processors, format=format,
options=options, autoconvert=autoconvert, spec=spec,
spec_id=spec_id)
super(ProcessedImageField, self).__init__(*args, **kwargs)
def clean(self, data, initial=None):
data = super(ProcessedImageField, self).clean(data, initial)
if data and data != initial:
spec = self.get_spec(source=data)
f = generate(spec)
# Name is required in Django 1.4. When we drop support for it
# then we can dirrectly return the result from `generate(spec)`
f.name = data.name
return f
return data

View file

@ -0,0 +1,13 @@
from .registry import register
from .processors import Thumbnail as ThumbnailProcessor
from .specs import ImageSpec
class Thumbnail(ImageSpec):
def __init__(self, width=None, height=None, anchor=None, crop=None, upscale=None, **kwargs):
self.processors = [ThumbnailProcessor(width, height, anchor=anchor,
crop=crop, upscale=upscale)]
super(Thumbnail, self).__init__(**kwargs)
register.generator('imagekit:thumbnail', Thumbnail)

35
imagekit/hashers.py Normal file
View file

@ -0,0 +1,35 @@
from copy import copy
from hashlib import md5
from pickle import MARK, DICT
try:
from pickle import _Pickler
except ImportError:
# Python 2 compatible
from pickle import Pickler as _Pickler
from .lib import StringIO
class CanonicalizingPickler(_Pickler):
dispatch = copy(_Pickler.dispatch)
def save_set(self, obj):
rv = obj.__reduce_ex__(0)
rv = (rv[0], (sorted(rv[1][0]),), rv[2])
self.save_reduce(obj=obj, *rv)
dispatch[set] = save_set
def save_dict(self, obj):
write = self.write
write(MARK + DICT)
self.memoize(obj)
self._batch_setitems(sorted(obj.items()))
dispatch[dict] = save_dict
def pickle(obj):
file = StringIO()
CanonicalizingPickler(file, 0).dump(obj)
return md5(file.getvalue()).hexdigest()

View file

@ -1,17 +1,52 @@
# flake8: noqa
# Required PIL classes may or may not be available from the root namespace
# depending on the installation method used.
try:
import Image
import ImageFile
import ImageFilter
import ImageEnhance
import ImageColor
from PIL import Image, ImageColor, ImageChops, ImageEnhance, ImageFile, \
ImageFilter, ImageDraw, ImageStat
except ImportError:
try:
from PIL import Image
from PIL import ImageFile
from PIL import ImageFilter
from PIL import ImageEnhance
from PIL import ImageColor
import Image
import ImageColor
import ImageChops
import ImageEnhance
import ImageFile
import ImageFilter
import ImageDraw
import ImageStat
except ImportError:
raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')
try:
from io import BytesIO as StringIO
except:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
from logging import NullHandler
except ImportError:
from logging import Handler
class NullHandler(Handler):
def emit(self, record):
pass
# Try to import `force_text` available from Django 1.5
# This function will replace `unicode` used in the code
# If Django version is under 1.5 then use `force_unicde`
# It is used for compatibility between Python 2 and Python 3
try:
from django.utils.encoding import force_text, force_bytes, smart_text
except ImportError:
# Django < 1.5
from django.utils.encoding import (force_unicode as force_text,
smart_str as force_bytes,
smart_unicode as smart_text)
__all__ = ['Image', 'ImageColor', 'ImageChops', 'ImageEnhance', 'ImageFile',
'ImageFilter', 'ImageDraw', 'ImageStat', 'StringIO', 'NullHandler',
'force_text', 'force_bytes', 'smart_text']

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -0,0 +1,53 @@
from django.core.management.base import BaseCommand
import re
from ...registry import generator_registry, cachefile_registry
from ...exceptions import MissingSource
class Command(BaseCommand):
help = ("""Generate files for the specified image generators (or all of them if
none was provided). Simple, glob-like wildcards are allowed, with *
matching all characters within a segment, and ** matching across
segments. (Segments are separated with colons.) So, for example,
"a:*:c" will match "a:b:c", but not "a:b:x:c", whereas "a:**:c" will
match both. Subsegments are always matched, so "a" will match "a" as
well as "a:b" and "a:b:c".""")
args = '[generator_ids]'
def add_arguments(self, parser):
parser.add_argument('generator_id', nargs='*', help='<app_name>:<model>:<field> for model specs')
def handle(self, *args, **options):
generators = generator_registry.get_ids()
generator_ids = options['generator_id'] if 'generator_id' in options else args
if generator_ids:
patterns = self.compile_patterns(generator_ids)
generators = (id for id in generators if any(p.match(id) for p in patterns))
for generator_id in generators:
self.stdout.write('Validating generator: %s\n' % generator_id)
for image_file in cachefile_registry.get(generator_id):
if image_file.name:
self.stdout.write(' %s\n' % image_file.name)
try:
image_file.generate()
except MissingSource as err:
self.stdout.write('\t No source associated with\n')
except Exception as err:
self.stdout.write('\tFailed %s\n' % (err))
def compile_patterns(self, generator_ids):
return [self.compile_pattern(id) for id in generator_ids]
def compile_pattern(self, generator_id):
parts = re.split(r'(\*{1,2})', generator_id)
pattern = ''
for part in parts:
if part == '*':
pattern += '[^:]*'
elif part == '**':
pattern += '.*'
else:
pattern += re.escape(part)
return re.compile('^%s(:.*)?$' % pattern)

View file

@ -1,36 +0,0 @@
from django.db.models.loading import cache
from django.core.management.base import BaseCommand, CommandError
from optparse import make_option
from imagekit.models import ImageModel
from imagekit.specs import ImageSpec
class Command(BaseCommand):
help = ('Clears all ImageKit cached files.')
args = '[apps]'
requires_model_validation = True
can_import_settings = True
def handle(self, *args, **options):
return flush_cache(args, options)
def flush_cache(apps, options):
""" Clears the image cache
"""
apps = [a.strip(',') for a in apps]
if apps:
for app_label in apps:
app = cache.get_app(app_label)
models = [m for m in cache.get_models(app) if issubclass(m, ImageModel)]
for model in models:
print 'Flushing cache for "%s.%s"' % (app_label, model.__name__)
for obj in model.objects.order_by('-id'):
for spec in model._ik.specs:
prop = getattr(obj, spec.name(), None)
if prop is not None:
prop._delete()
if spec.pre_cache:
prop._create()
else:
print 'Please specify on or more app names'

View file

@ -1,160 +0,0 @@
import os
from datetime import datetime
from django.conf import settings
from django.core.files.base import ContentFile
from django.db import models
from django.db.models.base import ModelBase
from django.db.models.signals import post_delete
from django.utils.html import conditional_escape as escape
from django.utils.translation import ugettext_lazy as _
from imagekit import specs
from imagekit.lib import *
from imagekit.options import Options
from imagekit.utils import img_to_fobj
# Modify image file buffer size.
ImageFile.MAXBLOCK = getattr(settings, 'PIL_IMAGEFILE_MAXBLOCK', 256 * 2 ** 10)
# Choice tuples for specifying the crop origin.
# These are provided for convenience.
CROP_HORZ_CHOICES = (
(0, _('left')),
(1, _('center')),
(2, _('right')),
)
CROP_VERT_CHOICES = (
(0, _('top')),
(1, _('center')),
(2, _('bottom')),
)
class ImageModelBase(ModelBase):
""" ImageModel metaclass
This metaclass parses IKOptions and loads the specified specification
module.
"""
def __init__(cls, name, bases, attrs):
parents = [b for b in bases if isinstance(b, ImageModelBase)]
if not parents:
return
user_opts = getattr(cls, 'IKOptions', None)
opts = Options(user_opts)
if not opts.specs:
try:
module = __import__(opts.spec_module, {}, {}, [''])
except ImportError:
raise ImportError('Unable to load imagekit config module: %s' \
% opts.spec_module)
opts.specs.extend([spec for spec in module.__dict__.values() \
if isinstance(spec, type) \
and issubclass(spec, specs.ImageSpec) \
and spec != specs.ImageSpec])
for spec in opts.specs:
setattr(cls, spec.name(), specs.Descriptor(spec))
setattr(cls, '_ik', opts)
class ImageModel(models.Model):
""" Abstract base class implementing all core ImageKit functionality
Subclasses of ImageModel are augmented with accessors for each defined
image specification and can override the inner IKOptions class to customize
storage locations and other options.
"""
__metaclass__ = ImageModelBase
class Meta:
abstract = True
class IKOptions:
pass
def admin_thumbnail_view(self):
if not self._imgfield:
return None
prop = getattr(self, self._ik.admin_thumbnail_spec, None)
if prop is None:
return 'An "%s" image spec has not been defined.' % \
self._ik.admin_thumbnail_spec
else:
if hasattr(self, 'get_absolute_url'):
return u'<a href="%s"><img src="%s"></a>' % \
(escape(self.get_absolute_url()), escape(prop.url))
else:
return u'<a href="%s"><img src="%s"></a>' % \
(escape(self._imgfield.url), escape(prop.url))
admin_thumbnail_view.short_description = _('Thumbnail')
admin_thumbnail_view.allow_tags = True
@property
def _imgfield(self):
return getattr(self, self._ik.image_field)
@property
def _storage(self):
return getattr(self._ik, 'storage', self._imgfield.storage)
def _clear_cache(self):
for spec in self._ik.specs:
prop = getattr(self, spec.name())
prop._delete()
def _pre_cache(self):
for spec in self._ik.specs:
if spec.pre_cache:
prop = getattr(self, spec.name())
prop._create()
def save_image(self, name, image, save=True, replace=True):
if self._imgfield and replace:
self._imgfield.delete(save=False)
if hasattr(image, 'read'):
data = image.read()
else:
data = image
content = ContentFile(data)
self._imgfield.save(name, content, save)
def save(self, clear_cache=True, *args, **kwargs):
super(ImageModel, self).save(*args, **kwargs)
is_new_object = self._get_pk_val() is None
if is_new_object:
clear_cache = False
if self._imgfield:
spec = self._ik.preprocessor_spec
if spec is not None:
newfile = self._imgfield.storage.open(str(self._imgfield))
img = Image.open(newfile)
img, format = spec.process(img, self)
if format != 'JPEG':
imgfile = img_to_fobj(img, format)
else:
imgfile = img_to_fobj(img, format,
quality=int(spec.quality),
optimize=True)
content = ContentFile(imgfile.read())
newfile.close()
name = str(self._imgfield)
self._imgfield.storage.delete(name)
self._imgfield.storage.save(name, content)
if self._imgfield:
if clear_cache:
self._clear_cache()
self._pre_cache()
def clear_cache(self, **kwargs):
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
self._clear_cache()
post_delete.connect(ImageModel.clear_cache, sender=ImageModel)

View file

@ -0,0 +1,4 @@
# flake8: noqa
from .. import conf
from .fields import ImageSpecField, ProcessedImageField

View file

@ -0,0 +1,126 @@
from __future__ import unicode_literals
from django.conf import settings
from django.db import models
from django.db.models.signals import class_prepared
from .files import ProcessedImageFieldFile
from .utils import ImageSpecFileDescriptor
from ...specs import SpecHost
from ...specs.sourcegroups import ImageFieldSourceGroup
from ...registry import register
class SpecHostField(SpecHost):
def _set_spec_id(self, cls, name):
spec_id = getattr(self, 'spec_id', None)
# Generate a spec_id to register the spec with. The default spec id is
# "<app>:<model>_<field>"
if not spec_id:
spec_id = ('%s:%s:%s' % (cls._meta.app_label,
cls._meta.object_name, name)).lower()
# Register the spec with the id. This allows specs to be overridden
# later, from outside of the model definition.
super(SpecHostField, self).set_spec_id(spec_id)
class ImageSpecField(SpecHostField):
"""
The heart and soul of the ImageKit library, ImageSpecField allows you to add
variants of uploaded images to your models.
"""
def __init__(self, processors=None, format=None, options=None,
source=None, cachefile_storage=None, autoconvert=None,
cachefile_backend=None, cachefile_strategy=None, spec=None,
id=None):
SpecHost.__init__(self, processors=processors, format=format,
options=options, cachefile_storage=cachefile_storage,
autoconvert=autoconvert,
cachefile_backend=cachefile_backend,
cachefile_strategy=cachefile_strategy, spec=spec,
spec_id=id)
# TODO: Allow callable for source. See https://github.com/matthewwithanm/django-imagekit/issues/158#issuecomment-10921664
self.source = source
def contribute_to_class(self, cls, name):
# If the source field name isn't defined, figure it out.
def register_source_group(source):
setattr(cls, name, ImageSpecFileDescriptor(self, name, source))
self._set_spec_id(cls, name)
# Add the model and field as a source for this spec id
register.source_group(self.spec_id, ImageFieldSourceGroup(cls, source))
if self.source:
register_source_group(self.source)
else:
# The source argument is not defined
# Then we need to see if there is only one ImageField in that model
# But we need to do that after full model initialization
def handle_model_preparation(sender, **kwargs):
image_fields = [f.attname for f in cls._meta.fields if
isinstance(f, models.ImageField)]
if len(image_fields) == 0:
raise Exception(
'%s does not define any ImageFields, so your %s'
' ImageSpecField has no image to act on.' %
(cls.__name__, name))
elif len(image_fields) > 1:
raise Exception(
'%s defines multiple ImageFields, but you have not'
' specified a source for your %s ImageSpecField.' %
(cls.__name__, name))
register_source_group(image_fields[0])
class_prepared.connect(handle_model_preparation, sender=cls, weak=False)
class ProcessedImageField(models.ImageField, SpecHostField):
"""
ProcessedImageField is an ImageField that runs processors on the uploaded
image *before* saving it to storage. This is in contrast to specs, which
maintain the original. Useful for coercing fileformats or keeping images
within a reasonable size.
"""
attr_class = ProcessedImageFieldFile
def __init__(self, processors=None, format=None, options=None,
verbose_name=None, name=None, width_field=None, height_field=None,
autoconvert=None, spec=None, spec_id=None, **kwargs):
"""
The ProcessedImageField constructor accepts all of the arguments that
the :class:`django.db.models.ImageField` constructor accepts, as well
as the ``processors``, ``format``, and ``options`` arguments of
:class:`imagekit.models.ImageSpecField`.
"""
# if spec is not provided then autoconvert will be True by default
if spec is None and autoconvert is None:
autoconvert = True
SpecHost.__init__(self, processors=processors, format=format,
options=options, autoconvert=autoconvert, spec=spec,
spec_id=spec_id)
models.ImageField.__init__(self, verbose_name, name, width_field,
height_field, **kwargs)
def contribute_to_class(self, cls, name):
self._set_spec_id(cls, name)
return super(ProcessedImageField, self).contribute_to_class(cls, name)
# If the project does not use south, then we will not try to add introspection
if 'south' in settings.INSTALLED_APPS:
try:
from south.modelsinspector import add_introspection_rules
except ImportError:
pass
else:
add_introspection_rules([], [r'^imagekit\.models\.fields\.ProcessedImageField$'])

View file

@ -0,0 +1,13 @@
from django.db.models.fields.files import ImageFieldFile
import os
from ...utils import suggest_extension, generate
class ProcessedImageFieldFile(ImageFieldFile):
def save(self, name, content, save=True):
filename, ext = os.path.splitext(name)
spec = self.field.get_spec(source=content)
ext = suggest_extension(name, spec.format)
new_name = '%s%s' % (filename, ext)
content = generate(spec)
return super(ProcessedImageFieldFile, self).save(new_name, content, save)

View file

@ -0,0 +1,21 @@
from ...cachefiles import ImageCacheFile
class ImageSpecFileDescriptor(object):
def __init__(self, field, attname, source_field_name):
self.attname = attname
self.field = field
self.source_field_name = source_field_name
def __get__(self, instance, owner):
if instance is None:
return self.field
else:
source = getattr(instance, self.source_field_name)
spec = self.field.get_spec(source=source)
file = ImageCacheFile(spec)
instance.__dict__[self.attname] = file
return file
def __set__(self, instance, value):
instance.__dict__[self.attname] = value

View file

@ -1,26 +0,0 @@
# Imagekit options
from imagekit import processors
from imagekit.specs import ImageSpec
class Options(object):
""" Class handling per-model imagekit options
"""
image_field = 'image'
crop_horz_field = 'crop_horz'
crop_vert_field = 'crop_vert'
preprocessor_spec = None
cache_dir = 'cache'
save_count_as = None
cache_filename_fields = ['pk', ]
cache_filename_format = "%(filename)s_%(specname)s.%(extension)s"
admin_thumbnail_spec = 'admin_thumbnail'
spec_module = 'imagekit.defaults'
specs = None
#storage = defaults to image_field.storage
def __init__(self, opts):
for key, value in opts.__dict__.iteritems():
setattr(self, key, value)
self.specs = list(self.specs or [])

5
imagekit/pkgmeta.py Normal file
View file

@ -0,0 +1,5 @@
__title__ = 'django-imagekit'
__author__ = 'Matthew Tretter, Venelin Stoykov, Eric Eldredge, Bryan Veloso, Greg Newman, Chris Drackett, Justin Driscoll'
__version__ = '4.0.2'
__license__ = 'BSD'
__all__ = ['__title__', '__author__', '__version__', '__license__']

View file

@ -1,177 +0,0 @@
""" Imagekit Image "ImageProcessors"
A processor defines a set of class variables (optional) and a
class method named "process" which processes the supplied image using
the class properties as settings. The process method can be overridden as well allowing user to define their
own effects/processes entirely.
"""
from imagekit.lib import *
class ImageProcessor(object):
""" Base image processor class """
@classmethod
def process(cls, img, fmt, obj):
return img, fmt
class Adjustment(ImageProcessor):
color = 1.0
brightness = 1.0
contrast = 1.0
sharpness = 1.0
@classmethod
def process(cls, img, fmt, obj):
img = img.convert('RGB')
for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
factor = getattr(cls, name.lower())
if factor != 1.0:
try:
img = getattr(ImageEnhance, name)(img).enhance(factor)
except ValueError:
pass
return img, fmt
class Format(ImageProcessor):
format = 'JPEG'
extension = 'jpg'
@classmethod
def process(cls, img, fmt, obj):
return img, cls.format
class Reflection(ImageProcessor):
background_color = '#FFFFFF'
size = 0.0
opacity = 0.6
@classmethod
def process(cls, img, fmt, obj):
# convert bgcolor string to rgb value
background_color = ImageColor.getrgb(cls.background_color)
# handle palleted images
img = img.convert('RGB')
# copy orignial image and flip the orientation
reflection = img.copy().transpose(Image.FLIP_TOP_BOTTOM)
# create a new image filled with the bgcolor the same size
background = Image.new("RGB", img.size, background_color)
# calculate our alpha mask
start = int(255 - (255 * cls.opacity)) # The start of our gradient
steps = int(255 * cls.size) # the number of intermedite values
increment = (255 - start) / float(steps)
mask = Image.new('L', (1, 255))
for y in range(255):
if y < steps:
val = int(y * increment + start)
else:
val = 255
mask.putpixel((0, y), val)
alpha_mask = mask.resize(img.size)
# merge the reflection onto our background color using the alpha mask
reflection = Image.composite(background, reflection, alpha_mask)
# crop the reflection
reflection_height = int(img.size[1] * cls.size)
reflection = reflection.crop((0, 0, img.size[0], reflection_height))
# create new image sized to hold both the original image and the reflection
composite = Image.new("RGB", (img.size[0], img.size[1]+reflection_height), background_color)
# paste the orignal image and the reflection into the composite image
composite.paste(img, (0, 0))
composite.paste(reflection, (0, img.size[1]))
# Save the file as a JPEG
fmt = 'JPEG'
# return the image complete with reflection effect
return composite, fmt
class Resize(ImageProcessor):
width = None
height = None
crop = False
upscale = False
@classmethod
def process(cls, img, fmt, obj):
cur_width, cur_height = img.size
if cls.crop:
crop_horz = getattr(obj, obj._ik.crop_horz_field, 1)
crop_vert = getattr(obj, obj._ik.crop_vert_field, 1)
ratio = max(float(cls.width)/cur_width, float(cls.height)/cur_height)
resize_x, resize_y = ((cur_width * ratio), (cur_height * ratio))
crop_x, crop_y = (abs(cls.width - resize_x), abs(cls.height - resize_y))
x_diff, y_diff = (int(crop_x / 2), int(crop_y / 2))
box_left, box_right = {
0: (0, cls.width),
1: (int(x_diff), int(x_diff + cls.width)),
2: (int(crop_x), int(resize_x)),
}[crop_horz]
box_upper, box_lower = {
0: (0, cls.height),
1: (int(y_diff), int(y_diff + cls.height)),
2: (int(crop_y), int(resize_y)),
}[crop_vert]
box = (box_left, box_upper, box_right, box_lower)
img = img.resize((int(resize_x), int(resize_y)), Image.ANTIALIAS).crop(box)
else:
if not cls.width is None and not cls.height is None:
ratio = min(float(cls.width)/cur_width,
float(cls.height)/cur_height)
else:
if cls.width is None:
ratio = float(cls.height)/cur_height
else:
ratio = float(cls.width)/cur_width
new_dimensions = (int(round(cur_width*ratio)),
int(round(cur_height*ratio)))
if new_dimensions[0] > cur_width or \
new_dimensions[1] > cur_height:
if not cls.upscale:
return img, fmt
img = img.resize(new_dimensions, Image.ANTIALIAS)
return img, fmt
class Transpose(ImageProcessor):
""" Rotates or flips the image
Method should be one of the following strings:
- FLIP_LEFT RIGHT
- FLIP_TOP_BOTTOM
- ROTATE_90
- ROTATE_270
- ROTATE_180
- auto
If method is set to 'auto' the processor will attempt to rotate the image
according to the EXIF Orientation data.
"""
EXIF_ORIENTATION_STEPS = {
1: [],
2: ['FLIP_LEFT_RIGHT'],
3: ['ROTATE_180'],
4: ['FLIP_TOP_BOTTOM'],
5: ['ROTATE_270', 'FLIP_LEFT_RIGHT'],
6: ['ROTATE_270'],
7: ['ROTATE_90', 'FLIP_LEFT_RIGHT'],
8: ['ROTATE_90'],
}
method = 'auto'
@classmethod
def process(cls, img, fmt, obj):
if cls.method == 'auto':
try:
orientation = Image.open(obj._imgfield.file)._getexif()[0x0112]
ops = cls.EXIF_ORIENTATION_STEPS[orientation]
except:
ops = []
else:
ops = [cls.method]
for method in ops:
img = img.transpose(getattr(Image, method))
return img, fmt

View file

@ -0,0 +1,12 @@
from pilkit.processors import *
__all__ = [
# Base
'ProcessorPipeline', 'Adjust', 'Reflection', 'Transpose',
'Anchor', 'MakeOpaque',
# Crop
'TrimBorderColor', 'Crop', 'SmartCrop',
# Resize
'Resize', 'ResizeToCover', 'ResizeToFill', 'SmartResize',
'ResizeCanvas', 'AddBorder', 'ResizeToFit', 'Thumbnail'
]

View file

@ -0,0 +1,7 @@
import warnings
from pilkit.processors.base import *
warnings.warn('imagekit.processors.base is deprecated use imagekit.processors instead', DeprecationWarning)
__all__ = ['ProcessorPipeline', 'Adjust', 'Reflection', 'Transpose', 'Anchor', 'MakeOpaque']

View file

@ -0,0 +1,7 @@
import warnings
from pilkit.processors.crop import *
warnings.warn('imagekit.processors.crop is deprecated use imagekit.processors instead', DeprecationWarning)
__all__ = ['TrimBorderColor', 'Crop', 'SmartCrop']

View file

@ -0,0 +1,7 @@
import warnings
from pilkit.processors.resize import *
warnings.warn('imagekit.processors.resize is deprecated use imagekit.processors instead', DeprecationWarning)
__all__ = ['Resize', 'ResizeToCover', 'ResizeToFill', 'SmartResize', 'ResizeCanvas', 'AddBorder', 'ResizeToFit', 'Thumbnail']

View file

@ -0,0 +1,5 @@
import warnings
from pilkit.processors.utils import *
warnings.warn('imagekit.processors.utils is deprecated use pilkit.processors.utils instead', DeprecationWarning)

201
imagekit/registry.py Normal file
View file

@ -0,0 +1,201 @@
from .exceptions import AlreadyRegistered, NotRegistered
from .signals import content_required, existence_required, source_saved
from .utils import autodiscover, call_strategy_method
class GeneratorRegistry(object):
"""
An object for registering generators. This registry provides
a convenient way for a distributable app to define default generators
without locking the users of the app into it.
"""
def __init__(self):
self._generators = {}
content_required.connect(self.content_required_receiver)
existence_required.connect(self.existence_required_receiver)
def register(self, id, generator):
registered_generator = self._generators.get(id)
if registered_generator and generator != self._generators[id]:
raise AlreadyRegistered('The generator with id %s is'
' already registered' % id)
self._generators[id] = generator
def unregister(self, id):
try:
del self._generators[id]
except KeyError:
raise NotRegistered('The generator with id %s is not'
' registered' % id)
def get(self, id, **kwargs):
autodiscover()
try:
generator = self._generators[id]
except KeyError:
raise NotRegistered('The generator with id %s is not'
' registered' % id)
if callable(generator):
return generator(**kwargs)
else:
return generator
def get_ids(self):
autodiscover()
return self._generators.keys()
def content_required_receiver(self, sender, file, **kwargs):
self._receive(file, 'on_content_required')
def existence_required_receiver(self, sender, file, **kwargs):
self._receive(file, 'on_existence_required')
def _receive(self, file, callback):
generator = file.generator
# FIXME: I guess this means you can't register functions?
if generator.__class__ in self._generators.values():
# Only invoke the strategy method for registered generators.
call_strategy_method(file, callback)
class SourceGroupRegistry(object):
"""
The source group registry is responsible for listening to source_* signals
on source groups, and relaying them to the image generated file strategies
of the appropriate generators.
In addition, registering a new source group also registers its generated
files with that registry.
"""
_signals = {
source_saved: 'on_source_saved',
}
def __init__(self):
self._source_groups = {}
for signal in self._signals.keys():
signal.connect(self.source_group_receiver)
def register(self, generator_id, source_group):
from .specs.sourcegroups import SourceGroupFilesGenerator
generator_ids = self._source_groups.setdefault(source_group, set())
generator_ids.add(generator_id)
cachefile_registry.register(generator_id,
SourceGroupFilesGenerator(source_group, generator_id))
def unregister(self, generator_id, source_group):
from .specs.sourcegroups import SourceGroupFilesGenerator
generator_ids = self._source_groups.setdefault(source_group, set())
if generator_id in generator_ids:
generator_ids.remove(generator_id)
cachefile_registry.unregister(generator_id,
SourceGroupFilesGenerator(source_group, generator_id))
def source_group_receiver(self, sender, source, signal, **kwargs):
"""
Relay source group signals to the appropriate spec strategy.
"""
from .cachefiles import ImageCacheFile
source_group = sender
# Ignore signals from unregistered groups.
if source_group not in self._source_groups:
return
specs = [generator_registry.get(id, source=source) for id in
self._source_groups[source_group]]
callback_name = self._signals[signal]
for spec in specs:
file = ImageCacheFile(spec)
call_strategy_method(file, callback_name)
class CacheFileRegistry(object):
"""
An object for registering generated files with image generators. The two are
associated with each other via a string id. We do this (as opposed to
associating them directly by, for example, putting a ``cachefiles``
attribute on image generators) so that image generators can be overridden
without losing the associated files. That way, a distributable app can
define its own generators without locking the users of the app into it.
"""
def __init__(self):
self._cachefiles = {}
def register(self, generator_id, cachefiles):
"""
Associates generated files with a generator id
"""
if cachefiles not in self._cachefiles:
self._cachefiles[cachefiles] = set()
self._cachefiles[cachefiles].add(generator_id)
def unregister(self, generator_id, cachefiles):
"""
Disassociates generated files with a generator id
"""
try:
self._cachefiles[cachefiles].remove(generator_id)
except KeyError:
pass
def get(self, generator_id):
for k, v in self._cachefiles.items():
if generator_id in v:
for file in k():
yield file
class Register(object):
"""
Register generators and generated files.
"""
def generator(self, id, generator=None):
if generator is None:
# Return a decorator
def decorator(cls):
self.generator(id, cls)
return cls
return decorator
generator_registry.register(id, generator)
# iterable that returns kwargs or callable that returns iterable of kwargs
def cachefiles(self, generator_id, cachefiles):
cachefile_registry.register(generator_id, cachefiles)
def source_group(self, generator_id, source_group):
source_group_registry.register(generator_id, source_group)
class Unregister(object):
"""
Unregister generators and generated files.
"""
def generator(self, id):
generator_registry.unregister(id)
def cachefiles(self, generator_id, cachefiles):
cachefile_registry.unregister(generator_id, cachefiles)
def source_group(self, generator_id, source_group):
source_group_registry.unregister(generator_id, source_group)
generator_registry = GeneratorRegistry()
cachefile_registry = CacheFileRegistry()
source_group_registry = SourceGroupRegistry()
register = Register()
unregister = Unregister()

9
imagekit/signals.py Normal file
View file

@ -0,0 +1,9 @@
from django.dispatch import Signal
# Generated file signals
content_required = Signal()
existence_required = Signal()
# Source group signals
source_saved = Signal()

View file

@ -1,145 +0,0 @@
""" ImageKit image specifications
All imagekit specifications must inherit from the ImageSpec class. Models
inheriting from ImageModel will be modified with a descriptor/accessor for each
spec found.
"""
import os
from StringIO import StringIO
from imagekit import processors
from imagekit.lib import *
from imagekit.utils import img_to_fobj
from django.core.files.base import ContentFile
class ImageSpec(object):
pre_cache = False
quality = 70
increment_count = False
processors = []
@classmethod
def name(cls):
return getattr(cls, 'access_as', cls.__name__.lower())
@classmethod
def process(cls, image, obj):
fmt = image.format
img = image.copy()
for proc in cls.processors:
img, fmt = proc.process(img, fmt, obj)
img.format = fmt
return img, fmt
class Accessor(object):
def __init__(self, obj, spec):
self._img = None
self._fmt = None
self._obj = obj
self.spec = spec
def _get_imgfile(self):
format = self._img.format or 'JPEG'
if format != 'JPEG':
imgfile = img_to_fobj(self._img, format)
else:
imgfile = img_to_fobj(self._img, format,
quality=int(self.spec.quality),
optimize=True)
return imgfile
def _create(self):
if self._obj._imgfield:
if self._exists():
return
# process the original image file
try:
fp = self._obj._imgfield.storage.open(self._obj._imgfield.name)
except IOError:
return
fp.seek(0)
fp = StringIO(fp.read())
self._img, self._fmt = self.spec.process(Image.open(fp), self._obj)
# save the new image to the cache
content = ContentFile(self._get_imgfile().read())
self._obj._storage.save(self.name, content)
def _delete(self):
if self._obj._imgfield:
try:
self._obj._storage.delete(self.name)
except (NotImplementedError, IOError):
return
def _exists(self):
if self._obj._imgfield:
return self._obj._storage.exists(self.name)
@property
def name(self):
if self._obj._imgfield.name:
filepath, basename = os.path.split(self._obj._imgfield.name)
filename, extension = os.path.splitext(basename)
for processor in self.spec.processors:
if issubclass(processor, processors.Format):
extension = processor.extension
filename_format_dict = {'filename': filename,
'specname': self.spec.name(),
'extension': extension.lstrip('.')}
cache_filename_fields = self._obj._ik.cache_filename_fields
filename_format_dict.update(dict(zip(
cache_filename_fields,
[getattr(self._obj, field) for
field in cache_filename_fields])))
cache_filename = self._obj._ik.cache_filename_format % \
filename_format_dict
if callable(self._obj._ik.cache_dir):
return self._obj._ik.cache_dir(self._obj, filepath,
cache_filename)
else:
return os.path.join(self._obj._ik.cache_dir, filepath,
cache_filename)
@property
def url(self):
if not self.spec.pre_cache:
self._create()
if self.spec.increment_count:
fieldname = self._obj._ik.save_count_as
if fieldname is not None:
current_count = getattr(self._obj, fieldname)
setattr(self._obj, fieldname, current_count + 1)
self._obj.save(clear_cache=False)
return self._obj._storage.url(self.name)
@property
def file(self):
self._create()
return self._obj._storage.open(self.name)
@property
def image(self):
if not self._img:
self._create()
if not self._img:
self._img = Image.open(self.file)
return self._img
@property
def width(self):
return self.image.size[0]
@property
def height(self):
return self.image.size[1]
class Descriptor(object):
def __init__(self, spec):
self._spec = spec
def __get__(self, obj, type=None):
return Accessor(obj, self._spec)

250
imagekit/specs/__init__.py Normal file
View file

@ -0,0 +1,250 @@
from copy import copy
from django.conf import settings
from django.db.models.fields.files import ImageFieldFile
from ..cachefiles.backends import get_default_cachefile_backend
from ..cachefiles.strategies import load_strategy
from .. import hashers
from ..exceptions import AlreadyRegistered, MissingSource
from ..utils import open_image, get_by_qname, process_image
from ..registry import generator_registry, register
class BaseImageSpec(object):
"""
An object that defines how an new image should be generated from a source
image.
"""
cachefile_storage = None
"""A Django storage system to use to save a cache file."""
cachefile_backend = None
"""
An object responsible for managing the state of cache files. Defaults to
an instance of ``IMAGEKIT_DEFAULT_CACHEFILE_BACKEND``
"""
cachefile_strategy = settings.IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY
"""
A dictionary containing callbacks that allow you to customize how and when
the image file is created. Defaults to
``IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY``.
"""
def __init__(self):
self.cachefile_backend = self.cachefile_backend or get_default_cachefile_backend()
self.cachefile_strategy = load_strategy(self.cachefile_strategy)
def generate(self):
raise NotImplementedError
MissingSource = MissingSource
"""
Raised when an operation requiring a source is attempted on a spec that has
no source.
"""
class ImageSpec(BaseImageSpec):
"""
An object that defines how to generate a new image from a source file using
PIL-based processors. (See :mod:`imagekit.processors`)
"""
processors = []
"""A list of processors to run on the original image."""
format = None
"""
The format of the output file. If not provided, ImageSpecField will try to
guess the appropriate format based on the extension of the filename and the
format of the input image.
"""
options = None
"""
A dictionary that will be passed to PIL's ``Image.save()`` method as keyword
arguments. Valid options vary between formats, but some examples include
``quality``, ``optimize``, and ``progressive`` for JPEGs. See the PIL
documentation for others.
"""
autoconvert = True
"""
Specifies whether automatic conversion using ``prepare_image()`` should be
performed prior to saving.
"""
def __init__(self, source):
self.source = source
super(ImageSpec, self).__init__()
@property
def cachefile_name(self):
if not self.source:
return None
fn = get_by_qname(settings.IMAGEKIT_SPEC_CACHEFILE_NAMER, 'namer')
return fn(self)
@property
def source(self):
src = getattr(self, '_source', None)
if not src:
field_data = getattr(self, '_field_data', None)
if field_data:
src = self._source = getattr(field_data['instance'], field_data['attname'])
del self._field_data
return src
@source.setter
def source(self, value):
self._source = value
def __getstate__(self):
state = copy(self.__dict__)
# Unpickled ImageFieldFiles won't work (they're missing a storage
# object). Since they're such a common use case, we special case them.
# Unfortunately, this also requires us to add the source getter to
# lazily retrieve the source on the reconstructed object; simply trying
# to look up the source in ``__setstate__`` would require us to get the
# model instance but, if ``__setstate__`` was called as part of
# deserializing that model, the model wouldn't be fully reconstructed
# yet, preventing us from accessing the source field.
# (This is issue #234.)
if isinstance(self.source, ImageFieldFile):
field = getattr(self.source, 'field')
state['_field_data'] = {
'instance': getattr(self.source, 'instance', None),
'attname': getattr(field, 'name', None),
}
state.pop('_source', None)
return state
def get_hash(self):
return hashers.pickle([
self.source.name,
self.processors,
self.format,
self.options,
self.autoconvert,
])
def generate(self):
if not self.source:
raise MissingSource("The spec '%s' has no source file associated"
" with it." % self)
# TODO: Move into a generator base class
# TODO: Factor out a generate_image function so you can create a generator and only override the PIL.Image creating part. (The tricky part is how to deal with original_format since generator base class won't have one.)
closed = self.source.closed
if closed:
# Django file object should know how to reopen itself if it was closed
# https://code.djangoproject.com/ticket/13750
self.source.open()
try:
img = open_image(self.source)
new_image = process_image(img,
processors=self.processors,
format=self.format,
autoconvert=self.autoconvert,
options=self.options)
finally:
if closed:
# We need to close the file if it was opened by us
self.source.close()
return new_image
def create_spec_class(class_attrs):
class DynamicSpecBase(ImageSpec):
def __reduce__(self):
try:
getstate = self.__getstate__
except AttributeError:
state = self.__dict__
else:
state = getstate()
return (create_spec, (class_attrs, state))
return type('DynamicSpec', (DynamicSpecBase,), class_attrs)
def create_spec(class_attrs, state):
cls = create_spec_class(class_attrs)
instance = cls.__new__(cls) # Create an instance without calling the __init__ (which may have required args).
try:
setstate = instance.__setstate__
except AttributeError:
instance.__dict__ = state
else:
setstate(state)
return instance
class SpecHost(object):
"""
An object that ostensibly has a spec attribute but really delegates to the
spec registry.
"""
def __init__(self, spec=None, spec_id=None, **kwargs):
spec_attrs = dict((k, v) for k, v in kwargs.items() if v is not None)
if spec_attrs:
if spec:
raise TypeError('You can provide either an image spec or'
' arguments for the ImageSpec constructor, but not both.')
else:
spec = create_spec_class(spec_attrs)
self._original_spec = spec
if spec_id:
self.set_spec_id(spec_id)
def set_spec_id(self, id):
"""
Sets the spec id for this object. Useful for when the id isn't
known when the instance is constructed (e.g. for ImageSpecFields whose
generated `spec_id`s are only known when they are contributed to a
class). If the object was initialized with a spec, it will be registered
under the provided id.
"""
self.spec_id = id
if self._original_spec:
try:
register.generator(id, self._original_spec)
except AlreadyRegistered:
# Fields should not cause AlreadyRegistered exceptions. If a
# spec is already registered, that should be used. It is
# especially important that an error is not thrown here because
# of South, which will create duplicate models as part of its
# "fake orm," therefore re-registering specs.
pass
def get_spec(self, source):
"""
Look up the spec by the spec id. We do this (instead of storing the
spec as an attribute) so that users can override apps' specs--without
having to edit model definitions--simply by registering another spec
with the same id.
"""
if not getattr(self, 'spec_id', None):
raise Exception('Object %s has no spec id.' % self)
return generator_registry.get(self.spec_id, source=source)

View file

@ -0,0 +1,175 @@
"""
Source groups are the means by which image spec sources are identified. They
have two responsibilities:
1. To dispatch ``source_saved`` signals. (These will be relayed to the
corresponding specs' cache file strategies.)
2. To provide the source files that they represent, via a generator method named
``files()``. (This is used by the generateimages management command for
"pre-caching" image files.)
"""
from django.db.models.signals import post_init, post_save
from django.utils.functional import wraps
import inspect
from ..cachefiles import LazyImageCacheFile
from ..signals import source_saved
from ..utils import get_nonabstract_descendants
def ik_model_receiver(fn):
"""
A method decorator that filters out signals coming from models that don't
have fields that function as ImageFieldSourceGroup sources.
"""
@wraps(fn)
def receiver(self, sender, **kwargs):
if not inspect.isclass(sender):
return
for src in self._source_groups:
if issubclass(sender, src.model_class):
fn(self, sender=sender, **kwargs)
# If we find a match, return. We don't want to handle the signal
# more than once.
return
return receiver
class ModelSignalRouter(object):
"""
Normally, ``ImageFieldSourceGroup`` would be directly responsible for
watching for changes on the model field it represents. However, Django does
not dispatch events for abstract base classes. Therefore, we must listen for
the signals on all models and filter out those that aren't represented by
``ImageFieldSourceGroup``s. This class encapsulates that functionality.
Related:
https://github.com/matthewwithanm/django-imagekit/issues/126
https://code.djangoproject.com/ticket/9318
"""
def __init__(self):
self._source_groups = []
uid = 'ik_spec_field_receivers'
post_init.connect(self.post_init_receiver, dispatch_uid=uid)
post_save.connect(self.post_save_receiver, dispatch_uid=uid)
def add(self, source_group):
self._source_groups.append(source_group)
def init_instance(self, instance):
instance._ik = getattr(instance, '_ik', {})
def update_source_hashes(self, instance):
"""
Stores hashes of the source image files so that they can be compared
later to see whether the source image has changed (and therefore whether
the spec file needs to be regenerated).
"""
self.init_instance(instance)
instance._ik['source_hashes'] = dict(
(attname, hash(getattr(instance, attname)))
for attname in self.get_source_fields(instance))
return instance._ik['source_hashes']
def get_source_fields(self, instance):
"""
Returns a list of the source fields for the given instance.
"""
return set(src.image_field
for src in self._source_groups
if isinstance(instance, src.model_class))
@ik_model_receiver
def post_save_receiver(self, sender, instance=None, created=False, update_fields=None, raw=False, **kwargs):
if not raw:
self.init_instance(instance)
old_hashes = instance._ik.get('source_hashes', {}).copy()
new_hashes = self.update_source_hashes(instance)
for attname in self.get_source_fields(instance):
if update_fields and attname not in update_fields:
continue
file = getattr(instance, attname)
if file and old_hashes.get(attname) != new_hashes[attname]:
self.dispatch_signal(source_saved, file, sender, instance,
attname)
@ik_model_receiver
def post_init_receiver(self, sender, instance=None, **kwargs):
self.init_instance(instance)
source_fields = self.get_source_fields(instance)
local_fields = dict((field.name, field)
for field in instance._meta.local_fields
if field.name in source_fields)
instance._ik['source_hashes'] = dict(
(attname, hash(file_field))
for attname, file_field in local_fields.items())
def dispatch_signal(self, signal, file, model_class, instance, attname):
"""
Dispatch the signal for each of the matching source groups. Note that
more than one source can have the same model and image_field; it's
important that we dispatch the signal for each.
"""
for source_group in self._source_groups:
if issubclass(model_class, source_group.model_class) and source_group.image_field == attname:
signal.send(sender=source_group, source=file)
class ImageFieldSourceGroup(object):
"""
A source group that repesents a particular field across all instances of a
model and its subclasses.
"""
def __init__(self, model_class, image_field):
self.model_class = model_class
self.image_field = image_field
signal_router.add(self)
def files(self):
"""
A generator that returns the source files that this source group
represents; in this case, a particular field of every instance of a
particular model and its subclasses.
"""
for model in get_nonabstract_descendants(self.model_class):
for instance in model.objects.all().iterator():
yield getattr(instance, self.image_field)
class SourceGroupFilesGenerator(object):
"""
A Python generator that yields cache file objects for source groups.
"""
def __init__(self, source_group, generator_id):
self.source_group = source_group
self.generator_id = generator_id
def __eq__(self, other):
return (isinstance(other, self.__class__)
and self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.source_group, self.generator_id))
def __call__(self):
for source_file in self.source_group.files():
yield LazyImageCacheFile(self.generator_id,
source=source_file)
signal_router = ModelSignalRouter()

View file

@ -0,0 +1,5 @@
{% if thumbnail %}
<a href="{{ model.get_absolute_url|default:original_image.url }}">
<img src="{{ thumbnail.url }}">
</a>
{% endif %}

View file

View file

@ -0,0 +1,285 @@
from __future__ import unicode_literals
from django import template
from django.utils.html import escape
from django.utils.safestring import mark_safe
from ..compat import parse_bits
from ..cachefiles import ImageCacheFile
from ..registry import generator_registry
from ..lib import force_text
register = template.Library()
ASSIGNMENT_DELIMETER = 'as'
HTML_ATTRS_DELIMITER = '--'
DEFAULT_THUMBNAIL_GENERATOR = 'imagekit:thumbnail'
def get_cachefile(context, generator_id, generator_kwargs, source=None):
generator_id = generator_id.resolve(context)
kwargs = dict((k, v.resolve(context)) for k, v in generator_kwargs.items())
generator = generator_registry.get(generator_id, **kwargs)
return ImageCacheFile(generator)
def parse_dimensions(dimensions):
"""
Parse the width and height values from a dimension string. Valid values are
'1x1', '1x', and 'x1'. If one of the dimensions is omitted, the parse result
will be None for that value.
"""
width, height = [d.strip() and int(d) or None for d in dimensions.split('x')]
return dict(width=width, height=height)
class GenerateImageAssignmentNode(template.Node):
def __init__(self, variable_name, generator_id, generator_kwargs):
self._generator_id = generator_id
self._generator_kwargs = generator_kwargs
self._variable_name = variable_name
def get_variable_name(self, context):
return force_text(self._variable_name)
def render(self, context):
variable_name = self.get_variable_name(context)
context[variable_name] = get_cachefile(context, self._generator_id,
self._generator_kwargs)
return ''
class GenerateImageTagNode(template.Node):
def __init__(self, generator_id, generator_kwargs, html_attrs):
self._generator_id = generator_id
self._generator_kwargs = generator_kwargs
self._html_attrs = html_attrs
def render(self, context):
file = get_cachefile(context, self._generator_id,
self._generator_kwargs)
attrs = dict((k, v.resolve(context)) for k, v in
self._html_attrs.items())
# Only add width and height if neither is specified (to allow for
# proportional in-browser scaling).
if not 'width' in attrs and not 'height' in attrs:
attrs.update(width=file.width, height=file.height)
attrs['src'] = file.url
attr_str = ' '.join('%s="%s"' % (escape(k), escape(v)) for k, v in
attrs.items())
return mark_safe('<img %s />' % attr_str)
class ThumbnailAssignmentNode(template.Node):
def __init__(self, variable_name, generator_id, dimensions, source, generator_kwargs):
self._variable_name = variable_name
self._generator_id = generator_id
self._dimensions = dimensions
self._source = source
self._generator_kwargs = generator_kwargs
def get_variable_name(self, context):
return force_text(self._variable_name)
def render(self, context):
variable_name = self.get_variable_name(context)
generator_id = self._generator_id.resolve(context) if self._generator_id else DEFAULT_THUMBNAIL_GENERATOR
kwargs = dict((k, v.resolve(context)) for k, v in
self._generator_kwargs.items())
kwargs['source'] = self._source.resolve(context)
kwargs.update(parse_dimensions(self._dimensions.resolve(context)))
generator = generator_registry.get(generator_id, **kwargs)
context[variable_name] = ImageCacheFile(generator)
return ''
class ThumbnailImageTagNode(template.Node):
def __init__(self, generator_id, dimensions, source, generator_kwargs, html_attrs):
self._generator_id = generator_id
self._dimensions = dimensions
self._source = source
self._generator_kwargs = generator_kwargs
self._html_attrs = html_attrs
def render(self, context):
generator_id = self._generator_id.resolve(context) if self._generator_id else DEFAULT_THUMBNAIL_GENERATOR
dimensions = parse_dimensions(self._dimensions.resolve(context))
kwargs = dict((k, v.resolve(context)) for k, v in
self._generator_kwargs.items())
kwargs['source'] = self._source.resolve(context)
kwargs.update(dimensions)
generator = generator_registry.get(generator_id, **kwargs)
file = ImageCacheFile(generator)
attrs = dict((k, v.resolve(context)) for k, v in
self._html_attrs.items())
# Only add width and height if neither is specified (to allow for
# proportional in-browser scaling).
if not 'width' in attrs and not 'height' in attrs:
attrs.update(width=file.width, height=file.height)
attrs['src'] = file.url
attr_str = ' '.join('%s="%s"' % (escape(k), escape(v)) for k, v in
attrs.items())
return mark_safe('<img %s />' % attr_str)
def parse_ik_tag_bits(parser, bits):
"""
Parses the tag name, html attributes and variable name (for assignment tags)
from the provided bits. The preceding bits may vary and are left to be
parsed by specific tags.
"""
varname = None
html_attrs = {}
tag_name = bits.pop(0)
if len(bits) >= 2 and bits[-2] == ASSIGNMENT_DELIMETER:
varname = bits[-1]
bits = bits[:-2]
if HTML_ATTRS_DELIMITER in bits:
if varname:
raise template.TemplateSyntaxError('Do not specify html attributes'
' (using "%s") when using the "%s" tag as an assignment'
' tag.' % (HTML_ATTRS_DELIMITER, tag_name))
index = bits.index(HTML_ATTRS_DELIMITER)
html_bits = bits[index + 1:]
bits = bits[:index]
if not html_bits:
raise template.TemplateSyntaxError('Don\'t use "%s" unless you\'re'
' setting html attributes.' % HTML_ATTRS_DELIMITER)
args, html_attrs = parse_bits(parser, html_bits, [], 'args',
'kwargs', None, False, tag_name)
if len(args):
raise template.TemplateSyntaxError('All "%s" tag arguments after'
' the "%s" token must be named.' % (tag_name,
HTML_ATTRS_DELIMITER))
return (tag_name, bits, html_attrs, varname)
#@register.tag
def generateimage(parser, token):
"""
Creates an image based on the provided arguments.
By default::
{% generateimage 'myapp:thumbnail' source=mymodel.profile_image %}
generates an ``<img>`` tag::
<img src="/path/to/34d944f200dd794bf1e6a7f37849f72b.jpg" width="100" height="100" />
You can add additional attributes to the tag using "--". For example,
this::
{% generateimage 'myapp:thumbnail' source=mymodel.profile_image -- alt="Hello!" %}
will result in the following markup::
<img src="/path/to/34d944f200dd794bf1e6a7f37849f72b.jpg" width="100" height="100" alt="Hello!" />
For more flexibility, ``generateimage`` also works as an assignment tag::
{% generateimage 'myapp:thumbnail' source=mymodel.profile_image as th %}
<img src="{{ th.url }}" width="{{ th.width }}" height="{{ th.height }}" />
"""
bits = token.split_contents()
tag_name, bits, html_attrs, varname = parse_ik_tag_bits(parser, bits)
args, kwargs = parse_bits(parser, bits, ['generator_id'], 'args', 'kwargs',
None, False, tag_name)
if len(args) != 1:
raise template.TemplateSyntaxError('The "%s" tag requires exactly one'
' unnamed argument (the generator id).' % tag_name)
generator_id = args[0]
if varname:
return GenerateImageAssignmentNode(varname, generator_id, kwargs)
else:
return GenerateImageTagNode(generator_id, kwargs, html_attrs)
#@register.tag
def thumbnail(parser, token):
"""
A convenient shortcut syntax for generating a thumbnail. The following::
{% thumbnail '100x100' mymodel.profile_image %}
is equivalent to::
{% generateimage 'imagekit:thumbnail' source=mymodel.profile_image width=100 height=100 %}
The thumbnail tag supports the "--" and "as" bits for adding html
attributes and assigning to a variable, respectively. It also accepts the
kwargs "anchor", and "crop".
To use "smart cropping" (the ``SmartResize`` processor)::
{% thumbnail '100x100' mymodel.profile_image %}
To crop, anchoring the image to the top right (the ``ResizeToFill``
processor)::
{% thumbnail '100x100' mymodel.profile_image anchor='tr' %}
To resize without cropping (using the ``ResizeToFit`` processor)::
{% thumbnail '100x100' mymodel.profile_image crop=0 %}
"""
bits = token.split_contents()
tag_name, bits, html_attrs, varname = parse_ik_tag_bits(parser, bits)
args, kwargs = parse_bits(parser, bits, [], 'args', 'kwargs',
None, False, tag_name)
if len(args) < 2:
raise template.TemplateSyntaxError('The "%s" tag requires at least two'
' unnamed arguments: the dimensions and the source image.'
% tag_name)
elif len(args) > 3:
raise template.TemplateSyntaxError('The "%s" tag accepts at most three'
' unnamed arguments: a generator id, the dimensions, and the'
' source image.' % tag_name)
dimensions, source = args[-2:]
generator_id = args[0] if len(args) > 2 else None
if varname:
return ThumbnailAssignmentNode(varname, generator_id, dimensions,
source, kwargs)
else:
return ThumbnailImageTagNode(generator_id, dimensions, source, kwargs,
html_attrs)
generateimage = register.tag(generateimage)
thumbnail = register.tag(thumbnail)

View file

@ -1,99 +0,0 @@
import os
import tempfile
import unittest
from django.conf import settings
from django.core.files.base import ContentFile
from django.db import models
from django.test import TestCase
from imagekit import processors
from imagekit.models import ImageModel
from imagekit.specs import ImageSpec
from imagekit.lib import Image
class ResizeToWidth(processors.Resize):
width = 100
class ResizeToHeight(processors.Resize):
height = 100
class ResizeToFit(processors.Resize):
width = 100
height = 100
class ResizeCropped(ResizeToFit):
crop = ('center', 'center')
class TestResizeToWidth(ImageSpec):
access_as = 'to_width'
processors = [ResizeToWidth]
class TestResizeToHeight(ImageSpec):
access_as = 'to_height'
processors = [ResizeToHeight]
class TestResizeCropped(ImageSpec):
access_as = 'cropped'
processors = [ResizeCropped]
class TestPhoto(ImageModel):
""" Minimal ImageModel class for testing """
image = models.ImageField(upload_to='images')
class IKOptions:
spec_module = 'imagekit.tests'
class IKTest(TestCase):
""" Base TestCase class """
def generate_image(self):
tmp = tempfile.TemporaryFile()
Image.new('RGB', (800, 600)).save(tmp, 'JPEG')
tmp.seek(0)
return tmp
def setUp(self):
self.p = TestPhoto()
img = self.generate_image()
self.p.save_image('test.jpeg', ContentFile(img.read()))
self.p.save()
img.close()
def test_save_image(self):
img = self.generate_image()
path = self.p.image.path
self.p.save_image('test2.jpeg', ContentFile(img.read()))
self.failIf(os.path.isfile(path))
path = self.p.image.path
img.seek(0)
self.p.save_image('test.jpeg', ContentFile(img.read()))
self.failIf(os.path.isfile(path))
img.close()
def test_setup(self):
self.assertEqual(self.p.image.width, 800)
self.assertEqual(self.p.image.height, 600)
def test_to_width(self):
self.assertEqual(self.p.to_width.width, 100)
self.assertEqual(self.p.to_width.height, 75)
def test_to_height(self):
self.assertEqual(self.p.to_height.width, 133)
self.assertEqual(self.p.to_height.height, 100)
def test_crop(self):
self.assertEqual(self.p.cropped.width, 100)
self.assertEqual(self.p.cropped.height, 100)
def test_url(self):
tup = (settings.MEDIA_URL, self.p._ik.cache_dir,
'images/test_to_width.jpeg')
self.assertEqual(self.p.to_width.url, "%s%s/%s" % tup)
def tearDown(self):
# make sure image file is deleted
path = self.p.image.path
self.p.delete()
self.failIf(os.path.isfile(path))

View file

@ -1,9 +1,191 @@
""" ImageKit utility functions """
from __future__ import unicode_literals
import logging
import re
from tempfile import NamedTemporaryFile
from hashlib import md5
import tempfile
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from pilkit.utils import *
from .lib import NullHandler, force_bytes
def img_to_fobj(img, format, **kwargs):
tmp = tempfile.TemporaryFile()
img.convert('RGB').save(tmp, format, **kwargs)
tmp.seek(0)
return tmp
bad_memcached_key_chars = re.compile('[\u0000-\u001f\\s]+')
_autodiscovered = False
def get_nonabstract_descendants(model):
""" Returns all non-abstract descendants of the model. """
if not model._meta.abstract:
yield model
for s in model.__subclasses__():
for m in get_nonabstract_descendants(s):
yield m
def get_by_qname(path, desc):
try:
dot = path.rindex('.')
except ValueError:
raise ImproperlyConfigured("%s isn't a %s module." % (path, desc))
module, objname = path[:dot], path[dot + 1:]
try:
mod = import_module(module)
except ImportError as e:
raise ImproperlyConfigured('Error importing %s module %s: "%s"' %
(desc, module, e))
try:
obj = getattr(mod, objname)
return obj
except AttributeError:
raise ImproperlyConfigured('%s module "%s" does not define "%s"'
% (desc[0].upper() + desc[1:], module, objname))
_singletons = {}
def get_singleton(class_path, desc):
global _singletons
cls = get_by_qname(class_path, desc)
instance = _singletons.get(cls)
if not instance:
instance = _singletons[cls] = cls()
return instance
def autodiscover():
"""
Auto-discover INSTALLED_APPS imagegenerators.py modules and fail silently
when not present. This forces an import on them to register any admin bits
they may want.
Copied from django.contrib.admin
"""
global _autodiscovered
if _autodiscovered:
return
try:
from django.utils.module_loading import autodiscover_modules
except ImportError:
# Django<1.7
_autodiscover_modules_fallback()
else:
autodiscover_modules('imagegenerators')
_autodiscovered = True
def _autodiscover_modules_fallback():
"""
Auto-discover INSTALLED_APPS imagegenerators.py modules and fail silently
when not present. This forces an import on them to register any admin bits
they may want.
Copied from django.contrib.admin
Used for Django versions < 1.7
"""
from django.conf import settings
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
# As of Django 1.7, settings.INSTALLED_APPS may contain classes instead of modules, hence the try/except
# See here: https://docs.djangoproject.com/en/dev/releases/1.7/#introspecting-applications
try:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
import_module('%s.imagegenerators' % app)
except:
# Decide whether to bubble up this error. If the app just
# doesn't have an imagegenerators module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, 'imagegenerators'):
raise
except ImportError:
pass
def get_logger(logger_name='imagekit', add_null_handler=True):
logger = logging.getLogger(logger_name)
if add_null_handler:
logger.addHandler(NullHandler())
return logger
def get_field_info(field_file):
"""
A utility for easily extracting information about the host model from a
Django FileField (or subclass). This is especially useful for when you want
to alter processors based on a property of the source model. For example::
class MySpec(ImageSpec):
def __init__(self, source):
instance, attname = get_field_info(source)
self.processors = [SmartResize(instance.thumbnail_width,
instance.thumbnail_height)]
"""
return (
getattr(field_file, 'instance', None),
getattr(getattr(field_file, 'field', None), 'attname', None),
)
def generate(generator):
"""
Calls the ``generate()`` method of a generator instance, and then wraps the
result in a Django File object so Django knows how to save it.
"""
content = generator.generate()
f = File(content)
# The size of the File must be known or Django will try to open a file
# without a name and raise an Exception.
f.size = len(content.read())
# After getting the size reset the file pointer for future reads.
content.seek(0)
return f
def call_strategy_method(file, method_name):
strategy = getattr(file, 'cachefile_strategy', None)
fn = getattr(strategy, method_name, None)
if fn is not None:
fn(file)
def get_cache():
try:
from django.core.cache import caches
except ImportError:
# Django < 1.7
from django.core.cache import get_cache
return get_cache(settings.IMAGEKIT_CACHE_BACKEND)
return caches[settings.IMAGEKIT_CACHE_BACKEND]
def sanitize_cache_key(key):
if settings.IMAGEKIT_USE_MEMCACHED_SAFE_CACHE_KEY:
# Memcached keys can't contain whitespace or control characters.
new_key = bad_memcached_key_chars.sub('', key)
# The also can't be > 250 chars long. Since we don't know what the
# user's cache ``KEY_FUNCTION`` setting is like, we'll limit it to 200.
if len(new_key) >= 200:
new_key = '%s:%s' % (new_key[:200-33], md5(force_bytes(key)).hexdigest())
key = new_key
return key

2
setup.cfg Normal file
View file

@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1

View file

@ -1,33 +1,80 @@
#/usr/bin/env python
from distutils.core import setup
#!/usr/bin/env python
import codecs
import os
from setuptools import setup, find_packages
import sys
# Workaround for multiprocessing/nose issue. See http://bugs.python.org/msg170215
try:
import multiprocessing # NOQA
except ImportError:
pass
if 'publish' in sys.argv:
os.system('python setup.py sdist bdist_wheel upload')
sys.exit()
read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read()
def exec_file(filepath, globalz=None, localz=None):
exec(read(filepath), globalz, localz)
# Load package meta from the pkgmeta module without loading imagekit.
pkgmeta = {}
exec_file(os.path.join(os.path.dirname(__file__),
'imagekit', 'pkgmeta.py'), pkgmeta)
setup(
name='django-imagekit',
version='0.4.0',
version=pkgmeta['__version__'],
description='Automated image processing for Django models.',
author='Justin Driscoll',
author_email='justin@driscolldev.com',
long_description=read(os.path.join(os.path.dirname(__file__), 'README.rst')),
author='Matthew Tretter',
author_email='m@tthewwithanm.com',
maintainer='Bryan Veloso',
maintainer_email='bryan@revyver.com',
license='BSD',
url='http://github.com/jdriscoll/django-imagekit/',
packages=[
'imagekit',
'imagekit.management',
'imagekit.management.commands'
url='http://github.com/matthewwithanm/django-imagekit/',
packages=find_packages(exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']),
zip_safe=False,
include_package_data=True,
tests_require=[
'beautifulsoup4>=4.4.0',
'nose>=1.3.6',
'nose-progressive>=1.5.1',
'django-nose>=1.4',
'Pillow',
'mock>=1.0.1',
],
test_suite='testrunner.run_tests',
install_requires=[
'django-appconf>=0.5',
'pilkit>=0.2.0',
'six',
],
extras_require={
'async': ['django-celery>=3.0'],
'async_rq': ['django-rq>=0.6.0'],
},
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Utilities'
]
],
)

22
testrunner.py Normal file
View file

@ -0,0 +1,22 @@
# A wrapper for Django's test runner.
# See http://ericholscher.com/blog/2009/jun/29/enable-setuppy-test-your-django-apps/
# and http://gremu.net/blog/2010/enable-setuppy-test-your-django-apps/
import os
import sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
test_dir = os.path.dirname(__file__)
sys.path.insert(0, test_dir)
from django.test.utils import get_runner
from django.conf import settings
def run_tests():
cls = get_runner(settings)
runner = cls()
failures = runner.run_tests(['tests'])
# Clean autogenerated junk before exit
from tests.utils import clear_imagekit_test_files
clear_imagekit_test_files()
sys.exit(failures)

0
tests/__init__.py Normal file
View file

16
tests/imagegenerators.py Normal file
View file

@ -0,0 +1,16 @@
from imagekit import ImageSpec, register
from imagekit.processors import ResizeToFill
class TestSpec(ImageSpec):
pass
class ResizeTo1PixelSquare(ImageSpec):
def __init__(self, width=None, height=None, anchor=None, crop=None, **kwargs):
self.processors = [ResizeToFill(1, 1)]
super(ResizeTo1PixelSquare, self).__init__(**kwargs)
register.generator('testspec', TestSpec)
register.generator('1pxsq', ResizeTo1PixelSquare)

BIN
tests/media/reference.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

72
tests/models.py Normal file
View file

@ -0,0 +1,72 @@
from django.db import models
from imagekit import ImageSpec
from imagekit.models import ProcessedImageField
from imagekit.models import ImageSpecField
from imagekit.processors import Adjust, ResizeToFill, SmartCrop
class Thumbnail(ImageSpec):
processors = [ResizeToFill(100, 60)]
format = 'JPEG'
options = {'quality': 60}
class ImageModel(models.Model):
image = models.ImageField(upload_to='b')
class Photo(models.Model):
original_image = models.ImageField(upload_to='photos')
# Implicit source field
thumbnail = ImageSpecField([Adjust(contrast=1.2, sharpness=1.1),
ResizeToFill(50, 50)], format='JPEG',
options={'quality': 90})
smartcropped_thumbnail = ImageSpecField([Adjust(contrast=1.2,
sharpness=1.1), SmartCrop(50, 50)], source='original_image',
format='JPEG', options={'quality': 90})
class ProcessedImageFieldModel(models.Model):
processed = ProcessedImageField([SmartCrop(50, 50)], format='JPEG',
options={'quality': 90}, upload_to='p')
class ProcessedImageFieldWithSpecModel(models.Model):
processed = ProcessedImageField(spec=Thumbnail, upload_to='p')
class CountingCacheFileStrategy(object):
def __init__(self):
self.on_existence_required_count = 0
self.on_content_required_count = 0
self.on_source_saved_count = 0
def on_existence_required(self, file):
self.on_existence_required_count += 1
def on_content_required(self, file):
self.on_content_required_count += 1
def on_source_saved(self, file):
self.on_source_saved_count += 1
class AbstractImageModel(models.Model):
original_image = models.ImageField(upload_to='photos')
abstract_class_spec = ImageSpecField(source='original_image',
format='JPEG',
cachefile_strategy=CountingCacheFileStrategy())
class Meta:
abstract = True
class ConcreteImageModel(AbstractImageModel):
pass
class ConcreteImageModelSubclass(ConcreteImageModel):
pass

69
tests/settings.py Normal file
View file

@ -0,0 +1,69 @@
import os
ADMINS = (
('test@example.com', 'TEST-R'),
)
BASE_PATH = os.path.abspath(os.path.dirname(__file__))
MEDIA_ROOT = os.path.normpath(os.path.join(BASE_PATH, 'media'))
# Django <= 1.2
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'imagekit.db'
TEST_DATABASE_NAME = 'imagekit-test.db'
# Django >= 1.3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'imagekit.db',
},
}
SECRET_KEY = '_uobce43e5osp8xgzle*yag2_16%y$sf*5(12vfg25hpnxik_*'
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'imagekit',
'tests',
'django_nose',
]
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'-s',
# When the tests are run --with-coverage, these args configure coverage
# reporting (requires coverage to be installed).
# Without the --with-coverage flag, they have no effect.
'--cover-tests',
'--cover-html',
'--cover-package=imagekit',
'--cover-html-dir=%s' % os.path.join(BASE_PATH, 'cover')
]
if os.getenv('TERM'):
NOSE_ARGS.append('--with-progressive')
CACHE_BACKEND = 'locmem://'
# Django >= 1.8
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
]

View file

@ -0,0 +1,9 @@
from imagekit.utils import get_nonabstract_descendants
from nose.tools import eq_
from . models import (AbstractImageModel, ConcreteImageModel,
ConcreteImageModelSubclass)
def test_nonabstract_descendants_generator():
descendants = list(get_nonabstract_descendants(AbstractImageModel))
eq_(descendants, [ConcreteImageModel, ConcreteImageModelSubclass])

115
tests/test_cachefiles.py Normal file
View file

@ -0,0 +1,115 @@
from unittest import mock
from django.conf import settings
from hashlib import md5
from imagekit.cachefiles import ImageCacheFile, LazyImageCacheFile
from imagekit.cachefiles.backends import Simple
from imagekit.lib import force_bytes
from nose.tools import raises, eq_
from .imagegenerators import TestSpec
from .utils import (assert_file_is_truthy, assert_file_is_falsy,
DummyAsyncCacheFileBackend, get_unique_image_file,
get_image_file)
def test_no_source_falsiness():
"""
Ensure cache files generated from sourceless specs are falsy.
"""
spec = TestSpec(source=None)
file = ImageCacheFile(spec)
assert_file_is_falsy(file)
def test_sync_backend_truthiness():
"""
Ensure that a cachefile with a synchronous cache file backend (the default)
is truthy.
"""
spec = TestSpec(source=get_unique_image_file())
file = ImageCacheFile(spec)
assert_file_is_truthy(file)
def test_async_backend_falsiness():
"""
Ensure that a cachefile with an asynchronous cache file backend is falsy.
"""
spec = TestSpec(source=get_unique_image_file())
file = ImageCacheFile(spec, cachefile_backend=DummyAsyncCacheFileBackend())
assert_file_is_falsy(file)
@raises(TestSpec.MissingSource)
def test_no_source_error():
spec = TestSpec(source=None)
file = ImageCacheFile(spec)
file.generate()
def test_repr_does_not_send_existence_required():
"""
Ensure that `__repr__` method does not send `existance_required` signal
Cachefile strategy may be configured to generate file on
`existance_required`.
To generate images, backend passes `ImageCacheFile` instance to worker.
Both celery and RQ calls `__repr__` method for each argument to enque call.
And if `__repr__` of object will send this signal, we will get endless
recursion
"""
with mock.patch('imagekit.cachefiles.existence_required') as signal:
# import here to apply mock
from imagekit.cachefiles import ImageCacheFile
spec = TestSpec(source=get_unique_image_file())
file = ImageCacheFile(
spec,
cachefile_backend=DummyAsyncCacheFileBackend()
)
file.__repr__()
eq_(signal.send.called, False)
def test_memcached_cache_key():
"""
Ensure the default cachefile backend is sanitizing its cache key for
memcached by default.
"""
class MockFile(object):
def __init__(self, name):
self.name = name
backend = Simple()
extra_char_count = len('state-') + len(settings.IMAGEKIT_CACHE_PREFIX)
length = 199 - extra_char_count
filename = '1' * length
file = MockFile(filename)
eq_(backend.get_key(file), '%s%s-state' %
(settings.IMAGEKIT_CACHE_PREFIX, file.name))
length = 200 - extra_char_count
filename = '1' * length
file = MockFile(filename)
eq_(backend.get_key(file), '%s%s:%s' % (
settings.IMAGEKIT_CACHE_PREFIX,
'1' * (200 - len(':') - 32 - len(settings.IMAGEKIT_CACHE_PREFIX)),
md5(force_bytes('%s%s-state' % (settings.IMAGEKIT_CACHE_PREFIX, filename))).hexdigest()))
def test_lazyfile_stringification():
file = LazyImageCacheFile('testspec', source=None)
eq_(str(file), '')
eq_(repr(file), '<ImageCacheFile: None>')
source_file = get_image_file()
file = LazyImageCacheFile('testspec', source=source_file)
file.name = 'a.jpg'
eq_(str(file), 'a.jpg')
eq_(repr(file), '<ImageCacheFile: a.jpg>')

View file

@ -0,0 +1,25 @@
from nose.tools import assert_false, assert_true
from .models import Thumbnail
from .utils import create_photo
def test_do_not_leak_open_files():
instance = create_photo('leak-test.jpg')
source_file = instance.original_image
# Ensure the FieldFile is closed before generation
source_file.close()
image_generator = Thumbnail(source=source_file)
image_generator.generate()
assert_true(source_file.closed)
def test_do_not_close_open_files_after_generate():
instance = create_photo('do-not-close-test.jpg')
source_file = instance.original_image
# Ensure the FieldFile is opened before generation
source_file.open()
image_generator = Thumbnail(source=source_file)
image_generator.generate()
assert_false(source_file.closed)
source_file.close()

49
tests/test_fields.py Normal file
View file

@ -0,0 +1,49 @@
from django import forms
from django.core.files.base import File
from django.core.files.uploadedfile import SimpleUploadedFile
from imagekit import forms as ikforms
from imagekit.processors import SmartCrop
from nose.tools import eq_
from . import imagegenerators # noqa
from .models import (ProcessedImageFieldModel,
ProcessedImageFieldWithSpecModel,
ImageModel)
from .utils import get_image_file
def test_model_processedimagefield():
instance = ProcessedImageFieldModel()
file = File(get_image_file())
instance.processed.save('whatever.jpeg', file)
instance.save()
eq_(instance.processed.width, 50)
eq_(instance.processed.height, 50)
def test_model_processedimagefield_with_spec():
instance = ProcessedImageFieldWithSpecModel()
file = File(get_image_file())
instance.processed.save('whatever.jpeg', file)
instance.save()
eq_(instance.processed.width, 100)
eq_(instance.processed.height, 60)
def test_form_processedimagefield():
class TestForm(forms.ModelForm):
image = ikforms.ProcessedImageField(spec_id='tests:testform_image',
processors=[SmartCrop(50, 50)], format='JPEG')
class Meta:
model = ImageModel
fields = 'image',
upload_file = get_image_file()
file_dict = {'image': SimpleUploadedFile('abc.jpg', upload_file.read())}
form = TestForm({}, file_dict)
instance = form.save()
eq_(instance.image.width, 50)
eq_(instance.image.height, 50)

View file

@ -0,0 +1,56 @@
from django.template import TemplateSyntaxError
from nose.tools import eq_, assert_false, raises, assert_not_equal
from . import imagegenerators # noqa
from .utils import render_tag, get_html_attrs, clear_imagekit_cache
def test_img_tag():
ttag = r"""{% generateimage 'testspec' source=img %}"""
clear_imagekit_cache()
attrs = get_html_attrs(ttag)
expected_attrs = set(['src', 'width', 'height'])
eq_(set(attrs.keys()), expected_attrs)
for k in expected_attrs:
assert_not_equal(attrs[k].strip(), '')
def test_img_tag_attrs():
ttag = r"""{% generateimage 'testspec' source=img -- alt="Hello" %}"""
clear_imagekit_cache()
attrs = get_html_attrs(ttag)
eq_(attrs.get('alt'), 'Hello')
@raises(TemplateSyntaxError)
def test_dangling_html_attrs_delimiter():
ttag = r"""{% generateimage 'testspec' source=img -- %}"""
render_tag(ttag)
@raises(TemplateSyntaxError)
def test_html_attrs_assignment():
"""
You can either use generateimage as an assignment tag or specify html attrs,
but not both.
"""
ttag = r"""{% generateimage 'testspec' source=img -- alt="Hello" as th %}"""
render_tag(ttag)
def test_single_dimension_attr():
"""
If you only provide one of width or height, the other should not be added.
"""
ttag = r"""{% generateimage 'testspec' source=img -- width="50" %}"""
clear_imagekit_cache()
attrs = get_html_attrs(ttag)
assert_false('height' in attrs)
def test_assignment_tag():
ttag = r"""{% generateimage 'testspec' source=img as th %}{{ th.url }}{{ th.height }}{{ th.width }}"""
clear_imagekit_cache()
html = render_tag(ttag)
assert_not_equal(html.strip(), '')

View file

@ -0,0 +1,16 @@
from nose.tools import assert_false
from unittest.mock import Mock, PropertyMock, patch
from .models import Photo
def test_dont_access_source():
"""
Touching the source may trigger an unneeded query.
See <https://github.com/matthewwithanm/django-imagekit/issues/295>
"""
pmock = PropertyMock()
pmock.__get__ = Mock()
with patch.object(Photo, 'original_image', pmock):
photo = Photo() # noqa
assert_false(pmock.__get__.called)

View file

@ -0,0 +1,49 @@
from nose.tools import assert_true, assert_false
from imagekit.cachefiles import ImageCacheFile
from unittest.mock import Mock
from .utils import create_image
from django.core.files.storage import FileSystemStorage
from imagekit.cachefiles.backends import Simple as SimpleCFBackend
from imagekit.cachefiles.strategies import Optimistic as OptimisticStrategy
class ImageGenerator(object):
def generate(self):
return create_image()
def get_hash(self):
return 'abc123'
def get_image_cache_file():
storage = Mock(FileSystemStorage)
backend = SimpleCFBackend()
strategy = OptimisticStrategy()
generator = ImageGenerator()
return ImageCacheFile(generator, storage=storage,
cachefile_backend=backend,
cachefile_strategy=strategy)
def test_no_io_on_bool():
"""
When checking the truthiness of an ImageCacheFile, the storage shouldn't
peform IO operations.
"""
file = get_image_cache_file()
bool(file)
assert_false(file.storage.exists.called)
assert_false(file.storage.open.called)
def test_no_io_on_url():
"""
When getting the URL of an ImageCacheFile, the storage shouldn't be
checked.
"""
file = get_image_cache_file()
file.url
assert_false(file.storage.exists.called)
assert_false(file.storage.open.called)

View file

@ -0,0 +1,43 @@
"""
Make sure that the various IK classes can be successfully serialized and
deserialized. This is important when using IK with Celery.
"""
from imagekit.cachefiles import ImageCacheFile
from .imagegenerators import TestSpec
from .utils import create_photo, pickleback, get_unique_image_file, clear_imagekit_cache
def test_imagespecfield():
clear_imagekit_cache()
instance = create_photo('pickletest2.jpg')
thumbnail = pickleback(instance.thumbnail)
thumbnail.generate()
def test_circular_ref():
"""
A model instance with a spec field in its dict shouldn't raise a KeyError.
This corresponds to #234
"""
clear_imagekit_cache()
instance = create_photo('pickletest3.jpg')
instance.thumbnail # Cause thumbnail to be added to instance's __dict__
pickleback(instance)
def test_cachefiles():
clear_imagekit_cache()
spec = TestSpec(source=get_unique_image_file())
file = ImageCacheFile(spec)
file.url
# remove link to file from spec source generator
# test __getstate__ of ImageCacheFile
file.generator.source = None
restored_file = pickleback(file)
assert file is not restored_file
# Assertion for #437 and #451
assert file.storage is restored_file.storage

View file

@ -0,0 +1,55 @@
from django.core.files import File
from imagekit.signals import source_saved
from imagekit.specs.sourcegroups import ImageFieldSourceGroup
from nose.tools import eq_
from . models import AbstractImageModel, ImageModel, ConcreteImageModel
from .utils import get_image_file
def make_counting_receiver(source_group):
def receiver(sender, *args, **kwargs):
if sender is source_group:
receiver.count += 1
receiver.count = 0
return receiver
def test_source_saved_signal():
"""
Creating a new instance with an image causes the source_saved signal to be
dispatched.
"""
source_group = ImageFieldSourceGroup(ImageModel, 'image')
receiver = make_counting_receiver(source_group)
source_saved.connect(receiver)
ImageModel.objects.create(image=File(get_image_file()))
eq_(receiver.count, 1)
def test_no_source_saved_signal():
"""
Creating a new instance without an image shouldn't cause the source_saved
signal to be dispatched.
https://github.com/matthewwithanm/django-imagekit/issues/214
"""
source_group = ImageFieldSourceGroup(ImageModel, 'image')
receiver = make_counting_receiver(source_group)
source_saved.connect(receiver)
ImageModel.objects.create()
eq_(receiver.count, 0)
def test_abstract_model_signals():
"""
Source groups created for abstract models must cause signals to be
dispatched on their concrete subclasses.
"""
source_group = ImageFieldSourceGroup(AbstractImageModel, 'original_image')
receiver = make_counting_receiver(source_group)
source_saved.connect(receiver)
ConcreteImageModel.objects.create(original_image=File(get_image_file()))
eq_(receiver.count, 1)

View file

@ -0,0 +1,71 @@
from django.template import TemplateSyntaxError
from nose.tools import eq_, raises, assert_not_equal
from . import imagegenerators # noqa
from .utils import render_tag, get_html_attrs, clear_imagekit_cache
def test_img_tag():
ttag = r"""{% thumbnail '100x100' img %}"""
clear_imagekit_cache()
attrs = get_html_attrs(ttag)
expected_attrs = set(['src', 'width', 'height'])
eq_(set(attrs.keys()), expected_attrs)
for k in expected_attrs:
assert_not_equal(attrs[k].strip(), '')
def test_img_tag_attrs():
ttag = r"""{% thumbnail '100x100' img -- alt="Hello" %}"""
clear_imagekit_cache()
attrs = get_html_attrs(ttag)
eq_(attrs.get('alt'), 'Hello')
@raises(TemplateSyntaxError)
def test_dangling_html_attrs_delimiter():
ttag = r"""{% thumbnail '100x100' img -- %}"""
render_tag(ttag)
@raises(TemplateSyntaxError)
def test_not_enough_args():
ttag = r"""{% thumbnail '100x100' %}"""
render_tag(ttag)
@raises(TemplateSyntaxError)
def test_too_many_args():
ttag = r"""{% thumbnail 'generator_id' '100x100' img 'extra' %}"""
render_tag(ttag)
@raises(TemplateSyntaxError)
def test_html_attrs_assignment():
"""
You can either use thumbnail as an assignment tag or specify html attrs,
but not both.
"""
ttag = r"""{% thumbnail '100x100' img -- alt="Hello" as th %}"""
render_tag(ttag)
def test_assignment_tag():
ttag = r"""{% thumbnail '100x100' img as th %}{{ th.url }}"""
clear_imagekit_cache()
html = render_tag(ttag)
assert_not_equal(html, '')
def test_single_dimension():
ttag = r"""{% thumbnail '100x' img as th %}{{ th.width }}"""
clear_imagekit_cache()
html = render_tag(ttag)
eq_(html, '100')
def test_alternate_generator():
ttag = r"""{% thumbnail '1pxsq' '100x' img as th %}{{ th.width }}"""
clear_imagekit_cache()
html = render_tag(ttag)
eq_(html, '1')

106
tests/utils.py Normal file
View file

@ -0,0 +1,106 @@
from bs4 import BeautifulSoup
import os
import shutil
from django.core.files import File
from django.template import Context, Template
from imagekit.cachefiles.backends import Simple, CacheFileState
from imagekit.conf import settings
from imagekit.lib import Image, StringIO
from imagekit.utils import get_cache
from nose.tools import assert_true, assert_false
import pickle
from tempfile import NamedTemporaryFile
from .models import Photo
def get_image_file():
"""
See also:
http://en.wikipedia.org/wiki/Lenna
http://sipi.usc.edu/database/database.php?volume=misc&image=12
https://lintian.debian.org/tags/license-problem-non-free-img-lenna.html
https://github.com/libav/libav/commit/8895bf7b78650c0c21c88cec0484e138ec511a4b
"""
path = os.path.join(settings.MEDIA_ROOT, 'reference.png')
return open(path, 'r+b')
def get_unique_image_file():
file = NamedTemporaryFile()
file.write(get_image_file().read())
return file
def create_image():
return Image.open(get_image_file())
def create_instance(model_class, image_name):
instance = model_class()
img = File(get_image_file())
instance.original_image.save(image_name, img)
instance.save()
img.close()
return instance
def create_photo(name):
return create_instance(Photo, name)
def pickleback(obj):
pickled = StringIO()
pickle.dump(obj, pickled)
pickled.seek(0)
return pickle.load(pickled)
def render_tag(ttag):
img = get_image_file()
template = Template('{%% load imagekit %%}%s' % ttag)
context = Context({'img': img})
return template.render(context)
def get_html_attrs(ttag):
return BeautifulSoup(render_tag(ttag), features="html.parser").img.attrs
def assert_file_is_falsy(file):
assert_false(bool(file), 'File is not falsy')
def assert_file_is_truthy(file):
assert_true(bool(file), 'File is not truthy')
class DummyAsyncCacheFileBackend(Simple):
"""
A cache file backend meant to simulate async generation.
"""
is_async = True
def generate(self, file, force=False):
pass
def clear_imagekit_cache():
cache = get_cache()
cache.clear()
# Clear IMAGEKIT_CACHEFILE_DIR
cache_dir = os.path.join(settings.MEDIA_ROOT, settings.IMAGEKIT_CACHEFILE_DIR)
if os.path.exists(cache_dir):
shutil.rmtree(cache_dir)
def clear_imagekit_test_files():
clear_imagekit_cache()
for fname in os.listdir(settings.MEDIA_ROOT):
if fname != 'reference.png':
path = os.path.join(settings.MEDIA_ROOT, fname)
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(path)

18
tox.ini Normal file
View file

@ -0,0 +1,18 @@
[tox]
envlist =
py38-django{master,30,22,21,20,111},
py37-django{master,30,22,21,20,111},
py36-django{master,30,22,21,20,111},
py35-django{21,20,111},
[testenv]
commands = python setup.py test
deps =
djangomaster: git+https://github.com/django/django.git@master#egg=Django
django30: Django>=3.0,<3.1
django22: Django>=2.2,<3.0
django21: Django>=2.1,<2.2
django20: Django>=2.0,<2.1
django111: Django>=1.11,<2.0
django{21,20,111}: django-nose==1.4.5