![tox CI](https://github.com/engAmirEng/django-dynamic-storage/actions/workflows/tox.yml/badge.svg)
![PyPi Version](https://img.shields.io/pypi/v/django-dynamic-storage)
![Python Versions](https://img.shields.io/pypi/pyversions/poetry)
![Django Versions](https://img.shields.io/badge/django-2.2_|_3.0_|_3.1_|_3.2_|_4.0_|_4.1_|_4.2_|_5.0-green)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/engAmirEng/django-dynamic-storage/main.svg)](https://results.pre-commit.ci/latest/github/engAmirEng/django-dynamic-storage/main)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
## django-dynamic-storage
Have you ever wanted not to store every instance of FileFileds or ImageFileds of a model in one storage or one bucket of a storage?
Now you can, because I wanted that for [my project](https://github.com/SayIfOrg/say_wagtail).
### prerequisites:
If your django version is earlier than 3.1 (<=3.0) then your database should be PostgreSQL otherwise you are good to go.
### Usage:
```plaintext
pip install django-dynamic-storage
```
in storage.py:
```python
from django.utils.deconstruct import deconstructible
from dynamic_storage.storage import DynamicStorageMixin
from dynamic_storage.storage import AbstractBaseStorageDispatcher
class MyStorageDispatcher(AbstractBaseStorageDispatcher):
@staticmethod
def get_storage(instance, field, **kwargs):
if kwargs.get("my_storage_identifier") == "storage1":
return MyDynamicStorage(named_param1=kwargs["named_param1"], named_param2=kwargs["named_param2"])
elif isinstance(instance, models.Profile) and field.name == "profile_pic":
return MyDynamicStorage(named_param1="my_hard_coded_var", named_param2="my_other_hard_coded_var")
# elif ...
raise NotImplementedError
@deconstructible
class MyDynamicStorage(DynamicStorageMixin, AnyStorage):
def __init__(self, named_param1, named_param2):
# AnyStorage stuff
super().__init__(named_param1, named_param2)
def init_params(self) -> dict:
"""
here you should return a dictionary of key value pairs that
later are passed to MyStorageDispatcher.
should be json serializable!!!
"""
return {"my_storage_identifier": "storage1", "named_param1": self.named_param1, "named_param2": self.named_param2, ...}
```
`AnyStorage` can be a storage that you define yourself or import from [django-storages](https://pypi.org/project/django-storages/).
in settings.py:
```python
# path to your storage dispatcher
STORAGE_DISPATCHER = "myapp.storage.MyStorageDispatcher"
```
in models.py:
```python
from dynamic_storage.models import DynamicFileField, DynamicImageField
class MyModel(models.Model):
"""
DynamicFileField and DynamicImageField accept any options that django's native FileField and ImageField accept
"""
file = DynamicFileField()
image = DynamicImageField()
```
note that there is no Storage specified here!😎
Now your logic to take control of the storage where your content is going to be saved to:
```python
obj = MyModel(file=file, image=image)
obj.file.destination_storage = MyDynamicStorage(named_param1="something", named_param2="another_thing")
obj.image.destination_storage = MyDynamicStorage(named_param1="foo", named_param2="bar")
obj.save()
```
or using [signals](https://docs.djangoproject.com/en/4.2/topics/signals/):
(new to signals? learn how to [connect them](https://docs.djangoproject.com/en/4.2/topics/signals/#connecting-receiver-functions))
```python
from dynamic_storage.signals import pre_dynamic_file_save
@receiver(pre_dynamic_file_save, sender=models.MyModel)
def decide_the_storage_to_save(
instance
, field_file
, to_storage
, *args,
**kwargs
):
if not to_storage:
# destination_storage is not set, so we set it here
field_file.destination_storage = MyDynamicStorage(named_param1="something", named_param2="another_thing")
elif to_storage == wrong_storage:
# override the destination_storage set earlier
field_file.destination_storage = MyAnotherDynamicStorage(named_param1="foo", named_param2="bar")
```
#### Performance penalty?!
Not even a bit!
#### HOW?
We are just using the django's built in `JsonField` instead of `CharField` Â to store more data (init_params output) in addition to the path to the file.
so no extra queries, no extra steps, no performance penalty.
### How to migrate from django's native `FileField` and `ImageField`?
the schema saved to the `JSONField` is like this:
```json
{
"name": "this/is/the-path/to-the-file",
"storage": {
"constructor": {
// here is the key values that passed to MyStorageDispatcher.get_storage as **kwargs
}
}
}
```
so just write a [custom migration](https://docs.djangoproject.com/en/5.0/howto/writing-migrations/)
that satisfies this schema
Raw data
{
"_id": null,
"home_page": "https://github.com/engAmirEng/django-dynamic-storage",
"name": "django-dynamic-storage",
"maintainer": null,
"docs_url": null,
"requires_python": "<3.12,>=3.7",
"maintainer_email": null,
"keywords": "django, django-storages, djangostorage",
"author": "Amir Khalife",
"author_email": "eng.amir.bu@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/75/0c/0359eaaeb6a0183a09623ba5f1911a35b026a608c8162a629ce39842aa73/django_dynamic_storage-1.0.0.tar.gz",
"platform": null,
"description": "![tox CI](https://github.com/engAmirEng/django-dynamic-storage/actions/workflows/tox.yml/badge.svg)\n![PyPi Version](https://img.shields.io/pypi/v/django-dynamic-storage)\n![Python Versions](https://img.shields.io/pypi/pyversions/poetry)\n![Django Versions](https://img.shields.io/badge/django-2.2_|_3.0_|_3.1_|_3.2_|_4.0_|_4.1_|_4.2_|_5.0-green)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/engAmirEng/django-dynamic-storage/main.svg)](https://results.pre-commit.ci/latest/github/engAmirEng/django-dynamic-storage/main)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)\n\n## django-dynamic-storage\n\nHave you ever wanted not to store every instance of FileFileds or ImageFileds of a model in one storage or one bucket of a storage?\n\nNow you can, because I wanted that for [my project](https://github.com/SayIfOrg/say_wagtail).\n\n### prerequisites:\n\nIf your django version is earlier than 3.1 (<=3.0) then your database should be PostgreSQL otherwise you are good to go.\n\n### Usage:\n\n```plaintext\npip install django-dynamic-storage\n```\n\nin storage.py:\n\n```python\nfrom django.utils.deconstruct import deconstructible\nfrom dynamic_storage.storage import DynamicStorageMixin\nfrom dynamic_storage.storage import AbstractBaseStorageDispatcher\n\nclass MyStorageDispatcher(AbstractBaseStorageDispatcher):\n @staticmethod\n def get_storage(instance, field, **kwargs):\n if kwargs.get(\"my_storage_identifier\") == \"storage1\":\n return MyDynamicStorage(named_param1=kwargs[\"named_param1\"], named_param2=kwargs[\"named_param2\"])\n elif isinstance(instance, models.Profile) and field.name == \"profile_pic\":\n return MyDynamicStorage(named_param1=\"my_hard_coded_var\", named_param2=\"my_other_hard_coded_var\")\n # elif ...\n raise NotImplementedError\n\n\n@deconstructible\nclass MyDynamicStorage(DynamicStorageMixin, AnyStorage):\n def __init__(self, named_param1, named_param2):\n # AnyStorage stuff\n super().__init__(named_param1, named_param2)\n \n\tdef init_params(self) -> dict:\n\t\t\"\"\"\n\t\there you should return a dictionary of key value pairs that \n\t\tlater are passed to MyStorageDispatcher.\n\t\tshould be json serializable!!!\n\t\t\"\"\"\n\t\treturn {\"my_storage_identifier\": \"storage1\", \"named_param1\": self.named_param1, \"named_param2\": self.named_param2, ...}\n```\n\n`AnyStorage` can be a storage that you define yourself or import from [django-storages](https://pypi.org/project/django-storages/).\n\nin settings.py:\n\n```python\n# path to your storage dispatcher\nSTORAGE_DISPATCHER = \"myapp.storage.MyStorageDispatcher\"\n```\n\nin models.py:\n\n```python\nfrom dynamic_storage.models import DynamicFileField, DynamicImageField\n\nclass MyModel(models.Model):\n\t\"\"\"\n\tDynamicFileField and DynamicImageField accept any options that django's native FileField and ImageField accept\n\t\"\"\"\n\tfile = DynamicFileField()\n\timage = DynamicImageField()\n```\nnote that there is no Storage specified here!\ud83d\ude0e\n\nNow your logic to take control of the storage where your content is going to be saved to:\n\n```python\nobj = MyModel(file=file, image=image)\nobj.file.destination_storage = MyDynamicStorage(named_param1=\"something\", named_param2=\"another_thing\")\nobj.image.destination_storage = MyDynamicStorage(named_param1=\"foo\", named_param2=\"bar\")\nobj.save()\n```\n\nor using [signals](https://docs.djangoproject.com/en/4.2/topics/signals/):\n\n(new to signals? learn how to [connect them](https://docs.djangoproject.com/en/4.2/topics/signals/#connecting-receiver-functions))\n\n```python\nfrom dynamic_storage.signals import pre_dynamic_file_save\n\n@receiver(pre_dynamic_file_save, sender=models.MyModel)\ndef decide_the_storage_to_save(\ninstance\n, field_file\n, to_storage\n, *args,\n **kwargs\n):\n if not to_storage:\n \t# destination_storage is not set, so we set it here\n field_file.destination_storage = MyDynamicStorage(named_param1=\"something\", named_param2=\"another_thing\")\n elif to_storage == wrong_storage:\n\t\t# override the destination_storage set earlier\n\t\tfield_file.destination_storage = MyAnotherDynamicStorage(named_param1=\"foo\", named_param2=\"bar\")\n```\n\n#### Performance penalty?!\n\nNot even a bit!\n\n#### HOW?\n\nWe are just using the django's built in `JsonField` instead of `CharField` \u00a0to store more data (init_params output) in addition to the path to the file.\n\nso no extra queries, no extra steps, no performance penalty.\n\n### How to migrate from django's native `FileField` and `ImageField`?\n\nthe schema saved to the `JSONField` is like this:\n\n```json\n{\n \"name\": \"this/is/the-path/to-the-file\", \n \"storage\": {\n \"constructor\": {\n // here is the key values that passed to MyStorageDispatcher.get_storage as **kwargs\n }\n }\n}\n```\nso just write a [custom migration](https://docs.djangoproject.com/en/5.0/howto/writing-migrations/)\nthat satisfies this schema",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Use and choose storages at runtime based on your logic for each model FileField instance separately.",
"version": "1.0.0",
"project_urls": {
"Homepage": "https://github.com/engAmirEng/django-dynamic-storage",
"Repository": "https://github.com/engAmirEng/django-dynamic-storage"
},
"split_keywords": [
"django",
" django-storages",
" djangostorage"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f9cd3124552f5895dac9462c2ce80bf5bae7d7b345f9715722bab90e70ae1f05",
"md5": "612fa91f0128768ce69de61f737c44d0",
"sha256": "fdb0fd33e233fda5e295e05ca5328df2359dc48f4d7eafbdf4a1a23aa0ac1fea"
},
"downloads": -1,
"filename": "django_dynamic_storage-1.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "612fa91f0128768ce69de61f737c44d0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.12,>=3.7",
"size": 7123,
"upload_time": "2024-03-20T15:45:18",
"upload_time_iso_8601": "2024-03-20T15:45:18.809146Z",
"url": "https://files.pythonhosted.org/packages/f9/cd/3124552f5895dac9462c2ce80bf5bae7d7b345f9715722bab90e70ae1f05/django_dynamic_storage-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "750c0359eaaeb6a0183a09623ba5f1911a35b026a608c8162a629ce39842aa73",
"md5": "6589e88a2e711fa2f2a168d369841551",
"sha256": "1a7cbda8d62ec3532bc6edbdd9ea8badd283a078cc9f133e6cb3b0d1fa5e396c"
},
"downloads": -1,
"filename": "django_dynamic_storage-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "6589e88a2e711fa2f2a168d369841551",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.12,>=3.7",
"size": 5913,
"upload_time": "2024-03-20T15:45:20",
"upload_time_iso_8601": "2024-03-20T15:45:20.414784Z",
"url": "https://files.pythonhosted.org/packages/75/0c/0359eaaeb6a0183a09623ba5f1911a35b026a608c8162a629ce39842aa73/django_dynamic_storage-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-03-20 15:45:20",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "engAmirEng",
"github_project": "django-dynamic-storage",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "django-dynamic-storage"
}