Start of week in datepicker in Grappelli admin interface - django

The Grappelli default datepicker comes with Sunday as the first day of the week. This is really irritating! I want Monday to be the first day of the week for all my present and future models.
Is there a way to do this?
So far, the only "solution" I found involved changing the models' Media class. However, this solution does not seem to work as is with the following:
class Monkey(ImportExportActionModelAdmin):
class Media:
js = ("static/i18n/ui.datepicker-en-GB.js")
...
Where static/i18n/ui.datepicker-en-GB.js is
(function($) {
// datepicker, timepicker init
grappelli.initDateAndTimePicker = function() {
var options = {
closeText: "Done",
prevText: "Prev",
nextText: "Next",
currentText: "Today",
monthNames: [ "January","February","March","April","May","June",
"July","August","September","October","November","December" ],
monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ],
dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ],
weekHeader: "Wk",
dateFormat: "dd/mm/yy",
firstDay: 1,
isRTL: false,
showMonthAfterYear: false,
yearSuffix: ""
};
} )(grp.jQuery);
In any case, this solution is sub-optimal as any new model using a date would have to have the same Meta class defined.

The first part of the solution is to add another JavaScript file to the admin view, as already described by #Sardathrion:
class MyModelAdmin(ModelAdmin):
class Media:
js = ('js/grappellihacks.js',)
The second part is to re-configure the rendered datepicker widgets. Since they get a common class attribute, this code inside of your grappellihacks.js should be enough:
grp.jQuery(function() {
grp.jQuery('.hasDatepicker').datepicker('option', 'firstDay', 1);
});
The option is described in the jQuery UI documentation. The grp attribute is set by grappelli.js, which is loaded by the admin template before your custom media file.

There are indeed a few mistakes in the code above.
First, the AdminModel should be
class Monkey(ImportExportActionModelAdmin):
class Media:
js = ("i18n/ui.datepicker-en-GB.js", )
...
As the static directory is automatically appended and the js variable must be a tuple -- notice the comma.
Second, the javascrpt I used was a copy-and-paste of the static/grappelli/js/grappelli.js one with the option firstDay: 1, added to the options variable. It reads like so:
(function($) {
grappelli.initDateAndTimePicker = function() {
// HACK: get rid of text after DateField (hardcoded in django.admin)
$('p.datetime').each(function() {
var text = $(this).html();
text = text.replace(/^\w*: /, "");
text = text.replace(/<br>[^<]*: /g, "<br>");
$(this).html(text);
});
var options = {
firstDay: 1, // THIS IS THE ONLY CHANGE!!!
constrainInput: false,
showOn: 'button',
buttonImageOnly: false,
buttonText: '',
dateFormat: grappelli.getFormat('date'),
showButtonPanel: true,
showAnim: '',
// HACK: sets the current instance to a global var.
// needed to actually select today if the today-button is clicked.
// see onClick handler for ".ui-datepicker-current"
beforeShow: function(year, month, inst) {
grappelli.datepicker_instance = this;
}
};
var dateFields = $("input[class*='vDateField']:not([id*='__prefix__'])");
dateFields.datepicker(options);
if (typeof IS_POPUP != "undefined" && IS_POPUP) {
dateFields.datepicker('disable');
}
// HACK: adds an event listener to the today button of datepicker
// if clicked today gets selected and datepicker hides.
// use on() because couldn't find hook after datepicker generates it's complete dom.
$(document).on('click', '.ui-datepicker-current', function() {
$.datepicker._selectDate(grappelli.datepicker_instance);
grappelli.datepicker_instance = null;
});
// init timepicker
$("input[class*='vTimeField']:not([id*='__prefix__'])").grp_timepicker();
};
})(grp.jQuery);
Thirdly, I modified all the AdminModels that needed a datepicker. This is a PITA and I wish there was a better way of doing it. There might be...

Related

Bootstrap4 DatePicker Disable Hour Selection and set Default Hour

