Move verdantimages app to django-wagtail package

pull/3/head
Matt Westcott 2014-01-28 12:31:46 +00:00
rodzic 351e469b12
commit b433d5c23a
38 zmienionych plików z 1645 dodań i 15 usunięć

Wyświetl plik

@ -105,7 +105,7 @@
}
/*
These styles correspond to the image formats defined in verdantimages/formats.py,
These styles correspond to the image formats defined in wagtailimages/formats.py,
so that images displayed in the rich text field receive more or less the same
styling that they would receive on the site front-end.
TODO: when we implement a mechanism to configure the image format list on a

Wyświetl plik

@ -5,8 +5,8 @@
{% endcomment %}
{% comment %}
TODO: have a mechanism to specify this include within the verdantimages app -
ideally wagtailadmin shouldn't have to know anything at all about verdantimages
TODO: have a mechanism for sub-apps to specify their own declarations -
ideally wagtailadmin shouldn't have to know anything at all about wagtailimages and friends
{% endcomment %}
{% compress css %}

Wyświetl plik

@ -13,19 +13,19 @@
<script src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-verdant-toolbar.js"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-verdantlink.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailadmin/js/hallo-plugins/hallo-hr.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}verdantimages/js/hallo-plugins/hallo-verdantimage.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtailimages/js/hallo-plugins/hallo-verdantimage.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}verdantembeds/js/hallo-plugins/hallo-verdantembeds.coffee"></script>
<script type="text/coffeescript" src="{{ STATIC_URL }}wagtaildocs/js/hallo-plugins/hallo-verdantdoclink.coffee"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/page-editor.js"></script>
<script src="{{ STATIC_URL }}wagtailadmin/js/page-chooser.js"></script>
{% comment %}
TODO: have a mechanism to specify this include (and hallo-verdantimage.coffee)
within the verdantimages app -
ideally wagtailadmin shouldn't have to know anything at all about verdantimages
TODO: have a mechanism to specify image-chooser.js (and hallo-verdantimage.coffee)
within the wagtailimages app -
ideally wagtailadmin shouldn't have to know anything at all about wagtailimages
TODO: a method of injecting these sorts of things on demand when the modal is spawned.
{% endcomment %}
<script src="{{ STATIC_URL }}wagtailadmin/js/page-chooser.js"></script>
<script src="{{ STATIC_URL }}verdantimages/js/image-chooser.js"></script>
<script src="{{ STATIC_URL }}wagtailimages/js/image-chooser.js"></script>
<script src="{{ STATIC_URL }}wagtaildocs/js/document-chooser.js"></script>
<script src="{{ STATIC_URL }}wagtailsnippets/js/snippet-chooser.js"></script>
{% endcompress %}

Wyświetl plik

@ -45,9 +45,9 @@ def main_nav(context):
request = context['request']
user = request.user
if user.has_perm('verdantimages.add_image'):
if user.has_perm('wagtailimages.add_image'):
menu_items.append(
MenuItem('Images', urlresolvers.reverse('verdantimages_index'), classnames='icon icon-image', order=300)
MenuItem('Images', urlresolvers.reverse('wagtailimages_index'), classnames='icon icon-image', order=300)
)
if user.has_perm('wagtaildocs.add_document'):
menu_items.append(

Wyświetl plik

@ -5,7 +5,7 @@ from django.template import RequestContext
from django.template.loader import render_to_string
from wagtail.wagtailcore.models import Page, PageRevision, UserPagePermissionsProxy
from verdantimages.models import get_image_model
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtaildocs.models import Document
from wagtail.wagtailadmin import hooks

Wyświetl plik

@ -5,11 +5,11 @@ import re # parsing HTML with regexes LIKE A BOSS.
from wagtail.wagtailcore.whitelist import Whitelister
from wagtail.wagtailcore.models import Page
# FIXME: we don't really want to import verdantimages within core.
# FIXME: we don't really want to import wagtailimages within core.
# For that matter, we probably don't want core to be concerned about translating
# HTML for the benefit of the hallo.js editor...
from verdantimages.models import get_image_model
from verdantimages.formats import get_image_format
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.formats import get_image_format
from wagtail.wagtaildocs.models import Document

Wyświetl plik

@ -0,0 +1,12 @@
from django.contrib import admin
from django.conf import settings
from wagtail.wagtailimages.models import Image
if hasattr(settings, 'WAGTAILIMAGES_IMAGE_MODEL') and settings.WAGTAILIMAGES_IMAGE_MODEL != 'wagtailimages.Image':
# This installation provides its own custom image class;
# to avoid confusion, we won't expose the unused wagtailimages.Image class
# in the admin.
pass
else:
admin.site.register(Image)

Wyświetl plik

@ -0,0 +1,11 @@
from wagtail.wagtailadmin.edit_handlers import BaseChooserPanel
class BaseImageChooserPanel(BaseChooserPanel):
field_template = "wagtailimages/edit_handlers/image_chooser_panel.html"
object_type_name = "image"
js_function_name = "createImageChooser"
def ImageChooserPanel(field_name):
return type('_ImageChooserPanel', (BaseImageChooserPanel,), {
'field_name': field_name,
})

Wyświetl plik

@ -0,0 +1,87 @@
from django.conf import settings
from django.utils.importlib import import_module
from django.utils.html import escape
class Format(object):
def __init__(self, name, label, classnames, filter_spec):
self.name = name
self.label = label
self.classnames = classnames
self.filter_spec = filter_spec
def editor_attributes(self, image, alt_text):
"""
Return string of additional attributes to go on the HTML element
when outputting this image within a rich text editor field
"""
return 'data-embedtype="image" data-id="%d" data-format="%s" data-alt="%s" ' % (
image.id, self.name, alt_text
)
def image_to_editor_html(self, image, alt_text):
return self.image_to_html(
image, alt_text, self.editor_attributes(image, alt_text)
)
def image_to_html(self, image, alt_text, extra_attributes=''):
rendition = image.get_rendition(self.filter_spec)
if self.classnames:
class_attr = 'class="%s" ' % escape(self.classnames)
else:
class_attr = ''
return '<img %s%ssrc="%s" width="%d" height="%d" alt="%s">' % (
extra_attributes, class_attr,
escape(rendition.url), rendition.width, rendition.height, alt_text
)
FORMATS = []
FORMATS_BY_NAME = {}
def register_image_format(format):
if format.name in FORMATS_BY_NAME:
raise KeyError("Image format '%s' is already registered" % format.name)
FORMATS_BY_NAME[format.name] = format
FORMATS.append(format)
def unregister_image_format(format_name):
global FORMATS
# handle being passed a format object rather than a format name string
try:
format_name = format_name.name
except AttributeError:
pass
try:
del FORMATS_BY_NAME[format_name]
FORMATS = [fmt for fmt in FORMATS if fmt.name != format_name]
except KeyError:
raise KeyError("Image format '%s' is not registered" % format_name)
def get_image_formats():
search_for_image_formats()
return FORMATS
def get_image_format(name):
search_for_image_formats()
return FORMATS_BY_NAME[name]
_searched_for_image_formats = False
def search_for_image_formats():
global _searched_for_image_formats
if not _searched_for_image_formats:
for app_module in settings.INSTALLED_APPS:
try:
import_module('%s.image_formats' % app_module)
except ImportError:
continue
_searched_for_image_formats = True
# Define default image formats
register_image_format(Format('fullwidth', 'Full width', 'full-width', 'width-800'))
register_image_format(Format('left', 'Left-aligned', 'left', 'width-500'))
register_image_format(Format('right', 'Right-aligned', 'right', 'width-500'))

Wyświetl plik

@ -0,0 +1,25 @@
from django import forms
from django.forms.models import modelform_factory
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.formats import get_image_formats
def get_image_form():
return modelform_factory(get_image_model(),
# set the 'file' widget to a FileInput rather than the default ClearableFileInput
# so that when editing, we don't get the 'currently: ...' banner which is
# a bit pointless here
widgets = {'file': forms.FileInput()})
class ImageInsertionForm(forms.Form):
"""
Form for selecting parameters of the image (e.g. format) prior to insertion
into a rich text area
"""
format = forms.ChoiceField(
choices=[(format.name, format.label) for format in get_image_formats()],
widget=forms.RadioSelect
)
alt_text = forms.CharField()

Wyświetl plik

@ -0,0 +1,123 @@
from PIL import Image
def resize(image, size):
"""
resize image to the requested size, using highest quality settings
(antialiasing enabled, converting to true colour if required)
"""
if image.mode in ['1', 'P']:
image = image.convert('RGB')
return image.resize(size, Image.ANTIALIAS)
def crop_to_centre(image, size):
(original_width, original_height) = image.size
(target_width, target_height) = size
# final dimensions should not exceed original dimensions
final_width = min(original_width, target_width)
final_height = min(original_height, target_height)
if final_width == original_width and final_height == original_height:
return image
left = (original_width - final_width) / 2
top = (original_height - final_height) / 2
return image.crop(
(left, top, left + final_width, top + final_height)
)
def resize_to_max(image, size):
"""
Resize image down to fit within the given dimensions, preserving aspect ratio.
Will leave image unchanged if it's already within those dimensions.
"""
(original_width, original_height) = image.size
(target_width, target_height) = size
if original_width <= target_width and original_height <= target_height:
return image
# scale factor if we were to downsize the image to fit the target width
horz_scale = float(target_width) / original_width
# scale factor if we were to downsize the image to fit the target height
vert_scale = float(target_height) / original_height
# choose whichever of these gives a smaller image
if horz_scale < vert_scale:
final_size = (target_width, int(original_height * horz_scale))
else:
final_size = (int(original_width * vert_scale), target_height)
return resize(image, final_size)
def resize_to_min(image, size):
"""
Resize image down to cover the given dimensions, preserving aspect ratio.
Will leave image unchanged if width or height is already within those limits.
"""
(original_width, original_height) = image.size
(target_width, target_height) = size
if original_width <= target_width or original_height <= target_height:
return image
# scale factor if we were to downsize the image to fit the target width
horz_scale = float(target_width) / original_width
# scale factor if we were to downsize the image to fit the target height
vert_scale = float(target_height) / original_height
# choose whichever of these gives a larger image
if horz_scale > vert_scale:
final_size = (target_width, int(original_height * horz_scale))
else:
final_size = (int(original_width * vert_scale), target_height)
return resize(image, final_size)
def resize_to_width(image, target_width):
"""
Resize image down to the given width, preserving aspect ratio.
Will leave image unchanged if it's already within that width.
"""
(original_width, original_height) = image.size
if original_width <= target_width:
return image
scale = float(target_width) / original_width
final_size = (target_width, int(original_height * scale))
return resize(image, final_size)
def resize_to_height(image, target_height):
"""
Resize image down to the given height, preserving aspect ratio.
Will leave image unchanged if it's already within that height.
"""
(original_width, original_height) = image.size
if original_height <= target_height:
return image
scale = float(target_height) / original_height
final_size = (int(original_width * scale), target_height)
return resize(image, final_size)
def resize_to_fill(image, size):
"""
Resize down and crop image to fill the given dimensions. Most suitable for thumbnails.
(The final image will match the requested size, unless one or the other dimension is
already smaller than the target size)
"""
resized_image = resize_to_min(image, size)
return crop_to_centre(resized_image, size)

Wyświetl plik

@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
depends_on = (
("wagtailcore", "0002_initial_data"),
)
def forwards(self, orm):
# Adding model 'Image'
db.create_table(u'wagtailimages_image', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
('file', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
('width', self.gf('django.db.models.fields.IntegerField')()),
('height', self.gf('django.db.models.fields.IntegerField')()),
('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('uploaded_by_user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
))
db.send_create_signal(u'wagtailimages', ['Image'])
# Adding model 'Filter'
db.create_table(u'wagtailimages_filter', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('spec', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
))
db.send_create_signal(u'wagtailimages', ['Filter'])
# Adding model 'Rendition'
db.create_table(u'wagtailimages_rendition', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('filter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['wagtailimages.Filter'])),
('file', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
('width', self.gf('django.db.models.fields.IntegerField')()),
('height', self.gf('django.db.models.fields.IntegerField')()),
('image', self.gf('django.db.models.fields.related.ForeignKey')(related_name='renditions', to=orm['wagtailimages.Image'])),
))
db.send_create_signal(u'wagtailimages', ['Rendition'])
# Adding unique constraint on 'Rendition', fields ['image', 'filter']
db.create_unique(u'wagtailimages_rendition', ['image_id', 'filter_id'])
def backwards(self, orm):
# Removing unique constraint on 'Rendition', fields ['image', 'filter']
db.delete_unique(u'wagtailimages_rendition', ['image_id', 'filter_id'])
# Deleting model 'Image'
db.delete_table(u'wagtailimages_image')
# Deleting model 'Filter'
db.delete_table(u'wagtailimages_filter')
# Deleting model 'Rendition'
db.delete_table(u'wagtailimages_rendition')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'wagtailimages.filter': {
'Meta': {'object_name': 'Filter'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'spec': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
u'wagtailimages.image': {
'Meta': {'object_name': 'Image'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'height': ('django.db.models.fields.IntegerField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'uploaded_by_user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'width': ('django.db.models.fields.IntegerField', [], {})
},
u'wagtailimages.rendition': {
'Meta': {'unique_together': "(('image', 'filter'),)", 'object_name': 'Rendition'},
'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': u"orm['wagtailimages.Filter']"}),
'height': ('django.db.models.fields.IntegerField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'renditions'", 'to': u"orm['wagtailimages.Image']"}),
'width': ('django.db.models.fields.IntegerField', [], {})
}
}
complete_apps = ['wagtailimages']

Wyświetl plik

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
image_content_type, created = orm['contenttypes.ContentType'].objects.get_or_create(
model='image', app_label='wagtailimages', defaults={'name': 'image'})
add_permission, created = orm['auth.permission'].objects.get_or_create(
content_type=image_content_type, codename='add_image', defaults=dict(name=u'Can add image'))
change_permission, created = orm['auth.permission'].objects.get_or_create(
content_type=image_content_type, codename='change_image', defaults=dict(name=u'Can change image'))
delete_permission, created = orm['auth.permission'].objects.get_or_create(
content_type=image_content_type, codename='delete_image', defaults=dict(name=u'Can delete image'))
editors_group = orm['auth.group'].objects.get(name='Editors')
editors_group.permissions.add(add_permission, change_permission, delete_permission)
moderators_group = orm['auth.group'].objects.get(name='Moderators')
moderators_group.permissions.add(add_permission, change_permission, delete_permission)
def backwards(self, orm):
"Write your backwards methods here."
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'wagtailimages.filter': {
'Meta': {'object_name': 'Filter'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'spec': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
u'wagtailimages.image': {
'Meta': {'object_name': 'Image'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'height': ('django.db.models.fields.IntegerField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'uploaded_by_user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'width': ('django.db.models.fields.IntegerField', [], {})
},
u'wagtailimages.rendition': {
'Meta': {'unique_together': "(('image', 'filter'),)", 'object_name': 'Rendition'},
'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': u"orm['wagtailimages.Filter']"}),
'height': ('django.db.models.fields.IntegerField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'renditions'", 'to': u"orm['wagtailimages.Image']"}),
'width': ('django.db.models.fields.IntegerField', [], {})
}
}
complete_apps = ['wagtailimages']
symmetrical = True

Wyświetl plik

@ -0,0 +1,241 @@
from django.core.files import File
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, ValidationError
from django.db import models
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
from django.utils.safestring import mark_safe
from django.utils.html import escape
import StringIO
import PIL.Image
import os.path
from taggit.managers import TaggableManager
from wagtail.wagtailadmin.taggable import TagSearchable
from wagtail.wagtailimages import image_ops
class AbstractImage(models.Model, TagSearchable):
title = models.CharField(max_length=255)
def get_upload_to(self, filename):
folder_name = 'original_images'
filename = self.file.field.storage.get_valid_name(filename)
# replace non-ascii characters in filename with _ , to sidestep issues with filesystem encoding
filename = "".join((i if ord(i)<128 else '_') for i in filename)
while len(os.path.join(folder_name, filename)) >= 95:
prefix, dot, extension = filename.rpartition('.')
filename = prefix[:-1] + dot + extension
return os.path.join(folder_name, filename)
def file_extension_validator(ffile):
extension = ffile.name.split(".")[-1].lower()
if extension not in ["gif", "jpg", "jpeg", "png"]:
raise ValidationError("Not a valid image format. Please use a gif, jpeg or png file instead.")
file = models.ImageField(upload_to=get_upload_to, width_field='width', height_field='height', validators=[file_extension_validator])
width = models.IntegerField(editable=False)
height = models.IntegerField(editable=False)
created_at = models.DateTimeField(auto_now_add=True)
uploaded_by_user = models.ForeignKey('auth.User', null=True, blank=True, editable=False)
tags = TaggableManager(help_text=None, blank=True)
indexed_fields = {
'uploaded_by_user_id': {
'type': 'integer',
'store': 'yes',
'indexed': 'no',
'boost': 0,
},
}
def __unicode__(self):
return self.title
def get_rendition(self, filter):
if not hasattr(filter, 'process_image'):
# assume we've been passed a filter spec string, rather than a Filter object
# TODO: keep an in-memory cache of filters, to avoid a db lookup
filter, created = Filter.objects.get_or_create(spec=filter)
try:
rendition = self.renditions.get(filter=filter)
except ObjectDoesNotExist:
file_field = self.file
generated_image_file = filter.process_image(file_field.file)
rendition, created = self.renditions.get_or_create(
filter=filter, defaults={'file': generated_image_file})
return rendition
def is_portrait(self):
return (self.width < self.height)
def is_landscape(self):
return (self.height < self.width)
@property
def filename(self):
return os.path.basename(self.file.name)
@property
def default_alt_text(self):
# by default the alt text field (used in rich text insertion) is populated
# from the title. Subclasses might provide a separate alt field, and
# override this
return self.title
def is_editable_by_user(self, user):
if user.has_perm('wagtailimages.change_image'):
# user has global permission to change images
return True
elif user.has_perm('wagtailimages.add_image') and self.uploaded_by_user == user:
# user has image add permission, which also implicitly provides permission to edit their own images
return True
else:
return False
class Meta:
abstract=True
class Image(AbstractImage):
pass
class Meta:
permissions = (
('add_image', "Can add image"),
('change_image', "Can change image"),
('delete_image', "Can delete image"),
)
# Receive the pre_delete signal and delete the file associated with the model instance.
@receiver(pre_delete, sender=Image)
def image_delete(sender, instance, **kwargs):
# Pass false so FileField doesn't save the model.
instance.file.delete(False)
def get_image_model():
from django.conf import settings
from django.db.models import get_model
try:
app_label, model_name = settings.WAGTAILIMAGES_IMAGE_MODEL.split('.')
except AttributeError:
return Image
except ValueError:
raise ImproperlyConfigured("WAGTAILIMAGES_IMAGE_MODEL must be of the form 'app_label.model_name'")
image_model = get_model(app_label, model_name)
if image_model is None:
raise ImproperlyConfigured("WAGTAILIMAGES_IMAGE_MODEL refers to model '%s' that has not been installed" % settings.WAGTAILIMAGES_IMAGE_MODEL)
return image_model
class Filter(models.Model):
"""
Represents an operation that can be applied to an Image to produce a rendition
appropriate for final display on the website. Usually this would be a resize operation,
but could potentially involve colour processing, etc.
"""
spec = models.CharField(max_length=255, db_index=True)
OPERATION_NAMES = {
'max': image_ops.resize_to_max,
'min': image_ops.resize_to_min,
'width': image_ops.resize_to_width,
'height': image_ops.resize_to_height,
'fill': image_ops.resize_to_fill,
}
def __init__(self, *args, **kwargs):
super(Filter, self).__init__(*args, **kwargs)
self.method = None # will be populated when needed, by parsing the spec string
def _parse_spec_string(self):
# parse the spec string, which is formatted as (method)-(arg),
# and save the results to self.method and self.method_arg
try:
(method_name, method_arg_string) = self.spec.split('-')
self.method = Filter.OPERATION_NAMES[method_name]
if method_name in ('max', 'min', 'fill'):
# method_arg_string is in the form 640x480
(width, height) = [int(i) for i in method_arg_string.split('x')]
self.method_arg = (width, height)
else:
# method_arg_string is a single number
self.method_arg = int(method_arg_string)
except (ValueError, KeyError):
raise ValueError("Invalid image filter spec: %r" % self.spec)
def process_image(self, input_file):
"""
Given an input image file as a django.core.files.File object,
generate an output image with this filter applied, returning it
as another django.core.files.File object
"""
if not self.method:
self._parse_spec_string()
input_file.open()
image = PIL.Image.open(input_file)
file_format = image.format
# perform the resize operation
image = self.method(image, self.method_arg)
output = StringIO.StringIO()
image.save(output, file_format)
# generate new filename derived from old one, inserting the filter spec string before the extension
input_filename_parts = os.path.basename(input_file.name).split('.')
filename_without_extension = '.'.join(input_filename_parts[:-1])
filename_without_extension = filename_without_extension[:60] # trim filename base so that we're well under 100 chars
output_filename_parts = [filename_without_extension, self.spec] + input_filename_parts[-1:]
output_filename = '.'.join(output_filename_parts)
output_file = File(output, name=output_filename)
input_file.close()
return output_file
class AbstractRendition(models.Model):
filter = models.ForeignKey('Filter', related_name='+')
file = models.ImageField(upload_to='images', width_field='width', height_field='height')
width = models.IntegerField(editable=False)
height = models.IntegerField(editable=False)
@property
def url(self):
return self.file.url
def img_tag(self):
return mark_safe(
'<img src="%s" width="%d" height="%d" alt="%s">' % (escape(self.url), self.width, self.height, escape(self.image.title))
)
class Meta:
abstract=True
class Rendition(AbstractRendition):
image = models.ForeignKey('Image', related_name='renditions')
class Meta:
unique_together = (
('image', 'filter'),
)
# Receive the pre_delete signal and delete the file associated with the model instance.
@receiver(pre_delete, sender=Rendition)
def rendition_delete(sender, instance, **kwargs):
# Pass false so FileField doesn't save the model.
instance.file.delete(False)

Wyświetl plik

@ -0,0 +1,39 @@
# plugin for hallo.js to allow inserting images from the Verdant image library
(($) ->
$.widget "IKS.halloverdantimage",
options:
uuid: ''
editable: null
populateToolbar: (toolbar) ->
widget = this
# Create an element for holding the button
button = $('<span></span>')
button.hallobutton
uuid: @options.uuid
editable: @options.editable
label: 'Images'
icon: 'icon-picture'
command: null
# Append the button to toolbar
toolbar.append button
button.on "click", (event) ->
lastSelection = widget.options.editable.getSelection()
insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last()
ModalWorkflow
url: '/admin/images/chooser/?select_format=true' # TODO: don't hard-code this, as it may be changed in urls.py
responses:
imageChosen: (imageData) ->
elem = $(imageData.html).get(0)
lastSelection.insertNode(elem)
if elem.getAttribute('contenteditable') == 'false'
insertRichTextDeleteControl(elem)
widget.options.editable.element.trigger('change')
)(jQuery)

Wyświetl plik

@ -0,0 +1,28 @@
function createImageChooser(id) {
var chooserElement = $('#' + id + '-chooser');
var previewImage = chooserElement.find('.preview-image img');
var input = $('#' + id);
$('.action-choose', chooserElement).click(function() {
ModalWorkflow({
'url': '/admin/images/chooser/', /* TODO: don't hard-code this, as it may be changed in urls.py */
'responses': {
'imageChosen': function(imageData) {
input.val(imageData.id);
previewImage.attr({
'src': imageData.preview.url,
'width': imageData.preview.width,
'height': imageData.preview.height,
'alt': imageData.title
});
chooserElement.removeClass('blank');
}
}
});
});
$('.action-clear', chooserElement).click(function() {
input.val('');
chooserElement.addClass('blank');
});
}

