12

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?

Leopd
  • 41,333
  • 31
  • 129
  • 167
  • Does this help? http://www.djangoproject.com/documentation/models/many_to_one_null/ – colinjwebb Aug 24 '10 at 17:00
  • That's similar but not what I'm looking for. I want to put a number in the field, not have the field be null. But the number would not correspond to an object in the referenced table. – Leopd Aug 24 '10 at 17:04
  • Can you tell us more about the use case? Instead of misusing the ForeignKey concept, there's maybe a more elegant solution to this. – Andre Bossard Oct 04 '10 at 12:06
  • The SSN comment below is exactly right. We're gathering data from external sources that include ID values for other entities that we might or might not currently know anything more about. The ID values are stable. We might later learn something about the entities referred to by these IDs. – Leopd Oct 05 '10 at 19:14
  • Hey Leopd - I'm running into this same problem & since its been awhile with you using this I'm assuming you had no ill side affects. Did you basically declare in your model the ForeignKey relationship but then set managed=False? Just curious. I also asked a similar question before I saw this one -- http://stackoverflow.com/questions/6788558/understanding-mysql-aka-tricking-foreignkey-relationships-in-django – rh0dium Jul 29 '11 at 14:08
  • @rh0dium I ended up defining the FK as a BigIntegerField and handling all the relationship logic in my models. I'm sorry I don't recall why I didn't follow the conclusion of this discussion. I'm pretty sure I didn't try it and run into problems - probably just found a workable solution and moved on. – Leopd Jul 29 '11 at 18:05
  • @Leopd - I suspect you have the same feeling as I do - it isn't "right", it may work, and it's significantly easier to code but... Thanks! I've been wrestling with this and thought you jumped in.. Thanks for confirming! – rh0dium Jul 29 '11 at 19:58

6 Answers6

9

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/dev/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

furins
  • 4,979
  • 1
  • 39
  • 57
brandon
  • 427
  • 3
  • 12
  • Clarifying this because as a newcomer this confused me for a bit. For at least Oracle, Django sets up FKs such that they only check their constraints on commit (constraint "DEFERRABLE INITIALLY DEFERRED"). So if you can insert your out-of-order FK relationships within one transaction, there is no need to disable the FK constraint. – Connor Apr 17 '19 at 16:17
3

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

Brian
  • 1,028
  • 12
  • 20
3

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.

Matheus Moreira
  • 2,338
  • 4
  • 24
  • 31
  • Something like this is about the best I've come up with so far. I'm hoping for more because this way we lose a bunch of the cool django object relationship stuff. – Leopd Aug 24 '10 at 18:58
  • You can have a django foreign key in tandem with this. If there's a foreign key, use it, if not - get the data in the PositiveIntegerField. – OmerGertel Oct 04 '10 at 22:50
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.

Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
  • This works! Just need to go into the database and muck with it by hand. Any idea how to do this with south migrations? – Leopd Oct 05 '10 at 20:30
  • I posted an answer below with how to accomplish this with South (too messy for a comment). – Brian Apr 26 '13 at 21:50
2

(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).

Pi Delport
  • 10,356
  • 3
  • 36
  • 50
  • This is useful if you want to be able to reference rows in more than one table, but I think this is a different. This case seems closer to having an external unique ID, like an SSN, which may or may not have an entry in the foreign table, and where that entry could be created later. In other words, the id *always* refers to logical entries in the named table; those entries simply don't always exist. (For example, if the foreign row is added later, the relationship should be complete without having to carefully update other_type.) – Glenn Maynard Oct 01 '10 at 20:59
  • Glenn's right about how the data model should work. So to answer your questions, if a new entry is created in the target table which matches an ID in the FK-like field: great! Now we have a reference. In your solution, on every insert I'd have to search the source table for matches to that ID and update the type column. Other Q -- if an entry in the target table is deleted: nothing happens; do not cascade delete. – Leopd Oct 05 '10 at 19:26
  • Leopd: Ah, thank you for clarifying. In that case, typed references are not relevant, yes, and you would instead want to subclass or customize `ForeignKey` to implement the behavior you want: it could probably be made into a reusable `OptionalForeignKey` or `SoftForeignKey` field. – Pi Delport Oct 05 '10 at 19:40
-2

tablename= columnname.ForeignKey('table', null=True, blank=True, db_constraint=False)

use this in your program

  • This solution was already proposed in the approved [answer](https://stackoverflow.com/a/22082309/6682517), why duplicate it? – Sergey Shubin Jun 23 '20 at 06:15