Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

527 lines (426 sloc) 21.511 kb
from __future__ import division
import os
from django.utils.translation import ugettext_lazy as _
from django.contrib import admin, messages
from django import forms
from django.db import models
from django.conf import settings
from cms.plugin_pool import plugin_pool
from cms.plugin_base import CMSPluginBase
from easy_thumbnails.files import get_thumbnailer
from widgetry.tabs.admin import ModelAdminWithTabs
from widgetry import fk_lookup
from arkestra_utilities.output_libraries.plugin_widths import get_placeholder_width, calculate_container_width
from arkestra_utilities.admin_mixins import AutocompleteMixin, SupplyRequestMixin
from links import schema
from models import FilerImage, ImageSetItem, ImageSetPlugin
IMAGESET_ITEM_PADDING = getattr(settings, "IMAGESET_ITEM_PADDING", 10)
# a dictionary to show how many items per row depending on the number of items
LIGHTBOX_COLUMNS = {1:1, 2:2, 3:3, 4:4, 5:5, 6:3, 7:4, 8:4, 9:3, 10:5, 11:4, 12:4, 13:5, 14:5, 15:5, 16:4, 17:6, 18:6, 19:5, 20:5, 21:6, 22:6, 23:6, 24:6, 25:5 }
def width_of_image_container(context, plugin):
# Based on the plugin settings and placeholder width, calculate image container width
# work out native image aspect ratio
plugin.has_borders = False
# width values
# None: use native width
# <0: an absolute value
# <=100: a percentage of placeholder's width
# 1000: automatic, based on placeholder's width
placeholder_width = get_placeholder_width(context, plugin)
if plugin.width > 0 and plugin.width <= 100:
width = placeholder_width/100.0 * plugin.width
auto = False
else:
width = placeholder_width
auto = True
# calculate the width of the block the image will be in
width = calculate_container_width(context, plugin, width, auto)
# return the width of the container
print "width_of_image_container", width
return width
def width_of_image(plugin, image=None):
# no plugin width?
print "plugin width", plugin.width
if not plugin.width:
if image:
print "image", image
# use native image width
width = image.width
else:
# use container width
width = plugin.container_width
# negative numbers are absolutes
elif plugin.width < 0:
width = -plugin.width
else:
width = plugin.container_width
print "width_of_image", width
return width
def calculate_aspect_ratio(image):
return float(image.width)/image.height
def shave_if_floated(plugin, width):
# shave off 5 point if the image is floated, to make room for a margin
# see arkestra.css, span.image.left and span.image.right
if plugin.float and plugin.width > 0:
# print "-5 for float"
return width - 5
def calculate_height(given_width, given_height, given_aspect_ratio, width, aspect_ratio):
# given_width, given_height, given_aspect_ratio are values set by the plugin
# width and aspect_ratio are calculated values
# using given values gives us a chance to use given values to override calculated ones
# rules:
# if the instance has an aspect ratio, use that to calculate the height
# if the instance has no aspect ratio, but does have a height, use the height
# if the instance has no aspect ratio, we use the native aspect ratio
# has aspect ratio
print given_aspect_ratio
if given_aspect_ratio == -1:
height=width/aspect_ratio
elif given_aspect_ratio:
height = width / given_aspect_ratio
# if the instance has no width, but does have a height, use the aspect ratio to calculate it
if given_height and not given_width:
width = height * given_aspect_ratio
# has height
elif given_height:
height = given_height
# if it had no width either, we can use the native aspect ratio to calculate that
if not given_width:
width = aspect_ratio * height
# use the native aspect ratio
else:
height = width / aspect_ratio
return width, height
def slider(imageset):
imageset.template = "arkestra_image_plugin/slider.html"
width = width_of_image(imageset)
if imageset.aspect_ratio:
height = imageset.container_width / imageset.aspect_ratio
elif imageset.height:
height = imageset.height
else:
# use the aspect ratio of the widest image
heights = []
for item in imageset.items:
divider = 1.0/float(item.image.width)
height_multiplier = float(item.image.height)*divider
heights.append(height_multiplier)
heights.sort()
height_multiplier = heights[0]
height = width * height_multiplier
imageset.size = (int(width),int(height))
for item in imageset.items:
item.image_size = imageset.size
return imageset
def multiple_images(imageset, context):
# for lightboxes and multiple image sets
imageset.template = "arkestra_image_plugin/%s.html" %imageset.kind
imageset.padding = IMAGESET_ITEM_PADDING
padding_adjuster = IMAGESET_ITEM_PADDING * 2
# each item will be the same width - the user gets no say in this
# also, let's not have any nonsense about using the native widths
# - and only percentage and automatic widths are allowed
# we call width_of_image() without an image argument
each_item_width = width_of_image(imageset)
# if imageset.aspect_ratio is 0 (auto) get an average and use that
if imageset.aspect_ratio == 0 and not imageset.height:
aspect_ratio = sum([calculate_aspect_ratio(item.image) for item in imageset.items])/imageset.number_of_items
# otherwise use supplied aspect ratio
else:
aspect_ratio = imageset.aspect_ratio
# don't allow more items_per_row than there are items
if imageset.items_per_row > imageset.number_of_items:
imageset.items_per_row = imageset.number_of_items
items_per_row = imageset.items_per_row or LIGHTBOX_COLUMNS.get(imageset.number_of_items, 8)
# if we are using automatic/percentage widths, base them on the placeholder
if imageset.width > 0:
each_item_width = each_item_width / items_per_row
# fancybox icons and multiple images with links have padding, so:
if imageset.kind == "lightbox" or imageset.items_have_links:
each_item_width = each_item_width - padding_adjuster
else:
# otherwise give them a margin
if imageset.width > 0:
each_item_width = each_item_width - (items_per_row-1) * padding_adjuster/items_per_row
# if imageset.aspect_ratio is not -1 (forced native) calculate height/width for all
if not imageset.aspect_ratio == -1:
each_item_width, each_item_height = calculate_height(imageset.width, imageset.height, imageset.aspect_ratio, each_item_width, aspect_ratio)
# set up each item
# for counter, item in enumerate(imageset.items, start = 1): # enable this when we no longer need to support Python 2.5
for counter, item in enumerate(imageset.items):
counter=counter+1
# mark end-of-row items in case the CSS needs it
# only when we are using percentage widths
if imageset.width > 0 and not counter%items_per_row:
item.lastinrow = True
if imageset.aspect_ratio == -1:
aspect_ratio = calculate_aspect_ratio(item.image)
item.width,item.height = calculate_height(imageset.width, imageset.height, imageset.aspect_ratio, each_item_width, aspect_ratio)
item.width,item.height = int(item.width), int(item.height)
print item.width,item.height
else:
item.width,item.height = int(each_item_width), int(each_item_height)
print item.width,item.height
item.image_size = u'%sx%s' %(item.width, item.height)
item.caption_width=item.width
# # if forced_native, set each individually
# else:
# for counter, item in enumerate(imageset.items):
# counter=counter+1
# # mark end-of-row items in case the CSS needs it
# # only when we are using percentage widths
# if imageset.width > 0 and not counter%items_per_row:
# item.lastinrow = True
#
# aspect_ratio = calculate_aspect_ratio(item.image)
# item.width,item.height = calculate_height(imageset.width, imageset.height, imageset.aspect_ratio, each_item_width, aspect_ratio)
# item.width,item.height = int(item.width), int(item.height)
# item.image_size = u'%sx%s' %(item.width, item.height)
# item.caption_width=item.width
return imageset
def lightbox_single(imageset, context):
# for lightboxes and multiple image sets
imageset.template = "arkestra_image_plugin/%s.html" %"lightbox"
imageset.padding = IMAGESET_ITEM_PADDING
padding_adjuster = IMAGESET_ITEM_PADDING * 2
# choose the first image from the set
item = imageset.items[0]
# calculate its native aspect ratio
aspect_ratio = calculate_aspect_ratio(item.image)
# get width
print "item.image", item.image
width = width_of_image(imageset, item.image)
print "width = width_of_image(imageset, item.image", width
# shave if floated
width = shave_if_floated(imageset, width) or width
# fancybox icons have padding, so:
width = width - padding_adjuster
# calculate height
item.width, item.height = calculate_height(imageset.width, imageset.height, imageset.aspect_ratio, width, aspect_ratio)
item.image_size = u'%sx%s' % (int(item.width), int(item.height))
# item.caption = set_image_caption(imageset.item)
item.width,item.height = int(item.width), int(item.height)
item.caption_width = item.width
return imageset
def single_image(imageset, context):
imageset.template = "arkestra_image_plugin/single_image.html"
# choose an image at random from the set
imageset.item = imageset.imageset_item.order_by('?')[0]
# calculate its native aspect ratio
aspect_ratio = calculate_aspect_ratio(imageset.item.image)
# get width
width = width_of_image(imageset, imageset.item.image)
# shave if floated
width = shave_if_floated(imageset, width) or width
# calculate height
imageset.item.width, imageset.item.height = calculate_height(imageset.width, imageset.height, imageset.aspect_ratio, width, aspect_ratio)
imageset.item.image_size = u'%sx%s' % (int(imageset.item.width), int(imageset.item.height))
# imageset.item.caption = set_image_caption(imageset.item)
imageset.item.width,imageset.item.height = int(imageset.item.width), int(imageset.item.height)
imageset.item.caption_width = imageset.item.width
return imageset
class ImageSetItemPluginForm(forms.ModelForm):
class Meta:
model=ImageSetItem
def __init__(self, *args, **kwargs):
super(ImageSetItemPluginForm, self).__init__(*args, **kwargs)
if self.instance.pk is not None and self.instance.destination_content_type:
destination_content_type = self.instance.destination_content_type.model_class()
else:
destination_content_type = None
self.fields['destination_object_id'].widget = fk_lookup.GenericFkLookup('id_%s-destination_content_type' % self.prefix, destination_content_type)
self.fields['destination_content_type'].widget.choices = schema.content_type_choices()
def clean(self):
super(ImageSetItemPluginForm, self).clean()
# the Link is optional, but unless both fields both Type and Item fields are provided,
# reset them to None
if not self.cleaned_data["destination_content_type"] or not self.cleaned_data["destination_object_id"]:
self.cleaned_data["destination_content_type"]=None
self.cleaned_data["destination_object_id"]=None
if "click here" in self.cleaned_data["alt_text"].lower():
raise forms.ValidationError("'Click here'?! In alt text?! You cannot be serious. Fix this at once.")
return self.cleaned_data
class ImageSetItemFormFormSet(forms.models.BaseInlineFormSet):
def clean(self):
super(ImageSetItemFormFormSet, self).clean()
some_have_links = False
some_do_not_have_links = False
for form in self.forms:
# if a subform is invalid Django explicity raises
# an AttributeError for cleaned_data
try:
# only forms with an image field count - the others might be blank
if form.cleaned_data:
if form.cleaned_data.get("destination_content_type") and form.cleaned_data.get("destination_object_id"):
some_have_links = True
else:
some_do_not_have_links = True
except AttributeError:
pass
# # if some_have_links and some_do_not_have_links then that's inconsistent
# if some_have_links and some_do_not_have_links:
# message = "I won't put links on any of your images until they all have links"
# messages.add_message(self.request, messages.WARNING, message)
class ImageSetItemEditor(SupplyRequestMixin, admin.StackedInline, AutocompleteMixin):
form = ImageSetItemPluginForm
# formset = ImageSetItemFormFormSet
# related_search_fields = ['destination_content_type']
model=ImageSetItem
extra=1
fieldset_basic = ('', {'fields': (('image',),)})
fieldset_advanced = ('Caption', {
'fields': (( 'auto_image_title', 'manual_image_title'), ( 'auto_image_caption', 'manual_image_caption'),),
'classes': ('collapse',)
})
fieldsets = (fieldset_basic, fieldset_advanced,
("Link", {
'fields': (
('destination_content_type', 'destination_object_id',),
'alt_text',
( 'auto_link_title', 'manual_link_title'), ( 'auto_link_description', 'manual_link_description'),
),
'description': "Links will only be applied if <em>all</em> images in the set have links.",
'classes': ('collapse',),
}),
)
formfield_overrides = {
models.TextField: {'widget': forms.Textarea(attrs={'cols':30, 'rows':3,},),},
}
class ImageSetPluginForm(forms.ModelForm):
class Meta:
model=ImageSetPlugin
# when the user adds link to some but not items, we need to issue a warning
# this should warn them, but allow them to force a save
# def clean(self):
# force = self.cleaned_data.get('force_save')
# if not force:
# self.fields['force_save'] = forms.BooleanField(initial=True, widget=forms.HiddenInput())
# raise forms.ValidationError('You gotta check something')
# return self.cleaned_data
class ImageSetPublisher(SupplyRequestMixin, CMSPluginBase):
form = ImageSetPluginForm
model = ImageSetPlugin
name = "Image/link set"
text_enabled = True
raw_id_fields = ('image',)
inlines = (ImageSetItemEditor,)
admin_preview = False
fieldset_basic = ('Size & proportions', {'fields': (('kind', 'width', 'aspect_ratio',),)})
fieldset_advanced = ('Advanced', {'fields': (( 'float', 'height'),), 'classes': ('collapse',)})
fieldset_items_per_row = ('For Multiple and Lightbox plugins only', {'fields': ('items_per_row',), 'classes': ('collapse',)})
fieldsets = (fieldset_basic, fieldset_items_per_row, fieldset_advanced)
def __init__(self, model = None, admin_site = None):
self.admin_preview = False
self.text_enabled = True
super(ImageSetPublisher, self).__init__(model, admin_site)
def render(self, context, imageset, placeholder):
# don't do anything if there are no items in the imageset
if imageset.imageset_item.count():
# calculate the width of the block the image will be in
imageset.container_width = int(width_of_image_container(context, imageset))
# copy the queryset to a list. You know it makes sense
imageset.items = list(imageset.imageset_item.all())
# at least two items are required for a slider
if imageset.kind == "slider" and imageset.number_of_items > 1:
imageset = slider(imageset)
elif imageset.kind == "lightbox" or (imageset.kind == "multiple" and imageset.number_of_items > 1):
imageset = multiple_images(imageset, context)
elif imageset.kind == "lightbox-single":
imageset = lightbox_single(imageset, context)
else:
imageset = single_image(imageset, context)
self.render_template = imageset.template
context.update({
'imageset':imageset,
})
# no items, use a null template
else:
# print "using a null template" , imageset
self.render_template = "null.html"
# context.update({
# 'placeholder':placeholder,
# })
return context
def __unicode__(self):
return self
def icon_src(self, instance):
# print instance.kind
if instance.imageset_item.count() == 1:
return instance.imageset_item.all()[0].image.thumbnails['admin_tiny_icon']
elif instance.kind == "basic":
return "/static/plugin_icons/imageset_basic.png"
elif instance.kind == "lightbox-single":
return "/static/plugin_icons/lightbox.png"
elif instance.kind == "lightbox":
return "/static/plugin_icons/lightbox.png"
elif instance.kind == "slider":
return "/static/plugin_icons/image_slider.png"
else:
return "/static/plugin_icons/imageset.png"
plugin_pool.register_plugin(ImageSetPublisher)
def set_image_caption(image):
# prefer the manually-entered caption on the plugin, otherwise the one from the filer
if image.caption or (image.use_description_as_caption and image.image.description):
return image.caption or image.image.description
class FilerImagePlugin(CMSPluginBase):
model = FilerImage
name = _("Image")
render_template = "cmsplugin_filer_image/image.html"
text_enabled = True
raw_id_fields = ('image',)
admin_preview = False
def render(self, context, instance, placeholder):
self.render_template = "cmsplugin_filer_image/image.html"
instance.has_borders = False
# calculate its width and aspect ratio
instance.container_width = width_of_image_container(context, instance)
# calculate its native aspect ratio
aspect_ratio = calculate_aspect_ratio(instance.image)
# get width
width = width_of_image(instance, instance.image)
# shave if floated
width = shave_if_floated(instance, width) or width
# calculate height
instance.width, instance.height = calculate_height(instance.width, instance.height, instance.aspect_ratio, width, aspect_ratio)
# set caption
instance.caption = set_image_caption(instance)
instance.subject_location = instance.image.subject_location
instance.width, instance.height = int(instance.width), int(instance.height)
context.update({
'object':instance,
'image_size': u'%sx%s' % (int(instance.width), int(instance.height)),
'caption_width': int(width),
'placeholder':placeholder,
'subject_location': instance.image.subject_location,
'imageset':instance,
'imageset_item': instance,
'caption_width': int(instance.width),
'placeholder':placeholder,
})
return context
def get_thumbnail(self, context, instance):
if instance.image:
return instance.image.image.thumbnails['admin_tiny_icon']
def icon_src(self, instance):
return instance.image.thumbnails['admin_tiny_icon']
plugin_pool.register_plugin(FilerImagePlugin)
# gcbirzan's suggestion on how to calculate LIGHTBOX_COLUMNS
# d={}
# def mid(n):
# middle = int(n**.5)
# for diff in range(3):
# if n % (middle + diff) == 0:
# return middle + diff
#
#
# for n in range(2, 100):
# for diff in range(3):
# res = mid(n+diff)
# if not res:
# continue
# d[n] = res
# break
#
#
# print "****", d
# print d[len(items)]
Jump to Line
Something went wrong with that request. Please try again.