diff --git a/icekit/admin.py b/icekit/admin.py index 7d6cc7a4..61265cd4 100644 --- a/icekit/admin.py +++ b/icekit/admin.py @@ -9,7 +9,9 @@ from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.http import JsonResponse +from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ +from polymorphic.admin import PolymorphicChildModelFilter from polymorphic.admin import PolymorphicParentModelAdmin from icekit import models @@ -166,5 +168,18 @@ class MediaCategoryAdmin(admin.ModelAdmin): pass +class AssetParentAdmin(PolymorphicParentModelAdmin): + base_model = models.Asset + list_display = ['title', 'get_admin_thumbnail', 'get_child_type', 'caption', ] + list_display_links = ['title'] + filter_horizontal = ['categories', ] + list_filter = [PolymorphicChildModelFilter, 'categories'] + search_fields = ['title', 'caption', 'admin_notes'] + polymorphic_list = True + + def get_child_models(self): + return [import_string(kls) for kls in settings.ASSET_CLASSES] + +admin.site.register(models.Asset, AssetParentAdmin) admin.site.register(models.Layout, LayoutAdmin) admin.site.register(models.MediaCategory, MediaCategoryAdmin) diff --git a/icekit/migrations/0007_asset.py b/icekit/migrations/0007_asset.py new file mode 100644 index 00000000..8c5ec8d7 --- /dev/null +++ b/icekit/migrations/0007_asset.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('icekit', '0006_auto_20150911_0744'), + ] + + operations = [ + migrations.CreateModel( + name='Asset', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(help_text='The title is shown in the "title" attribute', max_length=255, blank=True)), + ('caption', models.TextField(blank=True)), + ('admin_notes', models.TextField(help_text='Internal notes for administrators only.', blank=True)), + ('categories', models.ManyToManyField(related_name='icekit_asset_related', to='icekit.MediaCategory', blank=True)), + ('polymorphic_ctype', models.ForeignKey(related_name='polymorphic_icekit.asset_set+', editable=False, to='contenttypes.ContentType', null=True)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/icekit/models.py b/icekit/models.py index bc2443df..1287ddf5 100644 --- a/icekit/models.py +++ b/icekit/models.py @@ -1,3 +1,9 @@ +import six +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from fluent_contents.models import ContentItem +from polymorphic.models import PolymorphicModel + from . import abstract_models, managers @@ -15,3 +21,40 @@ class MediaCategory(abstract_models.AbstractMediaCategory): A categorisation model for Media assets. """ pass + + +class Asset(PolymorphicModel): + """ + A static asset available for use on a CMS page. + """ + title = models.CharField( + max_length=255, + blank=True, + help_text=_('The title is shown in the "title" attribute'), + ) + caption = models.TextField( + blank=True, + ) + categories = models.ManyToManyField( + 'icekit.MediaCategory', + blank=True, + related_name='%(app_label)s_%(class)s_related', + ) + admin_notes = models.TextField( + blank=True, + help_text=_('Internal notes for administrators only.'), + ) + + def get_admin_thumbnail(self, width=150, height=150): + raise NotImplementedError + get_admin_thumbnail.short_description = "thumbnail" + + def get_child_type(self): + return self.polymorphic_ctype + get_child_type.short_description = 'type' + + def get_uses(self): + return [item.parent.get_absolute_url() for item in self.assetitem_set().all()] + + def __str__(self): + return self.title diff --git a/icekit/plugins/image/abstract_models.py b/icekit/plugins/image/abstract_models.py index 893209da..db36d374 100644 --- a/icekit/plugins/image/abstract_models.py +++ b/icekit/plugins/image/abstract_models.py @@ -1,16 +1,19 @@ from django.core.exceptions import ValidationError from django.template import Context from django.template.loader import render_to_string +from django.utils import six +from django.utils.html import format_html from django.utils.safestring import mark_safe from django.db import models from django.utils import six from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from fluent_contents.models import ContentItem +from icekit.models import Asset @python_2_unicode_compatible -class AbstractImage(models.Model): +class AbstractImage(Asset): """ A reusable image. """ @@ -18,38 +21,33 @@ class AbstractImage(models.Model): upload_to='uploads/images/', verbose_name=_('Image field'), ) - title = models.CharField( - max_length=255, - blank=True, - help_text=_('Can be included in captions'), - ) alt_text = models.CharField( max_length=255, help_text=_("A description of the image for users who don't see images. Leave blank if the image has no informational value."), blank=True, ) - caption = models.TextField( - blank=True, - ) - categories = models.ManyToManyField( - 'icekit.MediaCategory', - blank=True, - related_name='%(app_label)s_%(class)s_related', - ) - admin_notes = models.TextField( - blank=True, - help_text=_('Internal notes for administrators only.'), - ) is_active = models.BooleanField( default=True, ) + class Meta: + abstract = True + def clean(self): if not (self.title or self.alt_text): raise ValidationError("You must specify either title or alt text") - class Meta: - abstract = True + def get_admin_thumbnail(self, width=150, height=150): + try: + from easy_thumbnails.files import get_thumbnailer + except ImportError: + return 'Thumbnail error: easy_thumbnails not installed' + try: + thumbnailer = get_thumbnailer(self.image) + thumbnail = thumbnailer.get_thumbnail({'size': (width, height)}) + return format_html(''.format(thumbnail.url)) + except Exception as ex: + return 'Thumbnail exception: {0}'.format(ex) def __str__(self): return self.title or self.alt_text @@ -95,7 +93,7 @@ def caption(self): @caption.setter def caption(self, value): """ - If the caption property is assigned, make it use the + If the caption property is assigned to make it use the `caption_override` field. :param value: The caption value to be saved. diff --git a/icekit/plugins/image/admin.py b/icekit/plugins/image/admin.py index 12664102..991a9689 100644 --- a/icekit/plugins/image/admin.py +++ b/icekit/plugins/image/admin.py @@ -1,23 +1,13 @@ from django.contrib import admin from icekit.utils.admin.mixins import ThumbnailAdminMixin +from polymorphic.admin import PolymorphicChildModelAdmin from . import models -class ImageAdmin(ThumbnailAdminMixin, admin.ModelAdmin): - list_display = ['thumbnail', 'title', 'alt_text',] - list_display_links = ['alt_text', 'thumbnail'] - filter_horizontal = ['categories', ] - list_filter = ['categories',] - search_fields = ['title', 'alt_text', 'caption', 'admin_notes', 'image'] - +class ImageAdmin(PolymorphicChildModelAdmin): + base_model = models.Image change_form_template = 'image/admin/change_form.html' - # ThumbnailAdminMixin attributes - thumbnail_field = 'image' - thumbnail_options = { - 'size': (150, 150), - } - admin.site.register(models.Image, ImageAdmin) diff --git a/icekit/plugins/image/migrations/0009_assets_20161013_1124.py b/icekit/plugins/image/migrations/0009_assets_20161013_1124.py new file mode 100644 index 00000000..1a6d9946 --- /dev/null +++ b/icekit/plugins/image/migrations/0009_assets_20161013_1124.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +def create_assets(apps, schema_editor): + # We need to create a new asset for each image + Image = apps.get_model('icekit_plugins_image', 'Image') + Asset = apps.get_model('icekit', 'Asset') + for image in Image.objects.all(): + # Create a new asset + asset = Asset() + asset.admin_notes = image.admin_notes + asset.caption = image.caption + asset.title = image.title + asset.save() + asset.categories.add(*image.categories.all()) + + # Set the asset_ptr on the image + image.asset_ptr = asset + image.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('icekit', '0007_asset'), + ('icekit_plugins_image', '0008_auto_20160920_2114'), + ('icekit', '0006_auto_20150911_0744'), + ] + + operations = [ + # Add the ForeignKey first as not the primary key so we can fake it + migrations.AddField( + model_name='image', + name='asset_ptr', + field=models.OneToOneField(parent_link=True, auto_created=True, default=1, serialize=False, to='icekit.Asset'), + preserve_default=False, + ), + # Create asset links for existing images + migrations.RunPython( + create_assets + ), + migrations.RemoveField( + model_name='image', + name='admin_notes', + ), + migrations.RemoveField( + model_name='image', + name='caption', + ), + migrations.RemoveField( + model_name='image', + name='categories', + ), + migrations.RemoveField( + model_name='image', + name='id', + ), + migrations.RemoveField( + model_name='image', + name='title', + ), + # Make asset_ptr the primary key + migrations.AlterField( + model_name='image', + name='asset_ptr', + field=models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='icekit.Asset'), + preserve_default=False, + ), + ] diff --git a/icekit/project/settings/_base.py b/icekit/project/settings/_base.py index 0bd47cc4..db0afe94 100644 --- a/icekit/project/settings/_base.py +++ b/icekit/project/settings/_base.py @@ -610,6 +610,8 @@ MIDDLEWARE_CLASSES += ('icekit.publishing.middleware.PublishingMiddleware', ) +ASSET_CLASSES = ('icekit.plugins.image.models.Image',) + # MASTER PASSWORD ############################################################# AUTHENTICATION_BACKENDS += ('master_password.auth.ModelBackend', )