Django forme m2m sauvegarde « à » la table
Question
Je ne parviens pas à sauver les données m2m, contenant un « à » table de classe. Je veux sauver tous les membres sélectionnés (sélectionnés sous la forme) dans le tableau par. Mais je ne sais pas comment l'initialiser « à » table dans la vue.
mon code:
class Classroom(models.Model):
user = models.ForeignKey(User, related_name = 'classroom_creator')
classname = models.CharField(max_length=140, unique = True)
date = models.DateTimeField(auto_now=True)
open_class = models.BooleanField(default=True)
members = models.ManyToManyField(User,related_name="list of invited members", through = 'Membership')
class Membership(models.Model):
accept = models.BooleanField(User)
date = models.DateTimeField(auto_now = True)
classroom = models.ForeignKey(Classroom, related_name = 'classroom_membership')
member = models.ForeignKey(User, related_name = 'user_membership')
et dans la vue:
def save_classroom(request):
classroom_instance = Classroom()
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES, user = request.user)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
membership = Membership(member = HERE SELECTED ITEMS FROM FORM,classroom=new_obj)
membership.save()
Comment dois-je initialiser les membres de la table d'adhésion à être droite peuplée?
La solution
En cas d'utilisation normale par rapport m2m (pas par table intermédiaire), vous pouvez remplacer:
membership = Membership(member = HERE SELECTED ITEMS FROM FORM,classroom=new_obj)
membership.save()
avec
form.save_m2m()
Mais en cas d'utilisation des tables intermédiaires dont vous avez besoin pour gérer manuellement les données POST et créer des objets d'adhésion avec tous les champs obligatoires ( problème similaire). La solution la plus basique est de changer votre point de vue à quelque chose comme:
def save_classroom(request):
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
for member_id in request.POST.getlist('members'):
membership = Membership.objects.create(member_id = int(member_id), classroom = new_obj)
return HttpResponseRedirect('/')
else:
form = ClassroomForm()
return render_to_response('save_classroom.html', locals())
Notez comment request.POST est manipulé (.getlist). En effet, poste et obtenez sont QueryDict objets a des implications (request.POST [ 'membres'] retournera toujours un objet!).
Vous pouvez modifier ce code pour obtenir plus fiable (erreur de manipulation, etc.), et plus bavard, par exemple:
member = get_object_or_404(User, pk = member_id)
membership = Membership.objects.create(member = member , classroom = new_obj)
Mais notez que vous effectuez des requêtes db dans une boucle qui n'est pas une bonne idée en général (en termes de performance).
Autres conseils
Comme quoi Dzida a fait, mais l'utilisation form.cleaned_data au lieu de request.post:
def save_classroom(request):
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
for member in form.cleaned_data['members'].all():
Membership.objects.create(member = member, classroom = new_obj)
return HttpResponseRedirect('/')
else:
form = ClassroomForm()
return render_to_response('save_classroom.html', locals())
Vous devez également tenir compte des adhésions pourraient être supprimés, donc:
def save_classroom(request):
if request.method == 'POST':
form = ClassroomForm(request.POST, request.FILES)
if form.is_valid():
new_obj = form.save(commit=False)
new_obj.user = request.user
new_obj.save()
final_members = form.cleaned_data['members'].all()
initial_members = form.initial['members'].all()
# create and save new members
for member in final_members:
if member not in initial_members:
Membership.objects.create(member = member, classroom = new_obj)
# delete old members that were removed from the form
for member in initial_members:
if member not in final_members:
Membership.objects.filter(member = member, classroom = new_obj).delete()
return HttpResponseRedirect('/')
else:
form = ClassroomForm()
return render_to_response('save_classroom.html', locals())
Si vous utilisez des formulaires modèles (comme dans un CBV générique: form_class=ClassroomForm
), override et mettez la logique d'économie ci-dessus dans la méthode save
, quelque chose comme:
ClassroomForm(forms.ModelForm):
members = ModelMultipleChoiceField(
queryset=Classroom.objects.all(),
widget=SelectMultiple
)
def save(self, commit=True):
classroom = super().save(commit=False)
if commit:
classroom.save()
if 'members' in self.changed_data:
final_members = self.cleaned_data['members'].all()
initial_members = self.initial['members']
# create and save new members
for member in final_members:
if member not in initial_members:
Membership.objects.create(member = member, classroom = new_obj)
# delete old members that were removed from the form
for member in initial_members:
if member not in final_members:
Membership.objects.filter(member = member, classroom = new_obj).delete()
return classroom
Vous devez également spécifier la salle de classe pour les membres:
membership = Membership(member = request.user,
classroom=new_obj) #if new_obj if your classroom
membership.save()
Je suppose que vous devez également supprimer Voici comment je l'ai fait dans une vue à base de classe updateForm générique (django 1.8) pour une application similaire mais différent en utilisant la méthode form_valid. User
dans accept = models.BooleanField(User)
. Il ne devrait pas être nécessaire de fixer la date lors de l'enregistrement si vous utilisez auto_now
! Mais peut-être `auto_now_add est plus probablement ce que vous avez besoin (
def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
self.object.members.clear()
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
list_of_members = form.cleaned_data['members']
ClassRoom.objects.bulk_create([
Membership(
Course=self.object,
member=member_person,
order=num)
for num, member_person in enumerate(list_of_members)
])
return super(ModelFormMixin, self).form_valid(form)