Вопрос по django-models, constraints, foreign-keys, database – Django: «мягкое» ForeignField без проверки целостности базы данных

11

У меня есть проект Django, в котором есть несколько django «приложений». У одной из них есть модели для представления данных, поступающих из внешнего источника (я не контролирую эти данные).

Я хочу, чтобы мои другие приложения могли иметь ссылки на это & quot; внешнее приложение & quot; но я хочу избежать всех размышлений проверок целостности базы данных. Я не хочу, чтобы база данных имела какие-либо ограничения на эти "программные внешние ключи".

Знаете ли вы, как я могу кодировать настраиваемое поле, которое будет эмулировать настоящий Django ForeignKey без создания жесткого ограничения для базы данных?

Возможно, это уже существует, но мне не повезло в Google.

Заранее спасибо за помощь :-)

NB. Мне известно ородовые отношения система с типами содержимого. Но я не хочу родственных отношений. Я хочу, чтобы конкретные отношения с идентифицированными моделями только без жестких ограничений целостности.

РЕДАКТИРОВАТЬ:

Я нашел связанные ссылки:

Django ForeignKey which does not require referential integrity? Understanding / mySQL aka tricking ForeignKey relationships in Django

Но я не нашел правильного ответа на свой вопрос. :(

РЕДАКТИРОВАТЬ 2012, 4 июня:

Я изучил код django, чтобы найти, что нужно сделать, но я думаю, что простого подкласса ForeignKey будет недостаточно. Не могли бы вы дать мне несколько указаний о том, как это сделать?

NB. Я использую South для управления своей схемой базы данных, поэтому я полагаю, что мне тоже нужно что-то с этим делать. Но это может быть не в тему здесь :)

Хорошо, я хочу использовать все возможности django ForeignKey без ограничения db. Robin
Например, я хочу иметь возможность удалить строку из таблицы, на которую ссылается этотSoftForeignKey без каскадирования или установки ключа наNULL. И если объект имеет ссылку на несуществующую строку в целевой таблице, он должен поднятьObjectDoesNotExist исключение. Но я хочу, чтобы база данных принимала такое состояние. Robin
может бытьэт помогает. marianobianchi
Значит, это не чужой ключ, не так ли? Ignacio Vazquez-Abrams

Ваш Ответ

6   ответов
3

Мне удалось сделать то, что я хотел.

Во-первых, я создал новое поле:

from django.db.models.deletion import DO_NOTHING
from django.db.models.fields.related import ForeignKey, ManyToOneRel

class SoftForeignKey(ForeignKey):
    """
    This field behaves like a normal django ForeignKey only without hard database constraints.
    """
    def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
        ForeignKey.__init__(self, to, to_field=to_field, rel_class=rel_class, **kwargs)
        self.on_delete = DO_NOTHING

    no_db_constraints = True

Так как я использую Юг для управления схемой базы данных, мне пришлось добавить это:

from south.modelsinspector import add_introspection_rules
add_introspection_rules([], [r'^ecm\.lib\.softfk\.SoftForeignKey'])

Затем мне пришлось обезьянить патч на юг, чтобы no_db_constraints параметр в учет. В создании ограничений FK были задействованы две функции:

from django.db.models.deletion import DO_NOTHING
from django.db.models.fields.related import ForeignKey, ManyToOneRel
from django.core.management.color import no_style
from south.db.generic import DatabaseOperations, invalidate_table_constraints, flatten

def column_sql(self, table_name, field_name, field, tablespace='', with_name=True, field_prepared=False):
    """
    Creates the SQL snippet for a column. Used by add_column and add_table.
    """

    # If the field hasn't already been told its attribute name, do so.
