Просмотр исходного кода

Merge pull request #1139 from rafalp/extract-cache-to-app

Create basic misago.cache application
Rafał Pitoń 6 лет назад
Родитель
Сommit
2e7d18a145

+ 2 - 0
devproject/settings.py

@@ -188,6 +188,7 @@ INSTALLED_APPS = [
     # Misago apps
     'misago.admin',
     'misago.acl',
+    'misago.cache',
     'misago.core',
     'misago.conf',
     'misago.markup',
@@ -223,6 +224,7 @@ MIDDLEWARE = [
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 
+    'misago.cache.middleware.cache_versions_middleware',
     'misago.users.middleware.UserMiddleware',
     'misago.core.middleware.ExceptionHandlerMiddleware',
     'misago.users.middleware.OnlineTrackerMiddleware',

+ 2 - 10
misago/acl/migrations/0002_acl_version_tracker.py

@@ -1,20 +1,12 @@
 from django.db import migrations
 
-from misago.acl.constants import ACL_CACHEBUSTER
-from misago.core.migrationutils import cachebuster_register_cache
-
-
-def register_acl_version_tracker(apps, schema_editor):
-    cachebuster_register_cache(apps, ACL_CACHEBUSTER)
-
 
 class Migration(migrations.Migration):
+    """Superseded by 0004"""
 
     dependencies = [
         ('misago_acl', '0001_initial'),
         ('misago_core', '0001_initial'),
     ]
 
-    operations = [
-        migrations.RunPython(register_acl_version_tracker),
-    ]
+    operations = []

+ 16 - 0
misago/acl/migrations/0004_cache_version.py

@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+from django.db import migrations
+
+from misago.cache.operations import StartCacheVersioning
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('misago_acl', '0003_default_roles'),
+        ('misago_cache', '0001_initial'),
+    ]
+
+    operations = [
+        StartCacheVersioning("acl")
+    ]

+ 1 - 0
misago/cache/__init__.py

@@ -0,0 +1 @@
+default_app_config = 'misago.cache.apps.MisagoCacheConfig'

+ 7 - 0
misago/cache/apps.py

@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class MisagoCacheConfig(AppConfig):
+    name = 'misago.cache'
+    label = 'misago_cache'
+    verbose_name = "Misago Cache"

+ 0 - 0
misago/cache/management/__init__.py


+ 0 - 0
misago/cache/management/commands/__init__.py


+ 11 - 0
misago/cache/management/commands/invalidateversionedcaches.py

@@ -0,0 +1,11 @@
+from django.core.management.base import BaseCommand
+
+from misago.cache.versions import invalidate_all_caches
+
+
+class Command(BaseCommand):
+    help = 'Invalidates versioned caches'
+
+    def handle(self, *args, **options):
+        invalidate_all_caches()
+        self.stdout.write("Invalidated all versioned caches.")

+ 12 - 0
misago/cache/middleware.py

@@ -0,0 +1,12 @@
+from django.utils.functional import SimpleLazyObject
+
+from .versions import get_cache_versions
+
+
+def cache_versions_middleware(get_response):
+    """Sets request.cache_versions attribute with dict of cache versions."""
+    def middleware(request):
+        request.cache_versions = SimpleLazyObject(get_cache_versions)
+        return get_response(request)
+
+    return middleware

+ 24 - 0
misago/cache/migrations/0001_initial.py

@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.16 on 2018-11-25 15:15
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import misago.cache.utils
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='CacheVersion',
+            fields=[
+                ('cache', models.CharField(max_length=128, primary_key=True, serialize=False)),
+                ('version', models.CharField(default=misago.cache.utils.generate_version_string, max_length=8)),
+            ],
+        ),
+    ]

+ 0 - 0
misago/cache/migrations/__init__.py


+ 8 - 0
misago/cache/models.py

@@ -0,0 +1,8 @@
+from django.db import models
+
+from .utils import generate_version_string
+
+
+class CacheVersion(models.Model):
+    cache = models.CharField(max_length=128, primary_key=True)
+    version = models.CharField(max_length=8, default=generate_version_string)

+ 29 - 0
misago/cache/operations.py

@@ -0,0 +1,29 @@
+from django.db.migrations import RunPython
+
+
+class StartCacheVersioning(RunPython):
+    def __init__(self, cache):
+        code = start_cache_versioning(cache)
+        reverse_code = stop_cache_versioning(cache)
+        super().__init__(code, reverse_code)
+
+
+class StopCacheVersioning(RunPython):
+    def __init__(self, cache):
+        code = stop_cache_versioning(cache)
+        reverse_code = start_cache_versioning(cache)
+        super().__init__(code, reverse_code)
+
+
+def start_cache_versioning(cache):
+    def migration_operation(apps, _):
+        CacheVersion = apps.get_model('misago_cache', 'CacheVersion')
+        CacheVersion.objects.create(cache=cache)
+    return migration_operation
+
+
+def stop_cache_versioning(cache):
+    def migration_operation(apps, _):
+        CacheVersion = apps.get_model('misago_cache', 'CacheVersion')
+        CacheVersion.objects.filter(cache=cache).delete()
+    return migration_operation

