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)
Related
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.
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()
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)
)
I'm trying to create a dialog which uses jquery's .load() function to slurp in a rendered django form. The .load function is passed the pk of the "alert" object. Also available in the class functions are things like self.request.user so I can pre-fill those fields, shown below in the Message model (models.py):
class Message(models.Model):
user = models.ForeignKey(User)
alert = models.ForeignKey(Alert)
date = models.DateTimeField()
message = models.TextField()
Subclassing django's CreateView makes it pretty easy to generate a context with an instance of the ModelForm (views.py):
class MessageDialogView(CreateView):
""" show html form fragment """
model = Message
template_name = "message.html"
def get_initial(self):
super(MessageDialogView, self).get_initial()
alert = Alert.objects.get(pk=self.request.POST.get("alert_id"))
user = self.request.user
self.initial = {"alert":alert.id, "user":user.id, "message":"test"}
return self.initial
def post(self, request, *args, **kwargs):
super(MessageDialogView, self).post(request, *args, **kwargs)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(form=form)
return self.render_to_response(context)
The problem here is that self.initial does not get rendered with the form. I have insured that the form is indeed calling get_initial and the form instance has the proper initial data in post, but when the form is rendered in the template message.html it doesn't grab any of the initial data like I would expect. Is there a special trick to get this to work? I've scoured the docs (seems to be lacking examples on generic based class views) and source but I can't see what I'm missing.
get_initial() should just return a dictionary, not be bothered with setting self.initial.
Your method should look something like this:
def get_initial(self):
# Get the initial dictionary from the superclass method
initial = super(YourView, self).get_initial()
# Copy the dictionary so we don't accidentally change a mutable dict
initial = initial.copy()
initial['user'] = self.request.user.pk
# etc...
return initial
you can use like :
from django.shortcuts import HttpResponseRedirect
class PostCreateView(CreateView):
model = Post
fields = ('title', 'slug', 'content', 'category', 'image')
template_name = "create.html"
success_url = '/'
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
that's work for me
(Edited because what you're trying does actually work)
I ran into the same problem yesterday, but it's working now – I think I was returning an object instead of a dict in get_initial.
In terms of fixing your problem, I'm a little suspicious of how much you seem to be doing in post() – could you try it with the default (non-overrided) post()?
You could also use pdb (or print statements) to check the value of self.get_form_kwargs make sure that initial is being set.
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