Django forms: error not displaying in json - django

I am trying to set up my password forgot in Django using built-in validators, but I am getting no response if reproduce errors, but it supposed to show up the errors others classes, how can I fix this?
forms.py
class RestorePasswordForm(SetPasswordForm):
new_password1 = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'Password','required': True}))
new_password2 = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'Password','required': True}))
views
def post(self, request,token):
form = RestorePasswordForm(request.POST)
if form.is_valid():
obj = token(token)
if obj:
this_user = User.objects.get(id=obj.email_id)
if not this_user:
return False
this_user.set_password(request.POST.get('new_password2'))
this_user.save()
obj.token = None
obj.save()
return JsonResponse({"message":form.errors})
browser response display
{"message": {}}

When you are validating a form you should always check if it is posting and then only process it. Also 'forms.errors' is going to give you HTML string, but you already are aware of that.
The reason you are getting empty reason is because nothing is being posted to the form.
def post(self, request,token):
if request.method=="POST":
form = RestorePasswordForm(request.POST)
if form.is_valid():
obj = token(token)
if obj:
this_user = User.objects.get(id=obj.email_id)
if not this_user:
return False
this_user.set_password(request.POST.get('new_password2'))
this_user.save()
obj.token = None
obj.save()
else:
print("you didn't post anything to form")
return JsonResponse({"message":form.errors})

Related

How to test a django. view for correct calculated values

I have a simple view to recalculate values (g -> kg) in a django form to add data inside of the view and to simply re-calculate it.
Now, i want to test such view to give it the value and compare "output" How should I test it. Here is a code of view itself:
def weight_converter(request):
if request.method == 'POST':
form = Adder(request.POST)
if form.is_valid():
try:
value_in_g = int(form.cleaned_data['added_data'])
result_kg = value_in_g/1000
return render(request, 'weight_converter.html',
{'result': result_kg, 'initial_value': value_in_g,})
except ValueError as e:
e.message = 'Enter the number, not the string'
return render(request, 'weight_converter.html', {'message': e.message})
else:
form = Adder()
return render(request, 'weight_converter.html', {'form': form})
https://pastebin.com/tUkzsUvR
Would something like this work?
def test_weightconverter_view_calculation(self):
response = self.client.get('/weight_converter/', {'value_in_g': '1234'})
self.assertEqual(response.context['result'], 1.234)
Thanks in advance for any advice! )
You make a GET request, not a POST request, and the result is only specified with the POST request, and likely with added_data as key:
def test_weightconverter_view_calculation(self):
response = self.client.post('/weight_converter/', {'added_data': '1234'})
self.assertEqual(response.context['result'], 1.234)
Here however it would likely make more sense to do the calculations with a GET request, since there are no side effects when converting grams to kilograms.

CreateView form_invalid for multiple forms?

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.

How to plug the reponse of a django view GET request into the same view as a POST request during testing?

