12

When user uploads image, it is stored in media folder inside project directory. The problem is that when they want to see it on the website, nginx return 403 Forbidden error for images over approximately 3 Mb.

I set nginx.conf client_max_body_size to 8M

http {

        ##
        # Basic Settings
        ##
        client_max_body_size 8M;
     ...

And already changed memory size in settings.py:

FILE_UPLOAD_MAX_MEMORY_SIZE = 8388608

When I upload an image under 3 MB, there are no problems, if I upload image over 3 MB, I can see it inside media folder but the error is raised instead of serving image:

GET https://example.com/media/images/dom.jpg 403 (Forbidden)

I noticed that files under 3 MB have different permissions:

-rw-r--r-- 1 django www-data    4962 Jul 19 19:51 61682_3995232_IMG_01_0000.jpg.150x84_q85_crop.jpg
-rw-r--r-- 1 django www-data 1358541 Jul 20 09:32 byt.jpg
-rw------- 1 django www-data 3352841 Jul 20 09:32 dom.jpg
-rw-r--r-- 1 django www-data    5478 Jul 19 20:10 downloasd.jpeg.150x84_q85_crop.jpg
-rw-r--r-- 1 django www-data    3225 Jul  9 22:53 images.jpeg.100x56_q85_crop.jpg
-rw-r--r-- 1 django www-data    6132 Jul 19 20:00 NorthYorkHouse2.JPG.150x84_q85_crop.jpg

Do you know where is the problem?

EDIT:

VIEW

class NehnutelnostUploadImagesView(LoginRequiredMixin, ExclusiveMaklerDetailView, DetailView):
    template_name = "nehnutelnosti/nehnutelnost_image_upload.html"
    model = Nehnutelnost

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = ImageUploadForm(self.request.POST, self.request.FILES, nehnutelnost=self.object)
        if form.is_valid():
            nehnutelnost_image = form.save()
            images_count = self.object.images.count()

            data = {'is_valid': True, 'row_html': image_row_renderer(nehnutelnost_image, self.request),
                    'name': nehnutelnost_image.image.name, 'url': nehnutelnost_image.image.url,}
        else:
            images_count = self.object.images.count()

            data = {'is_valid': False, 'errors': form.errors, 'images_count': images_count}
        return JsonResponse(data)

    def get_context_data(self, **kwargs):
        context = super(NehnutelnostUploadImagesView, self).get_context_data(**kwargs)
        context['images'] = self.object.images.all()
        context['podorys'] = self.object.podorys
        return context

We use https://github.com/blueimp/jQuery-File-Upload plugin to upload images.

$(function () {

            $(".js-upload-photos").click(function () {
                $("#fileupload").click();
            });

            $("#fileupload").fileupload({
                dataType: 'json',
                sequentialUploads: true, /* 1. SEND THE FILES ONE BY ONE */
                start: function (e) {  /* 2. WHEN THE UPLOADING PROCESS STARTS, SHOW THE MODAL */
                    $(".modal").modal().show();
                },
                stop: function (e) {  /* 3. WHEN THE UPLOADING PROCESS FINALIZE, HIDE THE MODAL */
                    $(".modal").modal().hide();
                    $(".modal-backdrop").hide();

                },
                {#                TODO Chrome bug?#}
                progressall: function (e, data) {  /* 4. UPDATE THE PROGRESS BAR */
                    var progress = parseInt(data.loaded / data.total * 100, 10);
                    var strProgress = progress + "%";
                    $(".progress-bar").css({"width": strProgress});
                    $(".progress-bar").text(strProgress);
                },
                done: function (e, data) {
                    if (data.result.is_valid) {

                        $(".gridly").prepend(
                            data.result.row_html
                        )


                    }
                    var message = data.result.message;
                    addMessage('success', message);
                    var errors = data.result.errors;
                    if (errors) {
                        $.each(errors, function (fieldname, error_messages) {
                            $.each(error_messages, function (_, message) {
                                addMessage('danger', message);
                            })
                        })
                    }
                    var images_count_span = $('#images_count');
                    var images_count = data.result.images_count;
                    images_count_span.text(' - ' + images_count);
                    makegrid();

                }

            });
Milano
  • 18,048
  • 37
  • 153
  • 353

1 Answers1

17

From the documentation:

By default, if an uploaded file is smaller than 2.5 megabytes, Django will hold the entire contents of the upload in memory.

In more concrete terms, it means smaller files use the MemoryFileUploadHandler while larger files use the TemporaryFileUploadHandler. The latter uses tempfile to create a temporary file with user-only access.

After going through all form and model validation and everything, the actual saving is performed by FileSystemStorage._save method. At this point, the file is still either a TemporaryUploadedFile or a InMemoryUploadedFile depending on its size.

Now, a TemporaryUploadedFile is an actual file, created by tempfile, with user-only permissions.

The save method does the smart thing: if given a temporary file (namely, if hasattr(content, 'temporary_file_path')), it moves it instead of copying it. This means it keeps its user-only permissions and remains unreadable by www-data.

The problem doesn't show up with InMemoryUploadedFile, which will simply use whatever default permissions the process has (in your case, read/write for both user and group).

How to fix?

The storage object can set the permissions if so requested. For the default storage object, you can set this using FILE_UPLOAD_PERMISSIONS. Here,

FILE_UPLOAD_PERMISSIONS=0o640

…should do the trick.

(that's R/W for django user and read-only for nginx)

spectras
  • 13,105
  • 2
  • 31
  • 53
  • I've the same problem but I'm newbie with Django. I don't have understood where I must put "FILE_UPLOAD_PERMISSIONS=0o640". I put this in settings.py(if yes where?) or into the model of my upload app? @Milano – MaxDragonheart Dec 28 '18 at 18:18
  • 1
    @MassimilianoMoraca As you can see spectras added a link to the source. If you click on the link FILE_UPLOAD_PERMISSIONS, you can see it should be in settings.py. – Milano Dec 28 '18 at 18:25
  • Thank you @Milano , I've see this but my dubt is where in settings.py I must put it.... – MaxDragonheart Dec 28 '18 at 18:27
  • I've put the code after STATICFILES_DIRS, at the end of settings.py, and nothing is change. Is too hard explane in wich part of that file is correct to put the code? Between MIDDLEWARE and ROOT_URLCONF, for example?? – MaxDragonheart Dec 28 '18 at 20:39
  • 2
    It doesn't matter where you put it (unless you overwrite it somewhere above). The problem is somewhere else. If it is production server, you should check your server limits, for example NGINX and Gunicorn. – Milano Dec 28 '18 at 22:50
  • Unfortunately, even this didn't solve my problem. Files greater than 3MB are still having different permissions resulting in denial of permission for nginx. `-rw-r-----` this is the permission set on the files which cannot be read. Other non problematic files have the permissions set as `-rwxr-xr-x` – Mohammed Shareef C Jun 02 '19 at 12:36
  • @MohammedShareefC that's because your webserver is not in the same group as your django application, therefore it uses the "other" permission and not the "group" permission. You should probably fix that, but if you really cannot, then opening the files to reading to all users with `0o644` will work. – spectras Jun 05 '19 at 00:09
  • 1
    @spectras My nginx user is `www-data`. it worked well when `FILE_UPLOAD_PERMISSIONS=0o2755` was added – Mohammed Shareef C Jun 05 '19 at 13:25