Django Forms: Foreign Key in Hidden Field - python

My form:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(label="",
queryset=Profile.objects.all(),
widget=forms.HiddenInput())
etc...
class Meta:
model = Plan
Owner, in the model, is a ForeignKey to a Profile.
When I set this form, I set the value of "owner" to be a Profile object.
But when this comes out on the form, it seems to contain the name of the Profile like this:
<input type="hidden" name="owner" value="phil" id="id_owner" />
When the form is submitted and gets back to my views.py I try to handle it like this:
form = PlanForm(request.POST)
...
if form.is_valid():
plan = form.save()
return HttpResponseRedirect('/plans/%s'%plan.id) # Redirect after POST
However, what I get is a type-conversion error as it fails to turn the string "phil" (the user's name that was saved into the "owner" field) into an Int to turn it into the ForeignKey.
So what is going on here. Should a ModelForm represent a foreign key as a number and transparently handle it? Or do I need to extract the id myself into the owner field of the form? And if so, how and when do I map it back BEFORE I try to validate the form?

I suspect that the __unicode__ method for the Profile model instance, or the repr thereof is set to return a value other than self.id. For example, I just set this up:
# models.py
class Profile(models.Model):
name = models.CharField('profile name', max_length=10)
def __unicode__(self):
return u'%d' % self.id
class Plan(models.Model):
name = models.CharField('plan name', max_length=10)
profile = models.ForeignKey(Profile, related_name='profiles')
def __unicode__(self):
return self.name
# forms.py
class PlanForm(forms.ModelForm):
profile = forms.ModelChoiceField(queryset=Profile.objects.all(),
widget=forms.HiddenInput())
class Meta:
model = Plan
# views.py
def add_plan(request):
if request.method == 'POST':
return HttpResponse(request.POST['profile'])
profile = Profile.objects.all()[0]
form = PlanForm(initial={'profile':profile})
return render_to_response('add_plan.html',
{
'form':form,
},
context_instance=RequestContext(request))
With that, I see PlanForm.profile rendered thus in the template:
<input type="hidden" name="profile" value="1" id="id_profile" />

Hmm...
This might actually be a security hole.
Suppose a malicious attacker crafted a POST (say, by using XmlHttpRequest from FireBug) and set the profile term to some wacky value, like, your profile ID. Probably not what you wanted?
If possible, you may want to get the profile from the request object itself, rather than what's being submitted from the POST values.
form = PlanForm(request.POST)
if form.is_valid():
plan = form.save(commit=False)
plan.owner = request.user.get_profile()
plan.save()
form.save_m2m() # if neccesary

When you assign a Profile object to the form, Django stringifies it and uses the output as the value in the form. What you would expect though, is for Django to use the ID of the object instead.
Luckily, the workaround is simple: Just give the form primary key values of the Profile objects instead:
form = PlanForm(initial={'profile': profile.pk})
On the other end, when you're working with bound forms, however, they work much more sensibly:
form = PlanForm(request.POST)
if form.is_valid():
print form.cleaned_data['profile'] # the appropriate Profile object

There's usually no need to put related object into form field. There's a better way and this is specifying parent id in form URL.
Let's assume you need to render a form for new Plan object and then create one when form is bubmitted. Here's how your urlconf would look like:
(r"/profile/(?P<profile_id>\d+)/plan/new", view.new_plan), # uses profile_id to define proper form action
(r"/profile/(?P<profile_id>\d+)/plan/create", view.create_plan) # uses profile_id as a Plan field
And if you're changing existing object, all you need is plan_id, you can deduce any related record from it.

Since ModelChoiceField inherits from ChoiceFIeld, you should use the MultipleHiddenInput widget for this:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Profile.objects.all(),
widget=forms.MultipleHiddenInput())
class Meta:
model = Plan

Related

Django form User is 'not-null' even though it's manually set

