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?
Related
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.
In Django admin, when you are looking at a record there is a button called "history". When you click on this button, it tells you when the record was created and by whom and also each time a field was changed and by whom. I find this extremely useful. However, I noted that these only show up for actions that are done on the admin page. So when I change the record through a view, this is not displayed in that history. Is there an easy way to have my views store this information also so that it will all be visible from the admin page?
Thanks so much in advance for your help.
The admin app comes with a model - LogEntry. Every time you do something in the admin app, there is some code somewhere that saves a LogEntry. This is how the app works. For example in the changeform_view there is something like this:
def changeform_view(self, request, ...):
...
if request.method == "POST":
if all_valid(formsets) and form_validated:
# do some other stuff then
self.log_change(request, new_object, change_message)
def log_change(self, request, object, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=CHANGE,
change_message=message,
)
LogEntry.objects.log_action is where the log is created. Unfortunately, this doesn't happen anywhere else, unless you were to make it happen.
There's nothing stopping you from doing this though. You can create a LogEntry wherever you want.
Having said that, it might be a bit confusing since when you see a LogEntry you now no longer no if that is a change that has happened because of someone manually changing some data via the admin app, or a change that has occurred programatically. It would probably be a better idea to create your own Log model, and save logs where and when you want.
You can always display your Logs in your in the relevant admin view should you so wish. Something like this will do the trick:
class MyAdmin(admin.modelAdmin):
readonly_fields = (logs_field,)
def logs_field(self, instance):
logs = Log.objects.filter(object=instance)
return format_html_join(
'\n', "<p>{}: {}</p>",
((log.date, log.message) for log in logs)
)
I am using SessionWizardView from django-formtools project.
I've noticed that after successfully passing all form checks and executing done() function, which redirects to completely different view, user can still hit browser Back button and re-fill form again.
Isn't there any way to prevent that? I would assume that it would be some kind of session cleaning mechanism. But I cannot find any in documentation.
After some playing around I've found that it can be achieved in two lines:
def done(self, form_list, form_dict, **kwargs):
#regular form processing
self.instance_dict = None
self.storage.reset()
Now, after pressing Back button and submitting form it fails because no data exists and resets to first screen.
We have these moderately large and constantly growing tables and the most interesting stuff is always the most recent. Currently we have them ordered by creation time, so the interesting stuff is at the end. This requires 2 clicks when navigating, one to select the model and another select the last page.
I could of course change the order so the most recent stuff is at the front, but then the content of the pages would be continually changing as new stuff is pushed on the front, making exploration of the history harder.
So I was wondering if there was a way to have it go straight to the last page?
You can alter the change_list template (the pagination block) to show {{page.last}}
https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_list.html
This is a pretty peculiar request, but you could redirect the admin view if there is no page parameter in the URL (i.e. you are on the frontpage).
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
class MyAdmin(admin.ModelAdmin):
...
def changelist_view(self, request, extra_context=None):
if not request.GET.get("p", False):
return redirect("%s?p=%s" % (
reverse("admin:my_app__model__changeview"),
self.paginator.page_range[-1])
super(MyAdmin, self).changelist_view(request, extra_context=extra_context)
an alternative is to inject a p URL paremeter into the request before passing control over to the parent changelist_view method (but this requires copying the request). Edit: Wouter's solution is actually nice and simple
I am working with a SessionWizardView which is managing two forms. When I reload the page at the last step for instance I am back at the first step and have to type in all the fields again.
Is this the intended behaviour? If so, is it possible to get back to step I was at before I reloaded the page? Of course all the fields should be filled out accordingly.
class ManufacturingCalculatorWizard(SessionWizardView):
def get_template_names(self):
TEMPLATES = {
"blueprint": "manufacturing/forms/select_blueprint.haml",
"calculator": "manufacturing/forms/calculator.haml"
}
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
form_data = [form.cleaned_data for form in form_list]
rcontext = RequestContext(self.request, { 'data' : calculate_manufacturing_job(form_data) })
return render_to_response('manufacturing/forms/result.haml', rcontext)
Page rendered after done method is not part of wizard, so when you reload it, django will try to redirect to first page as new session of wizard.
If you want to add last step as something like preview and confirmation page, you can add a new step with dummy form and show appropriate data using the template. To get data from previous steps you can make use of get_context_data method of view, to build context with cleaned data of previous forms.