From ef56f4aec408d4e1c65cc8ecea08f0db089127f0 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Wed, 20 May 2015 00:43:33 +0530 Subject: [PATCH] track annotation id in table to avoid IntegrityErrors while adding multiple annotations to one item, fixes #2780 --- pandora/annotation/models.py | 9 +- .../0006_add_annotation_sequence.py | 166 ++++++++++++++++++ pandora/item/models.py | 32 +++- 3 files changed, 198 insertions(+), 9 deletions(-) create mode 100644 pandora/item/migrations/0006_add_annotation_sequence.py diff --git a/pandora/annotation/models.py b/pandora/annotation/models.py index 788cb116..4b33961f 100644 --- a/pandora/annotation/models.py +++ b/pandora/annotation/models.py @@ -115,14 +115,7 @@ class Annotation(models.Model): def set_public_id(self): if self.id: - public_id = Annotation.objects.filter(item=self.item, id__lt=self.id).count() + 1 - if public_id > 1: - previous = Annotation.objects.filter(item=self.item, - id__lt=self.id).order_by('-id')[0] - if not previous.public_id: - previous.set_public_id() - public_id = ox.fromAZ(previous.public_id.split('/')[-1]) + 1 - self.public_id = "%s/%s" % (self.item.public_id, ox.toAZ(public_id)) + self.public_id = self.item.next_annotationid() Annotation.objects.filter(id=self.id).update(public_id=self.public_id) @classmethod diff --git a/pandora/item/migrations/0006_add_annotation_sequence.py b/pandora/item/migrations/0006_add_annotation_sequence.py new file mode 100644 index 00000000..72a054ea --- /dev/null +++ b/pandora/item/migrations/0006_add_annotation_sequence.py @@ -0,0 +1,166 @@ +# -*- 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): + # Adding model 'AnnotationSequence' + db.create_table('item_annotationsequence', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('item', self.gf('django.db.models.fields.related.ForeignKey')(related_name='annotation_sequence', unique=True, to=orm['item.Item'])), + ('value', self.gf('django.db.models.fields.BigIntegerField')(default=1)), + )) + db.send_create_signal('item', ['AnnotationSequence']) + import item.models + for a in item.models.Annotation.objects.filter(public_id=None): a.save() + for i in item.models.Item.objects.all(): + item.models.AnnotationSequence.reset(i) + + def backwards(self, orm): + # Deleting model 'AnnotationSequence' + db.delete_table('item_annotationsequence') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + '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': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + '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': '255', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + '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': '255'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + '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'}), + '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'}) + }, + 'item.access': { + 'Meta': {'unique_together': "(('item', 'user'),)", 'object_name': 'Access'}, + 'access': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'accessed': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'accessed'", 'to': "orm['item.Item']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'accessed_items'", 'null': 'True', 'to': "orm['auth.User']"}) + }, + 'item.annotationsequence': { + 'Meta': {'object_name': 'AnnotationSequence'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_sequence'", 'unique': 'True', 'to': "orm['item.Item']"}), + 'value': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}) + }, + 'item.description': { + 'Meta': {'unique_together': "(('key', 'value'),)", 'object_name': 'Description'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'db_index': 'True'}) + }, + 'item.facet': { + 'Meta': {'unique_together': "(('item', 'key', 'value'),)", 'object_name': 'Facet'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'facets'", 'to': "orm['item.Item']"}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), + 'sortvalue': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'db_index': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'db_index': 'True'}) + }, + 'item.item': { + 'Meta': {'object_name': 'Item'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'data': ('ox.django.fields.DictField', [], {'default': '{}'}), + 'external_data': ('ox.django.fields.DictField', [], {'default': '{}'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'items'", 'blank': 'True', 'to': "orm['auth.Group']"}), + 'icon': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'json': ('ox.django.fields.DictField', [], {'default': '{}'}), + 'level': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'oxdbId': ('django.db.models.fields.CharField', [], {'max_length': '42', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'poster': ('django.db.models.fields.files.ImageField', [], {'default': 'None', 'max_length': '100', 'blank': 'True'}), + 'poster_frame': ('django.db.models.fields.FloatField', [], {'default': '-1'}), + 'poster_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'poster_source': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'poster_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'public_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128', 'blank': 'True'}), + 'rendered': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'stream_aspect': ('django.db.models.fields.FloatField', [], {'default': '1.3333333333333333'}), + 'stream_info': ('ox.django.fields.DictField', [], {'default': '{}'}), + 'torrent': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '1000', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'items'", 'null': 'True', 'to': "orm['auth.User']"}) + }, + 'item.itemfind': { + 'Meta': {'unique_together': "(('item', 'key'),)", 'object_name': 'ItemFind'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'find'", 'to': "orm['item.Item']"}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_index': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {'db_index': 'True', 'blank': 'True'}) + }, + 'item.itemsort': { + 'Meta': {'object_name': 'ItemSort'}, + 'accessed': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'aspectratio': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'bitrate': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'cinematographer': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'cutsperminute': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'date': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'director': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'duration': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'featuring': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'height': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'hue': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'item': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'sort'", 'unique': 'True', 'primary_key': 'True', 'to': "orm['item.Item']"}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'license': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'lightness': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'modified': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'numberofannotations': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'numberofcuts': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'numberoffiles': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'pixels': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'public_id': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'random': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'resolution': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'rightslevel': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'saturation': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'timesaccessed': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'topic': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'null': 'True', 'db_index': 'True'}), + 'volume': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'width': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'words': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'wordsperminute': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['item'] diff --git a/pandora/item/models.py b/pandora/item/models.py index f6a0c0ce..a31d780e 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -14,7 +14,7 @@ from datetime import datetime from glob import glob from urllib import quote -from django.db import models, transaction +from django.db import models, transaction, connection from django.db.models import Q, Sum, Max from django.conf import settings from django.contrib.auth.models import User, Group @@ -1538,6 +1538,7 @@ class Item(models.Model): with transaction.commit_on_success(): layer = subtitles['id'] Annotation.objects.filter(layer=layer, item=self).delete() + AnnotationSequence.reset(self) offset = 0 language = '' subtitles = self.files.filter(selected=True, is_subtitle=True, available=True) @@ -1614,6 +1615,9 @@ class Item(models.Model): 'value': format_value(a.value) } for a in annotations.order_by('start', 'end', 'sortvalue')]) + def next_annotationid(self): + return AnnotationSequence.nextid(self) + def delete_item(sender, **kwargs): i = kwargs['instance'] i.delete_files() @@ -1756,3 +1760,29 @@ class Description(models.Model): key = models.CharField(max_length=200, db_index=True) value = models.CharField(max_length=1000, db_index=True) description = models.TextField() + + +class AnnotationSequence(models.Model): + item = models.ForeignKey('Item', related_name='_annotation_sequence', unique=True) + value = models.BigIntegerField(default=1) + + @classmethod + def reset(cls, item): + s, created = cls.objects.get_or_create(item=item) + ids = [ox.fromAZ(a['public_id'].split('/')[1]) + for a in item.annotations.exclude(public_id=None).values('public_id')] + s.value = max(ids) if ids else 0 + s.save() + + @classmethod + def nextid(cls, item): + with transaction.commit_on_success(): + s, created = cls.objects.get_or_create(item=item) + if created: + nextid = s.value + else: + cursor = connection.cursor() + sql = "UPDATE %s SET value = value + 1 WHERE item_id = %s RETURNING value" % (cls._meta.db_table, item.id) + cursor.execute(sql) + nextid = cursor.fetchone()[0] + return "%s/%s" % (item.public_id, ox.toAZ(nextid))