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.
If your django version is earlier than 3.1 (<=3.0) then your database should be PostgreSQL otherwise you are good to go.
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")
Not even a bit!
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.
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