Skip to content

Commit b8e8f99

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

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

contentcuration/contentcuration/models.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,6 +1840,172 @@ def delete(self, *args, **kwargs):
18401840
self.secret_token.delete()
18411841

18421842

1843+
class Organization(models.Model):
1844+
"""
1845+
Represents an organization that manages and owns channels.
1846+
Organizations can have roles defined for users and manage multiple channels.
1847+
"""
1848+
1849+
id = UUIDField(primary_key=True, default=uuid.uuid4)
1850+
name = models.CharField(max_length=200, db_index=True)
1851+
description = models.TextField(blank=True)
1852+
tagline = models.CharField(
1853+
max_length=150, blank=True, null=True, help_text="Short description"
1854+
)
1855+
website = models.URLField(max_length=200, blank=True, null=True)
1856+
email = models.EmailField(blank=True, null=True)
1857+
thumbnail = models.TextField(blank=True, null=True)
1858+
thumbnail_encoding = JSONField(default=dict)
1859+
public = models.BooleanField(
1860+
default=False, db_index=True, help_text="Whether organization is publicly visible"
1861+
)
1862+
deleted = models.BooleanField(
1863+
default=False, db_index=True, help_text="Soft delete flag"
1864+
)
1865+
1866+
# Foreign Keys
1867+
channel = models.ForeignKey(
1868+
"Channel",
1869+
null=True,
1870+
blank=True,
1871+
related_name="organization_channel",
1872+
on_delete=models.SET_NULL,
1873+
help_text="Primary channel managed by this organization",
1874+
)
1875+
organization_role = models.ForeignKey(
1876+
"OrganizationRole",
1877+
null=True,
1878+
blank=True,
1879+
related_name="organizations",
1880+
on_delete=models.SET_NULL,
1881+
help_text="Tracking the creator or primary role associated with this organization",
1882+
)
1883+
1884+
# Metadata
1885+
preferences = models.TextField(
1886+
default=DEFAULT_USER_PREFERENCES, help_text="Organization preferences as JSON"
1887+
)
1888+
settings = JSONField(default=dict, help_text="Additional organization settings")
1889+
created_at = models.DateTimeField(auto_now_add=True)
1890+
updated_at = models.DateTimeField(auto_now=True)
1891+
1892+
objects = CustomManager()
1893+
1894+
class Meta:
1895+
verbose_name = "Organization"
1896+
verbose_name_plural = "Organizations"
1897+
ordering = ["name"]
1898+
indexes = [
1899+
models.Index(fields=["name"], name="organization_name_idx"),
1900+
models.Index(
1901+
fields=["deleted", "public"], name="organization_deleted_public_idx"
1902+
),
1903+
models.Index(fields=["created_at"], name="organization_created_at_idx"),
1904+
]
1905+
1906+
def __str__(self):
1907+
return self.name
1908+
1909+
1910+
class OrganizationRole(models.Model):
1911+
"""
1912+
Through model for the User-Organization relationship.
1913+
Defines the role and membership status of a user within an organization.
1914+
Each user-organization pairing has its own role and metadata.
1915+
"""
1916+
1917+
STATUS_CHOICES = [
1918+
("active", "Active"),
1919+
("inactive", "Inactive"),
1920+
("pending", "Pending Invitation"),
1921+
("suspended", "Suspended"),
1922+
("declined", "Declined Invitation"),
1923+
]
1924+
1925+
id = UUIDField(primary_key=True, default=uuid.uuid4)
1926+
1927+
# Foreign Keys
1928+
user = models.ForeignKey(
1929+
settings.AUTH_USER_MODEL,
1930+
on_delete=models.CASCADE,
1931+
related_name="organization_roles",
1932+
help_text="User in the organization",
1933+
)
1934+
organization = models.ForeignKey(
1935+
"Organization",
1936+
on_delete=models.CASCADE,
1937+
related_name="user_roles",
1938+
help_text="Organization the user belongs to",
1939+
)
1940+
1941+
# Role and status fields
1942+
role = models.CharField(
1943+
max_length=100,
1944+
help_text="Role name (e.g., Admin, Editor, Viewer, Content Creator)",
1945+
)
1946+
description = models.TextField(
1947+
blank=True, help_text="Description of the user's role within the organization"
1948+
)
1949+
permissions = JSONField(
1950+
default=dict, help_text="JSON object defining user's permissions"
1951+
)
1952+
status = models.CharField(
1953+
max_length=20,
1954+
choices=STATUS_CHOICES,
1955+
default="pending",
1956+
db_index=True,
1957+
help_text="Membership status",
1958+
)
1959+
1960+
# Metadata
1961+
joined_at = models.DateTimeField(
1962+
auto_now_add=True, help_text="Date user joined the organization"
1963+
)
1964+
updated_at = models.DateTimeField(
1965+
auto_now=True, help_text="Last update timestamp"
1966+
)
1967+
invitation_accepted_at = models.DateTimeField(
1968+
null=True, blank=True, help_text="Date invitation was accepted"
1969+
)
1970+
invited_by = models.ForeignKey(
1971+
settings.AUTH_USER_MODEL,
1972+
null=True,
1973+
blank=True,
1974+
related_name="organization_invitations_sent",
1975+
on_delete=models.SET_NULL,
1976+
help_text="User who invited this user to the organization",
1977+
)
1978+
1979+
class Meta:
1980+
unique_together = ("user", "organization")
1981+
verbose_name = "Organization Role"
1982+
verbose_name_plural = "Organization Roles"
1983+
ordering = ["-joined_at"]
1984+
indexes = [
1985+
models.Index(
1986+
fields=["organization", "status"], name="org_role_org_status_idx"
1987+
),
1988+
models.Index(
1989+
fields=["user", "organization"], name="org_role_unique_idx"
1990+
),
1991+
models.Index(fields=["status"], name="org_role_status_idx"),
1992+
]
1993+
1994+
def __str__(self):
1995+
return f"{self.user.email} - {self.organization.name} ({self.role})"
1996+
1997+
def accept_invitation(self):
1998+
"""Mark the invitation as accepted."""
1999+
self.status = "active"
2000+
self.invitation_accepted_at = timezone.now()
2001+
self.save()
2002+
2003+
def decline_invitation(self):
2004+
"""Mark the invitation as declined."""
2005+
self.status = "declined"
2006+
self.save()
2007+
2008+
18432009
class ContentTag(models.Model):
18442010
id = UUIDField(primary_key=True, default=uuid.uuid4)
18452011
tag_name = models.CharField(max_length=50)

0 commit comments

Comments
 (0)