diff --git a/.github/workflows/django-test.yml b/.github/workflows/django-test.yml index 4514720d..c5f45215 100644 --- a/.github/workflows/django-test.yml +++ b/.github/workflows/django-test.yml @@ -10,6 +10,18 @@ on: jobs: django-test: runs-on: ubuntu-latest + + services: + redis: + image: redis + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + steps: - uses: actions/checkout@v2 diff --git a/poetry.lock b/poetry.lock index aca34eac..b6382fee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -321,6 +321,21 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""} argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] +[[package]] +name = "django-cacheops" +version = "7.0.2" +description = "A slick ORM cache with automatic granular event-driven invalidation for Django." +optional = false +python-versions = ">=3.7" +files = [ + {file = "django-cacheops-7.0.2.tar.gz", hash = "sha256:77a37c73d7facfc7299365ed66a12be7a487dd745dd86c1d33ca2ebfd3b32878"}, +] + +[package.dependencies] +django = ">=3.2" +funcy = ">=1.8,<3.0" +redis = ">=3.0.0" + [[package]] name = "django-cleanup" version = "6.0.0" @@ -579,6 +594,17 @@ category = "main" optional = false python-versions = ">=3.8" +[[package]] +name = "funcy" +version = "2.0" +description = "A fancy and practical functional tools" +optional = false +python-versions = "*" +files = [ + {file = "funcy-2.0-py2.py3-none-any.whl", hash = "sha256:53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0"}, + {file = "funcy-2.0.tar.gz", hash = "sha256:3963315d59d41c6f30c04bc910e10ab50a3ac4a225868bfa96feed133df075cb"}, +] + [[package]] name = "hyperlink" version = "21.0.0" diff --git a/procollab/settings.py b/procollab/settings.py index 1d54ce63..410dd882 100644 --- a/procollab/settings.py +++ b/procollab/settings.py @@ -108,6 +108,7 @@ "channels", "taggit", "django_prometheus", + "cacheops", ] MIDDLEWARE = [ @@ -186,20 +187,30 @@ } } + REDIS_CONN_URL = "redis://127.0.0.1:6379" + CACHES = { "default": { - "BACKEND": "django_prometheus.cache.backends.filebased.FileBasedCache", - "LOCATION": "/var/tmp/django_cache", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": REDIS_CONN_URL, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, } } CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}} else: + REDIS_CONN_URL = "redis://redis:6379" + # fixme CACHES = { "default": { - "BACKEND": "django.core.cache.backends.redis.RedisCache", - "LOCATION": "redis://redis:6379", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": REDIS_CONN_URL, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, } } @@ -355,3 +366,9 @@ ) DATA_UPLOAD_MAX_NUMBER_FIELDS = None # for mailing + +CACHEOPS_REDIS = REDIS_CONN_URL + +CACHEOPS = { + "users.CustomUser": {"ops": "all", "timeout": 60 * 15}, +} diff --git a/projects/signals.py b/projects/signals.py index 59ea5d3b..2400b620 100644 --- a/projects/signals.py +++ b/projects/signals.py @@ -3,6 +3,7 @@ from chats.models import ProjectChat from projects.models import Collaborator, Project +from django.core.cache import cache @receiver(post_save, sender=Project) @@ -18,3 +19,7 @@ def create_project(sender, instance, created, **kwargs): Collaborator.objects.create( user=instance.leader, project=instance, role="Основатель" ) + + # invalidating cache from ProjectList view + keys = cache.keys("*project_list*") + cache.delete_many(keys) diff --git a/projects/views.py b/projects/views.py index 530252d9..1aa01e8d 100644 --- a/projects/views.py +++ b/projects/views.py @@ -10,6 +10,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from django.views.decorators.cache import cache_page +from django.utils.decorators import method_decorator from core.permissions import IsStaffOrReadOnly from core.serializers import SetLikedSerializer @@ -113,6 +115,10 @@ def post(self, request, *args, **kwargs): # set leader to current user return self.create(request, *args, **kwargs) + @method_decorator(cache_page(60 * 30, cache="default", key_prefix="project_list")) + def get(self, *args, **kwargs): + return super(ProjectList, self).get(*args, **kwargs) + class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Project.objects.get_projects_for_detail_view() diff --git a/pyproject.toml b/pyproject.toml index 4c88a97b..b4646515 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,9 +65,11 @@ django-stubs = {extras = ["compatible-mypy"], version = "^4.2.6"} djangorestframework-stubs = {extras = ["compatible-mypy"], version = "^3.14.4"} flake8-print = "^5.0.0" flake8-variables-names = "^0.0.6" +django-cacheops = "^7.0.2" pandas = "^2.2.1" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/vacancy/serializers.py b/vacancy/serializers.py index 2b1007cf..3a6d3442 100644 --- a/vacancy/serializers.py +++ b/vacancy/serializers.py @@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType from rest_framework import serializers + from core.models import Skill, SkillToObject from core.serializers import SkillToObjectSerializer from projects.models import Project