Laravel destroy nested resource - javascript

I have a resource name OrganizationEmailDomain
Route::resource('organizations.emaildomains', 'OrganizationEmailDomainController', ['except' => ['show']]);
and able to index it just fine
In that view, when clicking in the three dots there's the destroy part
<form action="{{ route('organizations.emaildomains.destroy', ['organization' => $id, 'emaildomain' => $email_domain->id]) }}" method="post">
#csrf
#method('delete')
<button type="button" class="dropdown-item" onclick="confirm('{{ __("Are you sure you want to delete this email domain?") }}') ? this.parentElement.submit() : ''">
{{ __('Delete') }}
</button>
</form>
If in that index view I echo $email_domains (<?php echo $email_domains;?>) then I get as expected (note that the following was before the images were added, so the records don't match the images)
[{"id":1,"email_domain":"test.com","organization_id":1,"created_at":"2021-03-02T14:46:50.000000Z","updated_at":"2021-03-02T14:46:56.000000Z"},{"id":2,"email_domain":"gmail.com","organization_id":1,"created_at":null,"updated_at":null}]
When I try to destroy
/**
* Remove the specified resource from storage.
*
* #param \App\OrganizationEmailDomain $emailDomain
* #return \Illuminate\Http\Response
*/
public function destroy(Organization $organization, OrganizationEmailDomain $emailDomain)
{
//dd($emailDomain);
$emailDomain->delete();
return redirect()->route('organizations.emaildomains.index',$organization->id)->withStatus(__("Organization's email domain successfully deleted."));
}
that dd($emailDomain) returns this
As langbox states
(...) hasMany relationship (...) so it returns a Collection always
So, inspired in this answer from Dan, just substituted
$emailDomain->delete();
with the following
$org_email = OrganizationEmailDomain::where('id', $organization->org_email_domains->pluck('id'))->delete();
given that in Organization model I have
/**
* Get the email domains of the organization
*
* #return void
*/
public function org_email_domains()
{
return $this->hasMany(OrganizationEmailDomain::class);
}
What's the problem here?
It always deletes the record that is in the row above.
For example, let's delete DDDDDDDDDDDDDDDDDDD
this deleted the first row, AAAAAAAAAAAAAAAAA
If I then try to delete BBBBBBBBBBBBBBB (which now became the first row) it'll delete just fine.
For reference, here's the index.blade.php where the form I shared previously is at
<table class="table table-flush" id="datatable-basic">
<thead class="thead-light">
<tr>
<th scope="col">{{ __('Organization') }}</th>
<th scope="col">{{ __('Email Domain') }}</th>
<th scope="col">{{ __('Creation date') }}</th>
#can('manage-org-emaildomains', App\User::class)
<th scope="col"></th>
#endcan
</tr>
</thead>
<tbody>
#foreach ($email_domains as $email_domain)
<tr>
<td>{{ $organization->name }}</td>
<td>{{ $email_domain->email_domain }}</td>
<td>{{ $email_domain->created_at ? $email_domain->created_at->format('d/m/Y H:i') : "NULL" }}</td>
#can('manage-org-emaildomains', App\User::class)
<td class="text-right">
#if (auth()->user()->can('update', $email_domain) || auth()->user()->can('show', $email_domain) || auth()->user()->can('delete', $email_domain))
<div class="dropdown">
<a class="btn btn-sm btn-icon-only text-light" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v"></i>
</a>
<div class="dropdown-menu dropdown-menu-right dropdown-menu-arrow">
#can('show', $email_domain)
<a class="dropdown-item" href="{{ route('organizations.emaildomains.show', ['organization' => $id, 'emaildomain' => $email_domain->id]) }}">{{ __('Show') }}</a>
#endcan
#can('update', $email_domain)
<a class="dropdown-item" href="{{ route('organizations.emaildomains.edit', ['organization' => $id, 'emaildomain' => $email_domain->id]) }}">{{ __('Edit') }}</a>
#endcan
#if (auth()->user()->can('delete', $email_domain))
<form action="{{ route('organizations.emaildomains.destroy', ['organization' => $id, 'emaildomain' => $email_domain->id]) }}" method="post">
#csrf
#method('delete')
<button type="button" class="dropdown-item" onclick="confirm('{{ __("Are you sure you want to delete this email domain?") }}') ? this.parentElement.submit() : ''">
{{ __('Delete') }}
</button>
</form>
#endif
</div>
</div>
#endif
</td>
#endcan
</tr>
#endforeach
</tbody>
</table>

You cant delete just the id, you are passing only the id.
in the controller you Must do Find , Ex :
$flight = Flight::find($emaidomain);
then u do
$flight->delete();
You are just passing id in the form, you must find the item in the controller via the id then delete, u cant delete id only.

I've changed the route to shallow nesting
Route::resource('organizations.emaildomains', 'OrganizationEmailDomainController', ['except' => ['show']])->shallow();
then the form to
<form action="{{ route('emaildomains.destroy', ['organization' => $id, 'emaildomain' => $email_domain->id]) }}" method="post">
#csrf
#method('delete')
<button type="button" class="dropdown-item" onclick="confirm('{{ __("Are you sure you want to delete this email domain?") }}') ? this.parentElement.submit() : ''">
{{ __('Delete') }}
</button>
</form>
and the destroy() to
/**
* Remove the specified resource from storage.
*
* #param \App\OrganizationEmailDomain $emailDomain
* #return \Illuminate\Http\Response
*/
public function destroy(Request $request, OrganizationEmailDomain $emailDomain)
{
$path = $request->path();
$id = substr($path, strrpos($path, '/') + 1);
$emailDomain = OrganizationEmailDomain::find($id);
$emailDomain->delete();
return redirect()->route('organizations.emaildomains.index',$request->query()['organization'])->withStatus(__("Organization's email domain successfully deleted."));
}
and now it works fine.

Related

Laravel 8 Delete Data Using Bootstrap Model

I have a table made with Bootstrap and I want to delete the data with bootstrap model confirm. Deleting works fine without model, but with model, delete button always send last row data to the model no matter which row delete button clicked.
model button
<i class="fe fe-trash text-danger fe-24"></i>
Modal delete button
<form action="{{ route('admin.users.delete', $user) }}" method="post">
#csrf
<button type="submit" class="btn mb-2 btn-danger">Delete</button>
</form>
js used
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget)
var recipient = button.data('user')
});
table foreach
<tbody>
#if ($users->count())
#foreach ($users as $user)
<tr>
<td>{{ $user->id }}</td>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>(478) 446-9234</td>
<td>159 address</td>
<td>
#if ($user->user_role == 0)
User
#elseif ($user->user_role == 1)
Editor
#else Admin
#endif
</td>
<td>{{ $user->created_at->format('M d, Y') }}</td>
<td>{{ $plans->find($user->plan_id)->name }}</td>
<td>
#if ($user->expiration_date != null)
{{ Carbon\Carbon::parse($user->expiration_date)->format('M d, Y') }}
#else
N/A
#endif
</td>
<td>
#if ($user->is_active == 1)
<span class="text-success font-weight-bold">Active</span>
#else
<span class="text-danger font-weight-bold">Disabled</span>
#endif
</td>
<td>
<i class="fe fe-edit text-secondary fe-24"></i>
<i class="fe fe-trash text-danger fe-24"></i>
</td>
</tr>
#endforeach
#endif
</tbody>
I am assuming that you have a foreach to creat the delete button s for every user
The $user that is passed will always be the last $user id
You need to pass the $user that was clicked
and pass this the the action of the form
try
var deleteModal = $('#deleteModal');
$('a').on('click', function (e) { // this is the "a" tag
var userId = $(this).data('user'),
var submitUrl = {!! url("/admin/users/delete/") !!}+userId;
var form = $('#form');
form.attr('action', submitUrl);
});
});
when the is clicked ,store the data-user and create the submit and change the form action
Hope this would be of any help to you.

