server/python/django2/renkanmanager/models.py
changeset 609 854a027c80ff
parent 589 0ae11aa255a3
child 610 b9edc1c1538a
equal deleted inserted replaced
608:8fd40139827c 609:854a027c80ff
     2 Created on Jul 17, 2014
     2 Created on Jul 17, 2014
     3 Reworked in December, 2015
     3 Reworked in December, 2015
     4 
     4 
     5 @author: tc, nd
     5 @author: tc, nd
     6 '''
     6 '''
     7 import uuid
     7 import uuid, logging, json, datetime
     8 
       
     9 from django.conf import settings
     8 from django.conf import settings
    10 from django.db import models
     9 from django.db import models, transaction
    11 from django.http import Http404
    10 from django.core.exceptions import ValidationError
    12 
    11 from django.utils import timezone, dateparse
    13 
    12 
       
    13 
       
    14 
       
    15 logger = logging.getLogger(__name__)
    14 auth_user_model = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
    16 auth_user_model = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
    15 
    17 
    16 class Workspace(models.Model):
    18 class Workspace(models.Model):
    17     
    19     
    18     workspace_guid = models.CharField(max_length=1024, default=uuid.uuid4, unique=True, blank=False, null=False) # typically UUID
    20     workspace_guid = models.CharField(max_length=1024, default=uuid.uuid4, unique=True, blank=False, null=False) # typically UUID
    20     creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="workspace_creator")
    22     creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="workspace_creator")
    21     creation_date = models.DateTimeField(auto_now_add=True)
    23     creation_date = models.DateTimeField(auto_now_add=True)
    22     
    24     
    23     @property
    25     @property
    24     def renkan_count(self):
    26     def renkan_count(self):
    25         return Renkan.objects.filter(workspace_guid=self.workspace_guid).count()
    27         return Renkan.objects.filter(workspace__workspace_guid=self.workspace_guid).count()
    26     
    28     
    27     class Meta:
    29     class Meta:
    28         app_label = 'renkanmanager'
    30         app_label = 'renkanmanager'
    29         permissions = (
    31         permissions = (
    30             ('view_workspace', 'Can view workspace'),
    32             ('view_workspace', 'Can view workspace'),
    31         )
    33         )
       
    34         
       
    35 
       
    36 class RenkanManager(models.Manager):
       
    37     
       
    38     @transaction.atomic
       
    39     def create_renkan(self, creator, title='', content='', source_revision=None, workspace = None):
       
    40         new_renkan = Renkan()
       
    41         new_renkan.creator = creator
       
    42         new_renkan_workspace_guid = ""
       
    43         new_renkan_title = title
       
    44         new_renkan_content = content
       
    45         if workspace is not None:
       
    46             new_renkan.workspace = workspace
       
    47             new_renkan_workspace_guid = workspace.workspace_guid
       
    48         if source_revision is not None:
       
    49             new_renkan.source_revision = source_revision
       
    50             if not title:
       
    51                 new_renkan_title = source_revision.title
       
    52             new_renkan_content = source_revision.content
       
    53         new_renkan.save()
       
    54         initial_revision = Revision(parent_renkan=new_renkan)
       
    55         initial_revision.modification_date = timezone.now()
       
    56         initial_revision.creator = creator
       
    57         initial_revision.last_updated_by = creator
       
    58         initial_revision.save() # saving once to set the creation date as we need it to fill the json content
       
    59         if initial_revision.modification_date != initial_revision.creation_date:
       
    60             initial_revision.modification_date = initial_revision.creation_date
       
    61         initial_revision.title = new_renkan_title if new_renkan_title else "Untitled Renkan"
       
    62         if new_renkan_content:
       
    63             try:
       
    64                 new_renkan_content_dict = json.loads(new_renkan_content)
       
    65             except ValueError:
       
    66                 raise ValidationError("Provided content to create Renkan is not a JSON-serializable")
       
    67             new_renkan_content_dict["created"] = str(initial_revision.creation_date)
       
    68             new_renkan_content_dict["updated"] = str(initial_revision.modification_date)
       
    69         else: 
       
    70             new_renkan_content_dict = {
       
    71                 "id": str(new_renkan.renkan_guid),
       
    72                 "title": initial_revision.title,
       
    73                 "description": "",
       
    74                 "created": str(initial_revision.creation_date),
       
    75                 "updated": str(initial_revision.modification_date),
       
    76                 "edges": [],
       
    77                 "nodes": [],
       
    78                 "users": [],
       
    79                 "space_id": new_renkan_workspace_guid,
       
    80                 "views": []
       
    81             }
       
    82         initial_revision.content = json.dumps(new_renkan_content_dict)
       
    83         initial_revision.save()
       
    84         return new_renkan
       
    85 
    32 
    86 
    33 class Renkan(models.Model):
    87 class Renkan(models.Model):
    34     
    88     
    35     renkan_guid = models.CharField(max_length=256, default=uuid.uuid4, unique=True, blank=False, null=False) # typically UUID
    89     renkan_guid = models.CharField(max_length=256, default=uuid.uuid4, unique=True, blank=False, null=False) # typically UUID
    36     workspace_guid = models.CharField(max_length=256, blank=True, null=True)
    90     workspace = models.ForeignKey('Workspace', null=True, blank=True, to_field='workspace_guid')
    37     current_revision_guid = models.CharField(max_length=256, blank=True, null=True)
    91     source_revision = models.ForeignKey('Revision', null=True, blank=True, related_name="renkan_source_revision", to_field='revision_guid', on_delete=models.SET_NULL)
    38     source_revision_guid = models.CharField(max_length=256, blank=True, null=True)
       
    39     creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="renkan_creator")
    92     creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="renkan_creator")
    40     creation_date = models.DateTimeField(auto_now_add=True)
    93     creation_date = models.DateTimeField(auto_now_add=True)
    41     state = models.IntegerField(default=1)
    94     state = models.IntegerField(default=1)
    42     
    95     
       
    96     objects = RenkanManager()
       
    97     
    43     @property
    98     @property
    44     def revision_count(self):
    99     def revision_count(self):
    45         return Revision.objects.filter(parent_renkan_guid=self.renkan_guid).count()
   100         return Revision.objects.filter(parent_renkan__renkan_guid=self.renkan_guid).count()
    46     
   101     
    47     @property
   102     @property
    48     def is_copy(self):
   103     def is_copy(self):
    49         return bool(self.source_revision_guid)
   104         return bool(self.source_revision)
       
   105     
       
   106     # Current revision object or None if there is none
       
   107     @property
       
   108     def current_revision(self):
       
   109         return Revision.objects.filter(parent_renkan__renkan_guid=self.renkan_guid).order_by('-creation_date').first()
    50     
   110     
    51     # Current revision title
   111     # Current revision title
    52     @property
   112     @property
    53     def title(self):
   113     def title(self):
    54         current_revision = Revision.objects.get(revision_guid = self.current_revision_guid)
   114         if self.current_revision:
    55         return current_revision.title
   115             return self.current_revision.title
       
   116         else:
       
   117             return ''
    56     
   118     
    57     # Current revision content
   119     # Current revision content
    58     @property
   120     @property
    59     def content(self):
   121     def content(self):
    60         current_revision = Revision.objects.get(revision_guid = self.current_revision_guid)
   122         if self.current_revision:
    61         return current_revision.content
   123             return self.current_revision.content
    62     
   124         else:
       
   125             return ''
       
   126     
       
   127     @transaction.atomic
       
   128     def save_renkan(self, updator, timestamp="", title="", content="", create_new_revision=False):
       
   129         """
       
   130             Saves over current revision or saves a new revision entirely. 
       
   131             Timestamp must be the current revision modification_date.
       
   132         """
       
   133         if (not timestamp) or ((self.current_revision is not None) and dateparse.parse_datetime(timestamp) < self.current_revision.modification_date):
       
   134             logger.error("SAVING RENKAN: provided timestamp is %r, which isn't current revision modification_date %r", timestamp, self.current_revision.modification_date)
       
   135             raise ValidationError(message="Error saving Renkan: provided timestamp isn't current revision modification_date")
       
   136         else:
       
   137             dt_timestamp = dateparse.parse_datetime(timestamp)
       
   138         self.save()
       
   139         if create_new_revision:
       
   140             revision_to_update = Revision(parent_renkan=self)
       
   141             revision_to_update.creator = updator
       
   142         else:
       
   143             revision_to_update = Revision.objects.select_for_update().get(revision_guid=self.current_revision.revision_guid)
       
   144         
       
   145         updated_content = content if content else current_revision.content
       
   146         try: 
       
   147             updated_content_dict = json.loads(updated_content)
       
   148         except ValueError:
       
   149             raise ValidationError(message="Provided content for Renkan is not a JSON-serializable")
       
   150         
       
   151         # If title is passed as arg to the method, update the title in the json
       
   152         if title:
       
   153             updated_title = title
       
   154             updated_content_dict["title"] = title
       
   155         # If it is not, we use the one in the json instead
       
   156         else:
       
   157             updated_title = updated_content_dict["title"] 
       
   158         
       
   159         revision_to_update.modification_date = timezone.now()
       
   160         updated_content_dict["updated"] = str(revision_to_update.modification_date)
       
   161         updated_content = json.dumps(updated_content_dict)  
       
   162         revision_to_update.title = updated_title
       
   163         revision_to_update.content = updated_content
       
   164         if dt_timestamp == revision_to_update.modification_date:
       
   165             revision_to_update.modification_date += datetime.resolution
       
   166         revision_to_update.last_updated_by = updator
       
   167         revision_to_update.save()
       
   168     
       
   169     @transaction.atomic
       
   170     def delete(self):
       
   171         """
       
   172             Deleting a renkan also deletes every related revision
       
   173         """
       
   174         renkan_revisions = Revision.objects.filter(parent_renkan__renkan_guid = self.renkan_guid)
       
   175         for child_revision in renkan_revisions:
       
   176             child_revision.delete()
       
   177         super(Renkan, self).delete()
       
   178         
    63     class Meta:
   179     class Meta:
    64         app_label = 'renkanmanager'
   180         app_label = 'renkanmanager'
    65         permissions = (
   181         permissions = (
    66             ('view_renkan', 'Can view renkan'),
   182             ('view_renkan', 'Can view renkan'),
    67         )
   183         )
    68 
   184 
       
   185         
    69 class Revision(models.Model):
   186 class Revision(models.Model):
    70     
   187     
    71     revision_guid = models.CharField(max_length=256, default=uuid.uuid4, unique=True) # typically UUID
   188     revision_guid = models.CharField(max_length=256, default=uuid.uuid4, unique=True) # typically UUID
    72     parent_renkan_guid = models.CharField(max_length=256)
   189     parent_renkan = models.ForeignKey('Renkan', null=False, blank=False, to_field='renkan_guid')
    73     title = models.CharField(max_length=1024, null=True, blank=True)
   190     title = models.CharField(max_length=1024, null=True, blank=True)
    74     content = models.TextField(blank=True, null=True)
   191     content = models.TextField(blank=True, null=True)
    75     creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="revision_creator")
   192     creator = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="revision_creator")
    76     last_updated_by = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="revision_last_updated_by")
   193     last_updated_by = models.ForeignKey(auth_user_model, blank=True, null=True, related_name="revision_last_updated_by")
    77     creation_date = models.DateTimeField(auto_now_add=True)
   194     creation_date = models.DateTimeField(auto_now_add=True)
    78     modification_date = models.DateTimeField(auto_now=True)
   195     modification_date = models.DateTimeField()
    79     
   196     
    80     @property
   197     @property
    81     def is_current_revision(self):
   198     def is_current_revision(self):
    82         try:
   199         # No need to check if parent_renkan.current_revision is not None, as it won't be if we're calling from a revision
    83             parent_project = Renkan.objects.get(renkan_guid=self.parent_renkan_guid)
   200         return self.parent_renkan.current_revision.revision_guid == self.revision_guid
    84         except Renkan.DoesNotExist: # SHOULD NOT HAPPEN!
       
    85             raise Http404
       
    86         return parent_project.current_revision_guid == self.revision_guid
       
    87     
   201     
    88     class Meta:
   202     class Meta:
    89         app_label = 'renkanmanager'
   203         app_label = 'renkanmanager'
    90         permissions = (
   204         permissions = (
    91             ('view_revision', 'Can view revision'),
   205             ('view_revision', 'Can view revision'),