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