Должен ли я загружать изображения в статический каталог в Django?

У меня есть эта модель, содержащая поле изображения.

from django.db import models
from django.contrib.auth.models import User


class Customer(models.Model):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=127)
    logo = models.ImageField(upload_to='customer/logo', null=True, blank=True)

    def __str__(self):
        return self.name

На мой взгляд, я загружаю изображение с указанного URL-адреса и сохраняю его в поле изображения. Для тестирования я использую тестового пользователя в качестве внешнего ключа.

import json
import urllib.request

from django.core.files.base import ContentFile
from django.http import HttpResponse
from django.contrib.auth.models import User

from customer.models import Customer


def create(request):
    values = json.loads(request.body.decode('utf-8'))
    values['user'] = User.objects.get(id=1)
    values['logo'] = ContentFile(urllib.request.urlopen(values['logo']).read(),
                                                                    'test.png')
    model = Customer.objects.create(**values)
    return HttpResponse('Created customer with ' + str(values))

Изображение загружается в customer/logo/test.png, как и ожидалось. Теперь, как я могу отобразить эти изображения во внешнем интерфейсе? Я мог бы сохранить их в каталог статических файлов, но только соответствующий пользователь должен иметь к нему доступ.

(Кстати, интерфейс администратора Django показывает, что файл загружен для объекта Customer. Но он ссылается на http://localhost:8000/admin/customer/customer/20/customer/logo/test.png, что является неправильным местоположением и ведет на ненайденную страницу.)


person danijar    schedule 04.05.2014    source источник


Ответы (1)


Файлы для FileField и ImageField загружаются относительно settings.MEDIA_ROOT и должны быть доступны по тому же относительному имени файла, добавленному к settings.MEDIA_URL. Вот почему ваш интерфейс администратора указывает на неправильный URL. Из соображений безопасности они должны отличаться от STATIC_ROOT и STATIC_URL, иначе Django вызовет ошибку ImproperlyConfiguredError.

Это не помешает пользователям получить доступ к файлам, которые они не должны видеть, если они знают или могут угадать URL-адрес. Для этого вам нужно будет обслуживать эти личные файлы через Django, а не через выбранный вами веб-сервер. По сути, вам нужно указать частный каталог на корневом веб-уровне, и вам нужно загрузить эти файлы, если у пользователя есть разрешение на просмотр файла. Например.:

from django.core.files.storage import FileSystemStorage

PRIVATE_DIR = os.path.join(ROOT_DIR, 'web-private')
fs = FileSystemStorage(location=PRIVATE_DIR)

class Customer(models.Model):
    logo = models.ImageField(upload_to='customer/logo', storage=fs, null=True, blank=True)

По вашему мнению, вам придется обслуживать этот файл. Одно из пользовательских приложений в моем текущем проекте использует следующую функцию для отправки статических файлов:

import mimetypes
from django.http import HttpResponse # StreamingHttpResponse
from django.core.servers.basehttp import FileWrapper

def send_file(file):
    """
    Send a file through Django without loading the whole file into
    memory at once. The FileWrapper will turn the file object into an
    iterator for chunks of 8KB.
    """
    filename = file.name
    if settings.PRIVATE_MEDIA_USE_XSENDFILE:
        # X-sendfile
        response = HttpResponse()
        response['X-Accel-Redirect'] = filename  # Nginx
        response['X-Sendfile'] = filename        # Apache 2, mod-xsendfile
        # Nginx doesn't overwrite headers, but does add the missing headers.
        del response['Content-Type']
    else:
        # Can use django.views.static.serve() method (which supports if-modified-since),
        # but this also does the job well, as it's mainly for debugging.
        mimetype, encoding = mimetypes.guess_type(filename)
        response = HttpResponse(FileWrapper(file), content_type=mimetype)
        response['Content-Length'] = os.path.getsize(filename)
    return response

А затем используйте send_file(customer.logo) в своем представлении.

Django >= 1.5 должен использовать новый StreamingHttpResponse вместо HttpResponse.

person knbk    schedule 04.05.2014
comment
Большое спасибо! Могу ли я использовать request.user как обычно для проверки разрешений? - person danijar; 04.05.2014
comment
Да, это, вероятно, лучший способ, если вы разрешаете пользователям просматривать только свои собственные файлы. - person knbk; 04.05.2014