How do I restrict foreign keys choices to related objects only in django
I just came across ForeignKey.limit_choices_to in the Django docs.
Not sure yet how it works, but it might be the right thing here.
Update: ForeignKey.limit_choices_to
allows one 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. Django: Limiting foreign key choices based on related object
Give the SoftwareAsset an FK to Company.
class SoftwareAsset(models.Model):
name = models.CharField(max_length=200)
company = models.ForeignKey(Company)
So a company can own many software assets. Now you can easily filter the software assets in your custom modelform.EmployeeSoftwareForm(forms.ModelForm):
class Meta:
model = EmployeeSoftware
def __init(self, *args, **kwargs):
super(EmployeeSoftwareForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
self.fields['assets'].queryset = SoftwareAsset.objects.filter(company = kwargs['instance'].employee.company)
Finally use this custom form for the EmployeeSoftware modeladmin:class EmployeeSoftwareAdmin(admin.ModelAdmin):
form = EmployeeSoftwareForm
This eliminates the need for the CompanyAsset model.UPDATE: OK, you want each new instance of EmployeeSoftware to know what assets it can have, based on the employee company. But because you dont know what your employee is until you select it, its impossible to do on form init. You have to use javascript to filter the select based on the employee choice. Something like this (using JQuery):
$("#employee").change(function(){
$.post(<your assets select url>, $("employeeform").serialize(), function (data) {
//populate your returned JSON into the asset select
}, 'json');
})Obviously, the url your post goes to filters the assets based on the selected employee, which should be simple.
How do I restrict foreign keys choices in admin formset to related objects only in django
You have to pass the parent object from the InlineAdmin to the FormSet to the actual Form. Took me quite a moment to figure that out.
In the example below CalibrationCertificateData
is relate to the CalibrationCertificate
and I only want to show Quantities
that are related with the SensorCategory.
from django.contrib import admin
from django.forms import ModelForm, BaseInlineFormSet
from src.admin import site_wm
from . import models
from quantity_management.models import QuantitySensor
# Register your models here.
site_wm.register(models.CalibrationInstitute, site=site_wm)
class CertDataAdminForm(ModelForm):
class Meta:
model = models.CalibrationCertificateData
fields = "__all__"
def __init__(self, *args, **kwargs):
"""
Make sure that we only show the Quantites that are related to the selected Cert>Sensor>Category
"""
self.parent_obj = None
if 'parent_obj' in kwargs:
self.parent_obj = kwargs['parent_obj']
del kwargs['parent_obj']
super().__init__(*args, **kwargs)
if self.parent_obj:
# Do the actual filtering according to the parent object
category_id = self.parent_obj.Sensor.Category_id
self.fields['Quantity'].queryset = QuantitySensor.objects.filter(Sensor_id=category_id)
class CertDataAdminFormSet(BaseInlineFormSet):
form = CertDataAdminForm
def get_form_kwargs(self, index):
"""
Make sure the form knows about the parent object
"""
kwargs = super().get_form_kwargs(index)
if hasattr(self, 'parent_obj') and self.parent_obj:
kwargs['parent_obj'] = self.parent_obj
return kwargs
class CalibrationDataAdmin(admin.StackedInline):
model = models.CalibrationCertificateData
extra = 0
form = CertDataAdminForm
formset = CertDataAdminFormSet
def get_formset(self, request, obj=None, **kwargs):
"""
give the formset the current object, so it can limit the selection choices according to it
"""
formset = super().get_formset(request, obj, **kwargs)
if formset is not None:
formset.parent_obj = obj
return formset
def formfield_for_foreignkey(self, db_field, request, **kwargs):
print(self.parent_model)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(models.CalibrationCertificate, site=site_wm)
class CalibrationCertificateAdmin(admin.ModelAdmin):
inlines = (
CalibrationDataAdmin,
)
Limit choices and validate django's foreign key to related objects (also in REST)
Here what I've found so far:
To get Foreignkey
showed right in admin, you have to specify a form in ModelAdmin
class TodoAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['category'].queryset = Category.objects.filter(user__pk=self.instance.user.pk)
@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
form = TodoAdminForm
...
To get ManyToManyField
showed right in InlineModelAdmin
(e.g. TabularInline
) here comes more dirty hack (can it be done better?)You have to save your quiring field value from object and then manually set queryset in the field. My through
model has two members todo
and tag
And I'd like to filter tag
field (pointing to model Tag
):
class MembershipInline(admin.TabularInline):
model = Todo.tags.through
def get_formset(self, request, obj=None, **kwargs):
request.saved_user_pk = obj.user.pk # Not sure if it can be None
return super().get_formset(request, obj, **kwargs)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == 'tag':
kwargs['queryset'] = Tag.objects.filter(user__pk=request.saved_user_pk)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
And finally, to restrict elements only to related in Django REST framework, I have to implement custom Field
class PrimaryKeyRelatedByUser(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return super().get_queryset().filter(user=self.context['request'].user)
And use it in my serializer likeclass TodoSerializer(serializers.ModelSerializer):
category = PrimaryKeyRelatedByUser(required=False, allow_null=True, queryset=Category.objects.all())
tags = PrimaryKeyRelatedByUser(required=False, many=True, queryset=Tag.objects.all())
class Meta:
model = Todo
fields = ('id', 'category', 'tags', ...)
Not sure if it actually working in all cases as planned. I'll continue this small investigation.Question still remains. Could it be done simplier?
How to restrict Foreign Key choices to another Foreign Key in the same model
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:
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
.
How to restrict foreign key objects for autocomplete search results in django ModelAdmin.autocomplete_fields?
Instead of using autocomplete_fields = ['spm']
, overriding change_form.html
template and using JavaScript to make HTML select element searchable(with auto-completion) worked for me:
content of change_form.html
:
{% extends 'admin/change_form.html' %}
{% block admin_change_form_document_ready %}
{{ block.super }}
<script src="https://code.jquery.com/jquery-2.1.1.min.js" type="text/javascript"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/css/select2.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/js/select2.min.js"></script>
<script type="text/javascript">
$("#id_spm").select2({});
</script>
{% endblock %}
The HTML select element on which select2
function operate:<select name="spm" required="" id="id_spm" >
<option value="" selected="">---------</option>
<option value="67688">apple iphone 7</option>
<option value="69093">apple iphone 7 plus</option>
<option value="71453">apple ipad pro</option>
<option value="71076">apple ipad pro 9.7</option>
<option value="34840">apple ipad pro 10.5</option>
<option value="72303">apple iphone 8 plus</option>
<option value="72301">apple iphone 8</option>
<option value="72307">apple iphone x</option>
<option value="71243">apple ipad pro 12.9</option>
</select>
Related Topics
How to Create Module-Wide Variables in Python
Is There a Simple Process-Based Parallel Map for Python
Where Do the Python Unit Tests Go
How to Plot Empirical Cdf (Ecdf)
How to Change Data Points Color Based on Some Variable
Why Does the Floating-Point Value of 4*0.1 Look Nice in Python 3 But 3*0.1 Doesn'T
Serve Image Stored in SQLalchemy Largebinary Column
How to Download a File from Google Drive Using Python and the Drive API V3
How to Flatten a Pandas Dataframe with Some Columns as JSON
Wrapping Long Y Labels in Matplotlib Tight Layout Using Setp
How to Find the Min/Max Value of a Common Key in a List of Dicts
Convert Date to Datetime in Python
How to Build a Systemtray App for Windows
Opencv Python: Draw Minarearect ( Rotatedrect Not Implemented)
Can't Get Python to Import from a Different Folder
Safely Create a File If and Only If It Does Not Exist with Python