Querying the database in a Django unit test - python

I am creating a web application which has a POST endpoint, that does two things:
Saves the POSTed data (a university review) in the database.
Redirects the user to an overview page.
Here is the code for it:
if request.method == 'POST':
review = Review(university=university,
user=User.objects.get(pk=1),
summary=request.POST['summary'])
review.save()
return HttpResponseRedirect(reverse('university_overview', args=(university_id,)))
I haven't yet implemented passing the user data to the endpoint, and that's why I'm saving everything under the user with pk=1.
My test is as follows:
class UniversityAddReviewTestCase(TestCase):
def setUp(self):
user = User.objects.create(username="username", password="password", email="email")
university = University.objects.create(name="Oxford University", country="UK", info="Meh", rating="9")
Review.objects.create(university=university, summary="Very nice", user_id=user.id)
Review.objects.create(university=university, summary="Very bad", user_id=user.id)
new_review = {
'summary': 'It was okay.'
}
self.response = Client().post('/%s/reviews/add' % university.id, new_review)
def test_database_updated(self):
self.assertEqual(len(Review.objects.all()), 3)
The result is this:
File ".../core/views.py", line 20, in detail
user=User.objects.get(pk=1),
File ".../ENV/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File ".../ENV/lib/python3.6/site-packages/django/db/models/query.py", line 403, in get
self.model._meta.object_name
django.contrib.auth.models.DoesNotExist: User matching query does not exist.
Why is this happening? I know the user I'm creating has a pk=1, as when I actually print it during the test it's 1.

pk is defined by the database. Something could make it not equal to 1 in your test.
Try this in your setUp method
user = User.objects.create_user(
username="username",
password="password",
email="test#example.com",
id=1
)
assert user.pk == 1

Take advantage of using self and then you can try this instead:
class UniversityAddReviewTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(
username="username",
password="password",
email="email")
....
and then
if request.method == 'POST':
review = Review(
university=university,
user=self.user,
summary=request.POST['summary'])
review.save()

I could make it work, with the following lines:
import unittest # instead of (from django.test import TestCase)
class NameTest(unittest.TestCase):
#lines to test your code here...

Related

With Flask-Admin and Flask how can I submit a form\view based on ModelView from code?

With Flask-Admin and Flask how can I submit a form\view based on ModelView from code?
I'm trying to create a separate view\form that would allow user to add multiple entries with one form. Specifically allowing to upload multiple images with common prefix name and common parameters. I'd like to do it by submitting a single-image upload form from code, because it does some additional processing like resizing images and I'd like to let Flask-Admin handle connecting database entries and files.
Here's the form I'd like to submit from code:
class ImageView(ModelView):
def _list_thumbnail(view, context, model, name):
if not model.path:
return ''
return Markup('<img src="%s">' % url_for('media',
filename=form.thumbgen_filename(model.path)))
column_labels = dict(show_in_header="Show In Header?",
path="Image")
form_create_rules = ("name",
"tags",
rules.Text(
"Use this image as header. If more than one image is selected header image will be random each time the page is loaded."),
"show_in_header",
"path")
form_excluded_columns = ("timestamp")
column_formatters = {
'path': _list_thumbnail
}
thumbnail_size = config("media", "thumbnail_size")
form_extra_fields = {
'path': BroImageUploadField('Image',
base_path=IMAGES_FOLDER,
thumbnail_size=(thumbnail_size, thumbnail_size, True),
endpoint="media",
url_relative_path='media',
relative_path=None)
}
def is_accessible(self):
return current_user.is_authenticated
def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
return redirect(url_for('login', next=request.url))
And here I'm creating a form and I'm just not sure that .process() is the function to submit it? Is there one at all?
lass MultipleImagesUploadView(BaseView):
#expose("/", methods=["GET", "POST"])
def index(self):
if request.method == "POST":
a_form = MultipleImagesForm(request.form)
base_name = a_form.base_name.data
tags = a_form.tags.data
show_in_header = a_form.show_in_header.data
print (request.files.getlist(a_form.images.name))
uploaded_files = request.files.getlist(a_form.images.name)
for i, uf in enumerate(uploaded_files):
try:
name, ext = os.path.splitext(uf.filename)
if ext not in IMAGE_EXTENSIONS:
flash("Image file {} was skipped as it's extension is not supported ({}).".format(uf.filename, ext), category="warning")
continue
image_contents = uf.stream.read()
image_form = ImageView()
image_form.name.data = "{}_{}".format(base_name, i)
image_form.tags.data = tags
image_form.show_in_header.data = show_in_header
image_form.path.data = image_contents
image_form.process()
except Exception as e:
flash ("Unhandled exception: {}".format(e), category="warning")
flash("Images were added to the gallery.", category='success')
a_form = MultipleImagesForm()
print("############", a_form)
return self.render('/admin/multiple_images_upload.html', form=a_form)
I can't figure out a way to submit a form from code, been trying to find the answer in docs and google for hours now with no luck.
Found the issue. In my case I was missing the enctype="multipart/form-data". Without that files part was sent as empty.
Also changed to using from flask_wtf import FlaskForm and enabling it as {{ form.files(class="form-control", multiple="") }} in the template.
Files can then be accessed with uploaded_files = request.files.getlist("files") on POST request, it will hold array of file-objects.
I hope this helps someone. If any additional formatting is required I will add or expand the answer.

