models.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. from django.conf import settings
  2. from django.core.validators import MaxValueValidator
  3. from django.db import models
  4. from django.db.models import F, OuterRef, Q
  5. from django.db.models.functions import Coalesce
  6. from django.utils.text import slugify
  7. from django.utils.translation import gettext_lazy as _
  8. from organizations.abstract import SharedBaseModel
  9. from organizations.base import (
  10. OrganizationBase,
  11. OrganizationInvitationBase,
  12. OrganizationOwnerBase,
  13. OrganizationUserBase,
  14. )
  15. from organizations.managers import OrgManager
  16. from organizations.signals import owner_changed, user_added
  17. from sql_util.utils import SubqueryCount, SubquerySum
  18. from apps.observability.metrics import clear_metrics_cache
  19. from .constants import OrganizationUserRole
  20. from .fields import OrganizationSlugField
  21. class OrganizationManager(OrgManager):
  22. def with_event_counts(self, current_period=True):
  23. subscription_filter = Q()
  24. event_subscription_filter = Q()
  25. checks_subscription_filter = Q()
  26. if current_period and settings.BILLING_ENABLED:
  27. subscription_filter = Q(
  28. created__gte=OuterRef(
  29. "djstripe_customers__subscriptions__current_period_start"
  30. ),
  31. created__lt=OuterRef(
  32. "djstripe_customers__subscriptions__current_period_end"
  33. ),
  34. )
  35. event_subscription_filter = Q(
  36. date__gte=OuterRef(
  37. "djstripe_customers__subscriptions__current_period_start"
  38. ),
  39. date__lt=OuterRef(
  40. "djstripe_customers__subscriptions__current_period_end"
  41. ),
  42. )
  43. checks_subscription_filter = Q(
  44. start_check__gte=OuterRef(
  45. "djstripe_customers__subscriptions__current_period_start"
  46. ),
  47. start_check__lt=OuterRef(
  48. "djstripe_customers__subscriptions__current_period_end"
  49. ),
  50. )
  51. queryset = self.annotate(
  52. issue_event_count=Coalesce(
  53. SubquerySum(
  54. "projects__issueeventprojecthourlystatistic__count",
  55. filter=event_subscription_filter,
  56. ),
  57. 0,
  58. ),
  59. transaction_count=Coalesce(
  60. SubquerySum(
  61. "projects__transactioneventprojecthourlystatistic__count",
  62. filter=event_subscription_filter,
  63. ),
  64. 0,
  65. ),
  66. uptime_check_event_count=SubqueryCount(
  67. "monitor__checks", filter=checks_subscription_filter
  68. ),
  69. file_size=(
  70. Coalesce(
  71. SubquerySum(
  72. "release__releasefile__file__blob__size",
  73. filter=subscription_filter,
  74. ),
  75. 0,
  76. )
  77. + Coalesce(
  78. SubquerySum(
  79. "projects__debuginformationfile__file__blob__size",
  80. filter=subscription_filter,
  81. ),
  82. 0,
  83. )
  84. )
  85. / 1000000,
  86. total_event_count=F("issue_event_count")
  87. + F("transaction_count")
  88. + F("uptime_check_event_count")
  89. + F("file_size"),
  90. )
  91. return queryset.distinct("pk")
  92. class Organization(SharedBaseModel, OrganizationBase):
  93. slug = OrganizationSlugField(
  94. max_length=200,
  95. blank=False,
  96. editable=True,
  97. populate_from="name",
  98. unique=True,
  99. help_text=_("The name in all lowercase, suitable for URL identification"),
  100. )
  101. is_accepting_events = models.BooleanField(
  102. default=True, help_text="Used for throttling at org level"
  103. )
  104. event_throttle_rate = models.PositiveSmallIntegerField(
  105. default=0,
  106. validators=[MaxValueValidator(100)],
  107. help_text="Probability (in percent) on how many events are throttled. Used for throttling at project level",
  108. )
  109. open_membership = models.BooleanField(
  110. default=True, help_text="Allow any organization member to join any team"
  111. )
  112. scrub_ip_addresses = models.BooleanField(
  113. default=True,
  114. help_text="Default for whether projects should script IP Addresses",
  115. )
  116. objects = OrganizationManager()
  117. def save(self, *args, **kwargs):
  118. new = False
  119. if not self.pk:
  120. new = True
  121. super().save(*args, **kwargs)
  122. if new:
  123. clear_metrics_cache()
  124. def delete(self, *args, **kwargs):
  125. super().delete(*args, **kwargs)
  126. clear_metrics_cache()
  127. def slugify_function(self, content):
  128. reserved_words = [
  129. "login",
  130. "register",
  131. "app",
  132. "profile",
  133. "organizations",
  134. "settings",
  135. "issues",
  136. "performance",
  137. "_health",
  138. "rest-auth",
  139. "api",
  140. "accept",
  141. "stripe",
  142. "admin",
  143. "status_page",
  144. "__debug__",
  145. ]
  146. slug = slugify(content)
  147. if slug in reserved_words:
  148. return slug + "-1"
  149. return slug
  150. def add_user(self, user, role=OrganizationUserRole.MEMBER):
  151. """
  152. Adds a new user and if the first user makes the user an admin and
  153. the owner.
  154. """
  155. users_count = self.users.all().count()
  156. if users_count == 0:
  157. role = OrganizationUserRole.OWNER
  158. org_user = self._org_user_model.objects.create(
  159. user=user, organization=self, role=role
  160. )
  161. if users_count == 0:
  162. self._org_owner_model.objects.create(
  163. organization=self, organization_user=org_user
  164. )
  165. # User added signal
  166. user_added.send(sender=self, user=user)
  167. return org_user
  168. @property
  169. def owners(self):
  170. return self.users.filter(
  171. organizations_ext_organizationuser__role=OrganizationUserRole.OWNER
  172. )
  173. @property
  174. def email(self):
  175. """Used to identify billing contact for stripe."""
  176. billing_contact = self.owner.organization_user.user
  177. return billing_contact.email
  178. def get_user_scopes(self, user):
  179. org_user = self.organization_users.get(user=user)
  180. return org_user.get_scopes()
  181. def change_owner(self, new_owner):
  182. """
  183. Changes ownership of an organization.
  184. """
  185. old_owner = self.owner.organization_user
  186. self.owner.organization_user = new_owner
  187. self.owner.save()
  188. owner_changed.send(sender=self, old=old_owner, new=new_owner)
  189. def is_owner(self, user):
  190. """
  191. Returns True is user is the organization's owner, otherwise false
  192. """
  193. return self.owner.organization_user.user == user
  194. class OrganizationUser(SharedBaseModel, OrganizationUserBase):
  195. user = models.ForeignKey(
  196. "users.User",
  197. blank=True,
  198. null=True,
  199. on_delete=models.CASCADE,
  200. related_name="organizations_ext_organizationuser",
  201. )
  202. role = models.PositiveSmallIntegerField(choices=OrganizationUserRole.choices)
  203. email = models.EmailField(
  204. blank=True, null=True, help_text="Email for pending invite"
  205. )
  206. class Meta(OrganizationOwnerBase.Meta):
  207. unique_together = (("user", "organization"), ("email", "organization"))
  208. def __str__(self, *args, **kwargs):
  209. if self.user:
  210. return super().__str__(*args, **kwargs)
  211. return self.email
  212. def get_email(self):
  213. if self.user:
  214. return self.user.email
  215. return self.email
  216. def get_role(self):
  217. return self.get_role_display().lower()
  218. def get_scopes(self):
  219. role = OrganizationUserRole.get_role(self.role)
  220. return role["scopes"]
  221. @property
  222. def pending(self):
  223. return self.user_id is None
  224. @property
  225. def is_active(self):
  226. """Non pending means active"""
  227. return not self.pending
  228. class OrganizationOwner(OrganizationOwnerBase):
  229. """Only usage is for billing contact currently"""
  230. class OrganizationInvitation(OrganizationInvitationBase):
  231. """Required to exist for django-organizations"""