...
...
...

        if field.rel and self.supports_foreign_keys:
            # HACK: "soft" FK handling begin
            if not hasattr(field, 'no_db_constraints') or not field.no_db_constraints:
                self.add_deferred_sql(
                    self.foreign_key_sql(
                        table_name,
                        field.column,
                        field.rel.to._meta.db_table,
                        field.rel.to._meta.get_field(field.rel.field_name).column
                    )
                )
            # HACK: "soft" FK handling end

    # Things like the contrib.gis module fields have this in 1.1 and below
    if hasattr(field, 'post_create_sql'):
        for stmt in field.post_create_sql(no_style(), ta
....
....

# monkey patch South here
DatabaseOperations.column_sql = column_sql

А также

from django.db.models.deletion import DO_NOTHING
from django.db.models.fields.related import ForeignKey, ManyToOneRel
from django.core.management.color import no_style
from south.db.generic import DatabaseOperations, invalidate_table_constraints, flatten

@invalidate_table_constraints
def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
    """
    Alters the given column name so it will match the given field.
    Note that conversion between the two by the database must be possible.
    Will not automatically add _id by default; to have this behavour, pass
    explicit_name=False.

    @param table_name: The name of the table to add the column to
    @param name: The name of the column to alter
    @param field: The new field definition to use
    """

    if self.dry_run:
        if self.debug:
...
...
    if not ignore_constraints:
        # Add back FK constraints if needed
        if field.rel and self.supports_foreign_keys:
            # HACK: "soft" FK handling begin
            if not hasattr(field, 'no_db_constraints') or not field.no_db_constraints:
                self.execute(
                    self.foreign_key_sql(
                        table_name,
                        field.column,
                        field.rel.to._meta.db_table,
                        field.rel.to._meta.get_field(field.rel.field_name).column
                    )
                )
            # HACK: "soft" FK handling end

# monkey patch South here
DatabaseOperations.alter_column = alter_column

Это действительно ужасно, но я не нашел другого выхода.

Теперь вы можете использовать поле SoftForeignKey точно так же, как и обычный ForeignKey, за исключением того, что у вас не будет никакого обеспечения ссылочной целостности.

Смотрите полный патч для обезьян:http: //eve-corp-management.org/projects/ecm/repository/entry/ecm/lib/softfk.p

3

ля, просто добавьтеdb_constraint=False в это поле.

user = models.ForeignKey('User', db_constraint=False)

Смотрите также:Django - Как предотвратить создание ограничений внешнего ключа базы данных

2

но оно не сработало, потому что у меня есть другие столбцы, кроме «поддельной ФК». Код, который я пробовал, был:

class DynamicPkg(models.Model):
    @property
    def cities(self):
        return City.objects.filter(dpdestinations__dynamic_pkg=self)


class DynamicPkgDestination(models.Model):
    dynamic_pkg = models.ForeignKey(DynamicPkg, related_name='destinations')
    # Indexed because we will be joining City.code to
    # DynamicPkgDestination.city_code and we want this to be fast.
    city_code = models.CharField(max_length=10, db_index=True)


class UnmanagedDynamicPkgDestination(models.Model):
    dynamic_pkg = models.ForeignKey(DynamicPkg, related_name='destinations')
    city = models.ForeignKey('City', db_column='city_code', to_field='code', related_name='dpdestinations')

    class Meta:
        managed = False
        db_table = DynamicPkgDestination._meta.db_table


class City(models.Model):
    code = models.CharField(max_length=10, unique=True)

и ошибки, которые я получил, были:

Error: One or more models did not validate:
travelbox.dynamicpkgdestination: Accessor for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.
travelbox.dynamicpkgdestination: Reverse query name for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.
travelbox.unmanageddynamicpkgdestination: Accessor for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.
travelbox.unmanageddynamicpkgdestination: Reverse query name for field 'dynamic_pkg' clashes with related field 'DynamicPkg.destinations'. Add a related_name argument to the definition for 'dynamic_pkg'.

Как бы то ни было, я нашел рабочее решение с использованием прокси-модели. Мне все еще приходилось взламывать некоторые проверки Django, которые не позволяют включать поля в модели прокси:

class DynamicPkg(models.Model):
    @property
    def cities(self):
        return City.objects.filter(dpdestinations__dynamic_pkg=self)



def proxify_model(new_class, base):
    """
    Like putting proxy = True in a model's Meta except it doesn't spoil your
    fun by raising an error if new_class contains model fields.
    """
    new_class._meta.proxy = True
    # Next 2 lines are what django.db.models.base.ModelBase.__new__ does when
    # proxy = True (after it has done its spoil-sport validation ;-)
    new_class._meta.setup_proxy(base)
    new_class._meta.concrete_model = base._meta.concrete_model


class DynamicPkgDestination(models.Model):
    dynamic_pkg = models.ForeignKey(DynamicPkg, related_name='destinations')
    # Indexed because we will be joining City.code to
    # DynamicPkgDestination.city_code and we want this to be fast.
    city_code = city_code_field(db_index=True)


class ProxyDynamicPkgDestination(DynamicPkgDestination):
    city = models.ForeignKey('City', db_column='city_code', to_field='code', related_name='dpdestinations')


proxify_model(ProxyDynamicPkgDestination, DynamicPkgDestination)


class City(models.Model):
    code = models.CharField(max_length=10, unique=True)
Ваша проблема заключается в использовании: related_name = 'destination' для обоих ForeignKeys, для каждого из них используется managed_destination и unmanaged_destination соответственно. Aaron McMillin
1

from django.db import models


class ReferencedModel(models.Model):
    pass


class ManagedModel(models.Model):
    my_fake_fk = models.IntegerField(
        db_column='referenced_model_id'
    )


class UnmanagedModel(models.Model):
    my_fake_fk = models.ForeignKey(
        ReferencedModel, 
        db_column='referenced_model_id'
    )

    class Meta:
        managed = False
        db_table = ManagedModel._meta.db_table

Указываяmanaged=False в классе модели Meta не создаст для него таблицу базы данных. Тем не менее, он будет вести себя точно так же, как и другие модели.

1

один из вариантов ForeignKey.on_delete -

DO_NOTHING: не предпринимать никаких действий. Если ваш сервер базы данных обеспечивает ссылочную целостность, это вызовет IntegrityError, если вы не добавите ограничение SQL ON DELETE вручную в поле базы данных (возможно, с использованием исходного sql).

Это в сочетании с отключением ограничений внешнего ключа на уровне БД должно помочь. Из того, что я могу сказать, есть два способа сделать это. Вы можете полностью отключить ограничения fk следующим образом:

from django.db.backend.signals import connection_created
from django.dispatch import receiver

@receiver(connection_created)
def disable_constraints(sender, connection):
    connection.disable_constraint_checking()

Похоже, что бэкэнды django db предлагают также контекстный менеджер constraint_checks_disabled, так что вы можете обернуть соответствующие обращения к db в коде, как показано ниже, чтобы избежать отключения проверок:

from django.db import connection
with connection.constraint_checks_disabled():
    do_stuff()
0

thing_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
thing_object_id = models.UUIDField(default=uuid.uuid4, blank=True, null=True)

thing = GenericForeignKey(ct_field='thing_content_type', fk_field='thing_object_id')

С другой стороны, это не из коробки, Джанго

С другой стороны, у вас есть три дополнительных атрибута в вашей модели.

Кроме того, обратные отношения не работают автоматически, но в моем случае я в порядке.

Похожие вопросы