pythondjangodjango-formsdjango-permissionsdjango-widget

How to update widget attribute based on user role in Django


I have this custom widget:

class RelatedFieldWidgetCanAdd(widgets.Select):

    def __init__(self, related_model, related_url=None, can_add_related=True, *args, **kw):
        self.can_add_related = can_add_related

        super(RelatedFieldWidgetCanAdd, self).__init__(*args, **kw)

        if not related_url:
            rel_to = related_model
            info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
            related_url = 'admin:%s_%s_add' % info

        # Be cautious, as "reverse" is not allowed here
        self.related_url = related_url

    def render(self, name, value, *args, **kwargs):
        self.related_url = reverse(self.related_url)
        output = [u'<div class="d-flex">']
        output.append(super(RelatedFieldWidgetCanAdd, self).render(name, value, *args, **kwargs))
        if self.can_add_related:
            output.append(u'<a href="%s?_to_field=id&_popup=1" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' %
                        (self.related_url, name))
            output.append(u'<img src="%sadmin/img/icon-addlink.svg" width="20" height="50" class="pb-2 mx-2" alt="%s"/></a>' %
                        (settings.STATIC_URL, _('Add Another')))
        output.append(u'</div>')
        return mark_safe(u''.join(output))

And this is how I'm using it in form.py:

class LeaveForm(forms.ModelForm):
    ...
    leave_type = forms.ModelChoiceField(
        queryset=LeaveType.objects.all().order_by('-pk'), empty_label='--------',
        widget=RelatedFieldWidgetCanAdd(
            LeaveType,
            related_url='leave_type_add'
        )
    )

Now, if I'm logged in as a superuser in my dashboard, I can see the + button to create a related object. That's fine, but when I'm logged in as a normal user, it allows me to add related data. I know it's because I've set can_add_related to True by default. I want to update it while rendering it by checking the user's is_superuser or is_admin attribute. I tried to access the Request inside the widget, but I don't have access to the Request object. So, I can't do it in RelatedFieldWidgetCanAdd. There's a method named get_form that I tried to use for this, but it removes the css class and sets the queryset empty. Even if I provide all values again in get_form, it doesn't work. Is there any other simpler way to do this?


Solution

  • You could implement the LeaveForm.__init__() to receive a parameter can_add_related in your view and inject it into the widget.

    Something like:

    class LeaveForm(forms.ModelForm):
    ...
        def __init__(self, *args, **kwargs):
            can_add_related = kwargs.pop("can_add_related", False)
            super().__init__(*args, **kwargs)
            ... 
            self.fields["leave_type"].widget.can_add_related = can_add_related
    

    In the view you call:

    form = LeaveForm(..., can_add_related = request.user.is_superuser | request_user.is_admin)