Graphene query hangs indefinitely when testing with pytest - django

I am trying to test my backend, written in Django 2.2.2 and Python 3. I created some graphql queries which are definitely working when testing with the graphql web interface. When testing with pytest and the graphene test client, however, these queries would always hang indefinitely. I put together a reproducible example which is actually based on the code example from the graphene-django documentation.
test_example.py:
import pytest
import graphene
from graphene_django import DjangoObjectType
from graphene.test import Client
from django.db import models
class UserModel(models.Model):
name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
class User(DjangoObjectType):
class Meta:
model = UserModel
class Query(graphene.ObjectType):
users = graphene.List(User)
def resolve_users(self, info):
return UserModel.objects.all()
schema = graphene.Schema(query=Query)
client = Client(schema)
def test_user():
query = '''
query {
users {
name,
lastName
}
}
'''
result = client.execute(query)
assert 0 # dummy assert
This example behaves in the same way (stalls forever, no errors). I am using the latest graphene-django (2.3.2) and pytest (4.6.3). I should probably also mention that I'm running this inside a Docker container. Any ideas why this happens? Is this a bug in the graphene-django library?

I found the answer myself after a while digging through the documentation. Pytest needs permission to use the database. So the issue is solved by simply adding the pytest mark #pytest.mark.django_db before the test. As an alternative the whole module can be marked to allow database access by using pytestmark = pytest.mark.django_db. See pytest-django docs.
The documentation says the tests will fail if db access is not granted, so I would not expect them to stall forever.

Related

How to fix "Django test setUp() function not creating the django users"?

I am doing the django test cases. Now I have a problem that, user created in django test is not shown in the database created for test. consider my database name as db.
from django.contrib.auth.models import User,AnonymousUser
from django.test import TestCase,LiveServerTestCase
from django.db import connection
class machinelist(TestCase):
def setUp(self):
self.user=User.objects.create_user(username="vishnu***",email="vishnu#clartrum.com",password="vishnu#12345")
print self.user.username
db_name = connection.settings_dict['NAME']
print db_name
t=raw_input("PAUSE")
def tearDown(self):
print 'd'
def test1(self):#view function
print "2"
The output will print the username vishnu*** and db_name as test_db. But when you observe the database, there is no data.
In the code, when the raw_input is executed, I will sign into the mysql server and verified that test_db is created successfully and also the django's tables. But when i checked auth_user table there is no data in it.
I am using django 1.11.20 and mysql 5.6.33.
Why User is not updating on the database table?
Tests inside TestCase classes run in a transaction, which is rolled back at the end of each test. Since the default MySQL transaction isolation level is REPEATABLE READ, the data inserted within the transaction is not visible from outside.
I'm not sure why you want to check the data from outside the test, though. You can print it or assert on it from within the test itself.

Django 1.11 + py.test: Fix ConnectionDoesNotExist when test functions need access to unmanaged external tables/DB

We use py.test to test our Django system.
We use unmanaged DBs in our system:
class Example(models.Model):
class Meta:
managed = False
When we run tests that need access to the existing DBs with existing data, they fail with
ConnectionDoesNotExist: The connection my_connection_name doesn't exist
This is how our tests look like:
#pytest.mark.django_db
def test_example_access():
from example.models import Example
Example.objects.count()
We've tried to add the following to conftest.py as described in the top answer on Django test tables are not being created, but it did not change the matter (despite being executed).
#pytest.fixture(autouse=True, scope='session')
def __make_unmanaged_managed():
from django.apps import apps
unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
for m in unmanaged_models:
m._meta.managed = True
Thanks a lot for your ideas!
ps: Testing django application with several legacy databases provides a workaround for unit tests, however I would not know how to apply this when using py.test specifically.
I had the same problem here, and as far as I can tell, this is the snippet that worked for me.
I had to put this in the conftest.py file, in the root of my django project, to override the normal django_test_environment fixture in pytest-django.
#pytest.fixture(autouse=True, scope='session')
def django_test_environment(request):
"""
This is an override of the django_test_environment function in pytest-django,
to run through the unmanaged models, and just for the test, set them to be managed,
so we can create models in our tests, even if we don't manage directly in Django.
"""
from pytest_django.plugin import django_settings_is_configured
from pytest_django.plugin import _setup_django
from django.apps import apps
unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
for m in unmanaged_models:
m._meta.managed = True
if django_settings_is_configured():
_setup_django()
from django.conf import settings as dj_settings
from django.test.utils import (setup_test_environment,
teardown_test_environment)
dj_settings.DEBUG = False
setup_test_environment()
request.addfinalizer(teardown_test_environment)
What this does
When Pytest sets up a django database, it does so with the django_db_setup fixture. This fixture itself takes django_test_environment, which we override in conftest.py, to update the managed state on the unmanaged models.
When setup_test_environment is called as part of our overriden setup_test_environment fixture, it creates the necessary tables for us to reference models in our tests
I'd love to hear if this works, as it seems to have resolved the issue at my end, when I ended up having to work with many, many unmanaged models, and manually creating them with SQL seemed like a terrible idea.

