# Redis Caching in Django Rest Framework, Complete Guide

We will implement Redis caching in Django Rest Framework properly this time.

## Project Setup

*   I'm using Docker to start a Redis database server.
    

```bash
docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest
```

*   Start a Django project and install all required libraries.
    

```bash
pip install django djangorestframework django-redis
mkdir django-redis-example && cd django-redis-example
django-admin startproject config .
python manage.py startapp core
```

*   Update `config/settings.py` accordingly for the rest framework and Redis
    

```python
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "rest_framework",  # For Django REST Framework
    "core",  # For core application
]
# Connect to Redis server for cache backend
# LINK - https://github.com/jazzband/django-redis#configure-as-cache-backend
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://localhost:6379/1",
    }
}
# Set JSON as a default Renderer
REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
}
```

*   Create a Model and ModelViewSet to perform CRUD operation on this particular model.
    

```python
# core/models.py
class City(models.Model):
    name = models.CharField(max_length=100)
    state_code = models.CharField(max_length=100)
    state_name = models.CharField(max_length=100)
    country_code = models.CharField(max_length=100)
    country_name = models.CharField(max_length=100)

    def __str__(self):
        return "%s: %s: %s" % (self.name, self.state_code, self.country_code)
```

```python
# core/serializers.py
from core.models import City

class CitySerializer(serializers.ModelSerializer):
    class Meta:
        model = City
        fields = "__all__"
```

```python
# core/views.py
from rest_framework.viewsets import ModelViewSet

from core.models import City
from core.serializers import CitySerializer

class CityView(ModelViewSet):
    serializer_class = CitySerializer
    queryset = City.objects.all()

    http_method_names = ["get", "post", "patch", "delete"]
```

```python
# config/urls.py
from django.urls import path, include

from rest_framework.routers import DefaultRouter
from core.views import CityView

router = DefaultRouter()
router.register("", CityView, basename="root")

urlpatterns = [
    path("", include(router.urls)),
]
```

Here the basic setup is complete and we can List, Update, Retrieve and Delete from the City model at `localhost:8000`.

* * *

## Adding Caching in the view

Now we will modify ModelViewSet's `list()` function to cache the GET request data for 5 minutes.

```python
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

class CityView(ModelViewSet):
    ...
    @method_decorator(cache_page(300, key_prefix="city-view"))
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)
```

When we call the GET request for the first time, the response will be stored in our Redis database with two keys.

```bash
:1:views.decorators.cache.cache_header.city-view.cb4fbe6a5f6caec1f1715b85fecd2d7f.en-us.UTC
:1:views.decorators.cache.cache_page.city-view.GET.cb4fbe6a5f6caec1f1715b85fecd2d7f.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC
```

Here, `1` is the database name, we are using Django's default cache\_page therefore the prefix `views.decorators.cache` is always going to be the same for Redis, Memcached, or any other caching database. `city-view` is the key\_prefix we assigned in `cache_page` in our views file. We should always use unique keys. In the end `.en-us.UTC` is our project's `LANGUAGE_CODE` and `TIME_ZONE` which are already specified in the `settings.py` file.

* * *

**Problem:** Suppose we call the GET request and the response is cached for 5 minutes, now we updated something in the city model. We are not able to retrieve that updated data until the cached data is expired. Therefore we need to make sure whenever the data is updated in our database, the cache related to that model is also *updated* or *deleted*.

**Solution:** we will create a function that will delete the cached data by passing the key\_prefix we specified in the view which is`city-view` in our case.

```python
# config/utils.py
from django.core.cache import cache
from django.conf import settings

# LINK - https://github.com/jazzband/django-redis#scan--delete-keys-in-bulk
def delete_cache(key_prefix: str):
    """
    Delete all cache keys with the given prefix.
    """
    keys_pattern = f"views.decorators.cache.cache_*.{key_prefix}.*.{settings.LANGUAGE_CODE}.{settings.TIME_ZONE}"
    cache.delete_pattern(keys_pattern)
```

to use this function in our view we will override create, destroy, and partial\_update functions a little bit.

```python
from config.utils import delete_cache

class CityView(ModelViewSet):
    ...
    CACHE_KEY_PREFIX = "city-view"

    @method_decorator(cache_page(300, key_prefix=CACHE_KEY_PREFIX))
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    def create(self, request, *args, **kwargs):
        response = super().create(request, *args, **kwargs)
        delete_cache(self.CACHE_KEY_PREFIX)
        return response

    def destroy(self, request, *args, **kwargs):
        response = super().destroy(request, *args, **kwargs)
        delete_cache(self.CACHE_KEY_PREFIX)
        return response

    def partial_update(self, request, *args, **kwargs):
        response = super().partial_update(request, *args, **kwargs)
        delete_cache(self.CACHE_KEY_PREFIX)
        return response
```

The reason we are using a variable `response` to assign the super function instead of directly returning that is that if any validation error or object not found error is thrown. The cache will not be deleted. The proper update needs to be done before deleting it.

[Click here to check out the source code.](https://github.com/SagarYadav17/redis-with-drf)
