models.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. from urllib.parse import urlparse
  2. from uuid import uuid4
  3. from django.conf import settings
  4. from django.db import models
  5. from django.utils.text import slugify
  6. from django_extensions.db.fields import AutoSlugField
  7. from glitchtip.base_models import CreatedModel
  8. class Project(CreatedModel):
  9. """
  10. Projects are permission based namespaces which generally
  11. are the top level entry point for all data.
  12. """
  13. slug = AutoSlugField(populate_from=["name", "organization_id"], max_length=50)
  14. name = models.CharField(max_length=64)
  15. organization = models.ForeignKey(
  16. "organizations_ext.Organization",
  17. on_delete=models.CASCADE,
  18. related_name="projects",
  19. )
  20. platform = models.CharField(max_length=64, blank=True, null=True)
  21. first_event = models.DateTimeField(null=True)
  22. scrub_ip_addresses = models.BooleanField(
  23. default=True,
  24. help_text="Should project anonymize IP Addresses",
  25. )
  26. class Meta:
  27. unique_together = (("organization", "slug"),)
  28. def __str__(self):
  29. return self.name
  30. def save(self, *args, **kwargs):
  31. first = False
  32. if not self.pk:
  33. first = True
  34. super().save(*args, **kwargs)
  35. if first:
  36. ProjectKey.objects.create(project=self)
  37. @property
  38. def should_scrub_ip_addresses(self):
  39. """Organization overrides project setting"""
  40. return self.scrub_ip_addresses or self.organization.scrub_ip_addresses
  41. def slugify_function(self, content):
  42. """
  43. Make the slug the project name. Validate uniqueness with both name and org id.
  44. This works because when it runs on organization_id it returns an empty string.
  45. """
  46. if isinstance(content, str):
  47. return slugify(self.name)
  48. return ""
  49. class ProjectCounter(models.Model):
  50. """
  51. Counter for issue short IDs
  52. - Unique per project
  53. - Autoincrements on each new issue
  54. - Separate table for performance
  55. """
  56. project = models.OneToOneField(Project, on_delete=models.CASCADE)
  57. value = models.PositiveIntegerField()
  58. class ProjectKey(CreatedModel):
  59. """Authentication key for a Project"""
  60. project = models.ForeignKey(Project, on_delete=models.CASCADE)
  61. label = models.CharField(max_length=64, blank=True)
  62. public_key = models.UUIDField(default=uuid4, unique=True, editable=False)
  63. rate_limit_count = models.PositiveSmallIntegerField(blank=True, null=True)
  64. rate_limit_window = models.PositiveSmallIntegerField(blank=True, null=True)
  65. data = models.JSONField(blank=True, null=True)
  66. def __str__(self):
  67. return str(self.public_key)
  68. @classmethod
  69. def from_dsn(cls, dsn: str):
  70. urlparts = urlparse(dsn)
  71. public_key = urlparts.username
  72. project_id = urlparts.path.rsplit("/", 1)[-1]
  73. try:
  74. return ProjectKey.objects.get(public_key=public_key, project=project_id)
  75. except ValueError as err:
  76. # ValueError would come from a non-integer project_id,
  77. # which is obviously a DoesNotExist. We catch and rethrow this
  78. # so anything downstream expecting DoesNotExist works fine
  79. raise ProjectKey.DoesNotExist(
  80. "ProjectKey matching query does not exist."
  81. ) from err
  82. @property
  83. def public_key_hex(self):
  84. """The public key without dashes"""
  85. return self.public_key.hex
  86. def dsn(self):
  87. return self.get_dsn()
  88. def get_dsn(self):
  89. urlparts = settings.GLITCHTIP_URL
  90. # If we do not have a scheme or domain/hostname, dsn is never valid
  91. if not urlparts.netloc or not urlparts.scheme:
  92. return ""
  93. return "%s://%s@%s/%s" % (
  94. urlparts.scheme,
  95. self.public_key_hex,
  96. urlparts.netloc + urlparts.path,
  97. self.project_id,
  98. )
  99. def get_dsn_security(self):
  100. urlparts = settings.GLITCHTIP_URL
  101. if not urlparts.netloc or not urlparts.scheme:
  102. return ""
  103. return "%s://%s/api/%s/security/?glitchtip_key=%s" % (
  104. urlparts.scheme,
  105. urlparts.netloc + urlparts.path,
  106. self.project_id,
  107. self.public_key_hex,
  108. )