I'm new to flask admin and I need to move the delete button to the edit view.
Here is the AdminModelView class that my other views are inheriting from.
class AdminModelView(sqla.ModelView):
can_view_details = True
# column_extra_row_actions = [ViewRowAction()]
def is_accessible(self):
if not current_user.is_active or not current_user.is_authenticated:
return False
if current_user.can('administrator'):
return True
return False
def _handle_view(self, name, **kwargs):
"""
Override builtin _handle_view in order to redirect users when a view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
# permission denied
abort(403)
else:
# login
return redirect(url_for('auth.login', next=request.url))
print(self._edit_form_class)
def get_list_row_actions(self):
"""
Return list of row action objects, each is instance of
:class:`~flask_admin.model.template.BaseListRowAction`
"""
actions = []
if self.can_view_details:
if self.details_modal:
actions.append(template.ViewPopupRowAction())
else:
actions.append(template.ViewRowAction())
return actions + (self.column_extra_row_actions or [])
I've redefined get_list_row_actions to take the edit and delete buttons off of the list view. I'm wondering if there's a part of my AdminModelView class that I can change or if I need to change the template for the edit form.
My solution:
Wrote new endpoint on class AdminModelView(sqla.ModelView):
#expose('/delete', methods=('DELETE',))
def delete_view(self):
"""
Delete model
"""
id = request.args.get('id')
if id is None:
return jsonify({"success": False}), 404
model = self.get_one(id)
db.session.delete(model)
db.session.commit()
flash("{0} deleted".format(model))
return jsonify({"success": True}), 200
Used these macros to render a delete button and a modal to the edit view template.
{% if admin_view.can_delete and model %}
{{ add_modal_button(url='#', title='Delete', content='Delete', modal_window_id='delete_modal', btn_class='btn btn-danger') }}
{% endif %}
{% macro delete_modal() %}
<div class="modal fade" id="delete_modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
{# bootstrap version > 3.1.0 required for this to work #}
<div class="modal-content">
<div class="panel panel-danger">
<div class="panel-heading">Delete Record</div>
<div class="panel-body">
<p>You are about to delete this record. This action cannot be undone.</p>
<p>Would you like to proceed?</p>
</div>
<div class="panel-footer text-center">
<button type="button" class="btn btn-secondary" id="cancel_delete">Cancel</button>
<button type="button" class="btn btn-danger" id="confirm_delete">Delete</button>
</div>
</div>
</div>
</div>
</div>
{% endmacro %}
Hybrid Jinja2 and JavaScript. This should be refactored. {{model.id}} can be stored in the DOM and retrieved with jQuery before the AJAX request is made.
$("#cancel_delete").click(function() {
$("#delete_modal").modal("toggle")
});
$("#confirm_delete").click(function() {
$.ajax({
url: "../delete?id={{ model.id }}",
method: "DELETE"
}).done(function() {
window.location.replace("{{ return_url }}");
}).fail(function() {
$(".actions-nav").before(
'<div class="alert alert-danger alert-dismissable fade in">' +
'×' +
'<strong>Error:</strong> This object failed to delete.' +
'</div>'
)
})
})
Related
I have a Django form that receives a text (that I copy from Google Classroom: a bunch of student comments). I use these comments to make student's attendance. What I want to achieve is:
Accessing /insertion/ url via GET user receive the page form as a response, to choose the class (class01, class02, etc) and to past the text
When the user clicks on submit in this form (post method), it is redirect to the same /insertion/ url, but now the form is bound to the data submited, and the page shows a preview page (based on a boolean variable I'm passing through context), showing what students are present and what are absent based on the text informed. At that page, a new submit button will be shown below a text like "if everything's ok, hit the ok button".
After click this ok button, a pdf will be generated and the user will be redirected to /files/ url, to see the generated pdf and previous generated pdf.
views.py
def insertion(request):
context = {}
if request.method == 'GET':
form = AttendanceDataForm()
context.update({"form": form})
if request.method == 'POST':
form = AttendanceDataForm(request.POST)
context.update({"form": form})
if form.is_valid():
lesson = form.cleaned_data['lesson']
raw_text = form.cleaned_data['raw_text']
# Get course students
course_students = md.Student.objects.filter(course_id=lesson.course_id)
# Get present students based on raw text informed
present_students = [s for s in course_students if s.full_name in raw_text]
# Get absent students based on raw text informed
absent_students = [s for s in course_students if s.full_name not in raw_text]
context.update({
"present_students": present_students,
"absent_students": absent_students,
"render_preview": True
})
context.update({"active_freq": True})
return render(request, 'core/insertion.html', context)
def files(request):
context = {}
if request.method == 'POST':
# How can I access all expensive calculation I did in the previous view?
context.update({"active_gen": True})
return render(request, "core/files.html", context)
insertion.html
<div class="row">
<div class="col-12 col-md-6">
<h3>Informar Frequência</h3>
{% crispy form %}
</div>
<div class="col-12 col-md-6">
{% if render_preview %}
<div class="container">
<div class="row p-4 bg-white rounded mt-4">
<div class="col-12 col-sm-6">
<h5>Alunos presentes</h5>
<ul class="previewer-list">
{% for student in present_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
<div class="col-12 col-sm-6">
<h5>Alunos ausentes</h5>
<ul class="previewer-list">
{% for student in absent_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
</div>
<p class="mt-3">If everything's ok, hit the OK button</p>
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary">OK</button>
</form>
</div>
{% endif %}
</div>
</div>
I could get to implement 1 and 2, but 3 is a mistery right now. What I couldn't get is how I can access the expensive calculations I did in insertion view in the files view. How can I do that?
Here's a solution using session framework.
We'll save the calculations in the session and access those values in another view later.
For starters, we'll just save the ids (pk) of the students instead of the student instances because they are not JSON serializable [See note below].
def insertion(request):
# do expensive calucations ...
present_ids = [s.pk for s in present_students]
absent_ids = [s.pk for s in absent_students]
request.session['attendance_data'] = {
'present_ids': present_ids,
'absent_ids': absent_ids
}
def files(request):
attendance_data = request.session.get('attendance_data')
if not attendance_data:
# show error or something else ...
pass
present_students = md.Student.objects.filter(
pk__in=attendance_data['present_ids']
)
absent_students = md.Student.objects.filter(
pk__in=attendance_data['absent_ids']
)
# generate the pdf ...
Note: If you wish, you can also save the student instances in the session but you'll have to change the SESSION_SERIALIZER setting to use the PickleSerializer. See notes about session serialization.
You could submit the primary keys as form data in hidden fields. Just choose an appropriate delimiter based on your primary key (for example, don't delimit with a hyphen if you use a GUID primary key).
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<input type="hidden"
name="present"
value="{% for s in present_students %}{{ s.pk }},{% endfor %}"
>
<input type="hidden"
name="absent"
value="{% for s in absent_students %}{{ s.pk }},{% endfor %}"
>
<button type="submit" class="btn btn-primary">OK</button>
</form>
Then in the view you can pick up the PKs in the view from the form data then request.
def files(request):
context = {}
if request.method == 'POST':
present_pks = request.POST.pop('present').split(',')[:-1]
absent_pks = request.POST.pop('absent').split(',')[:-1]
# do type conversions if needed
...
# Because we already have the pks separated, we can combine them
# for the query in order to do just 1 query
course_students = md.Student.objects.filter(pk__in=present_pks + absent_pks).all()
absent_students = []
present_students = []
for student in course_students:
if student.pk in absent_pks:
absent_students.append(student)
else:
present_students.append(student)
I am trying to implement a form in a modal which is focused on modifying a comment in a post, the way i did it everything works, the problem is when I click on the submit button it sends me to the html in which I have the modal and there I edit the comment. when I try to delete the url in the action form that takes me to the second page of my form it throws the error "local variable 'form' referenced before assign", also if I put for example in form action the url of the login sends me towards There but the comment is not updated or edited.
My idea is simply that when I submitting the form, the modal closes, and the page where the modal was opened from the beginning, reload or simply the comment already edited appears.
if you need more information I can add it.
views.py
#need_analyst_role
def comment_modify(request, comment_id):
if 'comment_edit' in request.POST:
form_comment = FormComment(request.POST)
if form_comment.is_valid():
comment_text = form_comment.cleaned_data['text']
comment = ModelRiskTracking.objects.get(id=comment_id)
comment.comment = comment_text
print(comment.comment)
comment.save()
else:
messages.error(request, 'Error!', extra_tags="danger")
context = {}
context['comment'] = ModelRiskTracking.objects.get(id=comment_id)
return render(request, 'analyst_pages/comment_edit.html', context = context)
modal.html
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Editar comentario</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form action="{% url 'soc_irisk_modify' comment_id=comment.id %}" method="POST">
{% csrf_token %}
<textarea type="text" name="text" class="form-control" rows="15">{{ comment.comment|safe }}</textarea>
<div class="modal-footer">
<input type="submit" value="Actualizar" name="comment_edit" onsubmit="setFormSubmitting()" class="btn btn-info btn-sm pull-right" />
</div>
</form>
</div>
</div>
I open the modal with a button calling a jQuery function:
<script type="text/javascript">
function openModal(url){
$('#commentModal').load(url, function(){
$(this).modal('show');
});
}
</script>
<button type="button" class="btn btn-primary btn-sm pull-right" data-toggle="modal" data-target="#commentModal" onclick="openModal('{% url 'soc_comment_modify' comment_id=comment.id %}')">
when you saving the model, you need to redirect the user
#need_analyst_role
def comment_modify(request, comment_id):
if 'comment_edit' in request.POST:
form_comment = FormComment(request.POST)
if form_comment.is_valid():
comment_text = form_comment.cleaned_data['text']
comment = ModelRiskTracking.objects.get(id=comment_id)
comment.comment = comment_text
print(comment.comment)
comment.save()
return redirect("some_url")
else:
messages.error(request, 'Error!', extra_tags="danger")
return redirect("some_url")
context = {}
context['comment'] = ModelRiskTracking.objects.get(id=comment_id)
return render(request, 'analyst_pages/comment_edit.html', context = context)
I have been trying to create a to do list app and there has been no problems until when i was adding an edit button, when i press the edit button, it shows the edit page with the text that has to be edited but the submit button that is suposed to change the database is not working, I think I need to add something to the views.py file but I dont know what.
viws.py
def edit(request, id):
created_items = Create.objects.get(id=id)
return render(request, 'my_app/edit.html', {"created_items": created_items})
urls.py
urlpatterns = [
path('edit/<int:id>/', views.edit, name='edit'),
]
models.py
class Create(models.Model):
added_date = models.DateTimeField()
text = models.CharField(max_length=200)
edit.html
{% extends 'index.html' %}
{% block body %}
<div align="center">
<h1>Edit your post!</h1>
<div class="container form-group">
<h1>↓</h1>
<form method="POST">{% csrf_token %}
<textarea class="form-control" name="content" id="id" rows="3" style="text-align: center;">{{ created_items.text }}</textarea>
<button type="submit" class="btn btn-outline-success" style="margin-top: 5px;">Submit</button>
</form>
</div>
</div>
{% endblock %}
You will send POST request when you click submit and need to catch that request in some function like:
if request.method == 'POST':
edited_text = request.POST.get('text') ## 'text' might be changed. I don't know how you handle the request.
related_id = request.POST.get('id') ## You need to take updated id. (somehow?)
Create.objects.filter(id=related_id).update(text=edited_text) ## That will do the job.
Hope it helps,
My problem is that when the user creates a new record on the database in my webpage (Mysql) and the system redirects the user to the list of elements created, the new element won't show up until I click again the link option on a section. In other words I want django to show the new results when I redirect the user to the list, here's some fragments of code I'm using:
This is the link where the user clicks to show the list after creating a new object:
<div class="modal-footer">
<div class="col-lg-12 text-right">
Regresar a Listado
</div>
</div>
This is the list where I show the elements from the database:
{% for portal in portal_list %}
<section class="search-result-item">
<a class="image-link" href="/microsite/myportal/view/{{portal.portal_id}}">
<img class="image" src="{% static 'img/template-icon.png' %}">
</a>
<div class="search-result-item-body">
<div class="row">
<div class="col-sm-9">
<h4 class="search-result-item-heading">
No. de Plantilla : {{portal.portal_id}}
</h4>
<p class="description">
ID Terminos: {{portal.terms_id}}
</p>
</div>
<div class="col-sm-3 text-align-center">
<p class="value3 text-gray-dark mt-sm">
{{ randomNumber|random }} visitas
</p>
<p class="fs-mini text-muted">
Por Semana
</p>
<br>
Ver Detalles
</div>
</div>
</div>
</section>
{% endfor %}
Here's my function in views: (The object that comes from MySQL is called myportal_list)
from django.shortcuts import render
from django.http import HttpResponseRedirect
def myportal_list(request, device_serial):
logger.debug("views - myportal_create")
deviceObject = CaptivePortalService.CaptivePortalService().getNetwork(device_serial)
captive_portal_list=""
context = {'device_object': deviceObject}
myportal_list = my_portal.objects.all()
context['portal_list'] = myportal_list
number = []
for i in myportal_list:
number.append(randint(0, 25))
context['randomNumber'] = number
return render(request, URL_MYPORTAL_LIST, context)
And finally there's my list of options, that is actually the only way to show the new data from the database: (even if i press F5 the new data wont show)
<ul id="network-option" class="panel-collapse collapse ">
<li class="">Mi Portal</li>
<!-- <li class="">Configuraciones de Accceso</li> -->
<li class="">Configuraciones de Accceso</li>
<li class="">Promociones</li>
<li class="">Términos de Uso</li>
</ul>
The view function where I create a new record: (Im handling the response with AJAX to be able to handle messages if there's an error or if the form was submitted correctly, in this case the messages are listed in the modal footer that I posted first)
def myportal_create(request, device_serial):
if request.method == "GET":
logger.debug("views - myportal_create")
deviceObject = CaptivePortalService.CaptivePortalService().getNetwork(device_serial)
captive_portal_list=""
context = {'device_object': deviceObject}
promotion_list = promotions.objects.all()
context['promo_list'] = promotion_list
terms_list = use_terms.objects.all()
context['terms_list'] = terms_list
return render(request, URL_MYPORTAL_CREATE, context)
if request.method == "POST":
form = PortalForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect("/microsite/myportal/list/"+ device_serial)
else:
print(form.errors)
Also I have the correct configuration to use MySQL on settings so I don't think that being the problem.
Thanks for the time.
Update multiple documents in django-mongodb with user inputs
I have a form which is meant to update all the price attribute of the objects in the product_details collection of my mongoDB.It is like Bulk price updating feature.I have tried few but finding it difficult.
Please suggest the method to do so in django.
How can I update the price of multiple products using the same form and view?
price.html
<form class="col s12" action="{% url "bulk" %}" method="POST">{% csrf_token %}
<button class="btn waves-effect waves-light" type="submit" name="action">Update<i class="material-icons right">cloud</i>
</button>
{% for r in result %}
<div class="col s6 m7">
<div class="card horizontal">
<div class="card-image" >
<img class ="materialboxed" width="650" src="{{r.ppro}}" style="width:100px;
height:150px;
max-width:100%;
max-height:100%;" >
</div>
<div class="card-stacked">
<div class="card-content">
<p style="font-size:15px;">{{r.ptitle}}<br>{{r.price}}</p>
</div>
<div class="card-action">
<div class="input-field col s4">
<input id="input_text" type="text" name=price value="{{r.price}}" data-length="10">
<label for="input_text">Price</label>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</form>
</div>
views.py
def bulk_price(request):
product_list= user_db.product_details.find({"shop_id":request.session["_id"]})
user_data = user_db.store_details.find_one({"_id":request.session["_id"]})
if product_list is not None:
return render(request,'price.html',{'result':product_list,'status':user_data['status']})
return render(request,'price.html')
mongoDB structure of product_details object
First of all, your input field name should be unique, you can use product id as name -
<input id="input_text" type="text" name="{{r.object_id}}" value="{{r.price}}" data-length="10">
Now, in the Django view, you can iterate through all the post data received from the form and update the database. Here is the code that should work -
def bulk_price(request):
#If request method is POST, update the database
if request.method == "POST":
for key in request.POST: #Iterate through POST variables
value = request.POST[key]
try:
objectId = ObjectId(key)
except Exeption as e:
#The key is not a valid object id, it might be csrfmiddlewaretoken or some other post data
continue
user_db.product_details.update_one(
{'_id': objectId},
{'$set': {'price': value}},
upsert=False
)
#Render the update price page
product_list= user_db.product_details.find({"shop_id":request.session["_id"]})
user_data = user_db.store_details.find_one({"_id":request.session["_id"]})
if product_list is not None:
return render(request,'price.html',{'result':product_list,'status':user_data['status']})
return render(request,'price.html')
Don't forget to import ObjectId():
from bson.objectid import ObjectId
Note: To use MongoDB ObjectID in Django template, you will need a custom template filter. Refer to this answer - https://stackoverflow.com/a/24936772/8039601