# -*- coding: utf-8 -*- from __future__ import unicode_literals # from django-protected-media import mimetypes import os import stat from six.moves.urllib.parse import quote from django.conf import settings from django.http import HttpResponse, Http404, HttpResponseNotModified from django.utils.encoding import smart_str from django.utils.http import http_date from django.views.static import was_modified_since class BaseProtectedMediaServer(object): INTERNAL_URL = None ROOT = None FORCE_DOWNLOAD = True def get_url(self, relative_path): return os.path.join(self.INTERNAL_URL or settings.PROTECTED_MEDIA_INTERNAL_URL, relative_path).encode('utf-8') def get_full_path(self, relative_path): return os.path.join(self.ROOT or settings.PROTECTED_MEDIA_ROOT, relative_path) def get_mimetype(self, relative_path): return mimetypes.guess_type(self.get_full_path(relative_path))[0] or 'application/octet-stream' def get_force_download(self, overwrite=None): if overwrite is not None: return overwrite else: return getattr(settings, 'PROTECTED_MEDIA_FORCE_DOWNLOAD', self.FORCE_DOWNLOAD) def get_filename(self, relative_path): return smart_str(os.path.basename(self.get_full_path(relative_path))) def add_attachment_header(self, response, relative_path): """ Add header to force the browser to save the file instead of possibly displaying it. """ filename = self.get_filename(relative_path) response['Content-Disposition'] = "attachment; filename*=UTF=8''{0}".format(quote(filename)) return response def prepare_response(self, request, response, relative_path): if self.get_force_download(): response = self.add_attachment_header(response, relative_path) return response def serve(self, request, relative_path): response = HttpResponse() response = self.prepare_response(request, response, relative_path) return response from django.conf.urls import patterns, url from django.conf import settings def get_urls(self): return ( ) url(r'^{0}(?P.*)$'.format(settings.PRIVATE_MEDIA_URL.lstrip('/')), self.serve_protected_path,), ) def get_urls(self): from django.conf.urls import url, include # Since this module gets imported in the application's root package, # it cannot import models from other applications at the module level, # and django.contrib.contenttypes.views imports ContentType. from django.contrib.contenttypes import views as contenttype_views if settings.DEBUG: self.check_dependencies() def wrap(view, cacheable=False): def wrapper(*args, **kwargs): return self.admin_view(view, cacheable)(*args, **kwargs) wrapper.admin_site = self return update_wrapper(wrapper, view) # Admin-site-wide views. urlpatterns = [ url(r'^$', wrap(self.index), name='index'), url(r'^login/$', self.login, name='login'), url(r'^logout/$', wrap(self.logout), name='logout'), url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'), url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True), name='password_change_done'), url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'), url(r'^r/(?P\d+)/(?P.+)/$', wrap(contenttype_views.shortcut), name='view_on_site'), ] # Add in each model's views, and create a list of valid URLS for the # app_index valid_app_labels = [] for model, model_admin in self._registry.items(): urlpatterns += [ url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), ] if model._meta.app_label not in valid_app_labels: valid_app_labels.append(model._meta.app_label) # If there were ModelAdmins registered, we should have a list of app # labels for which we need to allow access to the app_index view, if valid_app_labels: regex = r'^(?P' + '|'.join(valid_app_labels) + ')/$' urlpatterns += [ url(regex, wrap(self.app_index), name='app_list'), ] return urlpatterns @property def urls(self): return self.get_urls(), 'admin', self.name def serve_protected_path(self, request, path): """ Serve private files to users with read permission. """ if not permissions.has_read_permission(request, path): if settings.DEBUG: raise PermissionDenied else: raise Http404('File not found') return server.serve(request, relative_path=path) class LocalDevelopmentServer(BaseProtectedMediaServer): """ Serve static files from the local filesystem through django. This is a bad idea for most situations other than testing. This will only work for files that can be accessed in the local filesystem. """ def serve(self, request, relative_path): # the following code is largely borrowed from `django.views.static.serve` # and django-filetransfers: filetransfers.backends.default full_path = self.get_full_path(relative_path) if not os.path.exists(full_path): raise Http404('"{0}" does not exist'.format(full_path)) # Respect the If-Modified-Since header. content_type = self.get_mimetype(relative_path) statobj = os.stat(full_path) if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): response = HttpResponseNotModified(content_type=content_type) else: response = HttpResponse(open(full_path, 'rb').read(), content_type=content_type) response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) response = self.prepare_response(request, response, relative_path) return response class ApacheXSendfileServer(BaseProtectedMediaServer): def prepare_response(self, request, response, relative_path): response = super(ApacheXSendfileServer, self).prepare_response(request, response, relative_path) # Apache expects a 'X-Sendfile' header an the full filesystem path response['X-Sendfile'] = self.get_full_path(request, relative_path) # From django-filer (https://github.com/stefanfoulis/django-filer/): # This is needed for lighttpd, hopefully this will # not be needed after this is fixed: # http://redmine.lighttpd.net/issues/2076 response['Content-Type'] = self.get_mimetype(relative_path) return response class NginxXAccelRedirectServer(BaseProtectedMediaServer): def prepare_response(self, request, response, relative_path): response = super(NginxXAccelRedirectServer, self).prepare_response(request, response, relative_path) # Nginx expects a 'X-Accel-Redirect' header to a protected url; # the actual filesystem path is set in the nginx configuration. response['X-Accel-Redirect'] = self.get_url(relative_path) response['Content-Type'] = self.get_mimetype(relative_path) return response