Little general explanation.
I'm pretty newbie in Django, I have a little knowledge, but nothing as experience. The code that I want ask about is working, but I have a question about good/bad practice. Does my approach is good or at least not bad?
Little use case explanation.
I have a site with the items. There is a functionality to add the items and now I want to add a possibility to delete the item.
Use case : on the page of item user clicks on the button Delete, we will show a page with details about this item and the button "Confirm delete" at the bottom. If user click on the button I delete this item from database.
So
I create in urls.py
path('item/delete/<int:id>/', views.delete_item, {}, 'delete_item'),
I create in views.py
def delete_item(request,id):
if id :
cur_item = get_object_or_404(Item, pk=id)
else :
raise Http404
if request.POST:
try :
post_item_id=int(request.POST["pk"])
except ValueError :
messages.error(request,"Wrong id number")
return render(request, 'myapp/item_delete_form.html', {'cur_item': cur_item})
if (request.POST["delete"] == "yes") and (post_item_id == id):
Item.objects.filter(pk=id).delete()
# Delete was successful, so redirect to another page
redirect_url = reverse('items')
return redirect(redirect_url)
else:
messages.error(request, "id number in form not the same as in URL")
return render(request, 'myapp/item_delete_form.html', {'cur_item' : cur_item})
return render(request, 'myapp/item_delete_form.html', {'cur_item' : cur_item})
I do not use Django Forms as for me it's not the real Django form (i.e. it's not linked to a Model). As mention by #akx in the comments I could always create a Form based directly on forms.Form. But it seems also useless for me as in fact there is almost no data in my form.
Instead I just create generic form in the template.
In templates/myapp/item_delete_form.html
First part of template get cur_item and show it. And then :
<form method="post">
{% csrf_token %}
<input type="hidden" value="{{ cur_item.id }}" name="pk">
<input class="btn btn-default btn-danger" name="delete" type="submit" value="yes"/>
</form>
Finally here the small test to check it :
def test_delete_item(self):
test_serial = "111-111"
new_item = Item(serial_number=test_serial)
new_item.save()
url = reverse("delete_item", kwargs={'id':new_item.id})
resp = self.client.get(url)
confirm_data = {'pk': new_item.id,'delete': 'yes'}
resp = self.client.post(url, confirm_data)
self.assertEqual(Item.objects.all().count(), 0)
Whilst I'll be very appreciate to have and comments about my realisation, here are some practical questions.
Does the practice to analyze request.POST list is not the "bad practice"?
As I do not use Django forms I could not do form.is_valid. Is it ok?
Are there some pitfalls that I should think about (for example does user have the rights to delete this item, but for the moment user system is not developed so I could do nothing about that)?
Does my test is relevant?
And finally, if my realization is not a good or acceptable practice, how should I do my use case ?
You can save yourself quite some trouble by just using Django's default DeleteView CBV:
urls.py
path('item/delete/<int:pk>/', DeleteItemView.as_view(), name='delete_item'),
views.py
class DeleteItemView(DeleteView):
model = Item
template_name = "myapp/item_delete_form.html"
success_url = reverse_lazy('items')
item_delete_form.html
<form method="post">
{% csrf_token %}
<input class="btn btn-default btn-danger" name="delete" type="submit" value="yes" />
</form>
In fact, you don't even necessarily need a views.py. Just setting the arguments for the CBV in urls.py would do...
path(
"item/delete/<int:pk>/",
DeleteView.as_view(
model=Item,
template_name="myapp/item_delete_form.html",
success_url=reverse_lazy("items"),
),
name="delete_item",
)
would be equivalent.
Related
What I really want to do is , if a user click on "ADD more" button then a same form repeat itself and the values should store in database, if he/she doesn't click of that button then only the values from first form should be stored.
I am not able to get this, I just created a form , and a table in database for those details but can't loop though the form neither in data.
please help.
This is the form and the button:
This is the model.py code:
from django.db import models
class experience(models.Model):
company_name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
startdate = models.Datefield(default = 01-01-2020)
lastdate = models.DateField(default = 01-01-2020)
profile = models.CharField(max_length=100)
description = models.TextField(max_length = 250)
This is the views.py code:
from django.shortcuts import render, redirect
import requests
from django.contrib.auth.models import User, auth
# Create your views here.
def profile(request):
return render(request, 'profile.html')
Unfortunately, there's no built-in way (as far as I know) in Django to do that without Javascript, but here's an approach:
HTML:
<div class="container" id="experiencesContainer">
<form method='POST' name='experienceForm'>
{{form.as_p}}
</form>
<form method='POST' name='experienceForm'>
{{form.as_p}}
</form>
<button type="button" id="addMoreButton">Add more</button>
<button type="submit">Save Changes</button>
</div>
Django POST method:
# Get a list of submitted forms
experiences = request.POST.getlist('experienceForm')
for experience in experiences:
# this is how you loop throuh every form
experience.get('company_name)
Your javascript something like:
// clonning his childs as well
let cloneForm = document.querySelector('form[name=experienceForm]').cloneNode(true);
document.querySelector('div#experiencesContainer').appendChild(cloneForm);
// see this https://www.w3schools.com/jsref/met_node_clonenode.asp
Of course this code is not tested but I've done this in several projects before, hope it works!
A simple way would be to request the same view from the "Add", just make sure your form view saves the data when request method is POST.
<form action="{% url 'your-form-url' %}" method="GET">
{% csrf_token %}
<input type="submit" value="Add">
</form>
one other way to repeat forms would be using formsets. Formsets allow you to repeat the same form 'extra' times. Check out the documentation for more about this.
def repeat_form(request):
ExpFormSet = formset_factory(ExperienceForm, extra=3)
#extra defines the no. of forms you want to display
if request.method == 'POST':
formset = ExpFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
#loop through each form in the formser
for form in formset.cleaned_data:
obj = form.save()
else:
formset = ExpFormSet()
return render(request, 'exp_form.html', {'formset': formset})
The corresponding template should be:-
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.as_p }}
{% endfor %}
</form>
Make sure you add form.management_form. Using the combination of the above might solve your problem of taking and saving several inputs.
i want to delete a task from the database so i use this code
this is my delete view
def task_Delete(request,id=None):
if request.method == 'POST':
form = TaskForm()
id = int(request.POST.get('task.id'))
task = Task.objects.get(id=id)
task.delete()
messages.success(request,"successfully delete")
return render_to_response('home.html', {'form': form})
and that is my urls.py
url(r'^task_Delete/$', views.task_Delete, name='task_Delete')
this the code of the button delete :
<form action="{% url 'task_Delete' %}" method="post" >
{% csrf_token %}
<input type="hidden" name="task_id" value="{{task.id}}" />
<input type="submit" value="delete task">
</form></td>
</tr>
when i click on delete nothing happend i don't know why , please help thanks in advance
There are various problems in your code (for example the TaskForm is not needed at all) however if you change the line
id = int(request.POST.get('task.id'))
to
id = int(request.POST.get('task_id'))
the object will probably be deleted; remember that the request parameter's name will be the same as the name of the input (task_id). I recommend using proper CBVs (a DeleteView) for what you want to do - if you want a slow and comprehensive tutorial on that I recommend this article: https://spapas.github.io/2018/03/19/comprehensive-django-cbv-guide/
I have a django template in which I'm dynamically rendering multiple fields (using ajax)
Below is a Django form (which has been rendered in a template) whose fields have same names. I want to use the cleaned_data method to clean form data in views.py before storing them in the database.
index.html
<div class="form-container">
<!-- ASSUMING I HAVE ALREADY ADDED FIELDS DYNAMICALLY -->
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
<input type='text' name='this_field'>
<input type='text' name='this_field'>
<button type="submit">Submit</button>
</form>
</div>
<form id="addItemForm">
{% csrf_token %}
<!-- BUTTON TO ADD MORE FIELDS DYNAMICALLY -->
<button id = "addItemButton">Add item</button>
</form>
<script>
var addItemButton = document.querySelector('#addItemButton');
addItemButton.onclick = function(){
$.ajax({
type: 'POST',
url: 'addItem/',
data: addItemForm.serialize(),
success: function (response) {
$("#orderForm").append(response);
console.log('Success');
},
error: function (response) {
console.log('Error = '+response);
}
});
};
</script>
forms.py
class ItemForm(forms.Form):
this_field = forms.CharField()
urls.py
urlpatterns = [
url(r'^newPickupOrder/$', views.pickup_order_view, name='new_pickup_order'),
]
views.py
def add_item(request):
if request.method == 'POST':
itemForm = ItemForm()
return HttpResponse(itemForm.as_p())
def pickup_order_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
same_name_fields = request.POST.getlist('this_field')
# WANT TO CLEAN DATA IN same_name_fields
if form.is_valid():
print(form.cleaned_data)
# ONLY PRINTS THE LAST FIELD's DATA
return HttpResponseRedirect('/viewPickupRequests')
The problem I'm facing is that if I use form.cleaned_data['this_field'], only the last field's data is fetched i.e. in this example, the field with value anotherTestValue is fetched and cleaned. If I fetch the data using request.POST.getlist('this_field'), all the fields' data is fetched and stored as a list, but, I don't know how to clean it using cleaned_data method. Is there a way to apply the cleaned_data method to the list of field data?
I'm sorry, I can't test if this works so this is not really an answer - but the comment system is not suitable for larger code chunks so I'm posting here.
Django forms lack a field type that renders to multiple text inputs with the same name. The proper thing to do would be to write a new form field class and a new widget. Since you are not rendering the form in the template (you are using it only for validation) I will omit the widget part.
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
Then use this field class instead of forms.CharField() (you may need to pass an empty choices parameter).
[update]
So essentially what you're saying is that I need to create new form field class and then render it to the template each time the user wants to add a new field? What if user has to add 15 fields, I'll need to create 15 classes then! I think this method won't be suitable in scenarios where number of fields required to be generated is large. I feel there should be some elegant way to do this which i'm not aware of – The OP
No, it is not what I'm saying. You probably want to subclass something like MultipleHiddenInput and set AcceptAnythingMultipleChoiceField.widget to it. You will have to create a new template based on the template for MultipleHiddenInput and replace input type="hidden" for type="text" (the original template is django/forms/widgets/multiple_hidden.html).
class AcceptAnythingWidget(MultipleHiddenInput):
template_name = 'django/forms/widgets/multiple_visible.html'
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
widget = AcceptAnythingWidget
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
This should render as many <input name='this_field'> as needed for instantiated forms at the frontend if you use:
{{ form.this_field }}
in the template, but will not add/remove them dynamically.
In order to do that you must plug in the JavaScript required to add/remove inputs dynamically in the widget but I will left this as an exercise for you. Look at Form Assets (the Media class) in the docs in order to figure out how to do that.
I think that what you are looking for is formsets. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
from django.forms import formset_factory
ItemFormSet = formset_factory(ItemForm, extra=2)
You can the essentialy use ItemFormSet in the way you would use a normal form except that this objects is iterable.
You will also have to change your jquery if you want to dynamically add items. There are many examples online on how to do this. In short what you do is
clone one of the forms in the formset
clear all the values from the copied form
update the input's (prefixes of) id's
Using Formsets doesn't solve the problem of fetching and validating
fields with same name. The issue still remains
It does however generate the end result you wanted (see below). My question would be why you need to have inputs with the same name? If there is some jquery stuff that uses these names I dont see any reason why you wouldn't be able to use name like... or assign a class to the inputs instead.
def pickup_order_view(request):
if request.method == 'GET':
ItemFormSet = formset_factory(ItemForm, extra=5)
item_formset = ItemFormSet()
template = "some_template.html"
template_context = {'item_formset': item_formset}
return render(request, template, template_context)
if request.method == 'POST':
ItemFormSet = formset_factory(ItemForm)
item_formset = ItemFormSet(request.POST)
same_name_fields=[]
if item_formset.is_valid():
for item_form in item_formset:
same_name_fields.append(item_form.cleaned_data['this_field'])
print(same_name_fields)
Template
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
{% csrf_token %}
{{ item_formset.management_form }}
{{ for item_form in item_formset }}
{{ item_form.as_p }}
{{ endfor }}
<input type='submit' value='submit'>
</form>
Go to newPickupOrder/ , fill in the 5 fields, hit submit, and watch it print your list.
So i'm not even sure how to search for someone who had the same thing happen to them.
I'm working on a django website and my form won't post to my database, instead, i get redirected to a URL containing the information that was in the forms, like this:
<form id="form">
<input type="hidden" id="compinp" name="compinp">
<input maxlength="20" onkeyup="showpost()" name="title" id="titleinput">
{{ captcha }}
</form>
Where compinp is some other data that gets posted, {{ captcha }} is a reCaptcha checkbox that works just fine, and when everything is filled in and getting posted, instead of running the post function from views.py, instead i get redirected to this:
http://localhost:8000/newentry/?compinp=XXXX&title=XXXX&g-recaptcha-response="xxxx-xxxx-xxxx"
It gets posted via jQuery through a button outside of the form, though i tried to add a submit button inside it and got the exact same thing.
The views.py function that handles that looks like this:
def newentry(request):
if request.method == "GET" and request.user.is_authenticated():
#creating objects for the view, works fine too
return render(request, "newentry.html",
{"champlist": complist, "captcha": captcha})
elif request.method == "POST" and request.user.is_authenticated():
captcha = Captcha(request.POST)
title = request.POST.get("title", False)
compname = request.POST.get("compinp", False)
comp = Comp.objects.get(title=compname)
if captcha.is_valid() and title and compname:
newe= entry_generator(request.user, title, comp)
newe.save()
return redirect('/')
else:
return redirect('/')
else:
handle_home(request.method, request.user)
This view tries to post models from another app in the same project, if that makes it any different.
I had added a print attempt at the right after the request check for post it didn't print anything.
Not sure what other info i can give to help, if you want any, just ask (:
You need to add the form method post:
<form id="form" method="post">
<input type="hidden" id="compinp" name="compinp">
<input maxlength="20" onkeyup="showpost()" name="title" id="titleinput">
{{ captcha }}
</form>
I have django 1.4 and I am following a tutorial which uses an older version of django. Its a simple tutorial which creates a wiki app with Page as model.
The problem is that the view function corresponding to a POST method in a form is not getting invoked.
This is the content in the urls.py:
url(r'^wikicamp/(?P<page_name>[^/]+)/edit/$', 'wiki.views.edit_page'),
url(r'^wikicamp/(?P<page_name>[^/]+)/save/$', 'wiki.views.save_page'),
url(r'^wikicamp/(?P<page_name>[^/]+)/$', 'wiki.views.view_page'),
This is the content of the template edit.html:
<from method = "get" action="/wikicamp/{{page_name}}/save/">
{% csrf_token %}
<textarea name = "content" rows="20" cols="60">
{{content}}
</textarea>
<br/>
<input type="submit" value="Save Page"/>
</form>
this is link to save
And this is the content in views.py:
def edit_page(request, page_name):
try:
page = Page.objects.get(pk=page_name)
content = page.content
except Page.DoesNotExist:
content = ""
return render_to_response("edit.html", {"page_name":page_name, "content":content}, context_instance=RequestContext(request))
def save_page(request, page_name):
return HttpResponse("You're looking at the page %s." % page_name)
I initially I was getting csrf related error and I then tried all the fixes provided in https://docs.djangoproject.com/en/dev/ref/contrib/casrf/ and followed many many stackoverflow question related to POST and django. Now nothing happens when I click the 'Save Page' button, nothing! Not even any request being sent from the form (Using firebug to track the HTTP request and response)
You have a typo in your HTML: from instead of form.
You may realize this, but that code won't really save anything. I'm not sure what blog you are following, but you would be better-off following the official Django tutorial in the documentation, then reading the forms docs.
You may need to change method to "POST" in your form.
<from method = "get" action="/wikicamp/{{page_name}}/save/">
to
<form method = "post" action="/wikicamp/{{page_name}}/save/">
There are some spelling mistakes, such as from instead of form.
Also the form is malformed.
Change:
this is link to save
to
<input type="submit" value="Save Page" />
And thirdly, change the method= "get"to method="POST".
The entire form should look like this
<form method = "POST" action="/wikicamp/{{page_name}}/save/">
{% csrf_token %}
<textarea name = "content" rows="20" cols="60">
{{content}}
</textarea>
<br/>
<input type="submit" value="Save Page"/>
</form>
Also what #DanielRoseman said. But hey, it might come further down the road.