Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Прокси-модель Django для другой базы данных

Автор вопроса: BonoОтветов: 3

Ситуация


У нас есть несколько различных приложений, которые используют тикеты из системы поддержки тикетов для различных функций.

Во-первых, у нас есть приложение, в котором есть несколько моделей, представляющих модели нашей системы поддержки билетов 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
  • @ e4c5 Да, я знаю это. Это хорошо и все такое, но если это имеет значение при перезаписи. Bono 11.01.2017
  • Вы сами так говорите. Это потому, что прокси-модель может быть передана другой моделью в будущем переносе. Зачем в противном случае создавать миграцию, если потом ее можно просто выбросить? Bono 11.01.2017
  • Тут ты меня подловил!! Отзыв закрытого голосования. e4c5 11.01.2017
  • @e4c5 Спасибо :) Без обид ;) Bono 11.01.2017
  • нисколько. Вот +1 от меня e4c5 11.01.2017
  • Пожалуйста, объясните, какие модели должны быть представлены таблицами. Имена KayakoTicket и Contact в настоящее время не определены. Модель TickedWrapper не создает отношения между Contact и SidebarTicket, поскольку это недопустимый прокси (без данных). Основная проблема - правильная модель базы данных, прошедшая проверку python manage.py check. Второстепенная важная проблема заключается в том, сколько баз данных используется и для них правильные маршрутизаторы db. hynekcer 14.01.2017

pythondjangodjango-migrationsproxy-classes

Ответы

Ответ 1 · рейтинг 6 · Anna Sirota

Как сказал @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

Ответ 2 · рейтинг 4 · yedpodtrzitko

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

Ответ 3 · рейтинг 4 · hynekcer

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. Один и тот же контакт может быть разделен на большее количество заявок. (Разве это не зарегистрированный пользователь? Он не может ничего изменить в своем профиле пользователя во время истории выдачи?) Это должен быть, вероятно, внешний ключ к Контакту, а не множественное наследование.

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы