'NoneType' object has no attribute 'model' error when retrieving POST request data

Viewed 793

I am getting the AttributeError 'NoneType' object has no attribute 'model'

First, let me explain I have 2 forms. The first form that the user encounters is the InitialSearchForm. It takes a date input, a selected object (instance of a trip) and an integer (for number of passengers). The POST request data from the InitialSearchForm is sent to the TripSelection view. This data is used to query the database and retrieve a queryset which will be used as a choice set in my 2nd form - DateChoiceForm - rendered in the HttpResponse.

The code below is for the TripSelection view in views.py. At the point that I initialise the DateChoiceForm, I pass the queryset as parameter 'trips':

class TripSelection(View):
""" A view to show results of search """

template = "bookings/trips_available.html"

def post(self, request):
    """
    Takes the POST data from the InitialSearchForm and uses it to
    initialise the DateChoiceForm before passing to the template
    """

    form = InitialSearchForm(request.POST)
    if form.is_valid():
        destination_choice = request.POST.get("destination")
        searched_date = request.POST.get("request_date")
        passenger_total = int(request.POST.get("passengers"))

        # Find trips with enough seats for requested no. of passengers
        available_trips = Trip.objects.filter(
            destination=destination_choice
        ).filter(seats_available__gte=passenger_total)

        # Refine to trips with dates closest to searched_date
        # limit to 3 results
        gte_dates = available_trips.filter(
            date__gte=searched_date
        ).order_by("date")[
            :3
        ]  # Returns trips that either match or are post- searched_date

        lt_dates = available_trips.filter(date__lt=searched_date).order_by(
            "-date"
        )[
            :3
        ]  # Returns trips that are pre- searched_date
        # Merge both queries
        trips = gte_dates | lt_dates

        naive_searched_date = datetime.strptime(searched_date, "%Y-%m-%d")

        # Find trip closest to searched_date and make timezone naive
        if gte_dates:
            date_gte = gte_dates[0]
            naive_closest_gte = self.timezone_naive(date_gte)
            if lt_dates:
                date_lt = lt_dates[0]
                naive_closest_lt = self.timezone_naive(date_lt)

                if (
                    naive_closest_gte - naive_searched_date
                    > naive_searched_date - naive_closest_lt
                ):
                    default_selected = date_lt
                else:
                    default_selected = date_gte
            else:
                default_selected = date_gte
        elif lt_dates:
            date_lt = lt_dates[0]
            naive_closest_lt = self.timezone_naive(date_lt)
            default_selected = date_lt
        else:
            messages.error(
                request,
                "Sorry, there are no dates currently available for the"
                "selected destination.",
            )

        form = DateChoiceForm(
            trips=trips,
            initial={
                "trip_date": default_selected,
                "num_passengers": passenger_total,
            },
        )
        return render(request, self.template, {"form": form})

def timezone_naive(self, query_object):
    """ Turns date attributes to a time-zone naive date """

    date_attr = query_object.date
    date_string = date_attr.strftime("%Y-%m-%d")
    date_obj_naive = datetime.strptime(date_string, "%Y-%m-%d")

    return date_obj_naive

In forms.py I set the queryset for the field 'trip' in the form's __init__ method:

class DateChoiceForm(forms.Form):

    num_passengers = forms.IntegerField(widget=forms.HiddenInput())
    trip = forms.ModelChoiceField(
        queryset=None,
        widget=forms.RadioSelect()
    )

    def __init__(self, *args, **kwargs):
        trips = kwargs.pop('trips', None)

        super(DateChoiceForm, self).__init__(*args, **kwargs)
        self.fields['trip'].queryset = trips

The form renders as expected in the template but when I make a selection and click to 'submit' on the form, that is when I get the AttributeError. This is caused in the view that handles the POST data for DateChoiceForm, specifically at the line form = DateChoiceForm(request.POST):

def trip_confirmation(request):

if request.method == "POST":
    form = DateChoiceForm(request.POST)
    if form.is_valid():
        passengers = request.POST.get("num_passengers")
        trip_choice = request.POST.get("trip")
        print(trip_choice)
    else:
        print("Hello")

    template = "bookings/confirm/trip.html"
    context = {
        "passengers": passengers,
        "trip_choice": trip_choice
    }

    return render(request, template, context)
print("Error!").   

If I understand correctly, this is because, having passed the queryset from the view and setting the queryset in the __init__ method, now every-time I call the DateChoiceForm it expects a parameter to be sent for the form to be initialised. Since I pass no parameter this time around, it reaches this line: trips = kwargs.pop('trips', None) and takes the fallback value of None.

I don't know how to go about setting the queryset for this form field since it is not a straightforward static filter that I can do from within forms.py. Is there another way of doing this? My only workaround at the moment is to pass the first form's post request data to session storage, retrieve it in the view and create the same queryset to pass as a parameter to DateChoiceForm(request.POST)

1 Answers

You can't use the except clause like that except (ValueError, TypeError, self.queryset.model.DoesNotExist). Use the actual model DoesNotExist instead: except (ValueError, TypeError, <modelname_here>.DoesNotExist).

Also, you can't set the queryset=None. That's not possible. You must use the <model>.objects.none() queryset instead.

If you want dynamic model selection for your view I recommend using class-based views instead. The inheritance makes it way easier to handle multiple model types.


Edit: after reading your question again I noticed that trip_dates = kwargs.pop('trips', None) actually retrieves 'trips' and not 'trip' is this intentional? Given your post variables trips will be equal to None.

Second: You don't actually set a queryset. self.fields['trip'].queryset = trip_dates will be either None or list of strings but never an actual queryset. So regardless of whether setting the queryset for the ModelChoiceField in the __init__ it's never a queryset.

Related