-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CS PackageRating rate form and view
- Loading branch information
Showing
8 changed files
with
359 additions
and
1 deletion.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
django/thunderstore/api/cyberstorm/tests/test_package_rating.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import json | ||
|
||
import pytest | ||
from rest_framework.test import APIClient | ||
|
||
from thunderstore.core.types import UserType | ||
from thunderstore.repository.models import Package | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_api_view__succeeds( | ||
api_client: APIClient, | ||
package: Package, | ||
user: UserType, | ||
) -> None: | ||
api_client.force_authenticate(user) | ||
|
||
response = api_client.post( | ||
f"/api/cyberstorm/package/{package.namespace}/{package.name}/rate/", | ||
json.dumps({"target_state": "rated"}), | ||
content_type="application/json", | ||
) | ||
actual = response.json() | ||
|
||
assert actual["state"] == "rated" | ||
assert actual["score"] == 1 | ||
|
||
response = api_client.post( | ||
f"/api/cyberstorm/package/{package.namespace}/{package.name}/rate/", | ||
json.dumps({"target_state": "unrated"}), | ||
content_type="application/json", | ||
) | ||
actual = response.json() | ||
|
||
assert actual["state"] == "unrated" | ||
assert actual["score"] == 0 | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_api_view__returns_error_for_non_existent_package( | ||
api_client: APIClient, | ||
user: UserType, | ||
) -> None: | ||
api_client.force_authenticate(user) | ||
response = api_client.post( | ||
f"/api/cyberstorm/package/BAD/BAD/rate/", | ||
json.dumps({"target_state": "rated"}), | ||
content_type="application/json", | ||
) | ||
actual = response.json() | ||
|
||
assert actual["detail"] == "Not found." | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_api_view__returns_error_for_no_user( | ||
api_client: APIClient, | ||
package: Package, | ||
) -> None: | ||
response = api_client.post( | ||
f"/api/cyberstorm/package/{package.namespace}/{package.name}/rate/", | ||
json.dumps({"target_state": "rated"}), | ||
content_type="application/json", | ||
) | ||
actual = response.json() | ||
|
||
assert actual["detail"] == "Authentication credentials were not provided." | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_api_view__returns_error_for_bad_data( | ||
api_client: APIClient, | ||
package: Package, | ||
user: UserType, | ||
) -> None: | ||
api_client.force_authenticate(user) | ||
package.is_active = False | ||
package.save() | ||
|
||
response = api_client.post( | ||
f"/api/cyberstorm/package/{package.namespace}/{package.name}/rate/", | ||
json.dumps({"bad_data": "rated"}), | ||
content_type="application/json", | ||
) | ||
actual = response.json() | ||
|
||
assert actual["target_state"] == ["This field is required."] | ||
|
||
response = api_client.post( | ||
f"/api/cyberstorm/package/{package.namespace}/{package.name}/rate/", | ||
json.dumps({"target_state": "bad"}), | ||
content_type="application/json", | ||
) | ||
actual = response.json() | ||
|
||
assert actual["__all__"] == ["Given target_state is invalid"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
django/thunderstore/api/cyberstorm/views/package_rating.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from django.http import HttpRequest | ||
from rest_framework import serializers | ||
from rest_framework.exceptions import ValidationError | ||
from rest_framework.generics import get_object_or_404 | ||
from rest_framework.permissions import IsAuthenticated | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from thunderstore.api.utils import conditional_swagger_auto_schema | ||
from thunderstore.repository.forms import RateForm | ||
from thunderstore.repository.models import Package | ||
|
||
|
||
class CyberstormPackageRatingRateRequestSerialiazer(serializers.Serializer): | ||
target_state = serializers.CharField() | ||
|
||
|
||
class CyberstormPackageRatingRateResponseSerialiazer(serializers.Serializer): | ||
state = serializers.CharField() | ||
score = serializers.IntegerField() | ||
|
||
|
||
class PackageRatingRateAPIView(APIView): | ||
permission_classes = [IsAuthenticated] | ||
|
||
@conditional_swagger_auto_schema( | ||
request_body=CyberstormPackageRatingRateRequestSerialiazer, | ||
responses={200: CyberstormPackageRatingRateResponseSerialiazer}, | ||
operation_id="cyberstorm.package_rating.rate", | ||
tags=["cyberstorm"], | ||
) | ||
def post(self, request: HttpRequest, namespace_id: str, package_name: str): | ||
serializer = CyberstormPackageRatingRateRequestSerialiazer(data=request.data) | ||
serializer.is_valid(raise_exception=True) | ||
package = get_object_or_404( | ||
Package, | ||
namespace__name=namespace_id, | ||
name__iexact=package_name, | ||
) | ||
form = RateForm( | ||
user=request.user, | ||
package=package, | ||
data=serializer.validated_data, | ||
) | ||
if form.is_valid(): | ||
(result_state, score) = form.execute() | ||
return Response( | ||
CyberstormPackageRatingRateResponseSerialiazer( | ||
{"state": result_state, "score": score} | ||
).data | ||
) | ||
else: | ||
raise ValidationError(form.errors) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
from .package_rating import * | ||
from .team import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from typing import Optional | ||
|
||
from django import forms | ||
from django.contrib.auth import get_user_model | ||
|
||
from thunderstore.core.types import UserType | ||
from thunderstore.repository.models import Package, PackageRating | ||
from thunderstore.repository.permissions import ensure_can_rate_package | ||
|
||
User = get_user_model() | ||
|
||
|
||
class RateForm(forms.Form): | ||
def __init__(self, user: Optional[UserType], package: Package, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.user = user | ||
self.package = package | ||
|
||
def clean(self): | ||
ensure_can_rate_package(self.user, self.package) | ||
target_state = self.data.get("target_state", None) | ||
if target_state == "rated": | ||
self.cleaned_data["target_state"] = "rated" | ||
elif target_state == "unrated": | ||
self.cleaned_data["target_state"] = "unrated" | ||
else: | ||
raise forms.ValidationError("Given target_state is invalid") | ||
|
||
return super().clean() | ||
|
||
def execute(self): | ||
if self.cleaned_data["target_state"] == "rated": | ||
PackageRating.objects.get_or_create(rater=self.user, package=self.package) | ||
result_state = "rated" | ||
else: | ||
PackageRating.objects.filter(rater=self.user, package=self.package).delete() | ||
result_state = "unrated" | ||
return (result_state, self.package.rating_score) |
149 changes: 149 additions & 0 deletions
149
django/thunderstore/repository/tests/test_package_rating_forms.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import pytest | ||
from rest_framework.exceptions import PermissionDenied | ||
|
||
from thunderstore.core.types import UserType | ||
from thunderstore.repository.forms.package_rating import RateForm | ||
from thunderstore.repository.models import Package | ||
from thunderstore.repository.models.package_rating import PackageRating | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_form__correct_values__succeeds( | ||
user: UserType, package: Package | ||
) -> None: | ||
# Rated | ||
p = Package.objects.get(pk=package.pk) | ||
form = RateForm( | ||
user=user, | ||
package=p, | ||
data={"target_state": "rated"}, | ||
) | ||
assert form.is_valid() is True | ||
(result_state, score) = form.execute() | ||
assert len(PackageRating.objects.filter(rater=user, package=package)) == 1 | ||
assert result_state == "rated" | ||
assert score == 1 | ||
# Unrated | ||
p = Package.objects.get(pk=package.pk) | ||
form = RateForm( | ||
user=user, | ||
package=p, | ||
data={"target_state": "unrated"}, | ||
) | ||
assert form.is_valid() is True | ||
(result_state, score) = form.execute() | ||
assert len(PackageRating.objects.filter(rater=user, package=package)) == 0 | ||
assert result_state == "unrated" | ||
assert score == 0 | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_form__already_on_state__succeeds( | ||
user: UserType, package: Package | ||
) -> None: | ||
# Rated | ||
p = Package.objects.get(pk=package.pk) | ||
form = RateForm( | ||
user=user, | ||
package=p, | ||
data={"target_state": "rated"}, | ||
) | ||
assert form.is_valid() is True | ||
(result_state, score) = form.execute() | ||
assert len(PackageRating.objects.filter(rater=user, package=package)) == 1 | ||
assert result_state == "rated" | ||
assert score == 1 | ||
# Second time | ||
p = Package.objects.get(pk=package.pk) | ||
form = RateForm( | ||
user=user, | ||
package=p, | ||
data={"target_state": "rated"}, | ||
) | ||
assert form.is_valid() is True | ||
(result_state, score) = form.execute() | ||
assert len(PackageRating.objects.filter(rater=user, package=package)) == 1 | ||
assert result_state == "rated" | ||
assert score == 1 | ||
# Unrated | ||
p = Package.objects.get(pk=package.pk) | ||
form = RateForm( | ||
user=user, | ||
package=p, | ||
data={"target_state": "unrated"}, | ||
) | ||
assert form.is_valid() is True | ||
(result_state, score) = form.execute() | ||
assert len(PackageRating.objects.filter(rater=user, package=package)) == 0 | ||
assert result_state == "unrated" | ||
assert score == 0 | ||
# Second time | ||
p = Package.objects.get(pk=package.pk) | ||
form = RateForm( | ||
user=user, | ||
package=p, | ||
data={"target_state": "unrated"}, | ||
) | ||
assert form.is_valid() is True | ||
(result_state, score) = form.execute() | ||
assert len(PackageRating.objects.filter(rater=user, package=package)) == 0 | ||
assert result_state == "unrated" | ||
assert score == 0 | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_form__bad_target_state__fails( | ||
user: UserType, package: Package | ||
) -> None: | ||
error = "Given target_state is invalid" | ||
form = RateForm( | ||
user=user, | ||
package=package, | ||
data={"target_state": "bad"}, | ||
) | ||
assert form.is_valid() is False | ||
assert error in str(repr(form.errors)) | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_form__user_none__fails( | ||
package: Package, | ||
) -> None: | ||
form = RateForm( | ||
user=None, | ||
package=package, | ||
data={"target_state": "rated"}, | ||
) | ||
with pytest.raises(PermissionDenied) as e: | ||
form.is_valid() | ||
assert "Must be authenticated" in str(e.value) | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_form__user_deactivated__fails( | ||
user: UserType, package: Package | ||
) -> None: | ||
user.is_active = False | ||
user.save() | ||
form = RateForm( | ||
user=user, | ||
package=package, | ||
data={"target_state": "rated"}, | ||
) | ||
with pytest.raises(PermissionDenied) as e: | ||
form.is_valid() | ||
assert "User has been deactivated" in str(e.value) | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_package_rating_form__user_is_service_account__fails( | ||
service_account: UserType, package: Package | ||
) -> None: | ||
form = RateForm( | ||
user=service_account.user, | ||
package=package, | ||
data={"target_state": "rated"}, | ||
) | ||
with pytest.raises(PermissionDenied) as e: | ||
form.is_valid() | ||
assert "Service accounts are unable to perform this action" in str(e.value) |
Oops, something went wrong.