Skip to main content

tox CI PyPi Version Python Versions Django Versions pre-commit.ci status Code style: black Imports: 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.

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:

pip install django-dynamic-storage

in storage.py:

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.

in settings.py:

# path to your storage dispatcher
STORAGE_DISPATCHER = "myapp.storage.MyStorageDispatcher"

in models.py:

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:

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:

(new to signals? learn how to connect them)

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:

{
  "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 that satisfies this schema

Metadata

Release files for django-dynamic-storage 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-dynamic-storage 1.0.0
File Size Uploaded
django_dynamic_storage-1.0.0.tar.gz 5.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-dynamic-storage 1.0.0
File Interpreter ABI Platform
django_dynamic_storage-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 13.0 kB

Release files / django_dynamic_storage-1.0.0.tar.gz

Download URL django_dynamic_storage-1.0.0.tar.gz
Size 5.9 kB
Tags Source
SHA-256 checksum
How to use checksums
1a7cbda8d62ec3532bc6edbdd9ea8badd283a078cc9f133e6cb3b0d1fa5e396c
BLAKE2b-256 checksum
How to use checksums
750c0359eaaeb6a0183a09623ba5f1911a35b026a608c8162a629ce39842aa73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.2 CPython/3.10.13 Linux/6.5.0-1016-azure

Release files / django_dynamic_storage-1.0.0-py3-none-any.whl

Download URL django_dynamic_storage-1.0.0-py3-none-any.whl
Size 7.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fdb0fd33e233fda5e295e05ca5328df2359dc48f4d7eafbdf4a1a23aa0ac1fea
BLAKE2b-256 checksum
How to use checksums
f9cd3124552f5895dac9462c2ce80bf5bae7d7b345f9715722bab90e70ae1f05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.2 CPython/3.10.13 Linux/6.5.0-1016-azure

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page