123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- from django import template
- from django.db import models
- from django.urls import reverse
- from django.contrib.auth.models import User
- from django.db.models.signals import pre_save, m2m_changed
- from django.dispatch import receiver
- '''
- TODO: nullable owner field, blank admins & users - this is for a 'blank' project that 'blank' canvasses use, which the trial user
- uses. The selected trial canvas is currently downloaded as any other canvas, and a new idea is created like other ones, but the idea
- 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
- 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
- This approach was written as a 'quick-fix', a better solution should be investigated.
- '''
- class Project(models.Model):
- '''
- Project - a collection of (ideally) related canvasses which share users, admins and tags
- '''
- title = models.CharField(max_length=25, db_index=True)
- date_created = models.DateTimeField(auto_now_add=True, db_index=True)
- date_modified = models.DateTimeField(auto_now=True, db_index=True)
- is_public = models.BooleanField(default=False, db_index=True)
- admins = models.ManyToManyField(User, related_name='admins', blank=True)
- users = models.ManyToManyField(User, related_name='users', blank=True)
- # Owner (creator) for canvas - owner promotes / demotes admins and can delete the canvas
- owner = models.ForeignKey(User, related_name = 'owner', on_delete=models.CASCADE, null=True, blank=True)
- def get_absolute_url(self):
- return reverse('project-detail', args=[self.pk])
- def get_delete_url(self):
- return reverse('delete-project', args=[self.pk])
- @receiver(pre_save, sender=Project)
- def ensure_project_has_atleast_one_admin(sender, instance, **kwargs):
- # TODO: check post_save hooks if you want behaviour to happen AFTER instance is saved
- if instance.pk is not None:
- '''
- The above line is to ensure that it doesn't break when creating a brand new project.
- It throws an error when the project is new; the project has no pk before it is saved, so
- an error is thrown when the m2m field is referenced below
- '''
- if instance.admins.count == 0 and title != 'blank-project':
- raise Exception('Project should have at least one admin.')
- class Canvas(models.Model):
- """Canvas
- A collection of ideas collected into categories"""
- title = models.CharField(max_length=25, db_index=True)
- date_created = models.DateTimeField(auto_now_add=True, db_index=True)
- date_modified = models.DateTimeField(auto_now=True, db_index=True)
- # 0 for Ethics, 1 for Business, 2 for Privacy (TBD)
- # TODO: change canvas_type to strings, update URL parameters to reflect this in routing, views.py, and in the html templates...
- canvas_type = models.PositiveSmallIntegerField(default=0)
- tags = models.ManyToManyField('CanvasTag', related_name='canvas_set', blank=True)
- project = models.ForeignKey(Project, related_name='canvas_set', on_delete=models.CASCADE, default=0)
- def __str__(self):
- return self.title
- def get_absolute_url(self):
- # NOTE: @andrew no need to str(int) here
- return reverse('canvas-detail', args=[self.pk])
- def get_collaborators_url(self):
- return reverse('collaborators', args=[self.pk])
- def get_delete_url(self):
- return reverse('delete-canvas', args=[self.pk])
- # @register.simple_tag
- # def get_remove_collaborator_url(self):
- # return reverse('delete-user', args=[self.pk])
- class Meta:
- # reverse ordered, least recently modified shows up first
- ordering = ('-date_modified',)
- class Idea(models.Model):
- """
- Idea
- A block/post belonging to a category on the Canvas
- """
- title = models.CharField(max_length=50)
- text = models.CharField(max_length=255)
- # Default = 9 for uncategorised
- category = models.PositiveSmallIntegerField(default=9, db_index=True);
- date_created = models.DateTimeField(auto_now_add=True, db_index=True)
- date_modified = models.DateTimeField(auto_now=True, db_index=True)
- canvas = models.ForeignKey('Canvas', on_delete=models.CASCADE)
- # M2M RELATION WITH IDEAS - A TAG CAN EXIST IN MANY IDEAS AND AN IDEA MAY CONTAIN MANY TAGS
- tags = models.ManyToManyField('CanvasTag', related_name='idea_set', blank=True)
- def __str__(self):
- return self.title
- def get_absolute_url(self):
- return reverse('idea-detail', args=[self.pk])
- def get_delete_url(self):
- return reverse('delete-idea', args=[self.pk])
- def get_comments_url(self):
- return reverse('comment-thread', args=[self.pk])
- class Meta:
- ordering = ('-date_modified',)
- class CanvasTag(models.Model):
- """
- Canvas Tag
- Model representing tags that relate ideas,
- many to many relationship (declared in canvas model),
- there may exist many different tags to an idea and vice versa
- """
- # labels should be short and to the point
- label = models.CharField(max_length=25)
- # including date_created and date_modified to help with ordering them
- date_created = models.DateTimeField(auto_now_add=True, db_index=True)
- date_modified = models.DateTimeField(auto_now=True, db_index=True)
- def __str__(self):
- return self.label
- class Meta:
- ordering = ('-date_modified',)
- class IdeaComment(models.Model):
- """
- IdeaComment
- Comments on an idea
- """
- text = models.CharField(max_length=255, help_text="Type a comment")
- resolved = models.BooleanField(default=False)
- user = models.ForeignKey(User, on_delete=models.CASCADE)
- idea = models.ForeignKey(
- 'Idea', null = False,
- on_delete=models.CASCADE,
- related_name='comments',
- db_index=True
- )
- timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
- def __str__(self):
- # @andrew this will return something like
- # "(Resolved) This is a comment - Harsh"
- if self.resolved:
- status = 'Resolved'
- else:
- status = 'Unresolved'
- return f'({status}) {self.text} - {self.user}'
- def get_delete_url(self):
- return reverse('delete-comment', args=[self.pk])
- def get_resolve_url(self):
- return reverse('comment-resolve', args=[self.idea.pk])
- class Meta:
- # comments are ordered by most recent first
- ordering = ('-timestamp',)
|