I want to set datePicker HH:mm always have 09:00AM. I managed to disable hour picker.
HTML
<input class="form-control" id="date_start_picker">
JS
function initDatePicker(id_picker, date, id_other_picker, is_first_picker = true, format = 'yyyy-mm-dd HH:mm') {
let this_object = this
var datePicker = $(id_picker)
datePicker.datepicker({
uiLibrary: 'bootstrap4',
modal: false,
footer: false,
value: date,
format: format,
minDate: "2000-01-01"
});
}
There is nothing mentioned in documentation
I managed to achieve my desired result by doing below
change: function (event) {
let picker_value = datePicker.val()
if (picker_value.includes('00:00')) {
picker_value = picker_value.replace("00:00", "09:00")
$(datePicker).datepicker("value",picker_value);
}
}

jqGrid free and ace admin template integration

I'm trying to play around with various admin templates and ran on to an old Bootstrap 3 one which has jqGrid support. While the demo is working great but it uses the commercial version and not the free jqGrid.
In the link to the repository of source of the demo here (Ace Admin Template), the main file is call jqgrid.html, if I use the most recent free jqGrid as shown below, then the attributes of the button images are no longer working. See the attached pictures.
Tests with commercial jqGrid:
Tests with free jqGrid
I replace the below lines
<script src="assets/js/jquery.jqGrid.min.js"></script>
by these one
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
<link rel="stylesheet" href="https://rawgit.com/free-jqgrid/jqGrid/master/css/ui.jqgrid.min.css ">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/themes/redmond/jquery-ui.min.css">
<script src="https://rawgit.com/free-jqgrid/jqGrid/master/js/jquery.jqgrid.min.js"></script>
So my question is what is the new code I should replace to fix this, since the below code is called in beforeShowForm?
//update buttons classes
var buttons = form.next().find('.EditButton .fm-button');
buttons.addClass('btn btn-sm').find('[class*="-icon"]').hide();//ui-icon, s-icon
buttons.eq(0).addClass('btn-primary').prepend('<i class="ace-icon fa fa-check"></i>');
buttons.eq(1).prepend('<i class="ace-icon fa fa-times"></i>')
With the premium version (Guriddo jqGrid JS - v5.0.2 - 2016-01-18), it works like a charm, see working premium images and free jqGrid images, but when I switched to free jqGrid, the buttons text are not working making hard to read action texts.
This great admin template is a nice add-on to free jQgrid to complete my side project. Not sure where to buy it since it is no longer available for purchase Ace Admin Template Info.
Updated
I still have one small display issue on the header, below is the screen shot
I used one of your demo code so you can reproduce it.
<script type="text/javascript">
jQuery(function($) {
var grid_selector = "#grid-table";
var pager_selector = "#grid-pager";
var parent_column = $(grid_selector).closest('[class*="col-"]');
//resize to fit page size
$(window).on('resize.jqGrid', function () {
$(grid_selector).jqGrid( 'setGridWidth', parent_column.width() );
})
//resize on sidebar collapse/expand
$(document).on('settings.ace.jqGrid' , function(ev, event_name, collapsed) {
if( event_name === 'sidebar_collapsed' || event_name === 'main_container_fixed' ) {
//setTimeout is for webkit only to give time for DOM changes and then redraw!!!
setTimeout(function() {
$(grid_selector).jqGrid( 'setGridWidth', parent_column.width() );
}, 20);
}
})
//if your grid is inside another element, for example a tab pane, you should use its parent's width:
/**
$(window).on('resize.jqGrid', function () {
var parent_width = $(grid_selector).closest('.tab-pane').width();
$(grid_selector).jqGrid( 'setGridWidth', parent_width );
})
//and also set width when tab pane becomes visible
$('#myTab a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
if($(e.target).attr('href') == '#mygrid') {
var parent_width = $(grid_selector).closest('.tab-pane').width();
$(grid_selector).jqGrid( 'setGridWidth', parent_width );
}
})
*/
$.jgrid.icons.aceFontAwesome = $.extend(true, {},
$.jgrid.icons.fontAwesome,
{
nav: {
add: "fa-plus-circle",
view: "fa-search-plus",
},
actions: {
save: "fa-check",
cancel: "fa-times"
},
pager: {
first: "fa-angle-double-left",
prev: "fa-angle-left",
next: "fa-angle-right",
last: "fa-angle-double-right"
},
form: {
prev: "fa-angle-left",
next: "fa-angle-right",
save: "fa-check",
cancel: "fa-times"
}
}
);
$.jgrid.icons.aceFontAwesome = $.extend(true, {},
$.jgrid.icons.fontAwesome,
{
nav: {
add: "fa-plus-circle",
view: "fa-search-plus",
},
actions: {
save: "fa-check",
cancel: "fa-times"
},
pager: {
first: "fa-angle-double-left",
prev: "fa-angle-left",
next: "fa-angle-right",
last: "fa-angle-double-right"
},
form: {
prev: "fa-angle-left",
next: "fa-angle-right",
save: "fa-check",
cancel: "fa-times"
}
}
);
var data = [
{code:"A", name:"Project A",
jan2017:1, feb2017:0, mar2017:0, apr2017:0,
may2017:0, jun2017:0, jul2017:0, aug2017:0,
sep2017:0, oct2017:0, nov2017:0, dec2017:1},
{code:"A", name:"Project A",
jan2017:1, feb2017:1, mar2017:0, apr2017:0,
may2017:1, jun2017:0, jul2017:0, aug2017:0,
sep2017:0, oct2017:1, nov2017:0, dec2017:0}
],
intTemplate = {
width: 20, template: "integer",
align: "center", editable: true
};
jQuery(grid_selector).jqGrid({
colModel: [
{ name: "code", label: "Code", width: 50, align: "center" },
{ name: "name", label: "Name", width: 70 },
{ name: "jan2017", label: "Jan", template: intTemplate },
{ name: "feb2017", label: "Feb", template: intTemplate },
{ name: "mar2017", label: "Mar", template: intTemplate },
{ name: "apr2017", label: "Apr", template: intTemplate },
{ name: "may2017", label: "May", template: intTemplate },
{ name: "jun2017", label: "Jun", template: intTemplate },
{ name: "jul2017", label: "Jul", template: intTemplate },
{ name: "aug2017", label: "Aug", template: intTemplate },
{ name: "sep2017", label: "Sep", template: intTemplate },
{ name: "oct2017", label: "Oct", template: intTemplate },
{ name: "nov2017", label: "Nov", template: intTemplate },
{ name: "dec2017", label: "Dec", template: intTemplate },
{ name: "jan2018", label: "Jan", template: intTemplate },
{ name: "feb2018", label: "Feb", template: intTemplate },
{ name: "mar2018", label: "Mar", template: intTemplate },
{ name: "apr2018", label: "Apr", template: intTemplate },
{ name: "may2018", label: "May", template: intTemplate },
{ name: "jun2018", label: "Jun", template: intTemplate },
{ name: "jul2018", label: "Jul", template: intTemplate },
{ name: "aug2018", label: "Aug", template: intTemplate },
{ name: "sep2018", label: "Sep", template: intTemplate },
{ name: "oct2018", label: "Oct", template: intTemplate },
{ name: "nov2018", label: "Nov", template: intTemplate },
{ name: "dec2018", label: "Dec", template: intTemplate }
],
cmTemplate: { autoResizable: true },
autoResizing: { compact: true },
viewrecords: true,
data: data,
iconSet: "fontAwesome",
rownumbers: true,
sortname: "invdate",
sortorder: "desc",
pager: true,
iconSet: "aceFontAwesome", //"fontAwesome",
grouping: true,
rowNum: 10,
rowList: [5, 10, 20, "10000:All"],
groupingView: {
groupField: ["name"],
groupText: ["<b>{0}</b>"]
},
loadComplete : function() {
var table = this;
var parent_column = $(grid_selector).closest('[class*="col-"]');
setTimeout(function(){
$(grid_selector).jqGrid( 'setGridWidth', parent_column.width() );
}, 0);
},
sortname : 'invid',
inlineEditing: {
keys: true
},
navOptions: {
add: false,
edit: false,
del: false,
search: false
},
inlineNavOptions: {
add: true,
edit: true
},
caption: "Test"
}).jqGrid("navGrid")
.jqGrid("inlineNav")
.jqGrid("rotateColumnHeaders",
["jan2017", "feb2017", "mar2017", "apr2017", "may2017", "jun2017",
"jul2017", "aug2017", "sep2017", "oct2017", "nov2017", "dec2017",
"jan2018", "feb2018", "mar2018", "apr2018", "may2018", "jun2018",
"jul2018", "aug2018", "sep2018", "oct2018", "nov2018", "dec2018"])
.jqGrid('setGroupHeaders', {
useColSpanStyle: true,
groupHeaders: [
{ startColumnName: 'code', numberOfColumns: 2, titleText: '<i>Project</i>' },
{ startColumnName: 'jan2017', numberOfColumns: 12, titleText: '2017' },
{ startColumnName: 'jan2018', numberOfColumns: 12, titleText: '2018' }
]
});
});
I replaced the above code in jqgrid.html. I don't know what really causes it. Could it be rotateColumnHeaders which breaks it?
Pic shows moving code after setgroupheader. The vertical lines are still cut.
More updates
After investigation and trial by error, I found out the issue but it masks an another one, I no longer have header issues but the buttons do not display nicely. Is there anyway to overwrite the css to make them look like the one without using the line : guistyle:bootstrap, seems like jqueryUI is conflicting some how with ace css.
Fix header, by adding : guiStyle: "bootstrap", action buttons do not look good. Blue color header is also gone along with button colors
Removing guiStyle: "bootstrap" breaks header, blue color header, action button look nicely
I've tried to reproduce with jsfiddle but no luck yet.
I looked through the Ace Admin template. One can see that it's created fror old jqGrid, which don't supports Font Awesome and Bootstrap. Free jqGrid supports both (see here and here). One more wiki article describes how one can use other Font Awesome icons to create his own iconSet. For example, one can define
$.jgrid.icons.aceFontAwesome = $.extend(true, {},
$.jgrid.icons.fontAwesome,
{
nav: {
add: "fa-plus-circle",
view: "fa-search-plus",
},
actions: {
save: "fa-check",
cancel: "fa-times"
},
pager: {
first: "fa-angle-double-left",
prev: "fa-angle-left",
next: "fa-angle-right",
last: "fa-angle-double-right"
},
form: {
prev: "fa-angle-left",
next: "fa-angle-right",
save: "fa-check",
cancel: "fa-times"
}
}
);
to use some other icons as defaults (see here). After that one can use iconSet: "aceFontAwesome" option instead of iconSet: "fontAwesome" used typically.
All other CSS settings of Ace Admin template are just customization of the standard CSS. I personally find Ace Admin CSS very nice, but one needs to invest some time to make free jqGrid looks exactly like Ace Admin. One needs no jqGrid knowledge for that. It's enough to use Developer Tools of Chrome to examine CSS used on http://ace.jeka.by/jqgrid.html and to implement the same (or close) settings on free jqGrid. I created the demo http://jsfiddle.net/OlegKi/jj0qbhbt/ which shows how one can do that. One needs just expend CSS settings, which I included in the demo.

