Django: Go to a URL with select option using GET and views.py or pure JavaScript gives a problem: Back to previous page requires 2 clicks - django

I show two solutions, one with GET, the other with Javascript. In both I need to double click the back button to come back to the previous page. I think this is a Django problem.
GET case
In templates I have the following form:
<form id="form-user" action="" method="GET">
<select id="user" data-slug="{{x.user}}" onChange=selectChange(this)>
<option value="">choose</option>
<option value="view">View</option>
</select>
</form>
<script>
function selectChange(select) {
var selectID = select.id;
var value = select.value;
if ($('#'+selectID).data('slug')) {
var my_slug = $('#'+selectID).data('slug');
var my_url = "{% url 'profiles:profile-detail' slug=none %}".replace(/none/, my_slug.toString());
}
if (value == 'view'){
$("#form-"+selectID).attr("action", my_url);
$("#form-"+selectID).submit();
}
}
</script>
In views.py I have:
class ProfileView(DetailView):
model = Profile
def get_object(self, *args, **kwargs):
myslug = self.kwargs.get('slug')
user = User.objects.get(username=myslug)
profile = Profile.objects.get(user=user)
return profile
In urls.py I have:
path('<slug>/', views.ProfileView.as_view(), name='profile-detail'),
This approach takes me to the profile page of {{x.user}}=user1, however when I click the backward button in the browser, it goes from mypath/user1 to mypath/user1?. I don't like this ?. To go back to the previous page I would need to click the backward button twice. I would like to click it only once and remove this ? transition step.
Javascript case
views.py doesn't change as it is not needed. template is quite similar, we just remove the method="GET" in the HTML form tag, while in the javascript we only change the following:
if (value == 'view'){
window.location.href = my_url;
}
Here also, I manage to go to the user page, however to come back to the previous page I need a double click, in this case it goes from mypath/user1 to mypath/user1 and then to the previous page.
I do believe it is a django problem but I'm not sure if we can solve it
EDIT:
As suggested a request POST solution is not required in this case. Also views.py is better written as:
def get_object(self, *args, **kwargs):
slug = self.kwargs.get('slug')
return get_object_or_404(Profile, user__username=slug)
However this doesn't fix the problem.

What about just a link?
View
Through CSS, you can style the link as a button or something else. This will trigger a GET request. A POST request makes not much sense, since you do not change the state: GET requests are supposed to have no side-effects, and POST requests are normally used to create, update, or remove data.
Your view can also be simplified: fetching the User is not necessary:
from django.shortcuts import get_object_or_404
class ProfileView(DetailView):
model = Profile
def get_object(self, *args, **kwargs):
return get_object_or_404(Profile, user__username=myslug)

Related

Django update boolean field with a form

