Django ChoiseField validation - django-forms

I have simple form witch has options for delivery; pickup and post it. There is also possibility to use discount coupons. When using coupons user will always have to go and pay in person with the coupons.
My problem is that when trying to force this behavior and change radio choices when using coupongs in form clean it will change form.cleaned_data but view will render form with original selection.
#forms.py
class DeliveryOptionsForm(forms.Form):
RADIO_CHOICES = (
('pickup',"Pick your stuff from office")),
('postit', "send to me by mail (+2€)"),
)
delivery = forms.ChoiceField(widget=forms.RadioSelect,
choices=RADIO_CHOICES,
help_text=_('Select shipping method'))
discount = forms.DecimalField(help_text=_('DISCOUNT TICKET'), required=False)
def clean(self):
data = self.cleaned_data
if 'discount' in data:
if data['discount'] == None:
data['discount'] = 0;
else:
#force pickup when putting discount coupongs
data['pickup'] = 'pickup'
return data
And my view
def purchase_confirmation(req):
cart = req.session['cart']
form = DeliveryOptionsForm(req.POST or None, initial={'pickup': 'postit'})
if req.method == 'POST' and form.is_valid():
discount = form.cleaned_data['discount']
pickup = form.cleaned_data['pickup']
# pickup will be correctly set, for logic,
# but form will render selection unchanged
#Some logic here
....
if 'confirm' in req.POST:
return redirect('shop-generating_bill')
if 'update' in req.POST:
pass
return render(req,"confirmation.html",{'form' : form})

Not sure if this is the cause of your issue but, in your clean method you should get the cleaned data by calling the clean method of the super class.
cleaned_data = super(DeliveryOptionsForm, self).clean()

Related

Combining AJAX with ModelMultipleChoiceField and custom form

