From 8c8cb2709c93068d1993049e41bdd27012d8b6f6 Mon Sep 17 00:00:00 2001 From: Ronan Boiteau Date: Tue, 5 Sep 2023 10:24:50 +0200 Subject: [PATCH 1/3] Add support for Django REST framework's --- django_ratelimit/core.py | 1 + django_ratelimit/tests.py | 15 +++++++++++++++ docs/keys.rst | 1 + test_settings.py | 1 + tox.ini | 1 + 5 files changed, 19 insertions(+) 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..fe603ef 100644 --- a/django_ratelimit/tests.py +++ b/django_ratelimit/tests.py @@ -7,6 +7,9 @@ from django.utils.decorators import method_decorator from django.views.generic import View +from rest_framework.decorators import api_view +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 +17,7 @@ rf = RequestFactory() +rest_rf = APIRequestFactory() class MockUser: @@ -152,6 +156,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 request.limited + + assert not view(rest_rf.post('/', {'foo': 'a'})) + assert view(rest_rf.post('/', {'foo': 'a'})) + assert not view(rest_rf.post('/', {'foo': 'b'})) + assert view(rest_rf.post('/', {'foo': 'b'})) + 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..944bb80 100644 --- a/test_settings.py +++ b/test_settings.py @@ -4,6 +4,7 @@ INSTALLED_APPS = ( 'django_ratelimit', + 'rest_framework', ) RATELIMIT_USE_CACHE = 'default' 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 From c2d58d07cbddd81ee3cab208fdbf5cc4010fa701 Mon Sep 17 00:00:00 2001 From: Ronan Boiteau Date: Mon, 20 May 2024 01:55:04 +0200 Subject: [PATCH 2/3] Update changelog --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) 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 ==== From faeb9e5b5d3c54c1954c92d53533e17fb498ab34 Mon Sep 17 00:00:00 2001 From: Ronan Boiteau Date: Mon, 3 Jun 2024 00:26:21 +0200 Subject: [PATCH 3/3] Update Django REST Framework tests --- .github/actions/test/action.yml | 2 +- django_ratelimit/tests.py | 11 ++++++----- test_settings.py | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) 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/django_ratelimit/tests.py b/django_ratelimit/tests.py index fe603ef..0d5f8b7 100644 --- a/django_ratelimit/tests.py +++ b/django_ratelimit/tests.py @@ -8,6 +8,7 @@ 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 @@ -160,12 +161,12 @@ def test_key_data(self): @api_view(['POST']) @ratelimit(key='data:foo', rate='1/m', block=False) def view(request): - return request.limited + return Response(request.limited) - assert not view(rest_rf.post('/', {'foo': 'a'})) - assert view(rest_rf.post('/', {'foo': 'a'})) - assert not view(rest_rf.post('/', {'foo': 'b'})) - assert view(rest_rf.post('/', {'foo': 'b'})) + 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(): diff --git a/test_settings.py b/test_settings.py index 944bb80..bcb2ff9 100644 --- a/test_settings.py +++ b/test_settings.py @@ -3,8 +3,10 @@ SILENCED_SYSTEM_CHECKS = ['django_ratelimit.E003', 'django_ratelimit.W001'] INSTALLED_APPS = ( - 'django_ratelimit', + 'django.contrib.auth', + 'django.contrib.contenttypes', 'rest_framework', + 'django_ratelimit', ) RATELIMIT_USE_CACHE = 'default'