I want to have a simple text form on every row of a table.
Fiddle illustration of expected result: https://jsfiddle.net/wstg759f/1/
My Models.py:
class Person(models.Model):
name = models.CharField(max_length=30)
class Quality(models.Model):
name = models.CharField(max_length=30)
person=models.ForeignKey(Person)
I have a queryset that returns aggregated list of all persons, count of qualities for each person, one random quality of this person:
[
{'the_count': 5, u'randomquality': u'Nice’, u'person__name': u'Joe'},
{'the_count': 4, u'randomquality': u'Generous’,u'person__name': u'Mike'},
{'the_count': 4, u'randomquality': u'Healthy’,u'person__name': u'John’'},
..
]
My view.html (qualities is my queryset)
<table>
<thead>
<tr>
<th>Person</th>
<th>Qualities count</th>
<th>One random quality</th>
<th>Add a Quality?</th>
</tr>
</thead>
<tbody>
{%for obj in qualities%}
<tr>
<td>{{ obj.person__name }}</td>
<td>{{ obj.the_count }}</td>
<td>{{ obj.randomquality }}</td>
<td>text form to submit a quality for this person</td>
</tr>
{% endfor %}
</tbody>
</table>
The user should be able to input a quality in the text field, and once submitted it will be added to the model, and the text field is replaced by "thanks, submitted"
The submit form have to be independent.
I have no clear direction where to look at.
How would you do?
From my reading, I understand that formset could be a solution, but they are really unclear for me.
Should I even use django form in this case?
If yes, I believe the form should take an argument form the template: I don't need the user to tell me about the person name as it's already here.
Let me know if I can clarify.
Thanks in advance.
As a bonus, maybe for later, I want to avoid page refresh.
Is ajax the only way?
You can do this with ajax, form and views
template.html
<table>
<thead>
<tr>
<th>Person</th>
<th>Qualities count</th>
<th>One random quality</th>
<th>Add a Quality?</th>
<th>Button</th>
</tr>
</thead>
<tbody>
{% for obj in qualities %}
<tr id="row_{{ obj.id }}">
<td>{{ obj.person__name }}</td>
<td>{{ obj.the_count }}</td>
<td>{{ obj.randomquality }}</td>
<td>{{ form.quality }}</td>
<td><button value="{{ obj.id }}" onclick="addQuality(this.value)">Add</button></td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
function addQuality(id_object) {
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value
let data_to_backend = new FormData()
let qualityToAdd = document.getElementById(`id_${id_object}-quality`).value
if (qualityToAdd !== '') {
data_to_backend.append('quality', qualityToAdd)
data_to_backend.append('object_index', id_object)
} else return
const request = new Request("./",
{
method: 'POST',
headers: {'X-CSRFToken': csrftoken},
body: data_to_backend
})
fetch(request, {
method: 'POST',
mode: 'same-origin'
}).then(
function(response) {
if (response.status === 200) {
let tableRow = document.getElementById(`row_${ id_object }`)
tableRow.innerHTML = "<p>thanks, submitted</p>"
} else {
alert("Error")
console.log(response)
}
}
)
}
</script>
Set your views to receive a post request or just create a new urls path and another views
views.py
def addQuantity(request, codigo_orcamento):
if request.method == "POST":
form = formAddQuantity(request.POST)
if form.is_valid():
id_object = form.cleaned_data['object_index']
quality = form.cleaned_data['quality']
# get the object and add this quality
return HttpResponse(status=200)
else:
return HttpResponse(status=400)
else:
return HttpResponse(status=405)
In this view we simply check if the form is valid, get the object from db and add the quality on it
forms.py
class formAddQuantity(forms.Form):
object_index = forms.IntegerField(min_value=0)
quality = forms.CharField(max_length=100)
A simple check, but i recommend you to add more requirements to this fields
(Not tested, if throws a error let me know)
Related
i have created vue file to display data in frontend. but i'm unable to print 2 tables on same page at same time. only table 2 is displaying data , in first table it shows data for 2 seconds and than disappears. what i'm doing wrong? please help. i am super new in vuejs and have not much knowledge.
here is my index.vue file,
Table 1
<tbody>
<tr
v-show="items && items.length"
v-for="(data, i) in items"
:key="i">
<td></td>
<td></td>
</tr>
and this is function code,
async fetchData1() {
this.$store.state.operations.loading = true;
let currentPage = this.pagination ? this.pagination.current_page : 1;
await this.$store.dispatch("operations/fetchData", {
path: "/api/calldata?page=",
currentPage: currentPage + "&perPage=" + this.perPage,
});
table 2
<tbody>
<tr
v-show="items && items.length"
v-for="(data, i) in items"
:key="i">
<td></td>
<td></td>
</tr>
and here is the function for table 2
async fetchData2() {
this.Loading2 = true
let currentPage = this.Pagination2 ? this.Pagination2.current_page : 1;
await this.$store.dispatch("operations/fetchData", {
path: "/api/datacall/data2?page=",
currentPage: currentPage + "&perPage=" + this.perPage,
});
this.Loading2 = false;
and this are the controller functions
public function index(Request $request)
{
return DataResource::collection(Datamodl::with('user')->where('type',1)->latest()->paginate($request->perPage));
}
public function index2(Request $request)
{
return DataResource::collection(Datamodl::with('user')->where('type',0)->latest()->paginate($request->perPage));
}
And Route ,
Route::get('/calldata/data2', [DataController::class, 'index2']);
Route::apiResource('calldata', DataController::class);
Observation : You are updating same variable which is items for both the tables. Hence, it is overriding the latest items with the old items array.
Solution : Here is the implementation as per my comment.
new Vue({
el: '#app',
data: {
table1Items: null,
table2Items: null
},
mounted() {
this.fetchData1();
this.fetchData2();
},
methods: {
fetchData1() {
this.table1Items = [{
id: 1,
name: 'table 1 alpha'
}, {
id: 2,
name: 'table 1 beta'
}]
},
fetchData2() {
this.table2Items = [{
id: 1,
name: 'table 2 alpha'
}, {
id: 2,
name: 'table 2 beta'
}]
}
}
})
table, th, td {
border: 1px solid black;
margin: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<thead>
<th>ID</th>
<th>Name</th>
</thead>
<tbody>
<tr v-show="table1Items" v-for="(data, i) in table1Items" :key="i">
<td>{{ data.id }}</td>
<td>{{ data.name }}</td>
</tr>
</tbody>
</table>
<table>
<thead>
<th>ID</th>
<th>Name</th>
</thead>
<tbody>
<tr v-show="table2Items" v-for="(data, i) in table2Items" :key="i">
<td>{{ data.id }}</td>
<td>{{ data.name }}</td>
</tr>
</tbody>
</table>
</div>
you are using same property which is items for both. so second request will changed first items. so in both table same data will visible. you have to store in different state property for different data rendering.
solution :
make another action fetchData2.
call another mutation setItems2. add state propery item2: []. and setItems2 value from this mutation.
render second table like this.
<tr
v-show="items2.length"
v-for="(data, i) in items2"
:key="i">
<td></td>
<td></td>
</tr>
For code quailty:
give proper and related variable name . don't use items1 and items2 like that.
never used v-if/v-show and v-for in same element.for more info
use template first in this senerio.
use the item's unique id instead of the index in the key.
if you take the items default value as [], instead of null, then you only required to check items.length instead of items && items.length. so always use list default value []
if both requests are not dependent on each other then you should use Promise.all() for fetching data concurrently. which saved tremendous time and also in this case you don't require two loading property.
I am working on exchange project using laravel and want to refresh my table after a
<tbody class="fixed-header">
#foreach($transactionsCustomer as $transaction)
<tr>
<td>{{ $transaction->description }}</td>
#foreach($accounts as $account)
#if($transaction->code == 0)
#if($account->currency->id==$transaction->currency_id)
<td>{{ $transaction->amount }}</td>
<td></td>
#else
<td></td>
<td></td>
#endif
#else
#if($account->currency->id==$transaction->currency_id)
<td></td>
<td>{{ $transaction->amount }}</td>
#else
<td></td>
<td></td>
#endif
#endif
#endforeach
<td><a href="{{ route('transaction.edit',$transaction->id)}}" class="btn btn-primary" >سمون</a></td>
</tr>
#endforeach
</tbody>
and here is my ajax code to store the data
$("#transaction-form").submit(function(stay){
var formdata = $(this).serialize(); // here $(this) refere to the form its submitting
$.ajax({
type: 'POST',
url: "{{ route('transaction.store') }}",
data: formdata, // here $(this) refers to the ajax object not form
success: function (data) {
$('input[type="text"],textarea').val('');
updateTable();
},
});
stay.preventDefault();
});
and here is my controller method to represent the data
public function singleCustomer(Request $request)
{
$customer = Customer::find($request->id);
$accounts = Account::where('customer_id', $request->id)->get();
$currencies = Currency::all();
$transactionsCustomer = DB::table('transactions')->
join('accounts','transactions.account_id' ,'=','accounts.id')->join('customers','accounts.customer_id','=','customers.id')->join('currencies','accounts.currency_id','=','currencies.id')->select('transactions.*','currencies.id as currency_id')->where('customers.id',$request->id)->get();
return view('transaction.index',compact('currencies','customer','accounts','transactionsCustomer'));
}
now i want to refresh my table after this ajax request
You need to separate the table in different view.
Here's the flow, you get all data from your Controller , pass it to the table view, in the main view you include the table view using #include('table-view-route').
And don't forget on your success ajax, call updateTable() which has functionality to load the table view using jquery
$("#tableContainerId").load("{{url('table-view-route')}}");
I created a web route that must delete a contact based on a specific id and it looks like this:
Route::delete('/api/deleteContact/{id}', 'ContactController#destroy');
Then inside the controller, I have the following:
public function destroy($id)
{
// delete a contact by id
return response()->json(Contact::whereId($id), 200);
}
Inside one of my blade template files, I call the Vue component which displays the list of contacts:
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Phone</th>
</tr>
</thead>
<tbody>
<tr v-for="contact in contacts">
<td> {{ contact.id }} </td>
<td> {{ contact.name }} </td>
<td> {{ contact.phone }} </td>
<td><button class="btn btn-secondary">Edit</button></td>
<td><button #click="deleteContact(contact.id)" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
The delete button calls the deleteContact method and received the contact id in question.
The method looks like this:
deleteContact(id){
axios.delete('/api/deleteContact/' + id)
.then(res => {
for(var i = 0; i < this.contacts.length; i++) {
if(this.contacts[i].id == id) {
this.contacts.splice(i, 1);
}
}
})
.catch(err => console.log(err));
}
When I click to delete, the promise(then) occurs, however, after refreshing the page, I noticed that nothing was deleted and I see no errors in the console.
How can I successfully delete the contact based on the id passed in the deleteContact function ?
You have to append delete at the end of query like this:
public function destroy($id)
{
// delete a contact by id
return response()->json(Contact::where('id',$id)->delete(), 200);
}
I wrote a query to pass parameter from view to controller, and I got this error:
Undefined property: Illuminate\Database\MySqlConnection::$username
View Passing the parameter
<td width="25%"><a class="btn btn-info" href="{{ route('gamesListDetail',$game->id) }}">{{ $game->name }}</a></td>
Receiving Controller
public function gamesListDetail($id = null)
{
$gamelists = DB::table("platform_games")
->select("platform_games.id", "platform_games.username","game_player.game_id")
->join("game_player","game_player.game_id","=","platform_games.id")
->where('platform_games.id',$id)
->take(5);
return view('soccerrave.games.gamesListDetail', compact('gamelists'));
}
Receiving View
<tbody>
#foreach($gamelists as $key => $gamelist)
<tr>
<td>{{ ++$key }}</td>
<td>{{ $gamelist->username }}</td>
</tr>
#endforeach
<tr>
<td colspan="8">
{{ $gamelists->links() }}
</td>
</tr>
</tbody>
I expect the view to display top 5 data based on the parameter. But I got this error:
Undefined property: Illuminate\Database\MySqlConnection::$username
Documentation says:
skip / take
To limit the number of results returned from the query, or to skip a
given number of results in the query, you may use the skip and take
methods:
$users = DB::table('users')->skip(10)->take(5)->get();
So by example we see that after take method there must be get() call.
So fix Your code:
public function gamesListDetail($id = null)
{
$gamelists = DB::table("platform_games")
->select(
"platform_games.id",
"platform_games.username",
"game_player.game_id"
)
->join(
"game_player",
"game_player.game_id", "=", "platform_games.id"
)
->where('platform_games.id', $id)
->take(5)
->get(); // this one is required
return view('soccerrave.games.gamesListDetail', compact('gamelists'));
}
I have a jquery ajax code that looks like below,Its working on a delete button and a checkbox.
$(".delete-row").click(function(){
$("table
tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){
var value = $('chk').val();
$(this).parents("tr").remove();
$.ajax({
url: "/delete/",
// type: "post", // or "get"
data: value
});
});
});
});
This jquery call should delete the checked row in a table and the added ajax call will call a django view.
My doubt is I am passing the checkbox value to django view in the above AJAX call. In this case how do django view come to know what table row to delete based on the checkbox value?
below is how my table is getting created in a for loop
{% for x in dirs %}
<tr id='this_row' style="text-align: center;">
<td>
<input type="checkbox" id="chk" value="this_row" name="record"></td>
<td>
{{ x.name }}
</td>
<td>
{{ x.created_date }}
</td>
<td>
{{ x.description }}
</td>
</tr>
{% endfor %}
below is the delete button
<button type="submit" name="erase" class="delete-row">Delete Row</button>
Just add the id of the row as value attribute to the checkbox, so your ajax will send a list of ID. Instead of this_row, add {{x.id}} the instance id
{% for x in dirs %}
<tr id='tr-{{x.id}}' style="text-align: center;">
<td><input type="checkbox" id="chk-{{x.id}}" value="{{x.id}}" name="record"></td>
<td>
{{ x.name }}</td>
<td>{{ x.created_date }}</td>
<td>{{ x.description }}</td>
</tr>
{% endfor %}
And in your ajax, you have access to it with
// send this array via ajax
$(".delete-row").click(function(){
var id_list = new Array();
$("input[name=record]:checked").each(function(){
id_lists.push($(this).val());
$(this).parent("tr").remove(); // Remove the row,
// the correct is parent() without 's', not parents()
});
// with type:'post', don't forget the csrf_token
$.ajax({
url: "/delete/",
type: "post",
data: {
id_list:id_list,
csrfmiddlewaretoken:"{{ csrf_token }}"
},
});
});
And in your view, you can retrieve the id_list containing all the selected rows with reqest.POST.getlist('id_list[]'). In case it was a GET request reqest.GET.getlist('id_list[]')