Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/api/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,16 +683,16 @@ Documents](#sharing-documents)
| ------ | ------- | --------------- | ----------------------------------------------------------------- |
| access | String | Default: `view` | The [access level](#sharing-documents) for this collaborator |
| email | Email | Create Only | Email address of user to add as a collaborator to this project |
| user | Integer | Read Only | The ID for the [user](#user) who is collaborating on this project |
| user | Integer | Read and Create | The ID for the [user](#user) who is collaborating on this project |

[Expandable fields](#expandable-fields): user

#### Endpoints

- `GET /api/projects/<project_id>/users/` - List collaborators on the project
- `POST /api/projects/<project_id>/users/` - Add a collaborator to the project
&mdash; you must know the email address of a user with a DocumentCloud
account in order to add them as a collaborator on your project
&mdash; you must know either the ID or the email address of a user with a
DocumentCloud account in order to add them as a collaborator on your project
- `GET /api/projects/<project_id>/users/<user_id>/` - Get a collaborator in the project
- `PUT /api/projects/<project_id>/users/<user_id>/` - Update collaborator in the project
- `PATCH /api/projects/<project_id>/users/<user_id>/` - Partial update collaborator in the project
Expand Down
55 changes: 42 additions & 13 deletions documentcloud/projects/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,25 @@ def validate(self, attrs):


class CollaborationSerializer(FlexFieldsModelSerializer):
user = serializers.PrimaryKeyRelatedField(
label=_("User"),
queryset=User.objects.all(),
required=False,
help_text=_(
"The ID of the user you wish to add as a collaborator. "
"Either this or `email` is required on creation."
),
)
email = serializers.SlugRelatedField(
label=_("Email"),
source="user",
slug_field="email",
write_only=True,
required=False,
queryset=User.objects.all(),
help_text=_("The email address of the user you wish to add as a collaborator"),
help_text=_(
"The email address of the user you wish to add as a collaborator. "
"Either this or `user` is required on creation."
),
error_messages={
**serializers.SlugRelatedField.default_error_messages,
"does_not_exist": _(
Expand All @@ -217,7 +229,6 @@ class CollaborationSerializer(FlexFieldsModelSerializer):
class Meta:
model = Collaboration
fields = ["user", "email", "access"]
extra_kwargs = {"user": {"read_only": True}}
expandable_fields = {"user": ("documentcloud.users.UserSerializer", {})}

def validate_access(self, value):
Expand All @@ -239,20 +250,38 @@ def validate_access(self, value):

return value

def validate_email(self, value):
def validate(self, attrs):
"""The user may be specified by either ID or email"""
email_user = attrs.pop("email", None)
user = attrs.get("user")
if user and email_user and user != email_user:
raise serializers.ValidationError(
"`user` and `email` must refer to the same user"
)
user = user or email_user

if user is None:
if not self.instance:
raise serializers.ValidationError(
{"user": "You must specify either `user` or `email`"}
)
return attrs

view = self.context.get("view")
project = Project.objects.get(pk=view.kwargs["project_pk"])

# on updates check that email does not change
if self.instance and self.instance.user != value:
raise serializers.ValidationError("You may not update `email`")
# on updates check that user does not change
if self.instance and self.instance.user != user:
raise serializers.ValidationError({"user": "You may not update `user`"})

# check for duplicates on creation (instance is none implies creation)
if (
not self.instance
and project.collaborators.filter(email=value.email).exists()
):
if not self.instance and project.collaborators.filter(pk=user.pk).exists():
raise serializers.ValidationError(
f"You may not add user {value.email} as a collaborator more than once"
{
"user": f"You may not add user {user.name} as a collaborator "
"more than once"
}
)
return value

attrs["user"] = user
return attrs
33 changes: 31 additions & 2 deletions documentcloud/projects/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,15 +673,44 @@ def test_list_queries(self, client, expand):
assert num_queries == len(connection.queries)
assert len(response.json()["results"]) == size + small_size + 1

def test_create(self, client, project, user):
"""Add a user to a project"""
def test_create_user(self, client, project, user):
"""Add a user to a project by id"""
client.force_authenticate(user=project.user)
response = client.post(f"/api/projects/{project.pk}/users/", {"user": user.pk})
assert response.status_code == status.HTTP_201_CREATED
assert project.collaborators.filter(pk=user.pk).exists()

def test_create_email(self, client, project, user):
"""Add a user to a project by email"""
client.force_authenticate(user=project.user)
response = client.post(
f"/api/projects/{project.pk}/users/", {"email": user.email}
)
assert response.status_code == status.HTTP_201_CREATED
assert project.collaborators.filter(pk=user.pk).exists()

def test_create_bad_user(self, client, project):
"""Trying adding a user ID that does not exist"""
client.force_authenticate(user=project.user)
response = client.post(f"/api/projects/{project.pk}/users/", {"user": 0})
assert response.status_code == status.HTTP_400_BAD_REQUEST

def test_create_bad_mismatch(self, client, project, user):
"""User ID and email must match if both are given"""
other_user = UserFactory()
client.force_authenticate(user=project.user)
response = client.post(
f"/api/projects/{project.pk}/users/",
{"user": user.pk, "email": other_user.email},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST

def test_create_bad_missing(self, client, project):
"""Must specify a user ID or email"""
client.force_authenticate(user=project.user)
response = client.post(f"/api/projects/{project.pk}/users/", {"access": "view"})
assert response.status_code == status.HTTP_400_BAD_REQUEST

def test_create_bad_collaborator(self, client, project, user):
"""You may not add a collaborator to a project you are not a collaborator on"""
client.force_authenticate(user=user)
Expand Down
Loading