开发者

How do I get the actual object id in a Django admin page (inside formfield_for_foreignkey)?

开发者 https://www.devze.com 2023-02-03 02:19 出处:网络
I \'ve already solved the problem of getting the object id being edited using this code: class CompanyUserInline(admin.StackedInline):

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 subsequent ContactForm 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 an instance 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)
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号