Wyświetl plik

@ -0,0 +1,49 @@
{% load image_tags ellipsistrim%}
<header class="merged tab-merged">
<h1>Choose an image</h1>
</header>
{% if uploadform %}
<ul class="tab-nav merged">
<li><a href="#search" class="active">Search</a></li>
<li><a href="#upload">Upload</a></li>
</ul>
{% endif %}
<div class="tab-content">
<section id="search" class="active nice-padding">
<form class="image-search search-bar" action="{% url 'wagtailimages_chooser' %}{% if will_select_format %}?select_format=true{% endif %}" method="GET" autocomplete="off">
<ul class="fields">
{% for field in searchform %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
<li class="submit"><input type="submit" value="Search" /></li>
{% if popular_tags %}
<li class="taglist">
<h3>Popular tags</h3>
{% for tag in popular_tags %}
<a class="suggested-tag tag" href="{% url 'wagtailimages_search_image' %}?q={{ tag.name|urlencode }}">{{ tag.name }}</a>
{% endfor %}
</li>
{% endif %}
</ul>
</form>
<div id="image-results">
{% include "wagtailimages/chooser/results.html" %}
</div>
</section>
{% if uploadform %}
<section id="upload" class="nice-padding">
<form class="image-upload" action="{% url 'wagtailimages_chooser_upload' %}{% if will_select_format %}?select_format=true{% endif %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in uploadform %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
<li><input type="submit" value="Upload" /></li>
</ul>
</form>
</section>
{% endif %}
</div>

Wyświetl plik

@ -0,0 +1,85 @@
function(modal) {
function ajaxifyLinks (context) {
$('.listing a', context).click(function() {
modal.loadUrl(this.href);
return false;
});
$('.pagination a', context).click(function() {
var page = this.getAttribute("data-page");
setPage(page);
return false;
});
}
var searchUrl = $('form.image-search', modal.body).attr('action');
function search() {
$.ajax({
url: searchUrl,
data: {q: $('#id_q').val()},
success: function(data, status) {
$('#image-results').html(data);
ajaxifyLinks($('#image-results'));
}
});
return false;
}
function setPage(page) {
if($('#id_q').val().length){
dataObj = {q: $('#id_q').val(), p: page};
}else{
dataObj = {p: page};
}
$.ajax({
url: searchUrl,
data: dataObj,
success: function(data, status) {
$('#image-results').html(data);
ajaxifyLinks($('#image-results'));
}
});
return false;
}
ajaxifyLinks(modal.body);
$('form.image-upload', modal.body).submit(function() {
var formdata = new FormData(this);
$.ajax({
url: this.action,
data: formdata,
processData: false,
contentType: false,
type: 'POST',
dataType: 'text',
success: function(response){
modal.loadResponseText(response);
}
});
return false;
});
$('form.image-search', modal.body).submit(search);
$('#id_q').on('input', function() {
clearTimeout($.data(this, 'timer'));
var wait = setTimeout(search, 200);
$(this).data('timer', wait);
});
$('a.suggested-tag').click(function() {
$('#id_q').val($(this).text());
search();
return false;
});
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
/* Add tag entry interface (with autocompletion) to the tag field of the image upload form */
$('#id_tags', modal.body).tagit({
autocomplete: {source: "{{ autocomplete_url|addslashes }}"}
});
}

Wyświetl plik

@ -0,0 +1,4 @@
function(modal) {
modal.respond('imageChosen', {{ image_json|safe }});
modal.close();
}

Wyświetl plik

@ -0,0 +1,22 @@
{% load image_tags ellipsistrim %}
{% if images %}
{% if is_searching %}
<h2>{{ images.paginator.count }} match{{ images.paginator.count|pluralize:"es" }}</h2>
{% else %}
<h2>Latest images</h2>
{% endif %}
<ul class="listing horiz images chooser">
{% for image in images %}
<li>
<a class="image-choice" href="{% if will_select_format %}{% url 'wagtailimages_chooser_select_format' image.id %}{% else %}{% url 'wagtailimages_image_chosen' image.id %}{% endif %}">
<div class="image">{% image image max-165x165 %}</div>
<h3>{{ image.title|ellipsistrim:60 }}</h3>
</a>
</li>
{% endfor %}
</ul>
{% include "wagtailadmin/shared/pagination_nav.html" with items=images is_ajax=1 %}
{% endif %}

Wyświetl plik

@ -0,0 +1,22 @@
{% load image_tags %}
<header>
<h1>Choose a format</h1>
</header>
<div class="row row-flush nice-padding">
<div class="col6">
{% image image max-800x600 %}
</div>
<div class="col6">
<form action="{% url 'wagtailimages_chooser_select_format' image.id %}" method="POST">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
<li><input type="submit" value="Insert image" /></li>
</ul>
</form>
</div>
</div>

Wyświetl plik

@ -0,0 +1,11 @@
function(modal) {
$('form', modal.body).submit(function() {
var formdata = new FormData(this);
$.post(this.action, $(this).serialize(), function(response){
modal.loadResponseText(response);
}, 'text');
return false;
});
}

Wyświetl plik

@ -0,0 +1,18 @@
{% extends "wagtailadmin/edit_handlers/chooser_panel.html" %}
{% load image_tags %}
{% block chooser_class %}image-chooser{% endblock %}
{% block chosen_state_view %}
<div class="preview-image">
{% if image %}
{% image image max-130x130 %}
{% else %}
<img>
{% endif %}
</div>
{% endblock %}
{% block clear_button_label %}Clear image{% endblock %}
{% block choose_another_button_label %}Choose another image{% endblock %}
{% block choose_button_label %}Choose an image{% endblock %}

Wyświetl plik

@ -0,0 +1,8 @@
{% extends "wagtailadmin/shared/field_as_li.html" %}
{% block form_field %}
<a href="{{MEDIA_URL}}images/{{ image.file }}" class="icon icon-image">{{ image.filename }}</a><br /><br />
Change image:
{{ field }}
{% endblock %}

Wyświetl plik

@ -0,0 +1,29 @@
{% extends "wagtailadmin/base.html" %}
{% load image_tags %}
{% block titletag %}Add an image{% endblock %}
{% block bodyclass %}menu-images{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/shared/tag_field_css.html" %}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/shared/tag_field_js.html" %}
{% endblock %}
{% block content %}
<header>
<h1>Add image</h1>
</header>
<div class="nice-padding">
<form action="{% url 'wagtailimages_add_image' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
<li><input type="submit" value="Save" /></li>
</ul>
</form>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,22 @@
{% extends "wagtailadmin/base.html" %}
{% load image_tags %}
{% block titletag %}Delete image{% endblock %}
{% block bodyclass %}menu-images{% endblock %}
{% block content %}
<header>
<h1>Delete image</h1>
</header>
<div class="row row-flush nice-padding">
<div class="col6">
{% image image max-800x600 %}
</div>
<div class="col6">
<p>Are you sure you want to delete this image?</p>
<form action="{% url 'wagtailimages_delete_image' image.id %}" method="POST">
{% csrf_token %}
<input type="submit" value="Yes, delete" class="serious" />
</form>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,41 @@
{% extends "wagtailadmin/base.html" %}
{% load image_tags %}
{% block titletag %}Editing image {{ image.title }}{% endblock %}
{% block bodyclass %}menu-images{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/shared/tag_field_css.html" %}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/shared/tag_field_js.html" %}
{% endblock %}
{% block content %}
<header>
<h1>Editing <span>{{ image.title }}</span></h1>
</header>
<div class="row row-flush nice-padding">
<div class="col7">
<form action="{% url 'wagtailimages_edit_image' image.id %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<ul class="fields">
{% for field in form %}
{% if field.name == 'file' %}
{% include "wagtailimages/images/_file_field.html" %}
{% else %}
{% include "wagtailadmin/shared/field_as_li.html" %}
{% endif %}
{% endfor %}
<li><input type="submit" value="Save" /><a href="{% url 'wagtailimages_delete_image' image.id %}" class="button button-secondary no">Delete image</a></li>
</ul>
</form>
</div>
<div class="col5">
{% image image max-800x600 %}
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,74 @@
{% extends "wagtailadmin/base.html" %}
{% load image_tags ellipsistrim %}
{% block titletag %}Images{% endblock %}
{% block bodyclass %}menu-images{% endblock %}
{% block extra_js %}
<script>
$('#id_q').on('input', function() {
clearTimeout($.data(this, 'timer'));
var wait = setTimeout(search, 200);
$(this).data('timer', wait);
});
$('a.suggested-tag').click(function() {
$('#id_q').val($(this).text());
search();
return false;
})
// These variables keep track of ajax requests to prevent an older request from replacing a new one
var search_current_index = 0;
var search_next_index = 0;
function search () {
search_next_index++;
var index = search_next_index;
$.ajax({
url: "{% url 'wagtailimages_index' %}",
data: {q: $('#id_q').val()},
success: function(data, status) {
if (index > search_current_index) {
search_current_index = index;
$('#image-results').html(data);
}
},
});
};
</script>
{% endblock %}
{% block content %}
<header>
<div class="row row-flush">
<div class="left col9">
<h1>Images</h1>
</div>
<div class="right col3">
<a href="{% url 'wagtailimages_add_image' %}" class="button icon icon-plus-inverse">Add an image</a>
</div>
</div>
</header>
<form class="search-bar full-width" action="{% url 'wagtailimages_index' %}" method="get">
<ul class="fields">
{% for field in form %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
<li class="submit"><input type="submit" value="Search" /></li>
{% if popular_tags %}
<li class="taglist">
<h3>Popular tags</h3>
{% for tag in popular_tags %}
<a class="suggested-tag tag" href="{% url 'wagtailimages_search_image' %}?q={{ tag.name|urlencode }}">{{ tag.name }}</a>
{% endfor %}
</li>
{% endif %}
</ul>
</form>
<div class="nice-padding">
<div id="image-results">
{% include "wagtailimages/images/results.html" %}
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,29 @@
{% load image_tags ellipsistrim %}
{% if images %}
{% if is_searching %}
<h2>{{ images.paginator.count }} match{{ images.paginator.count|pluralize:"es" }}</h2>
{% else %}
<h2>Latest images</h2>
{% endif %}
<ul class="listing horiz images">
{% for image in images %}
<li>
<a class="image-choice" href="{% url 'wagtailimages_edit_image' image.id %}">
<div class="image">{% image image max-165x165 %}</div>
<h3>{{ image.title|ellipsistrim:60 }}</h3>
</a>
</li>
{% endfor %}
</ul>
{% include "wagtailadmin/shared/pagination_nav.html" with items=images is_searching=is_searching search_query=search_query linkurl="wagtailimages_index" %}
{% else %}
{% if is_searching %}
<p>Sorry, no images match "<em>{{ search_query }}</em>"</p>
{% else %}
<p>You've not uploaded any images. Why not <a href="{% url 'wagtailimages_add_image' %}">add one now</a>?</p>
{% endif %}
{% endif %}

Wyświetl plik

@ -0,0 +1,51 @@
from django import template
from wagtail.wagtailimages.models import Filter
register = template.Library()
@register.tag(name="image")
def image(parser, token):
args = token.split_contents()
if len(args) == 3:
# token is of the form {% image self.photo max-320x200 %}
tag_name, image_var, filter_spec = args
return ImageNode(image_var, filter_spec)
elif len(args) == 5:
# token is of the form {% image self.photo max-320x200 as img %}
tag_name, image_var, filter_spec, as_token, out_var = args
if as_token != 'as':
raise template.TemplateSyntaxError("'image' tag should be of the form {%% image self.photo max-320x200 %%} or {%% image self.photo max-320x200 as img %%}")
return ImageNode(image_var, filter_spec, out_var)
else:
raise template.TemplateSyntaxError("'image' tag should be of the form {%% image self.photo max-320x200 %%} or {%% image self.photo max-320x200 as img %%}")
class ImageNode(template.Node):
def __init__(self, image_var_name, filter_spec, output_var_name=None):
self.image_var = template.Variable(image_var_name)
self.filter, created = Filter.objects.get_or_create(spec=filter_spec)
self.output_var_name = output_var_name
def render(self, context):
try:
image = self.image_var.resolve(context)
except template.VariableDoesNotExist:
return ''
if not image:
return ''
rendition = image.get_rendition(self.filter)
if self.output_var_name:
# return the rendition object in the given variable
context[self.output_var_name] = rendition
return ''
else:
# render the rendition's image tag now
return rendition.img_tag()

Wyświetl plik

@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

Wyświetl plik

@ -0,0 +1,15 @@
from django.conf.urls import patterns, url
urlpatterns = patterns('wagtail.wagtailimages.views',
url(r'^$', 'images.index', name='wagtailimages_index'),
url(r'^(\d+)/$', 'images.edit', name='wagtailimages_edit_image'),
url(r'^(\d+)/delete/$', 'images.delete', name='wagtailimages_delete_image'),
url(r'^add/$', 'images.add', name='wagtailimages_add_image'),
url(r'^search/$', 'images.search', name='wagtailimages_search_image'),
url(r'^chooser/$', 'chooser.chooser', name='wagtailimages_chooser'),
url(r'^chooser/(\d+)/$', 'chooser.image_chosen', name='wagtailimages_image_chosen'),
url(r'^chooser/upload/$', 'chooser.chooser_upload', name='wagtailimages_chooser_upload'),
url(r'^chooser/(\d+)/select_format/$', 'chooser.chooser_select_format', name='wagtailimages_chooser_select_format'),
)

Wyświetl plik

@ -0,0 +1,177 @@
from django.shortcuts import get_object_or_404, render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.auth.decorators import login_required, permission_required
import json
from wagtail.wagtailadmin.modal_workflow import render_modal_workflow
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.forms import get_image_form, ImageInsertionForm
from wagtail.wagtailadmin.forms import SearchForm
from wagtail.wagtailimages.formats import get_image_format
def get_image_json(image):
"""
helper function: given an image, return the json to pass back to the
image chooser panel
"""
preview_image = image.get_rendition('max-130x100')
return json.dumps({
'id': image.id,
'title': image.title,
'preview': {
'url': preview_image.url,
'width': preview_image.width,
'height': preview_image.height,
}
})
@login_required
def chooser(request):
Image = get_image_model()
if request.user.has_perm('wagtailimages.add_image'):
ImageForm = get_image_form()
uploadform = ImageForm()
else:
uploadform = None
q = None
if 'q' in request.GET or 'p' in request.GET:
searchform = SearchForm(request.GET)
if searchform.is_valid():
q = searchform.cleaned_data['q']
# page number
p = request.GET.get("p", 1)
images = Image.search(q, results_per_page=10, page=p)
is_searching = True
else:
images = Image.objects.order_by('-created_at')
p = request.GET.get("p", 1)
paginator = Paginator(images, 10)
try:
images = paginator.page(p)
except PageNotAnInteger:
images = paginator.page(1)
except EmptyPage:
images = paginator.page(paginator.num_pages)
is_searching = False
return render(request, "wagtailimages/chooser/results.html", {
'images': images,
'is_searching': is_searching,
'will_select_format': request.GET.get('select_format')
})
else:
searchform = SearchForm()
images = Image.objects.order_by('-created_at')
p = request.GET.get("p", 1)
paginator = Paginator(images, 10)
try:
images = paginator.page(p)
except PageNotAnInteger:
images = paginator.page(1)
except EmptyPage:
images = paginator.page(paginator.num_pages)
return render_modal_workflow(request, 'wagtailimages/chooser/chooser.html', 'wagtailimages/chooser/chooser.js',{
'images': images,
'uploadform': uploadform,
'searchform': searchform,
'is_searching': False,
'will_select_format': request.GET.get('select_format'),
'popular_tags': Image.popular_tags(),
})
@login_required
def image_chosen(request, image_id):
image = get_object_or_404(get_image_model(), id=image_id)
return render_modal_workflow(
request, None, 'wagtailimages/chooser/image_chosen.js',
{'image_json': get_image_json(image)}
)
@permission_required('wagtailimages.add_image')
def chooser_upload(request):
Image = get_image_model()
ImageForm = get_image_form()
if request.POST:
image = Image(uploaded_by_user=request.user)
form = ImageForm(request.POST, request.FILES, instance=image)
if form.is_valid():
form.save()
if request.GET.get('select_format'):
form = ImageInsertionForm(initial={'alt_text': image.default_alt_text})
return render_modal_workflow(
request, 'wagtailimages/chooser/select_format.html', 'wagtailimages/chooser/select_format.js',
{'image': image, 'form': form}
)
else:
# not specifying a format; return the image details now
return render_modal_workflow(
request, None, 'wagtailimages/chooser/image_chosen.js',
{'image_json': get_image_json(image)}
)
else:
form = ImageForm()
images = Image.objects.order_by('title')
return render_modal_workflow(
request, 'wagtailimages/chooser/chooser.html', 'wagtailimages/chooser/chooser.js',
{'images': images, 'uploadform': form}
)
@login_required
def chooser_select_format(request, image_id):
image = get_object_or_404(get_image_model(), id=image_id)
if request.POST:
form = ImageInsertionForm(request.POST, initial={'alt_text': image.default_alt_text})
if form.is_valid():
format = get_image_format(form.cleaned_data['format'])
preview_image = image.get_rendition(format.filter_spec)
image_json = json.dumps({
'id': image.id,
'title': image.title,
'format': format.name,
'alt': form.cleaned_data['alt_text'],
'class': format.classnames,
'preview': {
'url': preview_image.url,
'width': preview_image.width,
'height': preview_image.height,
},
'html': format.image_to_editor_html(image, form.cleaned_data['alt_text']),
})
return render_modal_workflow(
request, None, 'wagtailimages/chooser/image_chosen.js',
{'image_json': image_json}
)
else:
form = ImageInsertionForm(initial={'alt_text': image.default_alt_text})
return render_modal_workflow(
request, 'wagtailimages/chooser/select_format.html', 'wagtailimages/chooser/select_format.js',
{'image': image, 'form': form}
)

Wyświetl plik

@ -0,0 +1,173 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib.auth.decorators import permission_required, login_required
from django.core.exceptions import PermissionDenied
from wagtail.wagtailimages.models import get_image_model
from wagtail.wagtailimages.forms import get_image_form
from wagtail.wagtailadmin.forms import SearchForm
@permission_required('wagtailimages.add_image')
def index(request):
Image = get_image_model()
q = None
p = request.GET.get("p", 1)
is_searching = False
if 'q' in request.GET:
form = SearchForm(request.GET)
if form.is_valid():
q = form.cleaned_data['q']
is_searching = True
if not request.user.has_perm('wagtailimages.change_image'):
# restrict to the user's own images
images = Image.search(q, results_per_page=20, page=p, filters={'uploaded_by_user_id': request.user.id})
else:
images = Image.search(q, results_per_page=20, page=p)
else:
images = Image.objects.order_by('-created_at')
if not request.user.has_perm('wagtailimages.change_image'):
# restrict to the user's own images
images = images.filter(uploaded_by_user=request.user)
else:
images = Image.objects.order_by('-created_at')
if not request.user.has_perm('wagtailimages.change_image'):
# restrict to the user's own images
images = images.filter(uploaded_by_user=request.user)
form = SearchForm()
if not is_searching:
paginator = Paginator(images, 20)
try:
images = paginator.page(p)
except PageNotAnInteger:
images = paginator.page(1)
except EmptyPage:
images = paginator.page(paginator.num_pages)
if request.is_ajax():
return render(request, "wagtailimages/images/results.html", {
'images': images,
'is_searching': is_searching,
'search_query': q,
})
else:
return render(request, "wagtailimages/images/index.html", {
'form': form,
'images': images,
'is_searching': is_searching,
'popular_tags': Image.popular_tags(),
'search_query': q,
})
@login_required # more specific permission tests are applied within the view
def edit(request, image_id):
Image = get_image_model()
ImageForm = get_image_form()
image = get_object_or_404(Image, id=image_id)
if not image.is_editable_by_user(request.user):
raise PermissionDenied
if request.POST:
original_file = image.file
form = ImageForm(request.POST, request.FILES, instance=image)
if form.is_valid():
if 'file' in form.changed_data:
# if providing a new image file, delete the old one and all renditions.
# NB Doing this via original_file.delete() clears the file field,
# which definitely isn't what we want...
original_file.storage.delete(original_file.name)
image.renditions.all().delete()
form.save()
messages.success(request, "Image '%s' updated." % image.title)
return redirect('wagtailimages_index')
else:
messages.error(request, "The image could not be saved due to errors.")
else:
form = ImageForm(instance=image)
return render(request, "wagtailimages/images/edit.html", {
'image': image,
'form': form,
})
@login_required # more specific permission tests are applied within the view
def delete(request, image_id):
image = get_object_or_404(get_image_model(), id=image_id)
if not image.is_editable_by_user(request.user):
raise PermissionDenied
if request.POST:
image.delete()
messages.success(request, "Image '%s' deleted." % image.title)
return redirect('wagtailimages_index')
return render(request, "wagtailimages/images/confirm_delete.html", {
'image': image,
})
@permission_required('wagtailimages.add_image')
def add(request):
ImageForm = get_image_form()
ImageModel = get_image_model()
if request.POST:
image = ImageModel(uploaded_by_user=request.user)
form = ImageForm(request.POST, request.FILES, instance=image)
if form.is_valid():
form.save()
messages.success(request, "Image '%s' added." % image.title)
return redirect('wagtailimages_index')
else:
messages.error(request, "The image could not be created due to errors.")
else:
form = ImageForm()
return render(request, "wagtailimages/images/add.html", {
'form': form,
})
@permission_required('wagtailimages.add_image')
def search(request):
Image = get_image_model()
images = []
q = None
is_searching = False
if 'q' in request.GET:
form = SearchForm(request.GET)
if form.is_valid():
q = form.cleaned_data['q']
# page number
p = request.GET.get("p", 1)
is_searching = True
images = Image.search(q, results_per_page=20, page=p)
else:
form = SearchForm()
if request.is_ajax():
return render(request, "wagtailimages/images/results.html", {
'images': images,
'is_searching': is_searching,
'search_query': q,
})
else:
return render(request, "wagtailimages/images/index.html", {
'form': form,
'images': images,
'is_searching': is_searching,
'popular_tags': Image.popular_tags(),
'search_query': q,
})