11👍
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!
27👍
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)
- Where are the files downloaded using pip stored in virtualenv?
- How do I create a proper user with django allauth?
- Why use Django's collectstatic instead of just serving the files directly from your static directory?
- How to append pages of data using jQuery and Django pagination?
- Django querysets + memcached: best practices
18👍
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.
- How to compare version string ("x.y.z") in MySQL?
- Django admin add custom filter
- Can i get models field type from a model queryset in Django?
- Why is django's settings object a LazyObject?
4👍
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()
- How to choose the value and label from Django ModelChoiceField queryset
- How to describe parameters in DRF Docs
- Django Admin – add collapse to a fieldset, but have it start expanded
- How to send success message if we use django generic views
- How to get values of all object fields in Django?
1👍
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 field queryset based on the object being edited, it is probably better to either
- extend
ModelForm.__init__()
, as suggested in the documentation forformfield_for_foreignkey()
, or - extend
ModelAdmin.get_form()
, overriding the form class’sbase_fields
.
See examples below.
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 you can probably 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
Note that Django’s documentation for the Forms API warns against modifying base_fields
(also see e.g. this answer):
Beware not to alter the
base_fields
attribute because this modification will influence all subsequentContactForm
instances within the same Python process: …
However, I do not think this restriction applies here, because the admin form is generated dynamically, using a modelform_factory
.
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.
- Paypal monthly subscription plan settings for first day of the month and making monthly recurring payment – django python
- Colorizing the output of Django tests
- Django and Celery – ModuleNotFoundError: No module named 'celery.task'
0👍
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
- How to get object from PK inside Django template?
- Why is django's settings object a LazyObject?
- Using Django's built in web server in a production environment
- Django template img src not working
- Where do I put "WSGIPassAuthorization On"?
-2👍
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.
- How to run a Django celery task every 6am and 6pm daily?
- Accessing form fields as properties in a django view
- Django. Error message for login form
-2👍
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)
- How to override template in django-allauth?
- Python and sqlite3 – importing and exporting databases
- Celery workers unable to connect to redis on docker instances