feat: scope project access to Django Admin
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
@@ -60,6 +61,10 @@ RELEASE_CHANNEL_LABELS = {
|
||||
}
|
||||
|
||||
class MainProduct(models.Model):
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True,
|
||||
related_name="created_main_projects", editable=False,
|
||||
)
|
||||
name = models.CharField(max_length=200)
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
short_description = models.CharField(max_length=300)
|
||||
@@ -98,6 +103,10 @@ class MainProduct(models.Model):
|
||||
return reverse("products:main_product_detail", kwargs={"main_slug": self.slug})
|
||||
|
||||
class SubProduct(models.Model):
|
||||
created_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True,
|
||||
related_name="created_sub_projects", editable=False,
|
||||
)
|
||||
main_product = models.ForeignKey(
|
||||
MainProduct,
|
||||
on_delete=models.CASCADE,
|
||||
@@ -148,6 +157,56 @@ class SubProduct(models.Model):
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class ProjectAccessPolicy(models.Model):
|
||||
OWN = "own"
|
||||
NONE = "none"
|
||||
ALL_VIEW = "all_view"
|
||||
ALL_EDIT = "all_edit"
|
||||
MODE_CHOICES = [
|
||||
(OWN, "Own projects only"),
|
||||
(NONE, "No projects by default"),
|
||||
(ALL_VIEW, "View all projects"),
|
||||
(ALL_EDIT, "View and edit all projects"),
|
||||
]
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="project_access_policy")
|
||||
mode = models.CharField(max_length=12, choices=MODE_CHOICES, default=OWN)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user}: {self.get_mode_display()}"
|
||||
|
||||
|
||||
class ProjectAccess(models.Model):
|
||||
NONE = "none"
|
||||
VIEW = "view"
|
||||
EDIT = "edit"
|
||||
LEVEL_CHOICES = [(NONE, "No access"), (VIEW, "View only"), (EDIT, "View and edit")]
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="project_access_grants")
|
||||
main_product = models.ForeignKey(MainProduct, on_delete=models.CASCADE, null=True, blank=True, related_name="access_grants")
|
||||
sub_product = models.ForeignKey(SubProduct, on_delete=models.CASCADE, null=True, blank=True, related_name="access_grants")
|
||||
level = models.CharField(max_length=4, choices=LEVEL_CHOICES, default=VIEW)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Project access override"
|
||||
verbose_name_plural = "Project access overrides"
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
check=(models.Q(main_product__isnull=False, sub_product__isnull=True)
|
||||
| models.Q(main_product__isnull=True, sub_product__isnull=False)),
|
||||
name="project_access_exactly_one_project",
|
||||
),
|
||||
models.UniqueConstraint(fields=["user", "main_product"], condition=models.Q(main_product__isnull=False), name="unique_user_main_project_access"),
|
||||
models.UniqueConstraint(fields=["user", "sub_product"], condition=models.Q(sub_product__isnull=False), name="unique_user_sub_project_access"),
|
||||
]
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if (self.main_product_id is None) == (self.sub_product_id is None):
|
||||
raise ValidationError("Select exactly one main project or sub-project.")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user} → {self.main_product or self.sub_product}: {self.get_level_display()}"
|
||||
|
||||
class Article(models.Model):
|
||||
main_product = models.ForeignKey(
|
||||
MainProduct,
|
||||
|
||||
Reference in New Issue
Block a user