django-dynamic-storage


Namedjango-dynamic-storage JSON
Version 1.0.0 PyPI version JSON
download
home_pagehttps://github.com/engAmirEng/django-dynamic-storage
SummaryUse and choose storages at runtime based on your logic for each model FileField instance separately.
upload_time2024-03-20 15:45:20
maintainerNone
docs_urlNone
authorAmir Khalife
requires_python<3.12,>=3.7
licenseApache-2.0
keywords django django-storages djangostorage
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![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"
}
        
Elapsed time: 0.59072s