Form field with underscore doesn't work - python

I'm using a form to obtain the data from a post request and I noticed some behavior I'm not familiar with. I have two versions of my form shown below. The only variation is that in the second version of the form, I have an underscore in my variable names.
class EditTitleForm(Form):
newTitle = StringField('newTitle')
currentTitle = StringField('currentTitle')
class EditTitleForm(Form):
new_title = StringField('newTitle')
current_title = StringField('currentTitle')
However, when I try print(form.new_title.data) and print(form.current_title.data) I get an empty line, but when I switch the form to the version without the underscores, everything prints out fine. Why is that?
#auth.route('/edit_title', methods=['POST'])
def edit_title():
data = MultiDict(mapping=request.json)
print(data)
form = EditTitleForm(data)
print(form.newTitle.data)
print(form.currentTitle.data)

The names of the fields correspond to the keys in the data being read. If you change the field names, you need to change the keys in the JSON data as well. Setting the label (the first argument to a field) doesn't effect this, that's only used when rendering the fields as HTML.
# if attribute name is new_title
new_title = StringField()
# then data key must be new_title as well
data = {
"new_title": "Stack Overflow"
}
WTForms can work with JSON, but if you want something more suited to that, try Marshmallow or another serialization library.

Related

Saving ManytoMany fields in Django with multiple forms

I am trying to do the following in 3 forms that are processed at once:
1. Save the subject line for an email (this works)
2. Save the email content. (this works)
3. Save the email title (works) and save the relationship to the subject line and to the email content (not working).
I have read through every page I could which had to do with errors. I have tried it a variety of different ways, but they all haven't worked for me.
View.py:
def email_subject_create_view(request):
if request.method == 'POST':
email_subject_form = EmailSubjectForm(request.POST)
email_content_form = EmailContentForm(request.POST)
email_form = EmailForm(request.POST)
if email_subject_form.is_valid() and email_content_form.is_valid() and email_form.is_valid() :
subject = email_subject_form.save(commit=False)
subject.time_created = datetime.datetime.now()
contractor = Contractor.objects.get(user_id=request.user.id)
subject.save()
email_subject_form.save_m2m()
content = email_content_form.save(commit=False)
content.time_created = datetime.datetime.now()
content.save()
email_content_form.save_m2m()
email = email_form.save(commit=False)
email.time_created = datetime.datetime.now()
# this is what I want to do. email.email_core_contents is a M2M field
email.email_subject_lines = subject.id
email.save()
context = {
'email_subject_form': EmailSubjectForm(),
'email_content_form': EmailContentForm(),
'email_form': EmailForm(),
}
return render(request, 'advertise/email_subject_create.html', context)
I have tried:
email.email_subject_lines = EmailSubject.objects.get(pk=subject.id)
email.email_subject_lines.set(subject)
email.email_subject_lines.set(pk=subject.id)
I have also tried it without the .save_m2m() portions of code.
Edit:
Error in the main example:
Direct assignment to the forward side of a many-to-many set is prohibited. Use email_subject_lines.set() instead.
Error in 1 of the 3 set:
Direct assignment to the forward side of a many-to-many set is prohibited. Use email_subject_lines.set() instead.
Error in the 2 of the 3 set:
"<Email: sfeaesfe>" needs to have a value for field "id" before this many-to-many relationship can be used.
Error in the 3 of the 3 set:
"<Email: graegre>" needs to have a value for field "id" before this many-to-many relationship can be used.
Found two solutions. Here they are in case anyone runs into the same problem in the future.
1: Use the add method.
email.email_subject_lines.add(subject)
email.email_core_contents.add(content)
2. Fetch both objects (Email and EmailSubject) from the database again.
email2 = Email.objects.get(id=email.id)
subject2 = EmailSubject.objects.filter(id=subject.id)
email2.email_subject_lines.set(subject2)
email2.save()
Not sure why it works this second way, but not the original way. Note that I had to use filter instead of get in the second expression.

