Multiple Image Upload Like an E-commerce Application: Handling Django's "FileInput doesn't support multiple files

I want to allow users to upload multiple images per model, similar to how facebook or ebay does it. So far, I have fields for title and image, among others, in my model, but the “multiple” attribute doesn’t seem to work. I get the error message: ValueError: FileInput doesn’t support uploading multiple files. Thank you for your responses.
my models:

class Post(models.Model):
    SEX_CHOICES = [
        ('M', 'Masculin'),
        ('F', 'Féminin'),
    ]
    USER_TYPE_CHOICES = [
        ('particulier', 'Particulier'),
        ('professionnel', 'Professionnel'),
    ]
    GEMRE_CHOICES = [
        ('', 'selectionner un choix'),
        ('Un Homme', 'Un Homme'),
        ('Une Femme', 'Une Femme'),
    ]
    CLIENTS_CHOICES = [
        ('', 'selectionner un choix'),
        ('Tout le monde', 'Tout le monde'),
        ('Femme seulement', 'Femme seulement'),
        ('Homme seulement', 'Homme seulement'),
    ]
    DEPLACEMENT_CHOICES = [
        ('', 'selectionner un choix'),
        ('Reçoit', 'Reçoit'),
        ('Se déplace', 'Se déplace'),
        ('Réçoit ou Se déplace', 'Réçoit ou Se déplace'),
    ]
    TYPE_CHOICES = [
        ('Offre', 'Offre (vous proproser)'),
        ('Demande', 'Demande (vous rechercher)'),
    ]
    user = models.ForeignKey(User, on_delete=models.CASCADE,null=True, related_name='posts',default=1)
    titre = models.CharField(max_length=520)
    image = models.ImageField(upload_to='post/images/', blank=True, null=True, default='ing')
    description = models.TextField()
    user_type = models.CharField(max_length=20, choices=USER_TYPE_CHOICES, default='particulier')
    email = models.EmailField(max_length=254, default='default@example.com')
    created = models.DateTimeField(auto_now_add=True)
    phone_number = models.CharField(max_length=20, default='phone')
    sex = models.CharField(max_length=1, choices=SEX_CHOICES, default='M')
    id = models.CharField(max_length=100, default=uuid.uuid4, unique=True, primary_key=True, editable=False)
    date_publication = models.DateTimeField(auto_now_add=True, verbose_name="date_publication")
    genre = models.CharField(max_length=10, choices=GEMRE_CHOICES, default='')
    deplacement = models.CharField(max_length=20, choices=DEPLACEMENT_CHOICES, default='')
    clients = models.CharField(max_length=15, choices=CLIENTS_CHOICES, default='')
    types = models.CharField(max_length=7, choices=TYPE_CHOICES, default='types')
    age = models.IntegerField(validators=[MinValueValidator(18), MaxValueValidator(60)])
    quartier = models.CharField(max_length=200, verbose_name="quartier", default='')
    category = models.ForeignKey(Category, related_name='post', on_delete=models.CASCADE, default='category')
    
   

    def __str__(self):
        return str(self.titre)

    # Méthode qui retourne un extrait de la description
    def description_excerpt(self):
        return self.description[:50] + '...' if len(self.description) > 50 else self.description
    

class PostImage(models.Model):
    post = models.ForeignKey(Post, related_name='images', on_delete=models.CASCADE)
    image = models.FileField( null=True, blank=True, upload_to='post/images/')
    is_main_image = models.BooleanField(default=False)  # Indique si c'est l'image principale

    def __str__(self):
        return f"Image for {self.post.titre}"
    @property
    def main_image(self):
        return self.images.filter(is_main_image=True).first()

    class Meta:
        ordering = ['-post__date_publication']

