Backend Development 13 min read

Implementing Rate Limiting in Django Rest Framework: Theory, Code Walkthrough, and Testing

This article explains how to configure and implement request throttling in Django Rest Framework, walks through the relevant source code in throttling.py and views.py, demonstrates testing the limit, and shows how to trigger custom DingTalk alerts when the limit is exceeded.

New Oriental Technology
New Oriental Technology
New Oriental Technology
Implementing Rate Limiting in Django Rest Framework: Theory, Code Walkthrough, and Testing

The previous article introduced the concepts of debounce and throttle; this continuation demonstrates practical implementation on the backend using Django Rest Framework, with a semantic analysis of the framework's source code.

Software Versions

The following old and new versions were tested and are compatible:

Software

Old Version

New Version

python

3.8.16

3.11.2

django

3.0.5

4.1.5

djangorestframework

3.11.0

3.14.0

Source reading order:

1. python3.11/site-packages/rest_framework/throttling.py

2. python3.11/site-packages/rest_framework/views.py

3. python3.11/site-packages/rest_framework/exceptions.py

1. Simple Example Code

Assume a security requirement to limit a sensitive endpoint to no more than 30 requests per minute per authenticated user, returning a wait‑message and sending a DingTalk alert when the limit is exceeded. The flow diagram is shown below:

Configure a global throttling rule in settings.py under REST_FRAMEWORK to trigger when a user reaches 30 requests per minute:

Create a custom UserDisabledRateThrottle class that inherits from UserRateThrottle :

Assign this throttle class to the view by overriding the throttle_classes attribute:

With the code in place, the following sections dissect how Django Rest Framework implements the throttling mechanism.

2. Source Code Analysis (rest_framework/throttling.py)

The custom UserDisabledRateThrottle inherits from UserRateThrottle , which in turn inherits from SimpleRateThrottle . The abstract method get_cache_key() must be implemented; it combines scope and ident to form the cache key.

SimpleRateThrottle defines cache_format and requires subclasses to provide get_cache_key() . The key is built as throttle_%(scope)s_%(ident)s .

The core method allow_request() uses the generated cache key to decide whether to throttle:

Important parameters:

key : result of self.get_cache_key() .

history : list of timestamps stored in the cache (FIFO queue).

duration and num_requests : parsed from DEFAULT_THROTTLE_RATES (e.g., "30/minute" yields num_requests=30 , duration=60 ).

The method removes expired entries (lines 128‑129) and, if the history length exceeds num_requests (lines 130‑132), returns False to trigger throttling; otherwise it inserts the current timestamp and returns True .

Cache key generation can be verified via the database table cachetable (using CACHE_BACKEND = 'db://cachetable' ). Example SQL and interactive shell commands are shown below:

通过SQL语句查看库中的cache_key值:
(py3) mbp:src $ ./manage.py dbshell
mysql> desc cachetable;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| cache_key | varchar(255) | NO   | PRI | NULL    |       |
| value     | longtext     | NO   |     | NULL    |       |
| expires   | datetime(6)  | NO   | MUL | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
访问一下接口,缓存里出现一条数据:
mysql> select * from cachetable where cache_key like '%throttle_%';
+----------------------+----------------------------------+----------------------------+
| cache_key            | value                            | expires                    |
+----------------------+----------------------------------+----------------------------+
| :1:throttle_user_274 | gAWVDQAAAAAAAABdlEdB2PZuE43BZWEu | 2023-02-01 08:55:38.000000 |
+----------------------+----------------------------------+----------------------------+
1 row in set (0.00 sec)
通过代码获取value值:
(py3) mbp:src $ ./manage.py shell_plus
# 设置datatable为cache
>>> from django.core.cache import cache as default_cache
>>> cache = default_cache
# 设置key值为上面的cache_key值:
>>> key = "throttle_user_274"
# 查看用来测试的用户user_id:
>>> User.objects.filter(username='admin').first().id
274
# 注意:我在访问接口时使用的用户是admin,此时查到的admin的id是274,与上面cache_key中的274吻合
# scope的值:UserRateThrottle()中已将其设置为"user"
# ident的值:用户admin的user_id,即为274
# 将上述2个值作为变量代入 cache_format = 'throttle_%(scope)s_%(ident)s',则get_cache_key()结果值为"throttle_user_274",与上面的cache_key值结果相符
获取访问的1次接口的对应value:
>>> cache.get(key, [])
[1675212878.2149289]
再访问3次接口,列表数据内容变成4条(从队头插入):
>>> cache.get(key, [])
[1675212918.3631868, 1675212917.43252, 1675212915.0148559, 1675212878.2149289]

In summary, allow_request() in throttling.py implements the cache‑key generation and the decision logic for rate limiting.

3. Source Code Analysis (rest_framework/views.py)

The throttling hook is invoked from the view hierarchy. The view class inherits from ReadOnlyModelViewSet , which extends GenericViewSet and ultimately generics.GenericAPIView → APIView :

The APIView class defines common attributes such as throttle_classes , authentication_classes , and permission_classes . During initialization it calls check_throttles() :

check_throttles() uses the previously described allow_request() . If it returns False , the non‑empty throttle_durations list triggers self.throttle() , which raises a throttling exception:

The exception class defined in rest_framework/exceptions.py formats the response message and sets the HTTP status code (429 Too Many Requests):

Thus the throttling flow is fully connected across the three core files.

4. Testing the Rate Limiting

After deployment, frequent requests trigger the limit, and the API returns a response identical to the image below until the next time window begins:

The log shows the HTTP 429 status code (defined in Throttled.status_code = status.HTTP_429_TOO_MANY_REQUESTS ):

The custom DingTalk alert logic executes as expected:

All tests confirm the throttling behaves as intended.

Conclusion

The overall throttling workflow is illustrated in the final diagram:

Implementing rate limiting in Django Rest Framework requires three steps:

Set the throttling threshold.

Write the handling logic (custom throttle class and optional alert).

Attach the throttle to the view class.

Key source files:

throttling.py : cache‑key construction and limit decision logic.

views.py : view‑level hook that invokes throttling.

exceptions.py : formats the throttling error response.

For further questions, feel free to contact the author via DingTalk. Thank you for reading.

backendPythonDjangoREST APIrate limitingthrottling
New Oriental Technology
Written by

New Oriental Technology

Practical internet development experience, tech sharing, knowledge consolidation, and forward-thinking insights.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.