Python AttributeError: 'Class' object has no attribute 'name' - SetUpClass - python

I'm writing tests for a model in a Django app, but cannot get them to run. I've tried all I can think of to solve and searched but cannot find the solution.
The error I receive when I run the test is AttributeError: 'UserModelTest' object has no attribute 'firstUser', which would suggest I haven't defined firstUser correctly.
Here are the relevant bits of code.
tests.py
from django.test import TestCase
from .models import ContactForm
from datetime import datetime, timedelta
class UserModelTest(TestCase):
#classmethod
def setUpClass(cls):
cls.firstUser = ContactForm(
email="first#user.com",
name="first",
timestamp=datetime.today() + timedelta(days=2)
)
cls.firstUser.save()
def test_contactform_str_returns_email(self):
self.assertEqual("first#user.com", str(self.firstUser))
models.py
from django.db import models
import datetime
class ContactForm(models.Model):
name = models.CharField(max_length=150)
email = models.EmailField(max_length=250)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
class Meta:
ordering = ['-timestamp']
I'm using Python version 3.5 and Django version 1.10.

setUpClass has been deprecated for Django (I believe since 1.8). In order to set up the class now, I needed to use setUpTestData. The remaining code still works. setUpTestData is then run only once before all test methods in the class (not once before each test).
i.e. the code should be:
class UserModelTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.firstUser = ContactForm(
email="first#user.com",
name="first",
timestamp=datetime.today() + timedelta(days=2)
)
cls.firstUser.save()
def test_contactform_str_returns_email(self):
self.assertEqual("first#user.com", str(self.firstUser))
More information on setUpTestData can be found here.

Related

Get name of class for Attribute usage in Python Astroid / Pylint

I have the following files:
# in models.py
class User(object):
def __init__(self, name):
self.name = name
# in util.py
def get_user():
return User(name="tom")
# in views.py
from util import get_user
get_user().name
I want to detect all Attribute usages for the name attribute on the User model. So get_user().name would be flagged as one such usage, since get_user returns a role User object.
Is there an easy way to do this using Astroid only preferably?

Graphene Mutation error, fields must be a mapping (dict / OrderedDict)

