diff --git a/core/api/serializers/custom/__init__.py b/core/api/serializers/custom/__init__.py index 42047086..52eb24f4 100644 --- a/core/api/serializers/custom/__init__.py +++ b/core/api/serializers/custom/__init__.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.utils.encoding import smart_str +from django.conf import settings from rest_framework import serializers from rest_framework.fields import Field, MultipleChoiceField @@ -26,22 +27,25 @@ def to_representation(self, obj): } -# todo switch all of these back to ChoiceField. refer to git history for the old code. -class ContentTypeField(serializers.Field): +class ContentTypeField(serializers.ChoiceField): def __init__(self, **kwargs): + self.slug_field = "model" + choices = ContentType.objects.filter( + app_label="core", model__in=settings.POST_CONTENT_TYPES + ).values_list("model", "model") default_error_messages = { - "does_not_exist": "ContentType with model '{value}' does not exist.", - "invalid": 'Invalid value. Expected string with the model name e.g. "Comment"', + "does_not_exist": "ContentType with model '{value}' does not exist." } - kwargs["help_text"] = 'The model name e.g. "Comment" or "BlogPost"' - super().__init__(**kwargs) + super().__init__(choices, **kwargs) self.default_error_messages.update(default_error_messages) def to_internal_value(self, data): try: - return ContentType.objects.get(app_label="core", model=str(data).casefold()) + return ContentType.objects.get(app_label="core", model=data) except ObjectDoesNotExist: - self.fail("does_not_exist", value=smart_str(data)) + self.fail( + "does_not_exist", slug_name=self.slug_field, value=smart_str(data) + ) except (TypeError, ValueError): self.fail("invalid") @@ -49,6 +53,7 @@ def to_representation(self, obj): return obj.model + class SingleUserSerializer(serializers.ModelSerializer): gravatar_url = serializers.SerializerMethodField(read_only=True) diff --git a/core/api/views/objects/post_interactions.py b/core/api/views/objects/post_interactions.py index ce1ea7fb..25c05ac2 100644 --- a/core/api/views/objects/post_interactions.py +++ b/core/api/views/objects/post_interactions.py @@ -177,9 +177,20 @@ def get_last_modified_queryset(): class LikeSerializer(serializers.ModelSerializer): content_type = ContentTypeField() - + + def update(self, instance, validated_data): + raise NotImplementedError("You cannot update a like, if you wish to delete. do so.") + + def destroy(self, instance: Like, validated_data): + if instance.author != self.context["request"].user: + raise ValidationError("You cannot unlike another user's like.") + instance.delete(force=True) + def create(self, validated_data) -> Like: obj_name = validated_data["content_type"].name.lower().replace(" ", "") + if (self.context["request"].user != validated_data["author"]) and not self.context["request"].user.is_superuser: + raise ValidationError("You cannot like as another user.") + if obj_name not in settings.POST_CONTENT_TYPES: # is the object type valid? raise ValidationError( f"Invalid object type: {obj_name}, valid types are: {settings.POST_CONTENT_TYPES}" @@ -191,15 +202,17 @@ def create(self, validated_data) -> Like: .exists() ): # does the object exist? raise ValidationError(f"The specified {obj_name} does not exist.") + if Like.objects.filter( # has the user already liked this object? content_type=validated_data["content_type"], object_id=validated_data["object_id"], - author=self.context["author"], + author=validated_data['author'], # author is current user? ).exists(): raise ValidationError(f"User has already liked this {obj_name}") - like = Like(**validated_data) - like.save() - return like + else: + like = Like(**validated_data) + like.save() + return like class Meta: model = Like diff --git a/core/models/post.py b/core/models/post.py index e0bcd287..253cfb05 100644 --- a/core/models/post.py +++ b/core/models/post.py @@ -78,8 +78,8 @@ def delete(self, using=None, keep_parents=False, **kwargs): Don't actually delete the object, just set the user to None and save it. This way, we can still keep track of the likes, saves and comments. if force is set to True, then it will actually delete the object (used for when you want to delete a comment or unlike/save something) """ - if kwargs.get("force", True): - super().delete(using=using, keep_parents=keep_parents) + if kwargs.get("force", False): + return super().delete(using=using, keep_parents=keep_parents) self.user = None self.save()