Is APITest with Query params different then just normal url? - django

I'm writing some unit tests against an API that either returns all the books, or only returns the books of the given genre in the query params. This seems to be working when I hit it in my local dev server. However, it doesn't even go into the else statement if the genre is specified in my unit test.
My unit test looks like this:
class TitlesAndBlurbsListTestCase(APITestCase):
def setUp(self):
# Creates a lot of books with genre horror
# and books not in the horror genre
def test_horror_genre(self):
# Ensure that screener can see all the available books
self.client.login(username='b', password='b')
response = self.client.get('/api/titles-and-blurbs/?genre=horror')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Ensure that the screener gets all horror books at first
horror_books = TitlesAndBlurbs.objects.filter(genre='horror')
# I keep getting an assertion error here - it returns all the books
self.assertEqual(len(response.data), horror_books.count())
My api viewset looks like this
class TitlesAndBlurbsListViewSet(viewsets.mixins.ListModelMixin,
viewsets.mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
model = TitlesAndBlurbs
permission_classes = [ScreenerPermissions]
serializer_class = TitlesAndBlurbsSerializer
def get_queryset(self):
if self.action == 'list':
genre = self.request.QUERY_PARAMS.get('genre', None)
if not genre:
print 'i dont have query params of genre'
TitlesAndBlurbs.objects.all()
else:
print genre
TitlesAndBlurbs.objects.filter(genre=genre)
return TitlesAndBlurbs.objects.all()
my url/router looks like
router.register(r'api/titles-and-blurbs', TitlesAndBlurbsListViewSet)
When I hit the url 'api/titles-and-blurbs/?genre=horror' in my browser I get the print statement and titles and blurbs that have the genre horror. However, when I hit in the
test suite, I don't get the print statement genre I get the print statement of 'i dont have query params', and it returns all books. Any help is really
appreciated.

Try passing the query parameter as a data payload instead. Change the line in your test to:
response = self.client.get('/api/titles-and-blurbs/', {'genre': 'horror'})
Django docs here on the different ways to pass query parameters in urls.
Another person reported a similar issue with an empty QUERY_PARAMS while testing DRF (see here). It looks like they fixed it but maybe they missed something or you didn't get the update.

If someone comes across this, for me it helped to:to change the url from
/api/titles-and-blurbs?genre=horror
to
/api/titles-and-blurbs/?genre=horror
Both urls were working find in Postman, but only the second one is working properly in the tests.

Related

Query set too many objects to unpack expected(2) in django templateView

I have written a view to show open, completed, accepted and closed tickets on dashboard on clicking which goes into particular url to display the tickets accordingly and am switching the templates according to their status, I am querying the ticket status and I get the following error
Too many objects to unpack (expected 2)
models.py
class Modes(models.Model):
Error is due to this query...
Ticket.objects.filter('status')
You should specify something like:
if Ticket.objects.filter(status='Opened'):
template_name = 'app/open_tickets.html')
Also please check the logic. Above query makes no sense in get_template_name... You should be getting one template according to request. So if request is for 'opened' then you should get template only for 'opened'.
Suppose if you want to get for 'opened' you need to pass it in query and do this:
if self.request.GET.get('status', '') == 'Opened':
template_name = 'app/open_tickets.html')
def show(request,status):
if status.title() in ['O','A,'C','Cl']:
if status.title() == 'O':
models = Model.objects.filter(status='O')
else:
models = Model.objects.filter(status=status.title(),accepted_by=request.user)
templates = {'O':'p/op.html','A':'p/d.html','C':'p/pp.html','Cl:'a.html'}
return render(request,templates[status.title()],{'tickets':tickets})
inside your urls.py
path('models/<str:status>/',views.show,name='show')

Django unit tests - API calls slowing down testing greatly, optimize or rewrite?

I have an application that calls to OpenWeatherMap API and I decided to write a test for the purpose of verifying that the cities are listed on the url page context.
I used the setUpTestData class method to populate the test DB, but when I run the tests the time taken goes from 0.0XX seconds without the test DB to like 5.7XX seconds with the test DB.
I believe its the API calls to populate the DB that is causing the slow down (Which I assume also counts against my limit for calls per hour on the API key) and I am wondering what the proper way would be to test something like this? I.E something that relies on API data to work properly.
Do I need to optimize my code, or is my approach wrong? I am relatively new to unit testing, so I appreciate any tips or advice you may have.
views.py
class WeatherHomeView(TemplateView):
template_name='weather/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
url = 'API URL'
cities = City.objects.all()
weather_data = []
for city in cities:
city_weather = requests.get(url.format(city)).json()
weather = {
'city' : city,
'temperature' : city_weather['main']['temp'],
'description' : city_weather['weather'][0]['description'],
'icon' : city_weather['weather'][0]['icon'],
}
weather_data.append(weather)
context = {'weather_data' : weather_data}
return context
test_views.py
class WeatherViewTests(TestCase):
#classmethod
def setUpTestData(cls):
City.objects.create(
name='denver'
)
City.objects.create(
name='honolulu'
)
City.objects.create(
name='tokyo'
)
...
def test_home_shows_all_cities(self):
'''Tests to verify all cities in the DB (3) are listed in the home view.'''
cities = City.objects.all()
response = self.client.get(reverse('weather:weather-home'))
self.assertTrue(len(response.context['weather_data']) == 3)

model_mommy - user to assignment relationship

I'm finally setting up testing for my Django app, but I'm having difficulties getting started. I'm using model_mommy to create dynamic data for my tests, but have the following problem:
The view I'm testing is supposed to show me all the assignments a particular user has to complete. To test this, I want to create 500 assignments, log into the app and check if they are shown. So far I have the following test cases:
class TestLogin(TestCase):
def setUp(self):
self.client = Client()
user = User.objects.create(username='sam')
user.set_password('samspassword')
user.save()
def test_login(self):
self.client.login(username='sam', password='samspassword')
response = self.client.get('/')
print (response.content)
self.assertEqual(response.status_code, 200)
and
class TestShowAssignments(TestCase):
def setUp(self):
user_recipe = Recipe(User, username='sam', password="samspassword")
self.assignment = Recipe(Assignment,
coders = related(user_recipe))
self.assignments = self.assignment.make(_quantity=500)
def test_assignments(self):
self.assertIsInstance(self.assignments[0],Assignment)
self.assertEqual(len(self.assignments),500)
The first test passes fine and does what it should: TestLogin logs the user in and shows his account page.
The trouble starts with TestShowAssignments, which creates 500 assignments but if I look at the assignments with print (self.assignments[0].coders), I get auth.User.None. So it doesn't add the user I defined as a relation to the assignments. What might be important here is that the coders field in the model is a m2m field, which I tried to address by using related, but that doesn't seem to work.
What also doesn't work is logging in: if I use the same code I use for logging in during TestLogin in TestShowAssignments, I can't log in and see the user page.
So, my question: How do I use model_mommy to create Assignments and add them to a specific user, so that I can log in as that user and see if the assignments are displayed properly?
Do you want 500 Assignments that all have User "sam" as a single entry in the 'coders' field? If so, try:
from model_mommy import mommy
...
class TestShowAssignments(TestCase):
def setUp(self):
self.user = mommy.make(User, username='sam', password='samspassword')
self.assignments = mommy.make(Assigment, coders=[self.user], _quantity=500)

django-tastypie How to PUT a datetime field with "now" value

I've set up a RESTfull interface with django-tastypie.
All is going well so far, BUT i can't find a way to POST/PUT/PATCH a datetime field to NOW (as in SQL) to use server's current time, instead of the client one.
Maybe im doing it wrong, I have a resource with some fields and I want users to be able to validate or unvalidate it. So i've added "validated_at" in my model. Sending string "2012-01-01T15:43:00" works, but if I set to "Now", an error is returned because the date is not correctly formatted.
Is there a way to do that? Thanks!
Not the best code i wrote so far... but it does the trick.
def hydrate_validate_at(self, bundle):
validate_at = bundle.data.get("validate_at", None)
if validate_at == "None":
bundle.data["validate_at"] = None
elif validate_at == "Now":
bundle.data["validate_at"] = datetime.now()
return bundle

How can I write tests that populates raw_post_data and request.FILES['myfile']

I have something like this:
def upload_something(request):
data = {}
if request.FILES:
raw_file = request.FILES['myfile'].read()
else:
raw_file = request.raw_post_data
I can't seem to be able to write a unit-test that populates raw_post_data, how would I go about doing that? I basically just want to send an image file. I'm trying to create a test case for when I read raw_post_data and it errors with:
You cannot access raw_post_data after reading from request's data stream
I'm assuming you have figured this out by now, but as the answers are almost out of date with the deprecation of raw_post_data I thought i'd post.
def test_xml_payload(self):
data = '<?xml version="1.0" encoding="UTF-8"?><blah></blah>'
response = self.client.post(reverse('my_url'),
data=data,
content_type='application/xml')
def my_view(request):
xml = request.body
You can use mocking. Some examples available here and in docs here
Updated
Kit, I think it's very depends on your test case. But in general you shouldn't use raw_post_data directly. Instead it's have to be patched like in example below:
from mock import Mock, MagicMock
class SomeTestCase(TestCase):
def testRawPostData(self):
...
request = Mock(spec=request)
request.raw_post_data = 'myrawdata'
print request.raw_post_data # prints 'myrawdata'
file_mock = MagicMock(spec=file)
file_mock.read.return_value = 'myfiledata'
request.FILES = {'myfile': file_mock}
print request.FILES['myfile'].read() # prints 'myfiledata'
The error message the interpreter is giving is correct. After you access the POST data via if request.FILES, you can no longer access the raw_post_data. If in your actual code (not the tests) you hit that line, it would error with the same message. Basically, you need two separate views for form-based POSTS and direct file POSTS.
I took this listing here
c = Client()
f = open('wishlist.doc')
c.post('/customers/wishes/', {'name': 'fred', 'attachment': f})
f.close()
Client is a special class for testing your views. This is the example of posting files to your view. It's part of Django testing framework.