Django: url in "form action" doesn't work - django

In detail.html:
<form id="answer_form" class="form-horizontal" action="{% url 'puzzle:update' puzzle.id %}" method="POST" onSubmit="return ValidateAnswer();">
{% csrf_token %}
<p>Please entry your answer below: (Case insensitive)</p>
<div class="form-group col-xs-12">
<input id="player_answer" maxlength="30" name="player_answer" type="text">
</div>
<div class="form-group col-xs-12">
<button id="submit_answer_btn" class="btn btn-success" type="submit">Submit</button>
</div>
</form>
<script>
function ValidateAnswer() {
var player_answer = document.getElementById("player_answer");
if(player_answer.value == {{ puzzle.answer }}) {
alert("Congratulations!");
} else {
alert("Wrong answer!");
return false;
}
}
</script>
In url.py
app_name = 'puzzle'
urlpatterns = [
url(r'^(?P<pk>[0-9]+)/$', login_required(views.PuzzleDetailView.as_view()), name='detail'),
url(r'^(?P<puzzle_id>[0-9]+)/update/$', views.update_user_game_history, name='update'),
]
In views.py
class PuzzleDetailView(DetailView):
model = Puzzle
template_name = 'puzzle/detail.html'
def update_user_game_history(request, puzzle_id):
player_game_history = PlayerGameHistory.objects.get(user=request.user)
solved_puzzle = Puzzle.objects.get(id=puzzle_id)
player_game_history.score += solved_puzzle.point
player_game_history.save()
return HttpResponseRedirect('/')
What I am trying to do is to click on Submit button, via 2nd url, go to the update_user_game_history function in views.py.
However, everytime I submit, the flow tries to, via 1st url, go to the PuzzleDetailView. And I get Method Not Allowed (POST): /2/ in terminal

I am not sure if that solved your problem but basically in the view you don't have a way to ensure if the method is 'POST' or not. To solve that you can add this line in your code:
def update_user_game_history(request, puzzle_id):
if request.method == 'POST':
player_game_history = PlayerGameHistory.objects.get(user=request.user)
solved_puzzle = Puzzle.objects.get(id=puzzle_id)
player_game_history.score += solved_puzzle.point
player_game_history.save()
return HttpResponseRedirect('/')
But according with your example is not necessary call a form because you aren't using it. My recommendation is to improve this view because only with a url you can score many times you want

Many thanks to Alasdair. After checking the rendered form tag, I found out the problem. Value of action attribute in form tag was wrong. It was still "#". It seems like Chrome back button doesn't retrieve the lastest page from server. It only reload what it received before.

Related

Django Return redirect working for one view but not for the other

I cannot understand why it is working in one instance but not the other. I am working with django and output in django template. The only difference between the views/functions are in the second one (the one that is not working) I update the field with time. Time updates, saves in the model and displays updated time correctly. It is just the redirect that is not working.
The working redirect code-
Template, this code takes me to the edit page. Name of the url is "update" -
<td><button>Edit</button></td>
The form on the dit page-
{% block content %}
<div class="wrapper">
<h1 class="ok">Entry Form</h1>
<form action="" method="POST">
{% csrf_token %}
{{form}}
<input type="submit" value="submit">
</form>
</div>
<br>
{% endblock content %}
url-
path('update_entry/<str:pk>/', views.update, name = "update"),
And views.py-
def update(request, pk):
order=Bank1.objects.get(id=pk)
form = Bank1Form(instance=order)
if request.method == 'POST':
form = Bank1Form(request.POST, instance=order)
if form.is_valid():
form.save()
return redirect('/bank1')
context = {'form':form}
return render(request, 'myapp/entry.html', context)
Now here is the non working code. Template, the line that takes me to the update page. Name of the url is "update_enter_workout.-
<td><button>Start Time</button></td>
Form on the Edit page. Didn't add the entire form since I only need to update the time from this page. Just the submit button.-
{% block content %}
<Button>Close this page and go to Home</Button>
<div class="wrapper">
<h1 class="ok">Start/End the set now?</h1>
<form action="" method="post">
{% csrf_token %}
<input type="submit" value="YES!">
</form>
</div>
{% endblock content %}
url-
path('update_enter_workout/<str:pk>/', views.update_workout, name='update_enter_workout'),
Views.py-
def update_workout(request, pk):
order=WorkOut.objects.get(id=pk)
form=WorkOutForm(instance=order)
if request.method=='POST':
form=WorkOutForm(request.POST, instance=order)
time=datetime.now().strftime('%H:%M:%S')
WorkOut.objects.filter(id=pk).update(start=time)
if form.is_valid():
form.save()
return redirect('/bank1')
context={'form':form}
return render(request, 'myapp/enter_workout.html', context)
As you can see they are written the same way, but in the second instance redirect is not working. Any idea what can be changed. It is so simple, couldn't even find a typo or simple mistake like that.
Any advise?
Are you accepting str for id fields? Primary Keys are integers, not strings. You need to cast to an int path converter:
Instead of:
path('update_enter_workout/<str:pk>/', views.update_workout, name='update_enter_workout'),
Use:
path('update_enter_workout/<int:pk>/', views.update_workout, name='update_enter_workout'),

