Should I upload images to a static directory in Dj

2020-07-25 10:20发布

问题:

I have this model containing an image field.

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

In my view, I download the image from the specified url and store it in the image field. For testing, I use a test user as foreign key.

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))

The image gets uploaded to customer/logo/test.png as expected. Now, how can I display those images in the frontend? I could save them into the static files directory, but only the related user should be able to access it.

(By the way, Django admin interface shows that there is a file uploaded for the Customer object. But it links to http://localhost:8000/admin/customer/customer/20/customer/logo/test.png which is a wrong location and leads to a not found page.)

回答1:

The files for FileField and ImageField are uploaded relative to settings.MEDIA_ROOT and should be accessible by the same relative filename appended to settings.MEDIA_URL. This is why your admin interface points to the wrong url. For security reasons these should be different than STATIC_ROOT and STATIC_URL, otherwise Django will raise an ImproperlyConfiguredError.

This will not prevent users from accessing files they shouldn't see, if they know or can guess the url. For this you will need to serve these private files through Django, instead of your web server of choice. Basically you need to specify a private directory under the web root level, and you need to load these files if the user has permission to see the file. E.g.:

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)

In your view you will have to serve this file. One of the custom apps on my current project uses the following function to send static files:

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

And then use send_file(customer.logo) in your view.

Django >= 1.5 should use the new StreamingHttpResponse instead of HttpResponse.