Cascading Select Boxes in Admin - javascript

I have the following code to implement a cascading select boxes (as the field contract_mod is OneToOneField I can't use django-smart-selects or django-ajax-select).
When I'm creating a fieldset with the fields that I want to be shown, I have to put the contracts_from_selected in order to see the results in the admin interface (since contract_mod remain disabled after applying the code.).
fieldsets = [
[ None,
{
"fields" : [
("contracts_from_selected")
]
}
]
So I guess I should copy to another field the value of contracts_from_selected in order to be used in the fieldset.
Any suggestion?
models
class Person(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
def get_name(self):
return self.name
class Contract(models.Model):
person = models.ForeignKey(Person) #person hired
contract_mod = models.OneToOneField('self', blank = True, null = True)
contract_name = models.CharField(max_length=20) #just for testing
def __unicode__(self):
return self.get_name() + " " +self.contract_name
def get_name(self):
return self.person.get_name() #to make sure you get the person name in the admin
def contract_mod_name(self):
if self.contract_mod:
return self.contract_mod.contract_name
else:
return ""
admin
class SelectField(forms.ChoiceField):
def clean(self, value):
return value
class ContractForm(forms.ModelForm):
contracts_from_selected = SelectField()
class Meta:
model = Contract
widgets = { 'contract_mod' : forms.widgets.Select(attrs={'hidden' : 'true'}) }
class ContractAdmin(admin.ModelAdmin):
form = CForm
list_display = ('contract_name','get_name','contract_mod_name')#what you like
def save_model(self, request, obj, form, change):
if request.POST.get('contracts_from_selected'):
obj.contract_mod=Contract.objects.get(id=int(request.POST.get('contracts_from_selected')))
obj.save()
change_form
$(function () {
$("#id_person").change(function () {
var options = $("#id_contract_mod option").filter(function () {
return $(this).html().split(" ")[0] === $("#id_person option:selected").html();
}).clone();
$("#id_contracts_from_selected").empty();
$("#id_contracts_from_selected").append(options);
});
});

Related

How hide/show a field upon selection of a radio button in django admin?

models.py
from django.db import models
from django.contrib.auth.models import User
STATUS_CHOICES = ((1, 'Accepted'),(0, 'Rejected'),)
class Leave(models.Model):
----
----
----
----
status = models.IntegerField(choices=STATUS_CHOICES, default = 0)
reason_reject = models.CharField(('reason for rejection'),max_length=50, blank=True)
def __str__(self):
return self.name
admin.py
from django.contrib import admin
from .models import Leave
#admin.register(Leave)
class LeaveAdmin(admin.ModelAdmin):
------
------
-----
class Media:
js = ('/static/admin/js/admin.js')
-
admin.js
(function($) {
$(function() {
var reject = document.getElementById('id_status_0')
var accept = document.getElementById("id_status_1")
var reason_reject = document.getElementById("id_reason_reject")
if (accept.checked == true){
reason_reject.style.display = "none"
}
else{
reason_reject.style.display = "block"
}
});
})(django.jQuery);
Now I have imported the file in the admin.py, How do I trigger the jQuery function such that it works.
update-
the function works but, I need to reload the page to make the field appear and disappear. I want some thing equivalent to 'on-click' event in HTML. I have no idea about Javascript.
$(function() {
$('input[name="status"]').on('click', function() {
if ($(this).val() == '0') {
$('#id_reason_reject').show();
}
else {
$('#id_reason_reject').hide();
}
});
})

Rails field values not recovered correctly the first time

I'm having a little problem with my rails app, I've created a cascade city selection system, we first select the country and then I make an internal call to receive the state json of the states linked to the country and then once the state is selected, I do the same for the city, everything works fine except for a small bug.
When I load the page for the first time, I can change the city and it saves properly. on the other hand if I change country, state and city, then the country is safeguarded as it should be, the state too but not the city. when the page and returned after saving the city and is always the 1st of the list and it is only at this moment that if I change only the city it is saved well.
On the ruby side, I put a byebug in a before_update and I noticed that in the first case the one where the city is badly saved, it's the index of the city that I receive and not the full name of the city, whereas in the other case when it saves itself properly it's the name of the city that I get.
side js I recover the value of the dropdown in an onchange event that I send in console, and in any case it is the name of the city that appears.
person_settings.js
// This is executed when contry_select is changed
$('#person_country_of_birth').on('change', function(event) {
var stateInput = $("#person_state_of_birth");
var country_code = $('#person_country_of_birth :selected').val();
var sendDatas = { "country_code": country_code };
getDatas(sendDatas, stateInput, "update_states");
});
// This is executed when state_select is changed
$('#person_state_of_birth').on('change', function(event) {
var cityInput = $("#person_city_of_birth");
var country_code = $('#person_country_of_birth :selected').val();
var state_code = $('#person_state_of_birth :selected').val();
var sendDatas = { "country_code": country_code, "state_code": state_code};
getDatas(sendDatas, cityInput, "update_cities");
});
// Check value when change (test)
$('#person_city_of_birth').on('change', function(e) {
console.log(this.options[e.target.selectedIndex].text)
// $('#person_city_of_birth').text(this.options[e.target.selectedIndex].text);
});
// This function send selected data to get cascading response
function getDatas(sendDatas, inputTarget, urlAction){
var url = window.location.origin + "/dynamic_select/"
$.ajax({
type: "GET",
url: url + urlAction,
dataType: "json",
data: sendDatas,
success: function(response){
if(getType(response) == "[object Array]"){
console.log(response)
console.log(sendDatas)
appendStateData(response, inputTarget);
}
else if(getType(response) == "[object Object]"){
appendCityData(response, inputTarget);
}
},
error: function(resp) {
alert(resp)
console.log(resp)
}
});
};
// Append states datas on state_select
function appendStateData(datas, state_input){
state_input.empty();
$.each(datas, function(index, value) {
state_input.append("<option value="+index+">"+value+"</option>");
});
};
// Append cities datas on city_select
function appendCityData(datas, city_input){
city_input.empty();
$.each(datas, function(index, value) {
city_input.append("<option value="+index+">"+value+"</option>");
});
};
// This function check the type of response
// State datas is an object
// City datas is an array
function getType( obj ){
return Object.prototype.toString.call(obj).toString();
};
contact.haml
.row
.col-6
.inline-label-container
= form.label :spoken_languages, #spoken_languages_title
%span.alert-box-icon
= icon_tag("information", ["icon-fix"])
%small
= t("settings.profile.spoken_languages_description")
= form.select(:spoken_languages, #spoken_languages_datas.each { |p| [p] }, {prompt: 'Select a language'}, {multiple: true, size: #spoken_languages_datas.count, style: 'height: 180px;'})
.col-3
= form.label :birth_date, t("settings.profile.birth_date")
= form.date_field :birth_date, :class => "date_field", :maxlength => "10", paceholder: "30/12/2018", style: "height: 40px; width: 250px;"
.col-3
= form.label :nationality, t("settings.profile.nationality")
= form.select(:nationality, #nationality_datas)
.col-3
= form.label :country_of_birth, t("settings.profile.country_of_birth")
= form.country_select :country_of_birth, priority_countries: eu_countries_codes, include_blank: false
.col-3
= form.label :state_of_birth, t("settings.profile.state_of_birth")
= form.select(:state_of_birth, CS.states(target_user.country_of_birth).each { |sym, state| [sym, state]}.to_a.map {|arr| arr.reverse!})
.col-6
= form.label :city_of_birth, t("settings.profile.city_of_birth")
= form.select(:city_of_birth, CS.cities(target_user.state_of_birth, target_user.country_of_birth))
It solved, the problem came from the fact that for cities it was an array and not a hash that I received from the hit the index was used as a value, I added a javascript method that does the conversion for me after I received the answer and everything works.
// This function covert array into object
// and set keys == value ex : {lisboa: "lisboa"}
function toObject(arr) {
console.log(arr)
var rv = {};
for (var i = 0; i < arr.length; ++i)
rv[arr[i]] = arr[i];
console.log(rv)
return rv;
}

JavaScript in embedded forms

Let's say I have an entity User.
The User can have many Address.
In Address There's 2 fields :
Country
State
What I want :
When the user create his account, he can add as many address as he
wants.
When the user select a Country, it populate the State field based on the country.
What I've done :
UserType :
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('address', 'collection', array(
'type' => new AddressType(),
'allow_add' => true,
'allow_delete' => true))
//...
}
The Javascript to add address (which is working : based on symfony doc):
<script type="text/javascript">
$(document).ready(function() {
var $container = $('div#mybundle_user_address');
var $addLink = $('Add address');
$container.append($addLink);
$addLink.click(function(e) {
addAddress($container);
e.preventDefault();
return false;
});
var index = $container.find(':input').length;
$container.children('div').each(function() {
addDeleteLink($(this));
});
function addAddress($container) {
var $prototype = $($container.attr('data-prototype').replace(/__name__label__/g, 'Address n°' + (index+1))
.replace(/__name__/g, index));
addDeleteLink($prototype);
$container.append($prototype);
index++;
}
function addDeleteLink($prototype) {
$deleteLink = $('Delete');
$prototype.append($deleteLink);
$deleteLink.click(function(e) {
$prototype.remove();
e.preventDefault();
return false;
});
}
});
</script>
Ok, but now, How do I add I call a .change() on the select created after clicking on "add address" ?
Because if you add many address, divs will have this name :
#mybundle_user_address_0_country
#mybundle_user_address_1_country
#mybundle_user_address_2_country
...
So how do I select the div to have a .change() call of these divs ? And where do I put the javascript ? Inside the first javascript when I add divs ? Or outside ?

Django: How to refresh table after filtering without refreshing the whole page?

The root of the issue is how to refresh the filtered table dynamically, without refreshing the whole page.
I am almost novice in python/html/css, so please make some comments like for a newbie [Thanks].
After some research on StackOverFlow, I found that it could be made with js, but I have almost no experience with js and I don't know how to use it in Django.
Is there any possibility using only Django tools? And how efficient it would be?
Maybe you can provide some examples of resolving the issue.
Here is the model:
class Player(models.Model):
last_name = models.CharField(
null=True,
blank=True,
max_length=255,
verbose_name="прізвище"
)
first_name = models.CharField(
null=True,
blank=True,
max_length=255,
verbose_name="ім'я"
)
city = models.ForeignKey(
City,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="місто"
)
rating = models.PositiveIntegerField(
null=True,
blank=True,
verbose_name="рейтинг"
)
rank = models.ForeignKey(
Rank,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="ранг"
)
local_rank = models.ForeignKey(
LocalRank,
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name="розряд"
)
def __str__(self):
if self.last_name and self.first_name:
return self.last_name + ' ' + self.first_name
elif self.egd_last_name and self.egd_first_name:
return self.egd_last_name + ' ' + self.egd_first_name
else:
return self.id
I am using django-tables2 to render the table:
class PlayerTable(tables.Table):
full_name = tables.LinkColumn(
accessor="__str__",
verbose_name="Прізвище та ім'я",
order_by="last_name",
viewname='UGD:player_info',
empty_values=(),
args=[A('pk')]
)
local_rank = tables.Column(
accessor="local_rank.abbreviate",
order_by="id"
)
ufgo_member = tables.BooleanColumn(
verbose_name="Член УФГО"
)
class Meta:
model = Player
fields = (
'id',
'full_name',
'city',
'rating',
'rank',
'local_rank',
'ufgo_member'
)
attrs = {'class': 'main'}
I am using django-filter to make a filter form:
class PlayersFilter(django_filters.FilterSet):
last_name = django_filters.CharFilter(
lookup_expr='contains',
label="Прізвище"
)
first_name = django_filters.CharFilter(
lookup_expr='contains',
label="Ім'я"
)
city = django_filters.ChoiceFilter(
choices=[(city.id, city.name) for city in City.objects.all()],
empty_label="--Не обрано--",
label="Місто"
)
ufgo_member = django_filters.ChoiceFilter(
choices=[
(False, 'Ні'),
(True, 'Так')
],
name="ufgo_member",
label="Член УФГО",
)
class Meta:
model = Player
fields = (
'last_name',
'first_name'
)
The following is the issue - view / template.
I am using SingleTableMixin and FilterView in my view to take care of table and filter:
class RatingListView(SingleTableMixin, FilterView):
table_class = PlayerTable
table_pagination = False
template_name = 'UGD/rating_list.html'
filterset_class = PlayersFilter
Here I have an idea to divide the view on several parts, but I still don't know how to do it.
Maybe you have some suggestions to make it better?
My template:
<body>
<div class="filter">
<form id="filter_submit" class="filter">
{% block content %}
<div class="filter">
<table class="filter">
{{ filter.form.as_table }}
</table>
<button id="filter_submit_button" type="submit">OK</button>
</div>
{% endblock %}
</form>
</div>
<div>
{% render_table table %}
</div>
</body>
I think I should add some script, but I don't know how to use js yet.
The issue: after chosing filters and pressing OK, the whole page is refreshed with new data
And I want that only table was refreshed.
Please, give me a hint on how to do it.
Thank you very much.
Looking at the django-filter documentation (https://django-filter.readthedocs.io/en/develop/index.html), this app does the filtering in the back-end. I didn't see any javascript code executed to do this and it handles the filtering logic in the views. So that's why the app needs to refresh the page to show you the filtered table as a result.
I'm going to recommend you to implement the filtering functionality with javascript. You need to identify the elements of the table that you want to filter, the trigger elements (dropdowns and search boxes, as in django-filter) and link both of them to make modifications to the DOM. This way, you can have a dynamic table filter.
These couple of links can get you started with this:
http://www.w3schools.com/howto/howto_js_filter_table.asp
http://codepen.io/abocati/pen/vdKce [1]
[1]
JS
(function(document) {
'use strict';
var LightTableFilter = (function(Arr) {
var _input;
var _select;
function _onInputEvent(e) {
_input = e.target;
var tables = document.getElementsByClassName(_input.getAttribute('data-table'));
Arr.forEach.call(tables, function(table) {
Arr.forEach.call(table.tBodies, function(tbody) {
Arr.forEach.call(tbody.rows, _filter);
});
});
}
function _onSelectEvent(e) {
_select = e.target;
var tables = document.getElementsByClassName(_select.getAttribute('data-table'));
Arr.forEach.call(tables, function(table) {
Arr.forEach.call(table.tBodies, function(tbody) {
Arr.forEach.call(tbody.rows, _filterSelect);
});
});
}
function _filter(row) {
var text = row.textContent.toLowerCase(), val = _input.value.toLowerCase();
row.style.display = text.indexOf(val) === -1 ? 'none' : 'table-row';
}
function _filterSelect(row) {
var text_select = row.textContent.toLowerCase(), val_select = _select.options[_select.selectedIndex].value.toLowerCase();
row.style.display = text_select.indexOf(val_select) === -1 ? 'none' : 'table-row';
}
return {
init: function() {
var inputs = document.getElementsByClassName('light-table-filter');
var selects = document.getElementsByClassName('select-table-filter');
Arr.forEach.call(inputs, function(input) {
input.oninput = _onInputEvent;
});
Arr.forEach.call(selects, function(select) {
select.onchange = _onSelectEvent;
});
}
};
})(Array.prototype);
document.addEventListener('readystatechange', function() {
if (document.readyState === 'complete') {
LightTableFilter.init();
}
});
})(document);

Flask-Admin Custom Select2 Ajax Field

I'm trying to extend a one-to-many field in my Flask-Admin app to use a custom Select2 Field. The javascript code for the field looks something like this:
function format(data) {
if (!data.id) return data.text; // optgroup
return "<img class='flag' src='" + data.text + "'/>" + data.id;
}
function formatSelection(data) {
return data.id;
}
$("#da2").select2({
maximumSelectionSize: 3,
formatResult: format,
formatSelection: formatSelection,
escapeMarkup: function(m) { return m; }
});
I am unsure of what I need to change in my view code. I've tried something like this:
class PostForm(wtf.Form):
title = fields.TextField('Title')
photos = fields.SelectField('Photo', widget=widgets.Select(multiple=True), id='da2')
class PostView(ModelView):
form = PostForm
def _feed_user_choices(self, mform):
photos = Photo.query.all()
mform.photos.choices = [(x.path, url_for('static',
filename=form.thumbgen_filename(x.path))) for x in photos]
return mform
def create_form(self):
form = super(Post2View, self).create_form()
return self._feed_user_choices(form)
but its not ajax and there is an error when trying to parse the list.
I feel I'm close, but need some guidance to get there, thanks for the help.
what you probably need is a lambda
def _feed_user_choices(self, mform):
mform.photos.choices = [(x.path, url_for('static',filename=form.thumbgen_filename(x.path))) for x in lambda: Photo.query.all()]
return mform

Categories

Resources