Skip to content
Advertisement

Django upload multiple images per post

I want to let the user upload multiple images per post. Similarly to an e-commerce platform with multiple images per product. But till now the images are not sent to the database.

That’s my code so far:

models.py:

class Project(models.Model):
    title = models.CharField(max_length=200)
    describtion = models.TextField(null=True, blank=True)

class ProjectImage(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    image = models.FileField(upload_to="products")

forms.py:

class ProjectForm(ModelForm):
    image = forms.ImageField(widget=ClearableFileInput(attrs={'multiple':True}))
    class Meta:
        model = Project
        fields = ['title', 'describtion']

views.py:

def createProject(request):
    form = ProjectForm()

    if request.method == 'POST':
        form = ProjectForm(request.POST)
        images = request.FILES.getlist('image')
        if form.is_valid():
            project = form.save()
            for i in images:
                ProjectImage(project=project, image=i).save()
    context = {'form':form}
    return render(request, 'projects/project_form.html', context)

project_form.html:

<form class="form" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {% for field in form %}
    <div class="form__field">
        <label for="formInput#text">{{field.label}}</label>
        {{field}}
    </div>
    {% endfor %}
    <input type="submit" name="" id="">
</form>

settings.py:

STATIC_URL = '/static/'
MEDIA_URL = '/images/'

STATICFILES_DIRS = [
    BASE_DIR / 'static'
]

MEDIA_ROOT = os.path.join(BASE_DIR, 'static/images')
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

project urls.py

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

app urls.py

urlpatterns = [
    path("", views.createProject, name="create-project")
]

Advertisement

Answer

Issue:

  1. You have made ProjectForm which relates to Project model, but the image field is in ProjectImage model. So, image field is not even passing to the template and you also haven’t passed it in fields=['title','describtion'] in ProjectFrom.

  2. You haven’t made configurations for saving the media files in project’s urls.py.

Solution:

  1. You should make two forms in forms.py, first ProjectForm which will get the data for Project model and second ProjectImageForm which will get the list of images, then using request.FILES.getlist('image') you can save images which relates to a particular instance one by one in loop as you tried to save.

  2. You should make media configurations in project’s urls.py



Try Below Code:

forms.py

from django import forms
from django.forms import ClearableFileInput
from .models import Project, ProjectImage


class ProjectForm(forms.ModelForm):

    class Meta:
        model = Project
        fields = ['title', 'describtion']


class ProjectImageForm(forms.ModelForm):
    class Meta:
        model = ProjectImage
        fields = ['image']
        widgets = {
            'image': ClearableFileInput(attrs={'multiple': True}),
        }

views.py

from django.http import HttpResponse
from django.shortcuts import redirect, render
from .forms import ProjectImageForm, ProjectForm
from .models import Project, ProjectImage


def createProject(request):
    form = ProjectForm()
    form2 = ProjectImageForm()

    if request.method == 'POST':
        form = ProjectForm(request.POST)
        form2 = ProjectImageForm(request.POST, request.FILES)
        images = request.FILES.getlist('image')
        if form.is_valid() and form2.is_valid():
            title = form.cleaned_data['title']
            describ = form.cleaned_data['describtion']
            print(title, describ)
            project_instance = Project.objects.create(
                title=title, describtion=describ)
            print('-------------------------------------------')
            print(project_instance)
            print('-------------------------------------------')

            for i in images:
                ProjectImage.objects.create(project=project_instance, image=i)
            return redirect('thanks')

    context = {'form': form, 'form2': form2}
    return render(request, 'projects/project_form.html', context)


def thanks(request):
    return HttpResponse('<h1>Form saved.</h1>')

project_form.html or template file:

<form class="form" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {{form.title.label_tag}}
    {{form.title}}
        <br><br>
    {{form.describtion.label_tag}}
    {{form.describtion}}
        <br><br>
    {{form2.image.label_tag}}
    {{form2.image}}
        <br><br>

    <input type="submit" name="" id="">
</form>

project’s urls.py

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

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


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

app’s urls.py

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

Your models.py and settings.py can be remain same, but it’s recommended to use MEDIA_URL = 'media/' and MEDIA_ROOT = os.path.join(BASE_DIR, 'media/'), then you should make nested folders inside it to save images or any files.

Note: You should always return HttpResponseRedirect after dealing with POST data, the tip is not specific to Django, it’s a good practice in general as stated in the tutorial4.

Note: Function based views are generally written in snake_case not camelCase, you may change it to create_project from createProject.

Note: Add / at the end of upload_to as upload_to='products/' in FileField in ProjectImage model.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement