kopia lustrzana https://github.com/wagtail/wagtail
Move verdantimages app to django-wagtail package
rodzic
351e469b12
commit
b433d5c23a
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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,
|
||||
})
|
|
@ -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'))
|
|
@ -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()
|
|
@ -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)
|
|
@ -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']
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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');
|
||||
});
|
||||
}
|
|
@ -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>
|
|
@ -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 }}"}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
function(modal) {
|
||||
modal.respond('imageChosen', {{ image_json|safe }});
|
||||
modal.close();
|
||||
}
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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()
|
|
@ -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)
|
|
@ -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'),
|
||||
)
|
|
@ -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}
|
||||
)
|
|
@ -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,
|
||||
})
|
Ładowanie…
Reference in New Issue