Django: Testing Post View

I'm trying to test my post with a testing suite. I've just been trying to follow the documentation to do this. The main problem I'm having right now is that response.context is returning None.
This is what my test class looks like:
class JSONHandlerTester(TestCase):
def setUp(self):
self.client = Client()
self.jsonTestPath = os.path.join(settings.MEDIA_ROOT,'json','jsonTests')
def testing(self):
for test in os.listdir(self.jsonTestPath):
testFile = os.path.join(os.path.join(self.jsonTestPath),test)
split = test.split('.')
testName = split[0]
testNameArray = re.findall('[a-zA-z][^A-Z]*', testName)
project = testNameArray[0]
team = testNameArray[1]
with open(testFile) as json:
response = self.client.post('/JSONChecker', {'json_project': project, 'json_team': team, 'json': json})
print response
print response.context
if (response.context['title'] == "Congratulations!!! Your JSON Passes!!!" and testNameArray[2] == "Pass") or (response.context['title'][2:] == "The" and testNameArray[2] == "Fail"):
print test+': Works'
else:
print test+': BREAKS: PROBLEM DETECTED'
Also this is what my render looks like:
return render(request, 'JSONChecker.html',context = {'title': title, 'validationErrors':validationErrors,'errors':errors, 'isLoggedIn':isLoggedIn, 'form': form, 'post':post})
If the form is invalid or the extension isn't json this is what the render looks like (this shouldn't be triggered by suite):
return render(
request,
'JSONChecker.html',
context = {'title': title,'errors': errors,'isLoggedIn':isLoggedIn,'team':team, 'email':email,'form':form, 'post': post},
)
Content-Length: 0
Content-Type: text/html; charset=utf-8
Location: /JSONChecker/
I'm using Django 1.11 and Python 2.7
Context attribute is only populated when using the DjangoTemplates backend.

How to keep database data when run py.test?

