gbozee
gbozee

Reputation: 4796

creating a dynamic column in django admin based on an action form

I am trying to create a column on django admin whose values changes based on the input from an action form

So for example

#Action form
class CalculateDistanceForm(ActionForm):        
    latitude = forms.DecimalField()
    longitude = forms.DecimalField(initial=0)

#Admin
@register(Distance)
class DistanceAdmin(admin.ModelAdmin):
    list_display = ['latitude','longitude','distance']
    action_form = CalculateDistanceForm
    actions = ['update distance']

    def distance(self,obj):
        # how do i get the request instance to determine the post parameters on the form
        return distance_calculator(obj.latitude,obj.longitude)

    def update_distance(self,request,queryset):
        lat = request.POST.get('latitude',None)
        lon = request.POST.get('longitude',None)
        queryset.calculate_price(lat,lon)

My question is how can i dynamically display the new distance generated by the queryset?

Upvotes: 0

Views: 643

Answers (2)

GwynBleidD
GwynBleidD

Reputation: 20539

As I said in other answer, there is no simple way to get request in column method. But even if it will be possible, it will be different from one in your action method.

If we want to have our distance calculated every time, we can save our latitude and longitude in some more permanent storage. It can be database, cache or user session. Example with saving into session:

@register(Distance)
class DistanceAdmin(admin.ModelAdmin):
    list_display = ['latitude','longitude'] # not displaying distance by default
    list_display_alt = ['latitude', 'longitude', 'distance'] # we will use that one if this was proper action call
    action_form = CalculateDistanceForm
    actions = ['update_distance']

    def changelist_view(self, request, *args, **kwargs):
        # We can't get user session in our column method, so we will copy our values from that session into `ModelAdmin` instance here:        

        if request.session.get('admin_latitude') and request.session.get('admin_longitude'):
            self.latitude = request.session['admin_latitude']
            self.longitude = request.session['admin_longitude']
        return super(DistanceAdmin, self).changelist_view(self, request, *args, **kwargs)


    def distance(self,obj):
        return distance_calculator(obj.latitude,obj.longitude, self.latitude, self.longitude)

    def get_list_display(self, request):
        if hasattr(self, 'latitude') and hasattr(self, 'longitude'): # that means it was our action call, so we will modify default columns
            return list_display_alt
        return super(DistanceAdmin, self).get_list_display(request)

    def update_distance(self,request,queryset):
        #self.latitude = request.POST.get('latitude',None)
        #self.longitude = request.POST.get('longitude',None)
        # it will better to use form here instead of raw POST processing

        form = CalculateDistanceForm(request.POST, request.FILES)
        form.fields['action'].choices = (('update_distance', "Update distance"), ) # this is necessary because default ActionForm has no idea about valid actions

        if form.is_valid():
            latitude = form.cleaned_data['latitude']
            longitude = form.cleaned_data['longitude']

            request.session['admin_latitude'] = latitude
            request.session['admin_longitude'] = longitude
        else:
            # if form wasn't valid, we can inform about that using messages framework here

Upvotes: 1

GwynBleidD
GwynBleidD

Reputation: 20539

Unfortunately, there is no easy solution to get your request in distance method. Also, that request will be different that your post request because django admin will do automatic redirection after action is done processing, but we can prevent that by returning response inside your action.

When we are returning response in action, we can save latitude and longitude into ModelAdmin instance and retrieve it later in distance method.

@register(Distance)
class DistanceAdmin(admin.ModelAdmin):
    list_display = ['latitude','longitude'] # not displaying distance by default
    list_display_alt = ['latitude', 'longitude', 'distance'] # we will use that one if this was proper action call
    action_form = CalculateDistanceForm
    actions = ['update_distance']

    def distance(self,obj):
        return distance_calculator(obj.latitude,obj.longitude, self.latitude, self.longitude)

    def get_list_display(self, request):
        if hasattr(self, 'latitude') and hasattr(self, 'longitude'): # that means it was our action call, so we will modify default columns
            return list_display_alt
        return super(DistanceAdmin, self).get_list_display(request)

    def update_distance(self,request,queryset):
        #self.latitude = request.POST.get('latitude',None)
        #self.longitude = request.POST.get('longitude',None)
        # it will better to use form here instead of raw POST processing

        form = CalculateDistanceForm(request.POST, request.FILES)
        form.fields['action'].choices = (('update_distance', "Update distance"), ) # this is necessary because default ActionForm has no idea about valid actions

        if form.is_valid():
            self.latitude = form.cleaned_data['latitude']
            self.longitude = form.cleaned_data['longitude']

            request.method = GET  # tricking default changelist_view to think that there is no action called, without that we will end up in infinite loop.

            return self.changelist_view(request)
        else:
            # if form wasn't valid, we can inform about that using messages framework here

Upvotes: 0

Related Questions