How to access field names of a ModelForm in Django?

Just to be clear, I'm asking about accessing the fields in views.py
I want to add extra data into the form before it is validated (because it's a required field), and another answer on stackexchange seems to imply I have to create a new form to do so.
Right now my code look something like this:
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = TestForm(request.POST)
data = {}
for ---:
---add to data---
comp = Component.objects.get(name = path)
data['component'] = comp.id
form = TestForm(data)
if form.is_valid():
test = form.save(commit = 'false')
test.save()
return submitTest(request, var)
How could I fill in the parts with dashes?
This is the wrong thing to do. There is no reason to add in a required field programmatically; if you know the value of the field already, there is no reason to include it on the form at all.
I don't know what you mean about having to create another form; instead you should explicitly exclude that field, in the form's Meta class, and set the value on the test object before calling test.save().
Edit after comment I still don't really understand why you have data coming from two separate places, but maybe you should combine them before passing to the form:
data = request.POST.copy()
data['myvalue'] = 'myvalue'
form = MyForm(data)
I figured out what I was doing wrong. In my TestForm modelform I didn't include the 'component' field because I didn't want it to show up on the form. As a result, the 'component' data was being cleaned out during form validation even if I inserted it into the form correctly. So to solve this I just added 'component' into the fields to display, and to hide it on the form I added this line
widgets = {'component': HiddenInput()}
to the TestForm class in forms.py.

How to get a filed from self.data in model_formset_factory in clean method

I am using modelformset_factory to edit multiple images on my interface.
I have following fields in each image.
Name
User
City
I have allowed user to select new user that is currently not in the system, (for that case I should get a text "Jack" in my
def clean_user(self) instead of ID.
But using model_formseta_factory, I am getting some wired names in my self.data. and when I try to get self.data.get('user'), I get nothing, obviously there is no key with this name,
the key is formed like form_0_user etc.
fields = ['city', 'name']
note, i do not have user in my fields. if I do, it fails the validation.
def clean(self):
data = self.cleaned_data
data['name'] = data.get('name', '').strip()
return data
Works fine
pic_credits = self.data.get('user')
This does not.
pic_credits = self.data.get('form-0-name')
This works fine too.
Please help.
If you want to use self.data instead of self.cleaned_data, you can construct the "composite prefix" using the fields auto_id and prefix (or at least when the form has been instanced by a formset).
See _construct_form() https://docs.djangoproject.com/es/1.9/_modules/django/forms/formsets/
Your method will look like this:
def clean(self):
# ...
form_prefix_and_autoid = "%s-%d-" % (self.prefix, self.auto_id)
pic_credits = self.data.get(form_prefix_and_autoid + 'name')
# ...
Update:
A lot simpler is calling the method self.add_prefix
pic_credits = self.data.get(self.add_prefix('name'))

Decorate GET URL using forms

I have some question:
I use django form, and fields like MultipleChoiceField
in view.py I clean data and get GET URL like this
http://localhost:8000/?category=&style=&sex=&brand=ASICS&brand=Be+Positive&low_price=&high_price=
Give me advise, can I regroup brand field and hide empty.
I want getting something like this:
http://localhost:8000/?brand=1+2
And else one question:
How can I set empty value(empty_label) for forms.ModelMultipleChoiceFIeld
forms.py:
brand = forms.MultipleChoiceField(required=False,
widget=forms.SelectMultiple(attrs={'size':1})
)
def __init__(self,app_label=None, *args, **kwargs):
super(Search, self).__init__(*args, **kwargs)
self.fields['brand'].choices = [('', 'All brands')]+[(brand.name, brand) for brand in Brand.objects.all() ]
views.py:
if request.method == 'GET' and request.GET:
form = SearchForm(app_label, request.GET)
if form.is_valid():
brands = form.cleaned_data['brand']
kwargs.update({"brand__name__in": brands})
This is how the browser submits multiple data. It's part of the HTML specification, trying to change it would be folly and technically I can't understand why you would try to care about how your url GET data looks.
That being said, if you want to change the way it submits you'll need javascript to transform the data on form submit. Django has nothing to do with the matter.
Using jQuery for example:
$('#form').submit(function(){
//Get form data
//Transform into my custom set of vars
//Redirect to form's ACTION with my querystring appended.
});
Please keep in mind you will not get any automatic parsing of the values on the Django side. Normally it would turn it into a list for you, but now you're responsible for parsing the 'value+value+value' yourself.
For empty label in forms you could do this -
class SomeForm(forms.Form):
h=forms.CharField(label=u'',widget=forms.TextInput(attrs={'value':'Search'}))
By keeping label as '', you get the label as empty. The attrs are basically the HTML attributes of the form text field.
UPDATE: I didn't understand the first part of your Q, elaborate...

How to validate django form only when adding not editing

How could we make the django form to not validate if we are editing, not adding a new record. The code as following :
class PageForm(forms.Form):
name = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'textInput'}))
description = forms.CharField(max_length=300, required=False,widget=forms.TextInput(attrs={'class':'textInput'}))
body = forms.CharField(widget=forms.Textarea)
template = forms.CharField(max_length=30,widget=forms.TextInput(attrs={'class':'textInput'}))
navbar = forms.BooleanField(required=False, widget=forms.Select(choices=(('True','True'),
('False', 'False'))))
publish = forms.BooleanField(widget=forms.Select(choices=(('Published','Publish Now'),
('Private','Private'),
('Draft','Draft'))))
def save(self, page=None, commit=True):
data = self.cleaned_data
if not page:
page = models.Page(key_name=data['name'].replace(' ','-'))
page.name = data['name']
page.description = data['description']
page.body = data['body']
page.template = data['template']
page.publish = data['publish']
if commit: page.put()
return page
# prevent the same page 's name
def clean_name(self):
name = self.cleaned_data['name']
query = models.Page.all(keys_only=True)
query.filter('name = ', name)
page = query.get()
if page:
raise forms.ValidationError('Page name "%s" was already used before' % name)
return name
The purpose of this name validation is to prevent the records with the same name. BUt i found that, it also validate on edit, so we couldn't edit records, since it will said 'records with same name already exist'.
Actually for editing, the page param on save function wont be none, but prev record instead, and wil be none on saving a new one. But how we read this param, on clean_name function so we can now whether it is editing or creating?
Thanks a lot!
in your clean method, you can use self.initial to know whether it is adding or editing. If it is editing, the self.initial will not be empty. But when it is adding, self.initial will be dictionary of what the previous value.
If you are editing form, then the form has some instance, and you can check if that exists.
If it does, then you are probably editing existing object.. right?
Example:
If you are editing object with form, you create form object much like this:
form = MyForm(instance = myobject)
Then in your form class methods you can check if form has saved instance in a way that it is described here:
Test if Django ModelForm has instance
in your clean_name function exclude the current object from queryset
query.filter('name = ', name).exclude(pk=self.pk)
or change the if condition to check that page and current object are not the same.
Sorry, I couldn't comment below your guys post, don't know why.
#sunn0 : I didn't use django models, coz deploy the app in appengine, so use appengine model instead.
#Zayatzz : May you show a little code how to do it? Since whether we are adding or editing, we always bound the form to request.POST before validation, so don't know how to differentiate.
#Ashok : I made a workaround based on your suggestion. Since previously I didn't pass the pk to form, but passing the prev object as param instead, so couldn't exclude by using pk. So, I change the code and put additional key as pk (if create, let key empty, but if edit fill key with pk) and just check in if condition, if key field not empty, then it means we are editing. Not sure if it is best practice, but it works anyway.
I can suggest to override form's init method
https://stackoverflow.com/a/70845558/15080117
because there is an argument instance.

Categories

Resources