2

I have created an API in DRF which accepts POST request with some data, But sometimes I feel that same requests are coming in parallel which causing the duplicate data in DB.

class Feedback(models.Model):
    user = models.ForeignKey(Student)
    message = models.CharField(max_length=255)

Use can send same feedback multiple time. Let's think it's an open API so anyone can consume it and in someone's app user clicks multiple time on button and I received multiple requests but data should be saved only one time.

I have tried by adding a BooleanField to Student table and used the following code to prevent it. But as multiple requests coming in parallel they can read the same value True.

if student.can_submit_feedback:
   student.can_submit_feedback = False
   student.save()
else:   
   # Code for saving feedback
   student.can_submit_feedback = True
   student.save() 

I want to process only one API call on same endpoint and same IP at a time. How can I achieve it?

Updated

I have researched and found that we can add lock on table or object but I am looking for prevention on requests level

3
  • @dfundako I have researched a lot and tried multiple solutions but none of them works for me perfectly that's why I posted my question here. Commented May 25, 2018 at 18:35
  • maybe you can solve on client side too, disable submit button until your request ends. Commented May 27, 2018 at 5:28
  • @aliva You are correct, but we should handle it on server side too because API can be open for anyone. Commented May 27, 2018 at 9:28

3 Answers 3

2

The parallel requests can be prevented by using throttling in DRF. Basic configuration are as follow:

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day', # 100 requests per day, 
        'user': '1000/day'
    }
}

day can be replaced by second, minute, hour or day as per the requirement.

If you want to configure different rate limits for different request method like GET, POST and PUT. You can simply create your own throttle. Here is the example of throttle for GET request.

class CustomBaseThrottle(rest_throttles.SimpleRateThrottle):
    """
    Limits the rate of API calls.

    The IP address, url and request method will be used for make unique key for anonymous user.
    The user id, url and request method will be used for make unique key for authenticated user.
    """
    scope = 'get'

    def get_cache_key(self, request, view):

        if request.method.lower() == self.scope:
            if is_authenticated(request.user):
                return "{}-{}-{}".format(request.user.id, request.path, request.method)
            return "{}-{}-{}".format(self.get_ident(request), request.path, request.method)
        return None

Refer http://www.django-rest-framework.org/api-guide/throttling/ for more details

Sign up to request clarification or add additional context in comments.

Comments

0

It sounds like you want to enforce some uniqueness in your model. You didn't provide any code, but here's a Student model, for example:

class Student
    first_name = models.CharField(max_length=20)
    last_name = models.CharField(max_length=20)
    date_of_birth = models.DateField()
    admission_date = models.DateField()

    class Meta:
        unique_together = ['first_name', 'last_name', 'date_of_birth', 'admission_date']

Then, in your view that creates the student, you'll have to return an HTTP error code if the student already exists:

def create_student(request):
    new_student = Student(first_name=request.POST['first_name'],
                          last_name=request.POST['last_name'],
                          date_of_birth=request.POST['date_of_birth'],
                          admission_date=request.POST['admission_date'])
    try:
        new_student.save()
    except IntegrityError:
        response = HttpResponse("Student already created")
        response.status_code = 409    # code for conflict
        return response

    return HttpResponse("OK, new student created")

NOTE: You might want to think about your design if you will have multiple duplicate students being created in parallel.

2 Comments

it's not a good idea to put unique together on all fields. Let model have two fields user and message and user can send same message multiple time, but I want to prevent this when API hit is parallel from same IP.
I have updated the question, Please have a look again.
0

If you still have issues, you can do in this way:

from django.db import transaction
import datetime


@transaction.atomic
def my_view_function(student: Student):

    student = Student.objects.filter(
        id=student.id, 
        last_feedback=student.last_feedback
    ).update(
        last_feedback=datetime.datetime.now()
    )

    if student is True:
        # feedback models and rules
        pass

Then when you store it on database you will do:

try:
    my_view_function(student)
except:
    print("Unable to issue the feedback as other feedback is just issued")

Comments

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.