My simple web-application has two models that are linked (one to many).
The first model (Newplate) has a boolean field called plate_complete. This is set to False (0) at the start.
questions:
In a html page, I am trying to build a form and button that when pressed sets the above field to True. At the moment when I click the button the page refreshes but there is no change to the database (plate_complete is still False). How do I do this?
Ideally, once the button is pressed I would also like to re-direct the user to another webpage (readplates.html). This webpage does not require the pk field (but the form does to change the specific record) Hence for now I am just refreshing the extendingplates.html file. How do I do this too ?
My code:
"""Model"""
class NewPlate(models.Model):
plate_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
plate_complete = models.BooleanField()
"""view"""
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
newplate.plate_complete = True
newplate.save()
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete = True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
"""URLS"""
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('extendplates/<pk>/', views.publish_plates, name='publishplates'),
"""HTML"""
<form method="POST" action="{% url 'tablet:publishplates' newplate.plate_id %}">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button></form>
-------Added show plates view:---------
def show_plates(request,pk):
mod = NewPlate.objects.all()
newplate= get_object_or_404(mod, pk=pk)
add2plate= Add2Plate.objects.filter(Add2Plateid=pk)
return render(request, 'tablet/show_plates.html', {'newplate': newplate,'add2plate': add2plate})
Thank you
The problem is two of your urls have the same pattern 'extendplates/<pk>/'. Django uses the first pattern that matches a url. I suppose that one of these view views.show_plates is meant to display the form and the other views.publish_plates is meant to accept the posted form data.
This means that simply both of these views should simply be a single view (to differentiate if the form is submitted we will simply check the requests method):
from django.shortcuts import redirect, render
def show_plates(request, plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save()
return redirect('tablet:extendplates', plate_id)
context = {'newplate': newplate}
return render(request, 'your_template_name.html', context)
Now your url patterns can simply be (Note: Also captured arguments are passed as keyword arguments to the view so they should be consistent for your view and pattern):
urlpatterns = [
...
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<uuid:plate_id>/', views.show_plates, name='showplates'),
...
]
In your form simply forego the action attribute as it is on the same page:
<form method="POST">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button>
</form>
You should avoid changing state on a get request like your view does currently.
Handle the POST request and change the data if the request is valid (ensuring CSRF protection).
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save(update_fields=['plate_complete']) # a more efficient save
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete=True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
You could also put a hidden input in the form, or make a form in Django to hold the hidden input, which stores the plate_id value and that way you can have a generic URL which will fetch that ID from the POST data.
Now the real problem you've got here, is that you've got 2 URLs which are the same, but with 2 different views.
I'd suggest you change that so that URLs are unique;
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('publish-plates/<pk>/', views.publish_plates, name='publishplates'),

django get filtered by parameter

The site I am building has 6 profiles (desks) and these are accessed via buttons at the top of the page using:
path('home/<slug:slug>/', HomeView.as_view(), name='homepage')
and is accessed by a button with:
{% url 'homepage', slug=desk.slug %}
So is filtered in the queryset. How can I access what that value so I can use it in the template for styling purposes? basically I want to highlight the desk button on the page so the user has visual feedback on where he is. Is there a way to do this nicely, as currently the only way I can think of doing this is to use:
{% request.url %}
and then split off the end of the url, but that seems like a nasty hack to me. Unfortunately google can't get past the word filter so all I get is django-filter documentation and issues.
UPDATE: adding view:
class HomeView(ListView):
template_name = pcc_homepage/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['desks'] = Desk.objects.all()
context['updates'] = Update.objects.all().order_by('-published')
return context
def get_queryset(self):
return Handover.objects.filter(desk__slug=self.kwargs['slug']).order_by('-published')

Don't include blank fields in GET request emitted by Django form

On my Django-powered site, I have a search page with several optional fields. The search page is a Django form, and my view function is the typical:
def search(request):
form = SearchForm(request.GET or None)
if form.is_valid():
return form.display_results(request)
return render(request, 'search.html', {'form': form})
Form.display_results() uses the fields that are provided to query the DB and render a response. My search.html includes:
<form action="/search/" method="get">{% csrf_token %}
<!-- Render the form fields -->
<input type="submit" value="Search" />
<input type="reset" value="Reset form" />
</form>
Since most searches will have several blank fields, I'd like not to include them in the GET request emitted by the submit button on search.html. Current searches look something like:
http://mysite/search/?csrfmiddlewaretoken=blah&optional_field1=&optional_field2=&optional_field3=oohIWantThisOne
And I'd like them to look like:
http://mysite/search/?csrfmiddlewaretoken=blah&optional_field3=oohIWantThisOne
Of course, I have a several more fields. This would be nice to have because it would make search URLs more easily human-parsable and sharable.
You could use jQuery with an button trigger. Give the form and submit button ids.
$("#button_id").click(function(){
$("input").each(function(){
if($(this).val() == '') {
$(this).remove();
}
});
$("#form_id").submit();
});
That (or something similar) should remove all the empty fields before the submit.
You could also POST the form. Then build the search url and redirect with empty values removed.
See Hide empty fields from GET form by Bill Erickson:
jQuery(document).ready(function($){
// Remove empty fields from GET forms
// Author: Bill Erickson
// URL: http://www.billerickson.net/code/hide-empty-fields-get-form/
// Change 'form' to class or ID of your specific form
$("form").submit(function() {
$(this).find(":input").filter(function(){ return !this.value; }).attr("disabled", "disabled");
return true; // ensure form still submits
});
// Un-disable form fields when page loads, in case they click back after submission
$("form").find(":input").prop("disabled", false);
}
disclaimer: This is a very old question, and the only one I could find that matches the problem I ran into. It's entirely possible that my solution didn't exist at the time, and an even better way has since been added.
I'm using Django 3.2. I didn't want to use js/jQuery, nor did I want to use a POST form, so here's what I came up with. In a nutshell, it just checks the GET data to see if there are any default values present, and simply redirects to a URL that doesn't have them.
In your view:
from django.shortcuts import redirect
def myView(request):
if not is_clean_form(request.GET):
return redirect(whatever_url_path + clean_url_parameters(request.GET))
else:
# whatever your view does normally
Helper functions (consider making these static methods of your Form class, to keep all that type of stuff together):
from urllib.parse import urlencode
# fields and their default value (eg. empty string)
default_form_values = [
("some_field_name", ""),
# ...
]
def is_clean_form(form_dict):
for field, default_value in default_form_values:
if field in form_dict and form_dict[field] == default_value:
return False
return True
def clean_url_parameters(form_dict):
return "/?" + urlencode([
(field, form_dict[field]) for (field, default_value) in default_form_values
if field in form_dict and form_dict[field] != default_value
])

Django how to keep GET data?

Here is my problem :
I have a list of messages which I can filter using a form on the same page. Also, I can display one of the message below the list by clicking on it.
My problem is that if I filter the list and then selects a message to display it, the GET data filtering the list are 'lost'. So all the messages are displayed in the list again.
How could I display a message and keep the list as it was when I clicked on the message?
My urls:
url(r'^inbox/$', view='inbox', name="kernel-networking-messages-inbox"),
url(r'^inbox/(?P<message_pk>\d+)/$', view='inbox_message', name="kernel-networking-messages-inbox-read"),
My views:
class InboxView(SearchViewMixin):
template_name = "kernel/networking/messages/inbox.html"
form_class = InboxForm
inbox = InboxView.as_view()
class InboxMessageView(InboxView):
def dispatch(self, request, *args, **kwargs):
self.message = get_object_or_404(Message, pk=kwargs['message_pk'])
return super(InboxMessageView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(InboxMessageView, self).get_context_data(**kwargs)
context['message'] = self.message
return context
inbox_message = InboxMessageView.as_view()
inbox.html:
<form action="" method="get">
...
<input type="submit" value="Search"/>
<table>
...
</table>
{% if message %}
// display message
{% endif %}
I tried to be as clear as possible :/
I see two options.
1: Use Django's session framework.
When you receive a request for filtering, store the filter options in the session: request.session['filter_options'] = filter_option_dict
Then, when you load the page and don't see any filtering options in request.GET, check if there are any options in the session:
if(len(request.GET) > 0):
#get filter options from request.GET
#save filter options to session
elif('filter_options' in request.session):
#get filter options from session
else:
#no filter options, display without filtering
Note that the session is designed for temporary data, and is specific to one browser on one computer. That's not a problem for this use case, but it's something to remember.
2: Use ajax to load the message without changing the rest of the page. On the server, you create a view that returns a message without the page header or any other formatting, and javascript on the front-end to send requests and insert the retrieved messages into the page. This is very easy with JQuery, although it can be done without.
This would be my preferred solution, as websites that update themselves interactively feel much nicer from a user's perspective than one that has to refresh every time they click a link.

Django: Redirect to current article after comment post

I am trying to use comments application in my project.
I tried to use code ({% render_comment_form for event %}), shown in the documentation here:
Django comments
And the question is how to make the form redirect to the same page, after the submission.
Also the big question is:
Currently if we have any error found in the for, then we're redirected to preview template.
Is that possible to avoid this behaviour and display errors over the same form (on the same page)?
I will show you how I resolved it in my blog, so you could do something similar. My comments are for Entry model in entries application.
First add new method for your Entry (like) object.
def get_absolute_url(self):
return "/%i/%i/%i/entry/%i/%s/" % (self.date.year, self.date.month, self.date.day, self.id, self.slug)
It generates url for entry objects. URL example: /2009/12/12/entry/1/lorem-ipsum/
To urls.py add 1 line:
(r'^comments/posted/$', 'smenteks_blog.entries.views.comment_posted'),
So now you should have at least 2 lines for comments in your urls.py file.
(r'^comments/posted/$', 'smenteks_blog.entries.views.comment_posted'),
(r'^comments/', include('django.contrib.comments.urls')),
For entries (like) application in views.py file add function:
from django.contrib.comments import Comment #A
...
def comment_posted(request):
if request.GET['c']:
comment_id = request.GET['c'] #B
comment = Comment.objects.get( pk=comment_id )
entry = Entry.objects.get(id=comment.object_pk) #C
if entry:
return HttpResponseRedirect( entry.get_absolute_url() ) #D
return HttpResponseRedirect( "/" )
A) Import on top of file to have
access for comment object,
B) Get
comment_id form REQUEST,
C) Fetch
entry object,
D) Use
get_absolute_url method to make
proper redirect.
Now:
Post button in comment form on entry site redirects user on the same (entry) site.
Post button on preview site redirects user on the proper (entry) site.
Preview button in comment form on entry site and on preview site redirects user on preview site
Thankyou page is not more in use (That page was quite annoying in my opinion).
Next thing good to do is to override preview.html template:
Go to django framework dir, under linux it could by /usr/share/pyshared/.
Get original preview.html template from DJANGO_DIR/contrib/comments/templates/comments/preview.html
Copy it to templates direcotry in your project PROJECT_DIR/templates/comments/entries_preview.html
From now on, it shoud override default template, You can change extends in this way: {% extends "your_pagelayout.html" %} to have your layout and all css files working.
Take a look at "Django-1.4/django/contrib/comments/templates/comments/" folder and you will see in the "form.html" file, there is the line
{% if next %}<div><input type="hidden" name="next" value="{{ next }}" /></div>{% endif %}
Therefore, in the Article-Detail view, you can include the "next" attribute in the context data, and then the comment framework will do the rest
class ArticleDetailView(DetailView):
model = Article
context_object_name = 'article'
def get_context_data(self, **kwargs):
context = super(ArticleDetailView, self).get_context_data(**kwargs)
context['next'] = reverse('blogs.views.article_detail_view',
kwargs={'pk':self.kwargs['pk'], 'slug': self.kwargs['slug']})
return context
Simplify Django’s Free Comments Redirection
Update: Now have the option to redirect as part of the comment form: see https://django-contrib-comments.readthedocs.io/en/latest/quickstart.html#redirecting-after-the-comment-post
This is a really simple redirect to implement. It redirects you back to the page where the comment was made.
When a comment is posted, the url comments/posted/ calls the view comment_posted which then redirects back to the referer page.
Be sure to replace [app_name] with your application name.
views.py
from urlparse import urlsplit
def comment_posted( request ):
referer = request.META.get('HTTP_REFERER', None)
if referer is None:
pass
try:
redirect_to = urlsplit(referer, 'http', False)[2]
except IndexError:
pass
return HttpResponseRedirect(redirect_to)
urls.py
( r'^comments/posted/$', '[app_name].views.comment_posted' ),