'HttpResponseRedirect' object has no attribute '_meta'

I have the following code in views.py

class OrganisationAddView(CreateView):
    model = models.Organisation
    fields = (
        'full_name',
        'registration_number',
    )
    template_name = 'generic/add_form.html'

    def form_valid(self, form):
        form.instance.node_id = self.request.user.node_id
        return super().form_valid(form)


class OrganisationEditView(UpdateView):
    model = models.Organisation
    fields = (
        'full_name',
        'registration_number',
    )
    template_name = 'generic/edit_form.html'

    def get_object(self, queryset=None):
        try:
            obj = models.Organisation.objects.get(node_id=self.request.user.node_id)
        except ObjectDoesNotExist:
            return redirect('organisation-add')

        return obj

The idea is that in case of one-to-one relationships OrganisationEditView gets the object, when there is no object the view redirects to OrganisationAddView, otherwise the view renders an update form.

The problem is that when there is no object OrganisationEditView gives me an AttributeError:

Request Method:	GET
Request URL:	http://localhost:8000/nodes/organisation/edit
Django Version:	4.0.4
Exception Type:	AttributeError
Exception Value:	
'HttpResponseRedirect' object has no attribute '_meta'
Exception Location:	/Users/me/Documents/project/venv/lib/python3.10/site-packages/django/forms/models.py, line 105, in model_to_dict
Python Executable:	/Users/me/Documents/project/venv/bin/python3
Python Version:	3.10.4
Python Path:	
['/Users/me/Documents/project/backend',
 '/Library/Frameworks/Python.framework/Versions/3.10/lib/python310.zip',
 '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10',
 '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload',
 '/Users/annet/Documents/project/venv/lib/python3.10/site-packages']
Server time:	Tue, 19 Jul 2022 11:33:27 +0000

Why doesn’t this code work?

Kind regards,

Johanna

The get_object function must return an instance of the model object being accessed by the view. That method cannot return a redirect.

(The return statement here is identifying what’s being returned to the calling function, not what’s being sent to the client browser.)

Hi Ken,

Thanks for your reply, I get it.

Is there a way to implement the behavior I want using an other function?

Kind regards,

Johanna

You could create a “parent view” that checks to see whether or not the desired object exists and then create the instance of the appropriate CBV and dispatches to it. But that seems like a lot of work for very little real benefit.

You could try returning None - that’s what the CreateView CBV does. But I have no idea what nasty side-effects that may have within the UpdateView.

I’d probably approach it from the other direction. I’d look at the view that is sending you to this view and address the issue from that end.

Hi Ken,

I like the idea of the ‘parent view’ … I’m trying to get my head around the implementation.

I had a look at Classy Class-Based Views, the DetailView has both a dispatch() and get_object() method, is this the class I use to create the parent view?

This is what I’ve come up with so far, is this what you had in mind?

class OrganisationDetailView(DetailView):
    model = models.Organisation

    def get(self, request, *args, **kwargs):
        try:
            self.object = model.objects.get(node_id=self.request.user.node_id)
        except ObjectDoesNotExist:
            self.object = None

    def dispatch(self, request, *args, **kwargs):
        if self.object:
            return super(OrganisationEditView, self).dispatch(request, *args, **kwargs)
        else:
            return super(OrganisationAddView, self).dispatch(request, *args, **kwargs)


class OrganisationAddView(CreateView):
    model = models.Organisation


class OrganisationEditView(UpdateView):
    model = models.Organisation

Kind regards,

Johanna

No, unfortunately it doesn’t work like that.

What you’re doing with this “parent view” is actually replacing part of the URL dispatching logic with a function-based view, not the view dispatching logic in a CBV.

As a very rough attempt at showing it, this is generally what I’m thinking of:

def organization_detail_view(**kwargs):
    request = kwargs['request']
    node_id = request.user.node_id
    if model.objects.filter(node_id=node_id).exists():
        view = OrganizationEditView.as_view(**kwargs)
    else:
        view = OrganizationAddView.as_view(**kwargs)
    return view()

Note: I’m winging this - there may be an error or ten included here. (I know for certain I’m not doing any error checking - that should all be added.)

But like I said earlier - if I had this situation for real, I’d be looking to approach this from the other end. I’d be looking at where these links are being created and rendered on a page, and fix this at the source.

Hi Ken,

Thanks for your explanation and code example. I got the idea.

The reason I don’t fix this at the source is that doing so would result in a lot of unnecessary database hits.

Kind regards,

Johanna

There are two ways of addressing this at the source - you can either create these objects up front, or you can perform this test when the links are being created.

In the long run, creating the objects up front will create fewer “database hits” than this test will incur over the life of the application.

Honestly? Worrying about an extra DB call or two is wasted effort. We’re not talking about an N+1 situation or some O(N^2) algorithm here. You’re not going to see the difference and probably wouldn’t even be able to measure the difference.

If you’re set on making the edit/update view redirect when an object doesn’t exist, you would need to override the get function and wrap the get_object call in the try/except block.

    def get(self, request, *args, **kwargs):
        try:
            object = self.get_object()
        except ObjectDoesNotExist:
            return redirect('organisation-add')

        return return self.render_to_response(self.get_context_data())

As Ken said, get_object can’t return a HttpResponse. So you need to move up a level in the view’s flow.

Side question:

Would this

need to be adjusted if there are url parameters involved?

Yes, it would. I copied what was used in the original get_object function.

1 Like

Hi,

The following solution does what I want:

class OrganisationFormView(FormView):
    form_class = forms.OrganisationForm
    template_name = 'generic/form.html'
    success_url = reverse_lazy('node-index')

    def get_form(self, form_class=None):
        if form_class is None:
            form_class = self.get_form_class()
        try:
            obj = models.Organisation.objects.get(node_id=self.request.user.node_id)
            return form_class(instance=obj, **self.get_form_kwargs())
        except ObjectDoesNotExist:
            return form_class(**self.get_form_kwargs())

    def form_valid(self, form):
        form.instance.node_id = self.request.user.node_id
        form.save()
        return super().form_valid(form)

Are there any issues with this solution that I’m not aware of?

Kind regards,

Johanna

I don’t see anything here that concerns me - and if you tested it and works, then I think you’re good.