Django ForeignKey which does not require referenti

2020-02-09 10:45发布

问题:

I'd like to set up a ForeignKey field in a django model which points to another table some of the time. But I want it to be okay to insert an id into this field which refers to an entry in the other table which might not be there. So if the row exists in the other table, I'd like to get all the benefits of the ForeignKey relationship. But if not, I'd like this treated as just a number.

Is this possible? Is this what Generic relations are for?

回答1:

This question was asked a long time ago, but for newcomers there is now a built in way to handle this by setting db_constraint=False on your ForeignKey:

https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.ForeignKey.db_constraint

customer = models.ForeignKey('Customer', db_constraint=False)

or if you want to to be nullable as well as not enforcing referential integrity:

customer = models.ForeignKey('Customer', null=True, blank=True, db_constraint=False) 

We use this in cases where we cannot guarantee that the relations will get created in the right order.

EDIT: update link



回答2:

I'm new to Django, so I don't now if it provides what you want out-of-the-box. I thought of something like this:

from django.db import models

class YourModel(models.Model):
    my_fk = models.PositiveIntegerField()

    def set_fk_obj(self, obj):
        my_fk = obj.id

    def get_fk_obj(self):
        if my_fk == None:
            return None
        try:
            obj = YourFkModel.objects.get(pk = self.my_fk)
            return obj
        except YourFkModel.DoesNotExist:
            return None

I don't know if you use the contrib admin app. Using PositiveIntegerField instead of ForeignKey the field would be rendered with a text field on the admin site.



回答3:

This is probably as simple as declaring a ForeignKey and creating the column without actually declaring it as a FOREIGN KEY. That way, you'll get o.obj_id, o.obj will work if the object exists, and--I think--raise an exception if you try to load an object that doesn't actually exist (probably DoesNotExist).

However, I don't think there's any way to make syncdb do this for you. I found syncdb to be limiting to the point of being useless, so I bypass it entirely and create the schema with my own code. You can use syncdb to create the database, then alter the table directly, eg. ALTER TABLE tablename DROP CONSTRAINT fk_constraint_name.

You also inherently lose ON DELETE CASCADE and all referential integrity checking, of course.



回答4:

To do the solution by @Glenn Maynard via South, generate an empty South migration:

python manage.py schemamigration myapp name_of_migration --empty

Edit the migration file then run it:

def forwards(self, orm):
    db.delete_foreign_key('table_name', 'field_name')

def backwards(self, orm):
    sql = db.foreign_key_sql('table_name', 'field_name', 'foreign_table_name', 'foreign_field_name')
    db.execute(sql)

Source article



回答5:

(Note: It might help if you explain why you want this. There might be a better way to approach the underlying problem.)

Is this possible?

Not with ForeignKey alone, because you're overloading the column values with two different meanings, without a reliable way of distinguishing them. (For example, what would happen if a new entry in the target table is created with a primary key matching old entries in the referencing table? What would happen to these old referencing entries when the new target entry is deleted?)

The usual ad hoc solution to this problem is to define a "type" or "tag" column alongside the foreign key, to distinguish the different meanings (but see below).

Is this what Generic relations are for?

Yes, partly.

GenericForeignKey is just a Django convenience helper for the pattern above; it pairs a foreign key with a type tag that identifies which table/model it refers to (using the model's associated ContentType; see contenttypes)

Example:

class Foo(models.Model):

    other_type = models.ForeignKey('contenttypes.ContentType', null=True)
    other_id = models.PositiveIntegerField()
    # Optional accessor, not a stored column
    other = generic.GenericForeignKey('other_type', 'other_id')

This will allow you use other like a ForeignKey, to refer to instances of your other model. (In the background, GenericForeignKey gets and sets other_type and other_id for you.)

To represent a number that isn't a reference, you would set other_type to None, and just use other_id directly. In this case, trying to access other will always return None, instead of raising DoesNotExist (or returning an unintended object, due to id collision).