From af436e90268b3482950aa536b3ec516e7212cd94 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 3 Oct 2014 14:11:45 +0100 Subject: [PATCH 1/3] Added Django 1.7 migration for focal point blank=True. Fixes #664 --- .../migrations/0003_fix_focal_point_fields.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 wagtail/wagtailimages/migrations/0003_fix_focal_point_fields.py diff --git a/wagtail/wagtailimages/migrations/0003_fix_focal_point_fields.py b/wagtail/wagtailimages/migrations/0003_fix_focal_point_fields.py new file mode 100644 index 0000000000..b5cae9f8d3 --- /dev/null +++ b/wagtail/wagtailimages/migrations/0003_fix_focal_point_fields.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + """ + When the initial migration was created, the focal point fields on image + did not have blank=True set. + + This migration fixes this. + """ + + dependencies = [ + ('wagtailimages', '0002_initial_data'), + ] + + operations = [ + migrations.AlterField( + model_name='image', + name='focal_point_height', + field=models.PositiveIntegerField(null=True, blank=True), + ), + migrations.AlterField( + model_name='image', + name='focal_point_width', + field=models.PositiveIntegerField(null=True, blank=True), + ), + migrations.AlterField( + model_name='image', + name='focal_point_x', + field=models.PositiveIntegerField(null=True, blank=True), + ), + migrations.AlterField( + model_name='image', + name='focal_point_y', + field=models.PositiveIntegerField(null=True, blank=True), + ), + ] From e4449b3d791d9ec0e1cd69c31de140d68d6999f1 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 3 Oct 2014 14:22:36 +0100 Subject: [PATCH 2/3] Made focal_point_key not nullable --- .../0004_make_focal_point_key_not_nullable.py | 19 ++++ wagtail/wagtailimages/models.py | 5 +- ...to__chg_field_rendition_focal_point_key.py | 88 +++++++++++++++++++ wagtail/wagtailimages/tests.py | 28 +++++- 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 wagtail/wagtailimages/migrations/0004_make_focal_point_key_not_nullable.py create mode 100644 wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py diff --git a/wagtail/wagtailimages/migrations/0004_make_focal_point_key_not_nullable.py b/wagtail/wagtailimages/migrations/0004_make_focal_point_key_not_nullable.py new file mode 100644 index 0000000000..47e8c1f669 --- /dev/null +++ b/wagtail/wagtailimages/migrations/0004_make_focal_point_key_not_nullable.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailimages', '0003_fix_focal_point_fields'), + ] + + operations = [ + migrations.AlterField( + model_name='rendition', + name='focal_point_key', + field=models.CharField(blank=True, default='', max_length=255, editable=False), + ), + ] diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index abd7c06a70..35d55a7986 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -143,7 +143,7 @@ class AbstractImage(models.Model, TagSearchable): else: rendition = self.renditions.get( filter=filter, - focal_point_key=None, + focal_point_key='', ) except ObjectDoesNotExist: file_field = self.file @@ -175,7 +175,6 @@ class AbstractImage(models.Model, TagSearchable): else: rendition, created = self.renditions.get_or_create( filter=filter, - focal_point_key=None, defaults={'file': generated_image_file} ) @@ -345,7 +344,7 @@ class AbstractRendition(models.Model): file = models.ImageField(upload_to='images', width_field='width', height_field='height') width = models.IntegerField(editable=False) height = models.IntegerField(editable=False) - focal_point_key = models.CharField(max_length=255, null=True, editable=False) + focal_point_key = models.CharField(max_length=255, blank=True, default='', editable=False) @property def url(self): diff --git a/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py b/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py new file mode 100644 index 0000000000..f094d9553c --- /dev/null +++ b/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py @@ -0,0 +1,88 @@ +# -*- 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): + + def forwards(self, orm): + + # Changing field 'Rendition.focal_point_key' + db.alter_column(u'wagtailimages_rendition', 'focal_point_key', self.gf('django.db.models.fields.CharField')(max_length=255, default='')) + + def backwards(self, orm): + + # Changing field 'Rendition.focal_point_key' + db.alter_column(u'wagtailimages_rendition', u'focal_point_key', self.gf(u'django.db.models.fields.CharField')(max_length=255, null=True)) + + 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'}), + 'focal_point_height': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'focal_point_width': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'focal_point_x': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'focal_point_y': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + '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', 'focal_point_key'),)", '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']"}), + 'focal_point_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), + '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'] \ No newline at end of file diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index 46931bc755..80e9932af6 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -14,9 +14,10 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import Group, Permission from django.core.urlresolvers import reverse from django.core.files.uploadedfile import SimpleUploadedFile +from django.db.utils import IntegrityError from wagtail.tests.utils import unittest, WagtailTestUtils -from wagtail.wagtailimages.models import get_image_model +from wagtail.wagtailimages.models import get_image_model, Rendition from wagtail.wagtailimages.formats import ( Format, get_image_format, @@ -1132,3 +1133,28 @@ class TestIssue613(TestCase, WagtailTestUtils): # Check self.assertEqual(len(results), 1) self.assertEqual(results[0].id, image.id) + + +class TestIssue312(TestCase): + def test_duplicate_renditions(self): + # Create an image + image = Image.objects.create( + title="Test image", + file=get_test_image_file(), + ) + + # Get two renditions and check that they're the same + rend1 = image.get_rendition('fill-100x100') + rend2 = image.get_rendition('fill-100x100') + self.assertEqual(rend1, rend2) + + # Now manually duplicate the renditon and check that the database blocks it + self.assertRaises( + IntegrityError, + Rendition.objects.create, + image=rend1.image, + filter=rend1.filter, + width=rend1.width, + height=rend1.height, + focal_point_key=rend1.focal_point_key, + ) From 5a48af83fd55d0383e29dc781612ef99edb85d5c Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 3 Oct 2014 14:23:44 +0100 Subject: [PATCH 3/3] Python 3 support for migration --- ...to__chg_field_rendition_focal_point_key.py | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py b/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py index f094d9553c..4628273aa3 100644 --- a/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py +++ b/wagtail/wagtailimages/south_migrations/0004_auto__chg_field_rendition_focal_point_key.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + from south.utils import datetime_utils as datetime from south.db import db from south.v2 import SchemaMigration @@ -10,56 +12,56 @@ class Migration(SchemaMigration): def forwards(self, orm): # Changing field 'Rendition.focal_point_key' - db.alter_column(u'wagtailimages_rendition', 'focal_point_key', self.gf('django.db.models.fields.CharField')(max_length=255, default='')) + db.alter_column('wagtailimages_rendition', 'focal_point_key', self.gf('django.db.models.fields.CharField')(max_length=255, default='')) def backwards(self, orm): # Changing field 'Rendition.focal_point_key' - db.alter_column(u'wagtailimages_rendition', u'focal_point_key', self.gf(u'django.db.models.fields.CharField')(max_length=255, null=True)) + db.alter_column('wagtailimages_rendition', 'focal_point_key', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) models = { - u'auth.group': { + 'auth.group': { 'Meta': {'object_name': 'Group'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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'}) + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "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'}, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', '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'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, - u'auth.user': { + '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'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}), + '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']"}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, - u'contenttypes.contenttype': { + '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'}), + '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': { + 'wagtailimages.filter': { 'Meta': {'object_name': 'Filter'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'spec': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) }, - u'wagtailimages.image': { + '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'}), @@ -68,19 +70,19 @@ class Migration(SchemaMigration): 'focal_point_x': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 'focal_point_y': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 'height': ('django.db.models.fields.IntegerField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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'}), + 'uploaded_by_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), 'width': ('django.db.models.fields.IntegerField', [], {}) }, - u'wagtailimages.rendition': { + 'wagtailimages.rendition': { 'Meta': {'unique_together': "(('image', 'filter', 'focal_point_key'),)", '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']"}), + 'filter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['wagtailimages.Filter']"}), 'focal_point_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}), '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']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'renditions'", 'to': "orm['wagtailimages.Image']"}), 'width': ('django.db.models.fields.IntegerField', [], {}) } }