--- 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 '<a href="%s">%s</a>' % (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 '<a href="%s">%s</a>' % (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
--- /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),
+ )
+ ])
+ ]
--- /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;
+$$
--- 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'),
- ),
- ]
--- 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)
- ]
--- 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'),
- ),
- ]
--- 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'),
- )
-
+ )