Serving static files (media)¶
There are some files, such as PDB files, that need to be served by fragalysis. These are either:
Served to the front-end - files retrieved from a specific URL to be loaded and rendered in the front-end - most commonly PDB files loaded by NGL into the central 3D viewer
Served as downloads - files retrieved from a specific URL to be downloaded directly by a user.
It is not recommended to use Django to serve static files in production, so fragalysis makes use of nginx to serve its media. (https://docs.djangoproject.com/en/3.1/howto/static-files/deployment/#serving-static-files-from-a-dedicated-server).
NGINX¶
According to the NGINX website (https://www.nginx.com/resources/glossary/nginx/):
NGINX is open source software for web serving, reverse proxying, caching, load balancing, media streaming, and more. It started out as a web server designed for maximum performance and stability.
Adding an endpoint that serves media¶
In fragalysis, we use a FileField in a Model to specify a file, and where it is stored. For example, in
the target model we have two files that are served as files:
class Target(models.Model):
"""Django model to define a Target - a protein.
Parameters
----------
title: CharField
The name of the target
init_date: DateTimeField
The date the target was initiated (autofield)
project_id: ManyToManyField
Links targets to projects for authentication
uniprot_id: Charfield
Optional field where a uniprot id can be stored
metadata: FileField
Optional file upload defining metadata about the target - can be used to add custom site labels
zip_archive: FileField
Link to zip file created from targets uploaded with the loader
"""
# The title of the project_id -> userdefined
title = models.CharField(unique=True, max_length=200)
# The date it was made
init_date = models.DateTimeField(auto_now_add=True)
# A field to link projects and targets together
project_id = models.ManyToManyField(Project)
# Indicates the uniprot_id id for the target. Is a unique key
uniprot_id = models.CharField(max_length=100, null=True)
# metadatafile containing sites info for download
metadata = models.FileField(upload_to="metadata/", null=True, max_length=255)
# zip archive to download uploaded data from
zip_archive = models.FileField(upload_to="archive/", null=True, max_length=255)
The metadata file and the zip_archive file. To make sure that these Files are served as download links
, we have to tell nginx to allow access to the media area where the files are uploaded/saved to.
We define this in django_nginx.conf, for example for the metadata file:
location /metadata/ {
alias /code/media/metadata/;
internal;
}
This tells nginx that the url /metadata/ can be used to access files hosted on the back-end under
/code/media/metadata.
In order to tell django to serve these files at the given URL, we need a View, just like any other endpoint. The
view for serving metadata files can be found in media_serve/views.py:
from api.security import ISpyBSafeStaticFiles
from viewer.models import Target
def metadata_download(request, file_path):
"""
Download a metadata file by nginx redirect
:param request: the initial request
:param file_path: the file path we're getting from the static
:return: the response (a redirect to nginx internal)
"""
ispy_b_static = ISpyBSafeStaticFiles()
ispy_b_static.model = Target
ispy_b_static.request = request
ispy_b_static.permission_string = "project_id"
ispy_b_static.field_name = "metadata"
ispy_b_static.content_type = "application/x-pilot"
ispy_b_static.prefix = "/metadata/"
ispy_b_static.input_string = file_path
return ispy_b_static.get_response()
For our files, we’re authenticating user access to file downloads with ISpyBSafeStaticFiles, which is a custom
class inheriting ISpyBSafeQuerySet(viewsets.ReadOnlyModelViewSet): the same View method we use to authenticate
user access for all non-media views. The difference between ISpyBSafeQuerySet and ISpyBSafeStaticFiles
is that ISpyBSafeStaticFiles contains a method that sets the context of the response using NGINX’s redirect
method, returning the response including the file as an attachment:
class ISpyBSafeStaticFiles:
def get_queryset(self):
query = ISpyBSafeQuerySet()
query.request = self.request
query.filter_permissions = self.permission_string
query.queryset = self.model.objects.filter()
queryset = query.get_queryset()
return queryset
def get_response(self):
try:
queryset = self.get_queryset()
filter_dict = {self.field_name + "__endswith": self.input_string}
object = queryset.get(**filter_dict)
file_name = os.path.basename(str(getattr(object, self.field_name)))
if hasattr(self, 'file_format'):
if self.file_format=='raw':
file_field = getattr(object, self.field_name)
filepath = file_field.path
zip_file = open(filepath, 'rb')
response = HttpResponse(FileWrapper(zip_file), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename="%s"' % file_name
else:
response = HttpResponse()
response["Content-Type"] = self.content_type
response["X-Accel-Redirect"] = self.prefix + file_name
response["Content-Disposition"] = "attachment;filename=" + file_name
return response
except Exception:
raise Http404
Finally, just as with any other view, we have to specify a url. For example, for the metadata file, we specify
in media_serve/urls.py:
url(r"^metadata/(?P<file_path>.+)", views.metadata_download, name="get_metadata"),