Use data from GET request in get_intial() and get_form_kwargs() of FormVIew

1.1k Views Asked by At

I am trying to refactor my code to inherit FormView instead of View. The view I'm working with receives values in the GET request. I retrieve the values in the get_context_data method and pass them through different functions to end up with a set of variables that I can pass in the context.

In short:

For the sake of example, the set of variables includes variables FOO and BAR. I need to initialise my form by passing variable FOO in the kwargs and additionally set my form field's initial value to BAR. I understand I should use the get_initial() and get_form_kwargs() methods to do this. I am just struggling with how to get FOO and BAR from the get_context_data method.

I tried adding FOO and BAR to the context dictionary:

context = super().get_context_data(**kwargs)
context["FOO"] = foo
context["BAR"] = bar
return context

And then calling it from the other methods:

def get_initial(self):
    """ Get initial value for the form field """

    initial = super(NameOfView, self).get_initial()
    context = self.get_context_data()
    initial_value = context["BAR"]
    initial.update({'name': inital_value})
    return initial

and the same for get_form_kwargs. But I get a RecursionError:

maximum recursion depth exceeded while calling a Python object

Any help understanding how I can acheive this will be appreciated

UPDATE: My Actual code is a bit more like this:*

class ConfirmTripView(FormView):
    """
    Provides the user a set of choice options based on their search input in
    the products.TripsView
    """

    model = Booking
    template_name = "bookings/trips_available.html"
    form_class = DateChoiceForm

    def __init__(self):
        self.searched_date = None
        self.passengers = None
        self.destination_id = None
        self.gte_dates = None
        self.lt_dates = None

    def convert_to_int(self, type_tuple):
        """ Converts tuple value to integer """

        type_int = int(''.join(type_tuple))
        return type_int

    def get_available_trips(self, destination, passengers):
        """ Find trips with enough seats for searched no. of passengers """

        available_trips = Trip.objects.filter(
            destination=destination
        ).filter(seats_available__gte=passengers)
        return available_trips

    def get_trips_matched_or_post_date(self, date, destination, passengers):
        """
        Returns trips that either match or are post- searched_date
        Refine to trips with dates closest to searched_date
        limit to 3 results
        """

        available_trips = self.get_available_trips(destination, passengers)
        gte_dates = available_trips.filter(date__gte=date)[:3]
            return gte_dates

    def get_trips_preceding_date(self, date, destination, passengers):
        """
        Returns trips that are pre- searched_date
        Refines to trips with dates closest to searched_date
        limits to 3 results
        """

        available_trips = self.get_available_trips(destination, passengers)
        lt_dates = available_trips.filter(date__lt=date).order_by("-date")[:3]
        return lt_dates

    def make_timezone_naive(self, obj):
        """ Turns date attribute to a time-zone naive date object """

        date_attr = obj.date
        date_string = date_attr.strftime("%Y-%m-%d")
        datetime_naive = datetime.strptime(date_string, "%Y-%m-%d")
        return datetime_naive

    def get_trips_queryset(self, gte_dates, lt_dates):
        """ Creates the queryset that will be used by the ModelChoiceField
        in the DateChoiceForm """

        # Merge both queries
        trips = lt_dates | gte_dates
        trips = trips.order_by('date')
        return trips

    def get_initial(self, **kwargs):
        """ Takes values from get request and formulates variables
        to be used in the form """

        # Values from GET request
        self.searched_date = self.request.GET.get('request_date')
        self.passengers = self.request.GET.get('passengers')
        self.destination_id = self.convert_to_int(
            self.request.GET.get("destination")
        )

        # Return querysets for dates before/beyond searched_date respectively:
        self.gte_dates = self.get_trips_matched_or_post_date(
            self.searched_date,
            self.destination_id,
            self.passengers)

        self.lt_dates = self.get_trips_preceding_date(
            self.searched_date,
            self.destination_id,
            self.passengers)

        naive_searched_date = datetime.strptime(self.searched_date, "%Y-%m-%d")
        # Find the trip closest to the searched_date (for form initial value)
        if self.gte_dates:
            gte_date = self.gte_dates[0]
            naive_gte_date = self.make_timezone_naive(gte_date)
            if self.lt_dates:
                lt_date = self.lt_dates[0]
                naive_lt_date = self.make_timezone_naive(lt_date)

                if (
                    naive_gte_date - naive_searched_date
                    > naive_searched_date - naive_lt_date
                ):
                    default_selected = lt_date
                else:
                    default_selected = gte_date

            else:
                default_selected = gte_date

        elif self.lt_dates:
            lt_date = self.lt_dates[0]
            default_selected = lt_date

        else:
            messages.error(
                self.request,
                "Sorry, there are no dates currently available for the"
                "selected destination.",
            )

        # Get initial valuees for the form
        initial = super(ConfirmTripView, self).get_initial()
        initial.update({'trip': default_selected})
        return initial

    def get_form_kwargs(self, **kwargs):
        """ Provides keyword arguemnt """

        kwargs = super(ConfirmTripView, self).get_form_kwargs()

        trips = self.get_trips_queryset(self.gte_dates, self.lt_dates)
        kwargs.update({'trips': trips})
        return kwargs

    def get_context_data(self, **kwargs):

        context = super().get_context_data(**kwargs)
        destination = Product.objects.filter(id=self.destination_id)
        context["passengers"] = self.passengers
        context["destination_obj"] = destination
        return context

    def form_valid(self, form):
        """
        Takes the POST data from the DateChoiceForm and creates an
        Intitial Booking in the database
           """

        booking = form.save(commit=False)
        booking.status = "RESERVED"
        booking.save()
        trip = form.cleaned_data['trip']
        destination = trip.destination
        booking_line_item = BookingLineItem(
            booking=booking,
            product=destination,
            quantity=self.request.GET.get("passengers")
        )
        booking_line_item.save()
        return redirect('create_passengers', booking.pk)
1

There are 1 best solutions below

11
AudioBubble On

First of all, bookmark this.

Second, get_initial() and get_context_data() solve 2 different problems:

  • get_initial is to pass initial values to a form.
  • get_context_data is to pass variables to a template

As you can see in above site, the form is injected into the template variables through get_context_data() and that's where your recursion problem comes from:

- get()
 |- get_context_data()     <----------------------------------\
   |- get_form()                                              |
     |- get_form_kwargs()                                     |
       |- get_initial() --> you call get_context_data here ---/

Now, how your GET parameters and form should be working together is unclear from your question, but if you need some values from GET for initial form values, then get them inside get_initial().

UPDATE:

  1. I'd not have a method get_queryset() with a signature like this, reason is that several views dealing with models also have a get_queryset() method with a different signature. get_trips() in this context makes a lot of sense
  2. You've already extracted a few functionalities, which is good, but the "finding closest to searched date" can be extracted as well and it's result stored on self.
  3. It's possible to use __range lookups which probably makes your logic easier. Change the semantics to "find trips between 30 days before and 30 days after search date". This is not the same but a good enough approach in practical terms.

If you're still stuck, let us know on what specifically.