├── LICENSE ├── MANIFEST.in ├── README.rst ├── dummyimage ├── __init__.py ├── fonts │ └── DroidSans.ttf ├── forms.py ├── models.py ├── settings.py ├── templatetags │ ├── __init__.py │ └── dummyimage_tags.py ├── tests.py ├── urls.py └── views.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Rolando Espinoza La fuente. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Rolando Espinoza La fuente nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include dummyimage/fonts *.ttf 2 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django DummyImage 2 | ================= 3 | 4 | A simple app to generate dummy/filler images on the fly at whatever size you want. 5 | 6 | Inspired by http://dummyimage.com/ and http://github.com/xxx/fakeimage 7 | 8 | Installation 9 | ============ 10 | 11 | Installation using ``pip``:: 12 | 13 | $ pip install django-dummyimage 14 | 15 | Running tests:: 16 | 17 | $ DJANGO_SETTINGS_MODULE=dummyimage.settings django-admin.py test dummyimage 18 | 19 | Running demo:: 20 | 21 | $ DJANGO_SETTINGS_MODULE=dummyimage.settings django-admin.py runserver 22 | $ xdg-open "http://localhost:8000/500x150.png?text=hello+world" 23 | 24 | 25 | Setup & Settings 26 | ================ 27 | 28 | Add ``dummyimage`` to your ``INSTALLED_APPS`` setting. 29 | 30 | Default settings:: 31 | 32 | DUMMYIMAGE_MAX_DIMENSION = 1024 33 | DUMMYIMAGE_DEFAULT_BG = 'white' 34 | DUMMYIMAGE_DEFAULT_TEXT = 'grey' 35 | DUMMYIMAGE_DEFAULT_BORDER = 'grey' 36 | 37 | 38 | Template Tag 39 | ============ 40 | 41 | Code:: 42 | 43 | 44 | 45 | Output:: 46 | 47 | 48 | 49 | 50 | Example:: 51 | 52 | {% get_dummyimage_url 320 240 png as image %} 53 | 54 | 55 | 56 | Query Parameters 57 | ================ 58 | 59 | Available parameters: 60 | 61 | - ``text=string`` text to be rendered in the middle of the image. 62 | - ``textcolor=color`` text color. 63 | - ``bgcolor=color`` background color. 64 | - ``bordercolor=color`` border color. 65 | - ``noborder=1`` disable border. 66 | - ``cross=1`` draw a cross in the through the image. 67 | 68 | .. note:: 69 | Colors can be literal color names (e.g. ``white``, ``red``) or hexadecimal 70 | values starting with ``!``, for example: ``!333``, ``!AAA``, ``white``, 71 | ``blue``, ``!CBCBCB``. 72 | -------------------------------------------------------------------------------- /dummyimage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmax/django-dummyimage/e95c60a1a8f9f4c9ed1805dc219ed79ad83b877c/dummyimage/__init__.py -------------------------------------------------------------------------------- /dummyimage/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmax/django-dummyimage/e95c60a1a8f9f4c9ed1805dc219ed79ad83b877c/dummyimage/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /dummyimage/forms.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | from django import forms 4 | from django.conf import settings 5 | 6 | 7 | MAX_DIMENSION = getattr(settings, 'DUMMYIMAGE_MAX_DIMENSION', sys.maxint) 8 | DEFAULT_BG = getattr(settings, 'DUMMYIMAGE_DEFAULT_BG', 'white') 9 | DEFAULT_TEXT = getattr(settings, 'DUMMYIMAGE_DEFAULT_TEXT', 'grey') 10 | DEFAULT_BORDER = getattr(settings, 'DUMMYIMAGE_DEFAULT_BORDER', 'grey') 11 | 12 | DEFAULT_COLORS = { 13 | 'BG': DEFAULT_BG, 14 | 'TEXT': DEFAULT_TEXT, 15 | 'BORDER': DEFAULT_BORDER, 16 | } 17 | 18 | RE_HEX_FULL = re.compile('^[A-F0-9]{6}$', re.IGNORECASE) 19 | RE_HEX_SHORT = re.compile('^[A-F0-9]{3}$', re.IGNORECASE) 20 | 21 | 22 | def _get_color(value, type): 23 | """Returns valid color""" 24 | if value and value.startswith('!'): 25 | # normalize hex color 26 | if RE_HEX_FULL.match(value[1:]): 27 | color = '#%s' % ''.join(value[1:]).upper() 28 | elif RE_HEX_SHORT.match(value[1:]): 29 | # duplicate short values 30 | color = '#%s' % ''.join(c * 2 for c in value[1:]).upper() 31 | else: 32 | # invalid color 33 | color = DEFAULT_COLORS[type] 34 | return color 35 | else: 36 | # assume literal color. e.g. white, grey69, etc 37 | return value if value else DEFAULT_COLORS[type] 38 | 39 | 40 | class DummyImageForm(forms.Form): 41 | bgcolor = forms.CharField(initial=DEFAULT_BG, required=False) 42 | transparent = forms.BooleanField(initial=False, required=False) 43 | 44 | text = forms.CharField(required=False) 45 | textcolor = forms.CharField(initial=DEFAULT_TEXT, required=False) 46 | 47 | bordercolor = forms.CharField(initial=DEFAULT_BORDER, required=False) 48 | noborder = forms.BooleanField(initial=False, required=False) 49 | cross = forms.BooleanField(initial=False, required=False) 50 | 51 | width = forms.IntegerField(min_value=1, max_value=MAX_DIMENSION) 52 | height = forms.IntegerField(min_value=1, max_value=MAX_DIMENSION) 53 | rotate = forms.IntegerField(min_value=-359, max_value=359, initial=0, 54 | required=False) 55 | 56 | def clean_bgcolor(self): 57 | color = self.cleaned_data['bgcolor'] 58 | color = _get_color(color, 'BG') 59 | return color 60 | 61 | def clean_textcolor(self): 62 | color = self.cleaned_data['textcolor'] 63 | color = _get_color(color, 'TEXT') 64 | return color 65 | 66 | def clean_bordercolor(self): 67 | color = self.cleaned_data['bordercolor'] 68 | color = _get_color(color, 'BORDER') 69 | return color 70 | 71 | def clean(self): 72 | cleaned_data = super(DummyImageForm, self).clean() 73 | text = cleaned_data.get('text') 74 | width = cleaned_data.get('width') 75 | height = cleaned_data.get('height') 76 | 77 | if not text and width and height: 78 | text = '%d x %d' % (width, height) 79 | 80 | return cleaned_data 81 | -------------------------------------------------------------------------------- /dummyimage/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PIL import Image 3 | from PIL import ImageDraw 4 | from PIL import ImageFont 5 | 6 | from dummyimage.forms import DummyImageForm 7 | 8 | # TODO: move to settings 9 | FONT_FILE = 'DroidSans.ttf' 10 | FONT_PATH = os.path.join(os.path.dirname(__file__), 'fonts', FONT_FILE) 11 | 12 | 13 | class DummyImage(object): 14 | 15 | class InvalidParams(Exception): 16 | def __init__(self, message, form): 17 | self.form = form 18 | super(DummyImage.InvalidParams, self).__init__(message) 19 | 20 | @classmethod 21 | def new(cls, width, height, **kwargs): 22 | data = kwargs.copy() 23 | data.update({ 24 | 'width': width, 25 | 'height': height, 26 | }) 27 | form = DummyImageForm(data=data) 28 | 29 | if not form.is_valid(): 30 | raise cls.InvalidParams("Invalid image params", form) 31 | 32 | # custom text 33 | text = form.cleaned_data['text'] 34 | 35 | # mode. Use RGBA if transparent 36 | mode = 'RGBA' if form.cleaned_data.get('transparent') else 'RGB' 37 | width = form.cleaned_data['width'] 38 | height = form.cleaned_data['height'] 39 | size = (width, height) 40 | 41 | # allow transparent color 42 | if 'RGBA' == mode: 43 | bgcolor = None 44 | else: 45 | bgcolor = form.cleaned_data['bgcolor'] 46 | 47 | # color allows short hex format 48 | image = Image.new(mode, size, bgcolor) 49 | draw = ImageDraw.Draw(image) 50 | 51 | bordercolor = form.cleaned_data['bordercolor'] 52 | # draw border 53 | if not form.cleaned_data.get('noborder'): 54 | draw.polygon([(0, 0), (width - 1, 0), (width - 1, height - 1), 55 | (0, height - 1)], outline=bordercolor) 56 | 57 | # draw cross 58 | if form.cleaned_data.get('cross'): 59 | draw.line([(0, 0), (width - 1, height - 1)], fill=bordercolor) 60 | draw.line([(0, height - 1), (width - 1, 0)], fill=bordercolor) 61 | 62 | # draw text centered 63 | if text: 64 | font = ImageFont.truetype(FONT_PATH, width / 10) 65 | 66 | center = (width / 2, height / 2) 67 | text_size = font.getsize(text) 68 | text_center = (center[0] - text_size[0] / 2, 69 | center[1] - text_size[1] / 2) 70 | draw.text(text_center, text, font=font, 71 | fill=form.cleaned_data['textcolor']) 72 | 73 | return image 74 | -------------------------------------------------------------------------------- /dummyimage/settings.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | TEMPLATE_DEBUG = DEBUG 3 | 4 | DATABASES = { 5 | 'default': { 6 | 'ENGINE': 'django.db.backends.sqlite3', 7 | 'NAME': ':memory:', 8 | } 9 | } 10 | 11 | DUMMYIMAGE_MAX_DIMENSION = 1024 12 | 13 | ROOT_URLCONF = 'dummyimage.urls' 14 | 15 | INSTALLED_APPS = ( 16 | 'dummyimage', 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /dummyimage/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmax/django-dummyimage/e95c60a1a8f9f4c9ed1805dc219ed79ad83b877c/dummyimage/templatetags/__init__.py -------------------------------------------------------------------------------- /dummyimage/templatetags/dummyimage_tags.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.template import Library 3 | from django.template import Node 4 | from django.template import TemplateSyntaxError 5 | from django.template import Variable 6 | from django.template import resolve_variable 7 | from django.utils.translation import ugettext as _ 8 | 9 | register = Library() 10 | 11 | class DummyImageUrlNode(Node): 12 | def __init__(self, width, height, format, context_var): 13 | # TODO: support variable names instead of only integers 14 | self.width = width 15 | self.height = height 16 | self.format = format 17 | self.context_var = context_var 18 | 19 | def render(self, context): 20 | url = reverse('dummyimage.views.render_image', 21 | args=(self.width, self.height, self.format)) 22 | 23 | if self.context_var: 24 | # push url to context 25 | context[self.context_var] = url 26 | return '' 27 | else: 28 | # render url 29 | return url 30 | 31 | def do_get_dummyimage_url(parser, token): 32 | """ 33 | Returns dummy image URL 34 | 35 | Available formats: gif, jpg, png 36 | Filename format: {w}x{h}.{format} e.g. 100x100.jpg 37 | 38 | Usage:: 39 | {% get_dummyimage_url [width] [height] [format] as [varname] %} 40 | {% get_dummyimage_url [width] [height] as [varname] %} 41 | {% get_dummyimage_url [filename] as [varname] %} 42 | 43 | {% get_dummyimage_url [width] [height] [format] %} 44 | {% get_dummyimage_url [width] [height] %} 45 | {% get_dummyimage_url [filename] %} 46 | 47 | Example:: 48 | 49 | 50 | 51 | {% get_dummyimage_url 320 240 png as image_url %} 52 | {% get_dummyimage_url 320 240 as image_url %} 53 | {% get_dummyimage_url 320x240.gif as image_url %} 54 | """ 55 | bits = token.contents.split() 56 | # optional arguments 57 | format = None 58 | varname = None 59 | 60 | # tag + 1-5 arguments 61 | if not 1 < len(bits) <= 6: 62 | raise TemplateSyntaxError(_("%s tag requires between one and five arguments") % bits[0]) 63 | 64 | # tag + 5 arguments 65 | if len(bits) == 6: 66 | if not bits[4] == 'as': 67 | raise TemplateSyntaxError(_("if given, fourth argument to %s tag must be 'as'") % bits[0]) 68 | 69 | width = bits[1] 70 | height = bits[2] 71 | format = bits[3] 72 | varname = bits[5] 73 | 74 | # tag + 4 arguments 75 | if len(bits) == 5: 76 | if not bits[3] == 'as': 77 | raise TemplateSyntaxError(_("if given, third argument to %s tag must be 'as'") % bits[0]) 78 | 79 | width = bits[1] 80 | height = bits[2] 81 | varname = bits[4] 82 | 83 | # tag + 3 arguments 84 | if len(bits) == 4: 85 | if bits[2] == 'as': 86 | varname = bits[3] 87 | try: 88 | width, remain = bits[1].split('x') 89 | height, format = remain.split('.') 90 | except ValueError: 91 | raise TemplateSyntaxError(_("first argument to %s tag must be in format 'WidthxHeight.format'. e.g. 320x240.png") % bits[0]) 92 | else: 93 | width = bits[1] 94 | height = bits[2] 95 | format = bits[3] 96 | 97 | # tag + 2 arguments 98 | if len(bits) == 3: 99 | width = bits[1] 100 | height = bits[2] 101 | 102 | # tag + 1 argument 103 | if len(bits) == 2: 104 | try: 105 | width, remain = bits[1].split('x') 106 | height, format = remain.split('.') 107 | except ValueError: 108 | raise TemplateSyntaxError(_("first argument to %s tag must be in format 'WidthxHeight.format'. e.g. 320x240.png") % bits[0]) 109 | 110 | # validate width and height integers 111 | try: 112 | width = int(width) 113 | height = int(height) 114 | except ValueError: 115 | raise TemplateSyntaxError(_("width and height argument to %s tag must be integers") % bits[0]) 116 | 117 | # validate format 118 | if not format: 119 | format = 'jpg' # default 120 | elif not format in ('jpg', 'gif', 'png'): 121 | raise TemplateSyntaxError(_("format argument must be either jpg, png or gif")) 122 | 123 | return DummyImageUrlNode(width, height, format, varname) 124 | 125 | register.tag('get_dummyimage_url', do_get_dummyimage_url) 126 | 127 | 128 | -------------------------------------------------------------------------------- /dummyimage/tests.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.template import Context 3 | from django.template import Template 4 | 5 | from django.test import Client 6 | from django.test import TestCase 7 | 8 | from dummyimage.forms import _get_color 9 | 10 | 11 | class TemplateTagTest(TestCase): 12 | def setUp(self): 13 | self.client = Client() 14 | self.get_url = lambda w, h, fmt: reverse( 15 | 'dummyimage.views.render_image', 16 | args=(w, h, fmt)) 17 | 18 | def get_template(self, arguments): 19 | return '{%% load dummyimage_tags %%}{%% get_dummyimage_url %s %%}' % arguments 20 | 21 | def test_render_filename(self): 22 | url = self.get_url(100, 100, 'jpg') 23 | t = Template(self.get_template('100x100.jpg')) 24 | c = Context({}) 25 | self.failUnlessEqual(url, t.render(c)) 26 | 27 | def test_render_filename_to_context(self): 28 | url = self.get_url(100, 100, 'jpg') 29 | t = Template(self.get_template('100x100.jpg as myvar')) 30 | c = Context({}) 31 | t.render(c) 32 | self.failUnlessEqual(url, c['myvar']) 33 | 34 | def test_render_width_and_height(self): 35 | url = self.get_url(100, 100, 'jpg') 36 | #@@@ default format jpg 37 | t = Template(self.get_template('100 100')) 38 | c = Context({}) 39 | self.failUnlessEqual(url, t.render(c)) 40 | 41 | def test_render_width_and_height_to_context(self): 42 | url = self.get_url(100, 100, 'jpg') 43 | #@@@ default format jpg 44 | t = Template(self.get_template('100 100 as myvar')) 45 | c = Context({}) 46 | t.render(c) 47 | self.failUnlessEqual(url, c['myvar']) 48 | 49 | def test_render_width_height_format(self): 50 | url = self.get_url(100, 200, 'png') 51 | #@@@ default format jpg 52 | t = Template(self.get_template('100 200 png')) 53 | c = Context({}) 54 | self.failUnlessEqual(url, t.render(c)) 55 | 56 | def test_render_width_height_format_to_context(self): 57 | url = self.get_url(100, 200, 'png') 58 | #@@@ default format jpg 59 | t = Template(self.get_template('100 200 png as myvar')) 60 | c = Context({}) 61 | t.render(c) 62 | self.failUnlessEqual(url, c['myvar']) 63 | 64 | 65 | class RenderViewTest(TestCase): 66 | def setUp(self): 67 | self.client = Client() 68 | self.get_url = lambda w, h, fmt: reverse( 69 | 'dummyimage.views.render_image', 70 | args=(w, h, fmt)) 71 | 72 | def test_sizes_boundaries(self): 73 | # sizes 74 | response = self.client.get(self.get_url(0, 0, 'jpg')) 75 | self.failUnlessEqual(response.status_code, 404) 76 | 77 | response = self.client.get(self.get_url(1, 1, 'jpg')) 78 | self.failUnlessEqual(response.status_code, 200) 79 | 80 | response = self.client.get(self.get_url(1024, 1024, 'jpg')) 81 | self.failUnlessEqual(response.status_code, 200) 82 | 83 | response = self.client.get(self.get_url(1025, 1025, 'jpg')) 84 | self.failUnlessEqual(response.status_code, 404) 85 | 86 | def test_valid_formats(self): 87 | # formats 88 | response = self.client.get(self.get_url(1, 1, 'jpg')) 89 | self.failUnlessEqual(response.status_code, 200) 90 | self.failUnlessEqual(response['Content-Type'], 'image/jpeg') 91 | 92 | response = self.client.get(self.get_url(1, 1, 'gif')) 93 | self.failUnlessEqual(response.status_code, 200) 94 | self.failUnlessEqual(response['Content-Type'], 'image/gif') 95 | 96 | response = self.client.get(self.get_url(1, 1, 'png')) 97 | self.failUnlessEqual(response.status_code, 200) 98 | self.failUnlessEqual(response['Content-Type'], 'image/png') 99 | 100 | def test_invalid_formats(self): 101 | response = self.client.get(self.get_url(1, 1, 'avi')) 102 | self.failUnlessEqual(response.status_code, 404) 103 | 104 | response = self.client.get(self.get_url(1, 1, 'pic')) 105 | self.failUnlessEqual(response.status_code, 404) 106 | 107 | response = self.client.get(self.get_url(1, 1, 'bmp')) 108 | self.failUnlessEqual(response.status_code, 404) 109 | 110 | 111 | def test_rotation_param(self): 112 | ## Check Params 113 | url = self.get_url(1, 1, 'jpg') 114 | 115 | # rotation 116 | response = self.client.get(url, {'rotate': '-359'}) 117 | self.failUnlessEqual(response.status_code, 200) 118 | response = self.client.get(url, {'rotate': '0'}) 119 | self.failUnlessEqual(response.status_code, 200) 120 | response = self.client.get(url, {'rotate': '359'}) 121 | self.failUnlessEqual(response.status_code, 200) 122 | 123 | def test_invalid_rotation_param(self): 124 | url = self.get_url(1, 1, 'jpg') 125 | # invalid 126 | response = self.client.get(url, {'rotate': 'asdf'}) 127 | self.failUnlessEqual(response.status_code, 404) 128 | 129 | response = self.client.get(url, {'rotate': '-360'}) 130 | self.failUnlessEqual(response.status_code, 404) 131 | response = self.client.get(url, {'rotate': '360'}) 132 | self.failUnlessEqual(response.status_code, 404) 133 | 134 | def test_text_parameters(self): 135 | url = self.get_url(1, 1, 'jpg') 136 | 137 | response = self.client.get(url, {'text': 'asdf'}) 138 | # TODO: verify actual font rendering 139 | self.assertEquals(response.status_code, 200) 140 | 141 | 142 | class GetColorTest(TestCase): 143 | def test_get_color_util(self): 144 | """ 145 | Tests return of _get_color function 146 | """ 147 | #@@@ defaults: bg -> white, text -> black, border -> grey 148 | 149 | def test_default_returns(self): 150 | # Test defaults 151 | self.failUnlessEqual('white', _get_color('', 'BG')) 152 | self.failUnlessEqual('grey', _get_color('', 'TEXT')) 153 | self.failUnlessEqual('grey', _get_color('', 'BORDER')) 154 | 155 | def test_default_values_if_invalid(self): 156 | self.failUnlessEqual('white', _get_color('!invalid', 'BG')) 157 | self.failUnlessEqual('grey', _get_color('!invalid', 'TEXT')) 158 | self.failUnlessEqual('grey', _get_color('!invalid', 'BORDER')) 159 | 160 | def test_hex_values(self): 161 | # Test hex values 162 | self.failUnlessEqual('#000000', _get_color('!000000', 'BG')) 163 | self.failUnlessEqual('#FFFFFF', _get_color('!ffffff', 'BG')) 164 | 165 | def test_short_hex_values(self): 166 | # Shorts 167 | self.failUnlessEqual('#001122', _get_color('!012', 'BG')) 168 | self.failUnlessEqual('#AABBCC', _get_color('!abc', 'BG')) 169 | 170 | def test_invalid_hex_values(self): 171 | # invalids 172 | self.failUnlessEqual('grey', _get_color('!xyz', 'TEXT')) 173 | self.failUnlessEqual('grey', _get_color('!1234567', 'BORDER')) 174 | 175 | def test_invalid_type(self): 176 | # Test exceptions 177 | # invalid type 178 | self.failUnlessRaises(KeyError, _get_color, '', 'TYPE') 179 | self.failUnlessRaises(KeyError, _get_color, '!invalid', 'TYPE') 180 | 181 | -------------------------------------------------------------------------------- /dummyimage/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | from django.http import HttpResponseNotFound 3 | 4 | WIDTH_RE = r'(?P\d+)' 5 | HEIGHT_RE = r'(?P\d+)' 6 | FORMAT_RE = r'(?P[a-z]{3})' 7 | IMAGE_RE = r'^%sx%s\.%s$' % (WIDTH_RE, HEIGHT_RE, FORMAT_RE) 8 | 9 | urlpatterns = patterns('dummyimage.views', 10 | (IMAGE_RE, 'render_image'), 11 | ) 12 | 13 | 14 | def view404(request): 15 | return HttpResponseNotFound() 16 | 17 | 18 | handler404 = view404 19 | -------------------------------------------------------------------------------- /dummyimage/views.py: -------------------------------------------------------------------------------- 1 | from django.http import Http404 2 | from django.http import HttpResponse 3 | 4 | from .models import DummyImage 5 | 6 | AVAILABLE_FORMATS = ('jpeg', 'gif', 'png') 7 | 8 | 9 | def render_image(request, width, height, format): 10 | params = dict(request.GET.items()) 11 | image_format = 'jpeg' if format == 'jpg' else format 12 | try: 13 | dummyimage = DummyImage.new(width, height, **params) 14 | except DummyImage.InvalidParams: 15 | raise Http404 16 | 17 | if image_format not in AVAILABLE_FORMATS: 18 | raise Http404 19 | 20 | # write image to response 21 | response = HttpResponse(mimetype='image/%s' % image_format) 22 | #TODO: catch exceptions 23 | dummyimage.save(response, image_format) 24 | 25 | return response 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | 4 | setup( 5 | name = "django-dummyimage", 6 | version = "0.1.1", 7 | description = "Dynamic Dummy Image Generator For Django!", 8 | author = "Rolando Espinoza La fuente", 9 | author_email = "darkrho@gmail.com", 10 | url = "https://github.com/darkrho/django-dummyimage", 11 | license = "BSD", 12 | packages = find_packages(), 13 | zip_safe=False, # because we're including media that Django needs 14 | include_package_data = True, 15 | install_requires = [ 16 | 'django', 17 | 'pil', 18 | ], 19 | classifiers=[ 20 | 'Programming Language :: Python', 21 | 'Framework :: Django', 22 | 'Development Status :: 4 - Beta', 23 | 'Intended Audience :: Developers', 24 | ], 25 | ) 26 | --------------------------------------------------------------------------------