Django: “Soft” ForeignField without database integrity checks

前端 未结 6 1318
南旧
南旧 2021-02-05 17:05

I have a Django project that has multiple django \"apps\". One of them has models to represent data coming from an external source (I do not control this data).

I want m

6条回答
  •  悲&欢浪女
    2021-02-05 17:48

    Yo guys,

    I managed to make what I wanted.

    First, I created a new field:

    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
    

    Since I use South to manage my database schema, I had to add this:

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

    Then, I had to monkey patch south so that it takes the no_db_constraints parameter into account. There were two functions involved in the creation of FK constraints:

    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
    

    And:

    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
    

    This is really ugly but I didn't find another way.

    Now you can use the SoftForeignKey field exactly like a normal ForeignKey except that you won't have any referencial integrity enforcement.

    See here for the complete monkey-patch : http://eve-corp-management.org/projects/ecm/repository/entry/ecm/lib/softfk.py

提交回复
热议问题