Calendar/datepicker with color the available date

I want to color the available date in my calendar datepicker [26-11-2015, 28-11-2015, 30-11-2015] I select.
Can someone help me ?
$("#datepicker").datepicker({
inline: true,
dayNamesMin: [ "Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam" ],
monthNames: [ "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Auôt", "Septembre", "Octobre", "Novembre", "Décembre" ],
firstDay: 1,
minDate: 0,
beforeShowDay: function(date){
var day = date.getDay();
return [(day != +7)];
/* var string = jQuery.datepicker.formatDate('yy-mm-dd', date);
return [ array.indexOf(string) == -1 ];*/
},
closeText: 'Fermer',
// The hidden field to receive the date
altField: "#dateHidden",
prevText: '<',
nextText: '>',
// The format you want
altFormat: "yymmdd",
// The format the user actually sees
dateFormat: "yymmdd",
isRTL: false,
showMonthAfterYear: false,
yearSuffix: '',
onSelect: selectDate,
});
You can use CSS to change the colors.
Here is an example to change the color of available enabled dates:
.ui-datepicker .ui-state-default {
color: deepskyblue;
background: ghostwhite;
}
And for disabled dates:
.ui-datepicker td.ui-state-disabled>span {
color: tomato;
}

Displaying the number of rows in a grid - Extjs

