Модель прокси Django в другую базу данных
ситуация
У нас есть несколько разных приложений, которые используют билеты из системы поддержки билетов для разных видов функциональности.
Прежде всего, у нас есть приложение, в котором есть несколько моделей, которые представляют модели нашей системы поддержки билетов Kayako. Это приложение не должно ничего знать о других приложениях, которые его используют, и должно оставаться как можно более универсальным. Поскольку это приложение использует существующие таблицы Kayako, оно работает в одной базе данных. Давайте назовем это приложение kayakodb
,
Одно приложение связывает клиентов из нашей базы данных клиентов с билетами в системе поддержки билетов. Ранее у этой системы было собственное представление билетов в нашей системе поддержки билетов, запрашивая билеты с помощью API, предоставленного kayakodb
, Затем он использовал это представление билетов для связи клиентов и доменов. Это, однако, было слишком сложно и не очень логично. Поэтому мы решили переключить его на прокси-модель и перенести модели, представляющие ссылки на клиентов и домены, на kayakodb
, Давайте назовем это приложение sidebar
,
Другое новое приложение отображает билеты из системы поддержки билетов в четком обзоре вместе с вызовами, чтобы наш отдел поддержки мог легко увидеть, какие звонки и билеты связаны с какими клиентами. Эта система имеет модель прокси для модели прокси sidebar
, потому что некоторые из функций, предоставляемых sidebar
модель также требуется для этого приложения наряду с некоторыми другими, которые объявляет новая модель прокси. Давайте назовем этот проект WOW
,
sidebar
а также WOW
оба приложения являются частью одного проекта / хранилища. Мы назовем этот репозиторий Coneybeach
которая имеет свою собственную базу данных. Тем не мение, kayakodb
это совершенно не связанный проект. Входит в Coneybeach
через файл требований, который мы устанавливаем через pip
,
проблема
При создании миграций для новой настройки Django создает миграцию прокси-модели для установленной
kayakodb
что, конечно, не пойдет. В любое время мы установим новую версию kayakodb
это переписало бы эту миграцию. Не говоря уже о том, что kayakodb
не должен ничего знать о том, какие модели используют его.Код
Ticket
модель внутри 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
SidebarTicket
модель прокси внутри 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
Contact
учебный класс TicketWrapper
наследуется от (по запросу Hynekcer). Эта модель используется в качестве базовой модели для TicketWrapper
и другая модель, представляющая вызовы (хотя, насколько я знаю, с этой моделью проблем нет):
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)
TicketWrapper
модель прокси внутри 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
для обеих моделей прокси. Это создает правильные миграции, но заставляет модели прокси искатьkayakodb.Ticket
модель в базе данных Coneybeach. - Я пытался указать
abstract = True
для подклассов, но не был уверен, что это было, потому что я все еще хочу иметь возможность использовать менеджер для моделей. - Я рассмотрел перенос миграции, которая создается в настоящее время на фактическую
kayakodb
проект, но я не думаю, что это хорошее решение.kayakodb
не должен ничего знать о реализации своих моделей или о том, где они используются. ./manage.py check
возвращает 0 вопросов.
Вопрос
Как я могу создать прокси-модель для модели, которая находится в другой базе данных или проекте?
редактировать
После настройки
kayakodb.Ticket
модель неуправляемой WOW
Проект пытается создать миграцию для всех моделей в kayakodb
, Результат: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
3 ответа
Как сказал @hynekcer, если kayakodb
это существующая база данных, вам нужно установить managed = False
для всех своих моделей. Однако это по-прежнему оставляет проблему миграции для прокси-модели, созданной в неправильном приложении (kayakodb
).
Хакерское исправление, которое может сработать, - это изменение app_label
прокси-модели для любого приложения, в котором можно поставить миграцию (sidebar
в этом случае) и создание маршрутизатора, который будет указывать эту модель прокси для чтения и записи из kayakodb
,
Например, модель прокси:
# in sidebar/models.py
class SidebarTicket(KayakoTicket):
class Meta:
proxy = True
app_label = 'sidebar'
и маршрутизатор внутри проекта, который его использует:
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 Использование
class Meta:
managed = False
для всех моделей в БД, которые не должны контролироваться Django. (т.е. каякодб)
Следует отметить, что PyPI Kayako
Python API (без Djago) для некоторого приложения Kayako, написанного на другом языке
Полезно знать от вас, что Kayako и WOW находятся в разных базах данных, но это не фундаментальная информация. Django допускает, например, что одна модель присутствует в двух базах данных: первичной и вторичной БД. Наиболее важными являются мета-опции, опция managed = False
в этом случае. Это для случая интеграции Django с устаревшей базой данных. Это означает не только очень старый проект, но и проект, который не написан на Python+Django или не поддерживает миграции и не передает информацию о том, какие миграции еще не применены. Возможно, если новая версия Kayako добавит новые поля в базу данных, вам не нужно читать это поле или если вы добавите его в модель, Django не несет ответственности за добавление поля в базу данных, поскольку оно контролируется обновлением Kayako.
Вы также можете использовать маршрутизаторы базы данных, чтобы не создавать таблиц для приложения kayakodb (нигде), а также таблиц в базе данных kayakodb, например, для пользователей и групп Django:
файл myrouter.py или аналогичный
class MyRouter(object):
allow_migrate(db, app_label, model_name=None, **hints):
if app_label == 'kayakodb' or db == 'kayakodb':
return False
файл settings.py
DATABASE_ROUTERS = ['path.to.myrouter.MyRouter',...]
# ... if another router has been defined previously
Преимущество состоит в том, что это отключает миграцию для любой текущей или будущей модели, но я рекомендую добавить текст class Meta: managed = False
также где-то в комментариях kayakodb/models.py
чтобы сделать это понятным любому более позднему разработчику, потому что он может легко забыть сначала прочитать маршрутизаторы.
Вы можете написать зависимость версий вашего проекта от минимальной и максимальной версии API Kayako, но это не может быть в форме миграции.
Еще одна проблема заключается в том, что " прокси-модель должна наследоваться только от одного неабстрактного модельного класса...", но ваша прокси-модель TicketWrapper
наследуется отContact
а также SidebarTicket
, Это похоже на глупость, и мне интересно, что вы не видите ошибку TypeError: Proxy model 'TicketWrapper' has more than one non-abstract model base class.
Один и тот же контакт может быть использован несколькими билетами. (Разве это не зарегистрированный пользователь? Разве он не может изменить что-либо в своем профиле пользователя за всю историю проблемы?) Вероятно, это должен быть внешний ключ для контакта, а не множественное наследование.
Тл; д-р, но я ответил на ваш вопрос router
который не упоминается, так что я думаю, что вы ищете, это маршрутизаторы базы данных