diff --git a/frappe/__init__.py b/frappe/__init__.py index 12835b447c..da9ddb9fa7 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -446,13 +446,16 @@ def clear_cache(user=None, doctype=None): frappe.local.role_permissions = {} -def has_permission(doctype, ptype="read", doc=None, user=None, verbose=False, throw=False): +def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False): """Raises `frappe.PermissionError` if not permitted. :param doctype: DocType for which permission is to be check. :param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`. :param doc: [optional] Checks User permissions for given doc. :param user: [optional] Check for given user. Default: current user.""" + if not doctype and doc: + doctype = doc.doctype + import frappe.permissions out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user) if throw and not out: diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index f306a2cc81..d5d2442c66 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -118,28 +118,7 @@ def get_communications(doctype, name, start=0, limit=20): def _get_communications(doctype, name, start=0, limit=20): - communications = frappe.db.sql("""select name, communication_type, - communication_medium, comment_type, - content, sender, sender_full_name, creation, subject, delivery_status, _liked_by, - timeline_doctype, timeline_name, - reference_doctype, reference_name, - link_doctype, link_name, - "Communication" as doctype - from tabCommunication - where - communication_type in ("Communication", "Comment") - and ( - (reference_doctype=%(doctype)s and reference_name=%(name)s) - or (timeline_doctype=%(doctype)s - and timeline_name=%(name)s - and communication_type="Comment" - and comment_type in ("Created", "Updated", "Submitted", "Cancelled", "Deleted")) - ) - and (comment_type is null or comment_type != 'Update') - order by creation desc limit %(start)s, %(limit)s""", - { "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit }, - as_dict=True) - + communications = get_communication_data(doctype, name, start, limit) for c in communications: if c.communication_type=="Communication": c.attachments = json.dumps(frappe.get_all("File", @@ -153,6 +132,43 @@ def _get_communications(doctype, name, start=0, limit=20): return communications +def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=None, + group_by=None, as_dict=True): + '''Returns list of communicataions for a given document''' + if not fields: + fields = '''name, communication_type, + communication_medium, comment_type, + content, sender, sender_full_name, creation, subject, delivery_status, _liked_by, + timeline_doctype, timeline_name, + reference_doctype, reference_name, + link_doctype, link_name, + "Communication" as doctype''' + + conditions = '''communication_type in ("Communication", "Comment") + and ( + (reference_doctype=%(doctype)s and reference_name=%(name)s) + or (timeline_doctype=%(doctype)s + and timeline_name=%(name)s + and communication_type="Comment" + and comment_type in ("Created", "Updated", "Submitted", "Cancelled", "Deleted")) + ) + and (comment_type is null or comment_type != 'Update')''' + + if after: + # find after a particular date + conditions+= ' and creation > {after}' + limit = 1000 + + communications = frappe.db.sql("""select {fields} + from tabCommunication + where {conditions} {group_by} + order by creation desc limit %(start)s, %(limit)s""".format( + fields = fields, conditions=conditions, group_by=group_by or ""), + { "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit }, + as_dict=as_dict) + + return communications + def get_assignments(dt, dn): cl = frappe.db.sql("""select owner, description from `tabToDo` where reference_type=%(doctype)s and reference_name=%(name)s and status="Open" diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 18d9198837..535e190f47 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import frappe from frappe.utils import time_diff_in_seconds, now, now_datetime, DATETIME_FORMAT from dateutil.relativedelta import relativedelta -from frappe import _ @frappe.whitelist() def get_notifications(): @@ -178,9 +177,6 @@ def get_open_count(doctype, name): :param transactions: List of transactions (json/dict) :param filters: optional filters (json/list)''' - doc = frappe.get_doc(doctype, name) - if not doc.has_permission('read'): - frappe.msgprint(_("Not permitted"), raise_exception=True) links = frappe.get_meta(doctype).get_links_setup() @@ -192,15 +188,20 @@ def get_open_count(doctype, name): out = [] for doctype in items: filters = get_filters_for(doctype) + fieldname = links.get('non_standard_fieldnames', {}).get(doctype, links.fieldname) + data = {'name': doctype} if filters: # get the fieldname for the current document # we only need open documents related to the current document - fieldname = links.get('non_standard_fieldnames', {}).get(doctype, links.fieldname) filters[fieldname] = name + total = len(frappe.get_all(doctype, fields='name', + filters=filters, limit_page_length=6, distinct=True)) + data['open_count'] = total - if filters: - open_count = len(frappe.get_all(doctype, fields='name', - filters=filters, limit_page_length=6, distinct=True)) - out.append({'name': doctype, 'count': open_count}) + total = len(frappe.get_all(doctype, fields='name', + filters={fieldname: name}, limit_page_length=10, distinct=True)) + data['count'] = total + out.append(data) return out + diff --git a/frappe/public/build.json b/frappe/public/build.json index 079f315f02..76b43ae4cb 100644 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -42,7 +42,7 @@ "public/css/bootstrap.css", "public/css/font-awesome.css", "public/css/octicons/octicons.css", - "public/css/font/open-sans/open-sans.css", + "public/css/cal-heatmap.css", "public/css/desk.css", "public/css/indicator.css", "public/css/avatar.css", @@ -63,6 +63,8 @@ "public/js/lib/moment/moment-with-locales.min.js", "public/js/lib/moment/moment-timezone-with-data.min.js", "public/js/lib/socket.io.min.js", + "public/js/lib/d3.min.js", + "public/js/lib/cal-heatmap.js", "public/js/frappe/provide.js", "public/js/frappe/class.js", @@ -150,6 +152,7 @@ "public/js/frappe/form/templates/users_in_sidebar.html", "public/js/frappe/form/templates/set_sharing.html", "public/js/frappe/form/templates/form_sidebar.html", + "public/js/frappe/form/templates/form_dashboard.html", "public/js/frappe/form/templates/form_links.html", "public/js/frappe/views/formview.js", "public/js/legacy/form.js", diff --git a/frappe/public/css/cal-heatmap.css b/frappe/public/css/cal-heatmap.css new file mode 100755 index 0000000000..66d1323c09 --- /dev/null +++ b/frappe/public/css/cal-heatmap.css @@ -0,0 +1,140 @@ +/* Cal-HeatMap CSS */ + +.cal-heatmap-container { + display: block; +} + +.cal-heatmap-container .graph-label +{ + fill: #999; + font-size: 10px +} + +.cal-heatmap-container .graph, .cal-heatmap-container .graph-legend rect { + shape-rendering: crispedges +} + +.cal-heatmap-container .graph-rect +{ + fill: #ededed +} + +.cal-heatmap-container .graph-subdomain-group rect:hover +{ + stroke: #000; + stroke-width: 1px +} + +.cal-heatmap-container .subdomain-text { + font-size: 8px; + fill: #999; + pointer-events: none +} + +.cal-heatmap-container .hover_cursor:hover { + cursor: pointer +} + +.cal-heatmap-container .qi { + background-color: #999; + fill: #999 +} + +/* +Remove comment to apply this style to date with value equal to 0 +.q0 +{ + background-color: #fff; + fill: #fff; + stroke: #ededed +} +*/ + +.cal-heatmap-container .q1 +{ + background-color: #dae289; + fill: #dae289 +} + +.cal-heatmap-container .q2 +{ + background-color: #cedb9c; + fill: #9cc069 +} + +.cal-heatmap-container .q3 +{ + background-color: #b5cf6b; + fill: #669d45 +} + +.cal-heatmap-container .q4 +{ + background-color: #637939; + fill: #637939 +} + +.cal-heatmap-container .q5 +{ + background-color: #3b6427; + fill: #3b6427 +} + +.cal-heatmap-container rect.highlight +{ + stroke:#444; + stroke-width:1 +} + +.cal-heatmap-container text.highlight +{ + fill: #444 +} + +.cal-heatmap-container rect.now +{ + stroke: red +} + +.cal-heatmap-container text.now +{ + fill: red; + font-weight: 800 +} + +.cal-heatmap-container .domain-background { + fill: none; + shape-rendering: crispedges +} + +.ch-tooltip { + padding: 10px; + background: #222; + color: #bbb; + font-size: 12px; + line-height: 1.4; + width: 140px; + position: absolute; + z-index: 99999; + text-align: center; + border-radius: 2px; + box-shadow: 2px 2px 2px rgba(0,0,0,0.2); + display: none; + box-sizing: border-box; +} + +.ch-tooltip::after{ + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + content: ""; + padding: 0; + display: block; + bottom: -6px; + left: 50%; + margin-left: -6px; + border-width: 6px 6px 0; + border-top-color: #222; +} diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css index 0f40c6fafe..1d7ae44e4c 100644 --- a/frappe/public/css/form.css +++ b/frappe/public/css/form.css @@ -28,9 +28,30 @@ border-top: 1px solid #d1d8dd; } .form-dashboard { - display: none; border-bottom: 1px solid #EBEFF2; } +.form-dashboard-section { + padding: 15px 30px; + margin: 0px; + border-bottom: 1px solid #EBEFF2; +} +.form-dashboard-section:last-child { + border-bottom: none; +} +.form-heatmap { + padding-top: 30px; +} +.form-links .document-link { + margin-bottom: 10px; + height: 22px; + position: relative; +} +.form-links .count { + position: absolute; + left: -20px; + top: 3px; + display: inline-block; +} .form-section { margin: 0px; padding: 15px; diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 2c1b08b51c..aabe7bc34a 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -412,7 +412,6 @@ frappe.get_desktop_icons = function(show_hidden, show_global) { if(m.blocked && !show_global) { out = false; } - console.log(module, out); return out; } diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 50edd34105..7a963c26ec 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -117,6 +117,12 @@ frappe.ui.form.Control = Class.extend({ this.set_input(value); } }, + set_focus: function() { + if(this.$input) { + this.$input.get(0).focus(); + return true; + } + } }); frappe.ui.form.ControlHTML = frappe.ui.form.Control.extend({ @@ -1464,6 +1470,13 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ this.editor.set_input(value); this.md_editor.val(value); this.last_value = value; + }, + set_focus: function() { + var editor = this.$wrapper.find('.text-editor'); + if(editor) { + editor.focus(); + return true; + } } }); diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index ad63c76358..4610cdc8f5 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -4,27 +4,30 @@ frappe.ui.form.Dashboard = Class.extend({ init: function(opts) { $.extend(this, opts); - this.wrapper = $('
') - .prependTo(this.frm.layout.wrapper); - this.body = $('').appendTo(this.wrapper) - .css("padding", "15px 30px"); + this.wrapper = $(frappe.render_template('form_dashboard', + {frm: this.frm})).prependTo(this.frm.layout.wrapper); + + this.headline = this.wrapper.find('.form-headline'); + this.heatmap_area = this.wrapper.find('.form-heatmap'); + this.stats_area = this.wrapper.find('.form-stats'); + this.links_area = this.wrapper.find('.form-links'); + this.transactions_area = this.links_area.find('.transactions'); }, reset: function() { - this.wrapper.toggle(false); - this.body.empty(); - this.badge_area = $('' - + __("Documents related to {0}", [this.frm.doc.name]) +'
=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(oa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},oa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),oa.behavior={},oa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},oa.event=null,oa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Na=function(n,t){return t.querySelectorAll(n)},Ea=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ea=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Na=Sizzle,Ea=Sizzle.matchesSelector),oa.selection=function(){return oa.select(sa.documentElement)};var Aa=oa.selection.prototype=[];Aa.select=function(n){var t,e,r,u,i=[];n=A(n);for(var a=-1,o=this.length;++a =0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||pl,r=dl.get(r)||y,br(r(e.apply(null,la.call(arguments,1))))},oa.interpolateHcl=Rr,oa.interpolateHsl=Dr,oa.interpolateLab=Pr,oa.interpolateRound=Ur,oa.transform=function(n){var t=sa.createElementNS(oa.ns.prefix.svg,"g");return(oa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:ml)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var ml={a:1,b:0,c:0,d:1,e:0,f:0};oa.interpolateTransform=$r,oa.layout={},oa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e "+l+"t;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(a=[],"function"==typeof g)for(t=0;c>t;++t)a[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)a[t]=g;if(o=[],"function"==typeof p)for(t=0;u>t;++t)o[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)o[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=oa.behavior.drag().origin(y).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",nu)),arguments.length?void this.on("mouseover.force",tu).on("mouseout.force",eu).call(r):r},oa.rebind(l,c,"on")};var yl=20,Ml=1,xl=1/0;oa.layout.hierarchy=function(){function n(u){var i,a=[u],o=[];for(u.depth=0;null!=(i=a.pop());)if(o.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)a.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return au(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),o}var t=cu,e=ou,r=lu;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(iu(t,function(n){n.children&&(n.value=0)}),au(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},oa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(a=i.length)){var a,o,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=oa.sum(c),v=p?(f-l*g)/p:0,d=oa.range(l),m=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(a[n],a[t])}),d.forEach(function(n){m[n]={data:a[n],value:o=c[n],startAngle:s,endAngle:s+=o*v+g,padAngle:h}}),m}var t=Number,e=bl,r=0,u=Fa,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var bl={};oa.layout.stack=function(){function n(o,l){if(!(h=o.length))return o;var c=o.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,l);c=oa.permute(c,f),s=oa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return o}var t=y,e=pu,r=vu,u=gu,i=fu,a=hu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||pu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(u=t,n):u},n};var _l=oa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(du),i=n.map(mu),a=oa.range(r).sort(function(n,t){return u[n]-u[t]}),o=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=a[t],l>o?(o+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return oa.range(n.length).reverse()},"default":pu}),wl=oa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,a=[],o=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;i>e;++e)l[e]=(o-a[e])/2;return l},wiggle:function(n){var t,e,r,u,i,a,o,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;i+=a*n[t][e][1]}g[e]=l-=u?i/u*o:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,a=1/u,o=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=a}for(e=0;i>e;++e)o[e]=0;return o},zero:vu});oa.layout.histogram=function(){function n(n,i){for(var a,o,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i"+this.options.title+"
",this.el.className+=" shepherd-has-title"),this.options.showCancelLink&&(h=r("✕"),s.appendChild(h),this.el.className+=" shepherd-has-cancel-link",this.bindCancelLink(h)),null!=this.options.text){if(p=r(""),a=this.options.text,"function"==typeof a&&(a=this.options.text.call(this,p)),a instanceof HTMLElement)p.appendChild(a);else for("string"==typeof a&&(a=[a]),u=0,c=a.length;c>u;u++)l=a[u],p.innerHTML+="