So I'm trying to display the number of rows (records) returned from a grid. Here's the code:
Ext.define('AM.view.user.List' ,{
extend: 'Ext.grid.Panel',
alias: 'widget.userlist',
title: '<center>Data Management</center>',
store: 'Users',
dockedItems: [{
xtype: 'toolbar',
dock: 'bottom',
items: [
{ xtype: 'tbtext', text: 'Number of Records\:' + ***code that will return number of records*** },
{ xtype: 'tbfill' },
{ text: 'Print' },
{ text: 'Export' }
]
}],
...
I'm not sure how to use the getCount() method to return the number of rows from this grid (or store?). Any ideas?
Heres: my store:
Ext.define('AM.store.Users', {
extend: 'Ext.data.Store',
model: 'AM.model.User',
fields: ['field1', 'field2', 'field3'],
data: [
{field1: 'Data 1', field2: 'Data 2', field3: 'Data 3'},
{field1: 'Data 1', field2: 'Data 2', field3: 'Data 3'},
{field1: 'Data 1', field2: 'Data 2', field3: 'Data 3'}
]
});
You won't be able to do this dynamically, where it updates on its own, so you will have to use a placeholder and update the panel when the store is loaded.
{ xtype: 'tbtext', itemId: 'numRecords' }
Then:
listeners: {
render: function(store) {
store.on('load', function(records) {
var count = records.length; //or store.getTotalCount(), if that's what you want
grid.down('#numRecords').setText('Number of Records: ' + count);
});
}
}
You can try to add a paging toolbar and use the properties displayInfo & displayMsg.
Try this code :
bbar : {
xtype : 'pagingtoolbar',
displayInfo: true,
store:'your store',
displayMsg : 'Total rows {2}'// defaultvalue = 'Displaying {0} - {1} of {2}'
}
Assuming your store is loaded before the grid is rendered, this will probably work:
Ext.data.StoreManager.lookup("Users").getCount();
If the store loads dynamically, you will need to attach an event to the store's load event to update your grid, comment if the above code does not work and I can probably help you.
As it has already been said, you have to wait for the store to be loaded before using getCount. Except in the case you'd really use a memory store like the one in your example, but I doubt you'd want to display a dynamic number of record for that use case...
So, you have to listen for the load event of the store and update your text item then. The load event will fire each time the store is loaded, reloaded, etc., which may occur multiple times if your grid is paged or allow for filtering, etc. That means that our number of records will be kept in sync with the actual content of the store. Good.
Now, how to install that listener? One very common place for putting that kind of treatment is in the initComponent method of your component.
Here's the code. See the comments for a crash course in overriding initComponent (see another answer for a lecture on the topic).
Ext.define('AM.view.user.List' ,{
extend: 'Ext.grid.Panel',
alias: 'widget.userlist',
title: '<center>ECRIS-MetaData Management</center>',
store: 'Users',
dockedItems: [{
xtype: 'toolbar',
dock: 'bottom',
items: [
// Give an itemId to this component to make it easy to
// reference later.
{ xtype: 'tbtext', text: 'Loading...', itemId: 'recordNumberItem' },
{ xtype: 'tbfill' },
{ text: 'Print' },
{ text: 'Export' }
]
}],
initComponent: function() {
// We're overriding an existing method, so that's very important to call
// the parent method, or the component will break in awful sufferings
this.callParent(arguments);
// I'm putting the code *after* callParent, so that the store is available
var store = this.getStore(),
// Using ComponentQuery to retrieve the text item
textItem = this.down('#recordNumberItem');
// Using `mon` instead of `on` for better memory management (the listener
// will be removed from the store automatically when the component is
// destroyed).
this.mon(store, 'load', function() {
// We're left with the easy part...
textItem.setText("Number of records: " + store.getCount());
});
}
// ...
});

Sencha Touch 2: List and ListItem data fields

I wanted to know how to customize the ListItem content by combining different JSON data fields.
I have three JSON fields: {caption},{subCaption},{source}.
So far, I have been able to use dataMap and use custom classes to wrap additional text and styling around each. However, the only way I have been able to add content is to do so sequentially with the use of apply/update functions. As a result, my ListItems are simply {caption},{subCaption},{source} in their own lines.
Here's how I would like each ListItem to look like:
Combine {caption} and {subCaption} text and create a short story and add this as a panel to the ListItem
Render {source} in a small panel docked at the bottom right of the panel created in step 1.
How can I do the above? The distilled question would be: How can I access and combine the data from different JSON fields and render into ListItem?
My current code for ListItem is copied below for reference.
As always, any help is greatly appreciated! Thanks!
Mohammad
San Jose, CA
Ext.define('qxtapp.view.ResultsListItem', {
extend: 'Ext.dataview.component.ListItem',
requires: [
'qxtapp.view.ResultsListItemCaption'
],
xtype : 'resultslistitem',
alias : 'widget.resultslistitem',
config: {
caption: true,
subCaption: true,
source: true,
dataMap: {
getCaption: {
setHtml: 'caption'
},
getSubCaption: {
setHtml: 'subCaption'
},
getSource: {
setHtml: 'source'
}
},
layout: {
type: 'vbox'
}
},
applyCaption: function(config) {
return Ext.factory(config, qxtapp.view.ResultsListItemCaption, this.getCaption());
},
updateCaption: function(newCaption) {
if (newCaption) {
this.add(newCaption);
}
},
applySubCaption: function(config) {
return Ext.factory(config, Ext.Component, this.getSubCaption());
},
updateSubCaption: function(newSubCaption) {
if (newSubCaption) {
this.add(newSubCaption);
}
},
applySource: function(config) {
return Ext.factory(config, Ext.Component, this.getSource());
},
updateSource: function(newSource) {
if (newSource) {
this.add(newSource);
}
}
});
Ext.define('qxtapp.view.ResultsListItemCaption', {
extend: 'Ext.Component',
applyHtml: function(caption) {
// do some customization to caption and return it
return caption;
}
});
I'm not sure why you need to go through all that trouble, why not use an item template in a simple list?
Ext.define('qxtapp.view.ResultsList', {
extend: 'Ext.dataview.List',
alias: 'widget.resultslist',
config: {
...
itemTpl: new Ext.XTemplate(
"<div class='result-item'>",
"<p class='result-story'>",
"{[this.getStoryHtml(values.caption, values.subCaption)]}",
"</p>",
"<img src='{source}' alt='{caption}' />",
"</div>",
{
// This is a new function on the template created above and can be called
// from within the template html
getStoryHtml: function(caption, subCaption) {
// customize the text to your needs, then return the html to insert
}
}
),
...
}
});
Of course, you would then need to style these items using CSS, but that should be the easy part. ;)