diff --git a/README.md b/README.md index a02e5c26..47e4d6c0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # eVibes + ![LOGO](core/docs/images/evibes-big.png) -eVibes is an eCommerce backend service built with Django. It’s designed for flexibility, making it ideal for various use cases and learning Django skills. The project is easy to customize, allowing for straightforward editing and extension. +eVibes is an eCommerce backend service built with Django. It’s designed for flexibility, making it ideal for various use +cases and learning Django skills. The project is easy to customize, allowing for straightforward editing and extension. ## Table of Contents @@ -54,7 +56,8 @@ eVibes is an eCommerce backend service built with Django. It’s designed for fl ### Dockerfile -Don't forget to change the `RUN sed -i 's|https://deb.debian.org/debian|https://ftp..debian.org/debian|g' /etc/apt/sources.list.d/debian.sources` +Don't forget to change the +`RUN sed -i 's|https://deb.debian.org/debian|https://ftp..debian.org/debian|g' /etc/apt/sources.list.d/debian.sources` ### Environment Variables @@ -78,6 +81,10 @@ POSTGRES_DB="evibes" POSTGRES_USER="evibes_user" POSTGRES_PASSWORD="SUPERSECRETPOSTGRESPASSWORD" +DBBACKUP_SFTP_HOST="Your SFTP backup host" +DBBACKUP_SFTP_USER="The username to use to log in to that host" +DBBACKUP_SFTP_PASS="The password to use to log in to that host" + ELASTIC_PASSWORD="SUPERSECRETELASTICPASSWORD" REDIS_PASSWORD="SUPERSECRETREDISPASSWORD" @@ -111,6 +118,7 @@ ABSTRACT_API_KEY="Haha, really? x2" ## Usage Add these lines to your hosts-file to use django-hosts functionality on localhost: + ```hosts 127.0.0.1 api.localhost 127.0.0.1 b2b.localhost diff --git a/evibes/settings/__init__.py b/evibes/settings/__init__.py index b6805ab6..2c87ce42 100644 --- a/evibes/settings/__init__.py +++ b/evibes/settings/__init__.py @@ -3,6 +3,7 @@ from .celery import * # noqa: F403 from .constance import * # noqa: F403 from .csp import * # noqa: F403 from .database import * # noqa: F403 +from .dbbackup import * # noqa: F403 from .drf import * # noqa: F403 from .elasticsearch import * # noqa: F403 from .emailing import * # noqa: F403 diff --git a/evibes/settings/base.py b/evibes/settings/base.py index 954c8ca3..851977ac 100644 --- a/evibes/settings/base.py +++ b/evibes/settings/base.py @@ -81,6 +81,7 @@ INSTALLED_APPS = [ "drf_spectacular_sidecar", "django_json_widget", "django_elasticsearch_dsl", + "dbbackup", "corsheaders", "constance.backends.database", "django_mailbox", @@ -304,3 +305,17 @@ CSRF_COOKIE_HTTPONLY = True LANGUAGE_COOKIE_HTTPONLY = True DATA_UPLOAD_MAX_NUMBER_FIELDS = 8888 + +ADMINS = [('Egor Gorbunov', 'contact@fureunoir.com')] + +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, + "dbbackup": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + } +} diff --git a/evibes/settings/dbbackup.py b/evibes/settings/dbbackup.py new file mode 100644 index 00000000..69d0cb71 --- /dev/null +++ b/evibes/settings/dbbackup.py @@ -0,0 +1,20 @@ +from evibes.settings.base import getenv + +if getenv("DBBACKUP_SFTP_HOST") and getenv("DBBACKUP_SFTP_USER") and getenv("DBBACKUP_SFTP_PASS"): + DBBACKUP_STORAGE = "storages.backends.sftpstorage.SFTPStorage" + DBBACKUP_STORAGE_OPTIONS = { + "host": getenv("DBBACKUP_SFTP_HOST"), + "root_path": "/db_backups/", + "params": { + "username": getenv("DBBACKUP_SFTP_USER"), + "password": getenv("DBBACKUP_SFTP_PASS"), + "allow_agent": False, + "look_for_keys": False, + }, + "interactive": False, + "file_mode": 0o600, + "dir_mode": 0o700, + } +else: + DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage" + DBBACKUP_STORAGE_OPTIONS = {"location": "/app/db_backups/"} diff --git a/poetry.lock b/poetry.lock index e1468dce..ae5722f8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -206,6 +206,70 @@ files = [ [package.extras] dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] +[[package]] +name = "bcrypt" +version = "4.3.0" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.8" +files = [ + { file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd" }, + { file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af" }, + { file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231" }, + { file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c" }, + { file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f" }, + { file = "bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d" }, + { file = "bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4" }, + { file = "bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669" }, + { file = "bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb" }, + { file = "bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d" }, + { file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f" }, + { file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732" }, + { file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef" }, + { file = "bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304" }, + { file = "bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51" }, + { file = "bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62" }, + { file = "bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe" }, + { file = "bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0" }, + { file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f" }, + { file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23" }, + { file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe" }, + { file = "bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505" }, + { file = "bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a" }, + { file = "bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b" }, + { file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1" }, + { file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d" }, + { file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492" }, + { file = "bcrypt-4.3.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90" }, + { file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a" }, + { file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce" }, + { file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8" }, + { file = "bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938" }, + { file = "bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18" }, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + [[package]] name = "beautifulsoup4" version = "4.13.4" @@ -851,6 +915,21 @@ files = [ [package.dependencies] Django = ">=3.2" +[[package]] +name = "django-dbbackup" +version = "4.2.1" +description = "Management commands to help backup and restore a project database and media." +optional = false +python-versions = ">=3.7" +files = [ + { file = "django_dbbackup-4.2.1-py3-none-any.whl", hash = "sha256:b23265600ead0780ca781b1b4b594949aaa8a20d74f08701f91ee9d7eb1f08cd" }, + { file = "django_dbbackup-4.2.1.tar.gz", hash = "sha256:157a2ec10d482345cd75092e510ac40d6e2ee6084604a1d17abe178c2f06bc69" }, +] + +[package.dependencies] +django = ">=3.2" +pytz = "*" + [[package]] name = "django-elasticsearch-dsl" version = "8.0" @@ -1071,6 +1150,29 @@ redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" [package.extras] hiredis = ["redis[hiredis] (>=3,!=4.0.0,!=4.0.1)"] +[[package]] +name = "django-storages" +version = "1.14.6" +description = "Support for many storage backends in Django" +optional = false +python-versions = ">=3.7" +files = [ + { file = "django_storages-1.14.6-py3-none-any.whl", hash = "sha256:11b7b6200e1cb5ffcd9962bd3673a39c7d6a6109e8096f0e03d46fab3d3aabd9" }, + { file = "django_storages-1.14.6.tar.gz", hash = "sha256:7a25ce8f4214f69ac9c7ce87e2603887f7ae99326c316bc8d2d75375e09341c9" }, +] + +[package.dependencies] +Django = ">=3.2" + +[package.extras] +azure = ["azure-core (>=1.13)", "azure-storage-blob (>=12)"] +boto3 = ["boto3 (>=1.4.4)"] +dropbox = ["dropbox (>=7.2.1)"] +google = ["google-cloud-storage (>=1.36.1)"] +libcloud = ["apache-libcloud"] +s3 = ["boto3 (>=1.4.4)"] +sftp = ["paramiko (>=1.15)"] + [[package]] name = "django-timezone-field" version = "7.1" @@ -2460,6 +2562,27 @@ files = [ { file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e" }, ] +[[package]] +name = "paramiko" +version = "3.5.1" +description = "SSH2 protocol library" +optional = false +python-versions = ">=3.6" +files = [ + { file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61" }, + { file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822" }, +] + +[package.dependencies] +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" + +[package.extras] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=2.0)"] + [[package]] name = "parso" version = "0.8.4" @@ -2938,6 +3061,32 @@ pyyaml = "*" [package.extras] extra = ["pygments (>=2.19.1)"] +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + { file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1" }, + { file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92" }, + { file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394" }, + { file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d" }, + { file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858" }, + { file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b" }, + { file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff" }, + { file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" }, + { file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93" }, + { file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba" }, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + [[package]] name = "python-crontab" version = "3.2.0" @@ -3888,4 +4037,4 @@ worker = ["celery", "django-celery-beat"] [metadata] lock-version = "2.0" python-versions = ">=3.12,<3.13" -content-hash = "e67ba20e47d15b3ddbe6bd24249b07fca49ad330ffdf3dfc12ca75260f5ddad1" +content-hash = "4079e4ea32df5c36446f1a1bc151c2f6c17afac4fc686d188370d643c2a56e6d" diff --git a/pyproject.toml b/pyproject.toml index fe5c6142..1b2af5b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ psycopg2 = "2.9.10" polib = "1.2.0" openai = { version = "1.77.0", optional = true } swapper = "1.4.0" +paramiko = "3.5.1" filetype = "1.2.0" colorlog = "6.9.0" pymdown-extensions = "10.15" @@ -40,6 +41,8 @@ django-ratelimit = "4.1.0" django-hosts = "6.0" django-mptt = "0.17.0" django-filter = "25.1" +django-dbbackup = "4.2.1" +django-storages = "1.14.6" django-constance = "4.3.2" django-daisy = "1.0.23" django-cacheops = "7.2"