My django form is not valid. I guess it occurs because request.POST is almost empty(only csrf_token is included). Still I can't find the reason. Seeking for a help.
P.S. (To make it clear, what's happening in code) I have three levels of cascading categories and a list of patterns. I want to mark all the patterns with one category. Options of select2 depend on the selected option in select1. The same thing is for select3.
The form:
class AddPatternForm(forms.Form):
category_1 = forms.ChoiceField()
category_2 = forms.ChoiceField(required=False)
category_3 = forms.ChoiceField(required=False)
patterns = forms.CharField()
def verify(self):
valid = []
invalid = []
patterns = self.patterns.split('\n')
for pattern in patterns:
if VALID_DOMAIN.match(pattern):
valid.append(get_domain(pattern))
elif VALID_REGEXP.match(pattern):
valid.append(pattern)
else:
invalid.append(pattern)
return valid, invalid
The view:
def add_pattern(request):
patterns = Url.objects.all()
parents = Category.objects.filter(parent=None)
if request.method == 'POST':
form = AddPatternForm(request.POST)
if form.is_valid():
valid, invalid = AddPatternForm.verify()
if form.category_3:
parent_id = form.category_3
elif form.category_2:
parent_id = form.category_2
else:
parent_id = form.category_1
parent = Category.objects.get(pk=parent_id)
for pattern in valid:
Url(pattern=pattern, categories=parent).save()
return HttpResponseRedirect('')
else:
form = AddPatternForm()
return render(request, 'app/add_pattern.html', {
'form': form,
'patterns': patterns,
'parents': parents,
})
The html:
<form id="chooseCategory" method="post" action="">
{% csrf_token %}
<div class="form-group">
<h2 id="chosenCategory">Категория: </h2>
<select id="select1" class="form-control">
<option value="">---</option>
{% for parent in parents %}
<option value="{{ parent.id }}">
{{ parent }}
</option>
{% endfor %}
</select>
<option value="">---</option>
<select id="select2" class="form-control">
</select>
<option value="">---</option>
<select id="select3" class="form-control">
</select>
</div>
<div class="form-group">
<textarea class="form-control" rows="5"></textarea>
</div>
<button type="submit" class="btn btn-default">Save</button>
</form>
The JS (realization of cascading dropdown select):
$( "#select1" ).change(function () {
var str = "Категория: ";
$( "#select1 option:selected" ).each(function() {
str += $( this ).text() + " ";
});
$( "#chosenCategory" ).text( str );
$( "#select2" ).find('option').remove().end()
$( "#select2" ).append("<option value=''>---</option>")
$.get("/load_new_options/", {"parent_id": $( "#select1 option:selected" ).val()},
function(data) {
var c = JSON.parse(data);
for (i in c) {
$( "#select2" ).append("<option value='" + c[i].pk + "'>" + c[i].fields.name + "</option>")
}
})
})
.change();
$( "#select2" ).change(function () {
var str = "Категория: ";
$( "#select2 option:selected" ).each(function() {
str += $( this ).text() + " ";
});
$( "#chosenCategory" ).text( str );
$( "#select3" ).find('option').remove().end()
$( "#select3" ).append("<option value=''>---</option>")
$.get("/load_new_options/", {"parent_id": $( "#select2 option:selected" ).val()},
function(data) {
var c = JSON.parse(data);
for (i in c) {
$( "#select3" ).append("<option value='" + c[i].pk + "'>" + c[i].fields.name + "</option>")
}
})
})
.change();
$( "#select3" ).change(function () {
var str = "Категория: ";
$( "#select3 option:selected" ).each(function() {
str += $( this ).text() + " ";
});
$( "#chosenCategory" ).text( str );
})
.change();
$('#chooseCategory').cascadingDropdown({
selectBoxes: [
{
selector: '#select1',
},
{
selector: '#select2',
requires: ['#select1'],
},
{
selector: '#select3',
requires: ['#select1', '#select2'],
requireAll: true,
onChange: function(){
}
}
]
});
UPD
The form is still not valid. The form.errors look as follows:
category_3
Select a valid choice. 19 is not one of the available choices.
category_2
Select a valid choice. 4 is not one of the available choices.
category_1
Select a valid choice. 1 is not one of the available choices.
Don't guess. Output the errors in the template, then you'll know why it is not valid.
{{ form.errors }}
Edit You have not put any name attributes in your form field elements. Browsers only send data on a form submit if the field has a name, as that's what the data is associated with.
You really should be using the Django form to output the fields anyway, rather than creating the HTML manually.
Related
form.html
<form>
{% csrf_token %}
<table>
<tr>
<td>
<label for="name">NAME: </label>
<input type="text" data-bind="value: $data.name" name="name" class="form-control" id="name" placeholder="name of product">
</td>
</tr>
<tr>
<td>
<label for="price">PRICE </label>
<input type="text" name="price" data-bind="value:$data.price" class="form-control" id="price" placeholder="price">
</td>
</tr>
<tr>
<td>
<label for="description">DESCRIPTION: </label>
<textarea cols="10" rows="5" name="description" data-bind="value:$data.description" class="form-control" id="description" placeholder="product description"></textarea>
</td>
</tr>
<tr>
<td>
<label for="image">IMAGE: </label>
<input type="file" class="form-control-file" id="image" name="image" required>
</td>
</tr>
<tr><td><button type="button" id="submit" data-bind="click : save" class="btn btn-success">Submit</button></td></tr>
</table>
</form></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<!-- <script type="application/javascript" src="static/js/knockout-file-bind.js"></script> -->
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
</script>
<script>
var ViewModel = function () {
var self = this;
self.save = function () {
var formdata = new FormData();
formdata.append('image', $('#image').get(0).files[0]);
formdata.append('name', ko.observable(""));
formdata.append('price', ko.observable(""));
formdata.append('description', ko.observable(""));
console.log(formdata)
$.ajax({
type: 'POST',
url: "{% url 'addProduct' %}",
data: formdata,
headers: {'X-CSRFToken': csrftoken},
processData: false,
contentType: false,
success: function (){
alert('The post has been created!')
},
error: function () {
alert("fail");
}
});
};
};
ko.applyBindings(new ViewModel())
</script>
</body>
</html>
views.py
def productform(request):
return render(request, 'app/product_form.html')
class ProductCreateView(CreateView):
model = Product
fields = ['name', 'price', 'description', 'image']
success_url=reverse_lazy('create-form')
def addProduct(request):
if request.method == "POST":
product=Product()
product.name = request.POST['name']
product.description = request.POST['description']
product.price = request.POST['price']
if len(request.FILES) != 0:
if len(product.image) > 0:
os.remove(product.image.path)
product.image = request.FILES['image']
product.save()
return render(request, 'app/product_form.html')
else:
return render(request, 'app/product_form.html')
# def get_success_url(self):
# return reverse('successlist')
def detail_view(request, id):
context ={}
context["data"] = Product.objects.get(id = id)
return render(request, "detail_view.html", context)
Image is not saving in database django I don't know what is the issue but image file name is displaying in console.log in ajax. Issue is image is not saving to database
When i try to print(request.FILES) it shows multidict is empty
I have given html code views code please check
Please help me to solve this
Thanks in advance
in your form.html try adding
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
# the rest of your code
</form>
If you're using a formdata object in an $.ajax request you must set the data field to that formdata object.
Also all the fields must be in it, append all to fields into it when you're making the ajax request not before.
self.save = function () {
var formdata = new FormData();
formdata.append('image', $('#image').get(0).files[0]);
formdata.append('name', ko.observable(""));
formdata.append('price', ko.observable(""));
formdata.append('description', ko.observable(""));
$.ajax({
type: 'POST',
url: "{% url 'addProduct' %}",
data: formdata,
headers: {'X-CSRFToken': csrftoken},
processData: false,
contentType: false,
success: function (){
alert('The post has been created!')
},
error: function () {
alert("fail");
}
});
};
I found difficult to change the color of the like button when the button hit and increase by +1 (in likes) in Django using ajax
my html template
<form method="POST" action="{% url 'video:like' video.pk %}" id="my-like-form">
{% csrf_token %}
<input type="hidden" class="likin" name="next" value="{{ request.path }}">
<button class="remove-default-btn" type="submit" id="openPopup" class="like-btn{{ request.path }}" style="border: none; ">
<i class="fa fa-thumbs-up" aria-hidden="true"><span>{{ video.likes.all.count }}</span></i>
Like
</button>
JavaScript
$("#my-like-form").submit(function(e){
e.preventDefault(); // Prevent form submission
let form = $(this);
let url = form.attr("action");
let res
const likes = $(`.like-btn{{ request.path }}`).text();// this code stopping the function of like button from work
const trimCount = parseInt(likes)
$.ajax({
type: "POST",
url: url,
data: form.serialize(),
dataType: "json",
success: function(response) {
selector = document.getElementsByName(response.next);
if(response.liked==true){
$(selector).css("color","blue");
res = trimCount - 1
} else if(response.liked==false){
$(selector).css("color","black");
res = trimCount + 1
}
}
})
})
Instead of using jinja code inside jquery code you can simple use $(this).find("button[class*=like-btn] span") this will give you span tag which have your total likes count then using .text() add new count to span tag.
Demo Code :
//suppose this return from server
var response = {
"liked": true
}
$("#my-like-form").submit(function(e) {
e.preventDefault();
let form = $(this);
let url = form.attr("action");
let res
//get like button and then find span to get total value
const likes = $(this).find("button[class*=like-btn] span").text();
const trimCount = parseInt(likes)
console.log(trimCount)
var selector = $(this).find("button[class*=like-btn]") //to select that button
/* $.ajax({
type: "POST",
url: url,
data: form.serialize(),
dataType: "json",
success: function(response) {
*/
if (response.liked == true) {
$(selector).css("color", "blue");
res = trimCount + 1
} else if (response.liked == false) {
$(selector).css("color", "black");
res = trimCount - 1
}
$(selector).find("span").text(res) //add that value inside span
/*}
})*/
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form method="POST" action="{% url 'video:like' video.pk %}" id="my-like-form">
{% csrf_token %}
<input type="hidden" class="likin" name="next" value="{{ request.path }}">
<!--you have two clas attribute merge that in one here `1` is just for demo replace that with like-btn{{ request.path }} -->
<button class="remove-default-btn like-btn1" type="submit" id="openPopup" style="border: none; ">
<i class="fa fa-thumbs-up" aria-hidden="true"><span>23</span></i>
Like
</button>
</form>
I am building a Django e-commerce website and as I was working on my cart functionality I noticed that every time I try to change the quantity of a specific item in the cart, all the items in the cart are re-sorted in a different order. I was wondering if there's any way I can sort the items in the backend before they are displayed.
** I am getting this error only when the user is "Authenticated" ** Guest checkout is working correctly
This is my cart Views.py
def cart(request):
# Authenticated Checkout
if request.user.is_authenticated:
customer = request.user.customer
order, created = Order.objects.get_or_create(customer=customer, complete=False)
cartItems = order.get_cart_items
items = order.orderitem_set.all()
if cartItems == 0:
context = {"items": items, "order": order, "cartItems": cartItems}
return render(request, "cart_empty.html", context)
#Guest Checkout
else:
data = cartData(request)
cartItems = data["cartItems"]
order = data["order"]
items = data["items"]
if cartItems == 0:
context = {"items": items, "order": order, "cartItems": cartItems}
return render(request, "cart_empty.html", context)
context = {"items":items, "order": order, "cartItems":cartItems}
return render(request, "cart.html", context)
def update_cart(request):
data = json.loads(request.body)
productId = data["productId"]
action = data["action"]
customer = request.user.customer
product = Product.objects.get(id=productId)
order, created = Order.objects.get_or_create(customer=customer, complete=False)
orderItem, created = OrderItem.objects.get_or_create(order=order, product=product)
if action == "add":
orderItem.quantity = (orderItem.quantity + 1)
elif action == "reduce":
orderItem.quantity = (orderItem.quantity - 1)
orderItem.save()
if action == "remove":
orderItem = orderItem.delete()
return JsonResponse("item was added", safe=False)
This is My JavaScript File
// Getting the cart cookie Value stored in the browser
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1);
if (c.indexOf(name) == 0) return c.substring(name.length,c.length);
}
return "";
}
var cart = JSON.parse(getCookie("cart"));
// Finding all the Minus buttons on the page and adding a click event listener to each one
const reduce_btn = document.getElementsByClassName("btn minus1 update-cart")
for (i = 0; i < reduce_btn.length; i++) {
reduce_btn[i].addEventListener("click", function(event){
var productId = this.dataset.id
var action = this.dataset.action
if (user === "AnonymousUser"){
addCookieItem(productId, action)
} else {
updateUserOder(productId,action)
}
})
}
// Finding all the Plus buttons on the page and adding a click event listener to each one
const increase_btn = document.getElementsByClassName("btn add1 update-cart")
for (i = 0; i < increase_btn.length; i++){
increase_btn[i].addEventListener("click", function(event){
var productId = this.dataset.id
var action = this.dataset.action
if (user === "AnonymousUser"){
addCookieItem(productId, action)
} else {
updateUserOder(productId,action)
}
})
}
// Finding all the remove buttons on the page and adding a click event listener to each one
const removeButton = document.getElementsByClassName("removeButton")
for (i = 0; i < removeButton.length; i++) {
removeButton[i].addEventListener("click", function(event){
var productId = this.dataset.id
var action = this.dataset.action
if (user === "AnonymousUser"){
removeFromCart(productId, action)
} else {
updateUserOder(productId,action)
}
})
}
// Removing the product from the order if the remove button is clicked
function removeFromCart(productId, action) {
if (action == 'remove'){
delete cart[productId];
}
document.cookie ='cart=' + JSON.stringify(cart) + ";domain=;path=/"
location.reload()
}
// (Guest Checkout) Updating the order of the customer if he is not authenticated
function addCookieItem(productId, action) {
console.log("not logged in..........")
if (action == 'add'){
cart[productId]['quantity'] += 1
}
if (action == 'reduce'){
cart[productId]['quantity'] -= 1
}
document.cookie ='cart=' + JSON.stringify(cart) + ";domain=;path=/"
location.reload()
}
// csrf token
var csrftoken = localStorage.getItem("csrftoken")
// (Authenticated Checkout) Updating the order of the customer if he is Authenticated
function updateUserOder(productId, action){
var url = "http://127.0.0.1:8000/update_cart/"
fetch(url, {
method: "POST",
headers: {
"Content-Type":"application/json",
"X-CSRFToken":csrftoken,
},
body:JSON.stringify({"productId": productId, "action": action})
})
.then((response) =>{
return response.json();
})
.then((data) => {
location.reload()
})
}
This is my HTML File
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cart</title>
<link rel="stylesheet" type="text/css" href="{% static 'css/cart.css' %}">
<script type="text/javascript">
var user = "{{request.user}}"
</script>
</head>
<body>
<!-- navigation menu -->
{% include "topnav.html" %}
<div class="maincontent">
<h1 class="hhh1" style="font-size:50px; margin:0px; margin-bottom:30px">Your Cart</h1>
<div class="centerblock">
<!-- DYNAMIC CONTENT GOES HERE -->
<div class="cart-row">
<span class="cart-item cart-header cart-column">ITEM</span>
<span class="cart-header cart-column">PRICE</span>
<span class="cart-quantity cart-header cart-column">QUANTITY</span>
</div>
<div class="cart-items">
{% for item in items %}
<div>
<div class="cart-item cart-column">
<img class="cart-item-image" src="{{item.product.imageURL}}" width="100" height="100">
<span class="cart-item-title">{{item.product.title}}</span>
</div>
<span class="cart-price cart-column" data-price="{{item.product.price}}" >£{{item.product.price}}</span>
<div class="cart-quantity cart-column">
<div class="quantity">
<button data-id={{item.product.id}} data-action="reduce" id="minus" class="btn minus1 update-cart" >-</button>
<input class="cart-quantity-input quantity" type="number" id="id_form-0-quantity" name="quantity" min="1" max="5" value="{{item.quantity}}">
<button data-id={{item.product.id}} data-action="add" id="plus" class="btn add1 update-cart" >+</button>
<button data-id={{item.product.id}} data-action="remove" class="removeButton" type="button">REMOVE</button>
</div>
</div>
</div>
{% endfor %}
<div class="cart-total">
<strong class="cart-total-title">Total</strong>
<span class="cart-total-price">£{{order.get_cart_total}}</span>
</div>
<div class="ordersummary">
<form action="checkout-info" method="post">
{% csrf_token %}
<input type="submit" value="CHECKOUT">
</form>
</div>
</div>
</div>
</div>
{% include "footer.html" %}
<script src="{% static 'js/test2.js' %}" ></script>
</body>
</html>
Assuming you want them sorted in the reverse order they were added to the cart, you can sort them by their id field:
items = order.orderitem_set.order_by('-id')
In laravel / jquery apps if I need to make checks if user is logged I make in controller:
$loggedUser = Auth::user();
if ( empty($loggedUser->id) ) {
return response()->json(['error_code'=> 1, 'message'=> "You must be logged!"],HTTP_RESPONSE_INTERNAL_SERVER_ERROR);
}
as I do not need to leave the user from the page, but only restrict some functionality
I show error message above using bootstrapGrowl library.
Now with laravel 7 /livewire 1.3 / turbolinks:5 / alpine#v2 I search how can I generate error and
show similar error message, leaving user on the page ?
UPDATED :
Let me explain it with detailed example :
In laravel / jquery apps I have in JS code :
var quiz_quality_radio= $('input[name=quiz_quality_radio]:checked').val()
var href = this_frontend_home_url + "/make-quiz-quality";
$.ajax( {
type: "POST",
dataType: "json",
url: href,
data: {"quiz_quality_id": quiz_quality_radio, "vote_id": this_vote_id, "_token": this_csrf_token},
success: function( response )
{
$('input[name=quiz_quality_radio]:checked').prop('checked', false);
frontendVote.showQuizQualityResults()
popupAlert("Thank you for rating ! Your rate was added!", 'success')
},
error: function( error )
{
$('input[name=quiz_quality_radio]:checked').prop('checked', false);
popupAlert(error.responseJSON.message, 'danger') // 'info', 'success'
}
});
and relative action in control :
public function make_quiz_quality(Request $request)
{
$requestData = $request->all();
$quiz_quality_id = ! empty($requestData['quiz_quality_id']) ? $requestData['quiz_quality_id'] : '';
$vote_id = ! empty($requestData['vote_id']) ? $requestData['vote_id'] : '';
if ( ! Auth::check()) {
return response()->json(['message' => "To rate you must login to the system !"], HTTP_RESPONSE_BAD_REQUEST);
}
if (empty($quiz_quality_id)) {
return response()->json([
'message' => "To rate you must select quiz quality !",
'quiz_quality_id' => $quiz_quality_id
], HTTP_RESPONSE_OK);
}
$vote = Vote::find($vote_id);
if ($vote === null) {
return response()->json([ 'message' => "Vote Item # " . $vote_id . " not found !"],HTTP_RESPONSE_NOT_FOUND);
}
$loggedUser = Auth::user();
$found_count = QuizQualityResult
::getByVoteIdAndUserId($vote_id, $loggedUser->id)
->count();
if ($found_count > 0) {
return response()->json(['message' => "You have already rated '" . $vote->name . "' # vote !", 'vote_id' => $vote_id],
HTTP_RESPONSE_BAD_REQUEST);
}
$newVoteItemUsersResult = new QuizQualityResult();
try {
$newVoteItemUsersResult->quiz_quality_id = $quiz_quality_id;
$newVoteItemUsersResult->vote_id = $vote_id;
$newVoteItemUsersResult->user_id = $loggedUser->id;
DB::beginTransaction();
$newVoteItemUsersResult->save();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
return response()->json(['message' => $e->getMessage(), 'voteCategory' => null], HTTP_RESPONSE_INTERNAL_SERVER_ERROR);
}
return response()->json(['message' => '', 'id' => $newVoteItemUsersResult->id], HTTP_RESPONSE_OK_RESOURCE_CREATED);
} // public function make_quiz_quality(Request $request)
and in case of error generated in error block I show message with function popupAlert
(implemented with bootstrapGrowl), without leaving the page.
That is what I want to make in livewire / turbolinks / alpine app. How can I do it?
UPDATED # 2:
That is just listing of items user can vote for:
<div class="table-responsive">
<table class="table text-primary">
#foreach($quizQualityOptions as $key=>$next_quiz_quality_option)
<tr>
<td>
<input class="" type="radio" name="quiz_quality_radio" id="quiz_quality_radio_{{ $next_quiz_quality_option }}" value="{{ $key }}">
<label class="col-form-label" for="quiz_quality_radio_{{ $next_quiz_quality_option }}">{{ $next_quiz_quality_option }}</label>
</td>
</tr>
#endforeach
</table>
</div>
<div class="row p-3">
<a class="btn btn-primary a_link" onclick="javascript:frontendVote.MakeQuizQuality()">Rate !</a>
</div>
with 2 restrictions :
User must be logged
Any logged user can vote only once
these 2 errors were genarated at server.
UPDATED # 3:
I found decision with using of axios, like :
<button type="submit" class="btn btn-primary btn-sm m-2 ml-4 mr-4 action_link" #click.prevent="submitNewTodo()">
Submit
</button>
submitNewTodo() {
console.log('submitNewTodo::')
let is_insert= 1
let current_toto_id= 1
axios({
method: (is_insert ? 'post' : 'patch'),
url: '/api/todos' + (!is_insert ? "/" + current_toto_id : ''),
data: {
text : this.new_todo_text,
priority : this.new_todo_priority
},
}).then((response) => {
this.new_todo_text= ''
this.new_todo_priority= ''
this.loadTodosRows()
popupAlert( 'Todo ' + (is_insert ? 'added' : 'updated') + ' successfully !', 'success' )
}).catch((error) => {
var validationErrors= convertObjectToArray(error.response.data.errors.text)
this.validation_errors_text= ''
validationErrors.map((next_error, index) => {
if(next_error && typeof next_error[1] != "undefined" ) {
this.validation_errors_text += '<li>'+next_error[1]+'</li>'
}
})
popupErrorMessage(error.response.data.message)
});
},
With it I show message both on success and failure as I need but I see big disadvantage with it
as I use livewire and I would like to use livewire here, if that is possible...
Hope I explained what I want clearly...
Thanks!
With Alpine.js and axios you could do something like this, note that I'm not sure whether or not this_frontend_home_url, this_vote_id and this_csrf_token will be defined.
<div x-data="quiz()">
<div>
<div class="table-responsive">
<table class="table text-primary">
#foreach($quizQualityOptions as $key=>$next_quiz_quality_option)
<tr>
<td>
<input x-model="selected_quiz" class="" type="radio" name="quiz_quality_radio"
id="quiz_quality_radio_{{ $next_quiz_quality_option }}" value="{{ $key }}">
<label class="col-form-label"
for="quiz_quality_radio_{{ $next_quiz_quality_option }}">{{ $next_quiz_quality_option }}</label>
</td>
</tr>
#endforeach
</table>
</div>
<div class="row p-3">
<a class="btn btn-primary a_link" #click="submitQuizQuality()">Rate !</a>
</div>
</div>
</div>
<script>
function quiz() {
return {
selected_quiz: null,
submitQuizQuality() {
const url = this_frontend_home_url + "/make-quiz-quality";
axios.post(url, {
quiz_quality_id: this.selected_quiz,
vote_id: this_vote_id, // no idea where this is coming from,
_token: this_csrf_token // no idea where this is coming from
}).then(() => {
// reset "checked" state
this.selected_quiz = null;
frontendVote.showQuizQualityResults();
popupAlert("Thank you for rating ! Your rate was added!", 'success');
}).catch(error => {
// reset "checked" state
this.selected_quiz = null;
if (error && error.response) {
popupAlert(error.response.message, 'danger')
}
});
}
}
}
</script>
I am trying to make a form which includes the functionality of Add/Delete Row. And I am following this tutorial.
The problem that I am facing is that I am unable to show the Add or remove button as well as the input fields in the form.
Screenshot:
Here's the template:
<html>
<head>
<title>gffdfdf</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<form action="" method="post" class="">
{% csrf_token %}
<h2> Team</h2>
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }} : {{ field }}
{% endfor %}
{{ form.players.management_form }}
<h3> Product Instance(s)</h3>
<table id="table-product" class="table">
<thead>
<tr>
<th>player name</th>
<th>highest score</th>
<th>age</th>
</tr>
</thead>
{% for player in form.players %}
<tbody class="player-instances">
<tr>
<td>{{ player.pname }}</td>
<td>{{ player.hscore }}</td>
<td>{{ player.age }}</td>
<td><input id="input_add" type="button" name="add" value=" Add More "
class="tr_clone_add btn data_input"></td>
</tr>
</tbody>
{% endfor %}
</table>
<button type="submit" class="btn btn-primary">save</button>
</form>
</div>
<script>
var i = 1;
$("#input_add").click(function () {
$("tbody tr:first").clone().find(".data_input").each(function () {
if ($(this).attr('class') == 'tr_clone_add btn data_input') {
$(this).attr({
'id': function (_, id) {
return "remove_button"
},
'name': function (_, name) {
return "name_remove" + i
},
'value': 'Remove'
}).on("click", function () {
var a = $(this).parent();
var b = a.parent();
i = i - 1
$('#id_form-TOTAL_FORMS').val(i);
b.remove();
$('.player-instances tr').each(function (index, value) {
$(this).find('.data_input').each(function () {
$(this).attr({
'id': function (_, id) {
var idData = id;
var splitV = String(idData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + index + "-" + tData
},
'name': function (_, name) {
var nameData = name;
var splitV = String(nameData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + index + "-" + tData
}
});
})
})
})
} else {
$(this).attr({
'id': function (_, id) {
var idData = id;
var splitV = String(idData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + i + "-" + tData
},
'name': function (_, name) {
var nameData = name;
var splitV = String(nameData).split('-');
var fData = splitV[0];
var tData = splitV[2];
return fData + "-" + i + "-" + tData
}
});
}
}).end().appendTo("tbody");
$('#id_form-TOTAL_FORMS').val(1 + i);
i++;
});
</script>
</body>
</html>
Please help me figure out what the code is missing, I tried the solution given in comments too but that only solves the input fields problem
You can do this by looping through the last row added to the table and then updating all id & name attributes with new incremented i value like:
$("tbody tr:last :input").each(function() {
$(this).attr({
'id': function(_, id) {
return id.replace(/\d/g, i)
},
'name': function(_, name) {
return name.replace(/\d/g, i)
}
})
})
Also as Paul mentioned change players to player
I haven't reproduced your code so I can't be sure, but when I look at your models I read:
class PlayerForm(forms.Form):
pname = forms.CharField()
hscore= forms.IntegerField()
age = forms.IntegerField()
PlayerFormset= formset_factory(PlayerForm)
class TeamForm(forms.Form):
tname= forms.CharField()
player= PlayerFormset()
You pass the Teamform to the template in the variable form. That means you can access the individual player forms with:
{% for player in form.player %}
...
{% endfor %}
In your code I read form.players instead of form.player. Also at the top I read form.players.management_form. Django will not recognize these variables so return empty values. You can check this by looking at the HTML elements in your browser. In the example in the comments in the link you provided it says correctly form.player. I am not sure why in that case the add / remove button would not show.