Search code examples
djangodjango-viewsdjango-formsdjango-class-based-viewsdjango-generic-views

Django ModelChoiceField: filtering object based on pk in url


I've read many questions about this topic, but none of the methods work for me.

There are 3 related models:

class Trips(models.Model):
    lake = models.CharField("Lake", max_length=150)
    city = models.CharField("City", max_length=100, blank=True)
    s_date = models.DateTimeField("Starting Date", auto_now=False, auto_now_add=False)
    e_date = models.DateTimeField("Ending Date", auto_now=False, auto_now_add=False)
    trip_id = models.AutoField(primary_key=True)    

    class Meta:
        verbose_name = "Trip"
        verbose_name_plural = "Trips"

    def __str__(self):
        return f"{self.lake}-{self.trip_id}-{self.s_date}"

class Fisherman(models.Model):
    name = models.CharField("Fisherman", max_length=50)
    trip = models.ForeignKey(Trips, on_delete=models.CASCADE)
    fisherman_id = models.AutoField(primary_key=True)

    class Meta:
        verbose_name = "Fisherman"
        verbose_name_plural = "Fishermen"

    def __str__(self):
        return f"{self.name}-{self.fisherman_id}"


class Catch(models.Model):
    fish_type = models.CharField("Fish Type", max_length=50)
    catch_id = models.AutoField(primary_key=True)
    weight = models.DecimalField("Weight", max_digits=5, decimal_places=2)
    length = models.DecimalField("Length", max_digits=5, decimal_places=2, blank=True, null=True)
    datetime = models.DateTimeField("Catch Time", auto_now=False, auto_now_add=False)
    fisherman = models.ForeignKey(Fisherman, on_delete=models.CASCADE)
    trip = models.ForeignKey(Trips, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "Catch"
        verbose_name_plural = "Catches"

    def __str__(self):
        return f"{self.fish_type}-{self.catch_id}"

I have a ModelForm to create a new catch. Here I use a ModelChoiceField to list Fishermen, but I don't know how to filter them. I only want display those who belong to the trip.

class CatchForm(forms.ModelForm):
    fisherman = forms.ModelChoiceField(queryset= Fisherman.objects.all())
    class Meta:
        model = Catch
        fields = ["fish_type", "weight", "length", "datetime", "fisherman"]
        widgets = {
            "datetime": forms.DateTimeInput(format='%Y-%m-%d %H:%M', attrs={'class':'datetimefield form-control'}),
        }

views.py

I' ve read that get_form_kwargs should be used in views to override fields in the form, but it didn't work for me.

class NewCatchView(CreateView):
    model = Catch
    form_class = CatchForm
    template_name = "new_trip/new_catch.html"
    
    # Probably, this is wrong
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['fisherman'] = Fisherman.objects.filter(trip=self.kwargs.get('pk'))
        return kwargs
    
    def form_valid(self, form):
        form.instance.trip = Trips.objects.get(pk=self.kwargs['pk'])
        return super().form_valid(form)

    def get_success_url(self):
        return reverse('new_trip:trip_details', args=(self.kwargs['pk'],))

urls.py

path("trip_details/<int:pk>/new_catch/", views.NewCatchView.as_view(), name="new_catch"),

Thank you in advance for your help!


Solution

  • You're almost there. You've created the kwarg, so now you just need to use it in the form to overwrite the original queryset:

    class CatchForm(forms.ModelForm):
        ...
    
        def __init__(self, *args, **kwargs):
            fisherman = kwargs.pop('fisherman')
            super().__init__(*args, **kwargs)
            self.fields['fisherman'].queryset = fisherman