Django Redis caching with django-cacheops. This skill should be used when implementing caching, adding cache invalidation, optimizing API performance, modifying models that affect cached data, or debugging cache-related issues in the Django backend.
Implements Redis caching with django-cacheops for the DTX Django backend.
All models use 1-hour cache timeout (configured in settings.py):
CACHEOPS = {
"app.tournament": {"ops": "all", "timeout": 60 * 60},
"app.team": {"ops": "all", "timeout": 60 * 60},
"app.customuser": {"ops": "all", "timeout": 60 * 60},
"app.draft": {"ops": "all", "timeout": 60 * 60},
"app.game": {"ops": "all", "timeout": 60 * 60},
"app.draftround": {"ops": "all", "timeout": 60 * 60},
}
from cacheops import cached_as
def list(self, request, *args, **kwargs):
cache_key = f"model_list:{request.get_full_path()}"
@cached_as(Model1, Model2, extra=cache_key, timeout=60 * 60)
def get_data():
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return serializer.data
return Response(get_data())
Preferred: invalidate_after_commit — use inside transactions, signal handlers, or anywhere writes may be deferred:
from app.cache_utils import invalidate_after_commit
with transaction.atomic():
user.nickname = "New"
user.save()
invalidate_after_commit(tournament, org_user, org_user.organization)
Direct invalidate_obj — safe only OUTSIDE transactions (e.g., after M2M .add()/.remove()):
from cacheops import invalidate_obj
org.admins.add(user) # M2M auto-commits
invalidate_obj(org) # Safe — data already committed
When to use which:
| Context | Use |
|---|---|
Inside transaction.atomic() | invalidate_after_commit() |
Inside @transaction.atomic decorator | invalidate_after_commit() |
Inside signal handlers (post_save, etc.) | invalidate_after_commit() |
After .save() / M2M ops outside transactions | Direct invalidate_obj() is safe |
After .update() / .bulk_create() | invalidate_after_commit() (defensive) |
When modifying data, invalidate these related caches:
| Changed Model | Also Invalidate |
|---|---|
| DraftRound | Tournament, Draft, Team |
| Draft | Tournament |
| Team | Tournament (if tournament-scoped) |
| Game | Tournament, Team |
| CustomUser | Team (if member changes) |
invalidate_after_commit: Default to deferred invalidation in transactions — see app/cache_utils.py@cached_as(Model1, Model2, ...) to auto-invalidatekeep_fresh=True for single-object retrievalDISABLE_CACHE=true python manage.py <command>
from cacheops import invalidate_all, invalidate_model, invalidate_obj
# Nuclear option - invalidate everything
invalidate_all()
# Invalidate all instances of a model
invalidate_model(Tournament)
# Invalidate specific instance
invalidate_obj(tournament)
from django.core.cache import cache
cache.set('test_key', 'test_value', 30)
print(cache.get('test_key')) # Should print 'test_value'
| Data Type | Timeout | Reason |
|---|---|---|
| Static data | 60 * 60 (1h) | Rarely changes |
| Tournament state | 60 * 10 (10m) | Changes during events |
| Draft rounds | 60 * 10 (10m) | Active during drafts |
| External API (Discord) | 15s | Rate limiting, freshness |