models.py 9.0 KB

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