How Do You Trigger HTMX Page Refresh After User Updates Any Part of The Page?

I have been working with HTMX and it's pretty cool compared to the dreaded formsets and Javascript. I have it working....My only issue is when the user updates the form anywhere...you have to manually refresh the page to reset the list of todos. My issue is identical to this one...https://stackoverflow.com/questions/66664407/dynamically-update-table-when-creating-new-enty-using-htmx but there is no resolution listed.....
Here's a quick overview of my code...
My view...
def create_to_do(request):
user = User.objects.get(id=request.user.id)
to_dos = NewToDo.objects.filter(created_by=user)
form = ToDoForm(request.POST or None)
if request.method == "POST":
if form.is_valid():
to_do = form.save(commit=False)
to_do.created_by = user
to_do.creation_date = timezone.now()
to_do.save()
return redirect("MyToDoList:detail-to-do", pk=to_do.id)
else:
return render(request, "partials/to_do_form.html", {
"form":form
})
context = {
"form": form,
"user": user,
"to_dos": to_dos,
}
return render(request, "create_to_do.html", context)
Partial detailview....
<button class="button35" hx-get="{% url 'MyToDoList:update-to-do' to_do.id %}" hx-swap="outerHTML">
Update
</button>
<button class="button34" hx-post="{% url 'MyToDoList:delete-to-do' to_do.id %}" hx-swap="outerHTML">
Delete
</button>
</div>
Partial todo form....
<div hx-target="this" hx-swap="outerHTML" class="">
<form method="POST">
{% csrf_token %}
{% if to_do %}
<button class="button35" hx-post="{% url 'MyToDoList:update-to-do' to_do.id %}">
Save
</button>
<button class="button34" hx-get="{% url 'MyToDoList:detail-to-do' to_do.id %}">
Cancel
</button>
</div>
{% else %}
<button class="button35" hx-post=".">
Save
</button>
</div>
{% endif %}
</form>
</div>
My main create form html..
<button class="button36" hx-get="{% url 'MyToDoList:create-to-do-form' %}" hx-swap="beforeend" hx-target="#bookforms">Add Form</button>
<div id="bookforms" class=""></div>
<div class="">
{% if to_dos %}
{% for to_do in to_dos %}
{% include "partials/to_do_detail.html" %}
{% endfor %}
{% endif %}
After a day of playing and pulling my hair out..it's all working as I want...I just need to figure out how to incorporate a dynamic page load if anything changes so that the entire page gets reloaded....so that the records get sorted according to my number field...
Thanks in advance for any thoughts or suggestions.
So thanks to a kind soul on Facebook....I added this Javascript to my code and it works.
$(document).on("click", ".yourclass", function(){
location.reload();
});
In case you need this, I had the problem when I have to update two/mutliple parts of the page after post.
I manage to solve it by trigger_client_event
Thanks to this issue: How do I manually trigger a hx-get event of an element from JavaScript
HTMX rocks!

How to call a django function on click that doesn't involve changing page [duplicate]

