You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
7.4 KiB
193 lines
7.4 KiB
8 years ago
|
# -*- 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<path>.*)$'.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<content_type_id>\d+)/(?P<object_id>.+)/$', 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<app_label>' + '|'.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
|