How do I get the actual object id in a Django admin page (inside formfield_for_foreignkey)?
I 've already solved the problem of getting the object id being edited using this code:
class CompanyUserInline(admin.StackedInline):
"""
Defines tabular rules for editing company users direct in company admin
"""
model = CompanyUser
def formfi开发者_运维问答eld_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "user":
users = User.objects.filter( Q(is_superuser=False) )
query = Q()
for u in users:
aux = CompanyUser.objects.filter(user=u)
if aux.count() == 0:
query |= Q(pk=u.id)
try:
cpu = CompanyUser.objects.filter(company__id=int(request.path.split('/')[4]))
for p in cpu:
query |= Q(pk=p.user.id)
except:
pass
kwargs["queryset"] = User.objects.filter(query).order_by('username')
return super(CompanyUserInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
But, the int(request.path.split('/')[4]) is really ugly. I want to know how I get the id from the Django AdminModel. I'm sure it's somewhere inside it, anyone knows?
Thank you in advance! ;D
After some digging around, we were able to grab the arguments that get passed to the admin view (after being parsed by django admin's urls.py) and use that (self_pub_id) to grab the object:
class PublicationAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "authors":
#this line below got the proper primary key for our object of interest
self_pub_id = request.resolver_match.args[0]
#then we did some stuff you don't care about
pub = Publication.objects.get(id=self_pub_id)
kwargs["queryset"] = pub.authors.all()
return super(PublicationAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
A more elegant solution is to use the accepted answers recomendation and leverage the get_form ModelAdmin member function. Like so:
class ProfileAdmin(admin.ModelAdmin):
my_id_for_formfield = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.my_id_for_formfield = obj.id
return super(ProfileAdmin, self).get_form(request, obj, **kwargs)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "person":
kwargs["queryset"] = Person.objects.filter(profile=self.my_id_for_formfield)
return super(ProfileAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
The following code snippet will give you the object id:
request.resolver_match.kwargs['object_id']
Sample usage: (I'm filtering the phone numbers shown, to only show customer's phone numbers)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'preferred_contact_number':
kwargs['queryset'] = CustomerPhone.objects.filter(customer__pk=request.resolver_match.kwargs['object_id'])
return super().formfield_for_foreignkey(db_field, request, **kwargs)
P.S: Found it by debugging and walking through accessible variables.
As far as i know it is not possible to access the current instance through the formfield_for_...
-methods, because they will only be called for a single field instance!
A better point to hook into this logic where you can access the whole instance/form would be get_form
. You can also overwrite a form field's queryset there!
I made it work by rewrite change_view()
class CartAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, form_url='', extra_context=None):
self.object_id = object_id
return self.changeform_view(request, object_id, form_url, extra_context)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
print self.object_id
return super(CartAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
then you can call self.object_id
inside formfield_for_foreignkey()
Short answer
If you really need the object id
inside formfield_for_foreignkey()
, you could use request.resolver_match.kwargs.get('object_id')
(docs), as proposed in @mazyar-mk's answer.
But, if the goal is e.g. to filter a queryset based on the object, it is probably better to extend ModelForm.__init__()
, as suggested in the documentation for formfield_for_foreignkey()
.
See example at the bottom.
Long answer (with example)
The accepted answer by @BernhardVallant suggests extending ModelAdmin.get_form()
, and in the comments it is suggested to modify the base_fields
attribute. (Note that get_form()
returns a form class, not a bound form.)
This is tempting, and in some cases you might get away with something like this:
def get_form(self, request, obj=None, change=False, **kwargs):
form_class = super().get_form(request, obj, change, **kwargs)
if obj:
form_class.base_fields['my_field'].queryset = my_queryset.filter(
my_lookup=obj
)
return form_class
However, Django's documentation warns against this:
Beware not to alter the
base_fields
attribute because this modification will influence all subsequentContactForm
instances within the same Python process: ...
Also see e.g. this answer.
The answer by @fizxmike provides an alternative example using get_form()
without modifying base_fields
, but it still needs formfield_for_foreignkey()
as well.
Solution from documentation
The documentation for formfield_for_foreignkey()
suggests another approach altogether (also see this ticket and fields docs):
For more complex filters, you can use [the]
ModelForm.__init__()
method to filter based on aninstance
of your model ...
Once the form is initialized, you can access the fields
attribute to modify the queryset. Moreover, you get access to the actual object (as self.instance
), not just the object id.
For example:
class MyModelAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk is not None:
self.fields['my_field'].queryset = my_queryset.filter(
my_lookup=self.instance
)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
...
Here we check self.instance.pk
to see if the object exists in the database (add vs change view).
This approach also works with inlines.
A more general approach could be writing an helper method to obtain the model instance (if any), much as you normally do with a (bounded) ModelForm, and from that retrieve the id or any other property:
from django.contrib import admin
class MyModelAdmin(admin.ModelAdmin):
def get_instance(self, request):
try:
object_id = request.resolver_match.kwargs['object_id']
obj = self.get_object(request, object_id)
except:
obj = None
return obj
I was dealing with a similar situation, and realized that the id that that I needed from the request, I could from from the model it self since it was a foreign key to that model. So it would be something like:
cpu = CompanyUser.objects.filter(company__id=self.company_id)
or what ever the structure of your model dictates.
I made it work by creating a property() in model.py that returns the ID
models.py:
class MyModel(models.Model):
myfield = models.CharField(max_length=75)
...
def get_id(self):
return str(self.id)
getid = property(get_id)
admin.py:
from myapp.models import MyModel
class MyModelAdmin(admin.ModelAdmin):
list_display = ['mylink',]
def mylink(self, object):
return '<a href="http://mywebsite/'+object.getid+'/">Edit</a>'
mylink.allow_tags = True
admin.site.register(MyModel, MyModelAdmin)
精彩评论