Make a choice list from a field into other field through the Foreign key Django

I have two models connected with foreign key with each other as below :

class Service(models.Model):
    title = models.CharField(max_length=100)
    counter = models.PositiveIntegerField()

    def __str__(self) -> str:
        return f'{self.title}'


class Reservation(models.Model):
    user    = models.ForeignKey(User, on_delete=models.CASCADE)
    service = models.ForeignKey(Service, on_delete=models.CASCADE)
    turn    = models.PositiveIntegerField()

    def __str__(self) -> str:
        return f'{self.user}'

What I want to do is turn field in Reservation get option choices and choice values are from 1 to service.counter. service is the foreign key here. for example if counter in Service model is 5 the turn field in Reservation would be something like this : turn = models.PositiveIntegerField(choices=[1,2,3,4,5]). for loop won't work obviously :) . Should I create a property field or Meta class ? anyway, How can I change my field like this? Any help will appreciate and grateful.

Option 1: Only Service exists already and you want to create a complete new Reservation and let the user choose connected Service. The answer to this Option is most likely not found within django, the backend, but has to be handled within the frontend. Reason being that it requires to switch possible choices for turn on the fly when the user selects a different connected Service.

Option 2: An initial Reservation-Object with connected Service already exists when the user is asked to select turn. This can be done by django. Not on the models.py level though. This logic has to go to forms.py. We need a model form, that only needs the turn field populated.

class ReservationForm(forms.ModelForm):
    class Meta:
        model = Reservation
        fields = ['turn']
        widgets = {
            'turn': forms.Select(),  # Set the widget for the 'turn' field to Select
        }

    def __init__(self, *args, **kwargs):
        service_instance = kwargs.pop('service_instance', None)
        super(ReservationForm, self).__init__(*args, **kwargs)

        if service_instance:
            # Set the choices for the 'turn' field based on the service instance's counter
            self.fields['turn'].choices = [(i, i) for i in range(service_instance.counter + 1)]
        else:
            # If no service instance is provided, default to an empty list
            self.fields['turn'].choices = []


# views.py
service_instance = Service.objects.get(id=service_id)
form = ReservationForm(service_instance=service_instance)

Above shows how to do dynamic forms. Basically you are selecting up front in the view what you want to pass to the form. Then within the __init__ method you capture that and set it accordingly.

Since you said that this form is always already bound to an object and therefore is connected to a service already you can also go for this option:

class ReservationForm(forms.ModelForm):
    class Meta:
        model = Reservation
        fields = ['turn']
        widgets = {
            'turn': forms.Select(),  # Set the widget for the 'turn' field to Select
        }

    def __init__(self, *args, **kwargs):
        super(ReservationForm, self).__init__(*args, **kwargs)
        # next line checks if an object is already connected to the form and a service connected
        service_id = self.data.get('service') if self.is_bound else None

        if service_id:
            try:
                service_instance = Service.objects.get(id=service_id)
                # Set the choices for the 'turn' field based on the service instance's counter
                self.fields['turn'].choices = [(i, i) for i in range(service_instance.counter + 1)]
            except Service.DoesNotExist:
                # If the service does not exist, default to an empty list
                self.fields['turn'].choices = []
        else:
            # If no service ID is provided, default to an empty list
            self.fields['turn'].choices = []

# views.py
# here you need to pass an instance to the form
form = ReservationForm(request.POST, instance=Reservation.objects.get(id=1)
Back to Top