Related
I'm using Datatables to display tables in Django on the server side. After searching for a phrase, I have a button ready to save the current table after filtering to Excel. I would like the second button to create a new table in the database and save the same table to it as for Excel.
I don't know how I can send AJAX data to Django and read it in views in such a way that I can query the database.
javascript:
$(document).ready(function () {
function newexportaction(e, dt, button, config) {
var self = this;
var oldStart = dt.settings()[0]._iDisplayStart;
dt.one('preXhr', function (e, s, data) {
data.start = 0;
data.length = 2147483647;
dt.one('preDraw', function (e, settings) {
// Call the original action function
if (button[0].className.indexOf('buttons-excel') >= 0) {
$.fn.dataTable.ext.buttons.excelHtml5.available(dt, config) ?
$.fn.dataTable.ext.buttons.excelHtml5.action.call(self, e, dt, button, config) :
$.fn.dataTable.ext.buttons.excelFlash.action.call(self, e, dt, button, config);
}
dt.one('preXhr', function (e, s, data) {
settings._iDisplayStart = oldStart;
data.start = oldStart;
});
});
});
dt.ajax.reload();
}
$("#tb").DataTable({
"lengthChange": true,
"paging": true,
"searching": true,
"oLanguage": {
"sSearch": "Szukaj:",
},
"language": {
"processing": "Przetwarzanie",
"lengthMenu": "Pokaż _MENU_ elementów",
"info": "Wyświetlono od _START_ do _END_ z _TOTAL_ elementów",
"zeroRecords": "Nie znaleziono pasujących elementów",
"paginate": {
"first": "Pierwsza",
"last": "Ostatnia",
"next": "Kolejna",
"previous": "Poprzednia"
},
"emptyTable": "Brak danych do wyświetlenia",
},
"processing": true,
"serverSide": true,
buttons: [
{
extend: 'excel',
titleAttr: 'Excel',
title: '',
action: newexportaction
},
],
dom: 'B<"clear">lfrtip',
// "destroy": true,
"pageLength": 15,
"ordering": false,
"ajax": {
"url": "paging2/",
"type": "get"
},
});
});
views.py:
def paging2(request):
draw = int (request.GET.get ('draw')) # record the number of operations
start = int (request.GET.get ('start')) # start position
length = int (request.GET.get ('length')) # length of each page
search_key = request.GET.get ('search[value]') # search keyword
order_column = request.GET.get ('order [0] [column]') # sort field index
order_column = request.GET.get ('order [0] [dir]') #Ordering rule: ase / desc
# sql query all data, then do paging, the speed is slow
# if search_key:
# result = query(search_key)
# else:
# result = select_all()
# data = result[start:start+length]
# count = len(result)
# sql for paging, fast
if search_key:
sql_search = "SELECT NAZWA,TEL,NIP,Adres,WWW, EMAIL, Branza, NAZWA_SCRAPERA FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL AND Branza like '%%%s%%'" % search_key
sql_search_len = "SELECT COUNT(*) FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL AND Branza like '%%%s%%'"% search_key
result, count = query(sql_search, sql_search_len)
data = result[start:start + length]
else:
sql = """
SELECT NAZWA,TEL,NIP,Adres,WWW, EMAIL, Branza, NAZWA_SCRAPERA FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL
LIMIT %s OFFSET %s
"""
data = select_by_page(length, start, sql)
# data = select_by_page(start, start + length)
sql_len = "SELECT COUNT(*) FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL"
count = all_count(sql_len)
dic = {
'draw': draw,
'recordsTotal': count,
'recordsFiltered': count,
'data': data,
}
return HttpResponse(json.dumps(dic), content_type='application/json')
I have a model name Ratings and its connected to custom user model through foreignkey relationship.i used javascript to post data and get ratings in the backend by get method using id but the problem is rating process works fine but it just upgrades the current queryset when ever regarded delivery man gets rated. so how do i calculatye the average as it does not store the previous querysets rather its just upgrades the queryset of that particular deliveryman
views.py
def rate_user(request):
if request.method == 'POST':
el_id = request.POST.get('el_id')
val = request.POST.get('val')
print(val)
obj = Ratings.objects.get(id=el_id)
obj.rated_number = val
obj.save()
return JsonResponse({'success':'true', 'score': val}, safe=False)
return JsonResponse({'success':'false'})
models.py
class Ratings(models.Model):
rated_user = models.ForeignKey(User,on_delete=models.CASCADE,blank=True,null=True)
avg_rating = models.CharField(max_length=5,null=True,blank=True,default=0)
# count = models.CharField(max_length=100000,blank=True,null=True,default=0)
rated_number = models.IntegerField(blank=True,null=True,default=0,
validators = [
MaxValueValidator(5),
MinValueValidator(1),
]
)
def __str__(self):
return str(self.pk)
#receiver(post_save, sender=User)
def create_ratings(sender, instance, created, **kwargs):
if created:
if instance.is_delivery_man or instance.is_driver:
Ratings.objects.create(rated_user=instance)
js
<script>
const one = document.getElementById('first')
const two = document.getElementById('second')
const three = document.getElementById('third')
const four = document.getElementById('fourth')
const five = document.getElementById('fifth')
// get the form, confirm-box and csrf token
const form = document.querySelector('.rate-form')
const confirmBox = document.getElementById('confirm-box')
const csrf = document.getElementsByName('csrfmiddlewaretoken')
const handleStarSelect = (size) => {
const children = form.children
console.log(children[0])
for (let i = 0; i < children.length; i++) {
if (i <= size) {
children[i].classList.add('checked')
} else {
children[i].classList.remove('checked')
}
}
}
const handleSelect = (selection) => {
switch (selection) {
case 'first': {
handleStarSelect(1)
return
}
case 'second': {
handleStarSelect(2)
return
}
case 'third': {
handleStarSelect(3)
return
}
case 'fourth': {
handleStarSelect(4)
return
}
case 'fifth': {
handleStarSelect(5)
return
}
default: {
handleStarSelect(0)
}
}
}
const getNumericValue = (stringValue) => {
let numericValue;
if (stringValue === 'first') {
numericValue = 1
}
else if (stringValue === 'second') {
numericValue = 2
}
else if (stringValue === 'third') {
numericValue = 3
}
else if (stringValue === 'fourth') {
numericValue = 4
}
else if (stringValue === 'fifth') {
numericValue = 5
}
else {
numericValue = 0
}
return numericValue
}
if (one) {
const arr = [one, two, three, four, five]
arr.forEach(item => item.addEventListener('mouseover', (event) => {
handleSelect(event.target.id)
}))
arr.forEach(item => item.addEventListener('click', (event) => {
// value of the rating not numeric
const val = event.target.id
let isSubmit = false
form.addEventListener('submit', e => {
e.preventDefault()
if (isSubmit) {
return
}
isSubmit = true
// rate id
const id = e.target.id
// value of the rating translated into numeric
const val_num = getNumericValue(val)
$.ajax({
type: 'POST',
url: '/rate/',
data: {
'csrfmiddlewaretoken': csrf[0].value,
'el_id': id,
'val': val_num,
},
success: function (response) {
console.log(response)
confirmBox.innerHTML = `<h1>Successfully rated with ${response.score} star</h1>`
},
error: function (error) {
console.log(error)
confirmBox.innerHTML = '<h1>Ups... something went wrong</h1>'
}
})
})
}))
}
</script>
Form
{%for i in ratings%}
<a href="#" >Average Ratings: {{i.rated_number}}</a><br><br>
i class="fas fa-vote-yea"></i> Rate this user <br>
<form class="rate-form" action="" method="POST" id={{i.id}}>
{% csrf_token %}
<button type="submit" class="fa fa-star fa-3x my-btn" id="first"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="second"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="third"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="fourth"></button>
<button type="submit" class="fa fa-star fa-3x my-btn" id="fifth"></button>
</form>
{%endfor%}
<br>
<div id="confirm-box"></div>
</div>
Finally waiting for answer from stackoverflow i got tired and started debugging myself and i ended up with an nice mathmetical algorithm to achieve my desired goal and i think i have achieved it except it doest show or take values after decimal. avg are coming with a round shape .
i just changed a field in my model to store previous and now all rating number addition
class Ratings(models.Model):
rated_user = models.ForeignKey(User,on_delete=models.CASCADE,blank=True,null=True)
storing_prev_now_rated_value = models.IntegerField(null=True,blank=True,default=1)
avg_rating = models.IntegerField(null=True,blank=True,default=1)
count = models.IntegerField(blank=True,null=True)
rated_number = models.IntegerField(blank=True,null=True,default=1,
validators = [
MaxValueValidator(5),
MinValueValidator(1),
]
)
def __str__(self):
return self.rated_user.username
and i have changes in my views.py too
def rate_user(request):
if request.method == 'POST':
el_id = request.POST.get('el_id')
val = request.POST.get('val')
print(val)
obj = Ratings.objects.get(id=el_id)
storing_prev_rated_number = obj.rated_number
obj.rated_number = val
obj.count = obj.count + 1
store_count = obj.count
old_addition_values = obj.storing_prev_now_rated_value
obj.storing_prev_now_rated_value = int(old_addition_values) + int(val)
store_storing_prev_now_rated_value = obj.storing_prev_now_rated_value
print(store_storing_prev_now_rated_value)
obj.avg_rating = float(store_storing_prev_now_rated_value // store_count)
j = obj.avg_rating
print(j)
obj.save()
return JsonResponse({'success':'true', 'rated_number': val}, safe=False)
return JsonResponse({'success':'false'})
I recently upgraded to Django 2.2.2 and Python 3.6.8, and my filter_horizontal feature in Django admin has disappeared.
I tried viewing my admin in Chrome incognito mode, as some answers suggest, and I also tried changing verbose_name strings to unicode. However, none of these worked.
Here is an example of a model for which I am attempting to show filter_horizontal. This worked on my app prior to the upgrades.
admin.py
class ResearchAdmin(admin.ModelAdmin):
filter_horizontal = ('criteria', 'state')
def save_model(self, request, obj, form, change):
obj.save()
# Update cache for tasks containing the saved goal
tasks = Task.objects.filter(research__id=obj.pk).values_list('pk', flat=True)
for t in tasks:
cache.delete_many(['task%s' % t, 'task%s_research' % t])
models.py
"""
Clinical Research model
"""
def __str__(self):
return "%s" % (self.name)
name = models.CharField(max_length=50, null=True, blank=True)
type = models.CharField(max_length=50, null=True, blank=True)
cta = models.CharField(max_length=50, null=True, blank=True)
description = models.TextField(null=True, blank=True)
picture = models.ImageField(upload_to='images/%Y/%m/%d', null=True, blank=True, help_text="Upload portrait image for modal study description")
layout = models.CharField(max_length=1, choices=LAYOUT_TYPE, null=False, blank=False, default='r')
criteria = models.ManyToManyField('Answer', blank=True, db_index=True, help_text="Answers required for qualified patients")
required_contact = models.ManyToManyField('ContactField', blank=True, db_index=True, help_text="Contact info for patient to enter")
email = models.EmailField(null=True, blank=True, help_text="Sponsor email for notifying of screened patients")
link = models.URLField(null=True, blank=True)
state = models.ManyToManyField('State', blank=True, help_text="Qualifying states")
lat = models.CharField(max_length=60, null=True, blank=True)
lng = models.CharField(max_length=60, null=True, blank=True)
distance = models.PositiveIntegerField(null=True, blank=True, help_text="Maximum distance from user in miles to show")
class Meta:
verbose_name = u"Research"
verbose_name_plural = u"Research"
There are no error messages, but the filter_horizontal frontend doesn't show up in admin for the criteria and state fields.
##collectstatic##
As #iain-shelvington suggested, this issue may be due to some kind of interference with the cached front-end code required to display the filter_horizontal format. I have tried running in Google Incognito mode, clearing catch, and running collectstatic --clear, and none of these work. Moreover, there aren't any differences between the admin static files pre- and post- upgrade.
#SylvainBiehler pointed out that django_gulp may be overriding collectstatic. I disabled django_gulp and ran ./manage.py collectstatic --clear all admin files are now updated post Django upgrade.
##Comparing admin files pre- and post- Django upgrade##
I was able to spin up a version of my app pre-Django upgrade, and the filter_horizontal capability works in the older version. There are some differences in the construction of the Criteria field from Chrome console:
Old Version (works)
Select element prior to choices:
<select multiple="multiple" class="selectfilter" id="id_criteria" name="criteria">
Javascript after choices:
<script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_criteria", "criteria", 0, "https://xxxxxxx/static/admin/"); });</script>
New Version (broken)
Slightly different select element. No javascript after choices:
<select name="criteria" id="id_criteria" multiple class="selectfilter" data-field-name="criteria" data-is-stacked="0">
This seems to be causing the issue, but I have no idea how to fix it. Any ideas?
##Analyzing admin JS in console##
SelectBox.js loads in the console and declares var = SelectBox = {... which is never called.
SelectFilter.js also loads but the function is never called:
/*
SelectFilter2 - Turns a multiple-select box into a filter interface.
Requires core.js, SelectBox.js and addevent.js.
*/
(function($) {
function findForm(node) {
// returns the node of the form containing the given node
if (node.tagName.toLowerCase() != 'form') {
return findForm(node.parentNode);
}
return node;
}
window.SelectFilter = {
init: function(field_id, field_name, is_stacked, admin_static_prefix) {
if (field_id.match(/__prefix__/)){
// Don't intialize on empty forms.
return;
}
var from_box = document.getElementById(field_id);
from_box.id += '_from'; // change its ID
from_box.className = 'filtered';
var ps = from_box.parentNode.getElementsByTagName('p');
for (var i=0; i<ps.length; i++) {
if (ps[i].className.indexOf("info") != -1) {
// Remove <p class="info">, because it just gets in the way.
from_box.parentNode.removeChild(ps[i]);
} else if (ps[i].className.indexOf("help") != -1) {
// Move help text up to the top so it isn't below the select
// boxes or wrapped off on the side to the right of the add
// button:
from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
}
}
// <div class="selector"> or <div class="selector stacked">
var selector_div = quickElement('div', from_box.parentNode);
selector_div.className = is_stacked ? 'selector stacked' : 'selector';
// <div class="selector-available">
var selector_available = quickElement('div', selector_div, '');
selector_available.className = 'selector-available';
var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name]));
quickElement('img', title_available, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of available %s. You may choose some by selecting them in the box below and then clicking the "Choose" arrow between the two boxes.'), [field_name]));
var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter');
filter_p.className = 'selector-filter';
var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + "_input");
var search_selector_img = quickElement('img', search_filter_label, '', 'src', admin_static_prefix + 'img/selector-search.gif', 'class', 'help-tooltip', 'alt', '', 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name]));
filter_p.appendChild(document.createTextNode(' '));
var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
filter_input.id = field_id + '_input';
selector_available.appendChild(from_box);
var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link');
choose_all.className = 'selector-chooseall';
// <ul class="selector-chooser">
var selector_chooser = quickElement('ul', selector_div, '');
selector_chooser.className = 'selector-chooser';
var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link');
add_link.className = 'selector-add';
var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link');
remove_link.className = 'selector-remove';
// <div class="selector-chosen">
var selector_chosen = quickElement('div', selector_div, '');
selector_chosen.className = 'selector-chosen';
var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
quickElement('img', title_chosen, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name]));
var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
to_box.className = 'filtered';
var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link');
clear_all.className = 'selector-clearall';
from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
// Set up the JavaScript event handlers for the select box filter interface
addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); });
addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); });
addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
SelectBox.init(field_id + '_from');
SelectBox.init(field_id + '_to');
// Move selected from_box options to to_box
SelectBox.move(field_id + '_from', field_id + '_to');
if (!is_stacked) {
// In horizontal mode, give the same height to the two boxes.
var j_from_box = $(from_box);
var j_to_box = $(to_box);
var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); }
if (j_from_box.outerHeight() > 0) {
resize_filters(); // This fieldset is already open. Resize now.
} else {
// This fieldset is probably collapsed. Wait for its 'show' event.
j_to_box.closest('fieldset').one('show.fieldset', resize_filters);
}
}
// Initial icon refresh
SelectFilter.refresh_icons(field_id);
},
refresh_icons: function(field_id) {
var from = $('#' + field_id + '_from');
var to = $('#' + field_id + '_to');
var is_from_selected = from.find('option:selected').length > 0;
var is_to_selected = to.find('option:selected').length > 0;
// Active if at least one item is selected
$('#' + field_id + '_add_link').toggleClass('active', is_from_selected);
$('#' + field_id + '_remove_link').toggleClass('active', is_to_selected);
// Active if the corresponding box isn't empty
$('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0);
$('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0);
},
filter_key_up: function(event, field_id) {
var from = document.getElementById(field_id + '_from');
// don't submit form if user pressed Enter
if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
from.selectedIndex = 0;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = 0;
return false;
}
var temp = from.selectedIndex;
SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
from.selectedIndex = temp;
return true;
},
filter_key_down: function(event, field_id) {
var from = document.getElementById(field_id + '_from');
// right arrow -- move across
if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
var old_index = from.selectedIndex;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
return false;
}
// down arrow -- wrap around
if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
}
// up arrow -- wrap around
if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
}
return true;
}
}
})(django.jQuery);
##INSTALLED APPS##
########## APP CONFIGURATION
INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
DJANGO_APPS = (
'django_gulp',
# Default Django apps:
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Useful template tags:
# 'django.contrib.humanize',
# Python-Social-Auth
'social_django',
# Admin panel and documentation:
'django.contrib.admin',
'django.core.management',
# 'django.contrib.admindocs',
# for task queue
# For django-storages static store on AWS S3
'storages',
# Other django apps
'rest_framework',
)
# Apps specific for this project go here.
LOCAL_APPS = (
'base',
'myproject',
'users',
'campaigns',
)
I copied the static/admin files from Django.contrib using the following command in terminal:
cp -a /Users/username/.virtualenvs/rs/lib/python3.9/site-packages/django/contrib/admin/. /Users/username/Documents/myproject/static/
I then ran collectstatic to upload the files to S3, where they are stored for production. This actually worked, but seems a bit hacky. I must not be doing something right for these files to not update upon Django upgrade.
I have a model in django admin as follows
ChoiceA= (
("on-false","on-false"),
("on-true","on-true"),
)
ChoiceB = (
("always","always"),
("never","never"),
)
id = models.CharField(verbose_name="Field",max_length=32)
type = models.CharField(verbose_name="Expression",max_length=32)
action = models.CharField(max_length=32, choices=x)
Now based on the type entered by the user ie if user enters type = "a" then action's choices should be set to ChoiceA and if user enters type ="b" then action's choices should be set to ChoiceB. How can I achieve this in Django Admin?
Edit:
action_change.js
jQuery(document).ready(function(){
$("#id_type").change( function(event) {
$.ajax({
"type" : "POST",
"url" : "/action_choices/",
"dataType" : "json",
"cache" : false,
"error" : alert("hello"),
"success" : function(json) {
$('#id_action >option').remove();
for(var j = 0; j < json.length; j++){
$('#id_action').append($('<option></option>').val(json[j][0]).html(json[j][1]));
}
}
});
});
});
You can achieve it using Ajax and jQuery:
models.py:
type = models.CharField(verbose_name="Expression",max_length=32)
action = models.CharField(max_length=32, choices = (('', ''), ))
admin.py:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('type', )
class Media:
js = ['/static/js/action_change.js']
admin.site.register(MyModel, MyModelAdmin)
urls.py:
url(r'^action_choices/', 'myproject.myapp.views.action_choices'),
views.py:
def action_choices(request):
action_list = []
ChoiceA = ("on-false", "on-true")
ChoiceB = ("always", "never")
action_type = request.GET.get('action_type')
if str(action_type).lower() == 'a':
choices = ChoiceA
elif str(action_type).lower() == 'b':
choices = ChoiceB
else:
choices = ()
[action_list.append((each,each)) for each in choices]
json = simplejson.dumps(action_list)
return HttpResponse(json, mimetype='application/javascript')
Create the file action_change.js with following content in your static folder and define correct path in class Media of ModelAdmin.
action_change.js
(function($){
$(function(){
$(document).ready(function() {
$('#id_type').bind('keyup', type_change);
$('#id_action >option').show();
});
});
})(django.jQuery);
// based on the type, action will be loaded
var $ = django.jQuery.noConflict();
function type_change()
{
var action_type = $('#id_type').val();
$.ajax({
"type" : "GET",
"url" : "/action_choices/?action_type="+action_type,
"dataType" : "json",
"cache" : false,
"success" : function(json) {
$('#id_action >option').remove();
for(var j = 0; j < json.length; j++){
$('#id_action').append($('<option></option>').val(json[j][0]).html(json[j][1]));
}
}
})(jQuery);
}
This should work fine for the scenario you asked. And I'm giving my suggestion below:
models.py
type = models.CharField(verbose_name="Expression",max_length=32, choices = (('a', 'a'), ('b', 'b'), ))
action = models.CharField(max_length=32, choices = (('', ''), ))
action_change.js (line 5)
$('#id_type').bind('change', type_change);
You would have to initialize the action field with all possible choices, or Django will complain that a choice that didn't previously exist isn't a valid choice.
My recommendation would be to initialize the field with all of the possible choices, and use JavaScript to toggle the visibility of the choices, depending on the value of type. There are a few plugins around that will handle dynamic fields in Django admin, but most that I've seen deal with ForeignKey or ManyToMany fields that need to do lookups.
You're probably best off just adding some JavaScript to your admin form via the Media meta class and handling it yourself.
I want to fill a text input in my form using an autocomplete widget that I have created using jquery ui. Everything works exactly how I want to, except when the form is submitted.
The problem is that when I submit the form, the text input is automatically reseted (I don't know why) and after that, the page reloads saying that the field is required (just validation working how it's supposed to). Of course, if it didn't reset the field everything would go fine.
I dont know if my select event of the autocomplete is working fine, here is the code:
select : function (e, ui) {
// I create a new attribute to store de database primary key of this option. This is
// usefull later on.
$('#%(input_id)s').attr('itemid', ui.item.real_value);
// I set the input text value's.
$('#%(input_id)s').val(ui.item.label);
}
Here is the full code of the autocomplete:
class AutocompleteTextInputWidget (forms.TextInput):
def media(self):
js = ("/js/autocomplete.js", "pepe.js")
def __init__(self, source, options={}, attrs={}):
self.options = None
self.attrs = {'autocomplete': 'off'}
self.source = source
self.minLength = 1
self.delay = 0
if len(options) > 0:
self.options = JSONEncoder().encode(options)
self.attrs.update(attrs)
def render(self, name, value=None, attrs=None):
final_attrs = self.build_attrs(attrs)
options = ''
if value:
final_attrs['value'] = escape(value)
if isinstance(self.source, list) or isinstance(self.source, tuple):
# Crea un Json con las opciones.
source = '['
for i in range(0, len(self.source)):
if i > 0:
source += ', '
source += '"' + self.source[i] + '"'
source += ']'
options = u'''
delay : %(delay)d,
minLength : %(minlength)s,
source : %(source)s
''' % {
'delay' : self.delay,
'minlength' : self.minLength,
'source' : source
}
elif isinstance(self.source, str):
options = u'''
delay : %(delay)d,
minLength : %(minlength)s,
source : function (request, response) {
if ($(this).data('xhr')) {
$(this).data('xhr').abort();
}
$(this).data('xhr', $.ajax({
url : "%(source_url)s",
dataType : "json",
data : {term : request.term},
beforeSend : function(xhr, settings) {
$('#%(input_id)s').removeAttr('itemid');
},
success : function(data) {
if (data != 'CACHE_MISS') {
response($.map(data, function(item) {
return {
label : item[1],
value: item[1],
real_value : item[0]
};
}));
}
},
}))
},
select : function (e, ui) {
$('#%(input_id)s').attr('itemid', ui.item.real_value);
$('#%(input_id)s').val(ui.item.label);
}
''' % {
'delay' : self.delay,
'minlength' : self.delay,
'source_url' : self.source,
'input_id' : final_attrs['id'],
}
if not self.attrs.has_key('id'):
final_attrs['id'] = 'id_%s' % name
return mark_safe(u'''
<input type="text" %(attrs)s/>
<script type="text/javascript">
$("#%(input_id)s").autocomplete({
%(options)s
});
</script>
''' % {
'attrs' : flatatt(final_attrs),
'options' : options,
'input_id' : final_attrs['id']
})
Tip: If I write some text without selecting it from the autocomplete, it still fails.
Another tip: If I set the field as optional it arrives to the view empty.
What should I do to make this work when I submit the form??? I have spent hours trying to
make this work. How can I make the form to recognise that I have allready filled that field?
Here is the code of the form:
test = forms.CharField(label = "autotest", widget = AutocompleteTextInputWidget('/myjsonservice'))
This is the rendered html:
<input type="text" autocomplete="off" id="id_test"/>
<script type="text/javascript">
$("#id_test").autocomplete({
delay : 0,
minLength : 0,
source : function (request, response) {
if ($(this).data('xhr')) {
$(this).data('xhr').abort();
}
$(this).data('xhr', $.ajax({
url : "/myjsonservice",
dataType : "json",
data : {term : request.term},
beforeSend : function(xhr, settings) {
$('#id_test').removeAttr('itemid');
},
success : function(data) {
if (data != 'CACHE_MISS') {
response($.map(data, function(item) {
return {
label : item[1],
value: item[1],
real_value : item[0]
};
}));
}
},
}))
},
select : function (e, ui) {
$('#id_test').attr('itemid', ui.item.real_value);
$('#id_test').val(ui.item.label);
}
});
</script>
Finally found the answer, the problem was that the "name" attribute wasn't rendered. Hence, the field could't get to the view as part of the request.
The final code of the autocomplete widget ended up like this:
class AutocompleteTextInputWidget (forms.TextInput):
def media(self):
js = ("/js/autocomplete.js", "pepe.js")
def __init__(self, source, options={}, attrs={}):
self.options = None
self.attrs = {'autocomplete': 'off'}
self.source = source
self.minLength = 1
self.delay = 0
if len(options) > 0:
self.options = JSONEncoder().encode(options)
self.attrs.update(attrs)
def render(self, name, value=None, attrs=None):
final_attrs = self.build_attrs(attrs)
options = ''
if value:
final_attrs['value'] = escape(value)
if isinstance(self.source, list) or isinstance(self.source, tuple):
# Crea un Json con las opciones.
source = '['
for i in range(0, len(self.source)):
if i > 0:
source += ', '
source += '"' + self.source[i] + '"'
source += ']'
options = u'''
delay : %(delay)d,
minLength : %(minlength)s,
source : %(source)s
''' % {
'delay' : self.delay,
'minlength' : self.minLength,
'source' : source
}
elif isinstance(self.source, str):
options = u'''
delay : %(delay)d,
minLength : %(minlength)s,
source : function (request, response) {
if ($(this).data('xhr')) {
$(this).data('xhr').abort();
}
$(this).data('xhr', $.ajax({
url : "%(source_url)s",
dataType : "json",
data : {term : request.term},
beforeSend : function(xhr, settings) {
$('#%(input_id)s').removeAttr('itemid');
},
success : function(data) {
if (data != 'CACHE_MISS') {
response($.map(data, function(item) {
return {
label : item[1],
value: item[1],
real_value : item[0]
};
}));
}
},
}))
},
select : function (e, ui) {
$('#%(input_id)s').attr('itemid', ui.item.real_value);
$('#%(input_id)s').val(ui.item.label);
}
''' % {
'delay' : self.delay,
'minlength' : self.delay,
'source_url' : self.source,
'input_id' : final_attrs['id'],
}
if not self.attrs.has_key('id'):
final_attrs['id'] = 'id_%s' % name
return mark_safe(u'''
<input type="text" name="%(name)s" %(attrs)s/>
<script type="text/javascript">
$("#%(input_id)s").autocomplete({
%(options)s
});
</script>
''' % {
'attrs' : flatatt(final_attrs),
'options' : options,
'input_id' : final_attrs['id'],
'name' : name
})
If someone knows how to improve this messy code it would be nice.
If someone knows about a nice widget documentation for django 1.4 (Other than the oficial, which sucks by the way) it would be nice too.
Bye, good coding everyone!!!