I built a FormView with two fields in it. I want to use the form to pass queries to a view.
class SectorForm(forms.Form):
date = forms.DateField()
days = forms.IntegerField(max_value=365, min_value=1)
Is there a way I can take my FormView and build the success_url from the valid form fields?
So overriding the get_success_url.
def get_success_url(self):
return reverse_lazy('queryable-view' date days)
I thought I could pull it out from form_valid but never had success.
get_success_url doesn't have access to the Form instance, but form_valid does. Try something like this:
from django.shortcuts import redirect
class YourFormView(FormView):
# ...
def form_valid(self, form):
date, days = form.cleaned_data['date'], form.cleaned_data['days']
return redirect('queryable-view', date, days)
I had similar problem once. When you work with forms, you may not use kwargs and get_success_url doesn't have access to the Form (as told before)
I have get around as:
class YourView(CreateView):
appointment_id =0
def form_valid(self,form):
appointment = form.save()
self.appointment_id = appointment.pk
print self.appointment_id
return super(AppointmentCreate,self).form_valid(form)
def get_success_url(self, **kwargs):
if kwargs != None:
return reverse_lazy('payment', kwargs = {'appid': self.appointment_id})
Related
I am working with a CBV that uses 2 ModelForm instances. I would like to display the individual form errors. It seems like this is a little challenging when using multiple forms in a class based view.
Heres a smaller snippet to show what I am working with...
class EmployeeCreate(CreateView):
form_class = EmployeeCreateForm
form_class_2 = AddressCreateForm
def post(self, request, *args, **kwargs):
employee_form = self.form_class(request.POST)
address_form = self.form_class_2(request.POST)
# Make sure both forms are validated
if employee_form.is_valid() and address_form.is_valid():
employee = employee_form.save(commit=False)
address = address_form.save(commit=False)
employee.parent = self.request.user
employee.save()
address.user = employee
address.save()
return JsonResponse({'message': 'Employee created successfully.'}, status=200)
else:
return self.form_invalid(**kwargs)
def get_context_data(self, **kwargs):
# render both forms to create an Account, and Address
context = super(EmployeeCreateView, self).get_context_data()
context['employee_form'] = self.form_class
context['address_form'] = self.form_class_2
return context
def form_invalid(self, **kwargs):
return JsonResponse({'success': False})
Now when the form is invalid, the form_invalid method is getting called and returning the JsonResponse message, but I would much rather return the specific form error.
I am trying to find a way to display each individual form error for the employee_form and the address_form. Is there a possible way to do this override in the form_invalid method?
Thank you in advance!
you are returning both forms error in single JsonResponse. Instead you should return different forms error in single JsonResponse like
return JsonResponse({'employee_form_errors': self.form_invalid(employee_form),
'address_form_errors': self.form_invalid(address_form) }, status=400)
you should use individually use form_invalid with both forms.
I am trying to create a user profile page where users can see and update their preferences for certain things, like whether they are vegetarian, or have a particular allergy, etc. I want the data to be displayed as a form, with their current preferences already populating the form fields.
So I've created the following Model:
class FoodPreferences(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # One user has one set of food prefs
vegetarian = models.BooleanField()
vegan = models.BooleanField()
...
that's referenced in my forms.py:
class FoodPreferencesForm(forms.ModelForm):
class Meta:
model = FoodPreferences
exclude = ('user', )
I've tried creating a view that inherits FormView and then referencing the form, like this:
class UserProfileView(generic.FormView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
This saves the form to a instance of the model correctly, but obviously it just displays the blank form again, after updating, so the user has no idea what their current preferences are.
To implement this I thought I might need to override get() and post() to get the instance of FoodPreferences for the user, and then pass those values into the form like you would a request.POST object. However, firstly, I don't know how to do that, and secondly I'd be taking responsibility for correctly updating the database, which the FormView was already doing.
This is what I've got for that solution:
def get(self, request, *args, **kwargs):
prefs = FoodPreferences.objects.get(user=request.user)
form = self.form_class(prefs)
return render(request, self.template_name, {'form': form, })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if not form.is_valid():
return render(request, self.template_name, {'form': form, 'error': 'Something went wrong.'})
curr_prefs = FoodPreferences.objects.update_or_create(form.fields)
prefs.save()
return render(request, self.template_name, {'form': form, })
but I get a TypeError: argument of type 'FoodPreferences' is not iterable on the line in get():
form = self.form_class(prefs)
because it's not expecting a model instance.
Am I thinking about this in the right way? This seems like a common enough problem that Django would have something inbuilt to do it, but I can't find anything.
You should only rarely need to define get or post in a class-based view, and you definitely don't here.
To start with, you need to use a more appropriate base class for your view. Here you want to update an existing item, so you should use UpdateView.
Secondly, you need to tell the class how to get the existing object to update, which you can do by definining get_object. So:
class UserProfileView(generic.UpdateView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
def get_object(self, queryset=None):
return self.request.user.foodpreferences
# or, if you aren't certain that the object already exists:
obj, _ = FoodPreferences.objects.get_or_create(user=self.request.user)
return obj
I have researched this issue for a couple of days and can't seem to find what I'm looking for exactly. I have searched ModelChoiceField as well as ChoiceField on StackOverflow as well as Google and there are many variations of my question but nothing exactly. In a nutshell, I am trying to use a Class Based FormView and then capture the user selection and pass it to a Class Based ListView. Here is my code.
Forms.Py
class BookByStatus(forms.Form):
dropdown = forms.ChoiceField(choices=[],required=False)
def __init__(self, user, *args, **kwargs):
super(BookByStatus, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].choices =
Book.objects.values_list("author","author").distinct("Publisher")
The code above works fine, and shows me the output I'm looking for on my view. No issues there....Then I have my FormView...
class BookByStatusView(LoginRequiredMixin,FormView):
model = Book
form_class = BookByStatus
template_name = 'xyz123/publisher.html'
success_url = reverse_lazy('Book:book_by_list',kwargs=
{'dropdown':'dropdown'})
def get_form_kwargs(self):
kwargs = super(BookByStatusView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
self.request.POST['dropdown']
BookByStatusView = form.cleaned_data['dropdown']
return super(BookByStatusView, self).form_valid(form)
The code above works fine, but takes me to the ListView below which I can't seem to pass the dropdown value to....I've tried several different iterations of get_form_kwargs as well as changed my form to ModelChoiceField, but still can't seem to understand how to get a queryset based on the input from the user...
And finally the ListView...
class BookByStatusListView(LoginRequiredMixin,ListView):
model = Book
form_class = BookByStatus
context_object_name = 'book_list'
template_name = 'xyz123/book_by_status_list.html'
paginate_by = 15
def get_queryset(self, *kwargs):
form = self.form_class(self.request.GET)
dropdown = self.kwargs.get('dropdown', None)
if form.is_valid():
return Book.objects.filter(dropdown__icontains=form.
cleaned_data['dropdown'])
return Book.objects.all()
I'm trying to take the dropdown input from the FormView and then pass it to a list view using two separate views. I need to pass the value from the FormView to the ListView. I'm clear on how to get the data in the FormView in the ChoiceField, and how to display a ListView, but I can't seem to figure out how to pass the dropdown data from the FormView to the ListView. I can get the ListView to work, but only with the full queryset, not with a filtered one.
Here's the book model....
class Book(models.Model):
Author CHOICES = (
("New","New"),
("Old","Old"),
)
Author = models.CharField(choices=Author_CHOICES,max_length=10)
Here's the URL...
url(r'^book_by_list/(?
P<dropdown>\w+)/$',views.BookByStatusListView.as_view(),
name='book_by_list'),
Thanks in advance for any suggestions!
Updated Approach...Using request.session. My prior approach would not let me pass the value from the one view to the other, no matter how many get_context_data or get_form_kwargs combinations I tried. Based on the input I received, I began exploring the request.session approach and I've gotten much further. One last piece remains, getting the request.session value in my LISTVIEW so I can filter my querysets accordingly.
class BookByStatusView(LoginRequiredMixin,FormView):
model = Book
form_class = BookByStatus
template_name = 'xyz123/publisher.html'
success_url = reverse_lazy('Book:book_by_list')
def form_valid(self, form):
self.request.session['dropdown'] = form.cleaned_data['dropdown']
return super(BookByStatusView, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(BookByStatusView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
And in my html template, I leverage the request.session value as...
{{ request.session.dropdown }}
And I reverted the URL back to..
url(r'^book_by_list/$,views.BookByStatusListView.as_view(),
name='book_by_list'),
The last remaining piece is to figure out how to leverage the LISTVIEW with this approach.
My current Listview:
class BookByStatusListView(LoginRequiredMixin,ListView):
model = Book
form_class = BookByStatus
context_object_name = 'book_list'
template_name = 'xyz123/book_by_status_list.html'
paginate_by = 15
def get_queryset(self):
queryset = Book.objects.none()
dropdown = self.request.session.get('dropdown')
if dropdown == 'New':
queryset = Book.objects.all()
elif dropdown == 'Old':
queryset = Book.objects.none()
return queryset
I can't seem to figure out how to pass the dropdown value correctly to the ListView so the queryset is displayed properly. Based on my testing, I don't appear to be capturing dropdown properly in the get_queryset function. Any ideas?
I figured it out. I updated the get_queryset with the proper syntax. Thanks for all of the help to nudge me in the right direction. Last questions..is this the best way to pass a value from one view to another? Is there a better way to do this? Are there any concerns with this approach?
This doesn't work, because redirect creates a new request/response and data from previous are lost. If I understand what you want correctly, one of the options would be to save the drop-down value to session in BookByStatusView and then retrieve it in BookByStatusListView.
You save to session with:
request.session['dropdown_value'] = form.cleaned_data['dropdown']
and retrieve with:
dropdown_value = request.GET.get('dropdown_value')
Here is How to use session part of Django documentation.
EDIT: You can also pass the value as an url parameter like this:
author = 'michael cricthon'
title = 'kongo'
year = [1999, 2000, 2001]
type = ['electronic', 'print', 'hardcover', 'softcover']
params = '?author={}&title={}&&year={}&type={}'.format(
urllib.parse.quote_plus(author),
urllib.parse.quote_plus(title),
','.join(year),
','.join(type))
return HttpResponseRedirect(reverse('search') + params)
The link would look like this:
../search/?author=michael+crichton&title=kongo&year=1999,2000,2001&type=electronic,print,hardcover,softcover
You get parameters with
author = request.GET.get('author')
title = request.GET.get('title')
... etc.
I've been working with Django for about 3 months now and feel I'm getting a bit better, working my way up to class based views. On the surface they seem cleaner and easier to understand and in some cases they are. In others, not so much. I am trying to use a simple drop down view via ModelChoiceField and a form. I can get it to work with a function based view as shown below in my views.py file:
def book_by_name(request):
form = BookByName(request.POST or None)
if request.method == 'POST':
if form.is_valid():
book_byname = form.cleaned_data['dropdown']
return HttpResponseRedirect(book_byname.get_absolute_url1())
return render(request,'library/book_list.html',{'form':form})
Here is my form in forms.py:
class BookByName(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Book.objects.none())
def __init__(self, *args, **kwargs):
super(BookByName, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Book.objects.order_by('publisher')
This code works. When I have tried to convert to a Class Based View, that's when the trouble begins. I tried to do something like this in views.py:
class BookByNameView(FormView, View):
form_class = BookByName
initial = { 'Book' : Book }
template_name = 'library/book_list.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def get_success_url(self, *args):
return reverse_lazy('library:book_detail', args = (self.object.id,))
When using this with the same form, I receive an attribute error,
'BookByNameView' object has no attribute 'object'.
I've tried ListView as well and received several other errors along the way. The get_success_url also needs to take in a primary key and I can't figure out how to get that passed in as well. Again, I'm a 3 month Django newbie so please be gentle and thanks in advance for your thoughts and suggestions! I feel like I'm in the ballpark...just can't find my seat! I'm very open to doing this differently, if there's a cleaner/better way to do this!
Based on the latest feedback, it would appear the Class Based View should look like:
class BookNameView(FormView):
form_class = BookName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
Is this correct? I ran a test version of this and in response to your question as to why I am using self.object.id at all, I am trying to get the pk from the modelchoicefield that I am using to return the view I am trying to get. This may be where I am getting a bit lost. I am trying to get the detail view from the modelchoicefield dropdown, and return the book that is selected. However, I can't seem to pass the pk to this view successfully.
I updated my code to...
class BookByNameView(FormView, ListView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
But now it says error...Reverse for 'book_detail' with no arguments not found.
Why are you using self.object there at all? You used form.cleaned_data in the original view, that's what you should use in the class based version too. Note that the form is passed to form_valid.
Note that you've done lots of other weird things too. Your getmethod is pointless, as is your definition of the initial dict; you should delete them both. Also, FormView already inherits from View, there's no need to have View in your declaration explicitly.
You can override the form_valid() function in FormView to achieve what you want. If the form is valid then it is passed to the form_valid() function.
Try this:
class BookByNameView(FormView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def form_valid(self, form):
bookbyname = form.cleaned_data['dropdown']
return HttpResponseRedirect(bookbyname.get_absolute_url())
I am new to django class based views and may be the way I am approaching this is a little naive, so I would appreciate if you could suggest a better way.
So my problem is here:
There are three types of users in my project. 1. Student, 2. Teacher, 3. Parent. I need to be able to show different user settings pertaining to each type of user when the user requests the settings page in their respective forms. Also, I need to be able to save the data into the respective tables as the user submits the form.
I have a class based view (UserSettingsView):
class UserSettingsView(LoginRequiredMixin, FormView):
success_url = '.'
template_name = 'accts/usersettings.html'
def get_initial(self):
if self.request.user.is_authenticated():
user_obj = get_user_model().objects.get(email=self.request.user.email)
if user_obj.profile.is_student:
return {
'first_name': user_obj.profile.first_name,
'last_name': user_obj.profile.last_name,
""" and other student field variables """
}
if user_obj.profile.is_teacher:
return {
""" Teacher field variables """
}
else:
return render_to_response('allauth/account/login.html')
def form_valid(self, form):
messages.add_message(self.request, messages.SUCCESS, 'Settings Saved!')
return super(UserSettingsView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(UserSettingsView, self).get_context_data(**kwargs)
context['user'] = get_user_model().objects.get(email=self.request.user.email)
context['userprofile'] = UserProfile.objects.get(user_id=context['user'])
return context
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
form.full_clean()
if form.is_valid():
user = request.user
user.profile.first_name = form.cleaned_data['first_name']
user.profile.last_name = form.cleaned_data['last_name']
user.profile.save()
if user.profile.is_student:
""" update student database """
user.save()
user.student.save()
if user.profile.is_teacher:
""" update teacher database """
user.save()
user.teacher.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
Different instances of Usersettings view are called using the pick_settings generic view.
url(regex=r'^profilesettings/',view=pick_settings,name='profilesettings'),
And here is the pick_settings view:
def pick_settings(request):
if request.user.is_authenticated():
if request.method == 'GET':
if request.user.profile.is_student:
return UserSettingsView.as_view(form_class=StudentSettingsForm)(request)
if request.user.profile.is_teacher:
return UserSettingsView.as_view(form_class=TeacherSettingsForm)(request)
if request.user.profile.is_parent:
return UserSettingsView.as_view(form_class=ParentSettingsForm)(request)
else:
if request.method == 'POST':
"""
return ***<--- I need to know what to pass here to be able to call the appropriate post function of the UserSettingsView?---->"""***
else:
return HttpResponseRedirect('/accounts/login/')
I need to be able to call the post function of the UserSettingsView. May be using the get_context_data? But I am not sure how.
Again it will be great, if someone could suggest a better way because I am pretty sure this might be violating the DRY principle. Although, I am not too concerned with that as long as the job gets done as I am running a deadline. :) Thanks!
FormView has a method get_form_class(). It is called from get() and post(), so self.request will already be set (as will be self.request.user). Consequently,
class UserSettingsView(LoginRequiredMixin, FormView):
[...]
def get_form_class(self):
# no need to check is_authenticated() as we have LoginRequiredMixin
if request.user.profile.is_student:
return StudentSettingsForm
elif user.profile.is_teacher:
return TeacherSettingsForm
elif user.profile.is_parent:
return ParentSettingsForm
This should already to the trick as you get the correct form for each user type.
If you also need to render different templates, override get_template_names():
def get_template_names(self):
if request.user.profile.is_student:
return ['myapp/settings/student.html']
elif user.profile.is_teacher:
return ['myapp/settings/teacher.html']
elif user.profile.is_parent:
return ['myapp/settings/parent.html']
DRY can be achieved using proper inheritance in the templates combining common template fragments.
And lest I forget (I already forgot): To get rid of the if in the post() method of your view, simple override the save() method of you forms which I assume are ModelForms, anyway.