How to prepopulate test database by necessary data?

I need to do some unit tests in my Django project. The problem is that almost every use case depends on prepopulated database objects.
For example, I want to create a product and test, if there were all pre_save signals successful.
from django.contrib.auth.models import User
from django.test import TestCase
from .models import Product
class ProductTestCase(TestCase):
def setUp(self):
self.user = User.objects.create(username='test_user')
self.product = Product.objects.create(name='Test product',user=self.user)
def test_product_exists(self):
self.assertIsNotNone(self.product)
def product_is_active_by_default(self):
...
I can't do that because product has to have User object related. But I can't create a User object because User has to have related plan object. There are multiple plans in my production database from which one is default but there are no plans inside test database.
So to be able to do unit tests I need to prepopulate test database with multiple objects from multiple apps.
How can I do that?
you can simply use django fixtures for that :-)
first populate a sample db with data then export data with python manage.py dumpdata
then in one of your apps create a directory named fixtures and put exported json file there (named tests.json or something else)
in your test class load fixtures like this
class ProductTestCase(TestCase):
fixtures = ['tests.json', ]
checkout django docs
PS: checkout factory boy too (#Gabriel Muj) answer
I don't recommend using fixture since you will need to maintain them each time you make changes to the model. Here is a better approach on creating objects for tests by using this library https://factoryboy.readthedocs.io/en/latest/ which is more flexible.

Django - setUpTestData & Many to Many relationship

I need to add a many-to-many relationship in my setUpTestData sequence so that my tests will run correctly.
According to the docs, Many-to-Many relationships cannot be created until an object has been saved, as the primary key must first exist. This means that I cannot set the relationship in setUpTestData with Model.objects.create() as I do with other variables.
Is there any way to include a Many-to-Many relationship in setUpTestData?
As a partial answer, a solution I found in the docs seems to work half way. Here is my code:
(I'm linking a social app to Django's Sites framework so that my tests will be able to run).
SetupTestData.py
from django.test import TestCase
from django.contrib.sites.models import Site
from allauth.socialaccount.models import SocialApp
class TestData(TestCase):
#classmethod
def setUpTestData(cls):
cls.current_site = Site.objects.get_current()
cls.SocialApp1 = cls.current_site.socialapp_set.create(
provider="facebook",
name="facebook",
client_id="123456789",
secret="0987654321",
)
test_sandbox.py
from MyApp.tests.SetupTestData import TestData
from django.contrib.sites.models import Site
from allauth.socialaccount.models import SocialApp
class TestHomePage(TestData):
def test_sandbox(self):
print(Site.objects.all())
print(Site.objects.get_current())
print(Site.objects.get_current().socialapp_set.all())
print(self.SocialApp1)
print(self.SocialApp1.sites)
print(SocialApp.objects.get(provider='facebook').sites)
Test Output
Creating test database for alias 'default'...
[<Site: example.com>]
example.com
[<SocialApp: facebook>]
facebook
sites.Site.None
sites.Site.None
Destroying test database for alias 'default'...
You can see from the results that I can locate the app via Sites, but it doesn't seem to work backwards. I suppose if I somehow reversed the relationship in setUpTestData I could make it work the opposite way instead. I'll leave the question open for a better solution.

Django Proxy Model Permissions Do Not Appear

I extended Django admin site for my app to allow non-staff/superusers access. This is working just fine.
I created a proxy model for an existing model and registered it to my admin site, however, it doesn't appear for non-staff users. From the documentation I read, my understanding is that proxy models get their own permissions. I checked and these don't appear in the list of available permissions.
Here's my code in case it helps:
Normal Model
class Engagement(models.Model):
eng_type = models.CharField(max_length=5)
environment = models.CharField(max_length=8)
is_scoped = models.BooleanField()
class Meta:
ordering = ['eng_type', 'environment']
app_label = 'myapp'
Proxy Model
class NewRequests(Engagement):
class Meta:
proxy = True
app_label = 'myapp'
verbose_name = 'New Request'
verbose_name_plural = 'New Requests'
Model Admin
class NewRequestsAdmin(ModelAdmin):
pass
def queryset(self, request):
return self.model.objects.filter(is_scoped=0)
Custom Admin Registration
myapps_admin_site.register(NewRequests, NewRequestsAdmin)
I've been managing my DB with South. According to this post, you have to tamper with it a bit by following the instructions it points users to. This was a failure. My DB doesn't have a whole lot of info in it, so I uncommented South and ran a regular syncdb to rule out South. Unfortunately, this is still not working and I'm at a loss. Any help is appreciated.
Edit
This was on Django 1.4
Turns out I didn't do anything wrong. I was looking for the permissions under
myapp | New Request | Can add new request
Permissions fall under the parent model.
myapp | engagement | Can add new request
This is fixed in Django 2.2, quoting release notes:
Permissions for proxy models are now created using the content type of the proxy model rather than the content type of the concrete model. A migration will update existing permissions when you run migrate.
and docs:
Proxy models work exactly the same way as concrete models. Permissions are created using the own content type of the proxy model. Proxy models don’t inherit the permissions of the concrete model they subclass.
There is a workaround, you can see it here: https://gist.github.com/magopian/7543724
It can vary based on your django version, but the priciple is the same.
Tested with Django 1.10.1
# -*- coding: utf-8 -*-
"""Add permissions for proxy model.
This is needed because of the bug https://code.djangoproject.com/ticket/11154
in Django (as of 1.6, it's not fixed).
When a permission is created for a proxy model, it actually creates if for it's
base model app_label (eg: for "article" instead of "about", for the About proxy
model).
What we need, however, is that the permission be created for the proxy model
itself, in order to have the proper entries displayed in the admin.
"""
from __future__ import unicode_literals, absolute_import, division
import sys
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.apps import apps
from django.utils.encoding import smart_text
class Command(BaseCommand):
help = "Fix permissions for proxy models."
def handle(self, *args, **options):
for model in apps.get_models():
opts = model._meta
ctype, created = ContentType.objects.get_or_create(
app_label=opts.app_label,
model=opts.object_name.lower(),
defaults={'name': smart_text(opts.verbose_name_raw)})
for codename, name in _get_all_permissions(opts):
p, created = Permission.objects.get_or_create(
codename=codename,
content_type=ctype,
defaults={'name': name})
if created:
sys.stdout.write('Adding permission {}\n'.format(p))
How to use
create a directory /myproject/myapp/management/commands
create the file /myproject/myapp/management/__init__.py
create the file /myproject/myapp/management/commands/__init__.py
save the code above into /myproject/myapp/management/commands/fix_permissions.py
run /manage.py fix_permissions
This is a known bug in Django: https://code.djangoproject.com/ticket/11154 (check comments for some patches)
As of 2021 and Django 3+, the solution for missing permissions for proxy model is simple, just generate migrations with makemigrations:
app#e31a3ffef22c:~/app$ python manage.py makemigrations my_app
Migrations for 'main':
main/migrations/0193_myproxymodel.py
- Create proxy model MyProxyModel
I came here and wasn't really sure, what is the correct cause/solution to this problem.
For Django 1.11
This issue is related due to the wrong content_type_id in auth_permission table.
By default, it adds the content type of the base model instead of proxy model content type.
I realize this question was closed a while ago, but I'm sharing what worked for me in case it might help others.
It turns out that even though permissions for the proxy models I created were listed under the parent apps (as #chirinosky) has mentioned, and even though I granted my non-super user all permissions, it was still denied access to my proxy models through the admin.
What I had to do was workaround a known Django bug (https://code.djangoproject.com/ticket/11154) and connect to the post_syncdb signal to properly create permissions for the proxy models. The code below is modified from https://djangosnippets.org/snippets/2677/ per some of the comments on that thread.
I placed this in myapp/models.py that held my proxy models. Theoretically this can live in any of your INSTALLED_APPS after django.contrib.contenttypes because it needs to be loaded after the update_contenttypes handler is registered for the post_syncdb signal so we can disconnect it.
def create_proxy_permissions(app, created_models, verbosity, **kwargs):
"""
Creates permissions for proxy models which are not created automatically
by 'django.contrib.auth.management.create_permissions'.
See https://code.djangoproject.com/ticket/11154
Source: https://djangosnippets.org/snippets/2677/
Since we can't rely on 'get_for_model' we must fallback to
'get_by_natural_key'. However, this method doesn't automatically create
missing 'ContentType' so we must ensure all the models' 'ContentType's are
created before running this method. We do so by un-registering the
'update_contenttypes' 'post_syncdb' signal and calling it in here just
before doing everything.
"""
update_contenttypes(app, created_models, verbosity, **kwargs)
app_models = models.get_models(app)
# The permissions we're looking for as (content_type, (codename, name))
searched_perms = list()
# The codenames and ctypes that should exist.
ctypes = set()
for model in app_models:
opts = model._meta
if opts.proxy:
# Can't use 'get_for_model' here since it doesn't return
# the correct 'ContentType' for proxy models.
# See https://code.djangoproject.com/ticket/17648
app_label, model = opts.app_label, opts.object_name.lower()
ctype = ContentType.objects.get_by_natural_key(app_label, model)
ctypes.add(ctype)
for perm in _get_all_permissions(opts, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(Permission.objects.filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
objs = [
Permission(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_perms
if (ctype.pk, codename) not in all_perms
]
Permission.objects.bulk_create(objs)
if verbosity >= 2:
for obj in objs:
sys.stdout.write("Adding permission '%s'" % obj)
models.signals.post_syncdb.connect(create_proxy_permissions)
# See 'create_proxy_permissions' docstring to understand why we un-register
# this signal handler.
models.signals.post_syncdb.disconnect(update_contenttypes)