Ho to test a create function in Django? - django

# my .views
def post_create_view(request):
form = PostModelForm(request.POST or None)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
form = PostModelForm()
context = {'form': form}
return render(request, 'form.html', context)
# my .urls
path('create/', post_create_view, name='create')
# my .test
class TestViews(TestCase):
def setUp(self):
self.client = Client()
self.create_url = reverse('posts:create')
self.user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')
self.post1 = Post.objects.create(user=self.user, title='testpost', content='fgh')
self.query = Post.objects.all()
def test_blog_create_POST(self):
# this is where the error surely is
response = self.client.post(self.create_url, {'title': 'testpost2', 'content': 'asd'})
self.assertEqual(response.status_code, 302)
# this is where the test fail
self.assertEqual(self.query.last().title, "testpost2")
'testpost2' doesn't get created but my form works fine. How can I simulate the inputs in my testcase?
self.create_url redirect to the correct url, the one with the form in the template, such form works great, but when I write the response in my test the dictionary I pass as second arguments seems not to go through.

In case of invalid form it's just re-render form on page - that's produces status 200, not 302. Also, it's not obvious why you want to send redirect status in case of invalid form.
If this endpoint only used for POST request, then you need display form errors in your template. Or, if you think redirect is a better option in your case, if is_valid() returns false just perform redirect.

Related

Do I really need to accommodate both GET and POST request formats in a django form?

I'm new to django but something I don't understand is the need for accommodating both the GET and POST request types when developing a form. Please refer to code below from django docs:
from .forms import NameForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
The reason this confuses me is because I have developed a GET based form and it is working and I have no need for the POST portion above? See below:
# views.py
def simplifier_form(request):
form = LabelForm()
return render(request, "simplifier.html", {"form": form})
def process_simplifier(request):
label_name = request.GET.get('labels')
results = Disturbance.objects.filter(labeldisturbances__label_name=label_name)
painsresults = Pain.objects.filter(disturbances__pk__in=results).distinct()
total_disturbances = Disturbance.objects.filter(pain__pk__in=painsresults)
total_labels = Label.objects.filter(disturbances__pk__in=total_disturbances).distinct()
context = {'results': results, 'painsresults': painsresults, 'total_disturbances': total_disturbances, 'total_labels': total_labels}
return render(request,'process_simplifier.html', context)
# forms.py
class LabelForm(ModelForm):
labels = forms.ModelChoiceField(
queryset=Label.objects.all(),
to_field_name='label_name',
widget=forms.Select(attrs={'class': 'labels'}),
)
class Meta:
model = Label
fields = ['labels']
So why do the django docs and most examples include code for both methods when you only really use one like in my example above?
The question is... How do you want to process your from when submitted on POST?
Remember, your form can also be submitted on GET... You can decide what you would want to do if submitted on GET.

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 returning None instead of a HTTP response

OK im probably doing this all wrong!
I am trying to run a function in a view which calls another view.
This seems to pass my request into the next function as a POST method before loading the form from the second function.
my views.py
''' This section of hte code seems to function correctly '''
#login_required()
def joinLeague(request):
if request.method == 'POST':
league = JoinLeagueQueue(user=request.user)
form = JoinLeagueForm(instance=league, data=request.POST)
if form.is_valid():
form.save()
context = int(league.id) # displays id of model, JoinLeagueQueue
return HttpResponseRedirect(confirmLeague(request, context))
else:
form = JoinLeagueForm()
context = {'form':form}
return render(request, 'userteams/joinleagueform.html', context)
This section of the views file is not working correctly.
it seems to run the POST request without displaying the GET request with the form first.
#login_required()
def confirmLeague(request, league):
# gets ID of application to join league
joinleagueid=JoinLeagueQueue.objects.get(id=league)
pin = joinleagueid.pin # gets pin from application
user = joinleagueid.user # get user from application
leagueinquestion=League.objects.get(leaguePin=pin) # gets the league record for applied league
manager=leagueinquestion.leagueManager # Gets the league manager for league applied for
leaguename=leagueinquestion.leagueName # Gets the league name for league applied for
if request.method == 'POST':
if 'accept' in request.POST:
LeaguesJoinedTo.objects.create(
leaguePin = pin,
playerjoined = user,
)
return redirect('punterDashboard')# user homepage
else:
print("Error in POST request")
else:
context = {'leaguename':leaguename, 'pin':pin, 'manager':manager}
return render(request, 'userteams/confirmleague.html', context)
I now get an error saying Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/userteams/None
Using the URLconf defined in fanfoo_proj.urls, Django tried these URL patterns, in this order:
... im skipping a list of the patterns.
10. userteams/ confirmLeague [name='confirmLeague']
Ok i think the better way would be a HttpRedirect to the second view:
return confirmLeague(request, context)
should change to something like:
return redirect(confirmLeague,args=league)
django doc to urlresolvers: https://docs.djangoproject.com/en/3.0/topics/http/shortcuts/#redirect
def joinLeague(request):
if request.method == 'POST':
league = JoinLeagueQueue(user=request.user)
form = JoinLeagueForm(instance=league, data=request.POST)
if form.is_valid():
form.save()
context = league.id
return HttpResponseRedirect( reverse("your_confirmLeague_url",kwargs={'league':context}) )
else:
form = JoinLeagueForm()
context = {'form':form}
return render(request, 'userteams/joinleagueform.html', context)
def confirmLeague(request, league):
league = get_object_or_404(JoinLeagueQueue, pk=league)
pin = league.pin
if request.method == 'POST':
if 'accept' in request.POST: # This refers to the action from my form which is waiting for a button press in a html form.
LeaguesJoinedTo.objects.create(
leaguePin = pin,
playerjoined = request.user.id,
)
return redirect('punterDashboard')
else:
context = {'league':league}
return render(request, 'userteams/confirmleague.html', context)

