models.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
  2. # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
  3. # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import hashlib
  17. import os
  18. import os.path as path
  19. import random
  20. import re
  21. import uuid
  22. from unidecode import unidecode
  23. from django.db import models
  24. from django.dispatch import receiver
  25. from django.utils.translation import ugettext_lazy as _
  26. from django.contrib.auth.models import UserManager, AbstractBaseUser
  27. from django.core import validators
  28. from django.utils import timezone
  29. from django.utils.encoding import force_bytes
  30. from django.template.defaultfilters import slugify
  31. from django_pgjson.fields import JsonField
  32. from djorm_pgarray.fields import TextArrayField
  33. from taiga.auth.tokens import get_token_for_user
  34. from taiga.base.utils.slug import slugify_uniquely
  35. from taiga.base.utils.iterators import split_by_n
  36. from taiga.permissions.permissions import MEMBERS_PERMISSIONS
  37. def generate_random_hex_color():
  38. return "#{:06x}".format(random.randint(0,0xFFFFFF))
  39. def get_user_file_path(instance, filename):
  40. basename = path.basename(filename).lower()
  41. base, ext = path.splitext(basename)
  42. base = slugify(unidecode(base))
  43. basename = "".join([base, ext])
  44. hs = hashlib.sha256()
  45. hs.update(force_bytes(timezone.now().isoformat()))
  46. hs.update(os.urandom(1024))
  47. p1, p2, p3, p4, *p5 = split_by_n(hs.hexdigest(), 1)
  48. hash_part = path.join(p1, p2, p3, p4, "".join(p5))
  49. return path.join("user", hash_part, basename)
  50. class PermissionsMixin(models.Model):
  51. """
  52. A mixin class that adds the fields and methods necessary to support
  53. Django's Permission model using the ModelBackend.
  54. """
  55. is_superuser = models.BooleanField(_('superuser status'), default=False,
  56. help_text=_('Designates that this user has all permissions without '
  57. 'explicitly assigning them.'))
  58. class Meta:
  59. abstract = True
  60. def has_perm(self, perm, obj=None):
  61. """
  62. Returns True if the user is superadmin and is active
  63. """
  64. return self.is_active and self.is_superuser
  65. def has_perms(self, perm_list, obj=None):
  66. """
  67. Returns True if the user is superadmin and is active
  68. """
  69. return self.is_active and self.is_superuser
  70. def has_module_perms(self, app_label):
  71. """
  72. Returns True if the user is superadmin and is active
  73. """
  74. return self.is_active and self.is_superuser
  75. @property
  76. def is_staff(self):
  77. return self.is_superuser
  78. class User(AbstractBaseUser, PermissionsMixin):
  79. username = models.CharField(_('username'), max_length=255, unique=True,
  80. help_text=_('Required. 30 characters or fewer. Letters, numbers and '
  81. '/./-/_ characters'),
  82. validators=[
  83. validators.RegexValidator(re.compile('^[\w.-]+$'), _('Enter a valid username.'), 'invalid')
  84. ])
  85. email = models.EmailField(_('email address'), max_length=255, blank=True, unique=True)
  86. is_active = models.BooleanField(_('active'), default=True,
  87. help_text=_('Designates whether this user should be treated as '
  88. 'active. Unselect this instead of deleting accounts.'))
  89. full_name = models.CharField(_('full name'), max_length=256, blank=True)
  90. color = models.CharField(max_length=9, null=False, blank=True, default=generate_random_hex_color,
  91. verbose_name=_("color"))
  92. bio = models.TextField(null=False, blank=True, default="", verbose_name=_("biography"))
  93. photo = models.FileField(upload_to=get_user_file_path,
  94. max_length=500, null=True, blank=True,
  95. verbose_name=_("photo"))
  96. date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
  97. lang = models.CharField(max_length=20, null=True, blank=True, default="",
  98. verbose_name=_("default language"))
  99. theme = models.CharField(max_length=100, null=True, blank=True, default="",
  100. verbose_name=_("default theme"))
  101. timezone = models.CharField(max_length=20, null=True, blank=True, default="",
  102. verbose_name=_("default timezone"))
  103. colorize_tags = models.BooleanField(null=False, blank=True, default=False,
  104. verbose_name=_("colorize tags"))
  105. token = models.CharField(max_length=200, null=True, blank=True, default=None,
  106. verbose_name=_("token"))
  107. email_token = models.CharField(max_length=200, null=True, blank=True, default=None,
  108. verbose_name=_("email token"))
  109. new_email = models.EmailField(_('new email address'), null=True, blank=True)
  110. is_system = models.BooleanField(null=False, blank=False, default=False)
  111. USERNAME_FIELD = 'username'
  112. REQUIRED_FIELDS = ['email']
  113. objects = UserManager()
  114. class Meta:
  115. verbose_name = "user"
  116. verbose_name_plural = "users"
  117. ordering = ["username"]
  118. permissions = (
  119. ("view_user", "Can view user"),
  120. )
  121. def __str__(self):
  122. return self.get_full_name()
  123. def get_short_name(self):
  124. "Returns the short name for the user."
  125. return self.username
  126. def get_full_name(self):
  127. return self.full_name or self.username or self.email
  128. def save(self, *args, **kwargs):
  129. get_token_for_user(self, "cancel_account")
  130. super().save(*args, **kwargs)
  131. def cancel(self):
  132. self.username = slugify_uniquely("deleted-user", User, slugfield="username")
  133. self.email = "{}@taiga.io".format(self.username)
  134. self.is_active = False
  135. self.full_name = "Deleted user"
  136. self.color = ""
  137. self.bio = ""
  138. self.lang = ""
  139. self.theme = ""
  140. self.timezone = ""
  141. self.colorize_tags = True
  142. self.token = None
  143. self.set_unusable_password()
  144. self.save()
  145. self.auth_data.all().delete()
  146. class Role(models.Model):
  147. name = models.CharField(max_length=200, null=False, blank=False,
  148. verbose_name=_("name"))
  149. slug = models.SlugField(max_length=250, null=False, blank=True,
  150. verbose_name=_("slug"))
  151. permissions = TextArrayField(blank=True, null=True,
  152. default=[],
  153. verbose_name=_("permissions"),
  154. choices=MEMBERS_PERMISSIONS)
  155. order = models.IntegerField(default=10, null=False, blank=False,
  156. verbose_name=_("order"))
  157. # null=True is for make work django 1.7 migrations. project
  158. # field causes some circular dependencies, and due to this
  159. # it can not be serialized in one transactional migration.
  160. project = models.ForeignKey("projects.Project", null=True, blank=False,
  161. related_name="roles", verbose_name=_("project"))
  162. computable = models.BooleanField(default=True)
  163. def save(self, *args, **kwargs):
  164. if not self.slug:
  165. self.slug = slugify_uniquely(self.name, self.__class__)
  166. super().save(*args, **kwargs)
  167. class Meta:
  168. verbose_name = "role"
  169. verbose_name_plural = "roles"
  170. ordering = ["order", "slug"]
  171. unique_together = (("slug", "project"),)
  172. permissions = (
  173. ("view_role", "Can view role"),
  174. )
  175. def __str__(self):
  176. return self.name
  177. class AuthData(models.Model):
  178. user = models.ForeignKey('users.User', related_name="auth_data")
  179. key = models.SlugField(max_length=50)
  180. value = models.CharField(max_length=300)
  181. extra = JsonField()
  182. class Meta:
  183. unique_together = ["key", "value"]
  184. # On Role object is changed, update all membership
  185. # related to current role.
  186. @receiver(models.signals.post_save, sender=Role,
  187. dispatch_uid="role_post_save")
  188. def role_post_save(sender, instance, created, **kwargs):
  189. # ignore if object is just created
  190. if created:
  191. return
  192. instance.project.update_role_points()