I have a Django function-based form view that initializes a form with default data on a GET request, and saves the model object on a POST request:
def copy(request, ann_id):
new_ann = get_object_or_404(Announcement, pk=ann_id)
new_ann.pk = None # autogen a new primary key (quest_id by default)
new_ann.title = "Copy of " + new_ann.title
new_ann.draft = True
new_ann.datetime_released = new_ann.datetime_released + timedelta(days=7)
form = AnnouncementForm(request.POST or None, instance=new_ann)
if form.is_valid():
new_announcement = form.save(commit=False)
new_announcement.author = request.user
new_announcement.datetime_created = timezone.now()
new_announcement.save()
form.save()
return redirect(new_announcement)
context = {
"title": "",
"heading": "Copy an Announcement",
"form": form,
"submit_btn_value": "Create",
}
return render(request, "announcements/form.html", context)
I can't figure out how to test the form.is_valid() branch when the form is posted, without manually providing the form data to self.client.post(url, form_data) in my view.
Here' what I'm trying:
test_views.py
class AnnouncementViewTests(TestCase):
def setUp(self):
self.client = ...
... etc
def test_copy_announcement(self):
# log in a teacher
success = self.client.login(username=self.test_teacher.username, password=self.test_password)
self.assertTrue(success)
# hit the view as a get request first, to load a copy of the announcement in the form
response = self.client.get(
reverse('announcements:copy', args=[self.test_announcement.id]),
)
self.assertEqual(response.status_code, 200)
# The form in this response should be valid, and should bw
# accepted on a post request,
# that is what I am testing in this integration test.
form_data = response.how_do_get_the_form_data() # ???????
response = self.client.post(
reverse('announcements:copy', args=[self.test_announcement.id]),
data=form_data
)
# Get the newest announcement just made in the post request
new_ann = Announcement.objects.latest('datetime_created')
self.assertRedirects(
response,
new_ann.get_absolute_url()
)
What I want to actually test is that the result of the get provides valid default data for the form that can then be submitted via post request.
But I can't figure out how to access the form data resulting from the get request, so I can then feed it into the form_data provided to the post request.
EDIT
I found the location of the form in the get response, but I have no idea how to get that in code.
You can access the response form in this way:
response.context['form']
From here you can build your payload in this way:
retrieved_instance = response.context['form'].instance
form_data = dict(title=retrieved_instance.title, ... <all the other fields>)
response = self.client.post(
reverse('announcements:copy', args=[self.test_announcement.id]),
data=form_data)
)
This is not like resubmitting the page but is very similar because you're resubmitting the same form.
Actually, your test seems more like an e2e test (when speaking about integrations and e2e there is some ambiguity let's say), for this reason, if I were you, I would switch "tool" and use selenium to simulate a user interaction from the beginning (open an existing announcement) to the end, pressing the submit button on your web page.
Only in this way what you're submitting is the "real" response of the "get"
If you are new to this kind of test, you can find here a simple tutorial https://lincolnloop.com/blog/introduction-django-selenium-testing/ to understand the main concepts.
Your question is bit confusing but I will try to head you in right direction.
To clean-up the code you should use Class Based View where you can use your form easily anywhere. A sample code I just wrote:
class TestView(View):
template_name = 'index.html'
success_url = 'home' # dummy view
context = {"form": myform()}
# myform is the definition/class of your form which contains all attrs.
def get(self, request):
context = self.context
context['form'] = form # fill out your data here for get request
return render(request, self.template_name, context)
def post(self, request):
context=self.context
# self.context certain that you're using exact form which you defined in class-scope
form=context['form']
# Form Validation
if form.is_valid():
#perform any logical validations here and save the form if required
return redirect(self.success_url)
context = self.context
context['form'] = form # just to show you that you can access that form anywhere
return render(request, self.template_name, context)
You coul manually pass data to you form like this and test the is_valid function in a unit test:
form_data = {'my': 'value', 'form': 'value', 'fields': 'value'}
form = AnnouncementForm(form_data)
self.assertFalse(form.is_valid)

Django m2m field doesn't assign to parent object in view

THE CONTEXT
I am trying to implement a tagging system for my project. The various plug-in solutions (taggit, tagulous) are each unsuitable in some way.
I would like to allow users to select from existing tags or create new ones in a Select2 tagging field. Existing tags can be added or removed without problem. My difficulty is in the dynamic generation and assignment of new tags.
MY APPROACH
Select2 helpfully renders the manually-entered tags differently in the DOM from those picked from the database via autocomplete. So upon clicking submit, I have javascript collect the new tags and string them together in the value of a hidden input, then delete them from the Select2 field to avoid any validation errors (the form otherwise POSTs the tag names as the ids, which throws a db error).
In the view, I iterate over the desired new tags. For each entry I create the new tag, then add it to the parent object's related set.
THE PROBLEM
While this successfully creates each tag (verified via Admin) it doesn't add it to the related set.
No errors are generated on the (clearly not succeeding) related set add.
The newly-generated tags are correctly instantiated and can be Select2-chosen and sucessfully assigned on a subsequent UpdateView, so I'm certain the problem lies in the view-assignment of the tags to the parent.
The same code executed via the Django shell work flawlessly, so I don't believe its a simple syntax error.
Thus the locus of the problem seems to be in the POST view code adding newly-generated tags to the parent, but I cannot see where the code goes astray.
Thanks for any insights or advice!
models.py:
class Recipe_tag(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
tag = models.CharField('Tag name',max_length=32,null=False,unique=True)
def __str__(self):
return str(self.tag)
class Recipe_base(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField('Recipe name',max_length=128,null=False)
tags = models.ManyToManyField(Recipe_tag,related_name='recipes',null=True,blank=True)
def __str__(self):
return str(self.name)
The post portion of the view:
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateTagsForm(request.POST,instance=r)
form_valid = form.is_valid()
if form_valid:
if form.has_changed:
f = form.save(commit=False)
clean = form.cleaned_data
f.addedTags = clean['addedTags']
if f.addedTags == 'placeholder':
pass
else:
new_tags = f.addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
r.tags.add(a)
f.save()
form.save_m2m()
else:
pass
return self.form_valid(form)
else:
return self.form_invalid(form)
Doing further digging, I found a post on another site in which the OP was experiencing the same issues. The trick is to remove the m2m assignment from the "create" or "update" process entirely, because the final save() that occurs in form_valid will discard any changes to the parent's related set.
In my case, the solution was to punt the iteration/assignment of the new tags to form_valid, directly after the final save() occurs there:
def form_valid(self, form, **kwargs):
self.object = form.save()
addedTags = kwargs['addedTags']
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
if addedTags == 'placeholder':
pass
else:
new_tags = addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
# print("debug // new tag: %s, %s" % (a.tag, a.id))
r.tags.add(a)
# print("debug // added %s to %s" % (a.tag,r.id))
return HttpResponseRedirect(self.get_success_url())
def get(self, request, *args, **kwargs):
self.object = Recipe_base.objects.get(id=self.kwargs.get('pk'))
recipe_name = self.object.name
recipe_id = self.kwargs.get('pk')
form = RecipeUpdateForm(instance=Recipe_base.objects.get(id=self.kwargs.get('pk')))
return self.render_to_response(self.get_context_data(form=form,recipe_name=recipe_name,recipe_id=recipe_id))
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateForm(request.POST,instance=r)
form_valid = form.is_valid()
#print("debug // form_valid PASSED")
if form_valid:
if form.has_changed:
#print("debug // Form changed...")
f = form.save(commit=False)
#print("debug // Passes save C=F")
clean = form.cleaned_data
#print('debug // pre-existing tags: ',clean['tags'])
f.addedTags = clean['addedTags']
addedTags = clean['addedTags']
#print('debug // manual tags: ',clean['addedTags'])
f.save()
form.save_m2m()
#print("debug // Form saved")
else:
#print("debug // Form unchanged, skipping...")
pass
#print("debug // Reached successful return")
return self.form_valid(form, addedTags=addedTags,pk=r.id)
else:
#print("debug // Form fails validation")
#print("debug // Reached unsuccessful return")
return self.form_invalid(form)

Django request.POST as an argument of a form

I am having a hard time wrapping my head around what request.POST is doing as a argument in the following example:
def addauthorView(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
first_name = form.cleaned_data['firstname']
last_name = form.cleaned_data['lastname']
user_email = form.cleaned_data['email']
c = AuthorModel(firstname=first_name, lastname=last_name, email=user_email)
c.save()
return HttpResponseRedirect('thanks/')
else:
form = ContactForm(request.POST)
return render(request, 'addauthor.html', {'form': form})
So I know that this works, but for some reason I cannot understand the magic that is happening with form = ContactForm(request.POST). Why does the ContactForm need the request.POST argument? What is happening behind the scenes?
Extra question, why is form = ContactForm(request.POST) then repeated in the else: block. Why is that helpful and when is that useful? Examples?
In a nutshell, request.POST is simply the data that was sent when the form was submitted. It's a dictionary of what the user submitted for firstname, lastname and email in your code sample. For those that come from a PHP background, it's what is provided in $_POST.
form = ContactForm(request.POST) binds the data to the form class so Django can do fun stuff like validate inputs with is_valid().
Why then, would you add request.POST to the else: block? Well, have you ever submitted a form to a website and when there was an error you had to completely fill out the form again? That's a crappy user experience, right? By sending the form back to the user with the data from request.POST, you can re-render what the user inputed - along with helpful extras such as error messages - so they can fix them and resubmit.
EDIT: To expand, here is the init method from the BaseForm class in Django:
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False):
self.is_bound = data is not None or files is not None
self.data = data or {}
self.files = files or {}
self.auto_id = auto_id
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class
# Translators: This is the default suffix added to form field labels
self.label_suffix = label_suffix if label_suffix is not None else _(':')
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.
self._changed_data = None
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
self.fields = copy.deepcopy(self.base_fields)
When you pass request.POST to your form class, you're really doing data=request.POST. That in turn triggers the self.is_bound = True