How to Return a Boolean Value in Django REST Serializers Based on Related Models
Tags: django, webdev
Published: 2021-12-13 22:05:56

I built an auctions project purely in vanilla Django for CS50 Web. I am now rebuilding this project using Django REST Framework and React to practice building a more robust full stack application. I have been learning a lot about how to use DRF to automate much of the API routing process and really enjoying the power and extensibility. However like all new things you come across snags.

I wanted to be able to conditionally render a heart-outline or filled-heart for each listing if a user was watching/following that particular listing (like a watch list).

You can see from the models.py snippet below that Watching model entries are relational models connecting foreign keys: the user and the listing.

I actually realized while writing this that I never did attempt to solve this problem in vanilla Django, as I had only rendered a watch list status on the individual listing pages:


views.py (vanilla Django)
```python
# additional data removed for clarity
def view_listing(request, id):
    listing = Listing.objects.get(pk=id)
    user = get_user_or_none(request) 
        
    is_user_watching = Watching.objects.filter(user_id=user, listing_id=listing).exists()

    return render(request,"auctions/listing.html", {  
        "watching": is_user_watching,  
    })

```

listing.html (Django Template)
```html
...
<form action="{% url 'watchlist' listing.id %}" method="POST">
    {% csrf_token %}
    {% if watching == True %}
    <button> Remove from Watchlist</button>
    {% else %}
    <button> Add to Watchlist</button>
    {% endif %}
</form>
{% else %}
    <a href="{% url 'login' %}">Log In</a> to bid on this auction!
{% endif %}
...
```

models.py
```python
class Watching(models.Model):
    user_id = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="watching")
    listing_id = models.ForeignKey(
        Listing, on_delete=models.CASCADE, related_name="watching")
```


###_I have watch list status on individual listings, how do I get this for all listings in a single request?_


Sometimes the hardest thing can be defining the problem in a manner which helps you to ask the right questions to lead on you the correct path. 

Eventually I learned you can get the current user in a serializer method by accessing `self.context['request'].user`. Now that I had access to the User, I could use that to query the database and see if a related Watching entry exists! 

In serializers.py I used `Watching.objects.filter().exists()` to return True or False if such a follow does indeed exist.

serializers.py
```python
from rest_framework.fields import SerializerMethodField

class ListingSerializer(serializers.ModelSerializer):

# returns true if user is authenticated and a Watching instance  
# exists with this user's id and this listing's id
    def is_watched_by_user(self, instance):
        user_id = self.context['request'].user.id
        listing_id = instance.id
        try:
            return Watching.objects.filter(user_id=user_id, listing_id=listing_id).exists()
        except Exception:
            return False

    creator = serializers.ReadOnlyField(source='creator.username')
    comments = CommentSerializer(many=True, required=False)
    user_is_watching = SerializerMethodField(method_name='is_watched_by_user')

    class Meta:
        model = Listing
        fields = ['creator','comments','user_is_watching']

```

While writing I thought that the serializer method `is_watched_by_user` could probably be boiled down a little more by using the related_name `user.watching` to get a smaller data set. This way we could potentially avoid looking through **ALL** of the listings, and only look at listings this particular user has a relational database entry for.

*updated is_watched_by_user:*
```python
def is_watched_by_user(self, instance):
        user_id = self.context['request'].user
        listing_id = instance.id
        try:
            return user.watching.filter(listing_id=listing_id).exists()
        except Exception:
            return False
```

Now when querying the API, the listings will tell me if the current logged in user is following this listing or not. The Try/Except block assumes that if there is an AttributeError 'attribute Watching does not exist' or Model.DoesNotExist or similar error, then the user is an AnonymousUser or there is no entry. Either way we can return False.

Below is a GET request for a logged in user who follows listing_id 1, but not 2 or 3:

GET ../api/listings/
```json
   "results": [
        {
            "id": 1,
            "creator": "Alice",
            "comments": [],
            "user_is_watching": true
        },
        {
            "id": 2,
            "creator": "Bob",
            "comments": [],
            "user_is_watching": false
        },
        {
            "id": 3,
            "creator": "Charlie",
            "comments": [],
            "user_is_watching": false
        },
    ]
```