I want to test my query db function.
import pytest
from account_system.action import my_action
from account_system.models import MyUser
#pytest.mark.django_db
def test_ok_profile():
req = FakeRequest()
email = 'tester#example.com'
MyUser.objects.create_user(
email=email,
password="example",
)
# query MyUser table and return result
result = my_action.get_profile(email)
assert result == 'success'
But it's fail.
> assert result == 'success'
E assert None == 'success'
The function doesn't get any result from DB.
I check the database data and doesn't see any record.
(ex. User tester#example.com)
How to rewrite my code for testing?
Or how to keep data in database?
Thank you,
This is the way of how databases tests works..
When you set a django_db, the pytest will rollback your data after use.
But if you want to use the same data in several tests, you should take a look on
pytest.fixtures and factory_boy like :
import factory
class UserFactory(factory.DjangoModelFactory):
class Meta:
model = User
#pytest.fixture
def user():
return UserFactory(email = "bla#bla.com", password = "blabla")
And now you apply this reference on your test code:
test_test_ok_profile(user):
assert user.email = "bla#bla.com"

Adding ManytoManyField breaks Django

In my project, I have a model, Project, that when saved creates another model, Access, which contains a manytomanyfield, access_list, where users can add other users onto their project as collaborators. It works - when I create a new project, I can add additional users into it, but if I add a 2nd user, it will no longer serve the page with the error,
Exception Value: get() returned more than one Access -- it returned 2!"
If I switch to an account I've added to the project, then add other users on with that account, they add fine and it does not break the page.
When the page breaks, it also creates an additional instance of the project on my Projects page, even though there's only one instance of the project in the database.
My code:
Models.py:
class Project(models.Model):
created_by = models.ForeignKey(User)
super(Project, self).save()
Access.objects.get_or_create(project=self)
class Access(models.Model):
project = models.ForeignKey(Project)
access_list = models.ManyToManyField(User)
pubdate = models.DateTimeField(default=timezone.now)
Views.py:
#login_required
def access(request, project_id=1):
thisuser = request.user
if Access.objects.filter(Q(access_list=thisuser) | Q(project__created_by=thisuser), project__id=project_id).exists():
accesspermission = Access.objects.filter(Q(access_list=thisuser) | Q(project__created_by=thisuser), project__id=project_id).order_by('-project__project_pubdate')[0]
else:
accesspermission = None
if Entry.objects.filter(project_id=project_id).exists():
anyentries = Entry.objects.filter(project_id=project_id, entry_unique=1).order_by('-entry_pubdate')[0]
else:
anyentries = None
if Entry.objects.filter(project_id=project_id, entry_unique=1).exists():
firstentry = Entry.objects.filter(project_id=project_id, entry_unique=1).order_by('-entry_pubdate')[0]
else:
firstentry = None
if Entry.objects.filter(project_id=project_id).exists():
lastentry = Entry.objects.filter(project_id=project_id).order_by('-entry_pubdate')[0]
lastentrynumber = lastentry.entry_unique
else:
lastentrynumber = None
if request.method == "POST":
form = AddAccessForm(request.POST)
if form.is_valid():
p = form.save(commit=False)
adduserfromform = p.accessupdate
if User.objects.filter(username=adduserfromform).exists():
usertoadd = User.objects.get(username=adduserfromform)
projecttoadd = Access.objects.filter(project__id=project_id).order_by('-project__project_pubdate')[0]
projecttoadd.access_list.add(usertoadd)
else:
usertoadd = None
removeuserfromform = p.accessremove
if User.objects.filter(username=removeuserfromform).exists():
usertoremove = User.objects.get(username=removeuserfromform)
projecttoremove = Access.objects.filter(project__id=project_id).order_by('-project__project_pubdate')[0]
projecttoremove.access_list.remove(usertoremove)
else:
usertoremove = None
form.save()
return HttpResponseRedirect('/projects/get/%s/access' % project_id)
Traceback:
Traceback (most recent call last):
File "/webapps/filmeditdb/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 114, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/webapps/filmeditdb/local/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/webapps/filmeditdb/filmeditdb/docproject/views.py", line 284, in access
def access(request, project_id=1):
File "/webapps/filmeditdb/local/lib/python2.7/site-packages/django/db/models/manager.py", line 151, in get
return self.get_queryset().get(*args, **kwargs)
File "/webapps/filmeditdb/local/lib/python2.7/site-packages/django/db/models/query.py", line 310, in get
(self.model._meta.object_name, num))
MultipleObjectsReturned: get() returned more than one Access -- it returned 2!
2014-03-25 22:48:26 [17264] [INFO] Handling signal: winch
2014-03-25 22:48:26 [17264] [INFO] SIGWINCH ignored. Not daemonized
Adding a full traceback is generally recommeded, but from the error shown the issue is most likely on this line:
projecttoremove = Access.objects.get(project__id=project_id).order_by('-project__project_pubdate')[0]
Djangos get() always expects to return only 1 object. If there is 0 objects, it throws a DoesNotExist exception if there is more than 0, it throws MultipleObjectsReturned Exception.
Change that line to match the earlier line here:
projecttoadd = Access.objects.filter(project__id=project_id).order_by('-project__project_pubdate')[0]
Failing this, identify the erroneous call. The exception you are getting is quite clear.
At some point in the code, you are calling Access.objects.get() with some parameters and it is finding 2 objects instead of 1. Either fix the parameters, or moer likely switch it to use filter().

