|
79 | 79 | from contentcuration.constants import feedback |
80 | 80 | from contentcuration.constants import user_history |
81 | 81 | 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 |
82 | 85 | from contentcuration.db.models.expressions import Array |
83 | 86 | from contentcuration.db.models.functions import ArrayRemove |
84 | 87 | from contentcuration.db.models.functions import Unnest |
@@ -1147,6 +1150,14 @@ class Channel(models.Model): |
1147 | 1150 | ) |
1148 | 1151 | source_url = models.CharField(max_length=200, blank=True, null=True) |
1149 | 1152 | 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 | + ) |
1150 | 1161 |
|
1151 | 1162 | # Fields specific to content generated by Ricecooker |
1152 | 1163 | source_id = models.CharField(max_length=200, blank=True, null=True) |
@@ -1840,6 +1851,97 @@ def delete(self, *args, **kwargs): |
1840 | 1851 | self.secret_token.delete() |
1841 | 1852 |
|
1842 | 1853 |
|
| 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 | + |
1843 | 1945 | class ContentTag(models.Model): |
1844 | 1946 | id = UUIDField(primary_key=True, default=uuid.uuid4) |
1845 | 1947 | tag_name = models.CharField(max_length=50) |
|
0 commit comments