My application currently flows through 3 pages:
User selects question in index page
User submits answer in answer page
User is presented with result in results page.
I want to compress that down to a single page where the user submits an answer to the question and result is shown on the same page.
The following django-template code separates questions with Bootstrap accordion. How do I post the form without refreshing the whole page? I want to be able to display the result on the page, update CSS styling with Javascript etc.
<h2>{{ category.title }}</h2>
<div class="accordion" id="accordion{{category.title}}">
{% for challenge in category.challenge_set.all %}
<div class="card">
<div class="card-header" id="heading{{challenge.id}}">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapse{{challenge.id}}" aria-expanded="true" aria-controls="collapse{{challenge.id}}">
{{ challenge.question_text }} - {{ challenge.point_value }} points
</button>
</h2>
</div>
<div id="collapse{{challenge.id}}" class="collapse in" aria-labelledby="heading{{challenge.id}}" data-parent="#accordion{{category.title}}">
<div class="card-body">
<p>{{ challenge.description }}</p>
<form action="{% url 'challenges:answer' challenge.id %}" method="post">
{% if challenge|is_answered:request %}
<label for="answered">Answer</label>
<input type="text" name="answered" id="answered" value="{{ challenge.answer_text }}" readonly>
{% else %}
{% csrf_token %}
<label for="answer">Answer</label>
<input type="text" name="answer" id="answer">
<input type="submit" value="Submit">
{% endif %}
</form>
</div>
</div>
{% endfor %}
</div>
Here is the view:
def index(request):
context = {'challenges_by_category_list': Category.objects.all()}
return render(request, 'challenges/index.html', context)
def detail(request, challenge_id):
challenge = get_object_or_404(Challenge, pk=challenge_id)
return render(request, 'challenges/detail.html', {'challenge': challenge})
def results(request, challenge_id, result):
challenge = get_object_or_404(Challenge, pk=challenge_id)
return render(request, 'challenges/results.html', {'challenge':challenge, 'result':result})
def answer(request, challenge_id):
challenge = get_object_or_404(Challenge, pk=challenge_id)
result = "Incorrect, try again!"
if challenge.answer_text.lower() == request.POST['answer'].lower():
current_user = request.user
session = User_Challenge(user=current_user, challenge=challenge, answered=True)
session.save()
points = Profile(user=current_user, points=challenge.point_value)
points.save()
result = "Correct!"
return HttpResponseRedirect(reverse('challenges:results', args=(challenge.id, result)))
You can try this:
Add the below script in your template:
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
write a script and a function inside it to submit the form data.
<script type="text/javascript">
function submitData( challenge_id ){
// Get answer from the input element
var answer = document.getElementById("answer").value;
// add the url over here where you want to submit form & challenge_id is also taken as a parameter.
var url = "<your_url>";
$.ajax({
url: url,
data: {
'answer': answer,
},
dataType: 'JSON',
success: function(data){
// show an alert message when form is submitted and it gets a response from the view where result is provided and if url is provided then redirect the user to that url.
alert(data.result);
if (data.url){
window.open(data.url, '_self');
}
}
});
}
</script>
Change type of the submit button and add an onclick event to call the submitData() function and pass the challenge id to it. And remove the action attr from the form.
see below:
<form method="post">
{% csrf_token %}
{% if challenge|is_answered:request %}
<label for="answered">Answer</label>
<input type="text" name="answered" id="answered" value="{{ challenge.answer_text }}" readonly>
{% else %}
<label for="answer">Answer</label>
<input type="text" name="answer" id="answer">
// over here
<button type="button" onclick="submitData({{ challenge.id }})">
Submit
</button>
{% endif %}
</form>
Return a JsonReponse to the ajax call from the views.
views.py
def answer(request, challenge_id):
answer = request.GET.get('answer', False)
url = False
if challenge.objects.filter(id=challenge_id).exists() and answer:
challenge = Challenge.objects.get(id=challenge_id)
if challenge.answer_text.lower() == answer.lower():
current_user = request.user
session = User_Challenge(user=current_user, challenge=challenge, answered=True)
session.save()
points = Profile(user=current_user, points=challenge.point_value)
points.save()
result = "Correct!"
# specify the url where you want to redirect the user after correct answer
url = ""
else:
result = "Incorrect, try again!"
data = {
'result': result,
'url': url
}
return JsonResponse(data)

add multiple forms to page on button click

