Django admin onclick function - django

I created the following features on the Django admin list!
admin.py
class LawyerAdmin(admin.ModelAdmin):
list_display = ('lawyer_idx', 'show_firm_url', 'lawyer_name', 'lawyer_birthday', 'lawyer_mobile', 'lawyer_license_num', 'like_cnt', 'recommend_name', 'register_date', 'status', 'lawyer_agent',)
list_filter = ['lawyer_status']
def status(self, obj):
if obj.lawyer_status == "W":
html = "Awaiting certification<br> <input type='button' value='certification' onclick='lawyer_confirm({0})'>"
return format_html(html, obj.lawyer_idx)
admin.site.register(Lawyer, LawyerAdmin)
When I click the On-Click button, I want to run the following script, but I don't know how. Please help me.
1. I want to implement the on-click function.
2. How to run a script
<script>
function lawyer_confirm(lawyer_idx) {
if (confirm('Certified?') == true) {
$.ajax({
url: '/admin/lawyer/view/lawyer_confirm',
data: {
lawyer_idx : lawyer_idx
},
dataType: "json",
type: 'post',
success: function(result){
alert(result.msg);
if (result.code == '0' ) {
location.href = result.retURL;
}
}
});
}
}
</script>

To add media to the admin you can simply add it to the meta class Media of your admin class, e.g.:
class FooAdmin(admin.ModelAdmin):
# regular stuff
class Media:
js = (
'//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js', # jquery
'app/js/myscript.js', # app static folder
)
You can add custom logic to select HTML element and do what you want in JS.

Related

Save Django Model when list_editable fields are changed in Django Admin change_list view

