0

I'm creating a Django (1.8) webapp that saves racing laptimes and scoreboards. The database is populated using an API built using Django Rest Framework. It's the first time I'm trying to build a proper api using rest framework.

A quick overview of the models:

  • Event, A racing event/weekend
  • Session, A single race/practice/quali - FK Event
  • Car, A car taking part in a session - FK Session
  • Lap, Laps for specific car - FK Car

The Event is created manually, but the rest is supposed to be "dynamic" (get or create)

Right now I'm trying to create a new car using my API, but I'm stuck. To get the cars event and session I'm trying to use the url; /api/results/skrotbilsracet-29042016/r1/cars/

The idea is to post data to this url and "get or create" a new car object. To get the correct session object for the new car session FK, I need to use a custom function that takes the kwargs and tries to find the session.

The more I read about how to solve this, the more confused I get.

Could someone push me in the right direction?

This is my latest attempt at solving this, which just gives me "{"session":["This field is required."]}"

models.py

class Session(models.Model):
    session_types = (
        ('p', 'Practice'),
        ('q', 'Qualification'),
        ('r', 'Race')
    )
    event_id = models.ForeignKey(Event, related_name='sessions')
    name = models.CharField(max_length=2, blank=True)
    current_session = models.BooleanField(default=True)
    session_type = models.CharField(max_length=2,
                                    choices=session_types)
    started = models.DateTimeField(auto_now_add=True)
    ended = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['started']

    def save(self):
        if not self.name:
            # Get number of sessions
            session_count = Session.objects.filter(event_id=self.event_id)\
                            .filter(session_type=self.session_type)\
                            .count()
            session_count += 1
            self.name = self.session_type + str(session_count)

        super(Session, self).save()

    def __unicode__(self):
        string = self.started.strftime("%d-%m-%Y %H:%M") + ' - '
        string += self.name.upper()
        return(string)


class Car(models.Model):
    session = models.ForeignKey(Session, related_name='cars')
    number = models.IntegerField()
    full_name = models.CharField(max_length=256, blank=True)
    short_name = models.CharField(max_length=256, blank=True)
    race_class = models.CharField(max_length=50, blank=True)
    best_lap = models.IntegerField(blank=True, null=True)
    best_lap_time = models.CharField(max_length=20, blank=True)
    best_sector1 = models.CharField(max_length=20, blank=True)
    best_sector2 = models.CharField(max_length=20, blank=True)
    best_sector3 = models.CharField(max_length=20, blank=True)
    best_speed = models.IntegerField(blank=True, null=True)
    pitstops = models.IntegerField(blank=True, null=True)
    total_time = models.CharField(max_length=20, blank=True)
    transponder = models.CharField(max_length=50, blank=True)

apiUrls.py

urlpatterns = [
  url(r'^raceslug/$', raceSlugView.as_view(), name='race-slug'),
  url(r'^events/$', eventsView.as_view(), name='event-list'),
  url(r'^session/$', getSessionView.as_view(), name='session-pk'),
  url(r'^(?P<event_id>[a-z0-9\-]+)/$', eventView.as_view(), name='event-detail'),
  url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/$', sessionView.as_view(), name='session-detail'),
  url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/cars/$', carsView.as_view(), name='car-list'),
  url(r'^(?P<event_id>[a-z0-9\-]+)/(?P<name>[a-z0-9\-]+)/(?P<number>[0-9]+)/$', carView.as_view(), name='car-detail'),
]

urlpatterns = format_suffix_patterns(urlpatterns)

api.py

class carsView(generics.ListCreateAPIView):
    serializer_class = carSerializer

    def get_session(self, event_id, name):
        print('Getting session')
        # Get event object
        try:
            event = Event.objects.get(event_id=event_id)
            print('Found event')
        except ObjectDoesNotExist:
            print('Did not find event')
            return

        # Get session object
        try:
            session = event.sessions.get(name=name)
            print('Found session: ', session)
            return session
        except ObjectDoesNotExist:
            print('Did not find session')
            return


    def get_queryset(self):
        print('Getting queryset')
        print('event_id: ' + self.kwargs['event_id'])
        print('name: ' + self.kwargs['name'])
        session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
        return(Car.objects.filter(session=session.pk))


    def perform_create(self, serializer):
        print('Creating new car')
        session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
        serializer.save(session=session)

