I've read up on using different renderers or overriding renderer but I'm wondering if there's a better way to handle this pattern.
Right now, in my view I'm returning a set of items to the template:
#view_config(
route_name = 'name',
permission = 'perm',
renderer = 'r.mako'
)
def r( request ):
items = get_items()
return { 'items': items }
Now I want the ajax version to just renderer a subset of it, also with some data. My current working code:
#view_config(
route_name = 'name',
permission = 'perm',
renderer = 'r.mako'
)
def r( request ):
items = get_items()
if ajax:
return Response( to_json( {
'data1': 1,
'data2': 2,
'data3': 3,
'html': renderers.render( 'shortr.mako',
{ 'items': items },
request )
} )
return { 'items': items }
I guess specifically I wonder if there's a cleaner way to override the renderer and then wrap it in something, without explicitly calling render and making sure I got the dict right or request as a param. thanks
I would suggest using 2 views which properly allow you to apply a different "look-and-feel" (responses) to the same data.
def get_items(request):
return {} # values that you can pick and choose from in each view
#view_config(route_name='name', permission='perm', xhr=True, renderer='json')
def r_ajax(request):
items = get_items(request)
return {
'data1': 1, 'data2': 2, 'data3': 3,
'html': renderers.render('shortr.mako', {'items': items}, request),
}
#view_config(route_name='name', permission='perm', renderer='r.mako')
def r_html(request):
items = get_items(request)
return items
If you're afraid of repeating things for the view configuration, Pyramid 1.3 comes with a cool new feature on its class-based views:
#view_defaults(route_name='name', permission='perm')
class R(object):
def __init__(self, request):
self.request = request
self.items = # ...
#view_config(xhr=True, renderer='json')
def ajax(request):
return {
'data1': 1, 'data2': 2, 'data3': 3,
'html': renderers.render('shortr.mako', {'items': items}, request),
}
#view_config(renderer='r.mako')
def html(request):
return self.items
I'm not familiar with pyramid or what 'r.mako' is referencing, but you could probably hook in to the request before the controller is called with a custom function that inspects the Accepts headers of the request, looking for 'text/javascript' or 'application/json' as the foremost Accepts, and then set a flag on the request object (or have that method factored out for use in your r function).
Then do a custom renderer to handle either parsing with mako or dumping a json string
# pseudo-code
def before_controller(request, response):
if 'text/html' in request.headers.accepts:
request.is_json = False
elif 'application/json' in request.headers.accepts:
response.headers.set('Content-type', 'application/json')
request.is_json = True
# pseudo-code
def my_renderer(request, response, result):
if 'text/html' in request.headers.accepts:
return # render html
elif 'application/json' in request.headers.accepts:
response.headers.set('Content-type', 'application/json')
return json.dumps(result)
#
def r(request):
items = get_items()
if request.json:
pass # update items with additional data
return {'items': items} # one point of return
The method would also mean no extra leg-work if you dont need to do any additional processing on items, you simply return the result as normal and receive the json object on the other end.
If you cant hook into pyramid before the controller is called, you could write a convenience function to call is_json(request) and use that in the controller and the renderer for determining output and setting content-type header
Related
I have a problem with django-pytest
I'm using, djnago-rest-framework
There is a problem testing the details. As shown in the code below, I entered the same details, detail1, detail2, and detail3 codes. However, only detail1 succeeds and detail2, detail3 indicates that '/api/v1/stats/1/' could not be found. It also occurs when implementing delete. I am curious about the cause and solution of this error.
enter image description here
// tests/test_apis.py
import json
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from stats.models import Stats
class StatsApiTests(APITestCase):
def setUp(self):
Stats.objects.get_or_create(blockshots=1, memo='test1')
Stats.objects.get_or_create(blockshots=2, memo='test2')
self.create_read_url = reverse('api:stats:stats-list')
self.read_update_delete_url = reverse('api:stats:stats-detail', kwargs={'pk': '1'})
def test_detail1(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {
'blockshots': 1,
'memo': 'test1',
}
self.assertEqual(data, content)
def test_detail2(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {
'blockshots': 1,
'memo': 'test1',
}
self.assertEqual(data, content)
def test_detail3(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {
'blockshots': 1,
'memo': 'test1',
}
self.assertEqual(data, content)
def test_list(self):
response = self.client.get(self.create_read_url)
self.assertContains(response, 'test1')
self.assertContains(response, 'test2')
Its hard to know what your actual implementation for read_update_delete_url, hence I assume it is looking up the resource by primary key. In that case, you can simply add the primary key in the url like this:
stat_one, _ = Stats.objects.get_or_create(blockshots=1, memo='test1')
stat_two, _ = Stats.objects.get_or_create(blockshots=2, memo='test2')
self.read_update_delete_url = reverse('api:stats:stats-detail', kwargs={'pk': stat_one.pk})
Basically, get_or_create returns the object and the state of the object (created or not). You can use the object's id as the parameter of reverse function.
Am testing an endpoint that retrieves data using a ModelViewSet, and am passing a param via a URL to it to get data but am getting this error when I run the unit tests:
File "/Users/lutaayaidris/Documents/workspace/project_sample/project_sample/financing_settings/tests.py", line 195, in test_get_blocks
self.block_get_data), content_type='application/json')
File "/Users/lutaayaidris/Documents/workspace/project_sample/lib/python3.6/site-packages/rest_framework/test.py", line 286, in get
response = super().get(path, data=data, **extra)
File "/Users/lutaayaidris/Documents/workspace/project_sample/lib/python3.6/site-packages/rest_framework/test.py", line 194, in get
'QUERY_STRING': urlencode(data or {}, doseq=True),
File "/Users/lutaayaidris/Documents/workspace/project_sample/lib/python3.6/site-packages/django/utils/http.py", line 93, in urlencode
for key, value in query:
ValueError: not enough values to unpack (expected 2, got 1)
This is how I have structured my tests , plus some dummy data for testing :
class TemplateData:
"""Template Mock data."""
step_get_data = {
"param": "step"
}
block_get_data = {
"param": "block"
}
get_no_data = {
"param_": "block"
}
class TemplateViewTests(TestCase, TemplateData):
"""Template Tests (Block & Step)."""
def setUp(self):
"""
Initialize client, Step and Block id and data created.
"""
self.client = APIClient()
self.block_id = 0
self.step_id = 0
self.create_block_step_data()
def create_block_step_data(self):
"""Create ProcessVersion, Step, & Block mock data."""
self.process_version = ProcessVersion.objects.create(
tag="TESTING_TAG",
is_process_template=False,
status="IN EDITING",
attr_map="TESTING_ATTR",
loan_options=None
)
self.step = Step.objects.create(
version=self.process_version,
is_process_template=True,
title="TESTING",
help_text="TESTING",
order=1,
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
)
self.step_id = self.step.pk
self.block_id = Block.objects.create(
step=self.step,
is_process_template=True,
title="TESTING",
information_text="This is testing "
"information",
order=1,
depending_field="depending_field",
visibility_value="visibility_value",
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
).pk
self.process_version_1 = ProcessVersion.objects.create(
tag="TESTING_TAG",
is_process_template=False,
status="IN EDITING",
attr_map="TESTING_ATTR",
loan_options=None
)
self.step_1 = Step.objects.create(
version=self.process_version_1,
is_process_template=True,
title="TESTING",
help_text="TESTING",
order=1,
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
)
self.block_1 = Block.objects.create(
step=self.step,
is_process_template=True,
title="TESTING",
information_text="This is testing "
"information",
order=1,
depending_field="depending_field",
visibility_value="visibility_value",
slug="slug",
can_be_duplicated=False,
max_duplicated_number=2,
).pk
def test_get_blocks(self):
"""Test get list of Block. """
response = self.client.get(
"/api/v1/financing-settings/template/",
data=json.dumps(
self.block_get_data), content_type='application/json')
self.assertEqual(response.status_code, 200)
def test_get_steps(self):
"""Test get list of Step. """
response = self.client.get(
"/api/v1/financing-settings/template/",
data=json.dumps(
self.block_get_data),
content_type='application/json')
self.assertEqual(response.status_code, 200)
def test_no_step_or_block(self):
"""Test get no list of Step or Block. """
response = self.client.get(
"/api/v1/financing-settings/template/",
data=json.dumps(
self.block_get_data),
content_type='application/json')
self.assertEqual(response.status_code, 204)
As you can see above those are my tests, I have already setup the data , now I want to retrieve back the data, but because of the exception above I can't.
Lastly, in my endpoint implementation, I used a Viewset to handle this , below is the code :
class TemplateView(ModelViewSet):
"""ViewSet for Saving Block/ Step template."""
def list(self, request, *args, **kwargs):
"""Get list of Block/Steps with is_process_template is equal to True."""
param = request.data['param']
if param == "block":
_block = Block.objects.filter(is_process_template=True).values()
return JsonResponse({"data": list(_block)}, safe=False, status=200)
elif param == "step":
_step = Step.objects.filter(is_process_template=True)
return JsonResponse({"data": list(_step)}, safe=False, status=200)
return Response(status=status.HTTP_204_NO_CONTENT)
What is causing this , in my understanding I feel like everything should work.
The function Client.get expect a dictionary as data argument and try to encode it in the url using the function urlencode. You could do something like that:
from django.test import Client
c = Client()
block_get_data = {
"param": "block"
}
c.get('path', block_get_data)
block_get_data will be sent in the url as 'param=block'
If you want to send JSON formated data in a GET method, you can use Client.generic function as follow:
from django.test import Client
import json
c = Client()
block_get_data = {
"param": "block"
}
c.generic('GET', 'path', json.dumps(block_get_data), 'application/json')
You are facing this error because this dict
block_get_data = {
"param": "block"
}
you are trying to use it in this way
for key,val in block_get_data
and it will produce the error like
for key,val in block_get_data:
ValueError: too many values to unpack (expected 2)
It will be solved if your loop through dict by using .items() method.
for key,val in block_get_data.items():
I think by passing parameter as self.block_get_data.items() may solve your problem.
this is the call back URL which am getting from the payment gateway I am integrating with
http://127.0.0.1:8000/checkout/mfsuccess/?paymentId=060630091021527961&Id=060630091021527961
I want to extract the paymentId int from this URL so I can use it in my function
this is the URL line am using
path('checkout/mfsuccess/?<str:paymentId>', gateway_Success, name='mf_success'),
and this is my function
def gateway_Success(request, id):
payment_id = request.GET.get('id')
print(payment_id)
context = {
"payment_id": payment_id
}
return render(request, "carts/checkout-mf-success.html")
How I do that since the way am doing not working I am getting the following error
Not Found: /checkout/mfsuccess/
[20/Nov/2020 03:38:59] "GET /checkout/mfsuccess/?paymentId=060630091121528060&Id=060630091121528060 HTTP/1.1" 404 4378
You don't need to adjust your path() function to catch the URL query parameters
path('checkout/mfsuccess/', gateway_Success, name='mf_success'),
also, change your view as,
def gateway_Success(request):
id_ = request.GET.get('Id') # retrieving the `Id`
payment_id = request.GET.get('paymentId') # retrieving the `paymentId`
context = {
"payment_id": payment_id,
"id_": id_
}
return render(request, "carts/checkout-mf-success.html", context=context)
This is enough to catch the redirection.
I am testing my views using Django Unit testing. I am making get and post requests with params to check what status i get back.
But the problem how to check for context variables which are retuned in the response?
For example, on the View Cities page, I make a get request, the context dict in the view has the variable cities. So I want to check for context.
resp = self.client.post(
path=reverse('upload_video'),
data={"video_url": video_url, "pro": 1}
)
self.assertEqual(resp.status_code, 200)
Condition is True both ways, if the form is invalid or valid it returns 200. If I can check for context, then I can check what has been retuned from the view in response.
What I tried
=> resp.__dict__
{'templates': [], '_handler_class': None, '_headers': {'vary': ('Vary', 'Cookie'), 'content-type': ('Content-Type', 'application/json')}, '_charset': 'utf-8', '_closable_objects': [], 'cookies': <SimpleCookie: >, 'client': <django.test.client.Client object at 0x112bace10>, '_base_content_is_iter': False, 'context': None, 'request': {u'CONTENT_LENGTH': 202, u'wsgi.input': <django.test.client.FakePayload object at 0x113667990>, u'REQUEST_METHOD': 'POST', u'PATH_INFO': '/upload/video/modal/', u'CONTENT_TYPE': u'multipart/form-data; boundary=BoUnDaRyStRiNg', u'QUERY_STRING': ''}, '_container': ['{"error": {"msg": "Pro: Select a valid choice. That choice is not one of the available choices.", "head": null}}']}
Check _container has that variable. The form is invalidated, and retuned an error in the context. but when I do the following i get None
=> resp.context
None
Test
import os
from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
from django.test import TestCase
def test_video_upload(self):
""" Test that video upload is successful """
self.create_and_login(username="su", password="su", is_superuser=True)
video_urls = [
u"https://www.youtube.com/watch?v=abc",
u"https://vimeo.com/32222",
u"http://www.dailymotion.com/video/rer"
]
for video_url in video_urls:
resp = self.client.post(
path=reverse('upload_video'),
data={"video_url": video_url, "pro": 1}
)
set_trace() #Breakpoint
a = resp.context[-1] # <=== Not getting it here.
self.assertEqual(resp.status_code, 200) #passes
videos = Video.objects.all()
self.assertEqual(len(videos), 3)
View
ctx = {}
if request.method == Enums.Request.POST:
video_form = UploadVideoEasyForm(data=request.POST)
if video_form.is_valid():
video, log = video_form.save(request=request)
msg = 'Successfully Uploaded, View: here'.format(video.get_absolute_url())
ctx[Enums.ResponseAlert.Success] = {'msg': msg}
else:
ctx[Enums.ResponseAlert.Error] = make_alert(msg=form_error_to_string(video_form))
return HttpResponse(json.dumps(ctx), content_type="application/json")
elif request.method == Enums.Request.GET:
ctx['upload_video'] = UploadVideoEasyForm()
if request.user.is_authenticated() and request.user.is_superuser:
return render_to_response('new/modals/upload_video.html', context_instance=RequestContext(request, ctx))
Cheers.
The resp (An instance of django.test.Response) should have an context attribute.
You can access context value using context[..]:
self.assertEqual(resp.context['cities'], ...)
I have a problem with Django and Haystack. I'm trying to beautify the url (example.com/search/?q=hey in example/search/hey/) as follows:
def go(request):
"""
Search > Beautify
"""
search_query = request.GET.get('q', None)
return HttpResponseRedirect(reverse('search.views.root', kwargs={
'search_query': search_query,
}))
def root(request, search_query):
"""
Search > Root
"""
form = HaystackSearchForm(request.GET)
tutorials = form.search()
return render(request, 'search/search_root.html', {
'search_query' : search_query,
'tutorials' : tutorials,
})
The problem is that it doesn't work because the request for the go function is not the same than the root function. I want to find a way to pass the appropriate request to the HaystackForm (which mean with the query).