Package: django-private-storage
django-private-storageThis module offers a private media file storage,
so user uploads can be protected behind a login.It uses the Django storage API’s internally,
so all form rendering and admin integration work out of the box.Installationpip install django-private-storageConfigurationAdd to the settings:INSTALLED_APPS+=('private_storage',)PRIVATE_STORAGE_ROOT='/path/to/private-media/'PRIVATE_STORAGE_AUTH_FUNCTION='private_storage.permissions.allow_staff'Add tourls.py:importprivate_storage.urlsurlpatterns+=[path('private-media/',include(private_storage.urls)),]UsageIn a Django model, add thePrivateFileField:fromdjango.dbimportmodelsfromprivate_storage.fieldsimportPrivateFileFieldclassMyModel(models.Model):title=models.CharField("Title",max_length=200)file=PrivateFileField("File")ThePrivateFileFieldalso accepts the following kwargs:upload_to: the optional subfolder in thePRIVATE_STORAGE_ROOT.upload_subfolder: a function that defines the folder, it receives the current modelinstance.content_types: allowed content typesmax_file_size: maximum file size in bytes. (1MB is 1024 * 1024)storage: the storage object to use, defaults toprivate_storage.storage.private_storageImagesYou can also usePrivateImageFieldwhich only allows you to upload images:fromdjango.dbimportmodelsfromprivate_storage.fieldsimportPrivateImageFieldclassMyModel(models.Model):title=models.CharField("Title",max_length=200)width=models.PositiveSmallIntegerField(default=0)height=models.PositiveSmallIntegerField(default=0)image=PrivateFileField("Image",width_field='width',height_field='height')ThePrivateImageFieldalso accepts the following kwargs on top ofPrivateFileField:width_field: optional field for that stores the width of the imageheight_field: optional field for that stores the height of the imageOther topicsStoring files on Amazon S3ThePRIVATE_STORAGE_CLASSsetting can be redefined to point to a different storage class.
The default isprivate_storage.storage.files.PrivateFileSystemStorage, which uses
a private media folder thatPRIVATE_STORAGE_ROOTpoints to.Define one of these settings instead:PRIVATE_STORAGE_CLASS='private_storage.storage.s3boto3.PrivateS3BotoStorage'AWS_PRIVATE_STORAGE_BUCKET_NAME='private-files'# bucket nameThis usesdjango-storagessettings. Replace the prefixAWS_withAWS_PRIVATE_.
The following settings are reused when they don’t have an correspondingAWS_PRIVATE_...setting:AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_S3_URL_PROTOCOLAWS_S3_REGION_NAMEAWS_IS_GZIPPEDAll other settings should be explicitly defined withAWS_PRIVATE_...settings.By default, all URLs in the admin return the direct S3 bucket URls, with thequery parameter authenticationenabled.
WhenAWS_PRIVATE_QUERYSTRING_AUTH = False, all file downloads are proxied through ourPrivateFileViewURL.
This behavior can be enabled explicitly usingPRIVATE_STORAGE_S3_REVERSE_PROXY = True.To have encryption either configureAWS_PRIVATE_S3_ENCRYPTIONandAWS_PRIVATE_S3_SIGNATURE_VERSIONor use:PRIVATE_STORAGE_CLASS='private_storage.storage.s3boto3.PrivateEncryptedS3BotoStorage'Make sure an encryption key is generated on Amazon.MinIO storageDefine one of these settings :PRIVATE_STORAGE_CLASS='private_storage.storage.minio.PrivateMinioStorage'MINIO_PRIVATE_STORAGE_MEDIA_BUCKET_NAME='private-files'MINIO_PRIVATE_STORAGE_MEDIA_URL='/private-files'This usesdjango-minio-storagesettings. Replace the prefixMINIO_withMINIO_PRIVATE_.
The all settings are reused when they don’t have an correspondingMINIO_PRIVATE_...setting.MINIO_STORAGE_ENDPOINTMINIO_STORAGE_ACCESS_KEYMINIO_STORAGE_SECRET_KEYMINIO_STORAGE_USE_HTTPSMINIO_STORAGE_MEDIA_BUCKET_NAMEMINIO_STORAGE_MEDIA_URLMINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKETMINIO_STORAGE_AUTO_CREATE_MEDIA_POLICYMINIO_STORAGE_MEDIA_USE_PRESIGNEDMINIO_STORAGE_MEDIA_BACKUP_FORMATMINIO_STORAGE_ASSUME_MEDIA_BUCKET_EXISTSMINIO_STORAGE_MEDIA_OBJECT_METADATAAs with S3, you can enable proxy through ourPrivateFileViewURL.
Just specifyPRIVATE_STORAGE_MINO_REVERSE_PROXY = True.Defining access rulesThePRIVATE_STORAGE_AUTH_FUNCTIONdefines which user may access the files.
By default, this only includes superusers.The following options are available out of the box:private_storage.permissions.allow_authenticatedprivate_storage.permissions.allow_staffprivate_storage.permissions.allow_superuserYou can create a custom function, and use that instead.
The function receives aprivate_storage.models.PrivateFileobject,
which has the following fields:request: the Django request.storage: the storage engine used to retrieve the file.relative_name: the file name in the storage.full_path: the full file system path.exists(): whether the file exists.content_type: the HTTP content type.parent_object: only set whenPrivateStorageDetailViewwas used.Retrieving files by object IDTo implement more object-based access permissions,
create a custom view that provides the download.fromprivate_storage.viewsimportPrivateStorageDetailViewclassMyDocumentDownloadView(PrivateStorageDetailView):model=MyModelmodel_file_field='file'defget_queryset(self):# Make sure only certain objects can be accessed.returnsuper().get_queryset().filter(...)defcan_access_file(self,private_file):# When the object can be accessed, the file may be downloaded.# This overrides PRIVATE_STORAGE_AUTH_FUNCTIONreturnTrueThe following class-level attributes can be overwritten:model: The model to fetch (including every other attribute ofSingleObjectMixin).model_file_field: This should point to the field used to store the file.storage/get_storage(): The storage class to read the file from.server_class: The Python class used to generate theHttpResponse/FileResponse.content_disposition: Can be “inline” (show inside the browser) or “attachment” (saved as download).content_disposition_filename/get_content_disposition_filename(): Overrides the filename for downloading.Optimizing large file transfersSending large files can be inefficient in some configurations.In the worst case scenario, the whole file needs to be read in chunks
and passed as a whole through the WSGI buffers, OS kernel, webserver and proxy server.
In effect, the complete file is copied several times through memory buffers.There are more efficient ways to transfer files, such as thesendfile()system call on UNIX.
Django uses such feature when the WSGI server provideswsgi.file_handlersupport.In some situations, this effect is nullified,
for example by by a local HTTP server sitting in front of the WSGI container.
A typical case would be  running Gunicorn behind an Nginx or Apache webserver.For such situation, the native support of the
webserver can be enabled with the following settings:For apachePRIVATE_STORAGE_SERVER='apache'This requires in addition an installed and activated mod_xsendfile Apache module.
Add the following XSendFile configurations to your conf.d config file.<virtualhost...>...WSGIScriptAlias/...XSendFileOnXSendFilePath...[pathtowherethefilesare,sameasPRIVATE_STORAGE_ROOT]...</virtualhost>For NginxPRIVATE_STORAGE_SERVER='nginx'PRIVATE_STORAGE_INTERNAL_URL='/private-x-accel-redirect/'Add the following location block in the server config:location/private-x-accel-redirect/{internal;alias/path/to/private-media/;}For very old Nginx versions, you’ll have to configurePRIVATE_STORAGE_NGINX_VERSION,
because Nginx versions before 1.5.9 (released in 2014) handle non-ASCII filenames differently.Other webserversThePRIVATE_STORAGE_SERVERmay also point to a dotted Python class path.
Implement a class with a staticserve(private_file)method.Using multiple storagesThePrivateFileFieldaccepts astoragekwarg,
hence you can initialize multipleprivate_storage.storage.PrivateStorageobjects,
each providing files from a differentlocationandbase_url.For example:fromdjango.dbimportmodelsfromprivate_storage.fieldsimportPrivateFileFieldfromprivate_storage.storage.filesimportPrivateFileSystemStoragemy_storage=PrivateFileSystemStorage(location='/path/to/storage2/',base_url='/private-documents2/')classMyModel(models.Model):file=PrivateFileField(storage=my_storage)Then create a view to serve those files:fromprivate_storage.viewsimportPrivateStorageViewfrom.modelsimportmy_storageclassMyStorageView(PrivateStorageView):storage=my_storagedefcan_access_file(self,private_file):# This overrides PRIVATE_STORAGE_AUTH_FUNCTIONreturnself.request.is_superuserAnd expose that URL:urlpatterns+=[url('^private-documents2/(?P<path>.*)$',views.MyStorageView.as_view()),]ContributingThis module is designed to be generic. In case there is anything you didn’t like about it,
or think it’s not flexible enough, please let us know. We’d love to improve it!Running testsWe use tox to run the test suite on different versions locally (and travis-ci to automate the check for PRs).To tun the test suite locally, please make sure your python environment has tox and django installed:python3.6 -m pip install tox djangoAnd then simply execute tox to run the whole test matrix:tox