I am trying to create an event staff management tool in Django. One part of the data model is that a staff member (class Staff) is linked to a scheduled shift (ScheduledEventShift) via ManyToManyField through StaffShift.
Every member of staff can see the shifts available for him / her and select them via ModelMultipleChoiceField. This works perfectly fine and the relation is correctly written to the StaffShift table depending on whether the shift is ticked or not.
(sorry the tool is in German, but it should be clear from context what I mean)
What I want to do now is "the opposite" of that, i.e. I as an admin want to look at a single shift, see which staff members have applied for that shift and untick those that are "too much" (i.e. 2 required, 3 applied).
When using a ModelMultipleChoiceField in that direction, I am able to pick exactly those staff members that I should pick in the QuerySet (this is all fine) but all of them are always unticked (which should not be as they are exactly those that are in the StaffShift through table for that particular shift) and it has no effect whatsoever on the StaffShift table if I change the ticks.
Here the relevant part of my code:
models.py:
class Staff(models.Model):
<...>
shifts = models.ManyToManyField(ScheduledEventShift, through='StaffShift', related_name='applied_staff')
forms.py:
class ScheduledEventShiftEditForm(forms.ModelForm):
class Meta:
model = ScheduledEventShift
fields = ['shift_count_required', 'applied_staff', 'event_shift_info']
shift_count_required = models.IntegerField()
applied_staff = forms.ModelMultipleChoiceField(
queryset=None,
widget=forms.CheckboxSelectMultiple)
views.py:
class ScheduledEventShiftUpdateView(ObjectUpdateView):
model = ScheduledEventShift
form_class = ScheduledEventShiftEditForm
<...>
def get_form(self, form_class=None):
form = super().get_form(form_class=self.form_class)
form.fields['applied_staff'].queryset = ScheduledEventShift.objects.filter(
id=self.kwargs.get('pk')).first().applied_staff.all()
Is such a ManyToMany relationship with through table not perfectly symmetrical (i.e. connected models can be used the same way from both sides)? Do I have to do this in a different way when my model for the form is not the class where the ManyToManyField and the through table are defined but "the other class"? Thanks a lot
Edit: I was able to create a maximally reduced example from scratch where I encounter exactly the same issue (I wanted to make sure that nothing in the rest of the code has some side effect). For this example I will post the full .py files. StaffUpdateView/Form works as intended -- it saves the changes to StaffShift. ShiftUpdateView/Form shows all the staff but no ticks and also saves no changes.
models.py
from django.db import models
class Shift(models.Model):
shift_name = models.CharField(max_length=32)
class Staff(models.Model):
staff_name = models.CharField(max_length=32)
shifts = models.ManyToManyField(Shift, through='StaffShift', related_name='staff')
class StaffShift(models.Model):
staff = models.ForeignKey(Staff, on_delete=models.CASCADE)
shift = models.ForeignKey(Shift, on_delete=models.CASCADE)
views.py:
from django.views.generic import UpdateView
from .forms import *
class StaffUpdateView(UpdateView):
form_class = StaffUpdateForm
template_name = 'playground/base_form.html'
model = Staff
def get_success_url(self):
return f'/staff/{self.get_object().id}/'
class ShiftUpdateView(UpdateView):
form_class = ShiftUpdateForm
template_name = 'playground/base_form.html'
model = Shift
def get_success_url(self):
return f'/shift/{self.get_object().id}/'
forms.py:
from django import forms
from .models import *
class StaffUpdateForm(forms.ModelForm):
class Meta:
model = Staff
fields = ['staff_name', 'shifts']
staff_name = forms.CharField(max_length=32)
shifts = forms.ModelMultipleChoiceField(
queryset=Shift.objects.all(),
widget=forms.CheckboxSelectMultiple)
class ShiftUpdateForm(forms.ModelForm):
class Meta:
model = Shift
fields = ['shift_name', 'staff']
shift_name = forms.CharField(max_length=32)
staff = forms.ModelMultipleChoiceField(
queryset=Staff.objects.all(),
widget=forms.CheckboxSelectMultiple)
CodePudding user response:
Because staff
is not a field on the Shift
model, we have to manually set the initial value for the field and also save the changes manually too
class ShiftUpdateForm(ModelForm):
class Meta:
model = Shift
fields = ['shift_name', 'staff']
shift_name = forms.CharField(max_length=32)
staff = forms.ModelMultipleChoiceField(
queryset=Staff.objects.all(),
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['staff'].initial = self.instance.staff.all()
def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs)
instance.staff.set(self.cleaned_data['staff'])
return instance