3

I am currently working on building an API for an uni project. I am facing a problem with generating an auth token in a custom way. Let me explain: I am supposed to receive a POST request in an endpoint with the following:

{
   "university_id": 1,
    "coords":{
        "latitude": 0.0,
        "longitude": 0.0
    }
}

The idea is that, given the university_id and the coords, The backend validates it (checks if the coordinates are inside the valid area) and then, returns a token like so:

{
    "token": asdfsagag23214,
}

As you can see, there're no login credentials involved (nor a User model for the client application), so my guess was that I would need to create a custom token. I looked up Django REST Framework documentation, and came up with something like this for my token model:

class AuthToken(models.Model):
    key = models.CharField(verbose_name='Key', max_length=40, primary_key=True)
    created = models.DateTimeField(
        verbose_name='Creation date', auto_now_add=True)

    class Meta:
        verbose_name = 'Token'
        verbose_name_plural = 'Tokens'

    def save(self, *args, **kwargs):
        if not self.key:
            self.key = self.generate_key()
        return super().save(*args, **kwargs)

    def generate_key(self):
        return binascii.hexlify(os.urandom(20)).decode()

    def __str__(self):
        return self.key

and then, it's serializer:

class AuthTokenSerializer(serializers.Serializer):
    class Meta:
        model = AuthToken
        fields = ('key', 'created', )

    def to_internal_value(self, data):
        university = data.get('university')
        coords = data.get('coords')

        if not university:
            raise serializers.ValidationError({
                'university': 'This field is required.'
            })
        if not coords:
            raise serializers.ValidationError({
                'coords': 'This field is required.'
            })

        # coordinate validation goes here

        return {
            'university': int(university),
            'coords': coords
        }

    def create(self, validated_data):
        return AuthToken.objects.create(**validated_data)

And finally, the views.py:

@api_view(['POST'])
def generate_token(request):
    if request.method == 'POST':
        serializer = AuthTokenSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

I am not sure what am I missing, but I can't wrap my head around what I need to do to make this work properly. Right now I am using swagger to test it, and it does not work in any way, shape or form, There's no parameters to input, and even using cURL via terminal does not seem to give me the expected results.

For the record I am using Django 2.1 and Django Rest framework 3.8.2.

I would appreciate any help, as well as any further comments about this code (I am still learning after all). I am guessing that I am missing methods, but not sure where.

1 Answer 1

2

I am going step by step.

first, you need to define a serializer for coords because the standard way to get and validate the user input is through the serializers. so:

class Coordinates(serializers.Serializer):
    latitude = serializers.FloatField(min_value=-90, max_value=90)
    longitude = serializers.FloatField(min_value=-180, max_value=180)

so setting min and max value for the fields does enough validation I guess. if you want more validation on that fields you could use field level validation.

second, use serializer.ModelSerializer for AuthTokenSerializer, then add this field to it:

    coords = Coordinates(write_only=True)

and make it write_only.

also you can use PrimaryKeyRelatedField for getting the university like this:

    university = serializers.PrimaryKeyRelatedField(queryset=University.objects.all(), write_only=True)

so far your serializer should be like:

class AuthTokenSerializer(serializers.ModelSerializer):
    university = serializers.PrimaryKeyRelatedField(queryset=University.objects.all(), write_only=True)
    coords = Coordinates(write_only=True)

    class Meta:
        model = AuthToken
        fields = ('key', 'created', 'coords', 'university')

in the next step, you should make key and created read_only, because you don't want to get their values from the user input, you only want to show their value to the user after creating the AuthToken instance. so add them to read_only_fields in the Meta class.

in the next step, you should override the serializer validate method, and do the validation that is based on the more than one field (

checks if the coordinates are inside the valid area

)

    def validate(self, attrs):
        ### the the validation here, something like this
        ### you can access the selected university instance: attrs['university']

        if attrs['coords']['latitude'] < attrs['university']:# blah blah blah, anyway you want to validate
            raise serializers.ValidationError({'coords': ['invalid coords']})

        return super().validate(attrs)

so in the last step you should override the create method and pop the fields that you don't have them in your model(university and coords):

    def create(self, validated_data):
        ###  your AuthToken does not have this field, you should pop university and coords
        ###  out before creating an instance of Authtoken
        validated_data.pop('university')
        validated_data.pop('coords')

        return super().create(validated_data)

finally you serializer going to be like:

class AuthTokenSerializer(serializers.ModelSerializer):
    university = serializers.PrimaryKeyRelatedField(queryset=University.objects.all(), write_only=True)
    coords = Coordinates(write_only=True)

    class Meta:
        model = AuthToken
        fields = ('key', 'created', 'coords', 'university')
        read_only_fields = ('key', 'created')


    def validate(self, attrs):
        ### the the validation here, something like this
        ### you can access the selected university instance: attrs['university']

        if attrs['coords']['latitude'] < attrs['university']:# blah blah blah, anyway you want to validate
            raise serializers.ValidationError({'coords': ['invalid coords']})

        return super().validate(attrs)


    def create(self, validated_data):
        ###  your AuthToken does not have this field, you should pop university and coords
        ###  out before createing an instance of Authtoken
        validated_data.pop('university')
        validated_data.pop('coords')

        return super().create(validated_data)
Sign up to request clarification or add additional context in comments.

1 Comment

That did the trick. I was trying to find exactly how to insert the needed validation, and I didn't know I would need to create a separate serializer for the coordinates. Thanks a lot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.