Custom Dependent Dropdown menu inside the Django admin - javascript

I have a project foreign key in by Phase model. I'm having hard time Create a dependent drop-down list inside my Django admin page.
I want to when user select a project from (project drop-down) phase of that project show in second dop-down
What would be the best way to achieve this?
It would be great if the dropdowns filter items based on the value of its parent.
class Project(models.Model):
name = models.CharFieldmax_length = 100, unique= True)
short_name = models.CharField(max_length= 4, unique= True)
slug = models.SlugField(max_length= 100, allow_unicode=True, null=True, editable= False)
location = models.OneToOneField(Location, on_delete = models.SET_NULL, null= True, blank= False, verbose_name= 'موقعیت')
start_date = models.DateField(default= timezone.now, null= True, blank= True)
end_date = models.DateField(default= timezone.now, null= True, blank= True)
duration = models.IntegerField(default= 0, editable= False)
class Phase(models.Model):
title = models.CharField(max_length= 20)
class ProjectPhase(models.Model):
project = models.ForeignKey(Project, on_delete= models.CASCADE, related_name= 'phase')
phase = models.ForeignKey(Phase, on_delete=models.CASCADE, related_name= 'project')
start_date = models.DateField(default= timezone.now)
end_date = models.DateField(default= timezone.now)
duration = models.IntegerField(default= 0, editable= True)

1. import a js media file in ModelAdmin for Generaldata:
class YourModelAdmin(admin.ModelAdmin):
form = YourModelForm
#list_display = ['your fields',]
class Media:
js = ("yourapp/selectajax.js",)
admin.site.register(YourModel, YourModelAdmin)
2. create a new js file which saved yourproject/yourapp/static/yourapp/ directory or another proper directory.
jQuery(function($){
$(document).ready(function(){
$("#id_project_select").change(function(){
// console.log(obj.currentTarget.value);
$.ajax({
url:"/get_phases/",
type:"POST",
data:{project: $(this).val(),},
success: function(result) {
console.log(result);
cols = document.getElementById("id_phase_select");
cols.options.length = 0;
for(var k in result){
cols.options.add(new Option(k, result[k]));
}
},
error: function(e){
console.error(JSON.stringify(e));
},
});
});
});
});
3. create a view to process ajax
#login_required
def get_phases(request):
project = request.POST.get('project')
phases = {}
try:
if project:
prophases = Project.objects.get(pk=int(project)).phase
phases = {pp.phase.title:pp.pk for pp in prophases}
except:
pass
return JsonResponse(data=phases, safe=False)
4. add 'get_phases/ to urlpatterns.
Notice that you should modify some codes as your need.

The answer by Blackdoor is a good approach and it's the one we just implemented, but it has a couple of problems:
It's only executed when you change the main select, and I wanted the dependant select to be filtered also on page load.
Does not keep que selected item in the dependant select.
In his solution, in step 2, replace his code with this one and adapt the names (I'm using service and sub_service instead of project / phase):
jQuery(function($){
$(document).ready(function(){
var clone = document.getElementById("id_sub_service").cloneNode(true);
$("#id_service").change(function(){
update_sub_services($(this).val(), clone)
});
update_sub_services($("#id_service").val(), clone)
});
function update_sub_services(service, clone) {
$.ajax({
url:"/chained_dropdowns/get_sub_services/",
type:"GET",
data:{service: service,},
success: function(result) {
var cols = document.getElementById("id_sub_service");
cols.innerHTML = clone.innerHTML
Array.from(cols.options).forEach(function(option_element) {
var existing = false;
for (var k in result) {
if (option_element.value == k) {
existing = true
}
}
if (existing == false) {
$("#id_sub_service option[value='"+option_element.value+"']").remove();
}
})
},
error: function(e){
console.error(JSON.stringify(e));
},
});
}
});
As you can see, now instead of removing all the items from the dependant select and then refilling it (which leaves you without the selected property and any other custom property), it removes the options that should not be there.
I'm not a JS developer and I don't know jQuery so my modifications are in native JS, please feel free to improve it :)

Related

Reading a value from a Cascading DropDown List