For ease of use, one of the fields in my form can have multiple foos, but eachfoo should/will create it's own record in the database. I have a ModelForm (MyForm) that captures the fields that are repeated for each instance of foo.
Inside my views.py I copy MyForm for each instance of foo. All that is working, except for the bit where the logged in user is set as the submitter of the form. The following error is thrown: null value in column "submitter_id" violates not-null constraint.
models.py
class MyModel(models.Model):
foo_bar = models.CharField(max_length=10)
foo_baz = models.CharField(max_length=10)
submitter = models.ForeignKey(User)
forms.py
class MyForm(forms.ModelForm):
class Meta:
Model = MyModel
exclude = ['foo_bar','foo_baz','submitter']
views.py
Note: obj is a dictionary of however many foos were entered into the form
my_form = MyForm(request.POST)
if my_form.is_valid():
for k,v in obj:
copy = MyForm(request.post)
copy.save(commit=False)
copy.foo_bar = k
copy.foo_baz = v
copy.submitter = request.user # I have inspected this, and request.user is an instance of the User model
copy.save() # <-- here is where I get the error above
You need to set it on the model instance, which is returned from the form save. Also, for some reason you are re-instantiating the form after checking it is valid; you should not do that.
if my_form.is_valid():
instance = copy.save(commit=False)
instance.submitter = request.user # I have inspected this, and request.user is an instance of the User model
instance.save()
(Instead of messing about with dicts containing copies of forms, you should use formsets.)
Try setting it in the following way
copy.instance.submitter = request.user

Django. Get id of ForeignKey object in CreateView

