Search code examples
djangodjango-viewsdjango-forms

Django forms passing request via get_form_kwargs fails to give form access to self.request.user


Goal

I need to make the user accessible to a form for validation (i.e., through self.request.user)

Approach Taken

I use the get_form_kwargs() function in my view to make the request available to my form (as suggested here: Very similar problem).

Problem

Despite following the steps outlined in the StackOverflow answer linked above, I'm getting the error 'NoneType' object has no attribute 'user'

Code

views.py

class MemberDetailView(LoginRequiredMixin, generic.DetailView):
    """View class for member profile page"""
    model = User
    context_object_name = 'user'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs,)
        context['form'] = JoinCommunityForm()
        return context
    
class JoinCommunityFormView(FormView):
    """View class for join code for users to join communities"""
    form_class = JoinCommunityForm

    def get_form_kwargs(self, *args, **kwargs):
        form_kwargs = super().get_form_kwargs(*args, **kwargs)
        form_kwargs['request'] = self.request
        return form_kwargs

    def post(self, request, *args, **kwargs):
        """posts the request to join community only if user is logged in"""
        if not request.user.is_authenticated:
            return HttpResponseForbidden
        return super().post(request, *args, **kwargs)
    
class MemberDashboardView(View):
    """View class to bring together the MemberDetailView and the JoinCommunityFormView"""
    def get(self, request, *args, **kwargs):
        view = MemberDetailView.as_view()
        return view(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        view = JoinCommunityFormView.as_view()
        form = JoinCommunityForm(request.POST)
        if form.is_valid():

forms.py

*Line where error is being raised is user=self.request.user *

from communities.models import Community, UserRoleWithinCommunity

class JoinCommunityForm(forms.Form):
    pin = forms.RegexField(
                           '^[A-HJ-NP-Z1-9]$',
                           max_length=6, 
                           label='',
                           widget=forms.TextInput(attrs={
                               'oninput': 'this.value = this.value.toUpperCase()'
                                                        }
                                                  )
                           )

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(JoinCommunityForm, self).__init__(*args, **kwargs)
    
    def clean(self):
        cleaned_data = super().clean()
        pin = self.cleaned_data['pin']
        test_join_code = f'{pin}'
        if Community.objects.filter(join_code=test_join_code).exists():
            # Make sure user is not already a member of the community
            communityToJoin = Community.objects.get(join_code=test_join_code)
            if UserRoleWithinCommunity.objects.filter(
                community=communityToJoin,
                user=self.request.user
            ).exists():
                raise forms.ValidationError("You're already a member 
                                             of that Community")
            else:
                return cleaned_data
        else:
            raise forms.ValidationError("Join code is incorrect")

Edit 1:

I've tried the approach suggested by Willem but am still butting up against the same error 'NoneType' object has no attribute 'user', which is tracing back to that one line in my form where I need to access the user through self.request.user.

All forms.py code remains the exact same as above. My views.py code has changed as follows (I now only have a single view rather than two views (form view and detail view) that feed into another view):

views.py

# all necessary import statements

class MemberDetailView(
        LoginRequiredMixin, FormMixin,
        generic.DetailView, ProcessFormView
    ):
    """View class for member profile page"""
    model = User
    context_object_name = 'user'
    form_class = JoinCommunityForm
    success_url = '#communities-card'
    template_name = 'home/terpuser_detail.html'

    def get_queryset(self, *args, **kwargs):
        qs = super().get_queryset(*args, **kwargs)
        if not self.request.user.is_superuser:
            qs = qs.filter(pk=self.request.user.pk)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(
            **kwargs,
            demo=get_object_or_404(Scenario.objects, scenario_id='demo'),
            today=date.today(),
        )
        return context
    
    def form_valid(self, form):
        # this is where I put my code to create a new Community 
        # membership object
        return super().form_valid(self, form)

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('member/<slug:slug>/', views.MemberDetailView.as_view(), name='member-detail')
]

error message

  • Request Method: POST
  • Request URL: http://127.0.0.1:8000/member/tron/
  • Django Version: 4.1.5
  • Exception Type: AttributeError
  • Exception Value: 'NoneType' object has no attribute 'user'
  • Exception Location: ../projDir/home/forms.py, line 53, in clean
  • Raised during: home.views.MemberDetailView
  • Python Executable: ../projDir/django-terp-venv/bin/python
  • Python Version: 3.8.2

Solution

  • It appears I needed to get the current user (not just the request) using the form class's __init__ method. Below is the views.py and forms.py code that ended up working:

    views.py

    # all necessary import statements
    
    class MemberDetailView(
            LoginRequiredMixin, FormMixin,
            generic.DetailView, ProcessFormView
        ):
        """View class for member profile page"""
        model = User
        context_object_name = 'user'
        form_class = JoinCommunityForm
        success_url = '#communities-card'
        template_name = 'home/terpuser_detail.html'
    
        def get_queryset(self, *args, **kwargs):
            qs = super().get_queryset(*args, **kwargs)
            if not self.request.user.is_superuser:
                qs = qs.filter(pk=self.request.user.pk)
            return qs
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(
                **kwargs,
                demo=get_object_or_404(Scenario.objects, scenario_id='demo'),
                today=date.today(),
            )
            return context
        
        def form_valid(self, form):
            # this is where I put my code to create a new Community membership object
            return super().form_valid(self, form)
    

    forms.py

    The line that ended up solving my problems is in the form class's __init__ method: self.user = self.request.user. Then, I call self.user in the form class's clean method rather than self.request.user

    from communities.models import Community, UserRoleWithinCommunity
    
    class JoinCommunityForm(forms.Form):
        pin = forms.RegexField(
                               '^[A-HJ-NP-Z1-9]$',
                               max_length=6, 
                               label='',
                               widget=forms.TextInput(attrs={
                                   'oninput': 'this.value = this.value.toUpperCase()'
                                                            }
                                                      )
                               )
    
        def __init__(self, *args, **kwargs):
            self.request = kwargs.pop('request', None) # instantiate request kwarg as Willem suggests
            self.user = self.request.user # get user, this was key to resolving error
            super(JoinCommunityForm, self).__init__(*args, **kwargs)
        
        def clean(self):
            cleaned_data = super().clean()
            pin = self.cleaned_data['pin']
            test_join_code = f'{pin}'
            if Community.objects.filter(join_code=test_join_code).exists():
                # Make sure user is not already a member of the community
                communityToJoin = Community.objects.get(join_code=test_join_code)
                if UserRoleWithinCommunity.objects.filter(
                    community=communityToJoin,
                    user=self.user # here I call self.user instead of self.request.user
                ).exists():
                    raise forms.ValidationError("You're already a member 
                                                 of that Community")
                else:
                    return cleaned_data
            else:
                raise forms.ValidationError("Join code is incorrect")