I don't understand tests in Django, Can you help me please?

I am having a hard time with tests in Django and Python, for my final project I am making a forums website, but I don't really have any idea how or what my tests should be. Here is the views page from mysite file. Could someone please walk me through what I should test for besides if a user is logged in.
from django.core.urlresolvers import reverse
from settings import MEDIA_ROOT, MEDIA_URL
from django.shortcuts import redirect, render_to_response
from django.template import loader, Context, RequestContext
from mysite2.forum.models import *
def list_forums(request):
"""Main listing."""
forums = Forum.objects.all()
return render_to_response("forum/list_forums.html", {"forums":forums}, context_instance=RequestContext(request))
def mk_paginator(request, items, num_items):
"""Create and return a paginator."""
paginator = Paginator(items, num_items)
try: page = int(request.GET.get("page", '1'))
except ValueError: page = 1
try:
items = paginator.page(page)
except (InvalidPage, EmptyPage):
items = paginator.page(paginator.num_pages)
return items
def list_threads(request, forum_slug):
"""Listing of threads in a forum."""
threads = Thread.objects.filter(forum__slug=forum_slug).order_by("-created")
threads = mk_paginator(request, threads, 20)
template_data = {'threads': threads}
return render_to_response("forum/list_threads.html", template_data, context_instance=RequestContext(request))
def list_posts(request, forum_slug, thread_slug):
"""Listing of posts in a thread."""
posts = Post.objects.filter(thread__slug=thread_slug, thread__forum__slug=forum_slug).order_by("created")
posts = mk_paginator(request, posts, 15)
thread = Thread.objects.get(slug=thread_slug)
template_data = {'posts': posts, 'thread' : thread}
return render_to_response("forum/list_posts.html", template_data, context_instance=RequestContext(request))
def post(request, ptype, pk):
"""Display a post form."""
action = reverse("mysite2.forum.views.%s" % ptype, args=[pk])
if ptype == "new_thread":
title = "Start New Topic"
subject = ''
elif ptype == "reply":
title = "Reply"
subject = "Re: " + Thread.objects.get(pk=pk).title
template_data = {'action': action, 'title' : title, 'subject' : subject}
return render_to_response("forum/post.html", template_data, context_instance=RequestContext(request))
def new_thread(request, pk):
"""Start a new thread."""
p = request.POST
if p["subject"] and p["body"]:
forum = Forum.objects.get(pk=pk)
thread = Thread.objects.create(forum=forum, title=p["subject"], creator=request.user)
Post.objects.create(thread=thread, title=p["subject"], body=p["body"], creator=request.user)
return HttpResponseRedirect(reverse("dbe.forum.views.forum", args=[pk]))
def reply(request, pk):
"""Reply to a thread."""
p = request.POST
if p["body"]:
thread = Thread.objects.get(pk=pk)
post = Post.objects.create(thread=thread, title=p["subject"], body=p["body"],
creator=request.user)
return HttpResponseRedirect(reverse("dbe.forum.views.thread", args=[pk]) + "?page=last")
First read the Django testing documentation. You might also want to read this book. It's dated in some areas, but testing is still pretty much the same now as it was in 1.1.
It's a bit much of a topic to cover in an SO answer.
Well, you could test:
If you have the right number of pages for the objects you're paginating.
If the page you're viewing contains the right object range. If trying to access a page that doesn't exist returns the
appropriate error.
If your views for listing objects and object detail return the correct HTTP status code (200)
For starters. Hope that helps you out.

Categories