Using Django ORM get_or_create with multiple databases - python

Django's ORM supports querying from a specific database (when multiple are defined in your project) via the .using() function for filter-based operations.
e.g. MyModel.objects.filter(name='Bob').using('my_non_default_database')
How would you do the equivalent when creating new records, via the class MyModel() or shortcut like get_or_create()?

using is a method on the MyModel.objects manager, so you can do
MyModel.objects.using('my_non_default_database').get_or_create(name="Bob")
If you have a MyModel instance, you can use the using keyword to specify the database to save to. The django docs point out some gotchas.
my_model = MyModel(name="Bob")
my_model.save(using='my_non_default_database')

using is just part of the standard query chain. So, you can use it anywhere before the query is sent to the DB. get_or_create, unlike filter, is atomic: when you call it, it does it's thing immediately, so you just need to call using before that.
MyModel.objects.using('my_non_default_database').get_or_create(name='Bob')

Related

Choosing from a populated table in factory boy while using flask

In factory boy it is possible to create a sqlalchemy object with a relationship to another table. There is a common recipe for Choosing from a populated table which is made for django, and I want to do this now for flask.
So I'd think that I have use this line.
user = factory.Iterator(User.query.all())
But of course it doesn't work. I get an error that there is no application context. It states in the documentation that the query wont be evaluated until the first call to to the Userfactory, however the error occurs when importing.
I want to have a factory that chooses the foreign key from an object that I already created. How can I do this in flask?
The example works for Django because a QuerySet is lazy; i.e. the request only hits the database when trying to iterate on it.
FactoryBoy's Iterator will only start iterating on the provided generator when the first factory call is made.
For SQLAlchemy, you need a way to return a generator instead of a list; I think this would work with factory.Iterator(User.query).
User.query.all() explicitly sends the SQL request and retrieves the results, which will fail before the app is properly configured.
If that doesn't work (the SQLAlchemy docs aren't very clear on the Query iteration behaviour), you could try with a helper function:
def lazy_users():
"""Turn `User.query.all()` into a lazily evaluated generator"""
yield from User.query.all()
class ProfileFactory(...):
user = factory.Iterator(lazy_users())

Do Django Model Managers require using=self._db

In working with the Django user model I've noticed model managers include a using=self._db parameter when acting on a database. If I'm only using a single database, is this necessary? What does using=self._db do other than specify the database. Is this specified as a fail-safe in the event another database is added?
Managers use that parameter to define on which database the underlying queryset the manager uses should operate on. This is simply there to optionally override it in case you have e.g. multiple databases and you want your manger/queryset to operate on a specific one.

Postgres Sequences as Default Value for Django Model Field

I have a PostgreSQL database that is being used by a front-end application built with Django, but being populated by a scraping tool in Node.js. I have made a sequence that I want to use across two different tables/entities, which can be accessed by a function (nexval(serial)) and is called on every insert. This is not the primary key for these tables, but simply a way to maintain order through some metadata. Using it in Node.js during the insertion of the data into the tables is trivial, as I am using raw SQL queries. However, I am struggling with how to represent this using Django models. There does not seem to be any way to associate this Postgres function with a model's field.
Question:
Is there a way to use a Postgres function as the default value of a Django model field?
you can also set your own function for the default
from django.db import connection, models
def sequence_id():
with connection.cursor() as cursor:
cursor.execute("""SELECT nextval('model_someid_seq')""")
return cursor.fetchone()[0]
class MyModel(models.Model):
field_id = models.IntegerField(default=sequence_id)
My eventual solution:
Override the save method of the model, using a raw query to SELECT nextval('serial') inside the override, setting that as the value of the necessary field, then call save on the parent (super(PARENT, self).save()).

How do you find the database a Django object is using?

Django supports using multiple databases simultaneously, and the docs explain how to specify the database when saving or retrieving an object. However, given a Django model instance, how do you determine which database it's currently using? I've inspected an instance, but it doesn't seem to have any .using attribute or anything similar.
From the Using routers documentation, you can use model_instance._state.db to inspect the database which the current model instance is using.

Mocking a Django Queryset in order to test a function that takes a queryset

