├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── README.rst
├── build.sh
├── djiiif
├── __init__.py
└── templatetags
│ ├── __init__.py
│ └── iiiftags.py
├── docs
├── Makefile
└── source
│ ├── conf.py
│ └── index.rst
├── requirements.txt
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | *.egg-info
3 | MANIFEST
4 | build
5 | dist
6 | docs/_build
7 | docs/_static
8 | venv*
9 | .tox
10 | *.bak
11 | .DS_Store
12 | .eggs/
13 | .idea/
14 | htmlcov/
15 | .coverage
16 | .cache/
17 | *.swp
18 | *.swo
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Roger Howard
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIEDi
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE.txt
2 | include README
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # django-iiif
2 |
3 | django-iiif is a package designed to make integrating the [IIIF Image API](http://iiif.io/api/image/2.1/) easier by extending Django's ImageField. By defining one or more named "profiles", your ImageFields expose IIIF-compatible URLs for each profile.
4 |
5 | ## Why Django-IIIF and not ImageKit
6 |
7 | I love ImageKit, but I recently worked on a project where we already had IIIF handling image derivative generation and serving, and Django ImageKit just got in the way. I wanted to still register my source images with Django, but serve them through an [IIIF server](https://github.com/loris-imageserver/loris), and this is what I came up with. I have lots of ideas for improvements here, but the initial release is just a santized version of what I used on my most recent project.
8 |
9 | ## Installation
10 |
11 | `pip install djiiif`
12 |
13 | ## Examples
14 |
15 | First, let's setup a new field (or convert an existing ImageField):
16 |
17 | `models.py`
18 | ```python
19 | from djiiif import IIIFField
20 |
21 | original = IIIFField()
22 | ```
23 |
24 | Second, configure the relevant settings.
25 |
26 | `settings.py`
27 | ```python
28 |
29 | IIIF_HOST = 'http://server/'
30 |
31 | IIIF_PROFILES = {
32 | 'thumbnail':
33 | {'host': IIIF_HOST,
34 | 'region': 'full',
35 | 'size': '150,',
36 | 'rotation': '0',
37 | 'quality': 'default',
38 | 'format': 'jpg'}
39 | }
40 | ```
41 |
42 |
43 | Finally, we can access profile(s) as attributes of the `iiif` attribute on an instance of `original`.
44 |
45 | In Python:
46 |
47 | ```python
48 | print(instance.original.name)
49 | > uploads/filename.jpg
50 |
51 | print(instance.original.iiif.thumbnail)
52 | > http://server/uploads/filename.jpg/full/150,/0/default.jpg
53 | ```
54 |
55 |
56 | In a Django template:
57 |
58 | ```
59 |
60 | ```
61 |
62 | As of version 0.15, we can also generate a IIIF info.json URL:
63 |
64 | ```
65 | print(instance.original.iiif.info)
66 | > http://server/uploads/filename.jpg/info.json
67 | ```
68 |
69 | ### callable-based profiles
70 |
71 | You can also use a callable to dynamically generate a URL. The callable will receive the parent `IIIFFieldFile` (a subclass of `ImageFieldFile`) as its sole parameter, `parent`, and must return a `dict` with the following keys: host, region, size, rotation, quality, and format. Using a callable allows you to implement more complex logic in your profile, including the ability to access the original file's name, width, and height.
72 |
73 | An example of a callable-based profile named `square` is below, used to generate a square-cropped image.
74 |
75 |
76 | ```python
77 | def squareProfile(original):
78 | width, height = original.width, original.height
79 |
80 | if width > height:
81 | x = int((width - height) / 2)
82 | y = 0
83 | w = height
84 | h = height
85 | region = '{},{},{},{}'.format(x,y,w,h)
86 | elif width < height:
87 | x = 0
88 | y = int((height - width) / 2)
89 | w = width
90 | h = width
91 | region = '{},{},{},{}'.format(x,y,w,h)
92 | else:
93 | region = 'full'
94 |
95 | spec = {'host': IIIF_HOST,
96 | 'region': region,
97 | 'size': '256,256',
98 | 'rotation': '0',
99 | 'quality': 'default',
100 | 'format': 'jpg'}
101 | return spec
102 | ```
103 |
104 | ```python
105 | IIIF_PROFILES = {
106 | 'thumbnail':
107 | {'host': IIIF_HOST,
108 | 'region': 'full',
109 | 'size': '150,',
110 | 'rotation': '0',
111 | 'quality': 'default',
112 | 'format': 'jpg'},
113 | 'preview':
114 | {'host': IIIF_HOST,
115 | 'region': 'full',
116 | 'size': '600,',
117 | 'rotation': '0',
118 | 'quality': 'default',
119 | 'format': 'jpg'},
120 | 'square': squareProfile
121 | }
122 | ```
123 |
124 | ### IIIF Template Tag
125 |
126 | An alternate way to access IIIF URLs for your IIIFField is via the `iiif` template tag.
127 |
128 | First, add `djiiif` to your `INSTALLED_APPS`:
129 |
130 |
131 | ```
132 | INSTALLED_APPS = [
133 | ...
134 | 'djiiif'
135 | ]
136 | ```
137 |
138 |
139 | Next, load our template tag library `iiiftags` in your template:
140 |
141 | ```
142 | {% load iiiftags %}
143 | ```
144 |
145 | Finally, use it in a template:
146 |
147 | ```
148 | {% iiif asset.original 'thumbnail' %}
149 | ```
150 |
151 | The first parameter (asset.original) is a reference to an IIIFField instance.
152 |
153 | The second parameter ('thumbnail') is the name of one of your IIIF profiles.
154 |
155 | This tag syntax is effectively the same as:
156 |
157 | ```
158 | {{ asset.original.iiif.thumbnail }}
159 | ```
160 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 |
2 | # djiiif
3 |
4 | djiiif is a package designed to make integrating the [IIIF Image API](http://iiif.io/api/image/2.1/) easier by extending Django's ImageField. By defining one or more named "profiles", your ImageFields expose IIIF-compatible URLs for each profile.
5 |
6 | ## Why djiiif and not ImageKit
7 |
8 | I love ImageKit, but I recently worked on a project where we already had IIIF handling image derivative generation and serving, and Django ImageKit just got in the way. I wanted to still register my source images with Django, but serve them through an [IIIF server](https://github.com/loris-imageserver/loris), and this is what I came up with. I have lots of ideas for improvements here, but the initial release is just a santized version of what I used on my most recent project.
9 |
10 | ## Installation
11 |
12 | `pip install djiiif`
13 |
14 | ## Examples
15 |
16 | First, let's setup a new field (or convert an existing ImageField):
17 |
18 |
19 |
20 | `models.py`
21 | ```python
22 | from djiiif import IIIFField
23 |
24 | original = IIIFField()
25 | ```
26 |
27 | Second, configure the relevant settings.
28 |
29 | `settings.py`
30 | ```python
31 |
32 | IIIF_HOST = 'http://server/'
33 |
34 | IIIF_PROFILES = {
35 | 'thumbnail':
36 | {'host': IIIF_HOST,
37 | 'region': 'full',
38 | 'size': '150,',
39 | 'rotation': '0',
40 | 'quality': 'default',
41 | 'format': 'jpg'}
42 | }
43 | ```
44 |
45 |
46 | Finally, we can access profile(s) as attributes of the `iiif` attribute on an instance of `original`.
47 |
48 | In Python:
49 |
50 | ```python
51 | print(instance.original.name)
52 | > uploads/filename.jpg
53 |
54 | print(instance.original.iiif.thumbnail)
55 | > http://server/uploads/filename.jpg/full/150,/0/default.jpg
56 | ```
57 |
58 |
59 | In a Django template:
60 |
61 | ```
62 |
63 | ```
64 |
65 | As of version 0.15, we can also generate a IIIF info.json URL:
66 |
67 | ```
68 | print(instance.original.iiif.info)
69 | > http://server/uploads/filename.jpg/info.json
70 | ```
71 |
72 | ### callable-based profiles
73 |
74 | You can also use a callable to dynamically generate a URL. The callable will receive the parent `IIIFFieldFile` (a subclass of `ImageFieldFile`) as its sole parameter, `parent`, and must return a `dict` with the following keys: host, region, size, rotation, quality, and format. Using a callable allows you to implement more complex logic in your profile, including the ability to access the original file's name, width, and height.
75 |
76 | An example of a callable-based profile named `square` is below, used to generate a square-cropped image.
77 |
78 |
79 | ```python
80 | def squareProfile(original):
81 | width, height = original.width, original.height
82 |
83 | if width > height:
84 | x = int((width - height) / 2)
85 | y = 0
86 | w = height
87 | h = height
88 | region = '{},{},{},{}'.format(x,y,w,h)
89 | elif width < height:
90 | x = 0
91 | y = int((height - width) / 2)
92 | w = width
93 | h = width
94 | region = '{},{},{},{}'.format(x,y,w,h)
95 | else:
96 | region = 'full'
97 |
98 | spec = {'host': IIIF_HOST,
99 | 'region': region,
100 | 'size': '256,256',
101 | 'rotation': '0',
102 | 'quality': 'default',
103 | 'format': 'jpg'}
104 | return spec
105 | ```
106 |
107 | ```python
108 | IIIF_PROFILES = {
109 | 'thumbnail':
110 | {'host': IIIF_HOST,
111 | 'region': 'full',
112 | 'size': '150,',
113 | 'rotation': '0',
114 | 'quality': 'default',
115 | 'format': 'jpg'},
116 | 'preview':
117 | {'host': IIIF_HOST,
118 | 'region': 'full',
119 | 'size': '600,',
120 | 'rotation': '0',
121 | 'quality': 'default',
122 | 'format': 'jpg'},
123 | 'square': squareProfile
124 | }
125 | ```
126 |
127 | ### IIIF Template Tag
128 |
129 | An alternate way to access IIIF URLs for your IIIFField is via the `iiif` template tag.
130 |
131 | First, add `djiiif` to your `INSTALLED_APPS`:
132 |
133 |
134 | ```
135 | INSTALLED_APPS = [
136 | ...
137 | 'djiiif'
138 | ]
139 | ```
140 |
141 |
142 | Next, load our template tag library `iiiftags` in your template:
143 |
144 | ```
145 | {% load iiiftags %}
146 | ```
147 |
148 | Finally, use it in a template:
149 |
150 | ```
151 | {% iiif asset.original 'thumbnail' %}
152 | ```
153 |
154 | The first parameter (asset.original) is a reference to an IIIFField instance.
155 |
156 | The second parameter ('thumbnail') is the name of one of your IIIF profiles.
157 |
158 | This tag syntax is effectively the same as:
159 |
160 | ```
161 | {{ asset.original.iiif.thumbnail }}
162 | ```
163 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | python3 setup.py sdist bdist_wheel
4 | python3 -m twine upload dist/*
--------------------------------------------------------------------------------
/djiiif/__init__.py:
--------------------------------------------------------------------------------
1 | from django.db.models import ImageField
2 | from django.db.models.fields.files import ImageFieldFile
3 | from django.conf import settings
4 |
5 |
6 | def urljoin(parts):
7 | """
8 | Takes a list of URL parts and smushes em together into a string,
9 | while ensuring no double slashes, but preserving any trailing slash(es)
10 | """
11 | if len(parts) == 0:
12 | raise ValueError('urljoin needs a list of at least length 1')
13 | return '/'.join([x.strip('/') for x in parts[0:-1]] + [parts[-1].lstrip('/')])
14 |
15 |
16 | class IIIFObject(object):
17 | def __init__(self, parent):
18 |
19 | # for each profile defined in settings
20 | for name in settings.IIIF_PROFILES:
21 | profile = settings.IIIF_PROFILES[name]
22 |
23 | if parent.name:
24 | if type(profile) is dict:
25 | iiif = profile
26 | elif callable(profile):
27 | iiif = profile(parent)
28 |
29 | identifier = parent.name.replace("/", "%2F")
30 |
31 | url = urljoin([iiif['host'], identifier, iiif['region'], iiif['size'], iiif['rotation'], '{}.{}'.format(iiif['quality'], iiif['format'])])
32 | setattr(self, name, url)
33 | else:
34 | setattr(self, name, "")
35 |
36 | # Add info.json URL
37 | if parent.name:
38 | url = urljoin([iiif['host'], identifier, "info.json"])
39 | setattr(self, "info", url)
40 | else:
41 | setattr(self, "info", "")
42 |
43 | # Add plain identifier URL
44 | if parent.name:
45 | url = urljoin([iiif['host'], identifier)
46 | setattr(self, "identifier", url)
47 | else:
48 | setattr(self, "identifier", "")
49 |
50 |
51 | class IIIFFieldFile(ImageFieldFile):
52 | @property
53 | def iiif(self):
54 | return IIIFObject(self)
55 |
56 | def __init__(self, *args, **kwargs):
57 | super(IIIFFieldFile, self).__init__(*args, **kwargs)
58 |
59 |
60 | class IIIFField(ImageField):
61 | attr_class = IIIFFieldFile
62 |
63 | def __init__(self, *args, **kwargs):
64 | super(IIIFField, self).__init__(*args, **kwargs)
65 |
--------------------------------------------------------------------------------
/djiiif/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from django.db.models import ImageField
3 | from django.db.models.fields.files import ImageFieldFile
4 | from django.conf import settings
5 | from django import template
6 |
7 |
8 |
9 | def urljoin(parts):
10 | """
11 | Takes a list of URL parts and smushes em together into a string,
12 | while ensuring no double slashes, but preserving any trailing slash(es)
13 | """
14 | if len(parts) == 0:
15 | raise ValueError('urljoin needs a list of at least length 1')
16 | return '/'.join([x.strip('/') for x in parts[0:-1]] + [parts[-1].lstrip('/')])
17 |
18 |
19 | class IIIFObject(object):
20 | def __init__(self, parent):
21 | for name in settings.IIIF_PROFILES:
22 |
23 | profile = settings.IIIF_PROFILES[name]
24 |
25 | if type(profile) is dict:
26 | iiif = profile
27 | elif callable(profile):
28 | iiif = profile(parent)
29 |
30 | url = urljoin([iiif['host'], parent.name, iiif['region'], iiif['size'], iiif['rotation'], '{}.{}'.format(iiif['quality'], iiif['format'])])
31 | setattr(self, name, url)
32 |
33 | setattr(self, 'url', urljoin([settings.IIIF_HOST, parent.name]))
34 |
35 |
36 | class IIIFFieldFile(ImageFieldFile):
37 | @property
38 | def iiif(self):
39 | return IIIFObject(self)
40 |
41 | def __init__(self, *args, **kwargs):
42 | super(IIIFFieldFile, self).__init__(*args, **kwargs)
43 |
44 |
45 | class IIIFField(ImageField):
46 | attr_class = IIIFFieldFile
47 |
48 | def __init__(self, *args, **kwargs):
49 | super(IIIFField, self).__init__(*args, **kwargs)
50 |
--------------------------------------------------------------------------------
/djiiif/templatetags/iiiftags.py:
--------------------------------------------------------------------------------
1 |
2 | from django.db.models import ImageField
3 | from django.db.models.fields.files import ImageFieldFile
4 | from django.conf import settings
5 | from django import template
6 | from djiiif import IIIFObject
7 |
8 |
9 | class NotAnIIIFField(AttributeError):
10 | def __init__(self, *args, **kwargs):
11 | AttributeError.__init__(self, *args, **kwargs)
12 |
13 | register = template.Library()
14 |
15 |
16 | @register.simple_tag
17 | def iiif(imagefield, profile):
18 | """
19 | Returns an IIIF URL based on an IIIFField and a profile stored in IIIF_PROFILES.
20 | """
21 | try:
22 | return getattr(imagefield.iiif, profile)
23 | except AttributeError:
24 | raise NotAnIIIFField('The iiif template tag expects an instance of a IIIFField as its first parameter.')
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = python -msphinx
7 | SPHINXPROJ = djiiif
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # djiiif documentation build configuration file, created by
5 | # sphinx-quickstart on Sun Oct 22 16:02:01 2017.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #
20 | # import os
21 | # import sys
22 | # sys.path.insert(0, os.path.abspath('.'))
23 |
24 |
25 | # -- General configuration ------------------------------------------------
26 |
27 | # If your documentation needs a minimal Sphinx version, state it here.
28 | #
29 | # needs_sphinx = '1.0'
30 |
31 | # Add any Sphinx extension module names here, as strings. They can be
32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
33 | # ones.
34 | extensions = []
35 |
36 | # Add any paths that contain templates here, relative to this directory.
37 | templates_path = ['_templates']
38 |
39 | # The suffix(es) of source filenames.
40 | # You can specify multiple suffix as a list of string:
41 | #
42 | # source_suffix = ['.rst', '.md']
43 | source_suffix = '.rst'
44 |
45 | # The master toctree document.
46 | master_doc = 'index'
47 |
48 | # General information about the project.
49 | project = 'djiiif'
50 | copyright = '2017, Roger Howard'
51 | author = 'Roger Howard'
52 |
53 | # The version info for the project you're documenting, acts as replacement for
54 | # |version| and |release|, also used in various other places throughout the
55 | # built documents.
56 | #
57 | # The short X.Y version.
58 | version = '0.1'
59 | # The full version, including alpha/beta/rc tags.
60 | release = '0.1'
61 |
62 | # The language for content autogenerated by Sphinx. Refer to documentation
63 | # for a list of supported languages.
64 | #
65 | # This is also used if you do content translation via gettext catalogs.
66 | # Usually you set "language" from the command line for these cases.
67 | language = None
68 |
69 | # List of patterns, relative to source directory, that match files and
70 | # directories to ignore when looking for source files.
71 | # This patterns also effect to html_static_path and html_extra_path
72 | exclude_patterns = []
73 |
74 | # The name of the Pygments (syntax highlighting) style to use.
75 | pygments_style = 'sphinx'
76 |
77 | # If true, `todo` and `todoList` produce output, else they produce nothing.
78 | todo_include_todos = False
79 |
80 |
81 | # -- Options for HTML output ----------------------------------------------
82 |
83 | # The theme to use for HTML and HTML Help pages. See the documentation for
84 | # a list of builtin themes.
85 | #
86 | html_theme = 'alabaster'
87 |
88 | # Theme options are theme-specific and customize the look and feel of a theme
89 | # further. For a list of options available for each theme, see the
90 | # documentation.
91 | #
92 | # html_theme_options = {}
93 |
94 | # Add any paths that contain custom static files (such as style sheets) here,
95 | # relative to this directory. They are copied after the builtin static files,
96 | # so a file named "default.css" will overwrite the builtin "default.css".
97 | html_static_path = ['_static']
98 |
99 | # Custom sidebar templates, must be a dictionary that maps document names
100 | # to template names.
101 | #
102 | # This is required for the alabaster theme
103 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
104 | html_sidebars = {
105 | '**': [
106 | 'about.html',
107 | 'navigation.html',
108 | 'relations.html', # needs 'show_related': True theme option to display
109 | 'searchbox.html',
110 | 'donate.html',
111 | ]
112 | }
113 |
114 |
115 | # -- Options for HTMLHelp output ------------------------------------------
116 |
117 | # Output file base name for HTML help builder.
118 | htmlhelp_basename = 'djiiifdoc'
119 |
120 |
121 | # -- Options for LaTeX output ---------------------------------------------
122 |
123 | latex_elements = {
124 | # The paper size ('letterpaper' or 'a4paper').
125 | #
126 | # 'papersize': 'letterpaper',
127 |
128 | # The font size ('10pt', '11pt' or '12pt').
129 | #
130 | # 'pointsize': '10pt',
131 |
132 | # Additional stuff for the LaTeX preamble.
133 | #
134 | # 'preamble': '',
135 |
136 | # Latex figure (float) alignment
137 | #
138 | # 'figure_align': 'htbp',
139 | }
140 |
141 | # Grouping the document tree into LaTeX files. List of tuples
142 | # (source start file, target name, title,
143 | # author, documentclass [howto, manual, or own class]).
144 | latex_documents = [
145 | (master_doc, 'djiiif.tex', 'djiiif Documentation',
146 | 'Roger Howard', 'manual'),
147 | ]
148 |
149 |
150 | # -- Options for manual page output ---------------------------------------
151 |
152 | # One entry per manual page. List of tuples
153 | # (source start file, name, description, authors, manual section).
154 | man_pages = [
155 | (master_doc, 'djiiif', 'djiiif Documentation',
156 | [author], 1)
157 | ]
158 |
159 |
160 | # -- Options for Texinfo output -------------------------------------------
161 |
162 | # Grouping the document tree into Texinfo files. List of tuples
163 | # (source start file, target name, title, author,
164 | # dir menu entry, description, category)
165 | texinfo_documents = [
166 | (master_doc, 'djiiif', 'djiiif Documentation',
167 | author, 'djiiif', 'One line description of project.',
168 | 'Miscellaneous'),
169 | ]
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. djiiif documentation master file, created by
2 | sphinx-quickstart on Sun Oct 22 16:02:01 2017.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to djiiif's documentation!
7 | ==================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 |
14 |
15 | Indices and tables
16 | ==================
17 |
18 | * :ref:`genindex`
19 | * :ref:`modindex`
20 | * :ref:`search`
21 |
22 |
23 | djiiif
24 | ======
25 |
26 | djiiif is a package designed to make integrating the `IIIF Image API`_
27 | easier by extending Django’s ImageField. By defining one or more named
28 | “profiles”, your ImageFields expose IIIF-compatible URLs for each
29 | profile.
30 |
31 | Why djiiif and not ImageKit
32 | ---------------------------
33 |
34 | I love ImageKit, but I recently worked on a project where we already had
35 | IIIF handling image derivative generation and serving, and Django
36 | ImageKit just got in the way. I wanted to still register my source
37 | images with Django, but serve them through an `IIIF server`_, and this
38 | is what I came up with. I have lots of ideas for improvements here, but
39 | the initial release is just a santized version of what I used on my most
40 | recent project.
41 |
42 | Installation
43 | ------------
44 |
45 | ``pip install djiiif``
46 |
47 | Examples
48 | --------
49 |
50 | First, let’s setup a new field (or convert an existing ImageField):
51 |
52 | ``models.py``
53 |
54 | .. code:: python
55 |
56 | from djiiif import IIIFField
57 |
58 | original = IIIFField()
59 |
60 | Second, configure the relevant settings.
61 |
62 | ``settings.py``
63 |
64 | .. code:: python
65 |
66 |
67 | IIIF_HOST = 'http://server/'
68 |
69 | IIIF_PROFILES = {
70 | 'thumbnail':
71 | {'host': IIIF_HOST,
72 | 'region': 'full',
73 | 'size': '150,',
74 | 'rotation': '0',
75 | 'quality': 'default',
76 | 'format': 'jpg'}
77 | }
78 |
79 | Finally, we can access profile(s) as attributes of the ``iiif``
80 | attribute on an instance of ``original``.
81 |
82 | In Python:
83 |
84 | .. code:: python
85 |
86 | print(instance.original.name)
87 | > uploads/filename.jpg
88 |
89 | print(instance.original.iiif.thumbnail)
90 | > http://server/uploads/filename.jpg/full/150,/0/default.jpg
91 |
92 | In a Django template:
93 |
94 | ::
95 |
96 |
97 |
98 | callable-based profiles
99 | ~~~~~~~~~~~~~~~~~~~~~~~
100 |
101 | You can also use a callable to dynamically generate a URL. The callable
102 | will receive the parent ``IIIFFieldFile`` (a subclass of
103 | ``ImageFieldFile``) as its sole parameter, ``parent``, and must return a
104 | ``dict`` with the following keys: host, region, size, rotation, quality,
105 | and format. Using a callable allows you to implement more complex logic
106 | in your profile, including the ability to access the original file’s
107 | name, width, and height.
108 |
109 | An example of a callable-based profile named ``square`` is below, used
110 | to generate a square-cropped image.
111 |
112 | .. code:: python
113 |
114 | def squareProfile(original):
115 | width, height = original.width, original.height
116 |
117 | if width > height:
118 | x = int((width - height) / 2)
119 | y = 0
120 | w = height
121 | h = height
122 | region = '{},{},{},{}'.format(x,y,w,h)
123 | elif width < height:
124 | x = 0
125 | y = int((height - width) / 2)
126 | w = width
127 | h = width
128 | region = '{},{},{},{}'.format(x,y,w,h)
129 | else:
130 | region = 'full'
131 |
132 | spec = {'host': IIIF_HOST,
133 | 'region': region,
134 | 'size': '256,256',
135 | 'rotation': '0',
136 | 'quality': 'default',
137 | 'format': 'jpg'}
138 | return spec
139 |
140 | .. code:: python
141 |
142 | IIIF_PROFILES = {
143 | 'thumbnail':
144 | {'host': IIIF_HOST,
145 | 'region': 'full',
146 | 'size': '150,',
147 | 'rotation': '0',
148 | 'quality': 'default',
149 | 'format': 'jpg'},
150 | 'preview':
151 | {'host': IIIF_HOST,
152 | 'region': 'full',
153 | 'size': '600,',
154 | 'rotation': '0',
155 | 'quality': 'default',
156 | 'format': 'jpg'},
157 | 'square': squareProfile
158 | }
159 |
160 |
161 | IIIF Template Tag
162 | ~~~~~~~~~~~~~~~~~
163 |
164 | An alternate way to access IIIF URLs for your IIIFField is via the `iiif` template tag.
165 |
166 | First, add ``djiiif`` to your ``INSTALLED_APPS``:
167 |
168 | .. code:: python
169 |
170 | INSTALLED_APPS = [
171 | ...
172 | 'djiiif'
173 | ]
174 |
175 |
176 | Next, load our template tag library `iiiftags` in your template:
177 |
178 | .. code:: python
179 |
180 | {% load iiiftags %}
181 |
182 |
183 | Finally, use it in a template:
184 |
185 | .. code:: python
186 |
187 | {% iiif asset.original 'thumbnail' %}
188 |
189 |
190 | The first parameter (asset.original) is a reference to an IIIFField instance.
191 |
192 | The second parameter ('thumbnail') is the name of one of your IIIF profiles.
193 |
194 | This tag syntax is effectively the same as:
195 |
196 | .. code:: python
197 |
198 | {{ asset.original.iiif.thumbnail }}
199 |
200 |
201 | .. _IIIF Image API: http://iiif.io/api/image/2.1/
202 | .. _IIIF server: https://github.com/loris-imageserver/loris
203 |
204 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 |
2 | twine
3 | sphinx
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import find_packages, setup
2 | import os
3 |
4 | # allow setup.py to be run from any path
5 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
6 |
7 | setup(
8 | name='djiiif',
9 | version='0.21',
10 |
11 | packages=find_packages(),
12 | install_requires=['Django'],
13 | include_package_data=True,
14 | license='BSD License', # example license
15 | description='Simple IIIF integration for Django.',
16 | long_description='djiiif is a package designed to make integrating the IIIF Image API easier by extending Django\'s ImageField',
17 | url='https://github.com/rogerhoward/djiiif/',
18 | author='Roger Howard',
19 | author_email='rogerhoward+django@gmail.com',
20 | classifiers=[
21 | 'Environment :: Web Environment',
22 | 'Framework :: Django',
23 | 'Framework :: Django :: 1.11',
24 | 'Intended Audience :: Developers',
25 | 'License :: OSI Approved :: BSD License',
26 | 'Operating System :: OS Independent',
27 | 'Programming Language :: Python',
28 | 'Programming Language :: Python :: 3',
29 | 'Programming Language :: Python :: 3.6',
30 | 'Programming Language :: Python :: 3.7',
31 | 'Programming Language :: Python :: 3.8',
32 | 'Topic :: Internet :: WWW/HTTP',
33 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
34 | ],
35 | )
--------------------------------------------------------------------------------