Прокси-модель 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

person Bono    schedule 11.01.2017    source источник
comment
@ e4c5 Да, я знаю это. Это хорошо и все такое, но если это имеет значение при перезаписи.   -  person Bono    schedule 11.01.2017
comment
Вы сами так говорите. Это потому, что прокси-модель может быть передана другой моделью в будущем переносе. Зачем в противном случае создавать миграцию, если потом ее можно просто выбросить?   -  person Bono    schedule 11.01.2017
comment
Тут ты меня подловил!! Отзыв закрытого голосования.   -  person e4c5    schedule 11.01.2017
comment
@e4c5 Спасибо :) Без обид ;)   -  person Bono    schedule 11.01.2017
comment
нисколько. Вот +1 от меня   -  person e4c5    schedule 11.01.2017
comment
Пожалуйста, объясните, какие модели должны быть представлены таблицами. Имена KayakoTicket и Contact в настоящее время не определены. Модель TickedWrapper не создает отношения между Contact и SidebarTicket, поскольку это недопустимый прокси (без данных). Основная проблема — правильная модель базы данных, прошедшая проверку python manage.py check. Второстепенная важная проблема заключается в том, сколько баз данных используется и для них правильные маршрутизаторы db.   -  person hynekcer    schedule 14.01.2017
comment
@hynekcer Ах, мой плохой KayakoTicket - это просто kayakodb.Ticket, но импортированный с оператором as. Я добавлю определение класса Contact. Единственными моделями, которые должны иметь таблицы, представляющие их, являются модели в проекте kayakodb. К вашему сведению, ./manage.py check возвращает 0 ошибок или предупреждений (Django 1.9.10).   -  person Bono    schedule 16.01.2017


Ответы (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
person Anna Sirota    schedule 13.01.2017
comment
Это полезно, но условие if model == SidebarTicket: по-прежнему недействительно, потому что SidebarTicket — это всего лишь прокси, но модель базы данных еще недостаточно описана, и поэтому правильное условие неизвестно. - person hynekcer; 14.01.2017
comment
@hynekcer, как я уже сказал, как именно выполняется сравнение, не важно, рассмотрим приведенный выше половинный псевдокод. БД для чтения и записи может храниться даже в самой модели, что позволяет методам маршрутизатора при необходимости возвращать getattr(model, "_CUSTOM_DATABASE", None), что не является проблемой, поскольку модель передается в методы маршрутизатора. - person Anna Sirota; 16.01.2017
comment
обновлен фрагмент кода маршрутизатора с предложениями better if и отсутствующим методом allow_relation - person Anna Sirota; 16.01.2017

tl;dr, но я нашел в вашем вопросе слово router, которое не упоминается, поэтому я думаю, что вы ищете Маршрутизаторы баз данных

person yedpodtrzitko    schedule 11.01.2017
comment
Если возникает вопрос, как создать прокси-модель для модели, расположенной в другой базе данных или проекте, ответом может быть использование маршрутизатора базы данных. Извините за попытку помочь. - person yedpodtrzitko; 11.01.2017
comment
Извини, не хотел показаться грубым. Я действительно сожалею. Запись модели в другую базу данных не проблема (во всяком случае, для меня). Проблема в том, что миграция создается не в том месте (а если бы вы читали вопрос, то увидели бы это :)). Сообщите вам, если вы отредактируете свой вопрос, включив в него дополнительную информацию о маршрутизаторах БД (приведите пример). Я смогу удалить свой голос против. Глядя исключительно на сам вопрос, я согласен, что это могло быть ответом. - person Bono; 11.01.2017

tl;dr Использование

class Meta:
    managed = False

для всех моделей в базе данных, которые не должны контролироваться Django. (например, каякодб)


Следует отметить, что PyPI Kayako — это Python API (без Djago) для некоторого приложения Kayako, написанного на другом языке.

Полезно узнать от вас, что Kayako и WOW находятся в разных базах данных, но это не принципиальная информация. Джанго позволяет, например. что одна модель присутствует в двух базах данных: первичной и вторичной БД. Наиболее важными являются мета-опции, в данном случае это опция 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, чтобы сделать это понятным любому последующему разработчику, потому что он может легко забыть сначала прочитать маршрутизаторы.

Вы можете написать зависимость версий вашего проекта от минимальной и максимальной версии Kayako API, но это не может быть в виде миграции.


Другая ваша проблема заключается в том, что «модель прокси должен наследовать ровно от одного неабстрактного класса модели....", но ваша прокси-модель TicketWrapper наследуется от Contact и SidebarTicket. Это выглядит как бред, и мне интересно, что вы не видите ошибку TypeError: Proxy model 'TicketWrapper' has more than one non-abstract model base class. Один и тот же контакт может быть разделен на большее количество заявок. (Разве это не зарегистрированный пользователь? Он не может ничего изменить в своем профиле пользователя во время истории выдачи?) Это должен быть, вероятно, внешний ключ к Контакту, а не множественное наследование.

person hynekcer    schedule 13.01.2017
comment
Contact в данном случае является абстрактной моделью Django, так что все в порядке. Я установил managed = False для kayakodb моделей. Но когда я пытаюсь создать миграцию в проекте WOW, теперь он пытается создать миграцию для всех моделей kayakodb (даже если они неуправляемые). При настройке маршрутизатора на запрет миграции для моделей kayakodb это работает, но это также означает, что миграция прокси-модели не создается, что мне требуется (AFAIA). Поэтому я все еще сталкиваюсь с проблемой, что не могу создать прокси-модель (или миграцию для нее) для моделей, указанных в kayakodb. - person Bono; 16.01.2017