I've been banging my head for a while and could not find a similar issue.
I'll go over my code
Model
class RestauranteMenu(models.Model):
restaurante = models.ForeignKey(RestauranteUser)
name = models.CharField(max_length=60, blank=False)
class OpeningHours(models.Model):
...
restaurante = models.ForeignKey(RestauranteUser)
menu = models.ForeignKey(RestauranteMenu, blank=True, null=True)
...
Form
class MenuForm(ModelForm):
'''
View = menus(request)
Template = pages/menus.html
'''
horario = ModelMultipleChoiceField(queryset=OpeningHours.objects.all())
def save(self, restaurante, horario, commit=True):
#Linking relationship Restaurant x RestaurantMenu
menu = super(MenuForm, self).save(commit=False)
menu.restaurante = restaurante
if commit:
menu.save()
#Linking relationship RestaurantMenu x OpeningHours
horario = OpeningHours.objects.filter(id=horario, restaurante = restaurante).first()
if horario:
horario.menu = menu
horario.save()
return menu
class Meta:
model = RestauranteMenu
exclude = ['restaurante']
view
def menus(request):
#verify if its an update.
instance = request.POST.get('instance')
if instance not in [None, '']:
menu = RestauranteMenu.objects.get(id=instance)
form = MenuForm(request.POST or None, instance=menu, initial={'horario': OpeningHours.objects.filter(restaurante=request.user).values_list('id', flat=True)})
else:
form = MenuForm(request.POST or None, initial={'horario': OpeningHours.objects.filter(restaurante=request.user).values_list('id', flat=True)})
if request.POST:
if form.is_valid():
try:
#When saving, we pass a restaurant reference and OpeningHours reference.
form.save(request.user, form.data.get('horario'))
JS
$(document).on("click", "#sendmenuform", function() {
var horariosId = [];
$('#horario :selected').each(function(i, selected) {
horariosId.push($(selected).val());
});
$.ajax({
type: "POST",
url: "../menus/",
data: {
name : $('[name="name"]').val(),
horario : horariosId,
instance : $('#sendmenuform').attr("data-id"),
csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()
},
success : function(data) {
.... process response...
}
});
What's the issue
Based on my models, I want a Menu to have a ManyToOne relationship with OpeningHours, that means that one Menu can be at different OpeningHours.
When I'm submitting my form (via AJAX), I'm not able to populate the field 'horario' inside the form. When instantiating the form, I'm filling the field with a queryset that will filter by that Restaurant.
On the html, I have a select multiple, so that the restaurant is able to link one Menu to several different OpeningHours object.
I don't know how I'm supposed to process the information sent by the AJAX request to the view, specially this ModelMultipleChoiceField. Do I need to override any of the forms method?
ModelMultipleChoiceField expects model objects and not arbitrary strings. Hence using the values_list query set will only land you into trouble. Your form will not validate.
For your use case, use ChoiceFields. You can populate them with a string by overriding your __init__ method. For example, in your Forms.py
horario = ChoiceField(
choices[],)
def __init__(self, *args, **kwargs):
super(MenuForm, self).__init__(*args, **kwargs)
self.fields['horario'].choices = [(x.id, x.modelfieldtodisplay) for x in OpeningHours.objects.all()]
If you want to do this when you post the form or load it do it inside a view that is triggered by a listener for the event. You'll have to write Javascript to handle this similar to this tutorial but this asks you to use ModelChoiceField which I do not recommend for you because it doesn't work gracefully when you're trying to dynamically populate multiple fields and submit the form, validate it and run some operations on the data.
Instead, I implore you to take a look at Dajax and Dajaxice which takes altogether 5 minutes to set up and handles forms and AJAX effortlessly making your job significantly simpler. I do emphasize though, void using ModelChoiceField for you use case. I learnt that the hard way.

Django Admin - Add using intermediary form

I had posted a question without any result. I fear it may be how I posed it...
In the admin, I would like the Add button to direct the user to a different view than the normal form (that would simply consist of a search field and button). After the 'search' form has been validated, it would then send the user to the standard add/change form with some instance data. What is the best method to achieve this?
So, I got the Tumbleweeds bronze star for dearth of responses to this question. I ended up partially solving it as follows...
Added a get_urls method to my model's admin
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
addl = patterns(',
(r'^add/$', 'MyApp.views.my_add_view)
)
return addl + urls
Created a form for the 'Add' view
from django import forms
from MyApp.models import MyModel
class LookupForm(forms.Form):
pk = ''
url = forms.URLField(max_length=_URLMAXLEN, label='Video URL',
widget=forms.TextInput(attrs={'size':'60', 'autofocus':'autofocus'}))
def clean_url(self):
value = self.cleaned_data['url']
# some validation of 'value' here
# ...
self.pk = somefoo(value)
return value
And in views, I redirect to the change form after validating and saving the model instance
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from MyApp.forms import LookupForm
def my_add_view(request):
confirm = ''
if request.method == 'POST':
form = LookupForm(request.POST)
if form.is_valid():
if 'confirm_button' in request.POST:
foo_save() # save model instance
# LookupForm's 'pk' property set during some clean_ method
href = reverse('admin:MyApp_MyModel_change', args=(form.pk,))
return HttpResponseRedirect(href)
elif 'search_button' in request.POST:
confirm = foo_context() # some context from the search results
else:
form = LookupForm()
return render(request, 'my_template.html', {'form': form, 'confirm': confirm})
Unfortunately I cannot achieve my goal of displaying the change form with instance data. Instead of having to save and redirect to the change_form, I would prefer to be able to direct with MyModelForm(instance=some_instance)

Django: Displaying a form from one model with content from a second model on one page view

I have an irksome little problem with a forum that I am building.
I need to generate a page that contains a form to populate one
model but that page should also display an entry from another related model.
I want the form to populate a new response in the model Responses (see code below).
That model has the model StartMsg as a foreign key. I want the page view (response_form) to display StartMsg.msg that the user is responding to. The problem is that I am using django's built in forms to generate the form and render_to_response to call the page. The render_to_response statement (marked (A)) sends a dictionary containing the form components from the Responses model to the html template.
How do I include info about the model StartMsg into the render_to_response statement (marked
with (A), below)? Is there a better way to accomplish what I am after?
Here are the models:
class StartMsg (models.Model):
msg_title = models.TextField(max_length=500)
msg = models.TextField(max_length=2000)
pub_date = models.DateTimeField('date published')
author = models.ForeignKey(User)
def __unicode__(self):
return self.msg
class Responses (models.Model):
startmsg = models.ForeignKey(StartMsg) #one startmsg can have many responses
response = models.TextField()
responder = models.ForeignKey(User)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.response
Below is the form processing function followed by the form model.
def response_form (request, msg_id):
msg = get_object_or_404(StartMsg, pk=msg_id)
form = MsgRespForm(request.POST or None)
if form.is_valid():
new_rspns = form.save(commit =False)
#retrieve StartMsg entry to assign to the response entry foreign key
message = StartMsg.objects.get(pk=msg_id)
new_rspns.startmsg = message
response = form.cleaned_data['response']
new_rspns.response = response
new_rspns.responder= request.user.username()
new_rspns.pub_date = datetime.now()
new_rspns.save()
return HttpResponseRedirect(reverse('forum.views.forum', )) #if form is processed, view goes here
return render_to_response( #if form not processed, view goes here
'forumresponseform.html',
{'form': form}, (A)
context_instance = RequestContext(request)
)
class MsgRespForm(forms.ModelForm):
# Add Labels to form fields:
response = forms.CharField(label='Your Response',
widget=forms.Textarea(attrs={'cols': 60, 'rows': 10}))
class Meta: #Define what fields in the form
model = Responses
fields = ('response',)
I found a solution that seems to work.
You can make a function that builds a dictionary out of a variable number of model queries then pass that dictionary to the form template on line (A).
def build_dict(request, ** kwargs):
d = dict(user=request.user, ** kwargs)
return d
msg = get_object_or_404(StartMsg, pk=msg_id)
form = MsgRespForm(request.POST or None)
dict = build_dict(request, msg=msg, form=form)
return render_to_response( #if form not processed, view goes here
'forumresponseform.html',
{'form': dict}, (A)
context_instance = RequestContext(request)
)

django form in multiple views

I want my form to display in multiple views with this behaviour:
1.) Form errors are shown in the view that the user submitted the form from.
2.) If form validates, send user back to the view they submitted the form from.
How might I be able to do that?
I'm pretty sure the first behavior is default, if i understand your question correctly. For the second one, if you don't redirect after saving and validation, it should just re-render the view that you submitted from. Placing a success variable is probably good to see if the form saved. Here is an example of using a single form in multiple views.
models.py:
class MyModel(models.Model):
name = models.CharField()
forms.py:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
views.py:
def first_view(request):
success = False
if request.method=="POST":
form = MyModelForm(request.POST)
if form.is_valid():
form.save()
success = True
else:
form = MyModelForm()
context = { 'form':form,
'success': success, }
return render_to_response('first_view_template.html', context,
context_instance=RequestContext(request))
def second_view(request):
success = False
if request.method=="POST":
form = MyModelForm(request.POST)
if form.is_valid():
form.save()
success = True
else:
form = MyModelForm()
context = { 'form':form,
'success': success, }
return render_to_response('second_view_template.html', context,
context_instance=RequestContext(request))
Have you tried the Django Form preview, if I am not wrong it can be used for your purpose

Django Ajax field help

I have a Django application where I'm trying to have a form that populates a drop down dynamically based on a previous dropdown.
simplified Models:
class VehicleMake(models.Model):
make = models.CharField(max_length = 30)
class VehicleModel(models.Model):
model = models.CharField(max_length = 80)
make = models.ForeignKey(VehicleMake)
class Listing(models.Model):
make = models.ForeignKey(VehicleMake)
model = models.ForeignKey(VehicleModel)
Form:
class DynamicChoiceField(ModelChoiceField):
def clean(self, value):
return value
class MyForm(ModelForm):
category = ModelChoiceField(VehicleCategory.objects, widget=forms.Select(attrs={'onchange':'FilterMakes();'}))
make = DynamicChoiceField(VehicleMake.objects,widget=forms.Select(attrs={'disabled':'true','onchange':'FilterModels();'}), empty_label="Select Model")
model = DynamicChoiceField(VehicleModel.objects,widget=forms.Select(attrs={'disabled':'true'}), empty_label="Select Make")
class Meta:
model = Listing
View:
def new_listing(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.save()
else:
form = MyForm()
return render_to_response("newlisting.html", {
"form": form,'model_id':model_id,'make_id':make_id
})
I also have some ajax defined for the auto-populate but this is not the problem
When I submit the form I get the following:
Cannot assign "u'2'": "Listing.make" must be a "VehicleMake" instance.
if I try
make=VehicleMake.objects.get(pk=request.POST['make'])
form.fields['make'] = make
then I get
'VehicleMake' object has no attribute 'widget'
After the suggestion of one of the commenter's that the DynamicChoiceField class was the culprit I removed it and set the form objects for ModelChoiceFields with the exact same other parameters. The object appears to pass and validate correctly as well. The extra class existed based on an old tutorial I found. it appears that what the author did there works with the forms.ChoiceField but is not required for using a ModelChoiceField
thanks everyone for the help

Resources