Saving form data from a FormView to session - django

I am trying to capture the POST request data for each field and store it in the session so that I can use the data in another view. But I am getting errors in the other view because the session variables are returning 'None'.
Before, I had written the code using a non-class based view and fetched the values with request.POST.get('name') and this worked. What should I be doing here?
class TripsView(FormView):
""" A view to show all trips and receive trip search data """
template_name = "products/trips.html"
form_class = SearchTripsForm
def form_valid(self, form):
"""
Takes the POST data from the SearchTripsForm and stores it in the session
"""
trip_choice = form.cleaned_data["destination"].id
self.request.session["destination_choice"] = trip_choice
self.request.session["searched_date"] = "26-12-2021"
self.request.session["passenger_total"] = form.cleaned_data[
"passengers"
]
return super(TripsView, self).form_valid(form)
def get_context_data(self, **kwargs):
""" Adds to the context the Product objects categorized as trips """
context = super().get_context_data(**kwargs)
context["destinations"] = Product.objects.filter(category=3)
return context
def get_success_url(self):
""" Overides the success url when the view is run """
return reverse("selection")
My other view is as follows:
class SelectTripView(View):
"""
Provides the user a set of choice options based on their search input in
products.TripsView
"""
template_name = "bookings/trips_available.html"
form_class = DateChoiceForm
def get(self, request):
"""
Initialises the DateChoiceForm with data from SearchTripsForm
& render to the template
"""
searched_date = self.request.session["searched_date"]
print(searched_date)
naive_searched_date = datetime.strptime(searched_date, "%Y-%m-%d")
gte_dates = self.trips_matched_or_post_date(searched_date)
lt_dates = self.trips_preceding_date(searched_date)
... etc etc...
The code flags an error here at the datetime.strptime() method because it says argument 1 must be a string not a tuple - (None)
Update:* I have located that the problem is because the date string is being converted to a tuple (2,0,2,1 etc..) - maybe because it includes punctuation dashes '-' between Y-M-D? Is there a workaround for this? That being said, the string was just a placeholder. In the real case, I am pulling the value from form data which gives me date object and would need to be serialized before sending to the session.
I am confused as to why using request.POST.get() to retrieve the form data in non classed based View did not encounter such errors. Is there a difference to the data once it is cleaned_data?

Related

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

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)
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:
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
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.
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.

Looking for format for KeywordsField.save_form_data

I have a Mezzanine Project and am trying to update the keywords on a blog entry. I am having difficulty getting the format correct to call KeywordsField.save_form_data this invokes a js that will update the keywords on a blog post. See below:
From Messanine/generic/fields.py
class KeywordsField(BaseGenericRelation):
"""
Stores the keywords as a single string into the
``KEYWORDS_FIELD_NAME_string`` field for convenient access when
searching.
"""
default_related_model = "generic.AssignedKeyword"
fields = {"%s_string": CharField(editable=False, blank=True,
max_length=500)}
def __init__(self, *args, **kwargs):
"""
Mark the field as editable so that it can be specified in
admin class fieldsets and pass validation, and also so that
it shows up in the admin form.
"""
super(KeywordsField, self).__init__(*args, **kwargs)
self.editable = True
def formfield(self, **kwargs):
"""
Provide the custom form widget for the admin, since there
isn't a form field mapped to ``GenericRelation`` model fields.
"""
from mezzanine.generic.forms import KeywordsWidget
kwargs["widget"] = KeywordsWidget
return super(KeywordsField, self).formfield(**kwargs)
def save_form_data(self, instance, data):
"""
The ``KeywordsWidget`` field will return data as a string of
comma separated IDs for the ``Keyword`` model - convert these
into actual ``AssignedKeyword`` instances. Also delete
``Keyword`` instances if their last related ``AssignedKeyword``
instance is being removed.
"""
from mezzanine.generic.models import Keyword
related_manager = getattr(instance, self.name)
# Get a list of Keyword IDs being removed.
old_ids = [str(a.keyword_id) for a in related_manager.all()]
new_ids = data.split(",")
removed_ids = set(old_ids) - set(new_ids)
# Remove current AssignedKeyword instances.
related_manager.all().delete()
# Convert the data into AssignedKeyword instances.
if data:
data = [related_manager.create(keyword_id=i) for i in new_ids]
# Remove keywords that are no longer assigned to anything.
Keyword.objects.delete_unused(removed_ids)
super(KeywordsField, self).save_form_data(instance, data)
From my Views.py
class PubForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['keywords']
def UpdatePub(request, slug):
blog_post = BlogPost.objects.get(id=slug)
if request.method == 'POST':
form = PubForm(request.POST)
if form.is_valid():
publish_date = datetime.datetime.now()
blog_post.status = CONTENT_STATUS_PUBLISHED
publish_date=publish_date
tags=form.cleaned_data['keywords']
blog_post.save()
KeywordsField.save_form_data(user,blog_post,tags)
return HttpResponseRedirect('/write/')
else:
form = PubForm(instance=blog_post)
return render(request, 'blog_my_pub.html', {'form' : form})
It complains that the field 'user' has no attribute 'name'. I have tried many different values for this parameter and cannot figure it out. Any help would be appreciated.
Thanks for any input.

HttpResponse error django generic template

