First of all we have an application which has a few models that represent the models of our ticket support system Kayako. This application should not know anything about other applications that make use of it and should remain as generic as possible. Since this application is using existing tables of Kayako we have it running on the same database. Let's call this application kayakodb
.
One application links customers from our customer database to tickets in the ticket support system. Previously this system had it's own representation of the tickets inside our ticket support system, querying for tickets by using an API provided by kayakodb
. It then used this representation of tickets to link customers and domains to. This however was too complex and not very logical. So we opted to switch it to a proxy model and move the models that represent the links to customers and domains to kayakodb
. Let's call this application sidebar
.
Another, new application, shows the tickets from the ticket support system in a clear overview alongside calls so our support department can easily see which calls and tickets are related to which customers. This system has a proxy model to the proxy model of sidebar
, because some of the functionality provided by the sidebar
model are also required for this application alongside some others which the new proxy model declare. Let's call this project WOW
.
The sidebar
and WOW
applications are both part of the same project/repository. We'll call this repository Coneybeach
which has its own database. However, kayakodb
is a completely unrelated project. It is included in Coneybeach
via a requirements file which we install via pip
.
kayakodb
which is, of course, a no go. Any time we would install a new version of kayakodb
it would overwrite this migration. Let alone the fact that kayakodb
shouldn't know anything about which models make use of it.
Ticket
model inside kayakodb
:
class Ticket(models.Model):
"""
This model is a representation of the data stored in the "kayako" database table "swtickets". Minus a lot of stuff
we don't use. If you add a field make sure it has the same name as the field in kayako.swtickets.
"""
# Fields, functions and manager etc.
class Meta:
db_table = 'swtickets'
managed = False
The SidebarTicket
proxy model inside sidebar
:
from kayakodb.models import Ticket
class SidebarTicket(Ticket):
class Meta:
# Since this class is a wrapper we don't want to create a table for it. We only want to access the original
# model as we always do, but provide a different interface (when it comes to functions). Proxy models allow us
# to do this: https://docs.djangoproject.com/en/1.10/topics/db/models/#proxy-models
proxy = True
# Don't look for this model in the sidebar tables, but in the kayakodb tables.
app_label = 'kayakodb'
# Some extra functions
The Contact
class TicketWrapper
inherits from (as requested by Hynekcer). This model is used as base model for TicketWrapper
and another model representing calls (though there are not issues with this model as far as I'm aware):
class Contact(models.Model):
type = None
class Meta:
abstract = True
def __getattr__(self, attr):
if attr in ['customers', 'add_customer_id', 'remove_all_customers', 'byters', 'domainnames', 'add_domain_name',
'remove_domain_name', 'add_text', 'remove_text', 'texts', 'creation_date', 'add_tag', 'get_tags',
'remove_tag', 'identifier']:
raise NotImplementedError('You should implement {}'.format(attr))
raise AttributeError(attr)
The TicketWrapper
proxy model inside WOW
:
from sidebar.models import SidebarTicket
class TicketWrapper(Contact, SidebarTicket):
class Meta:
# Since this class is a wrapper we don't want to create a table for it. We only want to access the original
# model as we always do, but provide a different interface (when it comes to functions). Proxy models allow us
# to do this: https://docs.djangoproject.com/en/1.10/topics/db/models/#proxy-models
proxy = True
# Don't look for this model in the WOW database, but in the kayakodb database.
app_label = 'kayakodb'
# Some extra functions
app_label
for both the proxy models. This creates correct migrations, but causes the proxy models to look for the kayakodb.Ticket
model in the Coneybeach database.abstract = True
for the subclasses, but wasn't sure this was the because I still want to be able to make use of the manager for the models.kayakodb
project, but I don't think this is a good solution. kayakodb
shouldn't know anything about the implementations of its models or where they are used../manage.py check
returns 0 issues.kayakodb.Ticket
model to be unmanaged the WOW
project tries to create a migration for all models in kayakodb
. Result:
Migrations for 'sidebar':
0004_auto_20170116_1210.py:
- Delete model Ticket
Migrations for 'kayakodb':
0001_initial.py:
- Create model Staff
- Create model Tag
- Create model Ticket
- Create model TicketPost
- Create model TicketTag
- Create model TicketCustomer
- Create model TicketDomain
- Create proxy model SidebarTicket
- Alter unique_together for ticketdomain (1 constraint(s))
- Alter unique_together for ticketcustomer (1 constraint(s))
- Create proxy model TicketWrapper
Django's admin doesn't have any explicit support for multiple databases. If you want to provide an admin interface for a model on a database other than that specified by your router chain, you'll need to write custom ModelAdmin classes that will direct the admin to use a specific database for content.
Proxy models allow us to change the Python behavior of a model without changing the database. Proxy models are declared just like normal models. In our example, we tell Django that Honda is a proxy model by setting the proxy attribute of the Honda Meta class to True .
As @hynekcer said if kayakodb
is an existing database, you need to set managed = False
for all its models.
However, that still leaves the problem of the migration for the proxy model created inside the wrong app (kayakodb
).
The hacky fix that might work is changing the app_label
of the proxy model to whatever app is okay to put the migration to (sidebar
in this case), and making a router that will point this proxy model to read and write from kayakodb
.
E.g. the proxy model:
# in sidebar/models.py
class SidebarTicket(KayakoTicket):
class Meta:
proxy = True
app_label = 'sidebar'
and the router inside the project that uses it:
from django.conf import settings
from kayakodb.models import Ticket
class ProxyDatabaseRouter(object):
def allow_proxy_to_different_db(self, obj_):
# check if this is a sidebar proxy to the Ticket model in kayakodb
return isinstance(obj_, Ticket) and obj_._meta.proxy and obj_._meta.app_label == 'sidebar'
def db_for_read(self, model, **hints):
if issubclass(model, Ticket) and model._meta.proxy and model._meta.app_label == 'sidebar':
return 'kayakodb'
# the rest of the method goes here
def db_for_write(self, model, **hints):
if issubclass(model, Ticket) and model._meta.proxy and model._meta.app_label == 'sidebar':
return 'kayakodb'
return None
# the rest of the method goes here
def allow_relation(self, obj1, obj2, **hints):
if self.allow_proxy_to_different_db(obj1) or self.allow_proxy_to_different_db(obj2):
return True
# the rest of the method goes here
tl;dr but I grepped your question for a word router
which is not mentioned, so I'd think the thing you are looking for are Database Routers
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With