--- a/server/python/django2/renkanmanager/__init__.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/__init__.py Mon Jun 20 14:44:40 2016 +0200
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+default_app_config = 'renkanmanager.apps.RenkanManagerConfig'
+
VERSION = (0, 12, 19, "final", 0)
--- a/server/python/django2/renkanmanager/api/views.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/api/views.py Mon Jun 20 14:44:40 2016 +0200
@@ -10,6 +10,7 @@
import uuid
from django.db import transaction
+from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.http import Http404
from django.http.response import HttpResponse, HttpResponseBadRequest
@@ -41,32 +42,52 @@
def post(self, request, workspace_guid='', format=None):
create_data = {key:request.data[key] for key in request.data.keys()}
- source_renkan_guid = request.GET.get("source_renkan_id", request.data.get("source_renkan_id", None))
- source_revision_guid = request.GET.get("source_revision_id", request.data.get("source_revision_id", None))
+ logger.debug("HELLO %r", create_data)
+
+ source_renkan_guid_str = request.GET.get("source_renkan_id", request.data.get("source_renkan_id", None))
+ source_revision_guid_str = request.GET.get("source_revision_id", request.data.get("source_revision_id", None))
+ try:
+ source_renkan_guid = source_renkan_guid_str and uuid.UUID(source_renkan_guid_str) or None
+ source_revision_guid = source_revision_guid_str and uuid.UUID(source_revision_guid_str) or None
+ except ValueError:
+ return Response({'detail': 'Source renkan guid %s or source revision guid %s not correctly formatted'%(source_revision_guid_str, source_revision_guid_str)}, status=status.HTTP_400_BAD_REQUEST)
+
+ source_revision = None
if source_renkan_guid is not None:
try:
source_renkan=Renkan.objects.get(renkan_guid=source_renkan_guid)
except Renkan.DoesNotExist:
- return Response({'detail': 'Source renkan '+source_renkan_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
- source_revision_guid = source_renkan.current_revision.revision_guid
- if source_revision_guid is not None:
+ return Response({'detail': 'Source renkan %s does not exist'%source_renkan_guid}, status=status.HTTP_404_NOT_FOUND)
+ source_revision = source_renkan.current_revision
+ elif source_revision_guid is not None:
try:
source_revision=Revision.objects.get(revision_guid=source_revision_guid)
except Revision.DoesNotExist:
- return Response({'detail': 'Source revision '+source_revision_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Source revision %s does not exist'%source_revision_guid}, status=status.HTTP_404_NOT_FOUND)
+
+ if source_revision:
create_data["source_revision_id"] = source_revision.revision_guid
create_data["title"] = request.data.get("title", source_revision.title)
create_data["content"] = source_revision.content
- if workspace_guid:
+ logger.debug("SOURCE_REVISION CONTENT %r", create_data["content"])
+
+ try:
+ workspace_guid_uuid = workspace_guid and uuid.UUID(workspace_guid) or None
+ except ValueError:
+ return Response({'detail': 'workspace guid %r not correctly formatted'%workspace_guid}, status=status.HTTP_400_BAD_REQUEST)
+
+ if workspace_guid_uuid:
try:
- workspace = Workspace.objects.get(workspace_guid=workspace_guid)
+ workspace = Workspace.objects.get(workspace_guid=workspace_guid_uuid)
except Workspace.DoesNotExist:
return Response({'detail': 'Workspace '+workspace_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
create_data["workspace_id"] = workspace_guid
serializer = RenkanSerializer(data=create_data)
+ logger.debug("BEFORE SERIALIZER VALID %r", create_data)
if serializer.is_valid():
creator = request.user if request.user and not request.user.is_anonymous() else None
serializer.save(creator=creator)
+ logger.debug("AFTER SAVE SERIALIZER DATA %r", serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, content_type='application/json')
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -79,7 +100,11 @@
queryset = Renkan.objects
def get_object(self, renkan_guid):
- return self.queryset.get(renkan_guid=renkan_guid)
+ try:
+ renkan_uuid = uuid.UUID(renkan_guid)
+ except:
+ raise ValueError('renkan guid %r bad format'%renkan_guid)
+ return self.queryset.get(renkan_guid=renkan_uuid)
def dispatch(self, *args, **kwargs):
return super(RenkanDetail, self).dispatch(*args, **kwargs)
@@ -87,8 +112,11 @@
def get(self, request, renkan_guid, format=None):
try:
renkan = self.get_object(renkan_guid=renkan_guid)
+ except ValueError:
+ return Response({'detail': 'Renkan project %r guid badly formatted'%renkan_guid}, status=status.HTTP_400_BAD_REQUEST)
except Renkan.DoesNotExist:
- return Response({'detail': 'Renkan project '+renkan_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Renkan project %r does not exist'%renkan_guid}, status=status.HTTP_404_NOT_FOUND)
+
self.check_object_permissions(request, renkan)
serializer = RenkanSerializer(renkan)
if {'true': True, 'false': False, "0": False, "1": True}.get(request.GET.get("content_only", "false").lower()):
@@ -98,8 +126,10 @@
def put(self, request, renkan_guid, format=None):
try:
renkan = self.get_object(renkan_guid=renkan_guid)
+ except ValueError:
+ return Response({'detail': 'Renkan project %r guid badly formatted'%renkan_guid}, status=status.HTTP_400_BAD_REQUEST)
except Renkan.DoesNotExist:
- return Response({'detail': 'Renkan project '+renkan_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Renkan project %r does not exist'%renkan_guid}, status=status.HTTP_404_NOT_FOUND)
logger.debug("RENKAN PUT %r : CHECKING OBJECT PERMISSION", renkan_guid)
logger.debug("RENKAN PUT: permission? %r", request.user.has_perm("change_renkan", renkan))
self.check_object_permissions(request, renkan)
@@ -117,6 +147,7 @@
serializer.save(updator=request.user)
if {'true': True, 'false': False, "0": False, "1": True}.get(request.GET.get("content_only", "false").lower()):
return Response(json.loads(serializer.data["content"]), status=status.HTTP_200_OK, content_type='application/json')
+ logger.debug("RENKAN PUT: SERIALIZER DATA %r", serializer.data)
return Response(serializer.data, status=status.HTTP_200_OK, content_type='application/json')
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -124,8 +155,10 @@
def delete(self, request, renkan_guid, format=None):
try:
to_delete_renkan = self.get_object(renkan_guid=renkan_guid)
+ except ValueError:
+ return Response({'detail': 'Renkan project %r guid badly formatted'%renkan_guid}, status=status.HTTP_400_BAD_REQUEST)
except Renkan.DoesNotExist:
- return Response({'detail': 'Renkan project '+renkan_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Renkan project %r does not exist'%renkan_guid}, status=status.HTTP_404_NOT_FOUND)
self.check_object_permissions(request, to_delete_renkan)
to_delete_renkan.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -159,13 +192,22 @@
queryset = Workspace.objects
def get_object(self, workspace_guid):
- return self.queryset.get(workspace_guid=workspace_guid)
+ try:
+ workspace_uuid = uuid.UUID(workspace_guid)
+ except:
+ raise ValueError('workspace guid %r bad format'%workspace_guid)
+
+ return self.queryset.get(workspace_guid=workspace_uuid)
def get(self, request, workspace_guid, format=None):
try:
workspace = self.get_object(workspace_guid=workspace_guid)
+ except ValueError:
+ return Response({'detail': 'Workspace %r guid badly formatted'%workspace_guid}, status=status.HTTP_400_BAD_REQUEST)
except Workspace.DoesNotExist:
- return Response({'detail': 'Workspace '+workspace_guid+' does not exist.'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Workspace %r does not exist.'%workspace_guid}, status=status.HTTP_404_NOT_FOUND)
+
+
self.check_object_permissions(request, workspace)
serializer = WorkspaceSerializer(workspace)
return Response(serializer.data, status=status.HTTP_200_OK, content_type='application/json')
@@ -173,8 +215,11 @@
def put(self, request, workspace_guid, format=None):
try:
workspace = self.get_object(workspace_guid=workspace_guid)
+ except ValueError:
+ return Response({'detail': 'Workspace %r guid badly formatted'%workspace_guid}, status=status.HTTP_400_BAD_REQUEST)
except Workspace.DoesNotExist:
- return Response({'detail': 'Workspace '+workspace_guid+' does not exist.'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Workspace %r does not exist.'%workspace_guid}, status=status.HTTP_404_NOT_FOUND)
+
self.check_object_permissions(request, workspace)
serializer = WorkspaceSerializer(workspace, data=request.data)
if serializer.is_valid():
@@ -185,8 +230,11 @@
def delete(self, request, workspace_guid, format=None):
try:
to_delete_workspace = self.get_object(workspace_guid=workspace_guid)
+ except ValueError:
+ return Response({'detail': 'Workspace %r guid badly formatted'%workspace_guid}, status=status.HTTP_400_BAD_REQUEST)
except Workspace.DoesNotExist:
- return Response({'detail': 'Workspace '+workspace_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ return Response({'detail': 'Workspace %r does not exist.'%workspace_guid}, status=status.HTTP_404_NOT_FOUND)
+
self.check_object_permissions(request, to_delete_workspace)
if to_delete_workspace.renkan_count != 0:
return Response({'detail': 'Workspace '+workspace_guid+' cannot be deleted because it is not empty'}, status=status.HTTP_400_BAD_REQUEST)
@@ -219,31 +267,56 @@
lookup_field = "revision_guid"
def get_queryset(self, renkan_guid=""):
- if renkan_guid:
- return Revision.objects.filter(parent_renkan__renkan_guid=renkan_guid)
+ try:
+ renkan_uuid = renkan_guid and uuid.UUID(renkan_guid)
+ except:
+ raise ValueError("renkan guid %r not correctly formatted"%renkan_guid)
+
+
+ def get_revision(self, renkan_guid, revision_guid):
+ try:
+ renkan_uuid = renkan_guid and uuid.UUID(renkan_guid)
+ except:
+ raise ValueError('Renkan project %r guid bad format'%renkan_guid)
+ try:
+ revision_uuid = revision_guid and uuid.UUID(revision_guid)
+ except:
+ raise ValueError('Revision %r guid bad format'%revision_guid)
+
+ revisions = None
+ if renkan_uuid:
+ revisions = Revision.objects.filter(parent_renkan__renkan_guid=renkan_uuid)
else:
- return Revision.objects
+ revisions = Revision.objects
+ if not revisions:
+ raise ObjectDoesNotExist('Renkan project %r does not exist'%renkan_guid)
+ try:
+ revision = revisions.get(revision_guid=revision_uuid)
+ except Revision.DoesNotExist:
+ raise ObjectDoesNotExist('Revision %r does not exist'%revision_guid)
+ return revisions, revision
def get(self, request, renkan_guid, revision_guid, format=None):
- revisions = self.get_queryset(renkan_guid)
- if not revisions:
- return Response({'detail': 'Renkan project '+renkan_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+
try:
- revision = revisions.get(revision_guid=revision_guid)
- except Revision.DoesNotExist:
- return Response({'detail': 'Revision '+revision_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ _, revision = self.get_revision(renkan_guid, revision_guid)
+ except ValueError as e:
+ return Response({'detail': e.args[0]}, status=status.HTTP_400_BAD_REQUEST)
+ except ObjectDoesNotExist as e:
+ return Response({'detail': e.args[0]}, status=status.HTTP_404_NOT_FOUND)
+
self.check_object_permissions(request, revision)
serializer = RevisionSerializer(revision)
return Response(serializer.data, status=status.HTTP_200_OK, content_type='application/json')
def delete(self, request, renkan_guid, revision_guid, format=None):
- revisions = self.get_queryset(renkan_guid)
- if not revisions:
- return Response({'detail': 'Renkan project '+renkan_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
try:
- revision = revisions.get(revision_guid=revision_guid)
- except Revision.DoesNotExist:
- return Response({'detail': 'Revision '+revision_guid+' does not exist'}, status=status.HTTP_404_NOT_FOUND)
+ revisions, revision = self.get_revision(renkan_guid, revision_guid)
+ except ValueError as e:
+ return Response({'detail': e.args[0]}, status=status.HTTP_400_BAD_REQUEST)
+ except ObjectDoesNotExist as e:
+ return Response({'detail': e.args[0]}, status=status.HTTP_404_NOT_FOUND)
+
self.check_object_permissions(request, revision)
if revisions.count() == 1:
return Response({'detail': 'You cannot delete the last remaining revision of a renkan from the Revision API. Try deleting the parent Renkan with the Renkan API'}, status=status.HTTP_400_BAD_REQUEST)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/apps.py Mon Jun 20 14:44:40 2016 +0200
@@ -0,0 +1,8 @@
+from django.apps import AppConfig
+
+class RenkanManagerConfig(AppConfig):
+ name = 'renkanmanager'
+ verbose_name = "Renkan Manager"
+
+ def ready(self):
+ from renkanmanager import signals
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/migrations/0005_foreign_key_fields_initial.py Mon Jun 20 14:44:40 2016 +0200
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2016-06-16 14:55
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('renkanmanager', '0004_change_guid_field_type'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='revision',
+ options={'ordering': ['-modification_date'], 'permissions': (('view_revision', 'Can view revision'),)},
+ ),
+ migrations.AddField(
+ model_name='renkan',
+ name='current_revision',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='renkanmanager.Revision'),
+ ),
+ 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='copies', to='renkanmanager.Revision'),
+ ),
+ migrations.AddField(
+ model_name='renkan',
+ name='workspace',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='renkanmanager.Workspace'),
+ ),
+ 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'),
+ ),
+ migrations.AlterField(
+ model_name='revision',
+ name='modification_date',
+ field=models.DateTimeField(auto_now_add=True),
+ ),
+ ]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/migrations/0006_foreign_key_fields_migrate_data.py Mon Jun 20 14:44:40 2016 +0200
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2016-06-16 14:57
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+def populate_foreign_keys(apps, schema_editor):
+ Renkan = apps.get_model('renkanmanager', 'Renkan')
+ Revision = apps.get_model('renkanmanager', 'Revision')
+ Workspace = apps.get_model('renkanmanager', 'Workspace')
+
+ for renkan in Renkan.objects.all():
+ renkan.current_revision = Revision.objects.get(revision_guid=renkan.current_revision_guid)
+ if renkan.source_revision_guid:
+ renkan.source_revision = Revision.objects.get(revision_guid=renkan.source_revision_guid)
+ if renkan.workspace_guid:
+ renkan.workspace = Revision.objects.get(revision_guid=renkan.workspace_guid)
+ renkan.save()
+
+ for revision in Revision.objects.all():
+ revision.parent_renkan = Renkan.objects.get(renkan_guid=revision.parent_renkan_guid)
+ revision.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('renkanmanager', '0005_foreign_key_fields_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(populate_foreign_keys)
+ ]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/migrations/0007_foreign_key_final.py Mon Jun 20 14:44:40 2016 +0200
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2016-06-17 09:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('renkanmanager', '0006_foreign_key_fields_migrate_data'),
+ ]
+
+ 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='renkan',
+ name='workspace',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='renkans', to='renkanmanager.Workspace'),
+ ),
+ migrations.AlterField(
+ model_name='revision',
+ name='parent_renkan',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='renkanmanager.Renkan'),
+ ),
+ ]
--- a/server/python/django2/renkanmanager/models.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/models.py Mon Jun 20 14:44:40 2016 +0200
@@ -25,9 +25,7 @@
@property
def renkan_count(self):
- #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()
+ return self.renkans.all().count()
class Meta:
app_label = 'renkanmanager'
@@ -42,8 +40,7 @@
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_workspace_guid = None
new_renkan_title = title
new_renkan_content = content
if workspace is not None:
@@ -55,29 +52,30 @@
new_renkan_title = source_revision.title
new_renkan_content = source_revision.content
new_renkan.save()
+ creation_date = timezone.now()
initial_revision = Revision(parent_renkan=new_renkan)
- initial_revision.modification_date = timezone.now()
+ initial_revision.title = new_renkan_title if new_renkan_title else "Untitled Renkan"
+ initial_revision.creation_date = creation_date
+ initial_revision.modification_date = creation_date
initial_revision.creator = creator
initial_revision.last_updated_by = creator
- initial_revision.save() # saving once to set the creation date as we need it to fill the json content
- if initial_revision.modification_date != initial_revision.creation_date:
- initial_revision.modification_date = initial_revision.creation_date
- initial_revision.title = new_renkan_title if new_renkan_title else "Untitled Renkan"
+ logger.debug("CREATE RENKAN NEW CONTENT %r", new_renkan_content)
if new_renkan_content:
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)
+ logger.debug("CREATE RENKAN NEW CONTENT AFTER VALIDATE %r", new_renkan_content_dict)
+ new_renkan_content_dict["created"] = str(creation_date)
+ new_renkan_content_dict["updated"] = str(creation_date)
else:
new_renkan_content_dict = {
"id": str(new_renkan.renkan_guid),
"title": initial_revision.title,
"description": "",
- "created": str(initial_revision.creation_date),
- "updated": str(initial_revision.modification_date),
+ "created": str(creation_date),
+ "updated": str(creation_date),
"edges": [],
"nodes": [],
"users": [],
- "space_id": new_renkan_workspace_guid,
+ "space_id": str(new_renkan_workspace_guid),
"views": []
}
initial_revision.content = json.dumps(new_renkan_content_dict)
@@ -87,9 +85,9 @@
class Renkan(models.Model):
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)
+ workspace = models.ForeignKey('renkanmanager.Workspace', null=True, blank=True, on_delete=models.CASCADE, related_name="renkans")
+ current_revision = models.ForeignKey('renkanmanager.Revision', null=True, blank=True, on_delete=models.SET_NULL, related_name='+')
+ source_revision = models.ForeignKey('renkanmanager.Revision', null=True, blank=True, on_delete=models.SET_NULL, related_name='copies')
creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="renkan_creator")
creation_date = models.DateTimeField(auto_now_add=True)
@@ -99,44 +97,34 @@
@property
def revision_count(self):
- #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()
+ return self.revisions.all().count()
+
+ @property
+ def workspace_guid(self):
+ return self.workspace and self.workspace.workspace_guid
+
+ @property
+ def current_revision_guid(self):
+ return self.current_revision and self.current_revision.revision_guid
+
+ @property
+ def source_revision_guid(self):
+ return self.source_revision and self.source_revision.revision_guid
+
@property
def is_copy(self):
- #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()
+ return bool(self.source_revision)
# Current revision title
@property
def title(self):
- 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 ''
+ return self.current_revision and self.current_revision.title or ''
# Current revision content
@property
def content(self):
- #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
+ return self.current_revision and self.current_revision.content or ''
def __str__(self):
return self.renkan_guid
@@ -152,12 +140,13 @@
raise ValidationError(_("Cannot save, provided timestamp is invalid"))
else:
dt_timestamp = dateparse.parse_datetime(timestamp)
- self.save()
+ logger.debug("SAVE RENKAN create new revision %r", create_new_revision)
+
if create_new_revision:
revision_to_update = Revision(parent_renkan=self)
revision_to_update.creator = updator
else:
- revision_to_update = Revision.objects.select_for_update().get(revision_guid=self.current_revision.revision_guid)
+ revision_to_update = Revision.objects.select_for_update().get(id=self.current_revision.id)
updated_content = self.validate_json_content(content) if content else current_revision.content
updated_content_dict = json.loads(updated_content)
@@ -179,6 +168,7 @@
revision_to_update.modification_date += datetime.resolution
revision_to_update.last_updated_by = updator
revision_to_update.save()
+ self.save()
def validate_json_content(self, content):
"""
@@ -223,17 +213,6 @@
raise ValidationError("Provided content has an invalid 'users' key: not a list")
return json.dumps(content_to_validate_dict)
- #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 = (
@@ -244,28 +223,28 @@
class Revision(models.Model):
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')
+ parent_renkan = models.ForeignKey('renkanmanager.Renkan', on_delete=models.CASCADE, related_name="revisions")
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(auto_now=True)
- #modification_date = models.DateTimeField()
+ creation_date = models.DateTimeField(auto_now_add=True, editable=False)
+ modification_date = models.DateTimeField()
+
+ @property
+ def parent_renkan_guid(self):
+ return self.parent_renkan and self.parent_renkan.renkan_guid
@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 == self.parent_renkan.current_revision
class Meta:
app_label = 'renkanmanager'
+ ordering = ['-modification_date']
permissions = (
('view_revision', 'Can view revision'),
)
+
+ def __str__(self):
+ return str(self.revision_guid)
--- a/server/python/django2/renkanmanager/serializers.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/serializers.py Mon Jun 20 14:44:40 2016 +0200
@@ -5,11 +5,12 @@
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils import dateparse
+from renkanmanager import settings as renkan_settings
from renkanmanager.models import Renkan, Workspace, Revision
from rest_framework import serializers
logger = logging.getLogger(__name__)
-RENKAN_USER_DISPLAY_FIELD = getattr(settings, "RENKAN_USER_DISPLAY_FIELD", get_user_model().USERNAME_FIELD)
+RENKAN_USER_DISPLAY_FIELD = renkan_settings.RENKAN_USER_DISPLAY_FIELD
class RenkanSerializer(serializers.Serializer):
id = serializers.ReadOnlyField(source="renkan_guid")
@@ -43,6 +44,7 @@
"""
Method to create a new Renkan (and its first revision)
"""
+ logger.debug("RENKANSERIALIZER %r", validated_data)
creator = validated_data.get('creator')
workspace_obj = validated_data.get('workspace', None)
source_revision_obj = validated_data.get('source_revision', None)
@@ -62,6 +64,7 @@
"""
Method to update a Renkan object. Creates a new revision if needed
"""
+ logger.debug('RENKAN SERIALIZER UPDATE %r', validated_data)
updator = validated_data.get('updator')
create_new_revision = validated_data.get("create_new_revision", False)
title = validated_data.get('title', renkan.current_revision.title)
@@ -83,7 +86,9 @@
renkan.save_renkan(updator=updator, timestamp=validation_timestamp, title=title, content=content, create_new_revision=create_new_revision)
except ValidationError as ve:
raise serializers.ValidationError(str(ve.args[0]))
- return renkan
+ # FORCE Renkan reload.
+ # TODO: How to avoid the reload ???
+ return Renkan.objects.get(id=renkan.id)
def validate_workspace_id(self, value):
if self.instance is not None:
@@ -109,9 +114,9 @@
return value
def validate_validation_timestamp(self, value):
- logger.debug("%r < %r", dateparse.parse_datetime(value), self.get_current_revision_modification_date(self.instance))
+ logger.debug("%r < %r", dateparse.parse_datetime(value).timestamp(), self.get_current_revision_modification_date(self.instance).timestamp())
- if self.instance and dateparse.parse_datetime(value) < self.get_current_revision_modification_date(self.instance):
+ if self.instance and dateparse.parse_datetime(value) != self.get_current_revision_modification_date(self.instance):
raise serializers.ValidationError("Invalid timestamp was provided")
return value
--- a/server/python/django2/renkanmanager/settings.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/settings.py Mon Jun 20 14:44:40 2016 +0200
@@ -0,0 +1,6 @@
+# Settings for the renkanmanager project
+from django.conf import settings as django_settings
+from django.contrib.auth import get_user_model
+
+RENKAN_USER_DISPLAY_FIELD = getattr(django_settings, "RENKAN_USER_DISPLAY_FIELD", get_user_model().USERNAME_FIELD)
+RENKAN_AUTO_CURRENT_REVISION = getattr(django_settings, "RENKAN_AUTO_CURRENT_REVISION", True)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/python/django2/renkanmanager/signals.py Mon Jun 20 14:44:40 2016 +0200
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+import logging
+from renkanmanager import settings as renkan_settings
+from renkanmanager.models import Revision
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+
+logger = logging.getLogger(__name__)
+
+
+@receiver(post_save, sender=Revision)
+def revision_post_save(sender, instance, created, raw, using, update_fields, **kwargs):
+ renkan = instance.parent_renkan
+ parent_revisions = renkan.revisions.all()
+ logger.debug("REVISIONS %r, INSTANCE %r, CURRENT %r", parent_revisions, instance, renkan.current_revision)
+
+ if (not renkan.current_revision or renkan_settings.RENKAN_AUTO_CURRENT_REVISION) and parent_revisions.count() >= 1 and renkan.current_revision != parent_revisions[0]:
+ logger.debug("CHANGE CURRENT REVISION OLD: %r , NEW: %r", renkan.current_revision, parent_revisions[0])
+ renkan.current_revision = parent_revisions[0]
+ renkan.save()
--- a/server/python/django2/renkanmanager/tests/v1_0/tests_renkan.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/tests/v1_0/tests_renkan.py Mon Jun 20 14:44:40 2016 +0200
@@ -142,7 +142,7 @@
new_renkan = Renkan.objects.get(renkan_guid=new_renkan_guid)
# GUIDs and username
self.assertEqual(self.test_workspace_guid, post_response_dict.get("workspace_id", ""))
- self.assertEqual(self.test_workspace_guid, new_renkan.workspace.workspace_guid)
+ self.assertEqual(self.test_workspace_guid, str(new_renkan.workspace.workspace_guid))
self.assertEqual(getattr(self.user, User.USERNAME_FIELD), post_response_dict.get("created_by", ""))
self.assertEqual(getattr(self.user, User.USERNAME_FIELD), post_response_dict.get("last_updated_by", ""))
self.assertEqual(getattr(self.user, User.USERNAME_FIELD), getattr(new_renkan.creator, User.USERNAME_FIELD))
@@ -176,8 +176,19 @@
data = {"title": self.third_test_title, "content": self.third_test_content}
post_response = self.client.post(post_url, data, format="json")
+ self.assertEqual(post_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ ###################################################
+ # POSTing with wrong workspace_guid
+ ###################################################
+
+ post_url = reverse("v1.0:renkan_list_workspace", kwargs={"workspace_guid": "00000000-0000-0000-0000-000000000000"})
+ data = {"title": self.third_test_title, "content": self.third_test_content}
+
+ post_response = self.client.post(post_url, data, format="json")
self.assertEqual(post_response.status_code, status.HTTP_404_NOT_FOUND)
+
###################################################
# POSTing with non-JSON serializable content
###################################################
@@ -223,8 +234,17 @@
get_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "bad-id"})
get_response = self.client.get(get_url, format="json")
+ self.assertEqual(get_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ ###################################################
+ # GETting with wrong guid
+ ###################################################
+
+ get_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "00000000-0000-0000-0000-000000000000"})
+ get_response = self.client.get(get_url, format="json")
self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+
def test_get_renkan_list(self):
###################################################
@@ -300,7 +320,7 @@
"create_new_revision": False
}
put_response = self.client.put(put_url, put_data, format="json")
- self.assertEqual(put_response.status_code, status.HTTP_200_OK)
+ self.assertEqual(put_response.status_code, status.HTTP_200_OK, "response is %r"%put_response.content)
put_response_dict = json.loads(put_response.content.decode())
put_response_content_dict = json.loads(put_response_dict.get("content", "{}"))
put_response_ts = put_response_content_dict.get("updated", "")
@@ -311,7 +331,7 @@
self.assertEqual(put_response_dict.get("revision_count", ""), 1)
self.assertEqual(updated_project.revision_count, 1)
- self.assertEqual(revision_guid, updated_project.current_revision.revision_guid)
+ self.assertEqual(revision_guid, str(updated_project.current_revision.revision_guid))
# checking data was updated
# in the reponse
self.assertEqual(put_response_dict.get("title", ""), self.first_test_title)
@@ -339,7 +359,7 @@
"create_new_revision": True
}
put_response = self.client.put(put_url, put_data, format="json")
- self.assertEqual(put_response.status_code, status.HTTP_200_OK)
+ self.assertEqual(put_response.status_code, status.HTTP_200_OK, "response is %r"%put_response.content)
put_response_dict = json.loads(put_response.content.decode())
put_response_content_dict = json.loads(put_response_dict.get("content", "{}"))
put_response_ts = put_response_content_dict.get("updated", "")
@@ -354,7 +374,7 @@
self.assertEqual(updated_project.revision_count, 2)
# checking project now points towards new revision
- self.assertEqual(updated_project.current_revision.revision_guid, created_revision_guid)
+ self.assertEqual(str(updated_project.current_revision.revision_guid), created_revision_guid)
# checking data was updated
# in the reponse
self.assertEqual(put_response_dict.get("title", ""), self.second_test_title)
@@ -387,7 +407,7 @@
put_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "bad-id"})
put_response = self.client.put(put_url, {}, format="json")
- self.assertEqual(put_response.status_code, status.HTTP_404_NOT_FOUND)
+ self.assertEqual(put_response.status_code, status.HTTP_400_BAD_REQUEST)
def test_copy_renkan(self):
@@ -545,15 +565,26 @@
###################################################
bad_copy_data = {"source_revision_id": "bleh_bad_id"}
+ bad_copy_data_guid = {"source_revision_id": "00000000-0000-0000-0000-000000000000"}
+
# with query arg
qarg_bad_copy_url = post_url+"?source_revision_id=bleh_bad_id"
qarg_bad_copy_response = self.client.post(qarg_bad_copy_url, {}, format="json")
+ self.assertEqual(qarg_bad_copy_response.status_code, status.HTTP_400_BAD_REQUEST)
+ # with query arg
+ qarg_bad_copy_url = post_url+"?source_revision_id=00000000-0000-0000-0000-000000000000"
+ qarg_bad_copy_response = self.client.post(qarg_bad_copy_url, {}, format="json")
self.assertEqual(qarg_bad_copy_response.status_code, status.HTTP_404_NOT_FOUND)
# with data
data_bad_copy_url = post_url
data_bad_copy_response = self.client.post(data_bad_copy_url, bad_copy_data, format="json")
+ self.assertEqual(data_bad_copy_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ data_bad_copy_url = post_url
+ data_bad_copy_response = self.client.post(data_bad_copy_url, bad_copy_data_guid, format="json")
self.assertEqual(data_bad_copy_response.status_code, status.HTTP_404_NOT_FOUND)
+
def test_delete_renkan(self):
###################################################
@@ -614,4 +645,12 @@
delete_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "bad-id"})
delete_response = self.client.delete(delete_url, format="json")
+ self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ ###################################################
+ # Try to DELETE renkan with wrong guid
+ ###################################################
+
+ delete_url = reverse("v1.0:renkan_detail", kwargs={"renkan_guid": "00000000-0000-0000-0000-000000000000"})
+ delete_response = self.client.delete(delete_url, format="json")
self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
--- a/server/python/django2/renkanmanager/tests/v1_0/tests_revision.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/tests/v1_0/tests_revision.py Mon Jun 20 14:44:40 2016 +0200
@@ -67,14 +67,32 @@
get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : "bad-renkan-guid", "revision_guid": self.test_initial_revision_guid})
get_response = self.client.get(get_url, {}, format="json")
+ self.assertEqual(get_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ ###################################################
+ # GETting with wrong renkan guid
+ ###################################################
+
+ get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : "00000000-0000-0000-0000-000000000000", "revision_guid": self.test_initial_revision_guid})
+ get_response = self.client.get(get_url, {}, format="json")
self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+
###################################################
# GETting with wrong revision guid
###################################################
get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "bad-revision-guid"})
get_response = self.client.get(get_url, {}, format="json")
+ self.assertEqual(get_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+
+ ###################################################
+ # GETting with wrong revision guid
+ ###################################################
+
+ get_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "00000000-0000-0000-0000-000000000000"})
+ get_response = self.client.get(get_url, {}, format="json")
self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
def test_get_revision_list(self):
@@ -138,8 +156,6 @@
delete_response = self.client.delete(delete_url, {}, format="json")
self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
- # Restoring rightful user
- _ = self.client.login(username="blop", password="blop")
###################################################
# Try to DELETE the initial revision (should 204 now that we added a revision)
@@ -155,7 +171,7 @@
delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : "bad-renkan-guid", "revision_guid": self.test_initial_revision_guid})
delete_response = self.client.delete(delete_url, {}, format="json")
- self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
+ self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
###################################################
# Try to DELETE with wrong revision guid
@@ -163,4 +179,4 @@
delete_url = reverse("v1.0:revision_detail", kwargs={"renkan_guid" : self.test_renkan_guid, "revision_guid": "bad-revision-guid"})
delete_response = self.client.delete(delete_url, {}, format="json")
- self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
+ self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
--- a/server/python/django2/renkanmanager/tests/v1_0/tests_workspace.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/renkanmanager/tests/v1_0/tests_workspace.py Mon Jun 20 14:44:40 2016 +0200
@@ -114,8 +114,17 @@
get_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": "bleh-bad-workspace-id"})
get_response = self.client.get(get_url, format="json")
+ self.assertEqual(get_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ ###################################################
+ # GETting wrong workspace_guid
+ ###################################################
+
+ get_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": "00000000-0000-0000-0000-000000000000"})
+ get_response = self.client.get(get_url, format="json")
self.assertEqual(get_response.status_code, status.HTTP_404_NOT_FOUND)
+
def test_update_workspace(self):
###################################################
@@ -206,4 +215,13 @@
delete_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": "bad-workspace-guid"})
delete_response = self.client.delete(delete_url, format=json)
+ self.assertEqual(delete_response.status_code, status.HTTP_400_BAD_REQUEST)
+
+
+ ###################################################
+ # Try to DELETE workspace with wrong guid
+ ###################################################
+
+ delete_url = reverse("v1.0:workspace_detail", kwargs={"workspace_guid": "00000000-0000-0000-0000-000000000000"})
+ delete_response = self.client.delete(delete_url, format=json)
self.assertEqual(delete_response.status_code, status.HTTP_404_NOT_FOUND)
--- a/server/python/django2/tests/settings.py Wed Jun 15 16:31:43 2016 +0200
+++ b/server/python/django2/tests/settings.py Mon Jun 20 14:44:40 2016 +0200
@@ -12,6 +12,8 @@
},
]
+MIGRATION_MODULES = {"renkanmanager": None}
+
LOGGING = {
'version': 1,
'disable_existing_loggers': False,