Package: dj3-cloudinary-storage
Django 3 Cloudinary StorageDjango Cloudinary Storage is a Django package that facilitates integration withCloudinaryby implementingDjango Storage API.
With several lines of configuration, you can start using Cloudinary for both media and static files.
Also, it provides management commands for removing unnecessary files, so any cleanup will be a breeze.
It usespycloudinarypackage under the hood.Table of contentRequirementsInstallationUsage with media filesUsage with raw filesUsage with video filesUsage with static filesManagement commandscollectstaticdeleteorphanedmediadeleteredundantstaticSettingsHow to run testsRequirementsThe package requires Python 3.5+ and Django 3.0+. It has been tested on Linux and Mac OS X.InstallationTo install the package, just run:$ pip install dj3-cloudinary-storageIf you need to upload any video files, run:$ pip install dj3-cloudinary-storage[video]which will additionally installpython-magicfor uploaded video validation.Also, in case you use DjangoImageField, make sure you have Pillow installed:$ pip install PillowOnce you have done that, addcloudinaryandcloudinary_storageto you installed apps in yoursettings.py. If you
are going to use this package for static and/or media files, make sure thatcloudinary_storageis beforedjango.contrib.staticfiles:INSTALLED_APPS=[# ...'cloudinary_storage','django.contrib.staticfiles','cloudinary',# ...]because dj3-cloudinary-storage overwrites Djangocollectstaticcommand. If you are going to use it only for media files
though, it isdjango.contrib.staticfileswhich has to be first:INSTALLED_APPS=[# ...'django.contrib.staticfiles','cloudinary_storage','cloudinary',# ...]Next, you need to add Cloudinary credentials tosettings.py:CLOUDINARY_STORAGE={'CLOUD_NAME':'your_cloud_name','API_KEY':'your_api_key','API_SECRET':'your_api_secret'}Instead of putting credentials insettings.py, you can provide them asCLOUDINARY_CLOUD_NAME,CLOUDINARY_API_SECRETandCLOUDINARY_API_KEYenvironment variables. It is possible as well to set onlyCLOUDINARY_URLvariable, combining all the information, for example:$ export CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_nameFor those of you who use Heroku, that's a very good news, because you won't need to set it yourself, as Heroku setsCLOUDINARY_URLenvironment variable for you (provided you use Cloudinary as Heroku addon).Also, be aware thatsettings.pytakes precedence over environment variables.Usage with media filesThe package provides three media storages:cloudinary_storage.storage.MediaCloudinaryStoragefor imagescloudinary_storage.storage.RawMediaCloudinaryStoragefor raw files, like txt, pdfcloudinary_storage.storage.VideoMediaCloudinaryStoragefor videosAbove distinction if necessary as Cloudinary API needs to know resource type in many of its methods.Now, let's consider the most probable scenario that you will use Cloudinary for images uploaded by users of your
website. Let's say you created a following Django model:classTestModel(models.Model):name=models.CharField(max_length=100)image=models.ImageField(upload_to='images/',blank=True)All you need to do is to add two lines tosettings.py:MEDIA_URL='/media/'# or any prefix you chooseDEFAULT_FILE_STORAGE='cloudinary_storage.storage.MediaCloudinaryStorage'And that's it! All your models withImageFieldwill be connected to Cloudinary.Now, in order to put this image into your template, you can just type:<img src="{{test_model_instance.image.url}}" alt="{{test_model_instance.image.name}}">However, doing that in this way, the image will be downloaded with its original size, as uploaded by a user.
To have more control, you can use Cloudinary image transformations. For example, to change the image's size,
use below code:{%loadcloudinary%}{%cloudinarytest_model_instance.image.namewidth=100height=100%}Of cource, this only scratched the surface. Cloudinary is extremely powerful and I highly recommend you to checkpycloudinarydocumentation.Now, if you only need to use Cloudinary for images, you can skip the rest of this subsection.
However, if you are going to use it for videos and/or raw files, let's continue.Usage with raw filesIf your users can upload text or other raw files, but not images, you would just use different default storage
insettings.py:DEFAULT_FILE_STORAGE='cloudinary_storage.storage.RawMediaCloudinaryStorage'But what if they could upload both types? Well, not a problem! Just setDEFAULT_FILE_STORAGEsetting to the most
common resource type, and for fields of different type, you will need to set a correct storage individually, like this:fromdjango.dbimportmodelsfromcloudinary_storage.storageimportRawMediaCloudinaryStorageclassTestModelWithRawFileAndImage(models.Model):name=models.CharField(max_length=100)raw_file=models.ImageField(upload_to='raw/',blank=True,storage=RawMediaCloudinaryStorage())image=models.ImageField(upload_to='images/',blank=True)# no need to set storage, field will use the default oneIn above example we assumedDEFAULT_FILE_STORAGE = 'cloudinary_storage.storage.MediaCloudinaryStorage',
that's why we set storage explicitly only forraw_file.Usage with video filesUsage with video files is analogous to raw files, but you will need to usevalidate_videovalidator for video fields
to validate user's uploaded videos. If not, Cloudinary will raise an error if a user tries to upload non-video file,
which will crash your website. Of cource, you could use your own validator, but if you want to use built-in one,
do it like this:fromdjango.dbimportmodelsfromcloudinary_storage.storageimportVideoMediaCloudinaryStoragefromcloudinary_storage.validatorsimportvalidate_videoclassTestModelWithVideoAndImage(models.Model):name=models.CharField(max_length=100)video=models.ImageField(upload_to='videos/',blank=True,storage=VideoMediaCloudinaryStorage(),validators=[validate_video])image=models.ImageField(upload_to='images/',blank=True)# no need to set storage, field will use the default oneUsage with static filesIn order to move your static files to Cloudinary, update yoursettings.py:STATIC_URL='/static/'STATICFILES_STORAGE='cloudinary_storage.storage.StaticHashedCloudinaryStorage'After that, run Djangocollectstaticcommand:$ python manage.py collectstaticPlease note that only files with hashed name will be uploaded by default - this behavior can be changed by adding--upload-unhashed-filesargument tocollectstaticcommand. If you are not sure why it is useful to add hash to file
names, shortly speaking, it allows static files to be safely cached by Cloudinary CDN and web browsers. Without it
files' modification would become very problematic, because your website's users would use their private older copies.
Hashing prevents this issue as any file change will change its url as well, which would force a browser to download
a new version of a file.Also, be aware thatcollectstaticwill create a JSON file, which shows mapping of unhashed file names to their hashed
versions. This file will be available at./manifest/staticfiles.jsonby default - you could change that
in yoursettings.py, for example:importosBASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))CLOUDINARY_STORAGE={# other settings, like credentials'STATICFILES_MANIFEST_ROOT':os.path.join(BASE_DIR,'my-manifest-directory')}It is highly recommended to keep up-to-date version of this file in your version control system.In order to use static files from Cloudinary, make sure you write your templates in below style:{%loadstatic%}<link rel="stylesheet" href="{%static'css/style.css'%}"><img src="{%static'images/dummy-static-image.jpg'%}" alt="dummy static image">If you would like to apply Cloudinary transformations for static images or videos, please usecloudinary_statictemplate tag as follows:{%loadcloudinary_static%}{%cloudinary_static'images/dummy-static-image.jpg'width=50height=50%}You can adjustSTATIC_IMAGES_EXTENSIONSandSTATIC_VIDEOS_EXTENSIONSto set rules which file extensions are treated
as image or video files. Files with different extensions will be uploaded as Cloudinary raw files and no transformations
could be applied for those files. Also, please note thatcloudinary_staticis just a thin wrapper aroundcloudinarytag frompycloudinarylibrary, so please go to its documentation
to see what transformations are possible.Please note that you must setDEBUGtoFalseto fetch static files from Cloudinary. WithDEBUGequal toTrue,
Djangostaticfilesapp will use your local files for easier and faster development (unless you usecloudinary_statictemplate tag).Management commandsThe package provides three management commands:collectstaticdeleteorphanedmediadeleteredundantstaticcollectstaticAdds minor modifications to Djangocollectstaticto improve upload performance. It uploads only hashed files
as the default. Also, it uploads a file only when necessary, namely it won't upload the file if a file with the same
name and content will be already uploaded to Cloudinary, which will save both time and bandwidth.Optional arguments:--upload-unhashed-files- uploads files without hash added to their name along with hashed ones, use it only
when it is really necessary--noinput- non-interactive mode, the command won't ask you to do any confirmationsdeleteorphanedmediaDeletes needless media files, which are not connected to any model. It is possible to provide paths to prevent deletion
of given files inEXCLUDE_DELETE_ORPHANED_MEDIA_PATHSinsettings.py, for example:CLOUDINARY_STORAGE={# other settings'EXCLUDE_DELETE_ORPHANED_MEDIA_PATHS':('path/','second-path/')}Optional arguments:--noinput- non-interactive mode, the command won't ask you to do any confirmationsdeleteredundantstaticDeletes needless static files.Optional arguments:--keep-unhashed-files- use it if you usecollectstaticwith--upload-unhashed-filesargument,
without it this command will always delete all unhashed files--noinput- non-interactive mode, the command won't ask you to do any confirmationsSettingsBelow you can see all available settings with default values:importosfromdjango.confimportsettingsBASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))CLOUDINARY_STORAGE={'CLOUD_NAME':None,# required'API_KEY':None,# required'API_SECRET':None,# required'SECURE':True,'MEDIA_TAG':'media','INVALID_VIDEO_ERROR_MESSAGE':'Please upload a valid video file.','EXCLUDE_DELETE_ORPHANED_MEDIA_PATHS':(),'STATIC_TAG':'static','STATICFILES_MANIFEST_ROOT':os.path.join(BASE_DIR,'manifest'),'STATIC_IMAGES_EXTENSIONS':['jpg','jpe','jpeg','jpc','jp2','j2k','wdp','jxr','hdp','png','gif','webp','bmp','tif','tiff','ico'],'STATIC_VIDEOS_EXTENSIONS':['mp4','webm','flv','mov','ogv','3gp','3g2','wmv','mpeg','flv','mkv','avi'],'MAGIC_FILE_PATH':'magic','PREFIX':settings.MEDIA_URL}CLOUD_NAME,API_KEYandAPI_SECRETare mandatory and you need to define them inCLOUDINARY_STORAGEdictionary
insettings.py, the rest could be overwritten if required, as described below:SECURE- whether your Cloudinary files should be server over HTTP or HTTPS, HTTPS is the default, set it to False
to switch to HTTPMEDIA_TAG- name assigned to your all media files, it has to be different thanSTATIC_TAG, usually you don't
need to worry about this setting, it is useful when you have several websites which use the same Cloudinary acount, when
you should set it unique to distinguish it from other websites,INVALID_VIDEO_ERROR_MESSAGE- error message which will be desplayed in user's form when one tries to upload non-video
file in video fieldEXCLUDE_DELETE_ORPHANED_MEDIA_PATHS- looked bydeleteorphanedmediacommand, you can provide here tuple of paths
which will never be deletedSTATIC_TAG- name assigned to your all static files, it has to be different thanMEDIA_TAG, please seeMEDIA_TAGsetting to see when it is usefulSTATICFILES_MANIFEST_ROOT- path wherestaticfiles.jsonwill be saved aftercollectstaticcommand,./manifestis the default locationSTATIC_IMAGES_EXTENSIONS- list of file extensions with which static files will be treated as Cloudinary imagesSTATIC_VIDEOS_EXTENSIONS- list of file extensions with which static files will be uploaded as Cloudinary videosMAGIC_FILE_PATH: applicable only for Windows, needed for python-magic library for movie validation, please seepython-magicfor referencePREFIX- prefix to your all files uploaded byMediaCloudinaryStorage, defaultMEDIA_URL, it can be useful when
you useFileSystemStorageas default andMediaCloudinaryStoragefor some models fieldsHow to run tests$ coverage run -a --source=cloudinary_storage manage.py test -v 2NotesThis project is based on a fork fromdjango-cloudinary-storageThanx!