Skip to content

Commit 5112105

Browse files
committed
add collaborative feature
1 parent f10f2d2 commit 5112105

File tree

10 files changed

+1439
-0
lines changed

10 files changed

+1439
-0
lines changed

api/activities/collaborative.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
from typing import Any, Dict, Optional
2+
3+
from django.http import HttpRequest
4+
5+
from api.activities.base import track_model_activity
6+
from api.models.Collaborative import Collaborative
7+
from authorization.models import User
8+
9+
10+
def track_collaborative_created(
11+
user: User, collaborative: Collaborative, request: Optional[HttpRequest] = None
12+
) -> None:
13+
"""
14+
Track when a collaborative is created.
15+
"""
16+
track_model_activity(
17+
actor=user,
18+
verb="created",
19+
model_instance=collaborative,
20+
target=collaborative.organization if collaborative.organization else None,
21+
request=request,
22+
extra_data={
23+
"collaborative_title": collaborative.title,
24+
"collaborative_id": str(collaborative.id),
25+
"organization_id": (
26+
str(collaborative.organization.id)
27+
if collaborative.organization
28+
else None
29+
),
30+
"organization_name": (
31+
collaborative.organization.name if collaborative.organization else None
32+
),
33+
},
34+
)
35+
36+
37+
def track_collaborative_updated(
38+
user: User,
39+
collaborative: Collaborative,
40+
updated_fields: Optional[Dict[str, Any]] = None,
41+
request: Optional[HttpRequest] = None,
42+
) -> None:
43+
"""
44+
Track when a collaborative is updated.
45+
46+
Args:
47+
user: The user performing the update
48+
collaborative: The collaborative being updated
49+
updated_fields: Dictionary of fields that were updated
50+
request: The current HTTP request
51+
"""
52+
track_model_activity(
53+
actor=user,
54+
verb="updated",
55+
model_instance=collaborative,
56+
target=collaborative.organization if collaborative.organization else None,
57+
request=request,
58+
extra_data={
59+
"collaborative_title": collaborative.title,
60+
"collaborative_id": str(collaborative.id),
61+
"updated_fields": updated_fields or {},
62+
},
63+
)
64+
65+
66+
def track_collaborative_published(
67+
user: User, collaborative: Collaborative, request: Optional[HttpRequest] = None
68+
) -> None:
69+
"""
70+
Track when a collaborative is published (status changed to PUBLISHED).
71+
"""
72+
track_model_activity(
73+
actor=user,
74+
verb="published",
75+
model_instance=collaborative,
76+
target=collaborative.organization if collaborative.organization else None,
77+
request=request,
78+
extra_data={
79+
"collaborative_title": collaborative.title,
80+
"collaborative_id": str(collaborative.id),
81+
},
82+
)
83+
84+
85+
def track_collaborative_deleted(
86+
user: User,
87+
collaborative_id: str,
88+
collaborative_title: str,
89+
organization_id: Optional[str] = None,
90+
request: Optional[HttpRequest] = None,
91+
) -> None:
92+
"""
93+
Track when a collaborative is deleted.
94+
Since the collaborative is deleted, we need to pass its ID and title separately.
95+
"""
96+
# For deleted objects, we can't pass the model instance directly
97+
# Instead, we create a dictionary with the relevant information
98+
extra_data = {
99+
"collaborative_title": collaborative_title,
100+
"collaborative_id": collaborative_id,
101+
"action": "deleted",
102+
}
103+
104+
# If we have the organization, we can use it as the target
105+
target = None
106+
if organization_id:
107+
from api.models.Organization import Organization
108+
109+
try:
110+
target = Organization.objects.get(id=organization_id)
111+
except Organization.DoesNotExist:
112+
pass
113+
114+
# Record the activity
115+
from authorization.activity import record_activity
116+
117+
record_activity(
118+
actor=user,
119+
verb="deleted",
120+
action_object=None, # No action object since it's deleted
121+
target=target,
122+
request=request,
123+
data=extra_data,
124+
)
125+
126+
127+
def track_dataset_added_to_collaborative(
128+
user: User,
129+
collaborative: Collaborative,
130+
dataset_id: str,
131+
dataset_title: str,
132+
request: Optional[HttpRequest] = None,
133+
) -> None:
134+
"""
135+
Track when a dataset is added to a use case.
136+
"""
137+
from api.models.Dataset import Dataset
138+
139+
try:
140+
dataset = Dataset.objects.get(id=dataset_id)
141+
track_model_activity(
142+
actor=user,
143+
verb="added dataset to",
144+
model_instance=collaborative,
145+
target=dataset,
146+
request=request,
147+
extra_data={
148+
"collaborative_title": collaborative.title,
149+
"collaborative_id": str(collaborative.id),
150+
"dataset_id": dataset_id,
151+
"dataset_title": dataset_title,
152+
},
153+
)
154+
except Dataset.DoesNotExist:
155+
# If the dataset doesn't exist, we still want to track the activity
156+
# but we can't use the dataset as the target
157+
track_model_activity(
158+
actor=user,
159+
verb="added dataset to",
160+
model_instance=collaborative,
161+
request=request,
162+
extra_data={
163+
"collaborative_title": collaborative.title,
164+
"collaborative_id": str(collaborative.id),
165+
"dataset_id": dataset_id,
166+
"dataset_title": dataset_title,
167+
},
168+
)
169+
170+
171+
def track_dataset_removed_from_collaborative(
172+
user: User,
173+
collaborative: Collaborative,
174+
dataset_id: str,
175+
dataset_title: str,
176+
request: Optional[HttpRequest] = None,
177+
) -> None:
178+
"""
179+
Track when a dataset is removed from a use case.
180+
"""
181+
from api.models.Dataset import Dataset
182+
183+
try:
184+
dataset = Dataset.objects.get(id=dataset_id)
185+
track_model_activity(
186+
actor=user,
187+
verb="removed dataset from",
188+
model_instance=collaborative,
189+
target=dataset,
190+
request=request,
191+
extra_data={
192+
"collaborative_title": collaborative.title,
193+
"collaborative_id": str(collaborative.id),
194+
"dataset_id": dataset_id,
195+
"dataset_title": dataset_title,
196+
},
197+
)
198+
except Dataset.DoesNotExist:
199+
# If the dataset doesn't exist, we still want to track the activity
200+
# but we can't use the dataset as the target
201+
track_model_activity(
202+
actor=user,
203+
verb="removed dataset from",
204+
model_instance=collaborative,
205+
request=request,
206+
extra_data={
207+
"collaborative_title": collaborative.title,
208+
"collaborative_id": str(collaborative.id),
209+
"dataset_id": dataset_id,
210+
"dataset_title": dataset_title,
211+
},
212+
)

