TheBenimeni
TheBenimeni

Reputation: 121

Bad Request on File upload

I have setup my python project using nginx and gunicorn as described in this tutorial https://djangocentral.com/deploy-django-with-nginx-gunicorn-postgresql-and-lets-encrypt-ssl-on-ubuntu/

so far everything works perfectly fine. My project is in /root/project_folder/

I want to upload files via the admin page, but I get a bad request error 400. The media folder I want to add the files to is /var/www/my_domain/media (owned by the group www-data). This is also properly configured since I can see the images when I move them manually into that folder.

Do you guys maybe have any idea why the issue may be ?

the main problem is when uploading I need to save the image to the media root:

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

But when I send request to view the image the web server returns the following:

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

The upload_to path needs to be images/dad/ProfilePicture/offline_screen.png for correct request but then the image is uploaded to the wrong folder

Any ideas ? Thanks in advance!

UPDATE:

Here is my nginx config `

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
}

`

Here is my media root / media url:

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

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

And finally how I serve and upload Images:

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'

I wrote this line #######1 since set the upload path to the full media root but then in order to return the correct path I need to remove the media root prefix. And also very importantly this only works if DEBUg set to TRUE.

Upvotes: 1

Views: 1371

Answers (2)

TheBenimeni
TheBenimeni

Reputation: 121

So after spending an eternity on debugging, I have the solution to my problem:

First Thing that was problematic is the '=' between 'location' and '/media/' in the nginx file. Be sure that this is not root of the error. It should look like this:

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

and not like this:

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

The file upload to a custom local directory can be done like this:

from django.core.files.storage import FileSystemStorage

fs = FileSystemStorage(location=settings.MEDIA_ROOT)

and for the model add the file storage option on upload (or change the name of the image with the updated path afterwards):

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

thanks for the other answers. I learned quite a lot by fixing this erros :D

Upvotes: 0

orangecaterpillar
orangecaterpillar

Reputation: 866

I'm not certain this will solve your issue, but there are 2 things that need to be configured correctly here. If nginx is serving the files in your media directory ok, I'm guessing your nginx config is already ok and you just need to edit settings.py, but I'll include the nginx config in case it helps someone else.

UPDATE

I don't think those methods in your models are necessary, although I don't really know how they are used. Here's an example of how I configure a model with an ImageField:

Firstly, how to upload and save images

(letting django do most of the work)

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 (note the enctype of the form - important if you want to upload files)

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

Secondly, how to serve your images

Now you want to display the image in you markup

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

In your markup in post.html

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

(1) Define MEDIA_ROOT and MEDIA_URL in settings.py

In settings.py add

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

Note: Read the django docs on MEDIA_ROOT and MEDIA_URL


(2) define media location in nginx server block

In that tutorial the nginx configuration doesn't define a location for media. Here's the server block from the tutorial:

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;
    }
}

I think it should look something like this:

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;
    }
}

If that configuration doesn't work, I've posted what works for me below In my own experience with django, nginx and gunicorn however, my nginx configuration looks like this:

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;
    }

}

Note 1: Here are the nginx docs for alias and root directives.

Upvotes: 1

Related Questions