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'), |