3

I'm making a django application and I'm facing an issue. I am trying to define a model where one ForeignKey would be depending on another ForeignKey.

Description part

My application is about making choices.

So let's say you have a decision to make, a decision has multiple choices, and a choice has a status (because of other constraints).

A status can be used on multiple choices, but a status can be relevant only for one decision, the same one the choice is linked to.

Database schema

It isn't fixed and might be changed if needed :

,____________,                  ,____________,
|            | 1, n        1, 1 |            |
|  Decision  |------------------|   Status   |
|____________|                  |____________|
      |                                |
      | 1, n                           | 1, n
      |                                |
      | 1, 1                           |
,_____|______,                         |
|            | 1, 1                    |
|   Choice   |-------------------------'
|____________|

Code

And here is my current (simplified) (not working) code :

class Decision (models.Model):
    name = models.CharField (max_length = 63)

class Status (models.Model):
    value = models.CharField (max_length = 63)
    decision = models.ForeignKey (Decision)

class Choice (models.Model):
    name = models.CharField (max_length = 63)

    decision = models.ForeignKey (Decision)
    status = models.ForeignKey (Status, limit_choices_to = {'decision' : decision})

The important part here being the limit_choices_to = {'decision' : decision}.

Extra info

I found another SO question (In django, how to limit choices of a foreignfield based on another field in the same model?) dealing about the same question, but the question is becoming old, and the best answer was relying on an external app (django-smart-selects).

I'd rather not have to use something external, and I cannot see why something as simple as a 3-table relationship cannot be solved using only Django !

If someone has any solution, or any suggestion, please tell me.

Community
  • 1
  • 1
Jerska
  • 11,722
  • 4
  • 35
  • 54
  • 1
    Did you look at [this answer](http://stackoverflow.com/a/13076043/1628832) ? – karthikr Jun 28 '13 at 14:21
  • @karthikr This answer is applying on a form when I would like to apply it directly to the model (simplifying admin management at the same time). But if that's not possible, this is probably the path I'll choose. – Jerska Jun 28 '13 at 14:36
  • well you can do the same in the admin forms too.. I know its redundant, but till you find a better way, this could work – karthikr Jun 28 '13 at 14:37
  • Yeah, sure, and this is what I'm going to do if I can't find anything else. But I was hoping another more direct solution would be possible. Thanks for your answer, though. – Jerska Jun 28 '13 at 14:38
  • 2
    This cannot be done at the model level; it can only be done at the form level and at the database level. – Ignacio Vazquez-Abrams Jun 29 '13 at 04:57
  • @IgnacioVazquez-Abrams Why ? Could you consider adding an answer ? And then why is it possible with `django-smart-selects` ? – Jerska Jun 29 '13 at 05:21
  • 1
    @Jerska it's not possible with `django-smart-selects`, because it doesn't work at the model level. It works at the Form level. – Matt Jul 08 '13 at 12:56

4 Answers4

3

What you're asking is not possible, at least not within the boundaries you've set (no forms, no external libraries). The status field of your Choice model is a Foreign Key, a relationship between two tables… it doesn't deal with filtering itself, simply put – it doesn't have this feature. And this isn't a django thing, this is a database thing. The Django ORM isn't as far away from the database as you probably think, it's brilliant but it's not magic.

Some of the available solutions are:

  • Do it at the FormField level by filtering the queryset
  • Use something like django-smart-selects (this does the above)
  • Override save on your model, add the check there and throw an error if it fails
  • Make status a property and do validation checks on it when it's set

If you go with FormField method as well as overriding save you'll have the benefit of knowing there's no way a Choice can be saved it it violates this constraint, from either the user's end (filling out a form) or the back end (code that calls .save() on a Choice instance.

Matt
  • 8,758
  • 4
  • 35
  • 64
  • 1
    Well, your answer is the kind of answer I was expecting, even if I would have loved to see a solution at the model level. There goes my bounty. Thanks for the multiple solutions. – Jerska Jul 10 '13 at 01:50
1

I'm not familiar with Django, but I if you are trying to solve the "same one the choice is linked to" part of the problem, this is how it can be done at the database level:

enter image description here

Note the usage of identifying relationships, so the DecisionId is migrated down both "branches" and merged at the "bottom". So if a Choice has Status, they both must be linked to the same Decision.

Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167
  • The question itself is CW, so all answers are going to be CW as well. – Bill the Lizard Jul 07 '13 at 17:28
  • Well, I don't see any way to connect two foreign keys from different tables together in django, but it seems to be the correct database-design answer. – Jerska Jul 10 '13 at 01:48
1

I think that what you need here is a through model, like so:

class Choice (models.Model):
    name = models.CharField (max_length = 63)
    status = models.ForeignKey(Status)
    decision = models.ForeignKey(Decision)

class Status(Models.Model):
    name = models.CharField(max_length=20)

class Decision(models.Model):
    name = models.CharField(max_length = 63)
    choices = models.ManyToManyField(Status, through = "Choice")    

This way, every decision has many choices, each of which has only one status. You could do a query like: my_decision.choices.all() or my_status.decision_set.all()

I suggest you take a look at the documentation for an example on how to use through models

Basti
  • 252
  • 2
  • 9
  • I like this answer, because it made me discover the `through` parameter, and this is actually something very interesting. But your answer isn't actually what I was looking for, since it is still allowing any status to be linked to a choice, even if there is now a relationship between the status and the decision that is now dependent on the choices already made, which is quite interesting. I wanted a choice status to be limited by its decision. But as pointed out by Matt, the problem seems like not being solvable at the model level in Django, as I thought. – Jerska Jul 10 '13 at 01:47
0

In the following statement decision is neither a callable nor a models.Q object:

status = models.ForeignKey (Status, limit_choices_to = {'decision' : decision})

here is a way to represent your data:

class Decision(models.Model):
    ...

# a status is relevant for only one decision
# there may be more than one Status per Decision.
class Status(Models.Model):
    decision = models.ForeignKey(Decision)

# each choice is linked to one decision and has a status.
class Choice (models.Model):
    status = models.ForeignKey(Status)
    # if status is mandatory, then you can get the decision
    # from status.decision. per se, this fk could be optional.
    decision = models.ForeignKey(Decision)

here is another one:

class Decision(models.Model):
    ...

# a decision has multiple choices
# a choice pertains to only one decision
class Choice (models.Model):
    decision = models.ForeignKey(Decision)

# each status is relevant to one decision
# and may encompass multiple choices.
class Status(Models.Model):
    decision = models.ForeignKey(Decision)
    # problem with this representation is that this allows for
    # a choice to be linked to multiple statuses.
    # this happens when using M2M instead of ForeignKey.
    choices = models.ManyToManyField(Choice)
dnozay
  • 23,846
  • 6
  • 82
  • 104
  • Your first answer is actually what I already had, and your second is just removing constraints, making a `ManyToMany` relationship where there is a `OneToMany`. But you knew it when posting, as I can tell with your comments. Thanks for your answer, though. – Jerska Jul 10 '13 at 01:55
  • the first representation given is normal and that would be equivalent to @branko-dimitrijevic's answer. – dnozay Jul 10 '13 at 04:42