I solved the problem while writing this question but I wanted to post it so maybe someone needs this answer
Hello my friends.
i am new to django testing.
while i'm testing my views i faced this error in some views.
This is my views.py:
def all_programs(request):
programs = Program.objects.all()
return render(request, 'orders/all_programs.html', {'programs': programs})
def checkout(request, slug):
if request.method == 'POST':
# get data from form and save it
program = get_object_or_404(Program, slug=slug)
dates = ProgramDate.objects.filter(program=program)
return render(request, 'orders/checkout.html', {'program': program, 'dates': dates})
This is urls.py:
from django.urls import path
from django.views.generic import RedirectView
from .views import *
app_name = 'orders'
urlpatterns = [
path('', RedirectView.as_view(url='https://www.another-website.net')),
path('tests/', all_programs, name='all_programs'),
path('checkout/<str:slug>/', checkout, name='checkout'),
path('checkout/return_page/', ReturnPage.as_view(), name='return_page'),
]
And this is test_views.py:
from django.test import TestCase
from django.shortcuts import reverse
class TestViews(TestCase):
def test_all_programs(self):
response = self.client.get(reverse('orders:all_programs'))
self.assertTemplateUsed(response, 'orders/all_programs.html')
def test_checkout(self): # error id here
response = self.client.get(reverse('orders:all_programs', kwargs={'slug': 'test'})) # I tried this
# response = self.client.get('http://127.0.0.1:8000/checkout/test/') #and this
self.assertTemplateUsed(response, 'orders/checkout.html')
The solution in this case is:
The test in Django does not use the default database but rather creates its own database that does not have any records (I completely forgot that), so you must create records before you start tests that relate to the database.
in this case i must create new program before test:
class TestViews(TestCase):
_program = {
'name': 'test_program',
'price': 1000,
'abbreviation': 'test',
'description': 'test_program',
'slug': 'test_program',
'id_from_zoho': 1000,
}
def test_checkout(self):
program = Program(**self._program)
program.save()
response = self.client.get(reverse('orders:checkout', kwargs={'slug': program.slug}))
self.assertTemplateUsed(response, 'orders/checkout.html')
Related
I am trying to build a simple REST API.
I tried adding a viewset, somehow I get an error that there is no such attribute.
If I remove the viewset and just run using the APIView, it loads just fine. I am stuck. What could be the problem? What should I do to make it work?
Here's the rest_profiles.views.py FILE:
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import HelloSerializer
# Create your views here.
class HelloApiView(APIView):
'''Test API View'''
serializer_class = HelloSerializer
def get(self, request, format=None):
'''Returns a list of API features'''
an_apiview = [
'Uses HTTP methods as functions (get, psot, put, patch, delete)',
'Similar to Django View',
'Mapped manually to URLs'
]
return Response({'message': 'Hello from HelloAPIVIew', 'an_apiview': an_apiview})
def post(self, request):
'''Create Hello Message'''
serializer = HelloSerializer(data=request.data)
if serializer.is_valid():
name = serializer.data.get('name')
message = 'Hello {0}'.format(name)
return Response({'message': message})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def put(self, request):
'''Handles Updates'''
serializer = HelloSerializer(data=request.data)
return Response({'message': 'put'})
def patch(self, request, pk=None):
'''Handles partial Updates'''
serializer = HelloSerializer(data=request.data)
return Response({'message': 'patch'})
def delete(self, request, pk=None):
'''Handles deleting items'''
serializer = HelloSerializer(data=request.data)
return Response({'message': 'delete'})
class HelloViewSet(viewsets.ViewSet):
'''Test API Viewset'''
def list(self, request):
'''Return Hello Message'''
a_viewset = [
'Uses Actions (list, create, retrieve, update, partial_update)',
'automatically maps to URLs using Router',
'More functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
and Here is the urls.py file:
from django.conf.urls import url
from django.conf.urls import include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register('hello-viewset', views.HelloViewSet,
base_name='hello-viewset')
urlpatterns = [
url('hello-view/', views.HelloApiView.as_view()),
url('', include(router.urls))
]
What could possibly be the problem? And the possible work-around?
If your code is pasted correctly, it looks like your HelloViewSet is inside your HelloApiView.
Indentation is important in Python. You need to unindent this code:
class HelloViewSet(viewsets.ViewSet):
'''Test API Viewset'''
def list(self, request):
'''Return Hello Message'''
a_viewset = [
'Uses Actions (list, create, retrieve, update, partial_update)',
'automatically maps to URLs using Router',
'More functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
This will put it directly in your views module so it can be imported.
I forked customer app , to add a tab in http://oscar/accounts/...(example products)
to edit/show catalogue Views (Dashboard>Catalogue)
Error that I get to use that view is
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
I used views with the same method for payment checkout, but this one runs into errors.
# yourappsfolder.customer.apps.py
import oscar.apps.customer.apps as apps
from oscar.apps.dashboard.catalogue import apps as dapps
from django.views import generic
from django.conf.urls import url
from oscar.core.loading import get_class
from .views import ProductListView
class CustomerConfig(apps.CustomerConfig):
name = 'yourappsfolder.customer'
def ready(self):
super().ready()
self.extra_view =ProductListView
def get_urls(self):
urls = super().get_urls()
urls += [
url(r'products/',self.extra_view.as_view(),name='Products'),
]
return self.post_process_urls(urls)
This is the view I copied from oscar.apps.dashboard.catalogue
# yourappsfolder.customer.views
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
from django.views import generic
from oscar.apps.dashboard.catalogue.views import ProductListView as UserProductListView
class ProductListView(UserProductListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['form'] = self.form
ctx['productclass_form'] = self.productclass_form_class()
return ctx
def get_description(self, form):
if form.is_valid() and any(form.cleaned_data.values()):
return _('Product search results')
return _('Products')
def get_table(self, **kwargs):
if 'recently_edited' in self.request.GET:
kwargs.update(dict(orderable=False))
table = super().get_table(**kwargs)
table.caption = self.get_description(self.form)
return table
def get_table_pagination(self, table):
return dict(per_page=20)
def filter_queryset(self, queryset):
"""
Apply any filters to restrict the products that appear on the list
"""
return filter_products(queryset, self.request.user)
def get_queryset(self):
"""
Build the queryset for this list
"""
queryset = Product.objects.browsable_dashboard().base_queryset()
queryset = self.filter_queryset(queryset)
queryset = self.apply_search(queryset)
return queryset
def apply_search(self, queryset):
"""
Search through the filtered queryset.
We must make sure that we don't return search results that the user is not allowed
to see (see filter_queryset).
"""
self.form = self.form_class(self.request.GET)
if not self.form.is_valid():
return queryset
data = self.form.cleaned_data
if data.get('upc'):
# Filter the queryset by upc
# For usability reasons, we first look at exact matches and only return
# them if there are any. Otherwise we return all results
# that contain the UPC.
# Look up all matches (child products, products not allowed to access) ...
matches_upc = Product.objects.filter(upc__iexact=data['upc'])
# ... and use that to pick all standalone or parent products that the user is
# allowed to access.
qs_match = queryset.filter(
Q(id__in=matches_upc.values('id')) | Q(id__in=matches_upc.values('parent_id')))
if qs_match.exists():
# If there's a direct UPC match, return just that.
queryset = qs_match
else:
# No direct UPC match. Let's try the same with an icontains search.
matches_upc = Product.objects.filter(upc__icontains=data['upc'])
queryset = queryset.filter(
Q(id__in=matches_upc.values('id')) | Q(id__in=matches_upc.values('parent_id')))
if data.get('title'):
queryset = queryset.filter(title__icontains=data['title'])
return queryset
You have a circular import - move the import of the list view into the ready() method of the app config:
class CustomerConfig(apps.CustomerConfig):
name = 'yourappsfolder.customer'
def ready(self):
super().ready()
from .views import ProductListView
self.extra_view =ProductListView
I am trying to write a test for the get_success_url method in a CreateView, to make sure it redirects to the newly created page. But the response status code is 405 instead of 302 as I expected.
views.py
class BlogCreate(CreateView):
model = Blog
fields = [‘author’, 'title', ’post’]
def get_success_url(self):
return reverse_lazy('blog:blog-detail', kwargs={'slug': self.object.slug})
class BlogList(ListView):
model = Blog
ordering = ["-created"]
class BlogDetail(DetailView):
model = Blog
config/urls.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^blog/', include('blog.url', namespace='blog')),
blog/urls.py
from django.conf.urls import url
from .views import BlogCreate, BlogList, BlogDetail, BlogEdit, BlogDelete
urlpatterns = [
url(r'^(?P<slug>[-\w]+)/$', BlogDetail.as_view(), name='blog-detail'),
url(r'^(?P<slug>[-\w]+)/edit$', BlogEdit.as_view(), name='blog-edit'),
url(r'^(?P<slug>[-\w]+)/delete$', BlogDelete.as_view(), name='blog-delete'),
url(r'^new$', BlogCreate.as_view(), name='blog-create'),
url(r'^$', BlogList.as_view(), name='blog-list'),
]
tests.py
class BlogCreateTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='john', password='123')
def test_create_success_url(self):
post = {‘author’: self.user,
'title': ‘new blog’,
‘article’: ‘text’,
}
response = self.client.post('/blog/new/', post)
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, 'blog/new-blog/‘)
Are you trying to do a client.post() in your tests?
Remove the trailing slash from /blog/new/
I am using Django 1.8.4 and DRF 3.2.1 and when I run requests against the specified urls everything works fine, but when I run the tests with py.test the function update doesn't enter with the task_id param. But the url is fine.
Attached some code from urls.py and views.py and tests.py .. this is an excerpt of the code of course a lot of stuff is missing I just need eyes who can see if I am doing something wrong.
urls.py
from django.conf import settings
from django.conf.urls import include, patterns, url
from rest_framework import routers
from remotetask import views as rt_views
remotetask_detail = rt_views.RemoteTaskViewSet.as_view({'list': 'detail',
'put': 'update'})
remotetask_all = rt_views.RemoteTaskViewSet.as_view({'list': 'list'})
urlpatterns = patterns(
'',
url(r'^remotetasks/$', remotetask_all, name='api-remotetask-all'),
url(r'^remotetasks/(?P<task_id>\d+)/$', remotetask_detail,
name='api-remotetask-detail'),
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
)
views.py
from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response
from remotetask.models import RemoteTask
from remotetask.serializers import RemoteTaskSerializer
from rest_framework import viewsets
class RemoteTaskViewSet(viewsets.ViewSet):
queryset = RemoteTask.objects.all()
serializer_class = RemoteTaskSerializer
def detail(self, request, task_id=None):
task = get_object_or_404(RemoteTask, pk=task_id)
serializer = RemoteTaskSerializer(task)
return Response(serializer.data)
def update(self, request, task_id=None):
task = get_object_or_404(RemoteTask, pk=task_id)
new_status = request.data.get('status')
status_changed = task.change_status(new_status, stdout, stderr)
if status_changed:
response_status = status.HTTP_201_CREATED
else:
response_status = status.HTTP_400_BAD_REQUEST
serializer = RemoteTaskSerializer(task)
return Response(serializer.data, status=response_status)
And finally test_views.py
import pytest
from django.core.urlresolvers import reverse
from remotetask.factories import RemoteTaskFactory
from remotetask.models import RemoteTask
from remotetask.views import RemoteTaskViewSet
import json
#pytest.fixture()
#pytest.mark.django_db
def create_remotetask():
remotetask = RemoteTaskFactory.create()
return remotetask
#pytest.fixture()
#pytest.mark.django_db()
def clean_remotetask():
RemoteTask.objects.all().delete()
#pytest.fixture()
def rq_remotetasklist(rf):
url = reverse('api-remotetask-all')
request = rf.get(url)
response = RemoteTaskViewSet.as_view({'list': 'list'})(request)
return response
#pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
#pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, rf):
response = rq_remotetasklist
result = response.data.get('results')
id_to_work = result[0]['id']
rt = RemoteTask.objects.get(pk=id_to_work)
assert rt.status == 0
# new request
url = reverse('api-remotetask-detail', kwargs={'task_id':id_to_work})
params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})
request = rf.put(url, data=params,
content_type='application/json')
new_response = RemoteTaskViewSet.as_view({'put': 'update'})(request)
assert new_response.status_code == 200
By default when a new task is created it gets status 0, so I try to change the status to 2 and it fails, doing some debugging I found that is entering on the update function on RemoteTaskViewSet but is not getting the task_id.
I've followed lots of tutorials and changed back and forth the code and still having the same issue, luckily works in production but it worries to me that I cannot make it run test cases from this code.
The error output from py.test is this:
E assert 404 == 200
E + where 404 = <rest_framework.response.Response object at 0x7f9f465ae690>.status_code
I put a debugger into the update function, seems that task_id is None, but when I print request.stream the url is /api/remotetasks/1/ 1 should be the task_id but isn't getting it, I was about to open a ticket on djangoproject but I think isn't a django bug since it works with external client, this must be something on my code, or anything else.
Update: If I use client instead rf and comment the line where I assign new_response with the call of the method, and validate directly the against request.status_code it works!!!.
Something like this:
#pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
#pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, client):
response = rq_remotetasklist
result = response.data.get('results')
id_to_work = result[0]['id']
rt = RemoteTask.objects.get(pk=id_to_work)
assert rt.status == 0
# new request
url = reverse('api-remotetask-detail', kwargs={'task_id': id_to_work})
params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})
request = client.put(url, data=params,
content_type='application/json')
assert request.status_code == 201
Now the doubt is why it doesn't work in the previous way?
The issue (as noted in the updated) is in the request assignment:
request = rf.put(url, data=params,
content_type='application/json')
new_response = RemoteTaskViewSet.as_view({'put': 'update'})(request)
assert new_response.status_code == 200
There really is no need to do the custom call to the view. That's already being done by the request assignment. It's not working in the old test because that's not how the view gets called when it's routed through the url routes.
Most importantly in the pervious code the request object is not a request, it's the response to the call. Using the old code I believe this would have worked as well:
#pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
#pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, rf):
response = rq_remotetasklist
result = response.data.get('results')
id_to_work = result[0]['id']
rt = RemoteTask.objects.get(pk=id_to_work)
assert rt.status == 0
# new request
url = reverse('api-remotetask-detail', kwargs={'task_id':id_to_work})
params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})
response = rf.put(url, data=params,
content_type='application/json')
assert response.status_code == 200
models.py
#!/usr/bin/env python
from django.db import models
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import os
import json
import sys
import tempfile
import traceback
import re
import psycopg2
SUCCESS = 1
ERR_BAD_CREDENTIALS = -1
ERR_USER_EXISTS = -2
ERR_BAD_USERNAME = -3
ERR_BAD_PASSWORD = -4
class UsersModel(models.Model):
user = models.CharField(primary_key=True, max_length=128)
password = models.CharField(max_length=128)
count = models.IntegerField()
def login(user1, password1):
try:
con = psycopg2.connect(database='test', user='jeffrey')
cur = con.cursor()
cur.execute('SELECT user FROM UsersModel WHERE user1=user AND password1=password;')
rows = cur.fetchall()
print rows
if len(rows) == 1:
#on success update the count and return the count
cur.execute('UPDATE UsersModel SET count=count+1 WHERE user1=user AND password1=password;')
cur.execute('SELECT count FROM UsersModel WHERE user1=user AND password1=password;')
return cur.fetchone()[0]
else:
return ERR_BAD_CREDENTIALS
except psycopg2.DatabaseError, e:
print 'Error %s' % e
sys.exit(1)
def add(user1, password1):
try:
if user1=='' or len(user1)>128:
return ERR_BAD_USERNAME
elif len(password1)>128:
return ERR_BAD_PASSWORD
con = psycopg2.connect(database='test', user='jeffrey')
cur = con.cursor()
cur.execute('SELECT user FROM login_UsersModel WHERE user1=user;')
rows = cur.fetchall()
if len(row) == 1:
return ERR_USER_EXIST
else:
cur.execute('INSERT INTO login_UsersModel VALUES (user1, password1, 1);')
cur.execute('SELECT count FROM login_UsersModel WHERE user1=user AND password1=password;')
return cur.fetchone()[0]
except psycopg2.DatabaseError, e:
print 'Error %s' % e
sys.exit(1)
def TESTAPI_resetFixture(request):
con = psycopg2.connect(database='test', user='jeffrey')
cur = con.cursor()
cur.execute('DELETE FROM login_UsersModel')
return SUCCESS
views.py
from django.shortcuts import render
from django.http import HttpResponse
from login.models import UsersModel
from django.utils import simplejson
import os
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def loginx(request):
data = simplejson.loads(request.body)
name = data['user']
pw = data['password']
x = UsersModel()
code = x.login(name, pw)
if code > 0:
response_data = {'errCode':1, 'count':code}
return HttpResponse(simplejson.dumps(response_data), content_type="application/json")
else:
response_data = {'errCode':code}
return HttpResponse(simplejson.dumps(response_data), content_type="application/json")
#csrf_exempt
def addx(request):
data = simplejson.loads(request.body)
name = data['user']
pw = data['password']
x = UsersModel()
code = x.add(name, pw)
if code > 0:
response_data = {'errCode':1, 'count':code}
return HttpResponse(simplejson.dumps(response_data), content_type="application/json")
else:
response_data = {'errCode':code}
return HttpResponse(simplejson.dumps(response_data), content_type="application/json")
#csrf_exempt
def TESTAPI_resetFixturex(request):
x = UsersModel()
code = x.TESTAPI_resetFixture()
response_data = {'errCode': code}
return HttpResponse(simplejson.dumps(response_data), content_type="application/json")
#csrf_exempt
def unitTests(request):
os.system("make func_tests TEST_SERVER=localhost:8000")
return
url.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from login.views import loginx
from login.views import addx
from login.views import TESTAPI_resetFixturex
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'mysite.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
#url(r'^admin/', include(admin.site.urls)),rs
url(r'^users/login', loginx),
url(r'^users/add', addx),
url(r'^TESTAPI/resetFixture', TESTAPI_resetFixturex)
#url(r'^TESTAPI/unitTests', unitTests)
)
ive seen similar questions been asked before on stackoverflow, but their solutions were not able to help me and lead me to other errors. so yea i dont understand how 3 arguments can be given to add when i only put users and password as my argument in view. can someone help me. btw i am only testing the add function right now in view using:
curl -H "Content-Type: application/json" -H "Accept: application/json" -X POST -d '{"user":"test", "password":"test"}' localhost:8000/users/add -o test.html
The instance (conventionally referenced by self) is passed to each instance method of a class. This question explains some more about what self is and repreesnts. In addition there are a great number of high quality oop python tutorials.
I am assuming add is method of UsersModel (if it is please correct indentation, as it matters immensely`)
in which case your function definition should look like:
def add(self, user1, password1):
Additionally, i think it would be a great help to do the django tutorial. Django has created an ORM to allow you to interact with a database without writing any sql!:) It is extremely easy to use. Also django has a built in User model that provides authentication and user creation out of the box