Multiple files upload via customized admin form

Hi people!
Tried to add multiple file upload field following this example.
The upload control appears and works properly - the problem is with form’s header - the <form enctype="multipart/form-data" attribute is omitted,
so I get empty request.FILE in my custom save_model function.
Only filenames are passed as text parameters, so I get in request.__dict__:
"content_type": "application/x-www-form-urlencoded", ...
"_post": <QueryDict: ... { "photos_multiple": [ "image1.jpg", "image2.jpg" ],
instead of:
"content_type": "multipart/form-data", ...
"_files": <MultiValueDict: { "photos_multiple": [<InMemoryUploadedFile: image1.jpg (image/jpeg)>, <InMemoryUploadedFile: image2.jpg (image/jpeg)> ] }>,
(example from a working non-customized admin form with a single file upload)
I also have a working project config as an example, where
<form enctype="multipart/form-data" is present, and the whole thing works!
There seem to be no differences in this part of config.
Has anybody an idea why the <form enctype="multipart/form-data" may be dropped and how can I reinforce it when necessary (like in this case).

I’d start by looking at what the browser is sending. You can use your browser’s developers tools to look at exactly what it’s sending back to the server. I would find it surprising if Django is doing anything at that layer to alter the data coming in, although I could see where it’s possible that it’s not handling the submission correctly.

At least, if you verify that the browser is sending the right data, you know that your form is being created and built correctly and you can focus on the incoming side. However, if the data isn’t being submitted correctly, then it’s more likely that your form isn’t built correctly.

(The idea here is to narrow down what you need to look at to one of those two possibilities.)

Ken

Well, it doesn’t tell much…
The request and the form structure are correct, apart form missing
enctype="multipart/form-data"

Request

Request URL: http://0.0.0.0:8000/admin/product/product/1e9ceca6-ee73-4d34-b51b-39f95d34f8ae/change/
Request Method: POST
Status Code: 302 Found
Remote Address: 0.0.0.0:8000
Referrer Policy: no-referrer-when-downgrade
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Content-Language: ru
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Thu, 20 Aug 2020 14:53:50 GMT
Expires: Thu, 20 Aug 2020 14:53:50 GMT
Location: /admin/product/product/1e9ceca6-ee73-4d34-b51b-39f95d34f8ae/change/
Server: Werkzeug/0.14.1 Python/3.7.9
Set-Cookie: messages=“551bb16c6ad0e6b82b80c7bcb991727247636866$[[”__json_message"\0541\05425\054"\u041f\u0440\u043e\u0434\u0443\u043a\u0442 (\u043c\u043e\u0434\u0435\u043b\u044c) \"<a href=\"/admin/product/product/1e9ceca6-ee73-4d34-b51b-39f95d34f8ae/change/\">\u041f\u043e\u0440\u0442\u0430\u0442\u0438\u0432\u043d\u0430\u044f \u0430\u043a\u0443\u0441\u0442\u0438\u043a\u0430 3 (\u0430\u043a\u0443\u0441\u0442\u0438\u043a\u0430)\" \u0431\u044b\u043b \u0438\u0437\u043c\u0435\u043d\u0435\u043d \u0443\u0441\u043f\u0435\u0448\u043d\u043e. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0441\u043d\u043e\u0432\u0430 \u043d\u0438\u0436\u0435."]]"; HttpOnly; Path=/; SameSite=Lax
Vary: Accept-Language, Cookie
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 3651
Content-Type: application/x-www-form-urlencoded
Cookie: djdt=hide; csrftoken=aUtjZ4jaPEevOSAJMVtLQjir94WkamgZYHHEVikyWZ6rInYlKz4WsQYDnu1Fy9wN; sessionid=j95v32tzrie9p2c7yvlrw7g1pbctgdw2
Host: 0.0.0.0:8000
Origin: http://0.0.0.0:8000
Referer: http://0.0.0.0:8000/admin/product/product/1e9ceca6-ee73-4d34-b51b-39f95d34f8ae/change/
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 OPR/70.0.3728.106
csrfmiddlewaretoken: Z3zEHizZifIZLs5rPKVqbyet9gPeVT76NQNZDwAnpAAVFXt3NowBN5UFnGUzjGnU
name: Портативная акустика 3 (акустика)
subcategory: 18c05a40-af73-4f0c-b68e-09f4875409ce
price_before:
price: 119.77
order_number: 4
active: on
description:


Product_product_images-TOTAL_FORMS: 6
Product_product_images-INITIAL_FORMS: 6
Product_product_images-MIN_NUM_FORMS: 0
Product_product_images-MAX_NUM_FORMS: 1000
Product_product_images-0-id: 4540
Product_product_images-0-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-0-productimage: 2abda7ed-93d8-402c-986c-50f5534737cc
Product_product_images-1-id: 4597
Product_product_images-1-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-1-productimage: 1f9bc900-9d2c-47d7-9e34-c98ccccf7229
Product_product_images-2-id: 4541
Product_product_images-2-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-2-productimage: d98aac7f-3b42-4c27-820e-a40b42a8908a
Product_product_images-3-id: 4542
Product_product_images-3-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-3-productimage: 1675adea-50b7-474f-98b6-4f28daddab46
Product_product_images-4-id: 4543
Product_product_images-4-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-4-productimage: 8cc9c164-9c1e-4a51-b288-6db3cf7d5b76
Product_product_images-5-id: 4544
Product_product_images-5-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-5-productimage: 80647421-882a-4b49-8290-75f1000f8b83
Product_product_images-prefix-id:
Product_product_images-prefix-product: 1e9ceca6-ee73-4d34-b51b-39f95d34f8ae
Product_product_images-prefix-productimage:
photos_multiple: куго.jpg
photos_multiple: сегвеи.jpg
_continue: Сохранить и продолжить редактирование
This text will be hidden

Can you pls share your code to upload the multiple files to admin page. I am new to django and stuck there. Thanks