api/models/Collaborative.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from datetime import datetime
2+
from typing import TYPE_CHECKING, Any, cast
3+
4+
from django.db import models
5+
from django.utils.text import slugify
6+
7+
if TYPE_CHECKING:
8+
from api.models.Dataset import Dataset
9+
from api.models.Organization import Organization
10+
from authorization.models import User
11+
12+
from api.utils.enums import CollaborativeStatus, OrganizationRelationshipType
13+
from api.utils.file_paths import _use_case_directory_path
14+
15+
16+
class Collaborative(models.Model):
17+
id = models.AutoField(primary_key=True)
18+
title = models.CharField(max_length=200, unique=True, blank=True, null=True)
19+
summary = models.CharField(max_length=10000, blank=True, null=True)
20+
logo = models.ImageField(
21+
upload_to=_use_case_directory_path, max_length=300, blank=True, null=True
22+
)
23+
created = models.DateTimeField(auto_now_add=True)
24+
modified = models.DateTimeField(auto_now=True)
25+
website = models.URLField(blank=True)
26+
contact_email = models.EmailField(blank=True, null=True)
27+
slug = models.SlugField(max_length=75, null=True, blank=True, unique=True)
28+
user = models.ForeignKey("authorization.User", on_delete=models.CASCADE)
29+
organization = models.ForeignKey(
30+
"api.Organization", on_delete=models.CASCADE, null=True, blank=True
31+
)
32+
status = models.CharField(
33+
max_length=50,
34+
default=CollaborativeStatus.DRAFT,
35+
choices=CollaborativeStatus.choices,
36+
)
37+
datasets = models.ManyToManyField("api.Dataset", blank=True)
38+
tags = models.ManyToManyField("api.Tag", blank=True)
39+
sectors = models.ManyToManyField(
40+
"api.Sector", blank=True, related_name="collaboratives"
41+
)
42+
contributors = models.ManyToManyField(
43+
"authorization.User", blank=True, related_name="contributed_collaboratives"
44+
)
45+
# Organizations can be added as supporters or partners through the intermediate model
46+
organizations = models.ManyToManyField(
47+
"api.Organization",
48+
through="api.CollaborativeOrganizationRelationship",
49+
related_name="related_collaboratives",
50+
blank=True,
51+
)
52+
started_on = models.DateField(blank=True, null=True)
53+
completed_on = models.DateField(blank=True, null=True)
54+
platform_url = models.URLField(blank=True, null=True)
55+
56+
def save(self, *args: Any, **kwargs: Any) -> None:
57+
if self.title and not self.slug:
58+
self.slug = slugify(cast(str, self.title))
59+
super().save(*args, **kwargs)
60+
61+
@property
62+
def is_individual_collaborative(self):
63+
return self.organization is None
64+
65+
@property
66+
def sectors_indexing(self):
67+
return [sector.name for sector in self.sectors.all()] # type: ignore
68+
69+
@property
70+
def tags_indexing(self):
71+
return [tag.value for tag in self.tags.all()] # type: ignore
72+
73+
class Meta:
74+
db_table = "collaborative"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import TYPE_CHECKING
2+
3+
from django.db import models
4+
5+
from api.models.Metadata import BaseMetadata
6+
7+
if TYPE_CHECKING:
8+
from api.models.Collaborative import Collaborative
9+
10+
11+
class CollaborativeMetadata(BaseMetadata):
12+
collaborative = models.ForeignKey(
13+
"api.Collaborative",
14+
on_delete=models.CASCADE,
15+
null=False,
16+
blank=False,
17+
related_name="metadata",
18+
)
19+
20+
def __str__(self) -> str:
21+
return f"{self.collaborative.title} - {self.metadata_item.label}"
22+
23+
class Meta(BaseMetadata.Meta):
24+
db_table = "collaborative_metadata"
25+
unique_together = ("collaborative", "metadata_item")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Model for relationship between collaborative and organizations."""
2+
3+
from django.db import models
4+
5+
from api.utils.enums import OrganizationRelationshipType
6+
7+
8+
class CollaborativeOrganizationRelationship(models.Model):
9+
"""Intermediate model for Collaborative-Organization relationship.
10+
This model stores the type of relationship between a collaborative and an organization.
11+
"""
12+
13+
collaborative = models.ForeignKey("api.Collaborative", on_delete=models.CASCADE)
14+
organization = models.ForeignKey("api.Organization", on_delete=models.CASCADE)
15+
relationship_type = models.CharField(
16+
max_length=50,
17+
choices=OrganizationRelationshipType.choices,
18+
)
19+
created_at = models.DateTimeField(auto_now_add=True)
20+
updated_at = models.DateTimeField(auto_now=True)
21+
22+
class Meta:
23+
db_table = "collaborative_organization_relationship"
24+
unique_together = ("collaborative", "organization", "relationship_type")
25+
verbose_name = "Collaborative Organization Relationship"
26+
verbose_name_plural = "Collaborative Organization Relationships"
27+
28+
def __str__(self) -> str:
29+
return f"{self.collaborative.title} - {self.organization.name} ({self.relationship_type})"

api/models/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from api.models.AccessModel import AccessModel, AccessModelResource
22
from api.models.Catalog import Catalog
3+
from api.models.Collaborative import Collaborative
4+
from api.models.CollaborativeMetadata import CollaborativeMetadata
5+
from api.models.CollaborativeOrganizationRelationship import (
6+
CollaborativeOrganizationRelationship,
7+
)
38
from api.models.Dataset import Dataset, Tag
49
from api.models.DatasetMetadata import DatasetMetadata
510
from api.models.DataSpace import DataSpace

0 commit comments

Comments
 (0)