Media files aren't stored unless uploaded via Django admin

Hi, I’ve been facing a problem with rendering user-uploaded images in my project (in development). I have a form in a template for creating an object for a Model that has an ImageField attribute. However, it does not seem to follow the upload_to argument. I would appreciate any info on how to fix this.

models.py:

class Product(models.Model):
    name = models.CharField(max_length=50)
    model = models.CharField(max_length=50)
    version = models.CharField(max_length=20)
    image = models.ImageField(default='products/default.jpeg', upload_to='products/', blank=False, null=False)

    def __str__(self):
        return self.name

views.py:

def add_product(request):

    if request.method == "POST":
           
        name = request.POST["product_name"]
        version = request.POST["product_version"]
        model = request.POST["product_model"]
        image = request.FILES.get("product_image", None)
        

        product = Product(
            name = name,
            model = model,
            version = version,
            image = image if image else 'products/default.jpeg'
        )
        product.save()

        return HttpResponseRedirect(reverse('products'))
        
    else:
        if request.user.is_authenticated:
            products = Product.objects.all()
            return render(request, 'add_product.html',{
                "products" : products
            })
        
        else:
            return render(request, "login.html", {
                "message": "Login to add products"
            })

The form in my template:

<form class="row gy-2 gx-3 align-items-center" action="{% url 'add_product' %}" method="post">
        {% csrf_token %}
        <div class="col-auto">
            <label for="autoSizingInput">Product Name</label>
            <input type="text" class="form-control" id="autoSizingInput" placeholder="Product name" name="product_name" required>
        </div>
        <div class="col-auto">
            <label for="autoSizingInputGroup">Version</label>
            <div class="input-group">
              <div class="input-group-text"><em>v</em></div>
              <input type="text" class="form-control" id="autoSizingInputGroup" placeholder="Product version" name="product_version" required>
            </div>
        </div>
        <div class="col-auto">
            <label for="autoSizingInput">Model number</label>
            <input type="text" class="form-control" id="autoSizingInput" placeholder="Model number" name="product_model" required>
        </div>
        <div class="col-auto">
            <label for="inputGroupFile02">Product image (optional)</label>
            <div class="input-group">
                <!--<div class="input-group-text">Upload</div>-->
                <input type="file" class="form-control" id="inputGroupFile02" name="product_image">
            </div>
        </div>
        <div class="col-auto">
            <button type="submit" class="btn btn-success">Save</button>
        </div>
    </form>

root urls.py:

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

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

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

app urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name="home"),
    path('login', views.signin, name="login"),
    path('logout', views.signout, name="logout"),
    path('register', views.signup, name="register"),

    path('products', views.products, name="products"),
    path('products/add', views.add_product, name="add_product"),

    path('devices', views.devices, name="device"),
    path('devices/add', views.add_device, name="add_device"),

    path('complaints', views.complaint, name="complaint"),
    path('complaints/add', views.add_complaint, name="add_complaint"),
    path('complaints/<int:complaint_id>', views.view_complaint, name="view_complaint")
]

settings.py:

STATIC_URL = 'static/'
STATICFILES_DIRS = [
    BASE_DIR / 'RMA_app/static',
]
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'

When the user creates a Product object by uploading an image, it is saved with the default image with this URL: http://localhost:8000/media/products/default.jpeg . No image is uploaded to the MEDIA_ROOT directory. However, when I create a Product object from the admin page, it works as expected by uploading the image to MEDIA_ROOT and the URL of the image being localhost:8000/media/products/filename.jpeg

I did try changing my view to:

def add_product(request):

    if request.method == "POST":
           
        name = request.POST["product_name"]
        version = request.POST["product_version"]
        model = request.POST["product_model"]
        
        image_response = request.POST["product_image"]
        if image_response:
            image = image_response
        else:
            image = None
        
        product = Product(
            name = name,
            model = model,
            version = version,
            image = image if image else 'products/default.jpeg'
        )
        product.save()

        return HttpResponseRedirect(reverse('products'))

but in this case, the images aren’t uploaded to MEDIA_ROOT. The URL for the images ended up being http://localhost:8000/media/filename.jpeg and not media/products/filename.jpeg

Any input would be appreciated. Thank you.

Welcome @radhesh !

From the docs at File Uploads | Django documentation | Django

Note that request.FILES will only contain data if the request method was POST , at least one file field was actually posted, and the <form> that posted the request has the attribute enctype="multipart/form-data" . Otherwise, request.FILES will be empty.

Your <form ... element does not show this attribute.

Side note: If you’re posting to the same url from which you got this template, you do not need the action attribute in that element. A form will post back to the url from which the page was retrieved.

2 Likes

Adding the enctype attribute to <form> solved it. Thanks a lot!

1 Like