serializers.py

class carSerializer(serializers.ModelSerializer):

  laps = lapSerializer(many=True, read_only=True)

  class Meta:
    model = Car
    fields = (
        'session',
        'number',
        'full_name',
        'short_name',
        'race_class',
        'best_lap',
        'best_lap_time',
        'best_sector1',
        'best_sector2',
        'best_sector3',
        'best_speed',
        'pitstops',
        'total_time',
        'transponder',
        'laps')

Solution:
This is what I actually changed to get it working.

api.py

from rest_framework.serializers import ValidationError

class carsView(generics.ListCreateAPIView):
...
    def perform_create(self, serializer):
        print('Creating new car')
        session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
        number = self.request.POST.get('number')
        car = session.cars.filter(number=number)
        if car.exists():
            raise ValidationError('Car already exists')
        serializer.save(session=session)

serializers.py

class carSerializer(serializers.ModelSerializer):

    laps = lapSerializer(many=True, read_only=True)
    session = serializers.StringRelatedField(required=False)
    ...
  • in your car model, add blank=True, or provide a session id (or hyperlink if it's HyperlinedModelSerializer) on posting a car. – Karina Klinkevičiūtė May 18 '16 at 10:32
  • That is my actual question, what would be a working way of providing a session id extracted from the url (possibly using my get_session function)? The session is required, otherwise I would just have car objects floating around not connected to any actual race. – Andreas Björshammar May 18 '16 at 10:58
  • what are you using to access your API? any API client or smth? Or DRF's API browser? if DRF's browser, then there should be a form for posting and you should be able to see it there. If some client, for example Pycharm's client or Postman then you have to add it to POST parameters (in Postman it's in Body when you select POST method – Karina Klinkevičiūtė May 18 '16 at 13:54
  • I updated my answer. I added it to an answer because I wanted to paste code there – Karina Klinkevičiūtė May 18 '16 at 16:34

1 Answers1

0

I see that you're creating your session ID there:

def get_queryset(self):
       ...
        session = self.get_session(self.kwargs['event_id'], self.kwargs['name'])
        return(Car.objects.filter(session=session.pk))

Then you don't need it in a serializer, only in a model. So you can set it a snot required in a serializer, but it will still be required in a model.

I guess this answer could help you: Django REST Framework serializer field required=false

Community
  • 1
  • 1
  • I'm not creating my session ID, I get an existing session object by feeding my "get_session" function with the event name and session name. Booth of these are kwargs from the url, in this exmple: `r'^(?P[a-z0-9\-]+)/(?P[a-z0-9\-]+)/cars/$'` event = 'skrotbilsracet-29042016' session = 'r1' The get_session function returns a session object. How do I use this session object as the session FK when creating a new car object via the API? It works for the get_queryset method you pasted above (GET), but I can't get it to work for creating a new car (POST). – Andreas Björshammar May 19 '16 at 09:22
  • Yes, but you're not getting it from POST method from HTTP, from the browser. So you ARE creating it in your application. And this error is thrown when you don't get session from your POST done by your client. When posting a new car, you should refer to the link I provided. Also you can try marking your session variable as read_only in serializer, but not sure if it helps, it can still require it. Also if you are creating your session and feeding it to the model anyway, you can mark it blank=True (but not null=True) so the form won't require it. – Karina Klinkevičiūtė May 19 '16 at 13:06
  • in other words, if it is set as required, then a form requires it in the raw data that is coming from POST (not as calculated field). Your session field is not coming from HTTP POST, you calculate it. So it misses it in POST and complains. – Karina Klinkevičiūtė May 19 '16 at 13:08
  • Now I finally get it :-) Thank you for your help! Will update my question with specifics. – Andreas Björshammar May 19 '16 at 15:08