18👍
As far as I know you can’t specify the database directly with the model since it would kind of prevent the app from ever being reusable, but from what I can see in the docs:
71👍
You can’t specify a database for a model, but you can define it in a custom DB router class.
# app/models.py
class SomeModel(models.Model):
...
# app/dbrouters.py
from app.models import SomeModel
...
class MyDBRouter(object):
def db_for_read(self, model, **hints):
""" reading SomeModel from otherdb """
if model == SomeModel:
return 'otherdb'
return None
def db_for_write(self, model, **hints):
""" writing SomeModel to otherdb """
if model == SomeModel:
return 'otherdb'
return None
# app/settings.py
DATABASE_ROUTERS = ('app.dbrouters.MyDBRouter',)
...
DATABASES = {
...
'otherdb': {
....
}
}
- [Django]-Error: Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <script>
- [Django]-How do I include related model fields using Django Rest Framework?
- [Django]-Why is using thread locals in Django bad?
19👍
The simple solution is to set the manager to always use a specific database for the model. Look into Django’s using
.
Example:
class User(models.Model):
birth_date = models.DateField()
class Meta:
managed = False
db_table = 'myotherapp_user'
User.objects = User.objects.using('myotherdb')
Then you can use User.objects
and it will always use the 'myotherdb'
database instead of 'default'
.
Note that relations between models from different databases will not work, but this is an issue with Django since it does not support this out of the box.
- [Django]-Django Rest Framework, passing parameters with GET request, classed based views
- [Django]-Phpmyadmin logs out after 1440 secs
- [Django]-Python MySQLDB: Get the result of fetchall in a list
12👍
I found that you can route models pretty simply with this manager:
class SecondDbManager(models.Manager):
def get_queryset(self):
qs = super().get_queryset()
# if `use_db` is set on model use that for choosing the DB
if hasattr(self.model, 'use_db'):
qs = qs.using(self.model.use_db)
return qs
Just add use_db='databasename'
and this manager to your model and it works.
Or to further simplify it I created a base model for it:
class SecondDbBase(models.Model):
use_db = 'my_second_db'
objects = SecondDbManager()
class Meta:
abstract = True
And with this all you need to do is extend it like so. Instead of:
class Customer(models.Model):
Just do this and it works:
class Customer(SecondDbBase):
PS. I’m not sure if it’s a good practice or the best solution but it works and routing to other databases is a breeze 🙂
PPS. I’ve only ever used these for only reading and writing tables that are not managed by Django(managed = False
) so if you need to create migrations for them, I’m not sure if it works or not. Might still need to use DATABASE_ROUTERS
for that.
- [Django]-Django, Models & Forms: replace "This field is required" message
- [Django]-Pip install PIL fails
- [Django]-Django or Django Rest Framework
10👍
Building on Mark’s excellent answer – here’s how to route both model read/write operations and migrations to different databases based on an attribute of the model
# app/models.py
class SomeModel(models.Model):
class params:
db = 'default'
class SomeOtherDbModel(models.Model):
class params:
db = 'otherdb'
...
# app/dbrouters.py
import app.models
allmodels = dict([(name.lower(), cls) for name, cls in app.models.__dict__.items() if isinstance(cls, type)])
...
class MyDBRouter(object):
def db_for_read(self, model, **hints):
""" reading model based on params """
return getattr(model.params, 'db')
def db_for_write(self, model, **hints):
""" writing model based on params """
return getattr(model.params, 'db')
def allow_migrate(self, db, app_label, model_name = None, **hints):
""" migrate to appropriate database per model """
model = allmodels.get(model_name)
return(model.params.db == db)
# app/settings.py
DATABASE_ROUTERS = ('app.dbrouters.MyDBRouter',)
...
DATABASES = {
...
'otherdb': {
....
}
}
See docs here: https://docs.djangoproject.com/en/3.0/topics/db/multi-db/#database-routers
The model_name argument is passed at runtime as the lower case of the model.__name__
, hence why we built the lookup dictionary by casting this attribute to lower.
Migrations should then be run as
python3 manage.py migrate app --database default
python3 manage.py migrate app --database otherdb
- [Django]-Django Test Client Method Override Header
- [Django]-Any thoughts on A/B testing in Django based project?
- [Django]-Django 1.8 KeyError: 'manager' on relationship
6👍
Tested with Django 2.2 and pytest.
Just to streamline a bit the excellent @chris-schon answer:
class LegacyDbModel(models.Model):
class Meta:
abstract = True
_db = 'legacy_db_alias'
class LegacyDbRouter(object):
def db_for_read(self, model, **hints):
""" reading model based on params """
if not hasattr(model, 'Meta'):
return None
return getattr(model.Meta, '_db', None)
def db_for_write(self, model, **hints):
""" writing model based on params """
if not hasattr(model, 'Meta'):
return None
return getattr(model.Meta, '_db', None)
It has couple of minor benefits:
- We don’t need a
params
nested class for models fromdefault
DB. - We leverage
Meta
nested class which is already part of Django. - Router works without
allow_migrate
even for tests.
The only drawback is that we need to inherit from Meta
explicitly:
class LegacyModel(LegacyDbModel):
# ... docs, fields, etc.
class Meta(LegacyDbModel.Meta):
managed = False
db_table = 'legacy_table'
But even that is more aligned with how Django works.
Remember:
Explicit is better than implicit.
- [Django]-When to use or not use iterator() in the django ORM
- [Django]-How to get the username of the logged-in user in Django?
- [Django]-How can I call a custom Django manage.py command directly from a test driver?
0👍
You can set objects to default for your database or create specific objects.
models.py:
class YourManagerName(models.Manager):
def get_queryset(self):
return super().get_queryset().using('YourDatabaseName')
class YourModel(models.Model):
# table fields
objects = models.Manager() # The default manager
your_objects = YourManagerName() # The your specific manager
views.py:
def get_data(request):
default_db_data = YourModel.objects.all()
specific_db_data = YourModel.your_objects.all()
- [Django]-Django 1.5 – How to use variables inside static tag
- [Django]-Django – Simple custom template tag example
- [Django]-Target WSGI script cannot be loaded as Python module