How do I get a Django redirect to work properly when updating my database? - django

I can't get my redirect to work correctly after updating a bicycle in my Django app.
When I use my bikes_update() method and update the bicycle profile, if my redirect is just to '/bikes', I'm taken back to the bicycle collection/bicycles owned with no issue. But when I try and redirect back to the profile of the bike I just editted, I get the error that my return pathway doesn't match any of the url patterns. Even though that's the same pathway that initially takes me to the bicycle profile view (not the same one where I edit the bicycle, that is a different view).
I've pasted the bikes_update() method here:
def bikes_update(request, bike_id):
if request.method != 'POST' or 'user_id' not in request.session:
return redirect("/")
this_bike = Bike.objects.filter(id=bike_id).update(
name = request.POST['bike_name'],
model = request.POST['model_id'],
)
return redirect('/bikes')
return redirect('/bikes/<int:bike_id>')
return redirect('/bikes/<int:bike_id>/')
I obviously only use one of the redirects, not all the three in the final block. But I can't figure out why at least one of the final two do not work. When I have the bottom two redirects commented out, the 'return redirect('/bikes')' line works fine, but it takes me to the page listing all of my bikes when I want to go back the profile of the bike I just editted so I don't have to navigate back to it from the bicycle collection.
Navigating to /bikes/int:bike_id/ works fine when going to the profile initially from the list of bikes, but after I finishing editing I run into this error even though it's the same line as what got me to the profile in the first place. As soon as I click the submit button and try to redirect back to /bikes/int:bike_id/ I get my error.
I'm able to update the bicycle profile correctly, so it's something to do with this redirect, but I don't get it.
I don't understand how line 12 in my screen snippet and the current path (both highlighted in the image), aren't the same/don't match.

If you want to redirect on /bikes/<int:bike_id>/ then you can use a format string like this...
def bikes_update(request, bike_id):
if request.method != 'POST' or 'user_id' not in request.session:
return redirect("/")
this_bike = Bike.objects.filter(id=bike_id).update(
name = request.POST['bike_name'],
model = request.POST['model_id'],
)
return redirect(f'/bikes/{bike_id}/')

You have to add the id here not this "int:bike_id". put your id variable here like this redirect(f'/bikes/{Id}').

redirect can take a model as an argument, so you could do something like:
def bikes_update(request, bike_id):
if request.method != 'POST' or 'user_id' not in request.session:
return redirect("/")
this_bike = Bike.objects.filter(id=bike_id).update(
name = request.POST['bike_name'],
model = request.POST['model_id'],
)
this_bike = get_object_or_404(Bike, pk=bike_id)
return redirect(this_bike)
You will need a get_absolute_url method in your models.py though. For example:
def get_absolute_url(self):
return reverse("bikes", kwargs={"pk": self.pk})
This can be used for other things in Django as well (including usage in templates).
More info here: Django Docs
Of course, as you're obtaining the object now for this purpose, you may want to consider refactoring (not using a queryset update and perhaps changing the this_bike attributes instead) how you update it in the first place, as you're only dealing with a single object here.

Related

Sending form to another view django

I am building a website and I want various views that will ask the user to request a quote from our page. I want to keep the code as DRY as possible so I am writing a view quote which will receive the quote requests from various views and, if there is a validation error redirect back to the page that made the request. I managed to solve this using the super bad practice 'global variables'. I need a better solution, I would like redirecting to respective view with the current form so I can iterate through the form.errors. Here is my code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
global session_form
session_form = form
return redirect('Main:' + quote_for) # Here I would like to send form instead of storing in global variable`
You can use the HttpResponseRedirect function, and pass as argument the page that made the request.
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
All the META data is store on a dictionary, if you want to learn more check the documentation.
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
If you redirect to the referrer, form.errors will be empty, as redirection is always a GET request.
I can think of two solutions to your problem:
Submit forms asynchronously using JavaScript and so populate the errors
Make all the views containing the form support POST - one way to do this would be to create a base class that inherits from FormView
The second option is a typical way of handling forms in Django - you process both POST and GET inside the same view.
After two days of searching I finally found the answer. Instead of saving form in request.session I just save request.POST and then redirect. Here is the code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
request.session['invalid_form'] = request.POST
return redirect('Main:endview')
def endview(request):
session_form = request.session.pop('invalid_form', False)
if session_form:
form = Quote(session_form)
# render template again with invalid form ;)
Now I can repeat this with all the views I want and just change the what_for input of each form to match the respective view (Like I intended).

Is there a way to remove hyperlinks from objects on django admin changelist that user does not have permission to change?