Limit the user to give the test only once

I want to limit the student to attempt specific test once.
There can be multiple quizzes but each student should be giving test once only.
The student field is one to one field with user
#login_required(login_url='studentlogin')
#user_passes_test(is_student)
def calculate_marks_view(request):
if request.COOKIES.get('course_id') is not None:
course_id = request.COOKIES.get('course_id')
course=QMODEL.Course.objects.get(id=course_id)
total_marks=0
questions=QMODEL.Question.objects.all().filter(course=course)
for i in range(len(questions)):
selected_ans = request.COOKIES.get(str(i+1))
actual_answer = questions[i].answer
if selected_ans == actual_answer:
total_marks = total_marks + questions[i].marks
student = models.Student.objects.get(user_id=request.user.id)
result = QMODEL.Result()
result.marks=total_marks
result.exam=course
result.student=student
result.save()
return HttpResponseRedirect('view-result')
(Edited)
I'm giving the models that I used in this .
The models used are as follows :
Student :~
class Student(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE)
profile_pic= models.ImageField(upload_to='profile_pic/Student/',null=True,blank=True)
address = models.CharField(max_length=40)
mobile = models.CharField(max_length=20,null=False)
#property
def get_name(self):
return self.user.first_name+" "+self.user.last_name
#property
def exams_taken(self):
return [r.course for r in Result.objects.get(student=self)]
#property
def get_instance(self):
return self
def __str__(self):
return self.user.first_name
Quiz :~
class Course(models.Model):
course_name = models.CharField(max_length=50)
question_number = models.PositiveIntegerField()
total_marks = models.PositiveIntegerField()
def __str__(self):
return self.course_name
class Question(models.Model):
course=models.ForeignKey(Course,on_delete=models.CASCADE)
marks=models.PositiveIntegerField()
question=models.CharField(max_length=600)
option1=models.CharField(max_length=200)
option2=models.CharField(max_length=200)
option3=models.CharField(max_length=200)
option4=models.CharField(max_length=200)
cat=(('Option1','Option1'),('Option2','Option2'),('Option3','Option3'),('Option4','Option4'))
answer=models.CharField(max_length=200,choices=cat)
class Result(models.Model):
student = models.ForeignKey(Student,on_delete=models.CASCADE)
exam = models.ForeignKey(Course,on_delete=models.CASCADE)
marks = models.PositiveIntegerField()
date = models.DateTimeField(auto_now=True)
The template for exam that I used is given as follows:
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h6 class="panel-title">Courses</h6>
</div>
<table class="table table-hover" id="dev-table">
<thead>
<tr>
<th>Exam Name</th>
<th>Take Exam</th>
</tr>
</thead>
{% for t in courses %}
<tr>
<td> {{t.course_name}}</td>
<td><a class="btn btn-danger btn-xs" href="{% url 'take-exam' t.id %}"><span class="glyphicon glyphicon-circle-arrow-right"></span></a></td>
</tr>
{% endfor %}
</table>
</div>
</div>
I want to show the button only to new user
<td><a class="btn btn-danger btn-xs" href="{% url 'take-exam' t.id %}"><span class="glyphicon glyphicon-circle-arrow-right"></span></a></td>
I assume from your code example, each course has one exam and each student should be able to take each of the exams once. Looks like you already have a model Result that stores the relation between the student and each exam or course. You could simply check if such an object already exists for the combination of the user and the course.
class Student(model):
[...]
#property
def exams_taken(self):
return [r.course for r in Result.objects.get(student=self)]
(I'm pretty sure there is a more elegant way to do use the query language instead of the list comprehension..)
In your template you could simply check each exam to be in this list.
{% for t in courses %}
<tr>
<td> {{t.course_name}}</td>
<td>
{% if t in student.exams_taken %}
<span> already taken </span>
{% else %}
<a class="btn btn-danger btn-xs" href="{% url 'take-exam' t.id %}"><span class="glyphicon glyphicon-circle-arrow-right"></span></a>
{% endif %}
</td>
{% endfor %}
have not tested the code and did a lot of assumtions to find a solution that fits your needs perfectly more Information as the definition of your current models could be helpful.
And make sure your exam view checks if the student has taken the exam from the database. Checking the cookies or hiding the button does not prevent "creative" people from taking the exam again.

How to pass checkbox values to modal bootstrap for Python Flask remove data from mysql table?

Environment:
Python 3.7.7
Flask 1.1.2
Werkzeug 1.0.1
Introduction:
I am making a Flask application for my saas dashboard.
I have a page "categories.html" which displays a list of categories in a table.
Each category has a checkbox if the user wants to delete several categories by checking the categories and clicking on the "DELETE" button. See screenshot below:
So users will be able to select multiple categories and remove them by clicking on the "DELETE" button.
But before to delete the rows in Mysql table categories, a confirmation popup is showing up. This popup is done by Bootstrap modal.
Problem:
I don't how to pass the list of checkbox values selected by the user to the modal popup.
What did I try:
I tried to fix this issue with some javascript code, but it doesn't work.
My code:
My template categories.html (I removed unecessary code):
<form>
<table id="categories" class="table dataTable no-footer" role="grid">
<thead>
<tr role="row">
<th tabindex="0" rowspan="1" colspan="1" style="white-space: nowrap"></th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr role="row" >
<td style="white-space: nowrap">
<input name="category_id" value="{{ category.ID }}" type="checkbox" class="form-check-input" style="float: left; margin: 0 auto;">
</td>
<td>{{ category.name }}</td>
<td style="white-space: nowrap">
{% if category.icon %}
{% if category.icon.find('<i class')!=-1 %}
{{ category.icon|safe }}
{% else %}
<img src="{{ url_for('static', filename='images/<category.icon>') }}">
{% endif %}
{% else %}
na
{% endif %}
</td>
</tr>
{% endfor %}
</tr></tbody>
</table>
</form>
<!-- Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete Category</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Are you sure you want to delete these categories?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<form action="{{ url_for('delete_category')}}" method="POST">
<input name="category_id" type="hidden" value="pass_checkedvalue" id="hidden_checkedinput">
<input class="btn btn-danger" type="submit" value="delete"/>
</form>
</div>
</div>
</div>
</div>
<script>
$('#deleteModal').on('show.bs.modal', function(e) {
var checkedValues = $('.record:checked').map(function(){ return this.value; }).get();
//put the ids in the hidden input as a comma separated string
$('#hidden_checkedinput').val(checkedValues.join(','));
});
</script>
My route.py:
#app.route('/delete_category', methods=['GET', 'POST'])
#login_required
def delete_category():
if request.method == "POST":
if request.form["category_id"]:
print(request.form["category_id"])
Category.query.filter(Category.ID.in_(request.form["category_id"])).delete()
db_mysql.session.commit()
flash('The categories have been deleted', 'success')
return redirect(url_for('categories'))
My models.py:
class Category(db_mysql.Model):
__tablename__ = "W551je5v_pb_categories"
ID = db_mysql.Column('ID', db_mysql.Integer, primary_key=True)
name = db_mysql.Column('name', db_mysql.Unicode)
icon = db_mysql.Column('icon', db_mysql.Unicode)
icon_blue_img = db_mysql.Column('icon_blue_img', db_mysql.Unicode)
icon_white_img = db_mysql.Column('icon_white_img', db_mysql.Unicode)
icon_black_img = db_mysql.Column('icon_black_img', db_mysql.Unicode)
platforms = db_mysql.relationship('Platform', backref='W551je5v_pb_categories', lazy=True)
def __repr__(self):
return f"Category('{self.ID}','{self.name}','{self.icon}','{self.icon_blue_img}','{self.icon_white_img}','{self.icon_black_img}')"
OUTPUT:
When I execute this code, I get this error message:
sqlalchemy.exc.InvalidRequestError
sqlalchemy.exc.InvalidRequestError: Could not evaluate current criteria in Python: "Cannot evaluate clauselist with operator <function comma_op at 0x0000026EB4542558>". Specify 'fetch' or False for the synchronize_session parameter.
And the print(request.form["category_id"]) showed in console:
pass_checkedvalue
Which is the value of my hidden field.
I have no idea what am I doing wrong. Can anyone help me, please?
I don't how to pass the list of checkbox values selected by the user to the modal popup.
I don't think that's the right approach.
Actually, I don't think you need to pass ANY data to the modal popup.
What I would instead do is add an on-click for the modal button that would run a javascript function.That function needs to simply iterate over the <tr> tags and find the checked rows.
After you have a list containing the checked rows' IDs, you can send that to your backend via some HTTP request (you can use Javascript's FETCH API for that).
Your code would like something like that (please treat this as a schema since I don't actually know how your HTML looks like):
let checked_arr = [];
let tr_lst = document.getElementsByTagName('tr'); // probably better to be done with getElementsByClassName
for (let i=0; i<tr_lst.length; i++) {
let checkbox_td = tr_lst[i].children[0]; // assuming first <td> is the checkbox
let checkbox_element = checkbox_td.children[0]; // assuming your HTML looks like <td><input type="checkbox"...></td>
if (checkbox_element.checked) {
checked_arr.push(tr_lst[i].id);
}
}
let response = await fetch('/your_api_endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({"data": checked_arr})
});
Also, here is a nice tutorial on how to use FETCH API:
https://javascript.info/fetch
Hope that's helpful :)

