models.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. from django import template
  2. from django.db import models
  3. from django.urls import reverse
  4. from django.contrib.auth.models import User
  5. from django.db.models.signals import pre_save, m2m_changed
  6. from django.dispatch import receiver
  7. '''
  8. TODO: nullable owner field, blank admins & users - this is for a 'blank' project that 'blank' canvasses use, which the trial user
  9. uses. The selected trial canvas is currently downloaded as any other canvas, and a new idea is created like other ones, but the idea
  10. is not added to the canvas model in the database. It is just so that a blank idea can be sent back to the trial user and appended
  11. to the list of ideas in the front-end. This is why View functions, when testing for user permissions, also check for project.title == blank-project
  12. This approach was written as a 'quick-fix', a better solution should be investigated.
  13. '''
  14. class Project(models.Model):
  15. '''
  16. Project - a collection of (ideally) related canvasses which share users, admins and tags
  17. '''
  18. title = models.CharField(max_length=25, db_index=True)
  19. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  20. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  21. is_public = models.BooleanField(default=False, db_index=True)
  22. admins = models.ManyToManyField(User, related_name='admins', blank=True)
  23. users = models.ManyToManyField(User, related_name='users', blank=True)
  24. # Owner (creator) for canvas - owner promotes / demotes admins and can delete the canvas
  25. owner = models.ForeignKey(User, related_name = 'owner', on_delete=models.CASCADE, null=True, blank=True)
  26. def get_absolute_url(self):
  27. return reverse('project-detail', args=[self.pk])
  28. def get_delete_url(self):
  29. return reverse('delete-project', args=[self.pk])
  30. @receiver(pre_save, sender=Project)
  31. def ensure_project_has_atleast_one_admin(sender, instance, **kwargs):
  32. # TODO: check post_save hooks if you want behaviour to happen AFTER instance is saved
  33. if instance.pk is not None:
  34. '''
  35. The above line is to ensure that it doesn't break when creating a brand new project.
  36. It throws an error when the project is new; the project has no pk before it is saved, so
  37. an error is thrown when the m2m field is referenced below
  38. '''
  39. if instance.admins.count == 0 and title != 'blank-project':
  40. raise Exception('Project should have at least one admin.')
  41. class Canvas(models.Model):
  42. """Canvas
  43. A collection of ideas collected into categories"""
  44. title = models.CharField(max_length=25, db_index=True)
  45. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  46. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  47. # 0 for Ethics, 1 for Business, 2 for Privacy (TBD)
  48. # TODO: change canvas_type to strings, update URL parameters to reflect this in routing, views.py, and in the html templates...
  49. canvas_type = models.PositiveSmallIntegerField(default=0)
  50. tags = models.ManyToManyField('CanvasTag', related_name='canvas_set', blank=True)
  51. project = models.ForeignKey(Project, related_name='canvas_set', on_delete=models.CASCADE, default=0)
  52. def __str__(self):
  53. return self.title
  54. def get_absolute_url(self):
  55. # NOTE: @andrew no need to str(int) here
  56. return reverse('canvas-detail', args=[self.pk])
  57. def get_collaborators_url(self):
  58. return reverse('collaborators', args=[self.pk])
  59. def get_delete_url(self):
  60. return reverse('delete-canvas', args=[self.pk])
  61. # @register.simple_tag
  62. # def get_remove_collaborator_url(self):
  63. # return reverse('delete-user', args=[self.pk])
  64. class Meta:
  65. # reverse ordered, least recently modified shows up first
  66. ordering = ('-date_modified',)
  67. class Idea(models.Model):
  68. """
  69. Idea
  70. A block/post belonging to a category on the Canvas
  71. """
  72. title = models.CharField(max_length=50)
  73. text = models.CharField(max_length=255)
  74. # Default = 9 for uncategorised
  75. category = models.PositiveSmallIntegerField(default=9, db_index=True);
  76. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  77. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  78. canvas = models.ForeignKey('Canvas', on_delete=models.CASCADE)
  79. # M2M RELATION WITH IDEAS - A TAG CAN EXIST IN MANY IDEAS AND AN IDEA MAY CONTAIN MANY TAGS
  80. tags = models.ManyToManyField('CanvasTag', related_name='idea_set', blank=True)
  81. def __str__(self):
  82. return self.title
  83. def get_absolute_url(self):
  84. return reverse('idea-detail', args=[self.pk])
  85. def get_delete_url(self):
  86. return reverse('delete-idea', args=[self.pk])
  87. def get_comments_url(self):
  88. return reverse('comment-thread', args=[self.pk])
  89. class Meta:
  90. ordering = ('-date_modified',)
  91. class CanvasTag(models.Model):
  92. """
  93. Canvas Tag
  94. Model representing tags that relate ideas,
  95. many to many relationship (declared in canvas model),
  96. there may exist many different tags to an idea and vice versa
  97. """
  98. # labels should be short and to the point
  99. label = models.CharField(max_length=25)
  100. # including date_created and date_modified to help with ordering them
  101. date_created = models.DateTimeField(auto_now_add=True, db_index=True)
  102. date_modified = models.DateTimeField(auto_now=True, db_index=True)
  103. def __str__(self):
  104. return self.label
  105. class Meta:
  106. ordering = ('-date_modified',)
  107. class IdeaComment(models.Model):
  108. """
  109. IdeaComment
  110. Comments on an idea
  111. """
  112. text = models.CharField(max_length=255, help_text="Type a comment")
  113. resolved = models.BooleanField(default=False)
  114. user = models.ForeignKey(User, on_delete=models.CASCADE)
  115. idea = models.ForeignKey(
  116. 'Idea', null = False,
  117. on_delete=models.CASCADE,
  118. related_name='comments',
  119. db_index=True
  120. )
  121. timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
  122. def __str__(self):
  123. # @andrew this will return something like
  124. # "(Resolved) This is a comment - Harsh"
  125. if self.resolved:
  126. status = 'Resolved'
  127. else:
  128. status = 'Unresolved'
  129. return f'({status}) {self.text} - {self.user}'
  130. def get_delete_url(self):
  131. return reverse('delete-comment', args=[self.pk])
  132. def get_resolve_url(self):
  133. return reverse('comment-resolve', args=[self.idea.pk])
  134. class Meta:
  135. # comments are ordered by most recent first
  136. ordering = ('-timestamp',)