+ 0 - 0
misago/cache/tests/__init__.py


+ 5 - 0
misago/cache/tests/conftest.py

@@ -0,0 +1,5 @@
+from misago.cache.models import CacheVersion
+
+
+def cache_version():
+    return CacheVersion.objects.create(cache="test_cache")

+ 50 - 0
misago/cache/tests/test_cache_versions_middleware.py

@@ -0,0 +1,50 @@
+from unittest.mock import Mock, PropertyMock, patch
+
+from django.test import TestCase
+from django.utils.functional import SimpleLazyObject
+
+from misago.cache.versions import CACHE_NAME
+from misago.cache.middleware import cache_versions_middleware
+
+
+class MiddlewareTests(TestCase):
+    def test_middleware_sets_attr_on_request(self):
+        get_response = Mock()
+        request = Mock()
+        cache_versions = PropertyMock()
+        type(request).cache_versions = cache_versions
+        middleware = cache_versions_middleware(get_response)
+        middleware(request)
+        cache_versions.assert_called_once()
+
+    def test_attr_set_by_middleware_on_request_is_lazy_object(self):
+        get_response = Mock()
+        request = Mock()
+        cache_versions = PropertyMock()
+        type(request).cache_versions = cache_versions
+        middleware = cache_versions_middleware(get_response)
+        middleware(request)
+        attr_value = cache_versions.call_args[0][0]
+        assert isinstance(attr_value, SimpleLazyObject)
+
+    def test_middleware_calls_get_response(self):
+        get_response = Mock()
+        request = Mock()
+        middleware = cache_versions_middleware(get_response)
+        middleware(request)
+        get_response.assert_called_once()
+
+    def test_middleware_is_not_reading_db(self):
+        get_response = Mock()
+        request = Mock()
+        with self.assertNumQueries(0):
+            middleware = cache_versions_middleware(get_response)
+            middleware(request)
+
+    @patch('django.core.cache.cache.get')
+    def test_middleware_is_not_reading_cache(self, cache_get):
+        get_response = Mock()
+        request = Mock()
+        middleware = cache_versions_middleware(get_response)
+        middleware(request)
+        cache_get.assert_not_called()

+ 44 - 0
misago/cache/tests/test_getting_cache_versions.py

@@ -0,0 +1,44 @@
+from unittest.mock import patch
+
+from django.test import TestCase
+
+from misago.cache.versions import (
+    CACHE_NAME, get_cache_versions, get_cache_versions_from_cache, get_cache_versions_from_db
+)
+
+
+class CacheVersionsTests(TestCase):
+    def test_db_getter_returns_cache_versions_from_db(self):
+        cache_versions = get_cache_versions_from_db()
+        assert cache_versions
+
+    @patch('django.core.cache.cache.get', return_value=True)
+    def test_cache_getter_returns_cache_versions_from_cache(self, cache_get):
+        assert get_cache_versions_from_cache() is True
+        cache_get.assert_called_once_with(CACHE_NAME)
+
+    @patch('django.core.cache.cache.get', return_value=True)
+    def test_getter_reads_from_cache(self, cache_get):
+        assert get_cache_versions() is True
+        cache_get.assert_called_once_with(CACHE_NAME)
+
+    @patch('django.core.cache.cache.set')
+    @patch('django.core.cache.cache.get', return_value=None)
+    def test_getter_reads_from_db_when_cache_is_not_available(self, cache_get, _):
+        db_caches = get_cache_versions_from_db()
+        assert get_cache_versions() == db_caches
+        cache_get.assert_called_once_with(CACHE_NAME)
+
+    @patch('django.core.cache.cache.set')
+    @patch('django.core.cache.cache.get', return_value=None)
+    def test_getter_sets_new_cache_if_no_cache_is_set(self, _, cache_set):
+        assert get_cache_versions()
+        db_caches = get_cache_versions_from_db()
+        cache_set.assert_called_once_with(CACHE_NAME, db_caches)
+
+    @patch('django.core.cache.cache.set')
+    @patch('django.core.cache.cache.get', return_value=True)
+    def test_getter_is_not_setting_new_cache_if_cache_is_set(self, _, cache_set):
+        assert get_cache_versions()
+        db_caches = get_cache_versions_from_db()
+        cache_set.assert_not_called()

+ 11 - 0
misago/cache/tests/test_invalidate_caches_management_command.py

