--- a/server/python/django2/renkanmanager/models.py Mon Apr 11 16:28:05 2016 +0200
+++ b/server/python/django2/renkanmanager/models.py Wed Apr 27 16:36:30 2016 +0200
@@ -4,13 +4,15 @@
@author: tc, nd
'''
-import uuid
-
+import uuid, logging, json, datetime
from django.conf import settings
-from django.db import models
-from django.http import Http404
+from django.db import models, transaction
+from django.core.exceptions import ValidationError
+from django.utils import timezone, dateparse
+
+logger = logging.getLogger(__name__)
auth_user_model = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
class Workspace(models.Model):
@@ -22,68 +24,180 @@
@property
def renkan_count(self):
- return Renkan.objects.filter(workspace_guid=self.workspace_guid).count()
+ return Renkan.objects.filter(workspace__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
+ new_renkan_workspace_guid = ""
+ new_renkan_title = title
+ new_renkan_content = content
+ if workspace is not None:
+ new_renkan.workspace = workspace
+ new_renkan_workspace_guid = workspace.workspace_guid
+ if source_revision is not None:
+ new_renkan.source_revision = source_revision
+ if not title:
+ new_renkan_title = source_revision.title
+ new_renkan_content = source_revision.content
+ new_renkan.save()
+ initial_revision = Revision(parent_renkan=new_renkan)
+ initial_revision.modification_date = timezone.now()
+ 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"
+ if new_renkan_content:
+ try:
+ new_renkan_content_dict = json.loads(new_renkan_content)
+ except ValueError:
+ raise ValidationError("Provided content to create Renkan is not a JSON-serializable")
+ new_renkan_content_dict["created"] = str(initial_revision.creation_date)
+ new_renkan_content_dict["updated"] = str(initial_revision.modification_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),
+ "edges": [],
+ "nodes": [],
+ "users": [],
+ "space_id": new_renkan_workspace_guid,
+ "views": []
+ }
+ 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_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('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)
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_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_guid)
+ return bool(self.source_revision)
+
+ # 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()
# Current revision title
@property
def title(self):
- current_revision = Revision.objects.get(revision_guid = self.current_revision_guid)
- return current_revision.title
+ if self.current_revision:
+ return self.current_revision.title
+ else:
+ return ''
# Current revision content
@property
def content(self):
- 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 ''
+ @transaction.atomic
+ def save_renkan(self, updator, timestamp="", title="", content="", create_new_revision=False):
+ """
+ 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):
+ logger.error("SAVING RENKAN: provided timestamp is %r, which isn't current revision modification_date %r", timestamp, self.current_revision.modification_date)
+ raise ValidationError(message="Error saving Renkan: provided timestamp isn't current revision modification_date")
+ else:
+ dt_timestamp = dateparse.parse_datetime(timestamp)
+ self.save()
+ 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)
+
+ updated_content = content if content else current_revision.content
+ try:
+ updated_content_dict = json.loads(updated_content)
+ except ValueError:
+ raise ValidationError(message="Provided content for Renkan is not a JSON-serializable")
+
+ # 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"]
+
+ revision_to_update.modification_date = timezone.now()
+ updated_content_dict["updated"] = str(revision_to_update.modification_date)
+ 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()
+
+ @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_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(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
class Meta:
app_label = 'renkanmanager'