I have a custom user model and subscription model, which contains ForeignKey
s to subscriber and user to subscribe to.
class Subscription(models.Model):
user = models.ForeignKey(
ApiUser,
on_delete=models.CASCADE,
related_name='subscriber'
)
subscription = models.ForeignKey(
ApiUser,
on_delete=models.CASCADE,
related_name='subscriptions'
)
Also i have viewset for subscription and two serializers: for write and for read.
class SubscribeViewSet(mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
queryset = ApiUser.objects.all()
serializer_class = SubscriptionSerializerForRead
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
queryset = ApiUser.objects.all().annotate(
is_subscribed=Case(
When(
subscribtions__exact=self.request.user.id,
then=Value(True)
),
default=Value(False),
output_field=BooleanField()
)
).order_by('id')
return queryset
def get_serializer_context(self):
context = super().get_serializer_context()
context.update({'subscription_id': self.kwargs['pk']})
return context
def get_serializer_class(self):
if self.request.method not in permissions.SAFE_METHODS:
return SubscriptionSerializerForWrite
return SubscriptionSerializerForRead
@action(
methods=['POST'],
detail=True,
url_path='subscribe'
)
def subscribe(self, request, pk):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class SubscriptionSerializerForRead(serializers.ModelSerializer):
is_subscribed = serializers.BooleanField()
class Meta:
model = ApiUser
fields = (
'email',
'id',
'username',
'first_name',
'last_name',
'is_subscribed'
)
class SubscriptionSerializerForWrite(serializers.ModelSerializer):
user = serializers.StringRelatedField(
required=False,
read_only=True,
default=serializers.CurrentUserDefault()
)
subscription = serializers.PrimaryKeyRelatedField(
read_only=True
)
class Meta:
model = Subscription
fields = ('user', 'subscription')
def validate(self, attrs):
attrs['user'] = self.context['request'].user
attrs['subscription_id'] = self.context['subscription_id']
if self.context['request'].user.id == self.context['subscription_id']:
raise serializers.ValidationError(
'Cannot subscribe to yourself'
)
return attrs
def to_representation(self, instance):
return SubscriptionSerializerForRead(
instance=instance.subscription
).data
After successful subscription i need to return response with subscribed user data and additional value is_subscribed
. I tried to do that through annotate
queryset in my viewset, but in response i always get an error:
backend-1 | AttributeError: Got AttributeError when attempting to get a value for field `is_subscribed` on serializer `SubscriptionSerializerForRead`.
backend-1 | The serializer field might be named incorrectly and not match any attribute or key on the `ApiUser` instance.
backend-1 | Original exception text was: 'ApiUser' object has no attribute 'is_subscribed'.
How could i make this work and what's causing that issue? Same annotation method works for other m2m related models in my project, but with user model it fails for some reason
The problem was at to_representation
function in SubscriptionSerializerForWrite
class which is calling SubscriptionSerializerForRead
class but the instance was not annotated when created from the serializer.
The solutions could be either:
Make the is_subscribed
optional. By changing it to SerializerMethodField
and write a custom logic like return getattr(obj, 'is_subscribed', False)
Manually set is_subscribed
attribute after created (or in to_representation
function):
def create(self, validated_data):
instance = super().create(validated_data)
setattr(instance, 'is_subscribed', True)
return instance