Django: Pass arguments from other form to submit button - python

I'm building a simple Django app that lets users track stuff for specific days:
It records entries with a name and a date using the upper form.
<form action="" method="post" style="margin-bottom: 1cm;">
{% csrf_token %}
<div class="form-group">
{{ form.entry_name.label_tag }}
<div class="input-group">
<input type="text" class="form-control" id="{{ form.entry_name.id_for_label }}" name="{{ form.entry_name.html_name }}" aria-label="new entry field">
{{ form.entry_date }}
<div class="input-group-append">
<button type="submit" class="btn btn-primary">Add</button>
</div>
</div>
<small id="{{ form.entry_name.id_for_label }}Help" class="form-text text-muted">This can be anything you want to track: An activity, food, how you slept, stress level, etc.</small>
</div>
</form>
Below the form, there are quick add buttons that let users quickly add a new entry with a specific name. In addition, I'd like to use the date selected in the form above. I.e., if a user sets a date in the upper form but then clicks one of the suggested buttons, it should still use the selected date for adding the new entry.
This is what the code for the suggested buttons currently looks like:
{% if entry_counts and entry_dict|length > 0 %}
<div class="card" style="margin-bottom: 1cm;">
<div class="card-body">
<div class="card-title">Suggested entries</div>
{% for name, count in entry_counts.items %}
<form method="post" action="{% url 'app:add_entry_with_date' name form.entry_date.value %}" style="display: inline-block;">
{% csrf_token %}
<button type="submit" class="btn btn-secondary" name="{{ name }}" style="margin-bottom: 5px;">{{ name }}</button>
</form>
{% endfor %}
</div>
</div>
{% endif %}
I'm trying to access the selected date and pass it to the corresponding view: action="{% url 'app:add_entry_with_date' name form.entry_date.value %}", but it still adds the entry at the default date (today) not on the selected date.
My guess, is that the problem is with <button type="submit" class="btn btn-secondary" name="{{ name }}" style="margin-bottom: 5px;">{{ name }}</button>. Does this just pass name but not the date when submitting?
Here are the relevant URL patterns:
class DateConverter:
regex = '\d{4}-\d{2}-\d{2}'
def to_python(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d')
def to_url(self, value):
return value
register_converter(DateConverter, 'yyyymmdd')
urlpatterns = [
path('', views.index, name='index'),
path('add/<entry_name>/', views.add_entry, name='add'),
path('add/<entry_name>/<yyyymmdd:entry_date>/', views.add_entry, name='add_entry_with_date'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
So whenever adding a new entry (with or without specific date), my add_entry view is called:
#login_required
def add_entry(request, entry_name, entry_date=datetime.date.today()):
# only works for post
# if request.method == 'POST':
entry_name = entry_name.strip().lower()
entry = Entry.objects.create(name=entry_name, date=entry_date, owner=request.user)
return HttpResponseRedirect(reverse('app:index'))

You're trying to pass the date value as part of the URL,
{% url 'app:add_entry_with_date' name form.entry_date.value %}
however, form.entry_date.value won't have a defined value unless your form is bound before it's passed to the template for rendering. As a result, probably, your add_entry view is being called via the URL pattern add, not add_entry_with_date.
Another challenge with your current code is that you want to have the same date-type input element ({{ form.entry_date }}) serve as the source for different, separate HTML forms (you have the first form for adding entries, and then you have one form for each suggested entry). Changing the value of that input when the page is already rendered in the browser won't update the action URLs for the suggested entry forms—unless you use JavaScript.
I think the quickest way to make your existing code work is to write some JavaScript to manipulate the action attribute for the suggested-entry forms whenever the date input value changes.
Manipulating action attributes looks strange though, and also I believe your view, which should work only for POST requests, should use only POST data and not rely on URL parameters. Therefore I recommend that you use hidden inputs, e.g.
<input type="hidden" name="variable-name" value="temporary-date-value-here">
and then have the JavaScript manipulate these input elements' values instead of the form action attribute. Of course you have to update the view too.
Update: sample JS for synchronizing inputs across forms
HTML:
<html>
<head>
<title>Sample synchronization of inputs across forms</title>
</head>
<body>
<h1>Sample synchronization of inputs across forms</h1>
<h2>Form 1</h2>
<form>
<input class="synchronized-inputs" type="date" name="input_date">
</form>
<h2>Form 2</h2>
<form>
<input class="synchronized-inputs" type="date" name="input_date">
</form>
<script src="sync-inputs-across-forms.js" type="text/javascript"></script>
</body>
</html>
JavaScript (sync-inputs-across-forms.js):
var syncedInputs = document.getElementsByClassName('synchronized-inputs');
Array.from(syncedInputs).forEach((source) => {
source.addEventListener('change', () => {
Array.from(syncedInputs).forEach((target) => {
target.value = source.value;
});
});
});
Note that:
Without the JS, selecting a date in one form won't update the other form's value
As indicated in the original answer, you'd want to use hidden inputs for the suggested-entry forms. To do that, just change type="date" to type="hidden" for the other form. Synchronization will still work as the value is tracked in the (invisible parts of the) DOM.

Related

Need to show result on the same page - Django

I'm creating a password generator app. The app is working and stores the value on db.
The problem is whenever I refresh, the form resubmits and takes the previous value and stores.
Also, I want to show the email and password on the same page.
Whenever I refresh, I want to show an empty form with empty fields.
Views.py
def home(request):
if request.method=='POST':
inputemail = request.POST.get('InputEmail')
gen = ''.join(random.choices((string.ascii_uppercase+string.ascii_lowercase+string.digits+string.punctuation), k=10))
newpass = Item(email=inputemail,encrypt=gen)
newpass.save()
return render(request,'home.html',{"gen":gen})
return render(request,'home.html',{})
Home.html
<form method = 'post' id='pass-form' >
{% csrf_token %}
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" name="InputEmail" >
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<button type="submit" name = "submit" class="btn btn-primary">Generate Password</button><br><br>
</form>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Generated Password</label>
<input type="text" id="InputPassword" name = "genpassword" value = {{gen}} >
</div>
Urls.py
urlpatterns = [
path('',views.home,name='home'),
]
According to docs:
you should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s good web development practice in general.
So you should make another page to show generated password, which will take submitted instance id of Item model created in home view so:
def home(request):
if request.method=='POST':
inputemail = request.POST.get('InputEmail')
gen = ''.join(random.choices((string.ascii_uppercase+string.ascii_lowercase+string.digits+string.punctuation), k=10))
newpass = Item(email=inputemail,encrypt=gen)
newpass.save()
return redirect('success', args=(newpass.pk))
return render(request,'home.html',{})
def success(request, pk):
item_obj = get_object_or_404(Item, pk=pk)
return render(request,'success.html', {'gen':item_obj.encrypt})
urls.py
urlpatterns=[
path('',views.home,name='home'),
path('success/<int:pk>/',views.success,name='success')
]
success.html
<body>
<h2>The form is successfully submitted.</h2>
<br>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Generated Password</label>
<input type="text" id="InputPassword" name="genpassword" value={{gen}} >
</div>
Again go to password generator page.
</body>
Another possible solution
You can make email field required in Html form and then hard refresh the page after submitting the form using Javascript's submit event so the template:
<form method='POST' id='pass-form'>
{% csrf_token %}
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" name="InputEmail" required>
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<button type="submit" class="btn btn-primary">Generate Password</button><br><br>
</form>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Generated Password</label>
<input type="text" id="InputPassword" name = "genpassword" value ={{gen}} >
</div>
<script type='text/javascript'>
let form = document.getElementById('pass-form');
addEventListener('submit', (event) => {
location.reload(true); // hard refreshed
console.log('hard refreshed')
});
</script>
Note: Then also there are certain browsers like Microsoft Edge which gives pop up as Resubmit the form? in which they mention The page you're looking for used information that you entered. Returning to the page might trigger a repitition of any action you took there. Do you want to continue?
The moment you click on continue it creates duplicacy of records, so I think as docs mentions the first approach is better.

Delete an object in Django

I am trying to delete an object. This is the HTML, todo should be deleted when you Click on button (I am trying to call delete_todo) :-
<ul>
{% for all %}
</ul>
This is the views.py,
You need to change few things in your code.
First of all change urlpattern delete_todo you need to add argument, which allows to determine in view what object you want to delete:
url(r'^(?P<todo_id>[0-9]+)/$', views.delete_todo, name='delete_todo'),
Then you need change delete_todo itself:
def delete_todo(request, todo_id):
instance = get_object_or_404(Todo, pk=todo_id)
instance.delete()
return redirect('index')
Here you can use get_object_or_404 fuction to get object with id.
And finally you need to pass url's argument to view from template:
<form action="{% url 'lists:delete_todo' todo_id=todo.id %}" method=post>
<input id="submit" type="button" value="Click" />
</form>
Just to add clarification on the use of the form and csrf: it's necessary in order to ensure that different users of your app can't delete content that isn't theirs.
In your template, you'll need to include the csrf tag as such:
<form method="post" action={% url 'delete_todo' todo_id=todo.id %}>
{% csrf_token %}
<input type="button" id="submit" value="Delete" />
</form>

In Django, how do I access template values in the views.py?

I'm fairly new to Django and I'm working on a page that takes in user information. If all of the information is correct, it will proceed to the next page. However, if the user does not provide all given info, it will to refresh the page. My problem is that there are quite a bit of fields the user has to fill out and if the person misses any fields, I don't want them to have to re-type everything out. So my workaround for it is that in the views.py I created a dictionary and it populates it with the input names in the template. However, when I go to run the code, it gives me an error saying that the values in my dictionary do not exist. I'm now thinking that my dictionary is not actually accessing any of the template values.
Here is my template:
<!DOCTYPE html>
{% extends "Checklist/base.html" %}
{% block main_content %}
{% load static %}
<html>
<body>
<form action="{% url 'Checklist:signin_check' %}" method="post">
{% csrf_token %}
<ul style="list-style-type:none">
<li>
<label for="driver_first_name">Driver First Name:</label>
<input type="text" name="driver_first_name" value="" id="driver_first_name">
</li>
<li>
<label for="driver_last_name">Driver Last Name:</label>
<input type="text" name="driver_last_name" value="" id="driver_last_name">
</li>
<li>
<label for="driver_wwid">Driver WWID:</label>
<input type="text" name="driver_WWID" value="" id="driver_WWID" maxlength="8"
onkeypress="return (event.charCode == 8 || event.charCode == 0) ? null : event.charCode >= 48 && event.charCode <= 57">
</li>
<li>
<label for="co_driver_first_name">CO-Driver First Name:</label>
<input type="text" name="co_driver_first_name" value="" id="co_driver_first_name">
</li>
<li>
<label for="co_driver_last_name">CO-Driver Last Name:</label>
<input type="text" name="co_driver_last_name" value="" id="co_driver_last_name">
</li>
<li>
<label for="co_driver_wwid">CO-Driver WWID:</label>
<input type="text" name="co_driver_WWID" value="" id="co_driver_WWID" maxlength="8"
onkeypress="return (event.charCode == 8 || event.charCode == 0) ? null : event.charCode >= 48 && event.charCode <= 57">
</li>
<li>
<input type="submit" value="Continue">
</li>
</ul>
</form>
</body>
</html>
{% endblock %}
Here is the views.py:
def signin_check(request):
driver_info_model = Driver()
if request.method == "POST":
driver_info_form = Driver_Form(request.POST)
c = {'driver_first_name':driver_first_name, 'driver_last_name':driver_last_name,
'driver_WWID':driver_WWID, 'co_driver_first_name':co_driver_first_name,
'co_driver_last_name':co_driver_last_name, 'co_driver_WWID': co_driver_WWID,}
if driver_info_form.is_valid():
driver_info_form.save()
return render(request, 'Checklist/checklist.html')
template = loader.get_template('Checklist/signin.html')
return HttpResponse(template.render(c, request))
any feedback would be greatly appreciated. Thanks!
However, when I go to run the code, it gives me an error saying that
the values in my dictionary do not exist. I'm now thinking that my
dictionary is not actually accessing any of the template values.
From your views.py alone I'm guessing the exception you're running into is that you're assigning dictionary values that aren't defined. For example, in 'driver_first_name':driver_first_name, Python is looking for a variable named driver_first_name but you haven't defined it. The data you're looking for, as Justin alluded to, can be found in requests.POST.
One solution, while more verbose, illustrates what needs to be done:
def signin_check(request):
driver_info_model = Driver()
if request.method == "POST":
driver_info_form = Driver_Form(request.POST)
driver_first_name = request.POST.get('driver_first_name', '')
driver_last_name = request.POST.get('driver_last_name', '')
driver_WWID = request.POST.get('driver_WWID', '')
co_driver_first_name = request.POST.get('co_driver_first_name', '')
co_driver_last_name = request.POST.get('co_driver_last_name', '')
co_driver_WWID = request.POST.get('co_driver_WWID', '')
c = {'driver_first_name': driver_first_name,
'driver_last_name': driver_last_name,
'driver_WWID': driver_WWID,
'co_driver_first_name': co_driver_first_name,
'co_driver_last_name': co_driver_last_name,
'co_driver_WWID': co_driver_WWID, }
if driver_info_form.is_valid():
driver_info_form.save()
return render(request, 'Checklist/checklist.html')
template = loader.get_template('Checklist/signin.html')
return HttpResponse(template.render(c, request))
My problem is that there are quite a bit of fields the user has to
fill out and if the person misses any fields, I don't want them to
have to re-type everything out.
To address your second concern you'll need to deal with your HTML template. Your input fields have a value of "", so any value you pass through your context is not going to reach any of them. Luckily you're on the right path and you're quite close, so all you need to do is fill those values in. For example:
<li>
<label for="driver_first_name">Driver First Name:</label>
<input type="text" name="driver_first_name" value="{{ driver_first_name }}" id="driver_first_name">
</li>
Note that {{ driver_first_name }} is referencing the driver_first_name that's being passed into the context.
Im not 100% sure as i'm fairly new to Django myself, but from what i've done previously you can get the POST data from the request that is passed in, like this:
request.POST['driver_first_name']
which raises an error if no data is present or from
request.POST.get('driver_first_name', 'optionaldefaultvalue')
which returns None if no data is present in the specified field, or an optional default.
It might also be easier to do what you are after with django's inbuilt forms

Django templates not altering form action

I have a URL file chat.urls.py:
`urlpatterns = patterns('',
url(r'^message/(?P<username>\w+)/$',views.message,name='message'),
url(r'^message/(?P<username>\w+)/submit/$',views.send_message,name='send_message'),
url(r'^inbox/$',views.inbox,name='inbox'),
url(r'^inbox/(?P<username>\w+)/$', views.inbox_by_user,name='inbox_by_user'),
)`
and a message.html template to send a message from with a form like this:
<form action="{% url 'inbox' %}" method="post">
{% csrf_token %}
<input type="text" name="text" id="text" value="" />
<label for="message">Enter your message here</label><br />
<input type="submit" value="Send" />
</form>
where I substituted previously working code for "url 'inbox'", and no matter what I substitute for the form action I always get html source rendered as
<form action="/chat/message/[username]/" method="post"...
no matter what. I have restarted the server, made sure I saved changes, and like it has a mind of its own, it's always /chat/message/[username]. When I changed that URL reverse to 'inbox' I should see chat/inbox based on the URLs.
According to the information in comment, you need {% url 'chat:inbox' %} not {% url 'inbox' %} in the form.

Django: Template {% url %} tag, how to pass parameter from form

There is template:
<form action="{% url 'nfoapp.views.kinoscrap' <I WANT MOVIE_ID THERE> selectshort.id %}" method="post">
<input type="text" class="form-control" name="MOVIE_ID">
<button type="submit" class="btn btn-danger">Kinopoisk Search by ID</button>
</form>
There is my urls.py:
(r'^kinoscrap/(?P<kinoid>\d+)/(?P<shortid>\d+)/$', kinoscrap),
I want pass to kinoscrap two parameters - text field from form (MOVIE_ID) and 'selectshort.id' variable. The problem is that i can't put simple MOVIE_ID in first line of template, I got error. But when I try put instead MOVIE_ID other variable, for example selectshort.id, program work without error.
How I can trasmit text field value to view?
p.s I use bootstrap, if it has some importance.
You could have the form action empty, so to the same view, and then in the view redirect using the POST data from the form.
<form action="" method="post">
<input type="text" class="form-control" name="MOVIE_ID">
<button type="submit" class="btn btn-danger">Kinopoisk Search by ID</button>
</form>
And then in the view
def searchView(request):
if request.method == 'POST':
# get variables from form and redirect
else:
# do your normal rendering
(r'^kinoscrap/(?P<kinoid>\d+)/(?P<shortid>\d+)/$', kinoscrap),
Your urls.py accepts to integer values in the url (something like kinnoscrap/12/21), if you pass anything beside integers it'll throw an error. If you want to pass a text field you'll have to change the regular expression.
Try out your regexes at regex101 here to see if they'll work.

Categories