123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- from django.conf import settings
- from django.core.validators import MaxValueValidator
- from django.db import models
- from django.db.models import F, OuterRef, Q
- from django.db.models.functions import Coalesce
- from django.utils.text import slugify
- from django.utils.translation import gettext_lazy as _
- from organizations.abstract import SharedBaseModel
- from organizations.base import (
- OrganizationBase,
- OrganizationInvitationBase,
- OrganizationOwnerBase,
- OrganizationUserBase,
- )
- from organizations.managers import OrgManager
- from organizations.signals import owner_changed, user_added
- from sql_util.utils import SubqueryCount, SubquerySum
- from apps.observability.metrics import clear_metrics_cache
- from .constants import OrganizationUserRole
- from .fields import OrganizationSlugField
- class OrganizationManager(OrgManager):
- def with_event_counts(self, current_period=True):
- subscription_filter = Q()
- event_subscription_filter = Q()
- checks_subscription_filter = Q()
- if current_period and settings.BILLING_ENABLED:
- subscription_filter = Q(
- created__gte=OuterRef(
- "djstripe_customers__subscriptions__current_period_start"
- ),
- created__lt=OuterRef(
- "djstripe_customers__subscriptions__current_period_end"
- ),
- )
- event_subscription_filter = Q(
- date__gte=OuterRef(
- "djstripe_customers__subscriptions__current_period_start"
- ),
- date__lt=OuterRef(
- "djstripe_customers__subscriptions__current_period_end"
- ),
- )
- checks_subscription_filter = Q(
- start_check__gte=OuterRef(
- "djstripe_customers__subscriptions__current_period_start"
- ),
- start_check__lt=OuterRef(
- "djstripe_customers__subscriptions__current_period_end"
- ),
- )
- queryset = self.annotate(
- issue_event_count=Coalesce(
- SubquerySum(
- "projects__issueeventprojecthourlystatistic__count",
- filter=event_subscription_filter,
- ),
- 0,
- ),
- transaction_count=Coalesce(
- SubquerySum(
- "projects__transactioneventprojecthourlystatistic__count",
- filter=event_subscription_filter,
- ),
- 0,
- ),
- uptime_check_event_count=SubqueryCount(
- "monitor__checks", filter=checks_subscription_filter
- ),
- file_size=(
- Coalesce(
- SubquerySum(
- "release__releasefile__file__blob__size",
- filter=subscription_filter,
- ),
- 0,
- )
- + Coalesce(
- SubquerySum(
- "projects__debuginformationfile__file__blob__size",
- filter=subscription_filter,
- ),
- 0,
- )
- )
- / 1000000,
- total_event_count=F("issue_event_count")
- + F("transaction_count")
- + F("uptime_check_event_count")
- + F("file_size"),
- )
- return queryset.distinct("pk")
- class Organization(SharedBaseModel, OrganizationBase):
- slug = OrganizationSlugField(
- max_length=200,
- blank=False,
- editable=True,
- populate_from="name",
- unique=True,
- help_text=_("The name in all lowercase, suitable for URL identification"),
- )
- is_accepting_events = models.BooleanField(
- default=True, help_text="Used for throttling at org level"
- )
- event_throttle_rate = models.PositiveSmallIntegerField(
- default=0,
- validators=[MaxValueValidator(100)],
- help_text="Probability (in percent) on how many events are throttled. Used for throttling at project level",
- )
- open_membership = models.BooleanField(
- default=True, help_text="Allow any organization member to join any team"
- )
- scrub_ip_addresses = models.BooleanField(
- default=True,
- help_text="Default for whether projects should script IP Addresses",
- )
- objects = OrganizationManager()
- def save(self, *args, **kwargs):
- new = False
- if not self.pk:
- new = True
- super().save(*args, **kwargs)
- if new:
- clear_metrics_cache()
- def delete(self, *args, **kwargs):
- super().delete(*args, **kwargs)
- clear_metrics_cache()
- def slugify_function(self, content):
- reserved_words = [
- "login",
- "register",
- "app",
- "profile",
- "organizations",
- "settings",
- "issues",
- "performance",
- "_health",
- "rest-auth",
- "api",
- "accept",
- "stripe",
- "admin",
- "status_page",
- "__debug__",
- ]
- slug = slugify(content)
- if slug in reserved_words:
- return slug + "-1"
- return slug
- def add_user(self, user, role=OrganizationUserRole.MEMBER):
- """
- Adds a new user and if the first user makes the user an admin and
- the owner.
- """
- users_count = self.users.all().count()
- if users_count == 0:
- role = OrganizationUserRole.OWNER
- org_user = self._org_user_model.objects.create(
- user=user, organization=self, role=role
- )
- if users_count == 0:
- self._org_owner_model.objects.create(
- organization=self, organization_user=org_user
- )
- # User added signal
- user_added.send(sender=self, user=user)
- return org_user
- @property
- def owners(self):
- return self.users.filter(
- organizations_ext_organizationuser__role=OrganizationUserRole.OWNER
- )
- @property
- def email(self):
- """Used to identify billing contact for stripe."""
- billing_contact = self.owner.organization_user.user
- return billing_contact.email
- def get_user_scopes(self, user):
- org_user = self.organization_users.get(user=user)
- return org_user.get_scopes()
- def change_owner(self, new_owner):
- """
- Changes ownership of an organization.
- """
- old_owner = self.owner.organization_user
- self.owner.organization_user = new_owner
- self.owner.save()
- owner_changed.send(sender=self, old=old_owner, new=new_owner)
- def is_owner(self, user):
- """
- Returns True is user is the organization's owner, otherwise false
- """
- return self.owner.organization_user.user == user
- class OrganizationUser(SharedBaseModel, OrganizationUserBase):
- user = models.ForeignKey(
- "users.User",
- blank=True,
- null=True,
- on_delete=models.CASCADE,
- related_name="organizations_ext_organizationuser",
- )
- role = models.PositiveSmallIntegerField(choices=OrganizationUserRole.choices)
- email = models.EmailField(
- blank=True, null=True, help_text="Email for pending invite"
- )
- class Meta(OrganizationOwnerBase.Meta):
- unique_together = (("user", "organization"), ("email", "organization"))
- def __str__(self, *args, **kwargs):
- if self.user:
- return super().__str__(*args, **kwargs)
- return self.email
- def get_email(self):
- if self.user:
- return self.user.email
- return self.email
- def get_role(self):
- return self.get_role_display().lower()
- def get_scopes(self):
- role = OrganizationUserRole.get_role(self.role)
- return role["scopes"]
- @property
- def pending(self):
- return self.user_id is None
- @property
- def is_active(self):
- """Non pending means active"""
- return not self.pending
- class OrganizationOwner(OrganizationOwnerBase):
- """Only usage is for billing contact currently"""
- class OrganizationInvitation(OrganizationInvitationBase):
- """Required to exist for django-organizations"""
|