Chapter 3.  Improving and Adding Authentication to an API With Django

In this chapter, we will improve the RESTful API that we started in the previous chapter and also add authentication related security to it. We will:

  • Add unique constraints to the models
  • Update a single field for a resource with the PATCH method
  • Take advantage of pagination
  • Customize pagination classes
  • Understand authentication, permissions and throttling
  • Add security-related data to the models
  • Create a customized permission class for object-level permissions
  • Persist the user that makes a request
  • Configure permission policies
  • Set a default value for a new required field in migrations
  • Compose requests with the necessary authentication
  • Browse the API with authentication credentials

Adding unique constraints to the models

Our API has a few issues that we need to solve. Right now, it is possible to create many game categories with the same name. We shouldn't be able to do so, and therefore, we will make the necessary changes to the GameCategory model to add a unique constraint on the name field. We will also add a unique constraint on the name field for the Game and Player models. This way, we will learn the necessary steps to make changes to the constraints for many models and reflect the changes in the underlying database through migrations.

Make sure that you quit Django's development server. Remember that you just need to press Ctrl + C in the terminal or Command Prompt window in which it is running. Now, we will make changes to introduce unique constraints to the name field for the models that we use to represent and persist the game categories, games, and players. Open the games/models.py, file and replace the code that declares the GameCategory, Game and Player classes with the following code. The three lines that change are highlighted in the code listing. The code for the PlayerScore class remains the same. The code file for the sample is included in the restful_python_chapter_03_01 folder, as shown:

class GameCategory(models.Model): 
    name = models.CharField(max_length=200, unique=True) 
 
    class Meta: 
        ordering = ('name',) 
 
    def __str__(self): 
        return self.name 
 
 
class Game(models.Model): 
    created = models.DateTimeField(auto_now_add=True) 
    name = models.CharField(max_length=200, unique=True) 
    game_category = models.ForeignKey( 
        GameCategory,  
        related_name='games',  
        on_delete=models.CASCADE) 
    release_date = models.DateTimeField() 
    played = models.BooleanField(default=False) 
 
    class Meta: 
        ordering = ('name',) 
 
    def __str__(self): 
        return self.name 
 
 
class Player(models.Model): 
    MALE = 'M' 
    FEMALE = 'F' 
    GENDER_CHOICES = ( 
        (MALE, 'Male'), 
        (FEMALE, 'Female'), 
    ) 
    created = models.DateTimeField(auto_now_add=True) 
    name = models.CharField(max_length=50, blank=False, default='', unique=True) 
    gender = models.CharField( 
        max_length=2, 
        choices=GENDER_CHOICES, 
        default=MALE, 
    ) 
 
    class Meta: 
        ordering = ('name',) 
 
    def __str__(self): 
        return self.name 

We just needed to add unique=True as one of the named arguments for models.CharField. This way, we indicate that the field must be unique and Django will create the necessary unique constraints for the fields in the underlying database tables.

Now, run the following Python script to generate the migrations that will allow us to synchronize the database with the unique constraints we added for the fields in the models:

python manage.py makemigrations games

The following lines show the output generated after running the previous command:

Migrations for 'games':
  0002_auto_20160623_2131.py:
    - Alter field name on game
    - Alter field name on gamecategory
    - Alter field name on player

The output indicates that the gamesapi/games/migrations/0002_auto_20160623_2131.py file includes the code to alter the field named name on game, gamecategory, and player. Note that the generated file name will be different in your configuration because it includes an encoded date and time. The following lines show the code for this file, which was automatically generated by Django. The code file for the sample is included in the restful_python_chapter_03_01 folder:

# -*- coding: utf-8 -*- 
# Generated by Django 1.9.7 on 2016-06-23 21:31 
from __future__ import unicode_literals 
 
from django.db import migrations, models 
 
 
class Migration(migrations.Migration): 
 
    dependencies = [ 
        ('games', '0001_initial'), 
    ] 
 
    operations = [ 
        migrations.AlterField( 
            model_name='game', 
            name='name', 
            field=models.CharField(max_length=200, unique=True), 
        ), 
        migrations.AlterField( 
            model_name='gamecategory', 
            name='name', 
            field=models.CharField(max_length=200, unique=True), 
        ), 
        migrations.AlterField( 
            model_name='player', 
            name='name', 
            field=models.CharField(default='', max_length=50, unique=True), 
        ), 
    ] 

The code defines a subclass of the django.db.migrations.Migration class named Migration that defines an operations list with many migrations.AlterField. Each migrations.AlterField will alter the field in the the table for each of the related models.

Now, run the following Python script to apply all the generated migrations and execute the changes in the database tables:

python manage.py migrate

The following lines show the output generated after running the previous command. Note that the ordering for the migrations might be different in your configuration.

Operations to perform:
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, games, sessions
Running migrations:
  Rendering model states... DONE
  Applying games.0002_auto_20160623_2131... OK

After we run the preceding command, we will have unique indexes on the name field for the games_game, games_gamecategory, and games_player tables in the PostgreSQL database. We can use the PostgreSQL command line or any other application that allows us to easily check the contents of the PostreSQL database to check the tables that Django updated. In case you decide to continue working with SQLite, use the commands or tools related to this database.

Now, we can launch Django's development server to compose and send HTTP requests. Execute any of the following two commands based on your needs to access the API in other devices or computers connected to your LAN. Remember that we analyzed the difference between them in Chapter 1, Developing RESTful APIs with Django:

python manage.py runserver
python manage.py runserver 0.0.0.0:8000

After we run any of the previous commands, the development server will start listening at port 8000.

Now, we will compose and send an HTTP request to create a game category with a name that already exists: '3D RPG':

http POST :8000/game-categories/ name='3D RPG'

The following is the equivalent curl command:

curl -iX POST -H "Content-Type: application/json" -d '{"name":"3D RPG"}'
    :8000/game-categories/

Django won't be able to persist a GameCategory instance whose name is equal to the specified value because it would violate the unique constraint added to the name field. Thus, we will receive a 400 Bad Request status code in the response header and a message related to the value specified for name in the JSON body. The following lines show a sample response:

HTTP/1.0 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Date: Sun, 26 Jun 2016 03:37:05 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
{
    "name": [
        "GameCategory with this name already exists."
    ]
}

After we have made the changes, we won't be able to add duplicate values for the name field in game categories, games, or players. This way, we can be sure that whenever we specify the name of any of these resources, we are going to reference the same unique resource.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.79.241