views:

137

answers:

3

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?

+2  A: 

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.emm
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
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
+1  A: 

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
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
I've never used it.
Glenn Maynard
A: 

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

Piet Delport
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
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
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.
Piet Delport