# HG changeset patch # User ymh # Date 1466001103 -7200 # Node ID 23416a833ca861424064838c19b86772bc72b927 # Parent e00a24b711a0066472f915bc7311fd3cea0140ec Change guid fields to more optimal type + migration diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/admin.py --- a/server/python/django2/renkanmanager/admin.py Mon Jun 13 14:23:58 2016 +0200 +++ b/server/python/django2/renkanmanager/admin.py Wed Jun 15 16:31:43 2016 +0200 @@ -6,18 +6,19 @@ class RenkanAdmin(admin.ModelAdmin): list_display = ('renkan_guid', 'current_revision_link', 'title', 'creator', 'creation_date') ordering = ('creation_date',) - readonly_fields = ('current_revision_link', 'source_revision', 'title', 'content', 'is_copy', 'revision_count', 'creation_date') + #readonly_fields = ('current_revision_link', 'source_revision', 'title', 'content', 'is_copy', 'revision_count', 'creation_date') + readonly_fields = ('current_revision_link', 'title', 'content', 'is_copy', 'revision_count', 'creation_date') fieldsets = ( (None, {'fields': ('renkan_guid',)}), ("Révision courante", {'fields': ('current_revision_link', 'title', 'content')}), ("Copie", {'fields': ( 'is_copy', 'source_revision',)}), ("Création", {'fields': ('creator', 'creation_date')}), ) - + def current_revision_link(self, obj): return '%s' % (reverse("admin:renkanmanager_revision_change", args=(obj.current_revision.id,)), obj.current_revision.revision_guid) current_revision_link.allow_tags = True - current_revision_link.short_description = "Révision courante" + current_revision_link.short_description = "Révision courante" class RevisionAdmin(admin.ModelAdmin): list_display = ('revision_guid', 'parent_renkan_link', 'creator', 'title', 'creation_date') @@ -29,13 +30,13 @@ ("Création", {'fields': ('creator', 'creation_date')}), ("Edition", {'fields': ('last_updated_by', 'modification_date')}) ) - + def parent_renkan_link(self, obj): return '%s' % (reverse("admin:renkanmanager_renkan_change", args=(obj.parent_renkan.id,)), obj.parent_renkan.renkan_guid) parent_renkan_link.allow_tags = True - parent_renkan_link.short_description = "Renkan associé" - + parent_renkan_link.short_description = "Renkan associé" + class WorkspaceAdmin(admin.ModelAdmin): pass diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/migrations/0004_change_guid_field_type.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/python/django2/renkanmanager/migrations/0004_change_guid_field_type.py Wed Jun 15 16:31:43 2016 +0200 @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-06-15 11:15 +from __future__ import unicode_literals + +from django.db import migrations, models +import os.path +import uuid + + +sql_path = os.path.splitext(os.path.abspath(__file__))[0]+'.sql' +with open(sql_path, "r") as sqlfile: + sql_commands = sqlfile.read() + +class Migration(migrations.Migration): + + dependencies = [ + ('renkanmanager', '0003_auto_20160105_0954'), + ] + + operations = [ + migrations.AlterModelOptions( + name='renkan', + options={'permissions': (('view_renkan', 'Can view renkan'),)}, + ), + migrations.AlterModelOptions( + name='revision', + options={'permissions': (('view_revision', 'Can view revision'),)}, + ), + migrations.AlterModelOptions( + name='workspace', + options={'permissions': (('view_workspace', 'Can view workspace'),)}, + ), + migrations.RunSQL(sql_commands, None, [ + migrations.AlterField( + model_name='renkan', + name='renkan_guid', + field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ), + migrations.AlterField( + model_name='revision', + name='revision_guid', + field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ), + migrations.AlterField( + model_name='workspace', + name='workspace_guid', + field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ) + ]) + ] diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/migrations/0004_change_guid_field_type.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/python/django2/renkanmanager/migrations/0004_change_guid_field_type.sql Wed Jun 15 16:31:43 2016 +0200 @@ -0,0 +1,74 @@ +-- from http://baltaks.com/2015/08/how-to-change-text-fields-to-a-real-uuid-type-for-django-and-postgresql +DO $$ +DECLARE + table_names text[]; + this_table_def text[]; + this_table_name text; + this_field_name text; + the_constraint_name text; + index_names record; + +BEGIN + +SELECT array[['renkanmanager_workspace', 'workspace_guid'], + ['renkanmanager_renkan', 'renkan_guid'], + ['renkanmanager_revision', 'revision_guid'] + ] + INTO table_names; + + +FOREACH this_table_def SLICE 1 IN ARRAY table_names +LOOP + this_table_name := this_table_def[1]; + this_field_name := this_table_def[2]; + RAISE notice 'migrating table %', this_table_name; + + SELECT CONSTRAINT_NAME INTO the_constraint_name + FROM information_schema.constraint_column_usage + WHERE CONSTRAINT_SCHEMA = current_schema() + AND COLUMN_NAME IN (this_field_name) + AND TABLE_NAME = this_table_name + GROUP BY CONSTRAINT_NAME + HAVING count(*) = 1; + if the_constraint_name is not NULL then + RAISE notice 'alter table % drop constraint %', + this_table_name, + the_constraint_name; + execute 'alter table ' || this_table_name + || ' drop constraint ' || the_constraint_name; + end if; + + FOR index_names IN + (SELECT i.relname AS index_name + FROM pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + WHERE t.oid = ix.indrelid + AND i.oid = ix.indexrelid + AND a.attrelid = t.oid + AND a.attnum = any(ix.indkey) + AND t.relkind = 'r' + AND a.attname = this_field_name + AND t.relname = this_table_name + ORDER BY t.relname, + i.relname) + LOOP + RAISE notice 'drop index %', quote_ident(index_names.index_name); + EXECUTE 'drop index ' || quote_ident(index_names.index_name); + END LOOP; -- index_names + + RAISE notice 'alter table % alter column % type uuid using uuid::uuid;', + this_table_name, this_field_name; + execute 'alter table ' || quote_ident(this_table_name) + || ' alter column ' || quote_ident(this_field_name) + || ' type uuid using ' || quote_ident(this_field_name) || '::uuid;'; + RAISE notice 'CREATE UNIQUE INDEX %_% ON % (%);', + this_table_name, this_field_name, this_table_name, this_field_name; + execute 'create unique index ' || this_table_name || '_' || this_field_name || ' on ' + || this_table_name || '('|| this_field_name ||');'; + +END LOOP; -- table_names + +END; +$$ diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/migrations/0004_foreign_key_fields_initial.py --- a/server/python/django2/renkanmanager/migrations/0004_foreign_key_fields_initial.py Mon Jun 13 14:23:58 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.1 on 2016-04-19 10:08 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('renkanmanager', '0003_auto_20160105_0954'), - ] - - operations = [ - migrations.AlterModelOptions( - name='renkan', - options={'permissions': (('view_renkan', 'Can view renkan'),)}, - ), - migrations.AlterModelOptions( - name='revision', - options={'permissions': (('view_revision', 'Can view revision'),)}, - ), - migrations.AlterModelOptions( - name='workspace', - options={'permissions': (('view_workspace', 'Can view workspace'),)}, - ), - migrations.AddField( - model_name='renkan', - name='source_revision', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='renkan_source_revision', to='renkanmanager.Revision', to_field='revision_guid'), - ), - migrations.AddField( - model_name='renkan', - name='workspace', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='renkanmanager.Workspace', to_field='workspace_guid'), - ), - migrations.AddField( - model_name='revision', - name='parent_renkan', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='renkanmanager.Renkan', to_field='renkan_guid'), - ), - ] diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/migrations/0005_foreign_key_fields_datamigration.py --- a/server/python/django2/renkanmanager/migrations/0005_foreign_key_fields_datamigration.py Mon Jun 13 14:23:58 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.1 on 2016-04-14 12:23 -from __future__ import unicode_literals - -from django.db import migrations - - -def populate_foreign_keys(apps, schema_editor): - renkans = apps.get_model('renkanmanager', 'Renkan') - revisions = apps.get_model('renkanmanager', 'Revision') - workspaces = apps.get_model('renkanmanager', 'Workspace') - for renkan in renkans.objects.all(): - current_revision_for_renkan = revisions.objects.get(revision_guid=renkan.current_revision_guid) - renkan.current_revision = current_revision_for_renkan - if renkan.source_revision_guid: - current_source_for_renkan = revisions.objects.get(revision_guid=renkan.source_revision_guid) - renkan.source_revision = current_source_for_renkan - if renkan.workspace_guid: - workspace_for_renkan = revisions.objects.get(revision_guid=renkan.workspace_guid) - renkan.workspace = workspace_for_renkan - renkan.save() - for revision in revisions.objects.all(): - parent_renkan_for_revision = renkans.objects.get(renkan_guid=revision.parent_renkan_guid) - revision.parent_renkan = parent_renkan_for_revision - revision.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('renkanmanager', '0004_foreign_key_fields_initial'), - ] - - operations = [ - migrations.RunPython(populate_foreign_keys) - ] diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/migrations/0006_foreign_key_fields_remove_guids_and_set_nonnullables.py --- a/server/python/django2/renkanmanager/migrations/0006_foreign_key_fields_remove_guids_and_set_nonnullables.py Mon Jun 13 14:23:58 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.1 on 2016-04-19 10:09 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('renkanmanager', '0005_foreign_key_fields_datamigration'), - ] - - operations = [ - migrations.RemoveField( - model_name='renkan', - name='current_revision_guid', - ), - migrations.RemoveField( - model_name='renkan', - name='source_revision_guid', - ), - migrations.RemoveField( - model_name='renkan', - name='workspace_guid', - ), - migrations.RemoveField( - model_name='revision', - name='parent_renkan_guid', - ), - migrations.AlterField( - model_name='revision', - name='parent_renkan', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='renkanmanager.Renkan', to_field='renkan_guid'), - ), - ] diff -r e00a24b711a0 -r 23416a833ca8 server/python/django2/renkanmanager/models.py --- a/server/python/django2/renkanmanager/models.py Mon Jun 13 14:23:58 2016 +0200 +++ b/server/python/django2/renkanmanager/models.py Wed Jun 15 16:31:43 2016 +0200 @@ -17,29 +17,32 @@ auth_user_model = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') class Workspace(models.Model): - - workspace_guid = models.CharField(max_length=1024, default=uuid.uuid4, unique=True, blank=False, null=False) # typically UUID + + workspace_guid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, blank=False, null=False) title = models.CharField(max_length=1024, null=True) creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="workspace_creator") creation_date = models.DateTimeField(auto_now_add=True) - + @property def renkan_count(self): - return Renkan.objects.filter(workspace__workspace_guid=self.workspace_guid).count() - + #TODO: check count and related objects + #return Renkan.objects.filter(workspace__workspace_guid=self.workspace_guid).count() + return Renkan.objects.filter(workspace_guid=self.workspace_guid).count() + class Meta: app_label = 'renkanmanager' permissions = ( ('view_workspace', 'Can view workspace'), ) - + class RenkanManager(models.Manager): - + @transaction.atomic def create_renkan(self, creator, title='', content='', source_revision=None, workspace = None): new_renkan = Renkan() new_renkan.creator = creator + #TODO: !!! new_renkan_workspace_guid is not set on the new renkan ! only on the content ! new_renkan_workspace_guid = "" new_renkan_title = title new_renkan_content = content @@ -64,7 +67,7 @@ new_renkan_content_dict = json.loads(new_renkan.validate_json_content(new_renkan_content)) new_renkan_content_dict["created"] = str(initial_revision.creation_date) new_renkan_content_dict["updated"] = str(initial_revision.modification_date) - else: + else: new_renkan_content_dict = { "id": str(new_renkan.renkan_guid), "title": initial_revision.title, @@ -80,57 +83,68 @@ initial_revision.content = json.dumps(new_renkan_content_dict) initial_revision.save() return new_renkan - + class Renkan(models.Model): - - renkan_guid = models.CharField(max_length=256, default=uuid.uuid4, unique=True, blank=False, null=False) # typically UUID - workspace = models.ForeignKey('Workspace', null=True, blank=True, to_field='workspace_guid') - source_revision = models.ForeignKey('Revision', null=True, blank=True, related_name="renkan_source_revision", to_field='revision_guid', on_delete=models.SET_NULL) + + renkan_guid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, blank=False, null=False) + workspace_guid = models.CharField(max_length=256, blank=True, null=True) + current_revision_guid = models.CharField(max_length=256, blank=True, null=True) + source_revision_guid = models.CharField(max_length=256, blank=True, null=True) + creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="renkan_creator") creation_date = models.DateTimeField(auto_now_add=True) state = models.IntegerField(default=1) - + objects = RenkanManager() - + @property def revision_count(self): - return Revision.objects.filter(parent_renkan__renkan_guid=self.renkan_guid).count() - + #TODO: check related object count + return Revision.objects.filter(parent_renkan_guid=self.renkan_guid).count() + #return Revision.objects.filter(parent_renkan__renkan_guid=self.renkan_guid).count() + @property def is_copy(self): - return bool(self.source_revision) - + #return bool(self.source_revision) + return bool(self.source_revision_guid) + # Current revision object or None if there is none - @property - def current_revision(self): - return Revision.objects.filter(parent_renkan__renkan_guid=self.renkan_guid).order_by('-creation_date').first() - +# @property +# def current_revision(self): +# return Revision.objects.filter(parent_renkan__renkan_guid=self.renkan_guid).order_by('-creation_date').first() + # Current revision title @property def title(self): - if self.current_revision: - return self.current_revision.title - else: - return '' - + current_revision = Revision.objects.get(revision_guid = self.current_revision_guid) + return current_revision.title + #TODO: not good -> 2 requests + #if self.current_revision: + # return self.current_revision.title + #else: + # return '' + # Current revision content @property def content(self): - if self.current_revision: - return self.current_revision.content - else: - return '' - + #TODO: not good -> 2 requests + current_revision = Revision.objects.get(revision_guid = self.current_revision_guid) + return current_revision.content + #if self.current_revision: + # return self.current_revision.content + #else: + # return '' + def __unicode__(self): return self.renkan_guid - + def __str__(self): return self.renkan_guid - + @transaction.atomic def save_renkan(self, updator, timestamp="", title="", content="", create_new_revision=False): """ - Saves over current revision or saves a new revision entirely. + Saves over current revision or saves a new revision entirely. Timestamp must be the current revision modification_date. """ if (not timestamp) or ((self.current_revision is not None) and dateparse.parse_datetime(timestamp) < self.current_revision.modification_date): @@ -144,33 +158,33 @@ revision_to_update.creator = updator else: revision_to_update = Revision.objects.select_for_update().get(revision_guid=self.current_revision.revision_guid) - + updated_content = self.validate_json_content(content) if content else current_revision.content updated_content_dict = json.loads(updated_content) - + # If title is passed as arg to the method, update the title in the json if title: updated_title = title updated_content_dict["title"] = title # If it is not, we use the one in the json instead else: - updated_title = updated_content_dict["title"] - + updated_title = updated_content_dict["title"] + revision_to_update.modification_date = timezone.now() updated_content_dict["updated"] = str(revision_to_update.modification_date) - updated_content = json.dumps(updated_content_dict) + updated_content = json.dumps(updated_content_dict) revision_to_update.title = updated_title revision_to_update.content = updated_content if dt_timestamp == revision_to_update.modification_date: revision_to_update.modification_date += datetime.resolution revision_to_update.last_updated_by = updator revision_to_update.save() - + def validate_json_content(self, content): """ Checks that the json content is valid (keys and structures), raise a ValidationError if format is wrong or value is wrong (for ids), if a key is missing, autocompletes with the empty default value - + Returns the validated json string """ try: @@ -198,7 +212,7 @@ content_to_validate_dict["views"] = [] if "users" not in content_to_validate_dict: content_to_validate_dict["users"] = [] - + if type(content_to_validate_dict["nodes"]) is not list: raise ValidationError("Provided content has an invalid 'nodes' key: not a list") if type(content_to_validate_dict["edges"]) is not list: @@ -208,43 +222,50 @@ if type(content_to_validate_dict["users"]) is not list: raise ValidationError("Provided content has an invalid 'users' key: not a list") return json.dumps(content_to_validate_dict) - - @transaction.atomic - def delete(self): - """ - Deleting a renkan also deletes every related revision - """ - renkan_revisions = Revision.objects.filter(parent_renkan__renkan_guid = self.renkan_guid) - for child_revision in renkan_revisions: - child_revision.delete() - super(Renkan, self).delete() - + + #TODO: + # @transaction.atomic + # def delete(self): + # """ + # Deleting a renkan also deletes every related revision + # """ + # renkan_revisions = Revision.objects.filter(parent_renkan__renkan_guid = self.renkan_guid) + # for child_revision in renkan_revisions: + # child_revision.delete() + # super(Renkan, self).delete() + class Meta: app_label = 'renkanmanager' permissions = ( ('view_renkan', 'Can view renkan'), ) - + class Revision(models.Model): - - revision_guid = models.CharField(max_length=256, default=uuid.uuid4, unique=True) # typically UUID - parent_renkan = models.ForeignKey('Renkan', null=False, blank=False, to_field='renkan_guid') + + revision_guid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, blank=False, null=False) + parent_renkan_guid = models.CharField(max_length=256) + #parent_renkan = models.ForeignKey('Renkan', null=False, blank=False, to_field='renkan_guid') title = models.CharField(max_length=1024, null=True, blank=True) content = models.TextField(blank=True, null=True) creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="revision_creator") last_updated_by = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="revision_last_updated_by") creation_date = models.DateTimeField(auto_now_add=True) - modification_date = models.DateTimeField() - + modification_date = models.DateTimeField(auto_now=True) + #modification_date = models.DateTimeField() + @property def is_current_revision(self): + try: + parent_project = Renkan.objects.get(renkan_guid=self.parent_renkan_guid) + except Renkan.DoesNotExist: # SHOULD NOT HAPPEN! + raise Http404 + return parent_project.current_revision_guid == self.revision_guid # No need to check if parent_renkan.current_revision is not None, as it won't be if we're calling from a revision - return self.parent_renkan.current_revision.revision_guid == self.revision_guid - + #return self.parent_renkan.current_revision.revision_guid == self.revision_guid + class Meta: app_label = 'renkanmanager' permissions = ( ('view_revision', 'Can view revision'), - ) - + )