I have a Django Model which I'm trying to update from the Admin change_list view when a field that is listed in list_editable has a change event fired. Right now the only way to save the updates is by clicking a "Save" button and then the page reloads. I don't want the page to reload at all. I just want it to update the model asynchronously.
For example I have the following model.
class Example(models.Model):
name = models.CharField()
hide = models.BooleanField(default=False)
In the Admin Form I have
class ExampleAdmin(admin.ModelAdmin):
list_display = [
"name",
"hide"
]
list_editable = [
"name",
"hide"
]
When viewing the change list for Examples now I will see the values listed out with an editable text input for name and a checkbox for hide.
I would like to listen for events on those inputs and when they are fired send an async request to update the specific model.
UPDATE
This is what I have right now.
window.addEventListener("load", function() {
(function($) {
$(".field-name .vTextField").on("change",function (event) {
$('#changelist-form').submit()
})
$('#changelist-form').submit(function(event) { // On form submit event
$.ajax({ // create an AJAX call...
data: $(this).serialize(), // get the form data
type: "POST", // GET or POST
url: $(this).attr('action'), // the file to call
success: function(response) { // on success..
console.log(response)
},
error: function(e, x, r) { // on error..
console.log(e)
}
});
event.preventDefault()
return false;
});
})(django.jQuery);
})
I can see this firing in the network requests and can verify that the serialized data being sent is updated. The response is a 200, but when I refresh the page the data has not been updated.
UPDATE
At this point I'm giving up on sending this data to Django and will be sending the serialized data to a custom endpoint and parse through and save the data in a custom save function.
Within your Admin Model add the following
class ExampleAdmin(admin.ModelAdmin):
class Media:
js = ("js/admin/example.js")
list_display = [
"name",
"hide"
]
list_editable = [
"name",
"hide"
]
example.js
window.addEventListener("load", function() {
(function($) {
$(".field-name .vTextField").on("change",function (event) {
submit()
})
function getFormData($form){
var unindexed_array = $form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
function submit(){
$.ajax({
data: getFormData($('#changelist-form')),
type: "POST",
url: '/api/save-example',
success: function(response) {
console.log(response)
},
error: function(e, x, r) {
console.log(e)
}
});
}
})(django.jQuery);
})
I'm using Django Rest Framework so I created a POST endpoint, but you could also do this in a Django View that is listening for a POST.
When you execute the post Django will send the data categorized by the row number and field name, like so.
form-0-id : 1
form-0-name: example-name
form-0-hide: false
form-0-example_parent: 100
form-1-id : 2
form-1-name: example-name-two
form-1-hide: true
form-1-example_parent: 200
#api_view(["POST"])
def save_example(request):
try:
total_rows = int(request.POST.get("form-TOTAL_FORMS"))
prefix = "form-"
form_fields = {
"id": "id",
"name": "name",
"hide": "hide",
"example_parent": "example_parent_id",
}
for x in range(total_rows):
item = {}
for key, val in form_fields.items():
post_field = prefix + str(x) + "-" + key
field = val
if key == "id":
id = request.POST.get(post_field)
else:
item[field] = request.POST.get(post_field)
Example.objects.filter(id=id).update(**item)
return Response(status=status.HTTP_200_OK)
except Exception as e:
return Response(status=status.HTTP_404_NOT_FOUND)
Note
total_rows = int(request.POST.get("form-TOTAL_FORMS")) which lets you know how to loop through all the form data that is passed back.
This doesn't take different values into consideration. (i.e. Checkboxes will return an on value but no value if not checked.
You will need to update your save_example function accordingly.

Multiple Django subforms displayed with ajax in a main form but data not registered when subforms are submited (without error)

EDIT #1
well maybe because it is not clear in my head
and your are right saying Django forms are not intented to be used with ajax that why I wonder if there is way way doing what I want using Django forms without losing power of Django forms validation
I will try to clear my demand
considering this UI below
When user reach this "patient page" = main form (image 1), he can select "Inclusion form = subform (INCL-001 for instance) in the menu
I want the corresponding form to be displayed without refresh the page ; that why I have used ajax to display the corresponding form
If the corresponding form does not exist, it is a CREATION and a blank "Inclusion form" is displayed : user can enter data and save
If the corresponding form already exist, it is a UPDATE and a prefilled "Inclusion form" is displayed : user can update data and save or delete the form
what would be an elegant way of doing that?
I have a main form (patient.html) and I want to display different subforms (inclusion.html for instance) with ajax in this form.
I manage to display a subform when user click on a button in the main form
But, data are not stored when submitted and it is quite normal as no view is linked to this subform
I have read about this in different post but I miss the concept how to...
I thinks it is a common task with web so probably Django as an easy way to do it?
thanks for advices
urls.py
app_name='ecrf'
urlpatterns = [
path('', views.index, name='index'),
path('patient/<pk>', views.patient, name='patient'),
path('sub_form', views.sub_form, name='sub_form'),
]
views.py
def patient(request,pk):
patient = {"id":1,"num":'A-001',"dat":'Mon 11, novembre 2020',"arm":1,"doc":'Slater'}
return render(request, 'ecrf/patient.html', {'patient':patient})
def sub_form(request):
if request.is_ajax():
return JsonResponse(
{
'html_inclusion': render_to_string(
'ecrf/inclusion.html',
{
'form': InclusionForm,
'language': request.session.get('language')
},
request=request
),
},
status=200
)
js
$(document).ready(function () {
...
$("#add").on("click",function (event) {
var csrftoken = getCookie('csrftoken');
$.ajax({
type: "POST",
url: '/ecrf/sub_form',
data: {
csrfmiddlewaretoken: csrftoken,
},
dataType: 'html',
success: function (data) {
$("#sub_form").children().remove();
obj = JSON.parse(data);
$("#sub_form").append(obj.html_inclusion);
},
error: function (data) {
console.log('error');
}
});
});
$("body")
.on("click", '#inclusion', function (event) {
console.log('inclusion click')
})
});

Show loading gif until the django view performs the data processing and renders the template with this data

I have a django project where the page has multiple nav links representing different agents. On clicking any nav link, the urls.py redirects to nav specific view and the view needs to perform some processing to get the data needed to render the template. However as this is syncrhonous rendering it takes a long while to load data (in the order of 15-20s).
Below is my urls.py:
from django.urls import path
from . import views
app_name = 'agent'
urlpatterns = [
path('agent1/', views.agent1, name='agent1'),
path('agent2/', views.agent2, name='agent2'),
path('agent3/', views.agent3, name='agent3'),
path('agent4/', views.agent4, name='agent4'),
]
My views method looks as below:
def agent1(request):
agent_data = Agent1.objects.all()
agent_details = get_agent_details(agent_data)
return render(request, 'manager/data.html', {'agent_data': agent_data, 'agent_details': agent_details})
I am using the {{ agent_data.name }}, {{ agent_data.code }}, {{ agent_data.qty }} and {{ agent_data.price }} along with data from agent_details dictionary in my html to populate a table's rows. How should I change my view method, so that it loads the data via AJAX (javascript) in order to show a loading gif in the meantime and also provide me the data so that I can populate the table. Could someone help me with the Ajax code and the steps as I am new to this technology and not finding any help going through the online tutorials.
So for this to work with ajax, you'll need some javascript in manager/data.html which knows the url to fetch data from.
As an example, I've got an ajax setup which checks a given email address isn't already in use;
(function($) {
$(document).ready(function() {
var validateEmailURL = $section_user_signup.data('ajax-email-url');
function validateEmailUnique() {
var valid = true;
clearError($email);
// Fetch unique status of the provided email
$.ajax({
async: false,
url: validateEmailURL,
method: 'POST',
type: 'POST',
dataType: 'json',
data: {
'email': $email.val(),
'csrftoken': $form.find('input[name="csrfmiddlewaretoken"]').val()
},
success: function (response) {
valid = true;
},
error: function (response) {
setError($email, response["responseJSON"]["error"]);
valid = false;
}
});
return valid;
}
});
})(window.jQuery);
This javascript uses the data attribute of a div for the URL to check;
<div data-ajax-email-url="{% url 'account_ajax_validate_email' %}">
The view which the ajax call goes to looks like this;
def ajax_check_email_unique(request, *args, **kwargs):
"""
Return an JsonResponse to identify if an email is unique.
"""
if not request.is_ajax():
return HttpResponseBadRequest()
if request.is_ajax and request.method == "POST":
email = request.POST.get('email')
if email_address_exists(email):
return JsonResponse(
{
"error":
"Email address already exists. Click "
f"here "
"to login"
},
status=400
)
return JsonResponse(
{"email": email},
status=200
)
# some error occurred
return JsonResponse({"error": ""}, status=400)
The important thing for any view which will be used by javascript is that you return a JsonResponse.
So if I was you, I'd setup a new view for ajax, and that makes your existing one really simple;
def agent1_ajax(request):
agent_data = Agent1.objects.all()
agent_details = get_agent_details(agent_data)
return JsonResponse({
"agent_data": agent_data, "agent_details": agent_details
}, status=200)
def agent1(request):
return render(request, 'manager/data.html', {})
And as far as a loading gif goes, you'd need an element that contains the gif and then you can bind to the ajax event to show/hide;
$(document).ajaxStart(function() {
$("#loading").show();
});
$(document).ajaxStop(function() {
$("#loading").hide();
});

Connecting django signal to a button click

I'm trying to use a Django signal to toggle a model's boolean value to False from True when a button is clicked. I think that the Django sender would be the button click and the receiver would be the function that changes the boolean value. I'm not sure how to write out a button click as a signal sender however.
Here is my model with the Boolean value.
From models.py:
class User(AbstractUser):
is_blue = models.BooleanField(default=False)
Any thoughts on a what a signal would look like that take a button click and changes this value to True?
To change your model field, you need to create a separate function for it in your views, e.g.:
from django.http import JsonResponse
from xxx.models import User
def ajax_change_user(request):
is_blue= request.GET.get('is_blue', False)
# Get your User model
user = User.objects.get(pk=user_id)
try:
is_blue = True
user.save()
return JsonResponse({"success": True})
except Exception as e:
return JsonResponse({"success": False})
return JsonResponse(data)
Then you need to add a url for this view:
path('ajax/change_user', views.ajax_change_user, name='ajax_change_user')
Then in your html, you need to add a tag with the necessary class, e.g.:
{% csrf_token %}
Change
And then finally the javascript with ajax call part:
<script>
$(".change-user").on('click', function () {
var user_id = $(this).data('userid);
var is_blue = False # say you start with the status being false
$.ajax({
url: '/ajax/change_user/',
data: {
'csrfmiddlewaretoken': $('input[name="csrfmiddlewaretoken"]').val(),
'is_blue': is_blue
'user_id': user_id
},
dataType: 'json',
success: function (data) {
if (data.success) {
alert("ajax call was successful.");
is_blue = True # if button is clicked, make 'is_blue' True
}else{
alert("ajax call was not successful.");
}
}
});
});
</script>
This is just an example to get you started, you would need to alter it to suit your needs. You could for example add an if statement before the ajax call to check if is_blue is true or not, so that you can toggle the value.

How do I add a custom button next to a field in Django admin?

I have a Client model, which includes a field for a client API key.
When adding a new client in Django Admin, I'd like to have a button next to the API field to generate a new key (i have the method for this). The field will then be updated with the key once generated.
How can I add this button next to the field? Should I use a custom widget?
In my case I am making an API call with a button I create so I'll throw in how I did that too. Ofcourse your button can do whatever you like.
First, in your model create a function that will output your button. I will use my example, i.e. models.py:
class YourModel(models.Model):
....
def admin_unit_details(self): # Button for admin to get to API
return format_html(u'<a href="#" onclick="return false;" class="button" '
u'id="id_admin_unit_selected">Unit Details</a>')
admin_unit_details.allow_tags = True
admin_unit_details.short_description = "Unit Details"
I then added the field as readonly and added it to the fieldsets, note you can only have either fields or fieldsets defined on the model admin. I aslo added media to overwrite some css and also added the js for where the ajax call will be made, admin.py:
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
list_display = ('id', 'agent', 'project', 'completed_date', 'selected_unit', 'is_accepted',
'get_lock_for_admin', 'status')
fields = ('agent', 'project', 'completed_date', 'selected_unit', 'is_accepted',
'lock', 'status')
readonly_fields = ('admin_unit_details', )
...
class Media:
js = ('admin_custom/js/myjs.js',) # in static
css = {'all': ('admin_custom/css/mycss.css', )}
I also wanted to note that I passed the API address and header through the Form, but you can use the right header/password in the code. I just keep mine all in one place (settings.py), forms.py (optional):
from settings import API_ADDRESS, API_HEADER
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(WorksheetForm, self).__init__(*args, **kwargs)
self.fields['selected_unit'].widget = forms.Select(choices=get_worksheet_unit_choice_list(self.instance.id),
attrs={'api_address': API_ADDRESS, 'api_header': API_HEADER})
....
Lastly here is a look at my js, as referenced by my admin Media class, it is in admin_custom/js/myjs.js:
This is similar to adding an admin image, see here. Also search for allow_tags attribute in this django doc, it shows a good example.
// Make sure jQuery (django admin) is available, use admin jQuery instance
if (typeof jQuery === 'undefined') {
var jQuery = django.jQuery;
}
var unit_information = {};
jQuery( document ).ready(function() {
jQuery('#id_admin_unit_selected').click( function() {
//get the data from id_selected_unit, from the api_header api_address attributes
var unit_select = jQuery('#id_selected_unit');
var header = unit_select.attr('api_header');
var address = unit_select.attr('api_address');
var selected_unit = unit_select.val();
if (header && address && selected_unit){
var unit_address = address + '/units/' + selected_unit
get_unit(header, unit_address)
}
else{
// if can't connect to api, so hide
jQuery('.field-admin_unit_details').hide();
}
});
});
function get_unit(header, address){
jQuery.ajax
({
type: "GET",
url: address,
dataType: 'json',
headers: {
"Authorization": header
},
success: function (result) {
//TODO: output this in a modal & style
unit_information = JSON.stringify(result);
alert(unit_information)
},
error: function(xhr, textStatus, errorThrown) {
alert("Please report this error: "+errorThrown+xhr.status+xhr.responseText);
}
});
}
This outputs it in an alert, you can also log it to the console or define your own modal / style for it.
Hope this helps, Cheers!