I am currently utilizing the has_change_permission hook in my custom django admin class to implement a simple form of row-level permissions and determine whether a non-superuser can edit a particular object, like so:
def has_change_permission(self, request, obj=None):
if obj is None or request.user.is_superuser or (obj and not obj.superuser_only): # (my model has a 'superuser_only' flag that gets set via fixtures, but its beyond the scope of this question)
return True
return False
This works well enough: all objects are shown to a user, but if they click on an object that they don't have permission to edit then they are taken to my 403 page, presumably because PermissionDenied is raised. However, giving them a hyperlink to a permission denied page doesn't seem ideal for this case; I would like to show the objects but not provide any hyperlinks to the edit page on the list page (in addition to raising PermissionDenied if they tried to manually use the URL for the object). Is there a straightforward hook for removing these hyperlinks without a horrendous hack? I'm an experienced django developer so if you can point me in the right direction (if any exists), I'll be able to implement the details or determine that its not worth it for now.
I was able to accomplish this in a fairly straightforward way by overwriting the default get_list_display_links function in the ModelAdmin class (in my admin.py file):
def get_list_display_links(self, request, list_display):
if request.user.is_superuser:
if self.list_display_links or not list_display:
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
else:
# Ensures that no hyperlinks appear in the change list for non-superusers
self.list_display_links = (None, )
return self.list_display_links
Note that my code maintains the hyperlink for superusers. You can easily just use the latter part of the function if you don't want to make this distinction.

Django Overwrite form data saved

I've posted about this problem before, but I still haven't found a solution so I'm hoping I'll have better luck this time.
I have a form that takes inputted data by the user. In another page, I am creating the identical form that the user has populated (pre-filled with that information) for editing purposes. Users will come to this page to EDIT the information they have already put in. My problem is that it isn't overwriting the instance.
def edit(request):
a = request.session.get('a', None)
if a is None:
raise Http404('a was not found')
if request.method == 'POST':
form = Name_Form(request.POST, instance=a)
if form.is_valid():
j = form.save( commit=False )
j.save()
else:
form = Name_Form( instance = a )
For this form, I'm using "unique_together" for some of the values. I'm also calling on `{{ form.non_field_errors }} in the template.
What is happening is when I make changes in the editing view, if the fields changes involves those defined in "unique_together" then an error is returned telling me that the instance already exists. Otherwise it saves a new instance. It isn't OVERWRITING.
Note that the reason i am using unique_together is that I want to prevent users from initially inputting the same form twice (before the editing stage, in the initial inputting view).
Any ideas?
EDIT: note that "a" refers to a session that includes a drop down box of all the available instances. This carried forward will indicate which instance the user wants to edit.
`
Why not do a database lookup of the model your trying to save and pull the fields from the form to the model then save the model?
Instead to store model a in session you should store it on database. Then edit it:
def edit(request, pk):
a = A.objects.get( pk = pk)
...
pk it the a identifier, you can send it to view via urls.py. I encourage to you to use POST/Redirect/GET pattern.
You can add a 'state' field on your model to control workflow (draft, valid)
You should not save objects in the session. If you really need to use a session - save a PK there and retrieve object right before giving it to Form. But the better solution is to send it in GET or POST parameters or included in url. Sessions are unreliable, data inside it can be destroyed between user's requests.
And you can retrieve value from a session in a more pythonic way:
try:
a = request.session['a']
except KeyError:
raise Http404('a was not found')

Model.objects.all() won't refresh using djangoappengine

I'm wondering if the queryset manager in django-nonrel is broken, but I may just be missing something about how to use it. Here's my issue:
I've put together a simple blog using Django, but using djangoappengine. The model I use for the blog entries is called an Entry.
I have a view for deleting entries. Once an entry is deleted, it redirects to the home page, which lists all the remaining entries. The trouble is, the first time the redirect happens the entry which I just deleted remains there. If I refresh the page, it disappears from the list. The issue seems to be that even though I call Entry.objects.all() after deleting the entry, it is caching the values from earlier.
I moved the code over to a normal Django project and this bug didn't manifest, so I think it's to do with the queryset manager in django-nonrel.
I've tried doing lots of different things but I can't work out how requery the database. Here's some code for the view - I've simplified it so it doesn't even redirect, it just renders to response the entry_list with a call to Entry.objects.all(). Still the same problem.
def update_or_delete_object(request, *args, **kwargs):
"A wrapper around the generic update_object view which allows a delete button too."
if request.method == 'POST' and 'delete' in request.POST:
#If they've just clicked the delete button
object = get_object_or_404(Entry, pk=kwargs['object_id'])
object.delete()
return render_to_response('entry_list.html', {'object_list':Entry.objects.all()})
return update_object(request, *args, **kwargs)
Any ideas?

Django URL configuration

I have a purchase page, it can take an optional argument as a gift, if it is a gift, the view passes a gift form to the template and if not, a regular purchase form.
my old regular url, which redirects to two seperate views:
(r'^(?P<item>[-\w]+)/purchase/$', 'purchase_view'),
(r'^(?P<item>[-\w]+)/purchase/gift$', 'gift_view'),
and the views was like this:
def purchase_view(request,item):
....use purchase form
def gift_view(request,item):
....use giftform
It is a bad design indeed, as both views having are almost everything same but the forms used.
I have also thougt about using GET and giving gift as a GET param however it wasnt a good idea as I am using POST method for these pages, especially would cause issue after validation.
How can I make this a single url and a single view?
Thanks
urls.py
url(r'^(?P<item>[-\w]+)/purchase/$', 'purchase_view', name='purchase_view'),
url(r'^(?P<item>[-\w]+)/purchase/(?P<gift>gift)/$', 'purchase_view', name='gift_view'),
views.py
def purchase_view(request, item, gift=False):
if gift:
form = GiftForm
else:
form = PurchaseForm
...