is there a way to override model methods in django? - django

I have a model like this:
class Car(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
create_date = models.DateTimeField('date added', auto_now_add=True)
modify_date = models.DateTimeField('date modified', auto_now=True)
...
def last_tracked_location(self):
...
try:
url = 'myurl'
return requests.get(
url,
).json()['location'])
except:
return False
This method get's called later for the admin's panel. It requests something from an api and then returns either this or false.
In testing mode the other api doesn't exist so the request delays all the tests till it timeouts and then returns False.
Is there a way to override this? I checked the docs but could only find out to override the settings.
Another idea I had was to test inside the method if it's being called in testing mode and then just never go into this try block. But I don't think this is a clean way.
UPDATE
I am calling the tests like so:
python3 manage.py test --settings=app.settings_test

You can write your tests so that they mock the response of the API. The unittest mock module is a good starting point and part of the Python standard library since Python 3.3.
I don't have access to your full code but here is an example to get you started:
from unittest import mock
from django.test import TestCase
from .models import Car
#mock.patch('yourapp.models.Car.last_tracked_location')
class CarTestCase(TestCase):
def test_get_last_tracked_location(self, mock_last_tracked_location):
mock_last_tracked_location.return_value = {'location': 'Paris'}
car = Car.objects.create()
response = car.last_tracked_location()
assert response['location'] == 'Paris'

Checking DEBUG directly in method is not pretty clean in my opinion.
Maybe it will be better for you to write some API class for making this request?
Then you can either write separate logic for this class to run in 'testing mode' (not sure i understand correctly what do you mean by that :) test env ?) or just create mock in tests.
Here you can read more about conditionally testing scenarios:
https://realpython.com/testing-third-party-apis-with-mocks/

Related

create users before running tests DRF

i want to run django tests,
but i want to create some users before i run the test and the users' username will be attribute of the class and can be shared in all tests somthing like this:
class DoSomeTests(TestCase):
def setup_createusers(self):
self.usr1 = create_user1()
self.usr2 = create_user1()
self.usr3 = create_user1()
def test_number_one(self):
use self.user1/2/3
def test_number_two(self):
use self.user1/2/3
def test_number_three(self):
use self.user1/2/3
how can i do it becuase every time i tried the test dont recognize the self's attributes
ive tried use setupclass and setUp but nothing happend
cretae users before running tests
Generally (and personally) since setUpTestData was introduced I use this one but you can use setUp also based on your approach and what you need.
In order to use setUpTestData you need to put a class decorator and approach it with cls instead of self since you want to set up data for the whole TestCase, something like:
class TestViews(TestCase):
#classmethod
def setUpTestData(cls):
cls.user1 = User.objects.create_user(........)
cls.user2 = User.objects.create_user(........)
cls.user3 = User.objects.create_user(........)
Then in your tests, in order to access (and log in) each user you can use this:
def test_number_one(self):
test_user = self.user1
self.client.force_login(test_user)

Is it considered good practice using too many factories in pytest?