I have successfully developed a cascading dropdown list using javascript thanks to some code I found online. The html code it generates looks as expected when I view the code inside my Firefox web developer tools. The problem I have is that my php backend cannot read this from the $_POST buffer. The error I get is "Undefined index". It's almost as if the php does not see the second DDL that is dynamically added to my html page. Is there a trick I'm missing?
<script type="text/javascript">
var created = 0;
function displayAccordingly() {
if (created == 1) {
removeDrop();
}
//Call mainMenu the main dropdown menu
var mainMenu = document.getElementById('mainMenu');
//Create the new dropdown menu
var whereToPut = document.getElementById('myDiv');
var newDropdown = document.createElement('select');
newDropdown.setAttribute('id',"newDropdownMenu");
newDropdown.setAttribute('name',"AccountNumber");
whereToPut.appendChild(newDropdown);
if if (mainMenu.value == "Office Expense") { //The person chose Office Expense
var option000000000=document.createElement("option");
option000000000.text="---";
option000000000.value="000000000";
newDropdown.add(option000000000,newDropdown.options[null]);
var option160006235=document.createElement("option");
option160006235.text="COPY PAPER AND SUPPLIES";
option160006235.value="160006235";
newDropdown.add(option1160006235,newDropdown.options[null]);
var option160006237=document.createElement("option");
option160006237.text="COPIER RENTAL AGREEMENT";
option160006237.value="160006237";
newDropdown.add(option1160006237,newDropdown.options[null]);
} else if (mainMenu.value == "Custodial") { //The person chose Custodial
var option000000000=document.createElement("option");
option000000000.text="---";
newDropdown.add(option000000000,newDropdown.options[null]);
var option164006410=document.createElement("option");
option164006410.value="164006410";
option164006410.text="CONTRACTED SERVICES-FACILITIES";
newDropdown.add(option164006410,newDropdown.options[null]);
var option164006415=document.createElement("option");
option164006415.value="164006415";
option164006415.text="MAINTENANCE-GROUNDS";
newDropdown.add(option164006415,newDropdown.options[null]);
var option164006420=document.createElement("option");
option164006420.value="164006420";
option164006420.text="MATERIALS AND SUPPLIES";
newDropdown.add(option164006420,newDropdown.options[null]);
}
created = 1
}
function removeDrop() {
var d = document.getElementById('myDiv');
var oldmenu = document.getElementById('newDropdownMenu');
d.removeChild(oldmenu);
}
</script>
What the development tools shows as my HTML code:
My PHP Code (simplified)
$AccountNumber = $_POST['AcountNumber'];
I can read Category from the $_POST buffer, but not AccountNumber.
I am thus thinking the Javascript works fine, I don't understand why the value for AccountNumber is not placed in the $_POST buffer.
The results from a print_r($_POST) is as follows (Right after [Category] I would expect [AccountNumber]=>):
Array ( [action] => POStepTwo [logged_in_user] => 1625605397 [who] => requester [UserID] => 1625605397 [Vendor] => 2080MED [Department] => Plant [Category] => Office Expense [ShippingInstructions] => 1 [RequesterNote] => test )
Thanks for all the help.

How to perform .delete() queryset in Django in a ListView?

Here is what I've done so far:
1.) I've made a javascript function that gets all the id's of the items (using checkbox select) in the database like so (this is DataTables):
function () {
// count check used for checking selected items.
var count = table.rows( { selected: true } ).count();
// Count check.
// Count must be greater than 0 to delete an item.
// if count <= 0, delete functionality won't continue.
if (count > 0) {
var data = table.rows( { selected: true } ).data();
var list = [];
for (var i=0; i < data.length ;i++){
// alert(data[i][2]);
list.push(data[i][2]);
}
var sData = list.join();
// alert(sData)
document.getElementById('delete_items_list').value = sData;
}
}
It outputs something like 1,2,5,7 depending on what rows I have selected.
2.) Passed the values inside a <input type="hidden">.
Now, I've read a post that says you can delete data in Django database using a checkbox, but I'm not sure how exactly can I use this.
I'm guessing I should put it in the ListView that I made, but how can I do that when I click the "Delete selected items" button, I can follow this answer?
I'm trying to achieve what Django Admin looks like when you delete items.
My ListView looks like this:
Yes you can use linked example. Django Admin do it the same way, You send selected ids and django do filtering by given values and after django apply selected action for selected items.
UPDATE
For example.
class List(ListView);
def post(self, request, *args, **kwargs):
ids = self.request.POST.get('ids', "")
# ids if string like "1,2,3,4"
ids = ids.split(",")
try:
# Check ids are valid numbers
ids = map(int, ids)
except ValueError as e:
return JsonResponse(status=400)
# delete items
self.model.objects.filter(id__in=ids).delete()
return JsonResponse({"status": "ok"}, status=204)
And html:
<button id="delete-button">Del</button>
<div id="items-table">
{% for object in objects_list %}
<div class="item" data-id="{{object.id}}">{{ object.name }}</div>
{% endfor %}
</div>
<script>
$(function(){
$('#delete-button').on('click', function(e) {
// Get selected items. You should update it according to your template structure.
var ids = $.map($('#items-table item'), function(item) {
return $(item).data('id')
}).join(',');
$.ajax({
type: 'POST',
url: window.location.href ,
data: {'ids': ids},
success: function (res) {
// Update page
window.location.href = window.location.href;
},
error: function () {
// Display message or something else
}
});
})
})();
</script>

