Skip to content

Commit 3feb4fd

Browse files
committed
Add Orgnization and OrganizationRole models to studio
1 parent e053d82 commit 3feb4fd

2 files changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
ORGANIZATION_ADMIN = "admin"
2+
ORGANIZATION_EDITOR = "editor"
3+
ORGANIZATION_VIEWER = "viewer"
4+
5+
organization_role_choices = (
6+
(ORGANIZATION_ADMIN, "Admin"),
7+
(ORGANIZATION_EDITOR, "Editor"),
8+
(ORGANIZATION_VIEWER, "Viewer"),
9+
)
10+
11+
ORGANIZATION_ROLE_STATUS_ACTIVE = "active"
12+
ORGANIZATION_ROLE_STATUS_INACTIVE = "inactive"
13+
ORGANIZATION_ROLE_STATUS_PENDING = "pending"
14+
ORGANIZATION_ROLE_STATUS_SUSPENDED = "suspended"
15+
ORGANIZATION_ROLE_STATUS_DECLINED = "declined"
16+
17+
organization_role_status_choices = (
18+
(ORGANIZATION_ROLE_STATUS_ACTIVE, "Active"),
19+
(ORGANIZATION_ROLE_STATUS_INACTIVE, "Inactive"),
20+
(ORGANIZATION_ROLE_STATUS_PENDING, "Pending Invitation"),
21+
(ORGANIZATION_ROLE_STATUS_SUSPENDED, "Suspended"),
22+
(ORGANIZATION_ROLE_STATUS_DECLINED, "Declined Invitation"),
23+
)

contentcuration/contentcuration/models.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
from contentcuration.constants import feedback
8080
from contentcuration.constants import user_history
8181
from contentcuration.constants.contentnode import kind_activity_map
82+
from contentcuration.constants.organization_roles import organization_role_choices
83+
from contentcuration.constants.organization_roles import organization_role_status_choices
84+
from contentcuration.constants.organization_roles import ORGANIZATION_ROLE_STATUS_PENDING
8285
from contentcuration.db.models.expressions import Array
8386
from contentcuration.db.models.functions import ArrayRemove
8487
from contentcuration.db.models.functions import Unnest
@@ -1147,6 +1150,14 @@ class Channel(models.Model):
11471150
)
11481151
source_url = models.CharField(max_length=200, blank=True, null=True)
11491152
demo_server_url = models.CharField(max_length=200, blank=True, null=True)
1153+
organization = models.ForeignKey(
1154+
"Organization",
1155+
null=True,
1156+
blank=True,
1157+
related_name="channel_organization",
1158+
on_delete=models.SET_NULL,
1159+
help_text="Organization that this channel belongs to.",
1160+
)
11501161

11511162
# Fields specific to content generated by Ricecooker
11521163
source_id = models.CharField(max_length=200, blank=True, null=True)
@@ -1840,6 +1851,97 @@ def delete(self, *args, **kwargs):
18401851
self.secret_token.delete()
18411852

18421853

1854+
class Organization(models.Model):
1855+
"""
1856+
Represents an organization that manages and owns channels.
1857+
Organizations can have roles defined for users and manage multiple channels.
1858+
"""
1859+
1860+
id = UUIDField(primary_key=True, default=uuid.uuid4)
1861+
name = models.CharField(max_length=200, db_index=True)
1862+
description = models.TextField(blank=True)
1863+
thumbnail = models.TextField(blank=True, null=True)
1864+
thumbnail_encoding = JSONField(default=dict)
1865+
public = models.BooleanField(
1866+
default=False, db_index=True, help_text="Whether organization is publicly visible"
1867+
)
1868+
deleted = models.BooleanField(
1869+
default=False, db_index=True, help_text="Soft delete flag"
1870+
)
1871+
1872+
# Metadata
1873+
created_at = models.DateTimeField(auto_now_add=True)
1874+
updated_at = models.DateTimeField(auto_now=True)
1875+
1876+
objects = CustomManager()
1877+
1878+
class Meta:
1879+
verbose_name = "Organization"
1880+
verbose_name_plural = "Organizations"
1881+
ordering = ["name"]
1882+
1883+
def __str__(self):
1884+
return self.name
1885+
1886+
1887+
class OrganizationRole(models.Model):
1888+
"""
1889+
Through model for the User-Organization relationship.
1890+
Defines the role and membership status of a user within an organization.
1891+
Each user-organization pairing has its own role and metadata.
1892+
"""
1893+
1894+
id = UUIDField(primary_key=True, default=uuid.uuid4)
1895+
1896+
# Foreign Keys
1897+
user = models.ForeignKey(
1898+
settings.AUTH_USER_MODEL,
1899+
on_delete=models.CASCADE,
1900+
related_name="organization_roles",
1901+
help_text="User in the organization",
1902+
)
1903+
organization = models.ForeignKey(
1904+
"Organization",
1905+
on_delete=models.CASCADE,
1906+
related_name="user_roles",
1907+
help_text="Organization the user belongs to",
1908+
)
1909+
1910+
# Role and status fields
1911+
role = models.CharField(
1912+
max_length=100,
1913+
choices=organization_role_choices,
1914+
help_text="The user's role within the organization.",
1915+
)
1916+
description = models.TextField(
1917+
blank=True, help_text="Description of the user's role within the organization"
1918+
)
1919+
status = models.CharField(
1920+
max_length=20,
1921+
choices=organization_role_status_choices,
1922+
default=ORGANIZATION_ROLE_STATUS_PENDING,
1923+
db_index=True,
1924+
help_text="Membership status",
1925+
)
1926+
1927+
# Metadata
1928+
joined_at = models.DateTimeField(
1929+
auto_now_add=True, help_text="Date user joined the organization"
1930+
)
1931+
updated_at = models.DateTimeField(
1932+
auto_now=True, help_text="Last update timestamp"
1933+
)
1934+
1935+
class Meta:
1936+
unique_together = ("user", "organization")
1937+
verbose_name = "Organization Role"
1938+
verbose_name_plural = "Organization Roles"
1939+
ordering = ["-joined_at"]
1940+
1941+
def __str__(self):
1942+
return f"{self.user.email} - {self.organization.name} ({self.role})"
1943+
1944+
18431945
class ContentTag(models.Model):
18441946
id = UUIDField(primary_key=True, default=uuid.uuid4)
18451947
tag_name = models.CharField(max_length=50)

0 commit comments

Comments
 (0)