I'm starting to wrap my head around with GraphQl/Graphene. I'm building a schema connected to a MongoDB. All seems to work so far except mutations. I've been following the example here and here without luck. Can someone point me towards what I'm doing wrong? Thanks in advance.
import graphene
class GeoInput(graphene.InputObjectType):
lat = graphene.Float(required=True)
lng = graphene.Float(required=True)
#property
def latlng(self):
return "({},{})".format(self.lat, self.lng)
class Address(graphene.ObjectType):
latlng = graphene.String()
class CreateAddress(graphene.Mutation):
class Arguments:
geo = GeoInput(required=True)
Output = Address
def mutate(self, info, geo):
return Address(latlng=geo.latlng)
class Mutation(graphene.ObjectType):
create_address = CreateAddress.Field()
class Query(graphene.ObjectType):
address = graphene.Field(Address, geo=GeoInput(required=True))
def resolve_address(self, info, geo):
return Address(latlng=geo.latlng)
schema = graphene.Schema(query=Query, mutation=Mutation)
The code above generates this error:
AssertionError: CreateAddress fields must be a mapping (dict /
OrderedDict) with field names as keys or a function which returns such
a mapping.
The problem is in the import.
I've had same issue when I used:
from graphene import ObjectType
I've found how to import it properly in next example from docs. Here it is:
from graphene_django.types import DjangoObjectType
The issue was with the version of graphene I had installed, installing graphene 2.0 solved the issue.
My problem was that I had declared all of my fields incorrectly. This is my Type:
class EventDateRangeType(DjangoObjectType):
class Meta:
model = EventDateRange
fields = ('start', 'end')
But my Model was:
class EventDateRange(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
So start & end don't match start_time & end_time. Making them the same fixed my issue.
In your mutation:
Output = Address
Should be a graphene object:
Output = graphene.Field(Address)
Had a similar error for a class that inherited from "InputObjectType". The solution was to import "InputObjectType" from graphene instead of from graphql.type.tests.test_definition (don't know why it was imported from that library in the first place)
It can even happen if no output is specified in graphene.Mutation inheriting class.
But that is not case for DevilWarior.

Why is the import statement importing a function outside of the class that is being imported?

I was under the understanding that using the syntax:
from foo import y
will import the class y from foo.py.
If this is true how come when I use the following:
models.py
from django.db import models
from .utils import codeGenerator, createShortcode
class KirrUrlManager(models.Manager):
def all(self, *args, **kwargs):
qs_main = super(KirrUrlManager, self).all(*args, **kwargs)
qs = qs_main.filter(active=True)
return qs
def refreshShortCodes(self):
qs = KirrUrl.objects.filter(id__gte=1) #id_gte is primary key
newCodes = 0
for q in qs:
q.shortcode = createShortcode(q)
q.save()
newCodes +=1
return "new codes made: {i} ".format(i=newCodes)
class KirrUrl(models.Model):
url = models.CharField(max_length=220, unique=True)
shortcode = models.CharField(max_length=50, unique=True, blank = True)
timestamp = models.DateTimeField(auto_now=True,)
timestamp = models.DateTimeField(auto_now_add=True,)
active = models.BooleanField(default=True,)
objects = KirrUrlManager()
def save(self, *args, **kwargs):
if self.shortcode is None or self.shortcode =="":
self.shortcode = createShortcode(self)
super(KirrUrl, self).save(*args, **kwargs)
def __str__(self):
return str(self.url)
foo.py
from django.core.management.base import BaseCommand, CommandError
from shortener.models import KirrUrl
class Command(BaseCommand):
help = "refreshes all shortcodes"
def handle(self, *args, **options):
return KirrUrl.objects.refreshShortCodes()
I am unsure why I am able to call the method "refreshShortCodes()" in foo.py. I am only using the import statement "from shortener.models import KirrUrl". Shouldnt this import statement only let me import the KirrUrl class? refreshShortCodes() is not part of the KirrUrl class, however it is the models.py file that is being imported.
Shouldnt this import statement only let me import the KirrUrl class?
Yes, and that's exactly what the import statement is doing. objects.refreshShortCodes() is part of the KirrUrl class. So you have access to the class name, as well as its attributes.
When Python imports a class, it imports the entire class object. That means all of variables and methods defined in the classes namespace can be reached. So Since you created an instance of KirrUrlManager() inside of the KirrUrl class, you can access the refreshShortCodes() method by first getting the KirrUrlManager() instance:
KirrUrl.objects
And then getting the refreshShortCodes() method from the KirrUrlManager() instance:
KirrUrl.objects.refreshShortCodes()
You only need to import the class to have access to its attributes; you can't import an object encapsulated in a class.
Therefore, after importing the class KirrUrl, the model manager objects is accessible via the class, and the method refreshShortCodes is equally accessible via the model manager instance which is composed in the class KirrUrl.
This is one of the ways objects that are not reachable by the import mechanism are accessed; dot referencing.

Python class method throws AttributeError

I'm having issues getting a class method to run in Flask.
In models/User.py:
from mongoengine import *
class User(Document):
first_name = StringField()
last_name = StringField()
...
def __init__(self, arg1, arg2, ...):
self.first_name = arg1
self.last_name = arg2
...
#classmethod
def create(self, arg1, arg2, ...):
#do some things like salting and hashing passwords...
user = self(arg1, arg2, ...)
user.save()
return user
In the main application python file:
from models import User
...
def func():
...
#Throws "AttributeError: type object 'User' has no attribute 'create'"
user = User.create(arg1, arg2, ...)
Shouldn't I be able to call create on the User class without instantiating a User object? I'm using Python 2.7.2, and I also tried the non-decorator syntax of using create = classmethod(create), but that didn't work. Thanks in advance!
EDIT: I found one issue: that the models folder did not contain an __init__.py file, so it wasn't a module, so from models import User was not actually importing the file I wanted it to. It did not give me an error from before because I used to have a models.py module in the same directory as the application python script, but after deleting it I never deleted the corresponding .pyc file. Now, I'm getting the error AttributeError: 'module' object has no attribute 'create' instead of what I had before, but I'm certain it is importing the correct file now.
EDIT2: Solved. I then changed the import to from models.User import User and It's hitting the method now.
The issue was twofold:
The User.py file was in the models/ folder, meaning that my import was actually looking for the User class in the models.py file, which no longer existed but still was being imported without error because the models.pyc file was still around
The import was incorrect for importing within a directory. it should have been from models.User import User, so long as the models/ folder is a module, so all I needed to do then was touch models/__init__.py.
>>> class foo(object):
... def __init__(self):
... pass
... #classmethod
... def classmethod(cls):
... return 0
...
>>> a = foo()
>>> a.classmethod()
0
>>>

How to create table during Django tests with managed = False

I have a model with managed = False.
class SampleModel(models.Model):
apple = models.CharField(max_length=30)
orange = models.CharField(max_length=30)
class Meta:
managed = False
I have a unit test which creates a SampleModel, however when I run the test I get:
DatabaseError: no such table: SAMPLE_SAMPLE_MODEL
The django docs - https://docs.djangoproject.com/en/dev/ref/models/options/#managed documents the following:
For tests involving models with managed=False, it's up to you to
ensure the correct tables are created as part of the test setup.
How can I actually "create" the tables during the test setup? Or alternatively, how can I make it so that when I am running tests, this model has "managed = True" for the duration of the test?
In the real application, this model is actually backed by a view in the database. However for the during of the test, I would like to treat this as a table and be able to insert test data in there.
You can use SchemaEditor in TestCase.setUp method to explicitly create models with managed = False.
# models.py
from django.db import models
class Unmanaged(models.Model):
foo = models.TextField()
class Meta:
# This model is not managed by Django
managed = False
db_table = 'unmanaged_table'
And in your tests:
# tests.py
from django.db import connection
from django.test import TestCase
from myapp.models import Unmanaged
class ModelsTestCase(TestCase):
def setUp(self):
super().setUp()
with connection.schema_editor() as schema_editor:
schema_editor.create_model(Unmanaged)
if (
Unmanaged._meta.db_table
not in connection.introspection.table_names()
):
raise ValueError(
"Table `{table_name}` is missing in test database.".format(
table_name=Unmanaged._meta.db_table
)
)
def tearDown(self):
super().tearDown()
with connection.schema_editor() as schema_editor:
schema_editor.delete_model(Unmanaged)
def test_unmanaged_model(self):
with self.assertNumQueries(num=3):
self.assertEqual(0, Unmanaged.objects.all().count())
Unmanaged.objects.create()
self.assertEqual(1, Unmanaged.objects.all().count())
Check out this blog post: http://www.caktusgroup.com/blog/2010/09/24/simplifying-the-testing-of-unmanaged-database-models-in-django/ It describes in detail the creation of a test runner for unmanaged models.
from django.test.simple import DjangoTestSuiteRunner
class ManagedModelTestRunner(DjangoTestSuiteRunner):
"""
Test runner that automatically makes all unmanaged models in your Django
project managed for the duration of the test run, so that one doesn't need
to execute the SQL manually to create them.
"""
def setup_test_environment(self, *args, **kwargs):
from django.db.models.loading import get_models
self.unmanaged_models = [m for m in get_models()
if not m._meta.managed]
for m in self.unmanaged_models:
m._meta.managed = True
super(ManagedModelTestRunner, self).setup_test_environment(*args,
**kwargs)
def teardown_test_environment(self, *args, **kwargs):
super(ManagedModelTestRunner, self).teardown_test_environment(*args,
**kwargs)
# reset unmanaged models
for m in self.unmanaged_models:
m._meta.managed = False
Execute raw SQL to create the table in the test setup:
from django.db import connection
class MyTest(unittest.TestCase):
def setUp(self):
connection.cursor().execute("CREATE TABLE ...")
def tearDown(self):
connection.cursor().execute("DROP TABLE ...")
Nice plug and play solution. Just paste this before your test class definition. (note: django 1.8 used)
from django.db.models.loading import get_models
def change_managed_settings_just_for_tests():
"""django model managed bit needs to be switched for tests."""
unmanaged_models = [m for m in get_models() if not m._meta.managed]
for m in unmanaged_models:
m._meta.managed = True
change_managed_settings_just_for_tests()
A quick fix if you don't have many unmanaged tables:
First add a new variable to the settings.
# settings.py
import sys
UNDER_TEST = (len(sys.argv) > 1 and sys.argv[1] == 'test')
then in the models
# models.py
from django.conf import settings
class SampleModel(models.Model):
apple = models.CharField(max_length=30)
orange = models.CharField(max_length=30)
class Meta:
managed = getattr(settings, 'UNDER_TEST', False)
Create your own test runner using this:
from django.test.simple import DjangoTestSuiteRunner
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
#set manage=True for that specific database on here
Then on your settings add this class to TEST_RUNNER.
Just to add :django.db.models.loading.get_models will be removed in Django 1.9 (see https://github.com/BertrandBordage/django-cachalot/issues/33).
Below is an updated one for Django 1.10:
class UnManagedModelTestRunner(DiscoverRunner):
'''
Test runner that automatically makes all unmanaged models in your Django
project managed for the duration of the test run.
Many thanks to the Caktus Group
'''
def setup_test_environment(self, *args, **kwargs):
from django.apps import apps
self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
for m in self.unmanaged_models:
m._meta.managed = True
super(UnManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)
def teardown_test_environment(self, *args, **kwargs):
super(UnManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
# reset unmanaged models
for m in self.unmanaged_models:
m._meta.managed = False
Note you also need to take care migrations(see Testing django application with several legacy databases)
MIGRATION_MODULES = {
'news': 'news.test_migrations',
'economist': 'economist.test_migrations'
}
Using pytest and pytest-django
To make this work (has been tested with django 3.0.2, pytest 5.3.5 and pytest-django 3.8.0):
Run your pytest with the additional argument --no-migrations.
Put the following code in your conftest.py. This has an unfortunate amount of copypasta, but I could not figure out how to first make my models unmanaged and then call the original django_db_setup. The issue of not being able to call pytest fixtures directly is discussed here: https://github.com/pytest-dev/pytest/issues/3950
conftest.py
# example file
import pytest
from pytest_django.fixtures import _disable_native_migrations
#pytest.fixture(scope="session")
def django_db_setup(
request,
django_test_environment,
django_db_blocker,
django_db_use_migrations,
django_db_keepdb,
django_db_createdb,
django_db_modify_db_settings,
):
# make unmanaged models managed
from django.apps import apps
unmanaged_models = []
for app in apps.get_app_configs():
unmanaged_models = [m for m in app.get_models()
if not m._meta.managed]
for m in unmanaged_models:
m._meta.managed = True
# copypasta fixture code
"""Top level fixture to ensure test databases are available"""
from pytest_django.compat import setup_databases, teardown_databases
setup_databases_args = {}
if not django_db_use_migrations:
_disable_native_migrations()
if django_db_keepdb and not django_db_createdb:
setup_databases_args["keepdb"] = True
with django_db_blocker.unblock():
db_cfg = setup_databases(
verbosity=request.config.option.verbose,
interactive=False,
**setup_databases_args
)
def teardown_database():
with django_db_blocker.unblock():
try:
teardown_databases(db_cfg, verbosity=request.config.option.verbose)
except Exception as exc:
request.node.warn(
pytest.PytestWarning(
"Error when trying to teardown test databases: %r" % exc
)
)
if not django_db_keepdb:
request.addfinalizer(teardown_database)
After spending a few hours testing and researching ways to test my django unmanaged models, I finally came up with a solution that worked for me.
My implementation is in this below snippet. It's working great with local tests using db.sqlite3.
# Example classes Implementation
from django.db import models, connection
from django.db.models.base import ModelBase as DjangoModelBase
from django.db.utils import OperationalError
from django.test import TransactionTestCase
class AbstractModel(models.Model):
name = models.TextField(db_column="FIELD_NAME", blank=True, null=True)
class Meta:
managed = False
abstract = True
class TestModel(AbstractModel):
test_field = models.TextField(db_column="TEST_FIELD", blank=True, null=True)
def test_method(self):
print("just testing")
class Meta(AbstractModel.Meta):
db_table = "MY_UNMANAGED_TABLE_NAME"
# My Custom Django TestCases Implementation for my tests
def create_database(model):
with connection.schema_editor() as schema_editor:
try:
schema_editor.create_model(model)
except OperationalError:
pass
def drop_database(model):
with connection.schema_editor() as schema_editor:
try:
schema_editor.delete_model(model)
except OperationalError:
pass
class BaseModelTestCase(TransactionTestCase):
"""Custom TestCase for testing models not managed by django."""
Model = DjangoModelBase
def setUp(self):
super().setUp()
create_database(self.Model)
def tearDown(self):
super().tearDown()
drop_database(self.Model)
class AbstractBaseModelTestCase(TransactionTestCase):
"""Custom TestCase for testing abstract django models."""
Model = DjangoModelBase
def setUp(self):
# this is necessary for testing an abstract class
self.Model = DjangoModelBase(
"__TestModel__" + self.Model.__name__,
(self.Model,),
{"__module__": self.Model.__module__},
)
create_database(self.Model)
def tearDown(self):
drop_database(self.Model)
# Example of usage
class TestModelTestCase(BaseModelTestCase):
Model = TestModel
def setUp(self):
super().setUp()
self.instance = TestModel.objects.create()
self.assertIsInstance(self.instance, TestModel)
class AbstractModelTestCase(AbstractBaseModelTestCase):
Model = AbstractModel
def setUp(self):
super().setUp()
self.instance = AbstractModel.objects.create()
self.assertIsInstance(self.instance, AbstractModel)
p.s. I am not using any Django migrations as soon as my models are managed=False. So this was not necessary for me.
Simply move your unmanaged models to a dedicated app and delete migrations folder. Details in my answer in Multi db and unmanged models - Test case are failing

Categories

Resources