@@ -0,0 +1,11 @@
+from unittest.mock import Mock, patch
+
+from django.core.management import call_command
+from django.test import TestCase
+
+
+class InvalidateCachesManagementCommandTests(TestCase):
+    @patch("misago.cache.versions.invalidate_all_caches")
+    def test_management_command_invalidates_all_caches(self, invalidate_all_caches):
+        call_command('invalidateversionedcaches', stdout=Mock())
+        invalidate_all_caches.assert_called_once()

+ 38 - 0
misago/cache/tests/test_invalidating_caches.py

@@ -0,0 +1,38 @@
+from unittest.mock import patch
+
+from django.test import TestCase
+
+from misago.cache.versions import (
+    CACHE_NAME, get_cache_versions_from_db, invalidate_cache, invalidate_all_caches
+)
+from misago.cache.models import CacheVersion
+
+from .conftest import cache_version
+
+
+class InvalidatingCacheTests(TestCase):
+    @patch('django.core.cache.cache.delete')
+    def test_invalidating_cache_updates_cache_version_in_database(self, _):
+        test_cache = cache_version()
+        invalidate_cache(test_cache.cache)
+        updated_test_cache = CacheVersion.objects.get(cache=test_cache.cache)
+        assert test_cache.version != updated_test_cache.version
+
+    @patch('django.core.cache.cache.delete')
+    def test_invalidating_cache_deletes_versions_cache(self, cache_delete):
+        test_cache = cache_version()
+        invalidate_cache(test_cache.cache)
+        cache_delete.assert_called_once_with(CACHE_NAME)
+
+    @patch('django.core.cache.cache.delete')
+    def test_invalidating_all_caches_updates_cache_version_in_database(self, _):
+        test_cache = cache_version()
+        invalidate_all_caches()
+        updated_test_cache = CacheVersion.objects.get(cache=test_cache.cache)
+        assert test_cache.version != updated_test_cache.version
+
+    @patch('django.core.cache.cache.delete')
+    def test_invalidating_all_caches_deletes_versions_cache(self, cache_delete):
+        cache_version()
+        invalidate_all_caches()
+        cache_delete.assert_called_once_with(CACHE_NAME)

+ 5 - 0
misago/cache/utils.py

@@ -0,0 +1,5 @@
+from django.utils.crypto import get_random_string
+
+
+def generate_version_string():
+    return get_random_string(8)

+ 38 - 0
misago/cache/versions.py

@@ -0,0 +1,38 @@
+from django.core.cache import cache
+
+from .models import CacheVersion
+from .utils import generate_version_string
+
+CACHE_NAME = "cache_versions"
+
+
+def get_cache_versions():
+    cache_versions = get_cache_versions_from_cache()
+    if cache_versions is None:
+        cache_versions = get_cache_versions_from_db()
+        cache.set(CACHE_NAME, cache_versions)
+    return cache_versions
+
+
+def get_cache_versions_from_cache():
+    return cache.get(CACHE_NAME)
+
+
+def get_cache_versions_from_db():
+    queryset = CacheVersion.objects.all()
+    return {i.cache: i.version for i in queryset}
+
+
+def invalidate_cache(cache_name):
+    CacheVersion.objects.filter(cache=cache_name).update(
+        version=generate_version_string(),
+    )
+    cache.delete(CACHE_NAME)
+
+
+def invalidate_all_caches():
+    for cache_name in get_cache_versions_from_db().keys():
+        CacheVersion.objects.filter(cache=cache_name).update(
+            version=generate_version_string(),
+        )
+    cache.delete(CACHE_NAME)

+ 2 - 10
misago/users/migrations/0003_bans_version_tracker.py

@@ -1,20 +1,12 @@
 from django.db import migrations
 
-from misago.core.migrationutils import cachebuster_register_cache
-from misago.users.constants import BANS_CACHEBUSTER
-
-
-def register_bans_version_tracker(apps, schema_editor):
-    cachebuster_register_cache(apps, BANS_CACHEBUSTER)
-
 
 class Migration(migrations.Migration):
+    """Migration superseded by 0016"""
 
     dependencies = [
         ('misago_users', '0002_users_settings'),
         ('misago_core', '0001_initial'),
     ]
 
-    operations = [
-        migrations.RunPython(register_bans_version_tracker),
-    ]
+    operations = []

+ 16 - 0
misago/users/migrations/0016_cache_version.py

@@ -0,0 +1,16 @@
+# Generated by Django 1.11.16 on 2018-11-25 15:31
+from django.db import migrations
+
+from misago.cache.operations import StartCacheVersioning
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('misago_users', '0015_user_agreements'),
+        ('misago_cache', '0001_initial'),
+    ]
+
+    operations = [
+        StartCacheVersioning("bans")
+    ]