I am trying to write tests for Django/DjangoREST project. I have decided to use pytest. I have poor experience in writing tests for Django projects specifically. So I am confused now.
Here is an example:
#pytest.mark.django_db
def test_some_view(
api_client,
simple_user,
model1_factory,
model2_factory,
... # many other model factories
modelN_factory
):
# ...
# creating here other objects that really depends on each other
# ...
model2_obj = ... # model2 object on its own side depends on model3, model4... and so on
model1_objs = []
for i in range(10):
model1_objs.append(model1_factory(some_field=100, some_model2_rel=model2_obj)
assert len(model1_objs) == 1, "Created items with duplicate `some_field`"
As you can see I have too many factories to be used in one test. But looking
at my model structure right now, I can't think of a better way. Is it ok to use so many
factories for one test? Or should I find some issues related to my tables' relations?
Any help is appreciated. Thanks in advance
The main goal of factory_boy is getting rid of fixtures; its typical use case is:
Design your Factory classes, which are basically recipes for getting a "realistic" object instance
In your test, call only the factories you need, specifying just the parameters for that test case.
As I understand it, pytest fixtures are intended for "setting up the test environment": booting the database, mocking an external service, etc.; creating objects inside the database isn't a good fit for them.
The way I'd write your code would be the following:
# factories.py
import factory
import factory.fuzzy
from . import models
class DivisionFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Division
name = factory.Faker('company')
class EmployeeFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Employee
username = factory.Faker('username')
name = factory.Faker('name')
employee_id = factory.Sequence(lambda n: 'EM199%06d' % n)
division = factory.SubFactory(DivisionFactory)
role = factory.fuzzy.FuzzyChoice(models.Employee.ROLES)
hired_on = factory.fuzzy.FuzzyDate(
start_date=datetime.date.today() - datetime.timedelta(days=100),
end_date=datetime.date.today() - datetime.timedelta(days=10),
)
We have a factory for an employee, and one for a division - and each employee gets assigned to a division.
Every mandatory field is provided; if we need to make specific factories for some object profiles, this can be added through either subclassing or using traits.
We can now write our tests, passing only the details required for the test:
# tests.py
#pytest.mark.django_db
def test_get_new_hire(api_client):
employee = factories.EmployeeFactory(
hired_on=datetime.date.today(),
division__name="Finance",
)
data = api_client.get(f'/employees/{employee.username}')
assert data['division'] == "Finance"
assert data['orientation_status'] == 'pending'
As a side note, wouldn't it make more sense to use Django's test runner directly? It's more finely tuned for Django internals: each test can be natively wrapped in a sub-transaction for performance, the test client provides helpers for in-depth introspection of view results, etc.

How to pass Django mock instance to class method?

The Mock testing library is the one Django topic I just can't seem to wrap my head around. For example, in the following code, why don't the mock User instances that I create in my unit test appear in the User object that I query in the 'get_user_ids' method? If I halt the test in the 'get_user_ids' method via the debug call and do "User.objects.all()", there's nothing in the User queryset and the test fails. Am I not creating three mock User instances that will be queried the the UserProxy's static method?
I'm using Django 1.6 and Postgres 9.3 and running the test with the command "python manage.py test -s apps.profile.tests.model_tests:TestUserProxy".
Thanks!
# apps/profile/models.py
from django.contrib.auth.models import User
class UserProxy(User):
class Meta:
proxy = True
#staticmethod
def get_user_ids(usernames):
debug()
user_ids = []
for name in usernames:
try:
u = User.objects.get(username__exact=name)
user_ids.append(u.id)
except ObjectDoesNotExist:
logger.error("We were unable to find '%s' in a list of usernames." % name)
return user_ids
# apps/profile/tests/model_tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock
from apps.profile.models import UserProxy
class TestUserProxy(TestCase):
def test_get_user_ids(self):
u1 = Mock(spec=User)
u1.id = 1
u1.username = 'user1'
u2 = Mock(spec=User)
u2.id = 2
u2.username = 'user2'
u3 = Mock(spec=User)
u3.id = 3
u3.username = 'user3'
usernames = [u1.username, u2.username, u3.username]
expected = [u1.id, u2.id, u3.id]
actual = UserProxy.get_user_ids(usernames)
self.assertEqual(expected, actual)
Mocking is awesome for testing, and can lead to very clean tests, however it suffers a little from (a) being a bit fiddly to get ones head around when starting out, and (b) does require some effort often to set up mock objects and have then injected/used in the correct places.
The mock objects you are creating for the users are objects that look like a Django User model object, but they are not actual model objects, and therefore do not get put into the database.
To get your test working, you have two options, depending on what kind of test you want to write.
Unit Test - Mock the data returned from the database
The first option is to get this working as a unit test, i.e. testing the get_user_ids method in isolation from the database layer. To do this, you would need to mock the call to User.objects.get(username__exact=name) so that it returns the three mock objects you created in your test. This would be the more correct approach (as it is better to test units of code in isolation), however it would involve more work to set up than the alternative below.
One way to achieve this would be to firstly separate out the user lookup into it's own function in apps/profile/models.py:
def get_users_by_name(name):
return User.objects.get(username__exact=name)
This would need to be called in your function, by replacing the call to Users.objects.get(username__exact=name) with get_users_by_name(name). You can then modify your test to patch the function like so:
from django.test import TestCase
from django.contrib.auth.models import User
from mock import Mock, patch
from apps.profile.models import UserProxy
class TestUserProxy(TestCase):
#patch('apps.profile.models.get_user_by_name')
def test_get_user_ids(self, mock_get_user_by_name):
u1 = Mock(spec=User)
u1.id = 1
u1.username = 'user1'
u2 = Mock(spec=User)
u2.id = 2
u2.username = 'user2'
u3 = Mock(spec=User)
u3.id = 3
u3.username = 'user3'
# Here is where we wire up the mocking - we take the patched method to return
# users and tell it that, when it is called, it must return the three mock
# users you just created.
mock_get_user_by_name.return_value = [u1, u2, u3]
usernames = [u1.username, u2.username, u3.username]
expected = [u1.id, u2.id, u3.id]
actual = UserProxy.get_user_ids(usernames)
self.assertEqual(expected, actual)
Integration Test - Create real user objects
The second approach is to modify this to be an integration test, i.e. one that tests both this unit of code and also the interaction with the database. This is a little less clean, in that you are now exposing your tests on the method to the chance of failing because of problems in a different unit of code (i.e. the Django code that interacts with the database). However, this does make the setup of the test a lot simpler, and pragmatically may be the right approach for you.
To do this, simply remove the mocks you have created and create actual users in the database as part of your test.

Django tests work using SQLite but not MySQL

The tests that I have written for my Django application have been working perfectly during initial development where I have been using SQLite. Now that I am getting ready to deploy I have setup a MySQL server (as that is what I'll be deploying to) but now some of my tests are failing.
Lastly the tests that are failing don't fail when I manually test the functionality.
What could be going on?
I'm not doing anything unusual, all of the views do some database shenanigans and return a response. There isn't anything timing related (no threading or anything).
The tests all inherit from django.test.TestCase and I'm not using any fixtures.
Here is an example of a test that fails.
class BaseTest(TestCase):
def setUp(self):
super(BaseTest, self).setUp()
self.userCreds = dict(username='test', password='a')
# Create an admin user
admin = models.User.objects.create_superuser(
email='', username='admin', password='a')
# Create a user and grant them a licence
user = models.User.objects.create_user(
email='some#address.com', first_name="Mister",
last_name="Testy", **self.userCreds)
profile = models.getProfileFor(user)
node = profile.createNode(
'12345', 'acomputer', 'auser',
'user#email.com', '0456 987 123')
self.node = node
class TestClientUIViews(BaseTest):
def test_toggleActive(self):
url = reverse('toggleActive') + '?nodeId=%s' % self.node.nodeId
self.assertFalse(self.node.active)
# This should fail because only authenticated users can toggle a node active
resp = self.client.get(url)
self.assertEqual(resp.status_code, 403)
self.assertFalse(self.node.active)
# Login and make sure visiting the url toggles the active state
self.client.login(**self.userCreds)
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertTrue(self.node.active)
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertFalse(self.node.active)
And here is what the model looks like:
class Node(models.Model):
#property
def active(self):
'''
Activation state gets explictly tracked in its own table but is
exposed as a property for the sake of convenience
'''
activations = NodeActivation.objects \
.filter(node=self) \
.order_by('-datetime')
try:
return activations[0].active
except IndexError:
return False
#active.setter
def active(self, state):
if self.active != state:
NodeActivation.objects.create(node=self, active=state)
class NodeActivation(models.Model):
node = models.ForeignKey("Node")
datetime = models.DateTimeField(default=datetimeM.datetime.now)
active = models.BooleanField(default=False)
My local MySQL is 5.5.19 (so its using InnoDB) but I get the same failures on the deployment server which is using 5.1.56. The tests fail regardless of the storage engine.
And as I mentioned at the beginning, if I switch back to use a SQLite database, all the tests go back to passing.
With more of the actual code now revealed I'll provide the following hypothesis as to why this test is failing.
The NodeActivation model is incorrect. The datetime field should be:
datetime = models.DateTimeField(auto_now=True)
Using datetime.datetime.now() in a model definition will only be evaluated once.
Every time the setter creates a new NodeActivation record, the record will be created with the same date/time. ie The date/time that the NodeActivation model was first evaluated.
Your getter only ever returns a single result. But since both activation records have the same date/time, the ordering may be dependent on the database back end. There will be two NodeActivation records in your database at the end of the test, which one is returned is indeterminate.
By changing the active property on the Node model class to this:
#property
def active(self):
'''
Activation state gets explictly tracked in its own table but is
exposed as a property for the sake of convenience
'''
activations = NodeActivation.objects \
.filter(node=self) \
.order_by('-id')
try:
return activations[0].active
except IndexError:
return False
the problem goes away.
Note the change to the order_by call.
The records were getting created so quickly that ordering by datetime wasn't deterministic, hence the erratic behaviour. And I guess SQLite is just slower than MySQL which is why it wasn't a problem when using it as the backing database.
NOTE: Thanks to Austin Phillips for the tip (check out comments in his answer)
I had a similar problem (not sure if its the same) going from SQLite to PostgreSQL using a setup method like you have for your initial database objects.
Example::
def setUp(self):
user = User.objects.create_user(username='john', password='abbeyRd', email='john#thebeatles.com')
user.save()
I found that this would work fine on SQLite, but on PostgreSQL I was getting database connection failures (it would still build the database like you said, but couldn't run the tests without a connection error).
Turns out, the fix is to run your setup method as follows:
#classmethod
def setUpTestData(cls):
user = User.objects.create_user(username='john', password='abbeyRd', email='john#thebeatles.com')
user.save()
I don't remember the exact reason why this works and the other one didn't, but it had something to do with the connection to the database trying to reconnect every test if you use setUp(self) and if you use setUpTestData(cls) it only connects when the class is instantiated, but I might not have the details perfect. All I really know is it works!
Hope that helps, not sure if it addresses your exact issue, but it took me a good few hours to figure out, so I hope it helps you and maybe others in the future!

How to add Check Constraints for Django Model fields?

While subclassing db.models.Model, sometimes it's essential to add extra checks/constraints.
For example, I have an Event model with start_date and end_date: I want to add validation into the fields or the model so that end_date > start_date.
At least I know this can be done outside the models.Model inside the ModelForm validation. But how to attach to the fields and the models.Model?
I would not put constraints like these in the save method, it's too late. Raising an exception there, doesn't help the user who entered the data in the wrong way, because it will end up as a 500 and the user won't get the form with errors back etc.
You should really check for this in the Forms/ModelForms clean method and raise a ValidationError, so form.is_valid() returns false and you can send the errors in the form back to the user for correction.
Also note that since version 1.2, Django has had Model Validation.
It would look something like this:
class Foo(models.Model):
# ... model stuff...
def clean(self):
if self.start_date > self.end_date:
raise ValidationError('Start date is after end date')
As of Django 2.2, database level constraints are supported:
from django.db import models
from django.db.models import CheckConstraint, Q, F
class Event(models.Model):
start_date = models.DatetimeField()
end_date = models.DatetimeField()
class Meta:
constraints = [
CheckConstraint(
check = Q(end_date__gt=F('start_date')),
name = 'check_start_date',
),
]
Do it inside your save method of your model:
def save(self, *args, **kwargs):
if(self.end_date > self.start_date):
super(Foo, self).save(*args, **kwargs)
else:
raise Exception, "end_date should be greater than start_date"
As #stefanw says, it's better user experience to check in the form's clean method.
This is enough if you're very sure that there isn't, and never will be, another way to change the value. But since you can rarely be sure of that, if database consistency is important, you can add another check (in addition to the form), one of:
The easier and database-independent way is in the model's save method as #umnik700 said. Note that this still doesn't prevent other users of the database (another app, or the admin interface) from creating an inconsistent state.
To be 'completely' sure the database is consistent, you can add a database level constraint. E.g. you can create a migration with RunSQL and SQL, something like (not tested):
migrations.RunSQL('ALTER TABLE app_event ADD CONSTRAINT chronology CHECK (start_date > end_date);')
(Not tested). This may be database dependent, which is a downside of course.
In your example, it's probably not worth it (incorrect start/end times just look a bit weird, but affect only the one inconsistent event), and you don't want manual schema changes. But it's useful in cases where consistency is critical.
EDIT: You can also just save the start time and the duration, instead of the start and end times.
As of today, both postgres 9.4 and MS SQL Server >= 2008 support check constraints in sql. On top of this, there is django issue 11964 which seems to be ready for review since yesterday, so hopefully we'll see this integrated into django 2. The project rapilabs/django-db-constraints seems to implement this too.
Summarizing the answers from before, here is a complete solution I used for a project:
from django.db import models
from django.db.models import CheckConstraint, Q, F
from django.utils.translation import gettext_lazy as _
class Event(models.Model):
start_date = models.DatetimeField()
end_date = models.DatetimeField()
class Meta:
constraints = [
# Ensures constraint on DB level, raises IntegrityError (500 on debug=False)
CheckConstraint(
check=Q(end_date__gt=F('start_date')), name='check_start_date',
),
]
def clean(self):
# Ensures constraint on model level, raises ValidationError
if self.start_date > self.end_date:
# raise error for field
raise ValidationError({'end_date': _('End date cannot be smaller then start date.')})
Too bad there is no django.core.validators that can handle this :(