0

This question or many like it has been asked multiple times but for some reason I am unable to find the answer.

I do have this working to an extent in the way that if you go on the api pages, it renders, creates and updates without problem. The issue is displaying a field (title) from the nested object instead of just the primary key on the front end.

Some background before getting into the code:

Races is a finite list (e.g. Race1, Race2, Race3) and the front end does not have the ability to add more.

Cards is not finite, but each card must link to an existing Race (this currently does so by Primary Key).

The front end should display the card_text and race title of the linked race. It also has the ability to add a new card but this works fine.

I have had this working with separate serializers for read and create/update where the read has a 'depth = 1' to pull through the entire object but the create/update doesn't and you then parse the object and send the primary key back (I couldn't find a way of doing this in the serializer, is it possible?).

So basically my question is, are you meant to pass the entire object through and parse it on a POST method, or do you pass the primary key and pull in the linked objects (Races) and use the primary key as an index (e.g. Races[card_race]). Also, why is 'linked_race' not coming through to the front end?

I realise I've almost answered my own question but as I'm new to Django I'm looking for the correct conventions and who knows, it may save someone else time when searching for the same answer.

urls.py

from .api import CardViewSet, RaceViewSet
from rest_framework.routers import DefaultRouter
from django.conf.urls import url, include
from .views import landing

router = DefaultRouter()
router.register(r'cards', CardViewSet)
router.register(r'races', RaceViewSet)

urlpatterns = [
    url(r'^$', landing),
    url(r'^api/', include(router.urls)),
]

api.py

from rest_framework.viewsets import ModelViewSet
from .serializers import CardSerializer, RaceSerializer
from .models import Card, Race


class CardViewSet(ModelViewSet):
    queryset = Card.objects.filter(active=True)

    def get_serializer_class(self):
        return CardSerializer

    def perform_create(self, serializer):
        serializer.save(creator=self.request.user)


class RaceViewSet(ModelViewSet):
    queryset = Race.objects.filter(active=True)
    serializer_class = RaceSerializer

models.py

from django.db import models
from django.conf import settings

User = settings.AUTH_USER_MODEL

class Race(models.Model):
    id = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=30, blank=False)
    active = models.BooleanField(default=True)

    def __str__(self):
        return "{}".format(self.title)

    def __unicode__(self):
        return self.title


class Card(models.Model):
    card_text = models.CharField(max_length=100, blank=False)
    card_description = models.CharField(max_length=100, blank=True)
    card_race = models.ForeignKey(Race, related_name='linked_race', on_delete=models.CASCADE)
    creator = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)
    active = models.BooleanField(default=True)

    def __str__(self):
        return self.card_text

    class Meta:
        ordering = ('created',)

serializers.py

from rest_framework import serializers
from .models import Card, Race


class RaceSerializer(serializers.ModelSerializer):

    class Meta:
        model = Race
        fields = '__all__'


class CardSerializer(serializers.ModelSerializer):
    linked_race = RaceSerializer(read_only=True, many=True)

    class Meta:
        model = Card
        fields = 'id', 'card_text', 'card_description', 'card_race', 'linked_race',

Javascript extract (AngularJS)

$http.get('/api/races/').then(function (response) {
  $scope.races = response.data;
  $scope.selectedOption = $scope.races[0];
});
$scope.cards = [];
$http.get('/api/cards/').then(function (response) {
  $scope.cards = orderBy(response.data, 'created', true);
});

html extract (AngularJS)

<div class="races--row" ng-repeat="c in cards | filter : card_filter |
orderBy : sortVal : sortDir" ng-class-odd="'odd'" ng-click="openModal(c)"> 

<div class="races--cell race">{{ c.card_race.title }}</div> 
<div class="races--cell card-text">{{ c.card_text }}</div> 

</div>
mwade
  • 189
  • 1
  • 4
  • 9

1 Answers1

0

Your first "problem" is with the Card model (I say problem because I don't think you intended to do this). You're defining related_name='linked_race' for the card_race field. This related_name is the name you use to refer to a card FROM a race.

I would suggest you leave it out and use the default that Django already gives us (i.e. my_race.card_set.all() in this case). So change change that field in the Card model to:

class Card(models.Model):
    ...
    card_race = models.ForeignKey(Race, on_delete=models.CASCADE)
    ...

And let's change the card serializer to:

class CardSerializer(serializers.ModelSerializer):
    # no more linked_race
    class Meta:
        model = Card
        fields = ('id', 'card_text', 'card_description', 'card_race')

Alright, this is a vary basic model serializer and you won't see details of a race yet. So now let's get to your main problem which was that you wanted to:

  • see the details of the associated race of a card
  • perform create/get/update/delete operations using the same serializer

For this, let's further change the CardSerializer to include another field called race_detail:

class CardSerializer(serializers.ModelSerializer):
    race_detail = RaceSerializer(source='card_race', read_only=True)

    class Meta:
        model = Card
        fields = ('id', 'card_text', 'card_description', 'card_race', 'race_detail')

We have defined two serializer fields for the same model field. Note the source and read_only attributes. This makes this field available when you GET a card (which is what we want), but not when you're performing POSTs or PUTs (which avoids the problem of sending the whole race object and parsing and stuff). You can just send the race id for the card_race field and it should work.

slider
  • 12,810
  • 1
  • 26
  • 42
  • 1
    Thank you very much! I made the changes as suggested but got a "'Race' object is not iterable" error. Removing the 'many=True' (or changing it to False) solved this problem and everything works perfectly! Obviously the front end needed changing to be 'c.race_detail.title' to pull through the different object. – mwade Oct 12 '18 at 13:10
  • @mwade Ah yes, a `Card` has only one `Race`, so no `many=True` needed. Good fix! I've updated my answer with that change. – slider Oct 12 '18 at 14:37