I study the CBV in django.
I want a user can upload images to a certain card of an apartment.
So The model was created:
class Photo(models.Model):
photo_path = models.ImageField(verbose_name='Фотография')
photo_user = models.ForeignKey(User, verbose_name='Агент, добавивший фото')
photo_flat = models.ForeignKey(Flat, verbose_name='Квартира')
photo_description = models.CharField(verbose_name='Описание', max_length=200, null=True, blank=True)
The initial data of the form are photo_user and the photo_flat.
When I try to save one or multiple pictures through a form on the page I've got AttributeError.
'NoneType' object has no attribute 'pk'
My ModelForm looks like this:
class PhotoUploadModelForm(forms.ModelForm):
class Meta:
model = Photo
fields = ['photo_path','photo_user','photo_flat','photo_description'
My CreateView:
class PhotoUploadView(CreateView):
form_class = PhotoUploadModelForm
def get_initial(self):
return {'photo_user': self.request.user,
'photo_flat': self.object.pk
}
def get_success_url(self):
return reverse('ha:flatdetail')
and my urls.py
url(r'^dev/flat/(?P<flat_id>[0-9]+)/$', views_dev.flat_ajax, name='flatdetail'),
url(r'^dev/photo-update/$', views_dev.PhotoUploadView.as_view(), name='image-update')
I understand the error but has not enough skills to handle it. am I correct that self.object.pk would be the id of the picture but not the apartment?
Can I get the id of the apartment?
You're misinterpreting the error, but that is partly because what you're doing doesn't make a lot of sense.
self.object can only refer to the object that this view is concerned with, which in this case is the Photo; but of course that photo doesn't exist yet when you do get_initial, because it hasn't been created. It seems that you want to use the ID of a specific Flat object; in which case you would need to pass that ID to the view, ie via the URL. You do this already in your flat_ajax view; you should do exactly the same thing here, and then you will be able to reference the ID via self.kwargs['flat_id'].
Note that get_initial isn't really the best place to be doing this, anyway. The User and the Flat object are not values that you want to pass to the form so that they populate fields initially but let the user change them; they are values you want to automatically associate with the object on save. So, this should really be done in form_valid, as shown in the documentation:
def form_valid(self, form):
form.instance.photo_user = self.request.user
form.instance.photo_flat_id = self.kwargs['flat_id']
return super(PhotoUploadView, self).form_valid(form)
If you do it this way, you should remove photo_user and photo_flat from the fields listed in the form.

Convert all CharField Form Field inputs to lowercase in Django forms

I am using a Django form for user signup, where the user is able to enter a coupon code. I want all characters entered in the coupon code field to be converted to lowercase. I've tried using .lower() in the save method, in a custom cleaning method, and in a custom validator, but am having no luck with those approaches. Below is my code.
class StripeSubscriptionSignupForm(forms.Form):
coupon = forms.CharField(max_length=30,
required=False,
validators=[validate_coupon],
label=mark_safe("<p class='signup_label'>Promo Code</p>")
def save(self, user):
try:
customer, created = Customer.get_or_create(user)
customer.update_card(self.cleaned_data["stripe_token"])
customer.subscribe(self.cleaned_data["plan"], self.cleaned_data["coupon"].lower())
except stripe.StripeError as e:
# handle error here
raise e
As mentioned above, I've also tried a cleaning method, but this doesn't work either:
def clean_coupon(self):
return self.cleaned_data['coupon'].lower()
The solution is to create a custom form field, which allows you to override the to_python method, in which the raw values from the form fields can then be modified.
class CouponField(forms.CharField):
def to_python(self, value):
return value.lower()
class StripeSubscriptionSignupForm(forms.Form):
coupon = CouponField(max_length=30,
required=False,
validators=[validate_coupon],
label=mark_safe("<p class='signup_label'>Promo Code</p>")
)
Try using a css text-transform with widget in your form like this:
class StripeSubscriptionSignupForm(forms.Form):
coupon = forms.CharField(max_length=30,
required=False,
validators=[validate_coupon],
label=mark_safe("<p class='signup_label'>Promo Code</p>")
widget=TextInput(attrs={'style': 'text-transform:lowercase;'})
)
I came across this problem myself when working on ensuring that the email field in the user model was only saved as lowercase. The advantage to the method I outline below is that you can control the formating of each field in the form - as against the selected answer above, which will convert all fields to lowercase regardless of whether you wish so or not.
The issue for me and I believe for the OP above is that the cleaned values are now indeed in lower case, however the HTML page (the one rendered after the validation and cleaning) shows the pre-cleaned value (i.e. still in uppercase), which would confuse the user. What is happening is that the the form field value is still as per initial data i.e. X#Y.com and the cleaned data is actually x#y.com .
After processing the submitted form:
>>>user_form.cleaned_data['email']
'x#y.com'
and
>>>user_form['email'].value()
'X#Y.com'
The template uses the user_form['email'].value() instead of the value provided by user_form.cleaned_data['email'], so the user thinks his email has been saved in the uppercase form whereas really it has been saved in lowercase.
In such cases, the simplest way to present the user_form back to the client with the cleaned fields appearing in the template is to just reload the saved form directly after saving. As per the following two examples (one saving to the database one not saving to the database).
forms.py
from django.contrib.auth.models import User
class UserForm(forms.ModelForm):
"""
UserForm is a simple form to allow a user to change his/her name and email.
"""
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']
def clean_email(self):
"""
ensure that email is always lower case.
"""
return self.cleaned_data['email'].lower()
in views.py
def post(self, request):
user_form = UserForm(request.POST, instance=request.user)
if user_form.is_valid():
user_form.save() # using the provided save from Modelform in this case
user_form = UserForm(instance=request.user) # reload the amended user data
return render(request, 'myApp/user_details.html',{'user_form': user_form})
The key line here is in views.py,the user_form = UserForm(instance=request.user), where the form is reloaded. The effect here is to repopulate the form with the cleaned, (and in this case saved) data before it is presented to the user.
Now you can change every charfield in the form to lowercase by having the appropriate clean_fieldname call for those fields.
Note: if you are not interacting with a database (or just don´t wish to reload from the database) you can repopulate the form as follows:
def post(self, request):
user_form = UserForm(request.POST) #gather the post'ed data
if user_form.is_valid():
user_form.process() # process the gathered cleaned data
user_form = UserForm(
{'email': user_form.cleaned_data['email'],
'first_name': user_form.cleaned_data['first_name'],
'last_name': user_form.cleaned_data['last_name'],}
) # reload the form
return render(request, 'myApp/user_details.html',{'user_form': user_form})
As a slight optimization here, you can use the built in check :
if user_form.has_changed():
following on from the is_valid() check (or in conjunction with it) -usually there is no need to save or process a form if nothing has changed on the form.

Django modelForm changes aspect when is submitted

I'm trying to create a web using django where a form is prompted to the user, that can fill some values of it and submit them. Then, the python program will fill the rest and show the form filled by the user, with also the fields filled by the server. I am using modelForms, as I want a complete matching between my model and my form.
For several reasons I have come to do it using the following code, but I don't know why after submitting the form, the fields don't appear as CharFields anymore, but as something similar to 'html labels', and are not editable anymore.
The code is a very simplified version of mine, where there is only one field, filled by the user.
views.py:
def manage_printers(request):
p = PrinterForm(request.POST or None)
if request.method == 'POST':
if p.is_valid():
f = p.save(commit=False)
f.name = request.POST.get('name')
f.save()
return render_to_response('web.html', {'printer': f})
else:
return HttpResponse("form not valid")
return render_to_response('web.html', {'printer': p}, )
models.py:
class Printer(models.Model):
name = models.CharField(max_length=200, default=' ')
def __unicode__(self):
return self.name
class PrinterForm(ModelForm):
class Meta:
model = Printer
web.html:
<form action="/useriface/" method="post">
<p>Name: {{ printer.name }}</p>
</form>
As I explained in the comment, when you save a form, you get a model instance. A model instance is not a form. It doesn't have form fields. When you pass the saved instance into the template in place of a form, you won't display form fields, you'll display the values of the saved instance's fields. If you want to redisplay the form, you should pass that into the template, not the saved instance.

form.is_valid() always returning false

Model class
class Fuzz_Engine(models.Model):
id = PositiveTinyIntField(primary_key = True)
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
View Class
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
# input validation for add phone model form
if form.is_valid():
fuzzEngineToAdd = Fuzz_Engine (engine_name = request.POST['engine_name'], version = request.POST['version'])
fuzzEngineToAdd.save(force_insert=True)
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': fuzzEngineToAdd.id,'fe': fuzzEngineToAdd,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
I have looked into a few similar questions on this issue, tried to print out the errors but doesn't seem to appear.
Django Formsets - form.is_valid() is False preventing formset validation
form.is_valid() always returning false
I have a feeling that the cause of the error lies in the structure of my model form class.
The .is_valid is false as I have placed a code in that if statement and it doesn't run, however if I have an else statement(which is not here) for if it is not valid, it will appear.
Can anyone provide another way of debugging this kind of error?
Couple of issues, it's hard to debug the code if the code you paste in isn't formatted well. The indentations were a mess so I'm not sure if that's causing a problem.
It seems like you are manually assigning a foreign key for your model. I would suggest just letting django handle the id for the model:
class Fuzz_Engine(models.Model):
engine_name = models.CharField(max_length=16)
version = models.CharField(max_length = 16)
class Meta:
db_table = 'fuzz_engine'
unique_together = ('engine_name', 'version')
Your form looks fine:
class AddFuzzEngineForm(ModelForm):
class Meta:
model = Fuzz_Engine
Some problems I see in your views include:
you shouldn't use request.POST['field_names'] directly. you should be getting the cleaned_data from your form.
you can save the form directly because it is a ModelForm. if you need the instance that you just created, that is what is returned from the save method, you can set a variable and use that as shown below.
def addengine(request)
if request.method == 'POST':
form = AddFuzzEngineForm(request.POST)
if form.is_valid():
instance = form.save()
return render_to_response('fuzz/fuzz_cengine_results.html', {'fid': instance.id,'fe': instance,},context_instance=RequestContext(request))
else:
form = AddFuzzEngineForm()
return render_to_response('fuzz/add_fuzz_engine.html', {'form': form},context_instance=RequestContext(request))
With your original view, it looks like you are trying to save a Fuzz_Engine instance with no id.
DTing has some great points, but I suspect your actual problem is related to your explicit definition of the id field on your model. Unless you have a really good reason, you should never do this - Django defines an autoincrement field automatically, and there is rarely any point overriding this, unless you are using a legacy db that can't be changed.
In your case, you have defined it as a tinyint without autoincrement. That means that field is going to be required on any form, as it needs to be specified manually every time you create a new instance. You haven't shown the template you're using to display the form so it's impossible to be sure, but I imagine you're not showing this field at all.
If you really really want to carry on doing it this way, you will need to specify exclude = ('id',) on the form Meta. Then in your is_valid clause, taking on board DTing's recommendations, you'll need to do this:
if form.is_valid():
instance = form.save(commit=False)
instance.id = some_function_for_calculating_id()
instance.save()
But as I say, you shouldn't be doing that at all.

Categories

Resources