I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset

Django: how to perform and display calculations on inputs obtained from a form post?

I have successfuly created a complex form with a lot of fields using ModelForm and FormView. I managed to save those data and display it in the admin. Thus it is only the beginning of what I want to do.
My next goal is to use the inputs posted by the user via the form to perform calculations on it and then display the results of those calculations in another view.
What would be the best approach to do so? So far here are my files (I do not display all the fields since it is not relevant)
Here is the view I use to display the form
class SimulInputView(FormView):
form_class = SimulInputForm
template_name = 'apps/simulateur/formulaire/form.html'
success_url = reverse_lazy('simulateur_results')
def get_initial(self):
initial_data = super(SimulInputView, self).get_initial()
for key, value in dict_simul_form_default_data.items():
initial_data[key] = value
return initial_data
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['data'] = SimulateurData
return context
def form_valid(self, form):
form.instance.user = self.request.user
form.save()
return super().form_valid(form)
How should I modify my form_valid function to use another module which would perform calculations on the form data received before displaying them in another view?
EDIT
I modified my code as following. It seems to work well but I would like to know if it's good practice to handle it this way. Could you please share your opinion?
# VIEWS
class SimulInputView(FormView):
form_class = SimulInputForm
template_name = 'apps/simulateur/simulateur_form.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
# save form and put in data_instance to get its id later
data_instance = form.save()
# save form data
form_data = form.data
# call calculs_simulation.py script which performs calculations on form data
result = calculs_simulation(form_data)
# put results of calculation in SimulResult model, and set the id for SimulInput foreignkey
result_model = SimulResult(simulinput_id=data_instance.id, **result)
# save the result model and get its id
result_model.save()
result_model_id = result_model.id
return redirect('simulateur_results', result_model_id)
class SimulResultView(DetailView):
model = SimulResult
template_name = 'apps/simulateur/simulateur_results.html'
# URLS
urlpatterns = [
path('formulaire/', simul_input_views.SimulInputView.as_view(), name="simulateur_form"),
path('resultats/<int:pk>/', simul_result_views.SimulResultView.as_view(), name="simulateur_results"),
]
You can return to another view with the i stance of the data saved to display the results you need. For example,
def form_valid(self, form):
.
.
data_instance = form.save()
return redirect(reverse('results_view', args=(data_instance.id, )))
Now in your results_view you can get your data_instance by id and calculate then render your results.
Hope this helps!

Django 1.11: "global name 'user' is not defined"

I have a survey app - you create a Survey and it saves the Response. It's registered in Django Admin. I can see the Survey and submit a Response. When I click Response in Admin, I get the following error:
ValueError at /admin/django_survey/response/
Cannot query "response 5f895af5999c49929a522316a5108aa0": Must be "User" instance.
So I checked the SQL database and for django_survey_response I can see that there is a response, but the column user_id is NULL.
I suspected that there's an issue with my Views and/or Forms and I'm not saving the logged in User's details, so I've tried to address that.
However, now I get
NameError at /survey/1/
global name 'user' is not defined
How do I resolve this? I want the form to save Response with the logged in user's ID.
The Traceback:
django_survey\views.py
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey) <.........................
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey)
print form
django_survey\forms.py
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
self.survey = survey
self.user = user <.........................
super(ResponseForm, self).__init__(*args, **kwargs)
self.uuid = random_uuid = uuid.uuid4().hex
# add a field for each survey question, corresponding to the question
# type as appropriate.
data = kwargs.get('data')
It might be worth noting that previously, instead of user, the model's field was called interviewee. I changed this and ran migrations again.
I am also using userena.
The error message in this instance is python trying to tell you that you are attempting to access a variable user that has not been defined in the scope of your method.
Let's look at the first few lines of the __init__() method:
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
self.survey = survey
self.user = user
We can see where the survey variable is defined: survey = kwargs.pop('survey'). It is passed into the form as a keyword argument and extracted in the forms __init__. However underneath you attempt to do the same thing with user but haven't actually defined it above. The correct code would look like:
def __init__(self, *args, **kwargs):
# expects a survey object to be passed in initially
survey = kwargs.pop('survey')
user = kwargs.pop('user')
self.survey = survey
self.user = user
However, this still won't work because we aren't passing the user variable to the form via kwargs. To do that we pass it in when we initialise the form in your views.py. What isn't clear is what user object you are expecting to pass in. the request.user? or does the Survey object have a user attribute? in which case you would not need to pass user in and would just use survey.user etc.
django_survey\views.py
def SurveyDetail(request, id):
survey = Survey.objects.get(id=id)
category_items = Category.objects.filter(survey=survey)
categories = [c.name for c in category_items]
print 'categories for this survey:'
print categories
if request.method == 'POST':
form = ResponseForm(request.POST, survey=survey, user=request.user)
if form.is_valid():
response = form.save()
return HttpResponseRedirect("/confirm/%s" % response.interview_uuid)
else:
form = ResponseForm(survey=survey, user=request.user)
print form
In your view when you initialize your form you need to pass it the user (current user in this case)? similar to this form = ResponseForm(request.POST, survey=survey, user=request.user). Then in the __init__ of your form pop the user object user = kwargs.pop('user'). I believe that will resolve your issue.