Django - How to create a simple confirmation view? - python

I'm trying to create a View in which the user is asked to confirm an action of updating a value of an instance. I want the View to only have a submit button. What I want is similar to a DeleteView except what I want is to update a value of a record instead of deleting the record.
I have tried using Class based UpdateView, but it requires that I specify a fields parameter in which I must specify at least one field and I do not want to show any field, just the submit button.
This is what my template now looks like:
<h4>Complete {{ item.name }}?</h4>
<form method='POST' class="gia-form">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" class="button" value="Complete Item">
Cancel
</form>
Url for the view:
url(r'^action/complete-item/(?P<pk>\d+)/$', views.CompleteItemView.as_view(), name='complete'),
My UpdateView:
class CompleteItemView(UpdateView):
model = models.Item
template_name = 'items/complete_item.html'
fields = ("status",)
def form_valid(self, form):
form.instance.status = 'completed'
return super().form_valid(form)
Above I have chosen to show the status field, and this is what I'm trying to get rid of in an elegant way as I just want to show the confirm button without any fields.

Instead of
{{ form.as_p }}
You can simply write
<input name="{{ form.status.html_name }}" id="{{ form.status.id_for_label }}"
value="{{ form.status.value }}" type="hidden">
This will render the status field as hidden and only the Submit button will be visible.

Have you considered using Bootstrap Modals.
You could have a 'Submit' button which wouldn't actually submit the form, but instead it would open the modal where you could have the form submit button.
You would just have to remember to create the modal inside the form tags.

You could use Javascript like this:
<script>
$(document).on('click', '.some_class', function(){
return confirm('Are you sure you want to update this?');
})
</script>
And add class some_class to your button

Related

Unable to select value in action intermediate page

I have two models User and Group.
I'm implementing an action "Change Groups" in UsersAdmin that redirects to an intermediate page with 2 MultipleChoiceFields for Groups, that I want to be used to either remove users from certain groups, add users to other groups, or do both in one go (i.e. move them).
The docs are very short about this subject, so in order to do this, I'm following this article.
Here's my form:
class ChangeUsersGroupsForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
from_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
to_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
My admin action:
def change_groups_action(self, request, queryset):
if 'apply' in request.POST:
from_groups = request.POST["from_groups"]
to_groups = request.POST["to_groups"]
from_groups_qs = Group.objects.filter(pk__in=from_groups).all()
to_groups_qs = Group.objects.filter(pk__in=to_groups).all()
user_ids = [u.user_id for u in queryset]
# task that will do the job of actually moving the users
change_users_groups.delay(from_groups_qs, to_groups_qs)
self.message_user(request, "Changed groups of %s users" % len(user_ids))
return HttpResponseRedirect(request.get_full_path())
form = ChangeUsersGroupsForm(initial={'_selected_action': queryset.values_list('id', flat=True)})
return render(request, "admin/change_users_groups.html", {'queryset': queryset, 'form': form})
change_groups_action.short_description = "Change Groups"
Here's my template:
<!-- users/templates/admin/change_users_groups.html -->
{% extends "admin/base_site.html" %} {% block content %}
<form action="" method="post">
{% csrf_token %}
{{ form }}
<br />
<br />
<p>The Group changes will be applied to the following users:</p>
<ul>
{{ queryset|unordered_list }}
</ul>
<input type="hidden" name="action" value="change_groups_action" />
<input type="submit" name="apply" value="Confirm" />
</form>
{% endblock %}
This is how the intermediate page renders:
First (but minor) issue is that the form fields are displayed in a row, instead of each in one row. But let's skip that for now.
The big issue is that when I select a Group, nothing happens, the Group doesn't seem to be selected.
Instead I see the following error on the browser Console:
Uncaught TypeError: django.jQuery is not a function
This error is printed every time I click on an option.
Anyone knows what's going on?
Django 2.2
Python 3.8.10
I've had this problem before, but in different situation (unrelated to Django admin). I almost always turned out to be because JQuery was not loaded or it was loaded too late in the template.
According to Django documentation:
If you want to use jQuery in your own admin JavaScript without
including a second copy, you can use the django.jQuery object on
changelist and add/edit views. Also, your own admin forms or widgets
depending on django.jQuery must specify js=['admin/js/jquery.init.js',
…] when declaring form media assets.
So that would make your form class look like:
class ChangeUsersGroupsForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
from_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
to_groups = forms.ModelMultipleChoiceField(Group.objects, required=False)
class Media:
js = ['admin/js/jquery.init.js']
Regarding your form field form. I suggest rendering each field separately like so:
{{ form.from_groups }}
<br/>
{{ form.to_groups }}
This seems like the simplest solution
Let me know if that helps :)

