文件上传错误请求

Bad Request on File upload

我已经按照本教程中所述使用 nginx 和 gunicorn 设置了我的 python 项目 https://djangocentral.com/deploy-django-with-nginx-gunicorn-postgresql-and-lets-encrypt-ssl-on-ubuntu/

到目前为止一切正常。 我的项目在 /root/project_folder/

我想通过管理页面上传文件,但收到错误请求错误 400。我想将文件添加到的媒体文件夹是 /var/www/my_domain/media(归 www-data 组所有)。这也已正确配置,因为当我手动将它们移动到该文件夹​​时我可以看到图像。

你们知道为什么会出现这个问题吗?

主要问题是上传时我需要将图像保存到媒体根目录:

/var/www/domain/media/images/dad/ProfilePicture/offline_screen.png

但是当我发送请求查看图像时,Web 服务器 return 如下:

/media/var/www/domain/media/images/dad/ProfilePicture/offline_screen.png

upload_to 路径需要是 images/dad/ProfilePicture/offline_screen.png 才能正确请求,但随后图像被上传到错误的文件夹

有什么想法吗?提前致谢!

更新:

这是我的 nginx 配置 `

server {
    server_name domain.net www.domain.net ip_address;

location = /favicon.ico { access_log off; log_not_found off; }
location  /static/ {
    root /var/www/domain;
}

location = /media/ {
    root /var/www/domain;
}

location / {
    include proxy_params;
    proxy_pass http://unix:/var/log/gunicorn/domain.sock;
}

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/domain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/domain/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {

if ($host = www.domain) {
    return 301 https://$host$request_uri;
} # managed by Certbot

if ($host = domain) {
    return 301 https://$host$request_uri;
} # managed by Certbot

listen 80;
server_name domain www.domain ip_address;
return 404; # managed by Certbot
}

`

这是我的媒体根目录/媒体 url:

MEDIA_ROOT = '/var/www/domain/media' #os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

url.py:

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('webpages.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)

最后是我如何提供和上传图片:

def get_path_of_content_image(instance, filename):
    return os.path.join(settings.MEDIA_ROOT, "images", str(instance.course.topic), str(instance.course), filename)

class Topic(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(max_length=445)
image = models.ImageField(upload_to=get_topic_profile_path, default="images/default.jpeg")

def __str__(self):
    return self.title

def image_tag(self):
    if self.image:
        return mark_safe('<img src="%s" style="width: 200px; height:200px;" />' % self.image.get_image())

def get_image(self):
    if self.image:
        return str(self.image.url).replace('/media/var/www/domain', '')  ########1
    else:
        return settings.STATIC_ROOT + 'webpages/img/default.jpeg'

image_tag.short_description = 'Current Image'

我写了这一行 #######1 因为将上传路径设置为完整的媒体根目录但是为了 return 正确的路径我需要删除媒体根目录前缀。而且非常重要的是,这仅在 DEBUg 设置为 TRUE 时有效。

我不确定这是否能解决您的问题,但这里有两处需要正确配置。如果 nginx 正在为您的媒体目录中的文件提供服务,那么我猜您的 nginx 配置已经可以了,您只需要编辑 settings.py,但我会包括 nginx 配置以防它帮助其他人。

更新

我不认为你的模型中的那些方法是必要的,虽然我真的不知道它们是如何使用的。这是我如何使用 ImageField:

配置模型的示例

首先,如何上传和保存图片

(让 django 完成大部分工作)

models.py

...
class Post(models.Model):
    """Represents a single blog post"""

    # model fields
    title = models.CharField(max_length=150, unique=True)
    content = models.TextField()
    title_image = models.ImageField(upload_to='image/', blank=True, null=True)
...

forms.py

...
class PostModelForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'title_image', 'content']
        widgets = {
            'content': Textarea(attrs={'cols': 80, 'rows': 25}),
        }
...

views.py

...
def new_post(request):
    context = {}
    template = 'new_post.html'
    # POST
    if request.method == 'POST':
        form = PostModelForm(request.POST or None, request.FILES)
        if form.is_valid():
            new_post = form.save()
            return redirect('list_post')
        else: 
            context['form'] = form
            return render(request, template, context)
    # GET
    else:
        context['form'] = PostModelForm()
        return render(request, template, context)
...

new_post.html(注意表格的 enctype - 如果您想上传文件,这很重要)

...
                    <div class="card bg-light">
                        <div class="card-header">
                            <h3>New Post</h3>
                        </div>
                        <div class="card-body">
                            <form action="{% url 'blog:new_post' %}" method="POST" class="w-100" enctype="multipart/form-data">
                                <div class="col-12">
                                    {% csrf_token %}
                                    {{ form.as_p }}                                    
                                </div>
                                <div class="col-12">
                                    <ul class="actions">
                                        <button class="primary" type="submit">Save</button>
                                    </ul>
                                </div>
                            </form>
                        </div>
                    </div>
...

其次,如何提供您的图像

现在您想在标记中显示图像

views.py

...
def detail(request, id):
    context = {}
    template_name = 'post.html'
    post = get_object_or_404(Post, id=id)
    context['post'] = post
    return render(request, template_name, context)
...

post.html

中的标记中
...
            {% if post.title_image %}
                <span class="img"><img src="{{ post.title_image.url }}" alt="image for {{ post.title }}" /></span>
            {% endif %}
...

(1) 在settings.py

中定义MEDIA_ROOT和MEDIA_URL

settings.py中添加

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

注意:阅读 MEDIA_ROOT and MEDIA_URL

上的 django 文档

(2) 在 nginx 服务器块中定义媒体位置

在该教程中,nginx 配置没有为媒体定义位置。这是教程中的服务器块:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location  /static/ {
        root /path/to/staticfiles;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/log/gunicorn/project_name.sock;
    }
}

我觉得应该是这样的:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }

    location  /media/ {
        root /path/to/media;
    }

    location  /static/ {
        root /path/to/staticfiles;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/log/gunicorn/project_name.sock;
    }
}

如果该配置不起作用,我在下面发布了适合我的配置 然而,根据我自己使用 django、nginx 和 gunicorn 的经验,我的 nginx 配置如下所示:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate /etc/letsencrypt/live/website_name.com-0001/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/website_name.com-0001/privkey.pem; # managed by Certbot

    server_name website_name.com www.website_name.com;


    location = /favicon.ico { access_log off; log_not_found off; }

    location /media {
        alias /home/user_name/website_name/project_name/media;
    }

    location /static/ {
        alias /home/user_name/website_name/project_name/static_root/;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }

}

注意 1:这里是 alias and root 指令的 nginx 文档。

所以在花了很长时间调试之后,我找到了解决问题的方法:

首先出现问题的是 nginx 文件中 'location' 和“/media/”之间的“=”。确保这不是错误的根源。它应该是这样的:

location /media {
    alias /home/user_name/website_name/project_name/media;
}

而不是这样:

location = /media {
    alias /home/user_name/website_name/project_name/media;
}

文件上传到自定义本地目录可以这样完成:

from django.core.files.storage import FileSystemStorage

fs = FileSystemStorage(location=settings.MEDIA_ROOT)

并为模型在上传时添加文件存储选项(或之后使用更新后的路径更改图像名称):

image = models.ImageField(upload_to=get_topic_profile_path, default="images/default.jpeg", storage=fs)

感谢其他回答。通过修复这个错误,我学到了很多东西 :D