Search code examples
pythondjangodjango-rest-framework

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


I have two models connected with the 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.GeneratedField(expression='',
output_field=models.CharField(max_length=1),
                                    db_persist=True,)

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

What I want to do is that 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]). A for loop won't work obviously :) .

Should I create a property field or Meta class? How can I change my field like this?

I am seeking a better approach to solve this. I've changed turn field into a Generated field so we make a query and assert it to the database. How can I do that?


Solution

  • 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)