I am working on developing a permitting app using django. This is my first django project so bear with me here...
we have a default utility permit that contains some basic info like property owner and address. Then from that you can attach a sewer, or water or row or any combination of related tables to the permit. Basically I am looking for a way to return a page with the default utility permit then have a series of links or buttons to add more forms to that page.
I made some model forms for each of the models and can display them individually on the page
forms.py
class UtilityPermitForm(forms.ModelForm):
class Meta:
model = UtilityPermit
fields = ['...']
class SewerPermitForm(forms.ModelForm):
class Meta:
model = SewerPermit
fields = ['...']
class WaterPermitForm(forms.ModelForm):
class Meta:
model = WaterPermit
fields = ['...']
I successfully added them to a list and could iterate through and get them to add
views.py
class BuildForms(View):
permits = []
utility_form = UtilityPermitForm
sewer_form = SewerPermitForm
water_form = WaterPermitForm
permits.append(utility_form)
permits.append(sewer_form)
permits.append(water_form)
template_name = 'engineering/UtilityPermitForm2.html'
def get(self, request, *args, **kwargs):
out_permits = []
for form in self.permits:
out_permits.append(form())
return render(request, self.template_name, {'form': out_permits})
def post(self, request, *args, **kwargs):
if request.GET.get('testButton'):
return HttpResponse("I guess")
form = self.utility_form(request.POST)
return render(request, self.template_name, {'form': form})
def add_permit(self, request, permit):
# need to get a thing to add a permit to the list
pass
.html
{% block content %}
<div>
<form class="site_form" action={% url 'engineering:utility_permit' %} method="post">
{% csrf_token %}
{% for item in form %}
{{ item }}
<hr>
{% endfor %}
<input type="submit" value="Submit">
</form>
</div>
{% endblock content %}
so again, my problem is I want to start with a one permit and then have links or buttons to add each form as needed. I'm a bit at a loss here and any help would be greatly appreciated.
EDIT:
so I have this base permit that comes up when a user navigates to it like so, and I want to have a user click the add sewer permit button or link or whatever
and then the corresponding permit will come up
you can create multiple same form in one page dynamically using formset
see Documentation
and maybe this tutorial is exactly what you are looking for.
EDITED
if I understand your question correctly, how about this:
first, it would be better to separate your form with dictionaries instead of list in your views.py
context = {
'utility_form': self.utility_form,
'sewer_form': self.sewer_form,
'water_form': self.water_form
}
return render(request, self.template_name, context)
then in your .html file,
if you want to add one form each time you click the button, my trick is:
show your base permit form first (said utility_form), button to add other form, and hide your other form first.
<div class="form-container">
<form class="site_form" action={% url 'engineering:utility_permit' %} method="post">
{% csrf_token %}
{{ utility_form }}
<div id="additional-forms"></div> <!-- notice this div -->
<hr>
<input type="submit" value="Submit">
</form>
</div>
<button class="add-sewer-form">Sewer Permit</button>
<div id="sewer-form-template" style="display: none;">
<div class="sewer-form-container">
{{ sewer_form }}
</div>
</div>
and then using jquery to add onclick listener, clone that hidden form, then insert it after base form (actually inside div with id additional-forms).
$('.add-sewer-form').click(function(){
let sewer_form = $('#sewer-form-template .sewer-form-container:first').clone(true);
$(sewer_form).appendTo($('#additional-forms'))
});
I haven't test it yet, but when you click the add button, it should be give result like this:
<div class="form-container">
<form class="site_form" action={% url 'engineering:utility_permit' %} method="post">
{% csrf_token %}
{{ utility_form }}
<div id="additional-forms">
<div class="sewer-form-container">
{{ sewer_form }}
</div>
</div>
<hr>
<input type="submit" value="Submit">
</form>
</div>
<button class="add-sewer-form">Sewer Permit</button>
<div id="sewer-form-template" style="display: none;">
<div class="sewer-form-container">
{{ sewer_form }}
</div>
</div>
Hope it can answer your question :)
First add the button
<button><button>
Then add onclick attribute to it which will help react on click
<button onclick='do'><button>
Then create script that contain the function to display the other form
<script>
function do() {
document.getElementById('form').innerHTML ='add your form here'
}
</script>
all together
<button onclick='do'><button>
<script>
function do() {
document.getElementById('form').innerHTML ='add your form here'
}
</script>

Django: update model field using button

I would like to use a button to update a field (claimant) in one of my models (PieceInstance) and then redirect the user to a page where he sees all of the claimed instances.
The code is the following:
button:
(looping through all instances)
<a target="_blank"
method="POST"
class="button"
href="{% url 'claim' pk=instance.pk %}">
Claim
</a>
views.py
def claim(request, pk):
piece_instance = PieceInstance.objects.get(pk=pk)
piece_instance.claimant = request.user
piece_instance.save()
return HttpResponseRedirect(reverse('my-claimed'))
urls.py
urlpatterns += [
path('myclaimedpieces/<uuid:pk>', views.claim, name='claim'),
]
It runs smoothly but does not update the field in the model and hence the content on the redirected page is still empty.
Help is much appreciated!
It looks almost good to me. The only thing is the use of method in a <a> tag makes no sense. You should either use a pure link:
<a target="_blank"
class="button"
href="{% url 'claim' pk=instance.pk %}">
Claim
</a>
Or use a form with a real button (not a link formatted as a button):
<form method="POST" action="{% url 'claim' pk=instance.pk %}">
<button type="submit"
class="button">
Claim
</button>
</form>