How do I make a button click change the django order_by?

Here's my views.py function.
def publications(request):
if request.method == 'POST':
publications = UserPublications.objects.all().order_by('status_date')
else:
publications = UserPublications.objects.all().order_by('contributors')
context = {'publications': publications}
return render(request, 'users/publications.html', context)
Here's my publications.html:
<button type="submit" method="POST">Filter By Date</button>
<ol class="text">
{% for publication in publications %}
<div>{{publication.text}}</div>
{% endfor %}
</ol>
I want a button to be clicked on and if someone clicks on it it changes to filter by date instead of by contributors (both attributes of my model). How would I do that? Right now I have no errors but when I click on the button it's not updating the sorting on my objects. Thanks in advance.
The button needs to be linked to a <form>, so:
<form method="POST" action="{% url 'view-name-for-publications' %}">
<button type="submit">Filter By Date</button>
</form>
It however does not make much sense to use the HTTP request method to determine whether to sort by date or not. Usually, you make a GET request with a query string [wiki] to specify on what field it will sort.

Using Django FormPreview the right way

My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')

Web server ini-file editor using django

I wish to edit ini files over web server, decided to use django, been using it for few days now. I can't figure out how to accomplish this. I have ini file structure looking like this:
{'GROUP', {PROPERTY : VALUE}}
Example when I read this kind of ini file:
[LOG]
FilePath = C:/Log
[CMD]
Level = 5
I will get my data structure filled like this:
{'LOG', {'FilePath' : 'C:/Log',},
{'CMD', {'Level', '5'}}}
Loop looks like this:
for group in settingsDict:
print group # group
for property in settingsDict[group]:
print property , # property
print settingsDict[group][property] # value
I am using ini file parser.
I am having trouble understanding how to correctly develop in django: views.py is some kind of controller for django and templates are views and model would be my ini file (probably linked with db using django model), or am I getting something wrong?
I have no problem passing this dictionary to template, making a for loop in it and creating html tags like: <input type="text" name={{ property }} value={{ value }} maxlength="100" />. But how do I then post all the edited values back to control to save them in file (or db)? I Would need all 3 values, that is GROUP, PROPERTY and VALUE.
Then I discovered django also has html widgets, which you create in views.py and then pass it to template. But this is where I stop understanding things, since I am creating widget in my controller class, but even if I am.
Shall I create a list of all django widgets and pass it to template? Same question occurs, how do I get all the widget values back to controller (views.py)?
Update (11.6.2012):
My code looks like this:
views.py
class DynForm(forms.Form):
def setFields(self, kwds):
keys = kwds.keys()
keys.sort()
for k in keys:
self.fields[k] = kwds[k]
def settings(request):
global Settings #my ini dict
kwargs = {}
for group in Settings:
for property in Settings[group]:
kwargs[property] = forms.CharField(label = property, initial = Settings[group][property])
f = DynForm()
f.setFields(kwargs)
return render_to_response('/settings.html',
{
'textWidget' : f,
})
#csrf_exempt
def save(request):
if request.method == 'POST': # If the form has been submitted...
form = DynForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# process form data
# and return response
settings.html
<form action="/save/" method="post">
{% csrf_token %}
{% for field in textWidget %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Save" /></p>
</form>
The problem is, DynForm(request.POST) returns null so I can't get field values. My request.POST is correct, containing all fields and values. As much as I know, I am not suppose to parse request.POST data "by hands"?
OK, finally figured it out, taking me a lot of time (I am lacking a lot of python and django knowledge). I can't paste final solution because of copy right permissions, here is the concept:
Form
class DynamicForm(forms.Form):
def __init__(self,*k,**kw):
forms.Form.__init__(self,*k,**kw)
# loop over data from **kw
# create field
# set field default value
Notes about this code:
If form doesn't use super(SuperForm, self).__init__(*args, **kwargs), you must use forms.Form.__init__(self,*k,**kw) so you can append fields to form using self.fields attribute.
If you need to use default field value, use self.data[field] = defVal not initial = defVal. Form becomes unbound and you won't be able to parse data in your request.POST method. Unbound form (and with errors) will always return is_valid() False.
With this class, you have no problems parsing request.POST data. Looping over dynamic form fields looks like this:
View
for name,field in form.fields.items():
# name - field name
# form.data[name] - field value
Notes:
For the sake of simplisity use #csrf_exempt tag before POST method. See http://jordanmessina.com/2010/05/24/django-1-2-csrf-verification-failed/
Template code loops over fields in form displaying field label and value separated with :
Template
<form action="/Tris/save/" method="post">
{% csrf_token %}
{% for field in textWidget %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.non_field_errors }}
{{ field.label }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Save" /></p>
</form>
Most of the solution is from here: http://jacobian.org/writing/dynamic-form-generation/ and django documentation.

How to realize a dynamic sidebar with Django?

I plan on creating a sidebar with changing elements (depending on the current url and authentication-status).
For example: The default sidebar shows a login and a tag cloud.
If a user is already logged in, I want to display a user menu.
If the current url is /tagcloud, I want to hide it from the sidebar.
Actually, I need a way which enables me to do something like this in a view:
def some_view(request):
if request.user.is_authenticated():
sidebar.remove('login')
sidebar.add('user_menu')
def tag_cloud(request):
sidebar.remove('tag_cloud')
Afterwards, I want to pass the sidebar (implicitly, without passing it to render_to_response) to the template where I have in mind to do something like this:
<div id="sidebar">
{{ sidebar }}
</div>
Is this possible?
You'd better do this in a context_processors.py file
That also mean you have to use a RequestContext when returning your views
def include_tagcloud(request):
if request.path == '/tagcould/':
tagcloud = Tags.objects.filter(active=True) #whatever
return {'tagcloud': tagcloud}
def include_login(request):
if request.user.is_authenticated():
loginform = MyLoginForm(request.POST)
#passing a Django form + POST data in the case of re-submit
return {'loginform' : loginform}
And then in your template :
{% if loginform %}
<form action="accounts/login/">
{{form.as_p}}
<input type="submit" name="Login">
</form>
{% endif %}
{% if tagcloud %}
{%for tag in tagcloud %}.....{%for}
{% endif %}
In this example the login form points to a fixed view,
if you want to catch the login form post on everyview, I don't know how to do
EDIT : if you don't use the CSRF features of Django, you could simply insert the login form in the base template without using any django form and pointing to a login view :
{% if user.is_authenticated %}
<form action="accounts/login/">
<input type="text" name="username"><input type="password" name="password">
<input type="submit" name="Login">
</form>
{% endif %}
Yeah, but you can use inheritance of templates as well as composition. Then include your sidebar in a parent template that is used/inherited from in all of your templates. Then it is easy to find the template for the sidebar: it's in a separate file.
Answer of #Dominique is correct but When you write something in context_processors that's load at any page of the website. That maybe makes a performance issue.
I think the right way to handle dynamic sidebar is simpletag and use where you need.
def get_sidebar():
tags = Tags.objects.filter(active=True)
latest_posts = Post.objects.all().order_by('-create_at')[:10]
html = render_to_string("sidebar.html", {
"tags": tags,
"latest_posts": latest_posts
})
return html
And now just use in template files:
<div class="col-md-4 sidebar">
{% get_sidebar %}
</div>
Also, you can pass request to simpletag to use user.is_authenticated for authenticated user access.

Categories

Resources