Flask does not return db query when I use the JS hide/unhide. Works if I comment out JS

I am trying to retrieve data from the database based on a users search results. The results, in a table format, should only be shown after the user hits the search button.
Query executes fine when javascript is cancelled out and the table "list" display is changed to block. However, when I enable the javascript I get no results.
<div style="text-align: center;">
{% from "_formhelpers.html" import render_field %}
<form method=post action="/">
<dl style="display: inline-block; text:white;" >{{render_field(form.search)}} </dl>
<button id="searchbutton" type="submit" style="display: inline-block;" class="btn btn-outline-success my-2 my-sm-0" onclick="fetchlist(); return false;">Search</button>
<br>
{% if error %}
<p class = "error"><strong>Error:</strong>{{error}}</p>
{% endif %}
</form>
</div>
<div style="text-align:center;">
<table id="list" style="display:none;" class = "table table-hover" >
<thead>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Rating</th>
<th scope="col">Review</th>
</thead>
<tbody>
{% for row in data %}
<tr>
{% for d in row %}
<td>{{ d }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
<script>
function fetchlist() {
if (document.getElementById('searchbutton').onclick) {
document.getElementById('list').style.display = 'inline';
}
else document.getElementById('list').style.display = 'inline';
}
</script>
{% endblock %}
</html>
#app.route('/', methods=['GET', 'POST'])
def homepage():
try:
form=SearchForm(request.form)
global d1
d1 =""
if request.method == "POST" and form.validate():
search = form.search.data
a = search.split(" ",1)
firstname, lastname = (a[0], a[1])
c,conn = connection()
qry = "SELECT FirstName, LastName FROM posts WHERE FirstName LIKE (%s) AND LastName like (%s)"
c.execute(qry, ((thwart(firstname)), (thwart(lastname))))
d1 = c.fetchall()
c.close()
conn.close()
else: print('error')
return render_template("index.html", data=d1, form = form)
except Exception as e:
return(str(e))
As you already know the problem is caused by your JS. This is because the JS is waiting for the search button to be clicked before it changes the style of the list to inline.
This all seems fine, but the problem comes in the fact that the JS is executed when the button is clicked. But then the request is posted to the server and a new page is sent to the browser with the search results. However, this new page the search button has never been clicked.
You can fix writing the method to display the results into your template. For instance you could wrap the table in an if statement like this...
{% if data|length %}
<div style="text-align:center;">
<table id="list" style="display:none;" class = "table table-hover" >
<thead>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Rating</th>
<th scope="col">Review</th>
</thead>
<tbody>
{% for row in data %}
<tr>
{% for d in row %}
<td>{{ d }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table
</div>
{% endif %}
The |length filter makes sure data is not an empty string. Otherwise I believe it may always be true. You could try {% if data %} to double check. It may work.
There are a lot more options....
You could wrap you <script> in the if statement. You should modify it a little. So it does not wait of the search button to be clicked.
You could wrap the inline style in the if statement. Of course you could use CSS classes instead of inline styling. This would be cleaner.

Populate a Symfony form row with a table row index

I'm facing a stupid issue there but I'm sure it would be easy for you, master of javascript !
Here is my symfony form :
$form = $this->createFormBuilder()
->add('accept', 'checkbox', array('required' => false, 'label' => 'Do You Accept This News ?'))
->add('custom', 'textarea', array('required' => false, 'attr' => array('rows' => 10), 'label' => 'Attach a Custom Message'))
->add('line', 'integer', array('required' => false))
->add('Send', 'submit',array('attr' => array('class' => 'button')))
->getForm();
And here is my twig template rendering it :
<table>
<thead>
<tr>
<th>Time</th>
<th>Statement</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% set count = 0 %}
{% for milestone in milestones %}
<tr>
<td>{{ milestone.createdAt | date('M-d H:i:s') }}</td>
<td>{{ milestone.idEntity.name}}{{ newStatements[count] }}
</td>
<td>
<a class="button revealModal" href="#" data-reveal-id="myModalJournalist">Treat News</a>
<div id="myModalJournalist" class="reveal-modal" data-reveal>
{{ form_start(form) }}
<div class="marginBottom">
{{ form_widget(form.accept) }}
{{form_label(form.accept)}}
</div>
<div class="fieldContainer fieldTextareaContainer">
{{form_label(form.custom)}}
{{form_widget(form.custom)}}
{{form_errors(form.custom)}}
</div>
<div class="hide">
{{form_widget(form.line)}}
</div>
{{ form_end(form) }}
<a class="close-reveal-modal">×</a>
</div>
</tr>
{% set count = count + 1 %}
{% endfor %}
</tbody>
</table>
So the thing is, I'm displaying a button for each row, but they all open the same modal with an unique form, in which I have a boolean, a textarea and an integer field which I'm hidding because I want to auto-fill the "line" row of my form with the index of the row of the button that was click, so I would know with row I need to update.
I've been trying that but unsuccessfully :
$(function(){
$("body").on('click','a.button.revealModal',function(){
var position = $(this).parents("tr").index();
$("#form").find("input#form_line").val(position);
});
});
Thanks for your help !
See https://stackoverflow.com/questions/469883/how-to-find-the-index-of-a-row-in-a-t‌​able-using-jquery
Have you tried:
$("tr").index(this)
The documentation shows just passing this and that the preceding selection should be where the node is found. If you need to find it in a specific table (and there are multiple), you may need to provide some context:
// haven't tested this
$("tr", $(this).closest("table")).index(this)

Categories

Resources