Django forms: error not displaying in json

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})

How to use parse_qs in redirecting from view to another?

I have a home in which I have a form that I get some info from students to suggest them some programs to apply to. The home view is as below:
def home(request):
template_name = 'home.html'
home_context = {}
if request.POST:
my_form = MyModelForm(request.POST)
if my_form.is_valid():
# do some stuff
return programs(request)
else:
my_form = MyModelForm()
home_context.update({'my_form': my_form, })
return render(request, template_name, home_context)
In the second view, I have the same form and I want this form to be pre-occupied with the information I entered in the home page. That is why in the above, I passed my POST request to programs view that is as below:
def programs(request):
template_name = 'programs.html'
programs_context = {}
if request.POST:
my_form = MyModelForm(request.POST)
if my_form.is_valid():
# do some other stuff
else:
my_form = MyModelForm()
programs_context.update({'my_form': my_form, })
return render(request, template_name, programs_context)
The drawback of this strategy (passing the POST request in the home view to the programs_view) is that the url in url bar does not change to 'example.com/programs' and stays as 'example.com' . I will have some problems including problems in pagination of the programs.
The alternative is that I do this:
def home(request):
template_name = 'home.html'
home_context = {}
if request.POST:
my_form = MyModelForm(request.POST)
if my_form.is_valid():
# do some stuff
querystring = request.POST
return redirect(reverse('programs') + '?' + parse_qs(querystring, keep_blank_values=True))
else:
my_form = MyModelForm()
home_context.update({'my_form': my_form, })
return render(request, template_name, home_context)
First Of all, I get an error when I do this: 'QueryDict' object has no attribute 'decode'
second, I do not know what to do in the programs view to make use of the query I am sending to the get branch of the programs view.
Third, I need to the stuff I used to do in the post branch of the programs view in get branch of the programs view if it is from a redirect not from an independent direct get request. How can I distinguish this in programs get request?
Overall, any alternative solution and help are highly appreciated.
Use request.GET['your_key'] to get data passed in a query string.
You can check this SO answer.
That is not at all what parse_qs does. Parsing is taking a string - ie a querystring - and turning it into usable objects; what you want to do is the opposite, taking the POST and turning it into a querystring; ie encoding it, not parsing it.
Note, despite what you've called it, request.POST is not a querystring, it's a QueryDict. To encode that dict into a string you can use the urlencode method: request.POST.urlencode()
However this still won't work, because a redirect is always a GET and your destination form is expecting a POST. This isn't really the right approach at all; instead what I would do would be to store the querydict in the session and use it from there in the second view.
I have a home in which I have a form that I get some info from students to suggest them some programs to apply to. The home view is as below:
def home(request):
template_name = 'home.html'
home_context = {}
if request.POST:
my_form = MyModelForm(request.POST)
if my_form.is_valid():
querystring = urlencode(request.POST)
return redirect(reverse('programs') + '?' + querystring)
else:
my_form = MyModelForm()
home_context.update({'my_form': my_form, })
return render(request, template_name, home_context)
and this in the programs view
def programs(request):
template_name = 'programs.html'
programs_context = {}
if request.POST:
my_form = MyModelForm(request.POST)
if my_form.is_valid():
# do some other stuff
else:
# if the get is from redirect
my_form = MyModelForm(request.GET)
# do some other stuff
# else:
my_form = MyModelForm()
programs_context.update({'my_form': my_form, })
return render(request, template_name, programs_context)
I now want to devide the get method of the programs view into to parts by checking whether a request is coming from entering example.com/programs or it coming from the redirect part of the home view. What is the standard method to do this? What is the standard way of writing the commented if and else above?