diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index a9bbd60..31aa611 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -20,7 +20,7 @@ runs: python -m pip install --upgrade pip if [[ ${{ inputs.django-version }} != 'main' ]]; then pip install --pre -q "Django>=${{ inputs.django-version }},<${{ inputs.django-version }}.99"; fi if [[ ${{ inputs.django-version }} == 'main' ]]; then pip install https://github.com/django/django/archive/main.tar.gz; fi - pip install flake8 django-redis pymemcache + pip install flake8 django-redis pymemcache djangorestframework - name: Test shell: sh diff --git a/CHANGELOG b/CHANGELOG index 9ab5653..bb994f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,11 @@ Change Log UNRELEASED ========== +Additions: +---------- + +- Add support for rate limiting on Django REST Framework's `Request.data` + v4.1 ==== diff --git a/django_ratelimit/core.py b/django_ratelimit/core.py index 1270799..4d0e449 100644 --- a/django_ratelimit/core.py +++ b/django_ratelimit/core.py @@ -81,6 +81,7 @@ def get_header(request, header): _ACCESSOR_KEYS = { 'get': lambda r, k: r.GET.get(k, ''), 'post': lambda r, k: r.POST.get(k, ''), + 'data': lambda r, k: r.data.get(k, ''), 'header': get_header, } diff --git a/django_ratelimit/tests.py b/django_ratelimit/tests.py index a58c89e..0d5f8b7 100644 --- a/django_ratelimit/tests.py +++ b/django_ratelimit/tests.py @@ -7,6 +7,10 @@ from django.utils.decorators import method_decorator from django.views.generic import View +from rest_framework.decorators import api_view +from rest_framework.response import Response +from rest_framework.test import APIRequestFactory + from django_ratelimit.decorators import ratelimit from django_ratelimit.exceptions import Ratelimited from django_ratelimit.core import (get_usage, is_ratelimited, @@ -14,6 +18,7 @@ rf = RequestFactory() +rest_rf = APIRequestFactory() class MockUser: @@ -152,6 +157,17 @@ def view(request): assert not view(rf.post('/', {'foo': 'b'})) assert view(rf.post('/', {'foo': 'b'})) + def test_key_data(self): + @api_view(['POST']) + @ratelimit(key='data:foo', rate='1/m', block=False) + def view(request): + return Response(request.limited) + + assert not view(rest_rf.post('/', {'foo': 'a'})).data + assert view(rest_rf.post('/', {'foo': 'a'})).data + assert not view(rest_rf.post('/', {'foo': 'b'})).data + assert view(rest_rf.post('/', {'foo': 'b'})).data + def test_key_header(self): def _req(): req = rf.post('/') diff --git a/docs/keys.rst b/docs/keys.rst index 278eaf9..4d3dad9 100644 --- a/docs/keys.rst +++ b/docs/keys.rst @@ -25,6 +25,7 @@ used ratelimit keys: ` notes. - ``'get:X'`` - Use the value of ``request.GET.get('X', '')``. - ``'post:X'`` - Use the value of ``request.POST.get('X', '')``. +- ``'data:X'`` - Use the value of ``request.data.get('X', '')`` (useful for projects using Django REST Framework). - ``'header:x-x'`` - Use the value of ``request.META.get('HTTP_X_X', '')``. diff --git a/test_settings.py b/test_settings.py index 0245d39..bcb2ff9 100644 --- a/test_settings.py +++ b/test_settings.py @@ -3,6 +3,9 @@ SILENCED_SYSTEM_CHECKS = ['django_ratelimit.E003', 'django_ratelimit.W001'] INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'rest_framework', 'django_ratelimit', ) diff --git a/tox.ini b/tox.ini index 636583e..5c5f514 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ deps = django42: Django>=4.2,<4.3 django50: Django>=5.0a1,<5.1 djangomain: https://github.com/django/django/archive/main.tar.gz + djangorestframework~=3.15.1 pymemcache>=4.0,<5.0 django-redis>=5.2,<6.0 flake8