my forms : `class PostCreateForm(forms.ModelForm):
class Meta:
model = Post

    fields = ['titre',   'email', 'phone_number', 'sex', 'genre', 'deplacement', 'clients','user_type', 'types', 'age', 'quartier','description','category',]
    labels={'category':'catégories',
            'types':'',
            'user_type':''}
    widgets = {
        'description': forms.Textarea(attrs={
        'class': 'form-textarea mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 resize-none overflow-y-auto',
    'style': 'background-color: #40414f; color: white; height: 100px;',  # Ajustez la hauteur ici
    'placeholder': "Décrivez votre produit ou services en détail",
            
            
        }),
        'titre': forms.TextInput(attrs={
            'id':"titre",
            'class': "form-textinput w-full p-4 rounded-md bg-gray-700 text-white border-none" ,
            'placeholder':'Saisissez un titre accrocheur pour votre annonce',
            'style':'background-color : #40414f; color: white;',
        }),
        'age': forms.NumberInput(attrs={
            'class':'mt-1 block w-full p-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  rounded-md bg-gray-700 text-white border-none ' ,
            'placeholder': 'Entrez l\'age',
            'style':'background-color : #40414f; color: white;',

        }),
        'clients': forms.Select(attrs={
            'class':'mt-1 block  border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500 w-full p-2  bg-gray-700 text-white border-none'  ,
            'style':'background-color : #40414f; color: white;',
        }),
        'genre': forms.Select(attrs={
            'class':'mt-1 block border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none '  ,
            'style':'background-color : #40414f; color: white;',
            }),
        'sex': forms.Select(attrs={
            'class':'mt-1 block border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none '  ,
            'style':'background-color : #40414f; color: white;',
            }),
        'deplacement': forms.Select(attrs={
            'class':'mt-1 block  border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none '  , 
            'style':'background-color : #40414f; color: white;',

        }),
        'types': forms.RadioSelect(attrs={
            'class':'mt-1 block flex  border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none '  ,
            'style':'background-color : #40414f; color: white;',
            
        }),
        'user_type': forms.RadioSelect(attrs={
            'class':'mt-1 mr-4 block flex  border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none ',
            'style':'background-color : #40414f; ',
                
        }),
        'quartier': forms.TextInput(attrs={
            'class': 'mt-1 block border-2 border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 text-xl focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none ' ,
            'placeholder':'Saisissez un titre accrocheur pour votre annonce',
            'style':'background-color : #40414f; color: white;',
        }),
        'phone_number': forms.NumberInput(attrs={
            'class':'mt-1 block form-input  border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none' ,
            'pattern':'[0-9]{10,15}',
            'inputmode':'tel',
            'placeholder': '+225 123 456 7890',
            'style':'background-color : #40414f; color: white;',
        }),
        'email': forms.EmailInput(attrs={
            'class':'w-full p-2 rounded-md bg-gray-700 text-white border-none',
            'id':"email" ,
            'type':"email",
            'placeholder':"Adresse Email",
            'style':'background-color : #40414f; color: white;',
        }),
        
        
        'category': forms.Select(attrs={
            'class':'mt-1 block  border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-red-500 focus:border-red-500  w-full p-2  bg-gray-700 text-white border-none ',
            'style':'background-color : #40414f; ',

}),

    }

class PostImageForm(forms.ModelForm):
image = forms.ImageField(widget=forms.FileInput(attrs={
‘class’: ‘hidden form-control’, # Cacher le champ par défaut
‘accept’: ‘image/*’,
}))

class Meta:
    model = PostImage
    fields = ['image','is_main_image']
    widgets = {
        
        'is_main_image': forms.CheckboxInput(attrs={'class': 'form-check-input'})
        
    }

my views :@login_required(login_url=‘login’)
def post_create_view(request):
if request.method == ‘POST’:
form = PostCreateForm(request.POST, request.FILES)
formset = request.FILES.getlist(‘image’) # Obtenir toutes les images

    if form.is_valid():
        post_instance = form.save(commit=False)
        post_instance.user = request.user
        post_instance.save()

        # Variable pour s'assurer qu'une image principale est définie
        main_image_set = False

        # Boucle sur les images téléchargées
        for image in formset:
            post_image_instance = PostImage(post=post_instance, image=image)
            
            # Définir la première image comme principale si aucune autre image principale n'est définie
            if not main_image_set:
                post_image_instance.is_main_image = True
                main_image_set = True
            
            post_image_instance.save()

        return redirect('home')
else:
    form = PostCreateForm()
    formset = PostImageForm(queryset=PostImage.objects.none())

return render(request, 'post/post_create.html', {'form': form, 'formset': formset})

` help me please

Not the exact issue but there was a similar post https://forum.djangoproject.com/t/multiplefilefield-returns-no-data/32278.

The code for the form is as below (which is in the link).

See if using the MultipleFileField instead of FileInput in your form will help you.

class MultipleFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True


class MultipleFileField(forms.FileField):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault("widget", MultipleFileInput())
        super().__init__(*args, **kwargs)

    def clean(self, data, initial=None):
        print('>>>', data, initial)
        single_file_clean = super().clean
        if isinstance(data, (list, tuple)):
            result = [single_file_clean(d, initial) for d in data]
        else:
            result = [single_file_clean(data, initial)]
        return result


class FileFieldForm(forms.Form):
    file_field = MultipleFileField()