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.