ModelForm self.object is NoneType in get_success_url

Hi, I added a OneToOne field to a “Profile” Model, an UpdateView and ModelForm so my users can update some of their User information along with some addition information (i.e. ‘us_citizen’). The updates are working correctly, but there is something odd.

If I override get_success_url in the view, there seems to be no issue, but if I set the success_url class variable, I get the error:

File “/home/dow/python/dawndir/env/lib/python3.10/site-packages/django/views/generic/edit.py”, line 120, in get_success_url
url = self.success_url.format(**self.object.dict)
AttributeError: ‘NoneType’ object has no attribute ‘dict

My worry is that I may be doing something wrong in my form or view code that is causing the view to lose its ‘object’ reference, and that this may cause other issues going forward. But after looking at the source code, I still don’t see how this is happening.

Here’s the relevant part of my code:

In models.py

class Profile(models.Model):
    user = models.OneToOneField(
        User, on_delete=models.CASCADE,
        primary_key=True,
        verbose_name=_('User'),
        related_name='profile')

    us_citizen = models.BooleanField(default=False)

@receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    instance.profile.save()

In views.py

class ProfileUpdateView(UpdateView):
    model = User
    form_class = ProfileUpdateForm
    template_name = 'hours/profile_form.html'
    # success_url = reverse_lazy('hours_list')
    # This gives message:
    #   AttributeError: 'NoneType' object has no attribute '__dict__'
   
    def test_func(self):
        obj = self.get_object()
        return self.request.user == obj.user

    # this works
    def get_success_url(self):
        return reverse('hours_list')

In forms.py

class ProfileUpdateForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        field = forms.BooleanField(label=_('U.S. Citizen?'),
                                   initial=self.instance.profile.us_citizen,
                                   required=False)
        self.fields['us_citizen'] = field
        self.helper.layout = Layout(
            Fieldset(
                _('User Profile'), 'username', 'first_name', 'last_name',
                'email', 'us_citizen'
            ),
        )
        self.helper.add_input(Submit('submit', _('Update')))

    def save(self, commit=True):
        value = self.cleaned_data.get('us_citizen')
        self.instance.profile.us_citizen = value
        super().save(commit=commit)

    class Meta:
        model = User
        fields = '''username first_name last_name email'''.split()

I really appreciate any help on this!

Best,
Dow

Please post your url definition for the url named hours_list.

Thanks, Ken – I actually have that url defined two places, which could be related to this issue. It’s here in the urls for the Hours app:

urlpatterns = [
    path("", views.HoursIndexView.as_view(), name="hours_list"),
    path("add/", views.HoursCreateView.as_view(), name="hours_create"),
    path("edit/<int:pk>/", views.HoursUpdateView.as_view(), name="hours_update"),
    path("delete/<int:pk>/", views.HoursDeleteView.as_view(), name="hours_delete"),
    path("set_lang/", views.SetLangView.as_view(), name="set_lang"),
    path("download/", views.AllHoursCSVView.as_view(), name="download"),
]

But I also later added it to the site URLs because I wanted it to be a sort of global landing page:

urlpatterns = [
    path('', include('django.contrib.auth.urls')),
    # use explicit paths for login and logout for the overridden views
    path('', include('account.urls')),
    path('hours/', include('hours.urls')),
    path('admin/', admin.site.urls),
    path('', HoursIndexView.as_view(), name="hours_list"),
    path('profile/<int:pk>', ProfileUpdateView.as_view(), name="profile"),
    path('__reload__/', include('django_browser_reload.urls')),
]

That duplication is probably the issue. It’s getting a bit late, but I’ll fix that tomorrow morning and let you know what I get…

Best,
Dow

You need to return the object from your form’s save method (otherwise it returns None).

1 Like

Of course! Good catch, and thanks for your help!!

1 Like