Give every angular material checkbox and ID

I am currently having a problem:
I need to create md-checkboxes from a Database. This part works fine with ng-repeat. But I am having a problem reading those checkboxes.
Every entry in the Database has its own unique ID (I am using RethinkDB) so I thought I just can apply this as an ID.
md-card(ng-repeat="n in ideas")
md-card-content
span
md-checkbox(type="checkbox" id='n.id') {{n.idea}}
I am working with Jade / Pug as View Engine.
But how am I now able to read out all checkboxes at once?
I tried many methods like looping through all ElementsByTagName("md-checkbox") and than with a for to read the checked value but it always returns undefined.
const checkboxes = document.getElementsByTagName("md-checkbox");
console.log(checkboxes) //works fine, prints all checkboxes
for (let i = 0; i < checkboxes.length; i++) {
console.log(checkboxes[i].checked); //returns undefined
}
Do you have any Ideas how to read all Boxes at once?
Edit #1 - More Code
index.js
angular.module('votes', ['ngMaterial'])
.controller("VoteMainController", function ($scope) {
$scope.safeApply = function (fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
register = [];
//var to store database content and add it to page
$scope.ideas;
//Downloading Database stuff as JSON
$.ajax({
type: "GET",
url: "./api",
async: true,
success: function (content) {
for (let i = 0; i < content.length; i++) {
register.push({
[content[i].id]: {
checked: false
}
})
}
$scope.ideas = content;
$scope.safeApply();
},
});
function checkChecked() {
const checkboxes = document.getElementsByTagName("md-checkbox");
for (let i = 0; i < checkboxes.length; i++) {
console.log(checkboxes[i].checked);
}
}
})
index.jade
form(id="login" method="post")
md-card(ng-repeat="n in ideas")
md-card-content
span
md-checkbox(type="checkbox" id='n.id') {{n.idea}}
md-input-container
label Andere Idee?
md-icon search
input(name='idea', ng-model='idea', id='idea', type='text')
div(layout="column")
md-button(type='submit', class="md-raised md-primary unselectable")
| Senden!
Your question title asks about assigning ids but Your question, which you wrote at the very end is
"Do you have any Ideas how to read all Boxes at once?"
So if you wanna do something with multiple or all checkbox elements you should assign them some css class, say "idea-checkbox".
Then in css styles or in jQuery you can access all those checkboxes at once by using the dotted syntax of ".idea-checkbox".
css ids are used to distinctively access any one html element and classes are used to get access to all the elements at once which has that class.
You can also try to the use angular's "track by n.id" syntax if you want to track them by their id.
You should be able to do this with ng-list and ng-model:
HTML
<div ng-app="MyApp" ng-controller="MyAppController">
<ng-list>
<ng-list-item ng-repeat="n in ideas">
<p>{{n.id}}: <input type="checkbox" ng-model="n.isChecked"></p>
</ng-list-item>
</ng-list>
<p>Current state of model: {{ ideas }}</p>
</div>
<p>Current state of model: {{ ideas }}</p>
Angular
angular
.module("MyApp", ['ngMessages'])
.controller('MyAppController', AppController);
function AppController($scope) {
$scope.ideas = [{
id: 193872768,
isChecked: false
},{
id: 238923113,
isChecked: false
}];
}
Demo
This also works with the material design counterparts, md-list and md-checkbox.

Passing Collection ID to generate Forms in Meteor

I've a collection "Country" which displays a list of countries and their info. A book option leads a user to the "Activity" form which has dropdown fields like To,From. How do I pass the "Country" ID so that the form fields in "Activity" belong to the "Country" whose Book button the user has clicked. I think I should use a reactive var to store the country id and then perform operations to change the form fields accordingly. I'm fairly new to this and a heads up to the approach would be deeply appreciated.
The submit code for activity collection is :
Template.activitySubmit.onRendered(function () {
if (Session.get("submit-bypass") === true) {
Session.set("submit-bypass", false);
window.history.back();
}
$("#activity-goal").material_select();
});
Template.activitySubmit.events({
"submit form": function (e) {
e.preventDefault();
var title = $(e.target).find("#activity-title").val()
if (title.length === 0) {
throwError("You must have a title.");
$("activity-title").focus();
return;
}
var activty = {
title: title,
goal: parseInt($(e.target).find("#activity-goal").val())
};
Meteor.call("activityInsert", activty, function (error, result) {
if (error) {
throwError(error.reason);
return;
}
Session.set("submit-bypass", true);
Router.go("activityPage", { _id: result._id });
});
}
});
This is how you should use reactive var package
Add the package to your project, in terminal meteor add reactive-var
Open your JS file for the template, like activitySubmit.js
Declare reactive var like so
Template.activitySubmit.onRendered(function () {
this.selectedCountry = new ReactiveVar(null);
});
Then update the reactive var as needed and use it
`
Template.activitySubmit.events({
'change #country': function(e){
var country = $(e.target).val()
var instance = Template.instance();
instance.selectedCountry.set(country);
},
'submit form': function (e) {
....
var instance = Template.instance();
var country = instance.selectedCountry.get(country);
....
}
});`
If you'd like to pass the data around across mulitple files (globally), you can simply use Session. Session.set('myId', id) and Session.get('myId')

use values submitted in a form to publish and subscribe results of a mongodb query in meteor

I want a user to be able to type 2 inputs into a form, hit submit, and have the database send back documents with field values that match the values that were typed into the form.
If I hard code values for 'name' and 'code' variables on the last line, I get the right results and everything renders fine. So I think the problem has to do with how I'm using the variables / variable scope, or something of that nature.
more detailed description below...
I am using Meteor.
I have a form with 2 input fields, for example, product and brand
I want to send a query of the following form:
PriceList.find({'name': name, 'brandCode': code});
I have a template that renders based on the results of this query. This relies on publishing the results of the query:
if (Meteor.isServer) {
Meteor.publish('byProductAndBrand', function(){
var name = product;
var code = brand;
return PriceList.find({'name': name, 'brandCode': code});
});
}
I'm trying to use Meteor.subscribe() to change dynamically based on the form inputs:
if (Meteor.isClient) {
Template.dataSelectionForm.events({
'submit form#addDataSelectionForm': function(event, template){
event.preventDefault();
product = template.find([name='product_name']).value;
brand = template.find([name='brandCode']).value;
}
});
Meteor.subscribe('byProductAndBrand');
}
Here's the relevant code (duplicates what I wrote above, but may be less annoying to read...)
PriceList = new Meteor.Collection('PriceList');
product = 'dummyProduct';
brand = 'dummyBrandCode';
if (Meteor.isClient) {
Template.dataSelectionForm.events({
'submit form#addDataSelectionForm': function(event, template){
event.preventDefault();
product = template.find([name='product_name']).value;
brand = template.find([name='brandCode']).value;
}
});
Meteor.subscribe('byProductAndBrand');
}
if (Meteor.isServer) {
Meteor.publish('PriceList', function(){
return PriceList.find();
});
Meteor.publish('byProductAndBrand', function(){
var name = product;
var code = brand;
return PriceList.find({'name': name, 'brandCode': code});
});
}
Pass params to Meteor.subscription :
if(Meteor.isServer){
Meteor.publish('byProductAndBrand', function(product, brand){
var name = product;
var code = brand;
return PriceList.find({'name': name, 'brandCode': code});
});
}
}
if (Meteor.isClient) {
Template.dataSelectionForm.events({
'submit form#addDataSelectionForm': function(event, template){
event.preventDefault();
product = template.find([name='product_name']).value;
brand = template.find([name='brandCode']).value;
var instance = Template.instance();
if(instance.byProductAndBrandHandle != null){
instance.byProductAndBrandHandle.stop();
}
instance.byProductAndBrandHandle = Meteor.subscribe('byProductAndBrand', product ,brand);
}
});
}
It seems to me that you would be better off using dependencies instead of subscriptions. This makes sense to me in this circumstance because only the one client viewing the page is looking at this particular price list, you are not publishing this price list for multiple users as far as I can tell.
For ex:
In declarations, before if(Meteor.isClient) and if(Meteor.isServer):
var byProductAndBrandDep = new Tracker.Dependency;
Inside of priceList helper:
byProductAndBrandDep.depend();
In "submit form#addDataSelectionForm".events: function(event, templates) {
byProductAndBrandDep.changed();
Then, every time the form is submitted, every helper that has the byProductAndBrandDep.depend() function inside it automatically updates.

Categories

Resources