Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I restrict foreign keys choices to related objects only in django

I have a two way foreign relation similar to the following

class Parent(models.Model):   name = models.CharField(max_length=255)   favoritechild = models.ForeignKey("Child", blank=True, null=True)  class Child(models.Model):   name = models.CharField(max_length=255)   myparent = models.ForeignKey(Parent) 

How do I restrict the choices for Parent.favoritechild to only children whose parent is itself? I tried

class Parent(models.Model):   name = models.CharField(max_length=255)   favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"}) 

but that causes the admin interface to not list any children.

like image 439
Jeff Mc Avatar asked Oct 24 '08 03:10

Jeff Mc


People also ask

Does Django automatically index foreign keys?

Django automatically creates an index for all models. ForeignKey columns. From Django documentation: A database index is automatically created on the ForeignKey .

What is ForeignKey relationship in Django?

What is ForeignKey in Django? ForeignKey is a Field (which represents a column in a database table), and it's used to create many-to-one relationships within tables. It's a standard practice in relational databases to connect data using ForeignKeys.

Can ForeignKey be null Django?

django rest framework - ForeignKey does not allow null values - Stack Overflow.


2 Answers

I just came across ForeignKey.limit_choices_to in the Django docs. Not sure yet how this works, but it might just be the right thing here.

Update: ForeignKey.limit_choices_to allows to specify either a constant, a callable or a Q object to restrict the allowable choices for the key. A constant obviously is of no use here, since it knows nothing about the objects involved.

Using a callable (function or class method or any callable object) seems more promising. However, the problem of how to access the necessary information from the HttpRequest object remains. Using thread local storage may be a solution.

2. Update: Here is what has worked for me:

I created a middleware as described in the link above. It extracts one or more arguments from the request's GET part, such as "product=1", and stores this information in the thread locals.

Next there is a class method in the model that reads the thread local variable and returns a list of ids to limit the choice of a foreign key field.

@classmethod def _product_list(cls):     """     return a list containing the one product_id contained in the request URL,     or a query containing all valid product_ids if not id present in URL      used to limit the choice of foreign key object to those related to the current product     """     id = threadlocals.get_current_product()     if id is not None:         return [id]     else:         return Product.objects.all().values('pk').query 

It is important to return a query containing all possible ids if none was selected so that the normal admin pages work ok.

The foreign key field is then declared as:

product = models.ForeignKey(     Product,     limit_choices_to={         id__in=BaseModel._product_list,     }, ) 

The catch is that you have to provide the information to restrict the choices via the request. I don't see a way to access "self" here.

like image 92
Ber Avatar answered Oct 25 '22 19:10

Ber


The 'right' way to do it is to use a custom form. From there, you can access self.instance, which is the current object. Example --

from django import forms from django.contrib import admin  from models import *  class SupplierAdminForm(forms.ModelForm):     class Meta:         model = Supplier         fields = "__all__" # for Django 1.8+       def __init__(self, *args, **kwargs):         super(SupplierAdminForm, self).__init__(*args, **kwargs)         if self.instance:             self.fields['cat'].queryset = Cat.objects.filter(supplier=self.instance)  class SupplierAdmin(admin.ModelAdmin):     form = SupplierAdminForm 
like image 41
s29 Avatar answered Oct 25 '22 21:10

s29