I have a utility function in my Django project, it takes a queryset, gets some data from it and returns a result. I'd like to write some tests for this function. Is there anyway to 'mock' a QuerySet? I'd like to create an object that doesn't touch the database, and i can provide it with a list of values to use (i.e. some fake rows) and then it'll act just like a queryset, and will allow someone to do field lookups on it/filter/get/all etc.
Does anything like this exist already?
For an empty Queryset, I'd go simply for using none as keithhackbarth has already stated.
However, to mock a Queryset that will return a list of values, I prefer to use a Mock with a spec of the Model's manager. As an example (Python 2.7 style - I've used the external Mock library), here's a simple test where the Queryset is filtered and then counted:
from django.test import TestCase
from mock import Mock
from .models import Example
def queryset_func(queryset, filter_value):
"""
An example function to be tested
"""
return queryset.filter(stuff=filter_value).count()
class TestQuerysetFunc(TestCase):
def test_happy(self):
"""
`queryset_func` filters provided queryset and counts result
"""
m_queryset = Mock(spec=Example.objects)
m_queryset.filter.return_value = m_queryset
m_queryset.count.return_value = 97
result = func_to_test(m_queryset, '__TEST_VALUE__')
self.assertEqual(result, 97)
m_queryset.filter.assert_called_once_with(stuff='__TEST_VALUE__')
m_queryset.count.assert_called_once_with()
However, to fulfil the question, instead of setting a return_value for count, this could easily be adjusted to be a list of model instances returned from all.
Note that chaining is handled by setting the filter to return the mocked queryset:
m_queryset.filter.return_value = m_queryset
This would need to be applied for any queryset methods used in the function under test, e.g. exclude, etc.
Of course you can mock a QuerySet, you can mock anything.
You can create an object yourself, and give it the interface you need, and have it return any data you like. At heart, mocking is nothing more than providing a "test double" that acts enough like the real thing for your tests' purposes.
The low-tech way to get started is to define an object:
class MockQuerySet(object):
pass
then create one of these, and hand it to your test. The test will fail, likely on an AttributeError. That will tell you what you need to implement on your MockQuerySet. Repeat until your object is rich enough for your tests.
I am having the same issue, and it looks like some nice person has written a library for mocking QuerySets, it is called mock-django and the specific code you will need is here https://github.com/dcramer/mock-django/blob/master/mock_django/query.py I think you can then just patch you models objects function to return one of these QuerySetMock objects that you have set up to return something expected!
For this I use Django's .none() function.
For example:
class Location(models.Model):
name = models.CharField(max_length=100)
mock_locations = Location.objects.none()
This is the method used frequently in Django's own internal test cases. Based on comments in the code
Calling none() will create a queryset that never returns any objects and no
+query will be executed when accessing the results. A qs.none() queryset
+is an instance of ``EmptyQuerySet``.
Try out the django_mock_queries library that lets you mock out the database access, and still use some of the Django query set features like filtering.
Full disclosure: I contributed some features to the project.
Have you looked into FactoryBoy? https://factoryboy.readthedocs.io/en/latest/orms.html
It's a fixtures replacement tool with support for the django orm - factories basically generate orm-like objects (either in memory or in a test database).
Here's a great article for getting started: https://www.caktusgroup.com/blog/2013/07/17/factory-boy-alternative-django-testing-fixtures/
One first advice would be to split the function in two parts, one that creates the queryset
and one that manipulates the output of it. In this way testing the second part is straightforward.
For the database problem, I investigated if django uses sqlite-in-memory and I found out that
recent version of django uses the sqlite -in-memory database, from The django unittest page:
When using the SQLite database engine the tests will by default use an
in-memory database (i.e., the database will be created in memory,
bypassing the filesystem entirely!).
Mocking the QuerySet object will not make you exercise its full logic.
You can mock like this:
#patch('django.db.models.query.QuerySet')
def test_returning_distinct_records_for_city(self, mock_qs):
self.assertTrue(mock_qs.called)
Not that I know of, but why not use an actual queryset? The test framework is all set up to allow you to create sample data within your test, and the database is re-created on every test, so there doesn't seem to be any reason not to use the real thing.

Categories

Resources