Selaa lähdekoodia

Merge branch 'develop'

version-14
Pratik Vyas 11 vuotta sitten
vanhempi
commit
20e36ff3f5
100 muutettua tiedostoa jossa 2465 lisäystä ja 2124 poistoa
  1. +4
    -0
      .travis.yml
  2. +49
    -0
      CONTRIBUTING.md
  3. +1
    -0
      MANIFEST.in
  4. +8
    -32
      README.md
  5. +127
    -106
      frappe/__init__.py
  6. +1
    -0
      frappe/__version__.py
  7. +6
    -5
      frappe/api.py
  8. +21
    -11
      frappe/app.py
  9. +7
    -2
      frappe/auth.py
  10. +47
    -20
      frappe/boot.py
  11. +37
    -24
      frappe/build.py
  12. +117
    -36
      frappe/cli.py
  13. +11
    -4
      frappe/client.py
  14. +29
    -11
      frappe/config/setup.py
  15. +10
    -7
      frappe/config/website.py
  16. +10
    -3
      frappe/core/doctype/bulk_email/bulk_email.json
  17. +15
    -3
      frappe/core/doctype/comment/comment.json
  18. +7
    -7
      frappe/core/doctype/comment/comment.py
  19. +7
    -0
      frappe/core/doctype/comment/test_records.json
  20. +6
    -9
      frappe/core/doctype/communication/communication.json
  21. +40
    -20
      frappe/core/doctype/communication/communication.py
  22. +37
    -0
      frappe/core/doctype/communication/communication_list.html
  23. +3
    -0
      frappe/core/doctype/communication/communication_list.js
  24. +10
    -0
      frappe/core/doctype/communication/test_communication.py
  25. +10
    -0
      frappe/core/doctype/communication/test_records.json
  26. +12
    -3
      frappe/core/doctype/custom_field/custom_field.js
  27. +15
    -5
      frappe/core/doctype/custom_field/custom_field.json
  28. +21
    -16
      frappe/core/doctype/custom_field/custom_field.py
  29. +10
    -0
      frappe/core/doctype/custom_field/test_custom_field.py
  30. +1
    -0
      frappe/core/doctype/custom_field/test_records.json
  31. +12
    -2
      frappe/core/doctype/custom_script/custom_script.json
  32. +23
    -36
      frappe/core/doctype/customize_form/customize_form.js
  33. +2
    -27
      frappe/core/doctype/customize_form/customize_form.json
  34. +17
    -15
      frappe/core/doctype/customize_form/customize_form.py
  35. +2
    -2
      frappe/core/doctype/customize_form/test_customize_form.py
  36. +134
    -82
      frappe/core/doctype/customize_form_field/customize_form_field.json
  37. +15
    -5
      frappe/core/doctype/docfield/docfield.json
  38. +0
    -7
      frappe/core/doctype/docperm/README.md
  39. +24
    -15
      frappe/core/doctype/docperm/docperm.json
  40. +2
    -2
      frappe/core/doctype/doctype/boilerplate/controller.py
  41. +34
    -0
      frappe/core/doctype/doctype/boilerplate/controller_list.html
  42. +4
    -0
      frappe/core/doctype/doctype/boilerplate/controller_list.js
  43. +10
    -0
      frappe/core/doctype/doctype/boilerplate/test_controller.py
  44. +6
    -0
      frappe/core/doctype/doctype/boilerplate/test_records.json
  45. +0
    -12
      frappe/core/doctype/doctype/doctype.js
  46. +3
    -12
      frappe/core/doctype/doctype/doctype.json
  47. +120
    -36
      frappe/core/doctype/doctype/doctype.py
  48. +0
    -0
      frappe/core/doctype/email_alert/__init__.py
  49. +41
    -0
      frappe/core/doctype/email_alert/email_alert.js
  50. +152
    -0
      frappe/core/doctype/email_alert/email_alert.json
  51. +103
    -0
      frappe/core/doctype/email_alert/email_alert.py
  52. +97
    -0
      frappe/core/doctype/email_alert/test_email_alert.py
  53. +56
    -0
      frappe/core/doctype/email_alert/test_records.json
  54. +0
    -0
      frappe/core/doctype/email_alert_recipient/__init__.py
  55. +42
    -0
      frappe/core/doctype/email_alert_recipient/email_alert_recipient.json
  56. +9
    -0
      frappe/core/doctype/email_alert_recipient/email_alert_recipient.py
  57. +9
    -0
      frappe/core/doctype/event/event.js
  58. +8
    -7
      frappe/core/doctype/event/event.json
  59. +8
    -7
      frappe/core/doctype/event/event.py
  60. +55
    -1
      frappe/core/doctype/event/test_event.py
  61. +70
    -242
      frappe/core/doctype/file_data/file_data.json
  62. +16
    -16
      frappe/core/doctype/file_data/file_data.py
  63. +5
    -5
      frappe/core/doctype/letter_head/letter_head.json
  64. +5
    -113
      frappe/core/doctype/module_def/module_def.json
  65. +6
    -3
      frappe/core/doctype/module_def/module_def.py
  66. +43
    -11
      frappe/core/doctype/notification_count/notification_count.py
  67. +30
    -7
      frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json
  68. +13
    -6
      frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py
  69. +6
    -3
      frappe/core/doctype/page/page.json
  70. +17
    -9
      frappe/core/doctype/print_format/print_format.js
  71. +32
    -17
      frappe/core/doctype/print_format/print_format.json
  72. +1
    -77
      frappe/core/doctype/print_format/print_format.py
  73. +0
    -82
      frappe/core/doctype/print_format/styles/classic.css
  74. +0
    -97
      frappe/core/doctype/print_format/styles/modern.css
  75. +0
    -355
      frappe/core/doctype/print_format/styles/standard.css
  76. +29
    -0
      frappe/core/doctype/print_format/test_print_format.py
  77. +8
    -0
      frappe/core/doctype/print_format/test_records.json
  78. +0
    -0
      frappe/core/doctype/print_settings/__init__.py
  79. +11
    -0
      frappe/core/doctype/print_settings/print_settings.js
  80. +96
    -0
      frappe/core/doctype/print_settings/print_settings.json
  81. +10
    -0
      frappe/core/doctype/print_settings/print_settings.py
  82. +5
    -3
      frappe/core/doctype/property_setter/property_setter.json
  83. +29
    -25
      frappe/core/doctype/property_setter/property_setter.py
  84. +8
    -0
      frappe/core/doctype/report/boilerplate/controller.js
  85. +9
    -0
      frappe/core/doctype/report/boilerplate/controller.py
  86. +16
    -1
      frappe/core/doctype/report/report.js
  87. +11
    -5
      frappe/core/doctype/report/report.json
  88. +25
    -10
      frappe/core/doctype/report/report.py
  89. +1
    -1
      frappe/core/doctype/role/role.js
  90. +3
    -3
      frappe/core/doctype/role/role.json
  91. +0
    -79
      frappe/core/doctype/social_login_keys/social_login_keys.json
  92. +0
    -0
      frappe/core/doctype/standard_reply/__init__.py
  93. +71
    -0
      frappe/core/doctype/standard_reply/standard_reply.json
  94. +9
    -0
      frappe/core/doctype/standard_reply/standard_reply.py
  95. +84
    -323
      frappe/core/doctype/system_settings/system_settings.json
  96. +6
    -5
      frappe/core/doctype/system_settings/system_settings.py
  97. +18
    -8
      frappe/core/doctype/todo/todo.js
  98. +20
    -8
      frappe/core/doctype/todo/todo.json
  99. +56
    -18
      frappe/core/doctype/todo/todo.py
  100. +29
    -0
      frappe/core/doctype/todo/todo_list.html

+ 4
- 0
.travis.yml Näytä tiedosto

@@ -14,6 +14,8 @@ install:
- sudo apt-get update
- sudo apt-get purge -y mysql-common
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev
- wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-precise-amd64.deb
- sudo dpkg -i wkhtmltox-0.12.1_linux-precise-amd64.deb

- CFLAGS=-O0 pip install -r requirements.txt
- pip install --editable .
@@ -23,10 +25,12 @@ script:
- frappe --use test_site
- frappe --reinstall
- frappe -b
- frappe --build_website
- frappe --serve_test &
- frappe --verbose --run_tests

before_script:
- mysql -e 'create database test_frappe'
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root


+ 49
- 0
CONTRIBUTING.md Näytä tiedosto

@@ -0,0 +1,49 @@
# Contributing to Frappe / ERPNext

## Reporting issues

We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems. Please read the following guidelines before opening any issue.

1. **Search for existing issues:** We want to avoid duplication, and you'd help us out a lot by first checking if someone else has reported the same issue. The issue may have already been resolved with a fix available.
1. **Report each issue separately:** Don't club multiple, unreleated issues in one note.
1. **Mention the version number:** Please mention the application, browser and platform version numbers.

### Issues

1. **Share as much information as possible:** Include operating system and version, browser and version, when did you last update ERPNext, how is it customized, etc. where appropriate. Also include steps to reproduce the bug.
1. **Include Screenshots if possible:** Consider adding screenshots annotated with what goes wrong.
1. **Find and post the trace for bugs:** If you are reporting an issue from the browser, Open the Javascript Console and paste us any error messages you see.


### Feature Requests

1. We need as much information you can to consider a feature request.
1. Think about **how** you want us to build the feature. Consider including:
1. Mockups (wireframes of features)
1. Screenshots (annotated with what should change)
1. Screenshots from other products if you want us to implement features present in other products.
1. Basically, the more you help us, the faster your request is likely to be completed.
1. A one line feature request like **Implement Capacity Planning** will be closed.

## Pull Requests

General guidelines for sending pull requests:

#### Don't Repeat Yourself (DRY)

We believe that the most effective way to manage a product like this is to ensure that
there is minimum repetition of code. So before contributing a function, please make sure
that such a feature or function does not exist else where. If it does, the try and extend
that function to accommodate your use case.

#### Don't create new DocTypes Unless Absolutely Necessary

DocTypes are easy to create but hard to maintain. If you find that there is a another DocType with a similar functionality, then please try and extend that functionality. For example, by adding a "type" field to classify the new type of record.

#### Tabs or spaces?

Tabs!

### Copyright

Please see README.md

+ 1
- 0
MANIFEST.in Näytä tiedosto

@@ -25,4 +25,5 @@ recursive-include frappe *.csv
recursive-include frappe *.ico
recursive-include frappe *.less
recursive-include frappe *.txt
recursive-include frappe/public *
recursive-exclude * *.pyc

+ 8
- 32
README.md Näytä tiedosto

@@ -1,41 +1,17 @@
## frappe [![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe)
Full-stack web application framework that uses Python/MySql on the server side and a tightly integrated client side library. Primarily built for erpnext.

Projects: [erpnext](http://erpnext.org) | [frappe/erpnext](https://github.com/frappe/erpnext)
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. [Built for ERPNext](https://erpnext.com)

## Setup
### Installation

To start a new project, in the application root:
[Install via Frappe Bench](https://github.com/frappe/bench)

Install:
### Website

* Go to the project folder
* Install frappe and your app:
```
mkdir bench
cd bench
git clone https://github.com/frappe/frappe.git
git clone https://github.com/frappe/[your_app]
sudo pip install -e frappe/ erpnext/ your_app/
mkdir sites
echo app >> sites/apps.txt
cd sites
frappe site.local --install dbname
frappe site.local --install_app your_app
```
* Run development server:
For details and documentation, see the website

```
cd sites
frappe site.local --serve
```
[https://frappe.io](https://frappe.io)

enjoy!
### License

## wnf.py

`frappe --help` for more info

## License

frappe is freely available to use under the MIT License
MIT License

+ 127
- 106
frappe/__init__.py Näytä tiedosto

@@ -4,19 +4,15 @@
globals attached to frappe module
+ some utility functions that should probably be moved
"""

from __future__ import unicode_literals

from werkzeug.local import Local, release_local
from werkzeug.exceptions import NotFound
from MySQLdb import ProgrammingError as SQLError

import os, sys, importlib, inspect
import json
import os, importlib, inspect, logging, json

# public
from frappe.__version__ import __version__
from .exceptions import *

__version__ = "4.0.1"
from .utils.jinja import get_jenv, get_template, render_template

local = Local()

@@ -68,7 +64,6 @@ response = local("response")
session = local("session")
user = local("user")
flags = local("flags")
restrictions = local("restrictions")

error_log = local("error_log")
debug_log = local("debug_log")
@@ -84,30 +79,37 @@ def init(site, sites_path=None):
sites_path = '.'

local.error_log = []
local.message_log = []
local.debug_log = []
local.flags = _dict({})
local.rollback_observers = []
local.test_objects = {}

local.site = site
local.sites_path = sites_path
local.site_path = os.path.join(sites_path, site)
local.message_log = []
local.debug_log = []

local.request_method = request.method if request else None
local.request_ip = None
local.response = _dict({"docs":[]})

local.conf = _dict(get_site_config())
local.lang = local.conf.lang or "en"
local.initialised = True
local.flags = _dict({})
local.rollback_observers = []

local.module_app = None
local.app_modules = None

local.user = None
local.restrictions = None
local.user_perms = {}
local.test_objects = {}
local.role_permissions = {}

local.jenv = None
local.jloader =None
local.cache = {}

setup_module_map()

local.initialised = True

def connect(site=None, db_name=None):
from database import Database
if site:
@@ -158,7 +160,7 @@ def get_traceback():

def errprint(msg):
from utils import cstr
if not request:
if not request or (not "cmd" in local.form_dict):
print cstr(msg)

error_log.append(cstr(msg))
@@ -199,36 +201,46 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False):
def throw(msg, exc=ValidationError):
msgprint(msg, raise_exception=exc)

def create_folder(path):
if not os.path.exists(path): os.makedirs(path)
def create_folder(path, with_init=False):
from frappe.utils import touch_file
if not os.path.exists(path):
os.makedirs(path)

if with_init:
touch_file(os.path.join(path, "__init__.py"))

def set_user(username):
from frappe.utils.user import User
local.session.user = username
local.session.sid = username
local.cache = {}
local.form_dict = _dict()
local.jenv = None
local.session.data = {}
local.user = User(username)
local.restrictions = None
local.user_perms = {}
local.role_permissions = {}

def get_request_header(key, default=None):
return request.headers.get(key, default)

def sendmail(recipients=(), sender="", subject="No Subject", message="No Message",
as_markdown=False, bulk=False):
as_markdown=False, bulk=False, ref_doctype=None, ref_docname=None,
add_unsubscribe_link=False, attachments=None):

if bulk:
import frappe.utils.email_lib.bulk
frappe.utils.email_lib.bulk.send(recipients=recipients, sender=sender,
subject=subject, message=message, add_unsubscribe_link=False)
subject=subject, message=message, ref_doctype = ref_doctype,
ref_docname = ref_docname, add_unsubscribe_link=add_unsubscribe_link, attachments=attachments)

else:
import frappe.utils.email_lib
if as_markdown:
frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message)
frappe.utils.email_lib.sendmail_md(recipients, sender=sender,
subject=subject, msg=message, attachments=attachments)
else:
frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message)
frappe.utils.email_lib.sendmail(recipients, sender=sender,
subject=subject, msg=message, attachments=attachments)

logger = None
whitelisted = []
@@ -276,18 +288,27 @@ def clear_cache(user=None, doctype=None):
translate.clear_cache()
reset_metadata_version()

for fn in frappe.get_hooks("clear_cache"):
get_attr(fn)()

frappe.local.role_permissions = {}

def get_roles(username=None):
from frappe.utils.user import User
if not local.session:
return ["Guest"]
elif not username or username==local.session.user:
return local.user.get_roles()

return get_user(username).get_roles()

def get_user(username):
from frappe.utils.user import User
if not username or username == local.session.user:
return local.user
else:
return User(username).get_roles()
return User(username)

def has_permission(doctype, ptype="read", doc=None):
def has_permission(doctype, ptype="read", doc=None, user=None):
import frappe.permissions
return frappe.permissions.has_permission(doctype, ptype, doc)
return frappe.permissions.has_permission(doctype, ptype, doc, user=user)

def is_table(doctype):
tables = cache().get_value("is_table")
@@ -300,6 +321,9 @@ def clear_perms(doctype):
db.sql("""delete from tabDocPerm where parent=%s""", doctype)

def reset_perms(doctype):
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for
delete_notification_count_for(doctype)

clear_perms(doctype)
reload_doc(db.get_value("DocType", doctype, "module"),
"DocType", doctype, force=True)
@@ -307,7 +331,8 @@ def reset_perms(doctype):
def generate_hash(txt=None):
"""Generates random hash for session id"""
import hashlib, time
return hashlib.sha224((txt or "") + repr(time.time())).hexdigest()
from .utils import random_string
return hashlib.sha224((txt or "") + repr(time.time()) + repr(random_string(8))).hexdigest()

def reset_metadata_version():
v = generate_hash()
@@ -332,15 +357,7 @@ def get_meta(doctype, cached=True):

def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False):
import frappe.model.delete_doc

if not ignore_doctypes:
ignore_doctypes = []

if isinstance(name, list):
for n in name:
frappe.model.delete_doc.delete_doc(doctype, n, force, ignore_doctypes, for_reload, ignore_permissions)
else:
frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions)
frappe.model.delete_doc.delete_doc(doctype, name, force, ignore_doctypes, for_reload, ignore_permissions)

def delete_doc_if_exists(doctype, name):
if db.exists(doctype, name):
@@ -362,7 +379,7 @@ def get_module(modulename):
return importlib.import_module(modulename)

def scrub(txt):
return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower()
return txt.replace(' ','_').replace('-', '_').lower()

def unscrub(txt):
return txt.replace('_',' ').replace('-', ' ').title()
@@ -396,11 +413,26 @@ def get_all_apps(with_frappe=False, with_internal_apps=True, sites_path=None):
return apps

def get_installed_apps():
if flags.in_install_db:
if getattr(flags, "in_install_db", True):
return []
installed = json.loads(db.get_global("installed_apps") or "[]")
return installed

@whitelist()
def get_versions():
versions = {}
for app in get_installed_apps():
versions[app] = {
"title": get_hooks("app_title", app_name=app),
"description": get_hooks("app_description", app_name=app)
}
try:
versions[app]["version"] = get_attr(app + ".__version__")
except AttributeError:
versions[app]["version"] = '0.0.1'

return versions

def get_hooks(hook=None, default=None, app_name=None):
def load_app_hooks(app_name=None):
hooks = {}
@@ -449,6 +481,7 @@ def setup_module_map():
if app=="webnotes": app="frappe"
local.app_modules.setdefault(app, [])
for module in get_module_list(app):
module = scrub(module)
local.module_app[module] = app
local.app_modules[app].append(module)

@@ -456,10 +489,13 @@ def setup_module_map():
_cache.set_value("app_modules", local.app_modules)
_cache.set_value("module_app", local.module_app)

def get_file_items(path, raise_not_found=False):
def get_file_items(path, raise_not_found=False, ignore_empty_lines=True):
content = read_file(path, raise_not_found=raise_not_found)
if content:
return [p.strip() for p in content.splitlines() if p.strip() and not p.startswith("#")]
# \ufeff is no-width-break, \u200b is no-width-space
content = content.replace("\ufeff", "").replace("\u200b", "").strip()

return [p.strip() for p in content.splitlines() if (not ignore_empty_lines) or (p.strip() and not p.startswith("#"))]
else:
return []

@@ -494,9 +530,9 @@ def call(fn, *args, **kwargs):
newargs[a] = kwargs.get(a)
return fn(*args, **newargs)

def make_property_setter(args):
def make_property_setter(args, ignore_validate=False):
args = _dict(args)
get_doc({
ps = get_doc({
'doctype': "Property Setter",
'doctype_or_field': args.doctype_or_field or "DocField",
'doc_type': args.doctype,
@@ -505,13 +541,16 @@ def make_property_setter(args):
'value': args.value,
'property_type': args.property_type or "Data",
'__islocal': 1
}).save()
})
ps.ignore_validate = ignore_validate
ps.insert()

def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
from frappe.core.page.data_import_tool import data_import_tool
data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)

def copy_doc(doc):
""" No_copy fields also get copied."""
import copy
if not isinstance(doc, dict):
d = doc.as_dict()
@@ -523,6 +562,8 @@ def copy_doc(doc):
newdoc.set("__islocal", 1)
newdoc.owner = None
newdoc.creation = None
newdoc.amended_from = None
newdoc.amendment_date = None
for d in newdoc.get_all_children():
d.name = None
d.parent = None
@@ -540,7 +581,7 @@ def respond_as_web_page(title, html, success=None, http_status_code=None):
local.message = html
local.message_success = success
local.response['type'] = 'page'
local.response['page_name'] = 'message.html'
local.response['page_name'] = 'message'
if http_status_code:
local.response['http_status_code'] = http_status_code

@@ -550,70 +591,16 @@ def build_match_conditions(doctype, as_condition=True):

def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None,
group_by=None, order_by=None, limit_start=0, limit_page_length=None,
as_list=False, debug=False, ignore_permissions=False):
as_list=False, debug=False, ignore_permissions=False, user=None):
import frappe.model.db_query
return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters,
fields=fields, docstatus=docstatus, or_filters=or_filters,
group_by=group_by, order_by=order_by, limit_start=limit_start,
limit_page_length=limit_page_length, as_list=as_list, debug=debug,
ignore_permissions=ignore_permissions)
ignore_permissions=ignore_permissions, user=user)

run_query = get_list

def get_jenv():
if not local.jenv:
from jinja2 import Environment, DebugUndefined
import frappe.utils

# frappe will be loaded last, so app templates will get precedence
jenv = Environment(loader = get_jloader(), undefined=DebugUndefined)
set_filters(jenv)

jenv.globals.update({
"frappe": sys.modules[__name__],
"frappe.utils": frappe.utils,
"_": _
})

local.jenv = jenv

return local.jenv

def get_jloader():
if not local.jloader:
from jinja2 import ChoiceLoader, PackageLoader

apps = get_installed_apps()
apps.remove("frappe")

local.jloader = ChoiceLoader([PackageLoader(app, ".") \
for app in apps + ["frappe"]])

return local.jloader

def set_filters(jenv):
from frappe.utils import global_date_format
from frappe.website.utils import get_hex_shade
from markdown2 import markdown
from json import dumps

jenv.filters["global_date_format"] = global_date_format
jenv.filters["markdown"] = markdown
jenv.filters["json"] = dumps
jenv.filters["get_hex_shade"] = get_hex_shade

# load jenv_filters from hooks.txt
for app in get_all_apps(True):
for jenv_filter in (get_hooks(app_name=app).jenv_filter or []):
filter_name, filter_function = jenv_filter.split(":")
jenv.filters[filter_name] = get_attr(filter_function)

def get_template(path):
return get_jenv().get_template(path)

def get_website_route(doctype, name):
return db.get_value("Website Route", {"ref_doctype": doctype, "docname": name})

def add_version(doc):
get_doc({
"doctype": "Version",
@@ -630,3 +617,37 @@ def get_test_records(doctype):
return json.loads(f.read())
else:
return []

def format_value(value, df, doc=None, currency=None):
import frappe.utils.formatters
return frappe.utils.formatters.format_value(value, df, doc, currency=currency)

def get_print_format(doctype, name, print_format=None, style=None, as_pdf=False):
from frappe.website.render import build_page
local.form_dict.doctype = doctype
local.form_dict.name = name
local.form_dict.format = print_format
local.form_dict.style = style

html = build_page("print")

if as_pdf:
print_settings = db.get_singles_dict("Print Settings")
if int(print_settings.send_print_as_pdf or 0):
from utils.pdf import get_pdf
return get_pdf(html, {"page-size": print_settings.pdf_page_size})
else:
return html
else:
return html

logging_setup_complete = False
def get_logger(module=None):
from frappe.setup_logging import setup_logging
global logging_setup_complete

if not logging_setup_complete:
setup_logging()
logging_setup_complete = True

return logging.getLogger(module or "frappe")

+ 1
- 0
frappe/__version__.py Näytä tiedosto

@@ -0,0 +1 @@
__version__ = "4.3.0"

+ 6
- 5
frappe/api.py Näytä tiedosto

@@ -25,7 +25,7 @@ def handle():
DELETE will delete
/api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method
"""
parts = frappe.request.path[1:].split("/")
parts = frappe.request.path[1:].split("/",3)
call = doctype = name = None

if len(parts) > 1:
@@ -43,18 +43,20 @@ def handle():

elif call=="resource":
if "run_method" in frappe.local.form_dict:
method = frappe.local.form_dict.pop("run_method")
doc = frappe.get_doc(doctype, name)
doc.is_whitelisted(frappe.local.form_dict.run_method)
doc.is_whitelisted(method)

if frappe.local.request.method=="GET":
if not doc.has_permission("read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
doc.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict)
doc.run_method(method, **frappe.local.form_dict)

if frappe.local.request.method=="POST":
if not doc.has_permission("write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
doc.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict)

doc.run_method(method, **frappe.local.form_dict)
frappe.db.commit()

else:
@@ -76,7 +78,6 @@ def handle():
frappe.db.commit()

if frappe.local.request.method=="DELETE":
doc.update(data)
# Not checking permissions here because it's checked in delete_doc
frappe.delete_doc(doctype, name)
frappe.local.response.http_status_code = 202


+ 21
- 11
frappe/app.py Näytä tiedosto

@@ -3,6 +3,7 @@

import sys, os
import json
import logging

from werkzeug.wrappers import Request, Response
from werkzeug.local import LocalManager
@@ -25,6 +26,8 @@ local_manager = LocalManager([frappe.local])
_site = None
_sites_path = os.environ.get("SITES_PATH", ".")

logger = frappe.get_logger()

@Request.application
def application(request):
frappe.local.request = request
@@ -36,7 +39,7 @@ def application(request):

init_site(request)

if frappe.local.conf.get('maintainance_mode'):
if frappe.local.conf.get('maintenance_mode'):
raise frappe.SessionStopped

make_form_dict(request)
@@ -63,28 +66,35 @@ def application(request):
except frappe.SessionStopped, e:
response = frappe.utils.response.handle_session_stopped()

except (frappe.AuthenticationError,
frappe.PermissionError,
frappe.DoesNotExistError,
frappe.NameError,
frappe.OutgoingEmailError,
frappe.ValidationError,
frappe.UnsupportedMediaType), e:
except Exception, e:
http_status_code = getattr(e, "http_status_code", 500)

if frappe.local.is_ajax:
response = frappe.utils.response.report_error(e.http_status_code)
response = frappe.utils.response.report_error(http_status_code)
else:
response = frappe.website.render.render("error", e.http_status_code)
frappe.respond_as_web_page("Server Error",
"<pre>"+frappe.get_traceback()+"</pre>",
http_status_code=http_status_code)
response = frappe.website.render.render("message", http_status_code=http_status_code)

if e.__class__ == frappe.AuthenticationError:
if hasattr(frappe.local, "login_manager"):
frappe.local.login_manager.clear_cookies()

if http_status_code==500:
logger.error('Request Error')

else:
if frappe.local.request.method in ("POST", "PUT") and frappe.db:
frappe.db.commit()
rollback = False

# update session
if getattr(frappe.local, "session_obj", None):
updated_in_db = frappe.local.session_obj.update()
if updated_in_db:
frappe.db.commit()

finally:
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback:
frappe.db.rollback()
@@ -131,4 +141,4 @@ def serve(port=8000, profile=False, site=None, sites_path='.'):
})

run_simple('0.0.0.0', int(port), application, use_reloader=True,
use_debugger=True, use_evalex=True)
use_debugger=True, use_evalex=True, threaded=True)

+ 7
- 2
frappe/auth.py Näytä tiedosto

@@ -13,6 +13,8 @@ from frappe import conf
from frappe.sessions import Session, clear_sessions, delete_session
from frappe.modules.patch_handler import check_session_stopped

from urllib import quote

class HTTPRequest:
def __init__(self):
# Get Environment variables
@@ -20,6 +22,8 @@ class HTTPRequest:
if self.domain and self.domain.startswith('www.'):
self.domain = self.domain[4:]

frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') \
or frappe.get_request_header('X-Forwarded-For') or '127.0.0.1'
# language
self.set_lang(frappe.get_request_header('HTTP_ACCEPT_LANGUAGE'))

@@ -157,7 +161,7 @@ class LoginManager:
ip_list = [i.strip() for i in ip_list]

for ip in ip_list:
if frappe.get_request_header('REMOTE_ADDR', '').startswith(ip) or frappe.get_request_header('X-Forwarded-For', '').startswith(ip):
if frappe.local.request_ip.startswith(ip):
return

frappe.throw(_("Not allowed from this IP Address"), frappe.AuthenticationError)
@@ -223,7 +227,8 @@ class CookieManager:

def flush_cookies(self, response):
for key, opts in self.cookies.items():
response.set_cookie(key, opts.get("value"), expires=opts.get("expires"))
response.set_cookie(key, quote((opts.get("value") or "").encode('utf-8')),
expires=opts.get("expires"))

# expires yesterday!
expires = datetime.datetime.now() + datetime.timedelta(days=-1)


+ 47
- 20
frappe/boot.py Näytä tiedosto

@@ -9,6 +9,7 @@ bootstrap client session
import frappe
import frappe.defaults
import frappe.widgets.page
from frappe.utils import get_gravatar

def get_bootinfo():
"""build and return boot info"""
@@ -23,8 +24,6 @@ def get_bootinfo():
# system info
bootinfo['sysdefaults'] = frappe.defaults.get_defaults()
bootinfo['server_date'] = frappe.utils.nowdate()
bootinfo["send_print_in_body_and_attachment"] = frappe.db.get_value("Outgoing Email Settings",
None, "send_print_in_body_and_attachment")

if frappe.session['user'] != 'Guest':
bootinfo['user_info'] = get_fullnames()
@@ -37,6 +36,8 @@ def get_bootinfo():
bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.get_data")() or {})
except ImportError:
pass
except AttributeError:
pass

bootinfo.module_app = frappe.local.module_app
bootinfo.hidden_modules = frappe.db.get_global("hidden_modules")
@@ -48,7 +49,9 @@ def get_bootinfo():
add_home_page(bootinfo, doclist)
add_allowed_pages(bootinfo)
load_translations(bootinfo)
add_timezone_info(bootinfo)
load_conf_settings(bootinfo)
load_print(bootinfo, doclist)

# ipinfo
if frappe.session['data'].get('ipinfo'):
@@ -63,6 +66,8 @@ def get_bootinfo():
if bootinfo.lang:
bootinfo.lang = unicode(bootinfo.lang)

bootinfo.error_report_email = frappe.get_hooks("error_report_email")

return bootinfo

def load_conf_settings(bootinfo):
@@ -72,14 +77,23 @@ def load_conf_settings(bootinfo):

def add_allowed_pages(bootinfo):
roles = frappe.get_roles()
bootinfo.page_info = dict(frappe.db.sql("""select distinct parent, modified from `tabPage Role`
where role in (%s)""" % ', '.join(['%s']*len(roles)), roles))
bootinfo.page_info = {}
for p in frappe.db.sql("""select distinct
tabPage.name, tabPage.modified, tabPage.title
from `tabPage Role`, `tabPage`
where `tabPage Role`.role in (%s)
and `tabPage Role`.parent = `tabPage`.name""" % ', '.join(['%s']*len(roles)),
roles, as_dict=True):

bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title}

# pages where role is not set are also allowed
bootinfo.page_info.update(dict(frappe.db.sql("""select parent, modified
for p in frappe.db.sql("""select name, modified, title
from `tabPage` where
(select count(*) from `tabPage Role`
where `tabPage Role`.parent=tabPage.name) = 0""")))
where `tabPage Role`.parent=tabPage.name) = 0""", as_dict=1):

bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title}

def load_translations(bootinfo):
if frappe.local.lang != 'en':
@@ -90,18 +104,15 @@ def get_fullnames():
"""map of user fullnames"""
ret = frappe.db.sql("""select name,
concat(ifnull(first_name, ''),
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')),
user_image, gender, email
from tabUser where ifnull(enabled, 0)=1""", as_list=1)
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname,
user_image as image, gender, email
from tabUser where ifnull(enabled, 0)=1""", as_dict=1)

d = {}
for r in ret:
if not r[2]:
r[2] = '/assets/frappe/images/ui/avatar.png'
else:
r[2] = r[2]

d[r[0]]= {'fullname': r[1], 'image': r[2], 'gender': r[3],
'email': r[4] or r[0]}
if not r.image:
r.image = get_gravatar()
d[r.name] = r

return d

@@ -113,16 +124,13 @@ def get_startup_js():

def get_user(bootinfo):
"""get user info"""
bootinfo['user'] = frappe.user.load_user()
bootinfo.user = frappe.user.load_user()

def add_home_page(bootinfo, docs):
"""load home page"""

if frappe.session.user=="Guest":
return

home_page = frappe.db.get_default("desktop:home_page")

try:
page = frappe.widgets.page.get(home_page)
except (frappe.DoesNotExistError, frappe.PermissionError):
@@ -131,3 +139,22 @@ def add_home_page(bootinfo, docs):

bootinfo['home_page'] = page.name
docs.append(page)

def add_timezone_info(bootinfo):
user = bootinfo.user.get("time_zone")
system = bootinfo.sysdefaults.get("time_zone")
if user and user != system:
import frappe.utils.momentjs
bootinfo.timezone_info = {"zones":{}, "rules":{}, "links":{}}

frappe.utils.momentjs.update(user, bootinfo.timezone_info)
frappe.utils.momentjs.update(system, bootinfo.timezone_info)

def load_print(bootinfo, doclist):
print_settings = frappe.db.get_singles_dict("Print Settings")
print_settings.doctype = ":Print Settings"
doclist.append(print_settings)
load_print_css(bootinfo, print_settings)

def load_print_css(bootinfo, print_settings):
bootinfo.print_css = frappe.get_attr("frappe.templates.pages.print.get_print_style")(print_settings.print_style or "Modern")

+ 37
- 24
frappe/build.py Näytä tiedosto

@@ -1,5 +1,5 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt

from __future__ import unicode_literals
from frappe.utils.minify import JavascriptMinify
@@ -11,12 +11,12 @@ Build the `public` folders and setup languages
import os, sys, frappe, json, shutil
from cssmin import cssmin

def bundle(no_compress, make_copy=False):
def bundle(no_compress, make_copy=False, verbose=False):
"""concat / minify js files"""
# build js files
make_asset_dirs(make_copy=make_copy)
build(no_compress)
build(no_compress, verbose)
def watch(no_compress):
"""watch and rebuild if necessary"""
import time
@@ -25,18 +25,18 @@ def watch(no_compress):
while True:
if files_dirty():
build(no_compress=True)
time.sleep(3)

def make_asset_dirs(make_copy=False):
assets_path = os.path.join(frappe.local.sites_path, "assets")
for dir_path in [
os.path.join(assets_path, 'js'),
os.path.join(assets_path, 'js'),
os.path.join(assets_path, 'css')]:
if not os.path.exists(dir_path):
os.makedirs(dir_path)
# symlink app/public > assets/app
for app_name in frappe.get_all_apps(True):
pymodule = frappe.get_module(app_name)
@@ -49,11 +49,11 @@ def make_asset_dirs(make_copy=False):
else:
os.symlink(os.path.abspath(source), target)

def build(no_compress=False):
def build(no_compress=False, verbose=False):
assets_path = os.path.join(frappe.local.sites_path, "assets")

for target, sources in get_build_maps().iteritems():
pack(os.path.join(assets_path, target), sources, no_compress)
pack(os.path.join(assets_path, target), sources, no_compress, verbose)

shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path)
# reset_app_html()
@@ -79,39 +79,52 @@ def get_build_maps():
else:
s = os.path.join(app_path, source)
source_paths.append(s)
build_maps[target] = source_paths
except Exception, e:
print path
raise
return build_maps

timestamps = {}

def pack(target, sources, no_compress):
def pack(target, sources, no_compress, verbose):
from cStringIO import StringIO
outtype, outtxt = target.split(".")[-1], ''
jsm = JavascriptMinify()
for f in sources:
suffix = None
if ':' in f: f, suffix = f.split(':')
if not os.path.exists(f) or os.path.isdir(f): continue
if not os.path.exists(f) or os.path.isdir(f):
print "did not find " + f
continue
timestamps[f] = os.path.getmtime(f)
try:
with open(f, 'r') as sourcefile:
with open(f, 'r') as sourcefile:
data = unicode(sourcefile.read(), 'utf-8', errors='ignore')
if outtype=="js" and (not no_compress) and suffix!="concat" and (".min." not in f):

extn = f.rsplit(".", 1)[1]

if outtype=="js" and extn=="js" and (not no_compress) and suffix!="concat" and (".min." not in f):
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
jsm.minify(tmpin, tmpout)
outtxt += unicode(tmpout.getvalue() or '', 'utf-8').strip('\n') + ';'
minified = tmpout.getvalue()
outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';'

if verbose:
print "{0}: {1}k".format(f, int(len(minified) / 1024))
elif outtype=="js" and extn=="html":
# add to frappe.templates
content = data.replace("\n", " ").replace("'", "\'")
outtxt += """frappe.templates["{key}"] = '{content}';\n""".format(\
key=f.rsplit("/", 1)[1][:-5], content=content)
else:
outtxt += ('\n/*\n *\t%s\n */' % f)
outtxt += '\n' + data + '\n'
except Exception, e:
print "--Error in:" + f + "--"
print frappe.get_traceback()
@@ -119,10 +132,10 @@ def pack(target, sources, no_compress):
if not no_compress and outtype == 'css':
pass
#outtxt = cssmin(outtxt)
with open(target, 'w') as f:
f.write(outtxt.encode("utf-8"))
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024)))

def files_dirty():
@@ -135,4 +148,4 @@ def files_dirty():
return True
else:
return False

+ 117
- 36
frappe/cli.py Näytä tiedosto

@@ -7,6 +7,7 @@ from __future__ import unicode_literals
import os
import subprocess
import frappe
from frappe.utils import cint

site_arg_optional = ['serve', 'build', 'watch', 'celery', 'resize_images']

@@ -35,7 +36,10 @@ def main():
args = parsed_args.copy()
args["site"] = site
frappe.init(site, sites_path=sites_path)
return run(fn, args)
ret = run(fn, args)
if ret:
# if there's a return value, it's an error, so quit
return ret
else:
site = get_site(parsed_args)
if fn not in site_arg_optional and not site:
@@ -128,18 +132,20 @@ def setup_install(parser):
help="Make a new application with boilerplate")
parser.add_argument("--install", metavar="DB-NAME", nargs=1,
help="Install a new db")
parser.add_argument("--root_password", metavar="ROOT-PASSWD",
help="MariaDB root password")
parser.add_argument("--sites_path", metavar="SITES_PATH", nargs=1,
help="path to directory with sites")
parser.add_argument("--install_app", metavar="APP-NAME", nargs=1,
help="Install a new app")
parser.add_argument("--add_to_installed_apps", metavar="APP-NAME", nargs="*",
help="Add these app(s) to Installed Apps")
parser.add_argument("--root-password", nargs=1,
help="Root password for new app")
parser.add_argument("--reinstall", default=False, action="store_true",
help="Install a fresh app in db_name specified in conf.py")
parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2,
help="Restore from an sql file")
parser.add_argument("--with_scheduler_enabled", default=False, action="store_true",
help="Enable scheduler on restore")
parser.add_argument("--add_system_manager", nargs="+",
metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles")

@@ -201,8 +207,10 @@ def setup_utilities(parser):
parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"),
help="Create new conf.py file")
parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE",
help="Create new conf.py file")
parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs=1,
help="Create new custome server script")
parser.add_argument("--init_list", nargs=1, metavar="DOCTYPE",
help="Create new list.js and list.html files")
parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs="*",
help="Set administrator password")
parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin")
parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site")
@@ -224,8 +232,8 @@ def setup_utilities(parser):
# clear
parser.add_argument("--clear_web", default=False, action="store_true",
help="Clear website cache")
parser.add_argument("--build_sitemap", default=False, action="store_true",
help="Build Website Route")
parser.add_argument("--build_website", default=False, action="store_true",
help="Sync statics and clear cache")
parser.add_argument("--sync_statics", default=False, action="store_true",
help="Sync files from templates/statics to Web Pages")
parser.add_argument("--clear_cache", default=False, action="store_true",
@@ -242,6 +250,11 @@ def setup_utilities(parser):
parser.add_argument("--run_scheduler_event", nargs=1,
metavar="all | daily | weekly | monthly",
help="Run a scheduler event")
parser.add_argument("--enable_scheduler", default=False, action="store_true",
help="Enable scheduler")
parser.add_argument("--disable_scheduler", default=False, action="store_true",
help="Disable scheduler")


# replace
parser.add_argument("--replace", nargs=3,
@@ -280,21 +293,52 @@ def use(sites_path):
sitefile.write(frappe.local.site)

# install
@cmd
def install(db_name, root_login="root", root_password=None, source_sql=None,
admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False):
def _install(db_name, root_login="root", root_password=None, source_sql=None,
admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None):
from frappe.installer import install_db, install_app, make_site_dirs
import frappe.utils.scheduler

verbose = not quiet

# enable scheduler post install?
enable_scheduler = _is_scheduler_enabled()

install_db(root_login=root_login, root_password=root_password, db_name=db_name, source_sql=source_sql,
admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall)
make_site_dirs()
install_app("frappe", verbose=verbose, set_as_patched=not source_sql)

if frappe.conf.get("install_apps"):
for app in frappe.conf.install_apps:
install_app(app, verbose=verbose, set_as_patched=not source_sql)

if install_apps:
for app in install_apps:
install_app(app, verbose=verbose, set_as_patched=not source_sql)

frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
print "*** Scheduler is", scheduler_status, "***"

@cmd
def install(db_name, root_login="root", root_password=None, source_sql=None,
admin_password = 'admin', force=False, site_config=None, reinstall=False, quiet=False, install_apps=None):
_install(db_name, root_login, root_password, source_sql, admin_password, force, site_config, reinstall, quiet, install_apps)
frappe.destroy()

def _is_scheduler_enabled():
enable_scheduler = False
try:
frappe.connect()
enable_scheduler = cint(frappe.db.get_default("enable_scheduler"))
except:
pass
finally:
frappe.db.close()

return enable_scheduler

@cmd
def install_app(app_name, quiet=False):
verbose = not quiet
@@ -310,7 +354,7 @@ def add_to_installed_apps(*apps):
all_apps = frappe.get_all_apps(with_frappe=True)
for each in apps:
if each in all_apps:
add_to_installed_apps(each, rebuild_sitemap=False)
add_to_installed_apps(each, rebuild_website=False)
frappe.destroy()

@cmd
@@ -318,18 +362,27 @@ def reinstall(quiet=False):
verbose = not quiet
try:
frappe.connect()
installed = frappe.get_installed_apps()
frappe.clear_cache()
except:
pass
installed = []
finally:
frappe.db.close()

install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True)
install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True, install_apps=installed)

@cmd
def restore(db_name, source_sql, force=False, quiet=False):
verbose = not quiet
install(db_name, source_sql=source_sql, verbose=verbose, force=force)
def restore(db_name, source_sql, force=False, quiet=False, with_scheduler_enabled=False):
import frappe.utils.scheduler
_install(db_name, source_sql=source_sql, quiet=quiet, force=force)

try:
frappe.connect()
frappe.utils.scheduler.toggle_scheduler(with_scheduler_enabled)
scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
print "*** Scheduler is", scheduler_status, "***"
finally:
frappe.destroy()

@cmd
def add_system_manager(email, first_name=None, last_name=None):
@@ -353,13 +406,12 @@ def update(remote=None, branch=None, reload_gunicorn=False):
subprocess.check_output("killall -HUP gunicorn".split())

@cmd
def latest(rebuild_website_config=True, quiet=False):
def latest(rebuild_website=True, quiet=False):
import frappe.modules.patch_handler
import frappe.model.sync
from frappe.website import rebuild_config
from frappe.utils.fixtures import sync_fixtures
import frappe.translate
from frappe.website import statics
from frappe.core.doctype.notification_count.notification_count import clear_notifications

verbose = not quiet

@@ -370,16 +422,13 @@ def latest(rebuild_website_config=True, quiet=False):
frappe.modules.patch_handler.run_all()
# sync
frappe.model.sync.sync_all(verbose=verbose)
frappe.translate.clear_cache()
sync_fixtures()

statics.sync().start()
# build website config if any changes in templates etc.
if rebuild_website_config:
rebuild_config()


frappe.translate.clear_cache()
clear_notifications()

if rebuild_website:
build_website()
finally:
frappe.destroy()

@@ -419,10 +468,10 @@ def reload_doc(module, doctype, docname, force=False):
frappe.destroy()

@cmd
def build(make_copy=False):
def build(make_copy=False, verbose=False):
import frappe.build
import frappe
frappe.build.bundle(False, make_copy=make_copy)
frappe.build.bundle(False, make_copy=make_copy, verbose=verbose)

@cmd
def watch():
@@ -487,12 +536,19 @@ def make_custom_server_script(doctype):
make_custom_server_script_file(doctype)
frappe.destroy()

@cmd
def init_list(doctype):
import frappe.core.doctype.doctype.doctype
frappe.core.doctype.doctype.doctype.init_list(doctype)

# clear
@cmd
def clear_cache():
import frappe.sessions
from frappe.core.doctype.notification_count.notification_count import clear_notifications
frappe.connect()
frappe.clear_cache()
clear_notifications()
frappe.destroy()

@cmd
@@ -511,17 +567,19 @@ def clear_all_sessions():
frappe.destroy()

@cmd
def build_sitemap():
from frappe.website import rebuild_config
def build_website(verbose=False):
from frappe.website import render, statics
frappe.connect()
rebuild_config()
render.clear_cache()
statics.sync(verbose=verbose).start()
frappe.db.commit()
frappe.destroy()

@cmd
def sync_statics():
def sync_statics(force=False):
from frappe.website import statics
frappe.connect()
statics.sync_statics()
statics.sync_statics(rebuild = force)
frappe.db.commit()
frappe.destroy()

@@ -558,6 +616,24 @@ def run_scheduler_event(event, force=False):
frappe.utils.scheduler.trigger(frappe.local.site, event, now=force)
frappe.destroy()

@cmd
def enable_scheduler():
import frappe.utils.scheduler
frappe.connect()
frappe.utils.scheduler.enable_scheduler()
frappe.db.commit()
print "Enabled"
frappe.destroy()

@cmd
def disable_scheduler():
import frappe.utils.scheduler
frappe.connect()
frappe.utils.scheduler.disable_scheduler()
frappe.db.commit()
print "Disabled"
frappe.destroy()

# replace
@cmd
def replace(search_regex, replacement, extn, force=False):
@@ -665,8 +741,13 @@ def checkout(branch):
git(("checkout", branch))

@cmd
def set_admin_password(admin_password):
def set_admin_password(admin_password=None):
import frappe
import getpass

while not admin_password:
admin_password = getpass.getpass("Administrator's password: ")

frappe.connect()
frappe.db.sql("""update __Auth set `password`=password(%s)
where user='Administrator'""", (admin_password,))
@@ -706,7 +787,7 @@ def smtp_debug_server():
os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"])

@cmd
def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None):
def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driver=None, force=False):
import frappe.test_runner
from frappe.utils import sel

@@ -715,7 +796,7 @@ def run_tests(app=None, module=None, doctype=None, verbose=False, tests=(), driv
ret = 1
try:
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose,
tests=tests)
tests=tests, force=force)
if len(ret.failures) == 0 and len(ret.errors) == 0:
ret = 0
finally:


+ 11
- 4
frappe/client.py Näytä tiedosto

@@ -42,7 +42,11 @@ def set_value(doctype, name, fieldname, value):
child.set(fieldname, value)
else:
doc = frappe.get_doc(doctype, name)
doc.set(fieldname, value)
df = doc.meta.get_field(fieldname)
if df.fieldtype == "Read Only" or df.read_only:
frappe.throw(_("Can not edit Read Only fields"))
else:
doc.set(fieldname, value)

doc.save()

@@ -88,7 +92,7 @@ def submit(doclist):
doclistobj = frappe.get_doc(doclist)
doclistobj.submit()

return doclist.as_dict()
return doclistobj.as_dict()

@frappe.whitelist()
def cancel(doctype, name):
@@ -108,8 +112,9 @@ def set_default(key, value, parent=None):
frappe.clear_cache(user=frappe.session.user)

@frappe.whitelist()
def make_width_property_setter():
doc = json.loads(frappe.form_dict)
def make_width_property_setter(doc):
if isinstance(doc, basestring):
doc = json.loads(doc)
if doc["doctype"]=="Property Setter" and doc["property"]=="width":
frappe.get_doc(doc).insert(ignore_permissions = True)

@@ -139,6 +144,8 @@ def has_permission(doctype, docname, perm_type="read"):

@frappe.whitelist()
def get_js(src):
if src[0]=="/":
src = src[1:]
contentpath = os.path.join(frappe.local.sites_path, src)
with open(contentpath, "r") as srcfile:
code = frappe.utils.cstr(srcfile.read())


+ 29
- 11
frappe/config/setup.py Näytä tiedosto

@@ -20,17 +20,25 @@ def get_data():
{
"type": "page",
"name": "permission-manager",
"label": "Permission Manager",
"label": _("Role Permissions Manager"),
"icon": "icon-lock",
"description": _("Set Permissions on Document Types and Roles")
},
{
"type": "page",
"name": "user-properties",
"label": _("User Permission Restrictions"),
"icon": "icon-user",
"description": _("Set Defaults and Restrictions for Users")
},
"name": "user-permissions",
"label": _("User Permissions Manager"),
"icon": "icon-shield",
"description": _("Set Permissions per User")
},
{
"type": "report",
"is_query_report": True,
"doctype": "User",
"icon": "icon-eye-open",
"name": "Permitted Documents For User",
"description": _("Check which Documents are readable by a User")
}
]
},
{
@@ -113,12 +121,27 @@ def get_data():
"name": "Outgoing Email Settings",
"description": _("Set outgoing mail server.")
},
{
"type": "doctype",
"name": "Email Alert",
"description": _("Setup Email Alert based on various criteria.")
},
{
"type": "doctype",
"name": "Standard Reply",
"description": _("Standard replies to common queries.")
},
]
},
{
"label": _("Printing and Branding"),
"icon": "icon-print",
"items": [
{
"type": "doctype",
"name": "Print Settings",
"description": _("Set default format, page size, print style etc.")
},
{
"type": "doctype",
"name": "Print Format",
@@ -168,11 +191,6 @@ def get_data():
"description": _("Send download link of a recent backup to System Managers"),
"hide_count": True
},
{
"type": "doctype",
"name": "Social Login Keys",
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
},
{
"type": "doctype",
"name": "Backup Manager",


+ 10
- 7
frappe/config/website.py Näytä tiedosto

@@ -16,6 +16,11 @@ def get_data():
"name": "Blog Post",
"description": _("Single Post (article)."),
},
{
"type": "doctype",
"name": "Web Form",
"description": _("User editable form on Website."),
},
{
"type": "doctype",
"name": "Blogger",
@@ -47,13 +52,6 @@ def get_data():
"name": "Website Settings",
"description": _("Setup of top navigation bar, footer and logo."),
},
{
"type": "page",
"name":"sitemap-browser",
"label": _("Sitemap Browser"),
"description": _("View or manage Website Route tree."),
"icon": "icon-sitemap"
},
{
"type": "doctype",
"name": "Style Settings",
@@ -89,6 +87,11 @@ def get_data():
"name": "Website Page Permission",
"description": _("Define read, write, admin permissions for a Website Page."),
},
{
"type": "doctype",
"name": "Social Login Keys",
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
}
]
},
]

+ 10
- 3
frappe/core/doctype/bulk_email/bulk_email.json Näytä tiedosto

@@ -1,5 +1,6 @@
{
"creation": "2012-08-02 15:17:28.000000",
"autoname": "hash",
"creation": "2012-08-02 15:17:28",
"description": "Bulk Email records.",
"docstatus": 0,
"doctype": "DocType",
@@ -8,30 +9,35 @@
{
"fieldname": "sender",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Sender",
"permlevel": 0
},
{
"fieldname": "recipient",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Recipient",
"permlevel": 0
},
{
"fieldname": "message",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Message",
"permlevel": 0
},
{
"fieldname": "status",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Status",
"permlevel": 0
},
{
"fieldname": "error",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Error",
"permlevel": 0
},
@@ -56,7 +62,7 @@
"icon": "icon-envelope",
"idx": 1,
"in_create": 1,
"modified": "2014-02-12 21:11:05.000000",
"modified": "2014-06-03 02:22:18.860832",
"modified_by": "Administrator",
"module": "Core",
"name": "Bulk Email",
@@ -67,8 +73,9 @@
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager"
}
],
"read_only": 1
"read_only": 0
}

+ 15
- 3
frappe/core/doctype/comment/comment.json Näytä tiedosto

@@ -1,22 +1,31 @@
{
"autoname": "CWR/.#####",
"creation": "2012-08-08 10:40:11.000000",
"autoname": "hash",
"creation": "2012-08-08 10:40:11",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"fieldname": "comment",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Comment",
"no_copy": 0,
"oldfieldname": "comment",
"oldfieldtype": "Text",
"permlevel": 0,
"reqd": 1,
"search_index": 0
},
{
"fieldname": "comment_type",
"fieldtype": "Data",
"label": "Comment Type",
"permlevel": 0
},
{
"fieldname": "comment_by",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Comment By",
"no_copy": 0,
"oldfieldname": "comment_by",
@@ -27,6 +36,7 @@
{
"fieldname": "comment_by_fullname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Comment By Fullname",
"no_copy": 0,
"oldfieldname": "comment_by_fullname",
@@ -37,6 +47,7 @@
{
"fieldname": "comment_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Comment Date",
"no_copy": 0,
"oldfieldname": "comment_date",
@@ -47,6 +58,7 @@
{
"fieldname": "comment_time",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Comment Time",
"no_copy": 0,
"oldfieldname": "comment_time",
@@ -94,7 +106,7 @@
"icon": "icon-comments",
"idx": 1,
"issingle": 0,
"modified": "2014-01-24 13:00:20.000000",
"modified": "2014-08-22 05:24:28.072749",
"modified_by": "Administrator",
"module": "Core",
"name": "Comment",


+ 7
- 7
frappe/core/doctype/comment/comment.py Näytä tiedosto

@@ -19,7 +19,7 @@ class Comment(Document):
self.update_comment_in_doc()

def update_comment_in_doc(self):
if self.comment_doctype and self.comment_docname and self.comment:
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment":
try:
_comments = self.get_comments_from_parent()
updated = False
@@ -59,14 +59,14 @@ class Comment(Document):
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype,
"%s", "%s"), (json.dumps(_comments), self.comment_docname))

# clear parent cache if route exists:
route = frappe.db.get_value("Website Route", {"ref_doctype": self.comment_doctype,
"docname": self.comment_docname})

if route:
clear_cache(route)
comment_doc = frappe.get_doc(self.comment_doctype, self.comment_docname)
if getattr(comment_doc, "get_route", None):
clear_cache(comment_doc.get_route())

def on_trash(self):
if (self.comment_type or "Comment") != "Comment":
frappe.only_for("System Manager")

_comments = self.get_comments_from_parent()
for c in _comments:
if c.get("name")==self.name:


+ 7
- 0
frappe/core/doctype/comment/test_records.json Näytä tiedosto

@@ -0,0 +1,7 @@
[
{
"doctype": "Comment",
"name": "_Test Comment 1",
"comment": "test comment"
}
]

+ 6
- 9
frappe/core/doctype/communication/communication.json Näytä tiedosto

@@ -1,8 +1,7 @@
{
"allow_attach": 1,
"allow_import": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-01-29 10:47:14.000000",
"creation": "2013-01-29 10:47:14",
"description": "Keep a track of all communications",
"docstatus": 0,
"doctype": "DocType",
@@ -122,6 +121,7 @@
"default": "__user",
"fieldname": "user",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "User",
"options": "User",
"permlevel": 0,
@@ -154,7 +154,7 @@
"idx": 1,
"in_dialog": 0,
"issingle": 0,
"modified": "2014-01-24 13:01:25.000000",
"modified": "2014-08-14 09:39:23.219125",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",
@@ -162,7 +162,7 @@
"permissions": [
{
"amend": 0,
"cancel": 0,
"apply_user_permissions": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -176,7 +176,6 @@
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -190,7 +189,7 @@
},
{
"amend": 0,
"cancel": 0,
"apply_user_permissions": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -203,7 +202,6 @@
"write": 1
},
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -216,7 +214,6 @@
"write": 1
},
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,


+ 40
- 20
frappe/core/doctype/communication/communication.py Näytä tiedosto

@@ -16,14 +16,17 @@ from frappe import _
from frappe.model.document import Document

class Communication(Document):
def validate(self):
if not self.parentfield:
self.parentfield = "communications"

def get_parent_doc(self):
return frappe.get_doc(self.parenttype, self.parent)

def update_parent(self):
"""update status of parent Lead or Contact based on who is replying"""
observer = getattr(self.get_parent_doc(), "on_communication", None)
if observer:
observer()
parent_doc = self.get_parent_doc()
parent_doc.run_method("on_communication")

def on_update(self):
self.update_parent()
@@ -31,7 +34,7 @@ class Communication(Document):
@frappe.whitelist()
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None):
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None):

if doctype and name and not frappe.has_permission(doctype, "email", name):
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
@@ -39,12 +42,12 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =

_make(doctype=doctype, name=name, content=content, subject=subject, sent_or_received=sent_or_received,
sender=sender, recipients=recipients, communication_medium=communication_medium, send_email=send_email,
print_html=print_html, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead,
print_html=print_html, print_format=print_format, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead,
date=date)

def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None):
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None):

# add to Communication
sent_via = None
@@ -86,7 +89,7 @@ def _make(doctype=None, name=None, content=None, subject=None, sent_or_received

if send_email:
d = comm
send_comm_email(d, name, sent_via, print_html, attachments, send_me_a_copy)
send_comm_email(d, name, sent_via, print_html, print_format, attachments, send_me_a_copy)

@frappe.whitelist()
def get_customer_supplier(args=None):
@@ -107,9 +110,10 @@ def get_customer_supplier(args=None):
}
return {}

def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', send_me_a_copy=False):
def send_comm_email(d, name, sent_via=None, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False):
footer = None


if sent_via:
if hasattr(sent_via, "get_sender"):
d.sender = sent_via.get_sender(d) or d.sender
@@ -118,21 +122,16 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s
if hasattr(sent_via, "get_content"):
d.content = sent_via.get_content(d)

footer = set_portal_link(sent_via, d)

send_print_in_body = frappe.db.get_value("Outgoing Email Settings", None, "send_print_in_body_and_attachment")
if print_html and not send_print_in_body:
d.content += "<p>Please see attachment for document details.</p>"
footer = "<hr>" + set_portal_link(sent_via, d)

mail = get_email(d.recipients, sender=d.sender, subject=d.subject,
msg=d.content, footer=footer, print_html=print_html if send_print_in_body else None)
msg=d.content, footer=footer)

if send_me_a_copy:
mail.cc.append(frappe.db.get_value("User", frappe.session.user, "email"))

if print_html:
print_html = scrub_urls(print_html)
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', print_html)
if print_html or print_format:
attach_print(mail, sent_via, print_html, print_format)

for a in json.loads(attachments):
try:
@@ -142,10 +141,31 @@ def send_comm_email(d, name, sent_via=None, print_html=None, attachments='[]', s

send(mail)

def attach_print(mail, sent_via, print_html, print_format):
name = sent_via.name
if not print_html and print_format:
print_html = frappe.get_print_format(sent_via.doctype, sent_via.name, print_format)

print_settings = frappe.db.get_singles_dict("Print Settings")
send_print_as_pdf = cint(print_settings.send_print_as_pdf)

if send_print_as_pdf:
try:
mail.add_pdf_attachment(name.replace(' ','').replace('/','-') + '.pdf', print_html)
except Exception:
frappe.msgprint(_("Error generating PDF, attachment sent as HTML"))
frappe.errprint(frappe.get_traceback())
send_print_as_pdf = 0

if not send_print_as_pdf:
print_html = scrub_urls(print_html)
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html',
print_html, 'text/html')

def set_portal_link(sent_via, comm):
"""set portal link in footer"""

footer = None
footer = ""

if is_signup_enabled() and hasattr(sent_via, "get_portal_page"):
portal_page = sent_via.get_portal_page()
@@ -154,7 +174,7 @@ def set_portal_link(sent_via, comm):
sent_via.get("contact_email")) in comm.recipients
if is_valid_recipient:
url = "%s/%s?name=%s" % (get_url(), portal_page, urllib.quote(sent_via.name))
footer = """<!-- Portal Link --><hr>
<a href="%s" target="_blank">View this on our website</a>""" % url
footer = """<!-- Portal Link -->
<p><a href="%s" target="_blank">View this on our website</a></p>""" % url

return footer

+ 37
- 0
frappe/core/doctype/communication/communication_list.html Näytä tiedosto

@@ -0,0 +1,37 @@
<div class="row" style="max-height: 30px;">
<div class="col-xs-10">
<div class="text-ellipsis">
{%= list.get_avatar_and_id(doc) %}

<!-- sample icon -->
<span style="margin-right: 8px;"
title="{%= __(doc.sent_or_received) %}" class="filterable"
data-filter="sent_or_received,=,{%= doc.sent_or_received %}">
<i class="icon-{%= doc.sent_or_received=="Sent" ?
"arrow-right" : "arrow-left" %} text-muted"></i>
</span>

<span style="margin-right: 8px;" class="filterable"
title="{%= doc.communication_medium %}"
data-filter="communication_medium,=,{%= doc.communication_medium %}">
<i class="icon-{%= {
"Chat": "comments",
"Phone": "phone",
"Email": "envelope",
"SMS": "comment",
"Visit": "male",
"Other": "comments"
}[doc.communication_medium] || "comments" %}"></i>
</span>

</div>
</div>
<div class="col-xs-2">
<div class="text-ellipsis">
<span style="margin-right: 8px;" class="filterable small"
data-filter="recipients,=,{%= doc.recipients %}" title="{%= doc.recipients %}">
{%= doc.recipients %}</span>

</div>
</div>
</div>

+ 3
- 0
frappe/core/doctype/communication/communication_list.js Näytä tiedosto

@@ -0,0 +1,3 @@
frappe.listview_settings['Communication'] = {
add_fields: ["sent_or_received", "recipients", "subject", "communication_medium"]
};

+ 10
- 0
frappe/core/doctype/communication/test_communication.py Näytä tiedosto

@@ -0,0 +1,10 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# See license.txt

import frappe
import unittest

test_records = frappe.get_test_records('Communication')

class TestCommunication(unittest.TestCase):
pass

+ 10
- 0
frappe/core/doctype/communication/test_records.json Näytä tiedosto

@@ -0,0 +1,10 @@
[
{
"doctype": "Communication",
"name": "_Test Communication 1",
"subject": "Test Subject",
"sent_or_received": "Received",
"parenttype": "User",
"parent": "Administrator"
}
]

+ 12
- 3
frappe/core/doctype/custom_field/custom_field.js Näytä tiedosto

@@ -47,9 +47,18 @@ cur_frm.fields_dict['dt'].get_query = function(doc, dt, dn) {
}

cur_frm.cscript.fieldtype = function(doc, dt, dn) {
if(doc.fieldtype == 'Link') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter name of the document you want this field to be linked to in <b>Options</b>.<br> Eg.: Customer';
else if(doc.fieldtype == 'Select') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter values in <b>Options</b>, with each option on a new line. <br>Eg.: <b>Field:</b> Country <br><b>Options:</b><br>China<br>India<br>United States<br><br><b>';
else cur_frm.fields_dict['options_help'].disp_area.innerHTML = '';
if(doc.fieldtype == 'Link') {
cur_frm.fields_dict['options_help'].disp_area.innerHTML =
__('Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer');
} else if(doc.fieldtype == 'Select') {
cur_frm.fields_dict['options_help'].disp_area.innerHTML =
__('Options for select. Each option on a new line. e.g.: <br>Option 1<br>Option 2<br>Option 3<br>');
} else if(doc.fieldtype == 'Dynamic Link') {
cur_frm.fields_dict['options_help'].disp_area.innerHTML =
__('Fieldname which will be the DocType for this link field.');
} else {
cur_frm.fields_dict['options_help'].disp_area.innerHTML = '';
}
}




+ 15
- 5
frappe/core/doctype/custom_field/custom_field.json Näytä tiedosto

@@ -1,5 +1,5 @@
{
"creation": "2013-01-10 16:34:01.000000",
"creation": "2013-01-10 16:34:01",
"description": "Adds a custom field to a DocType",
"docstatus": 0,
"doctype": "DocType",
@@ -57,11 +57,21 @@
"no_copy": 0,
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"permlevel": 0,
"reqd": 1,
"search_index": 0
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n1\n2\n3\n4\n5\n6",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "options_help",
"fieldtype": "HTML",
@@ -143,9 +153,9 @@
},
{
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_restrictions",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore Restrictions",
"label": "Ignore User Permissions",
"permlevel": 0
},
{
@@ -257,7 +267,7 @@
],
"icon": "icon-glass",
"idx": 1,
"modified": "2014-01-20 17:48:31.000000",
"modified": "2014-09-05 07:41:13.076820",
"modified_by": "Administrator",
"module": "Core",
"name": "Custom Field",


+ 21
- 16
frappe/core/doctype/custom_field/custom_field.py Näytä tiedosto

@@ -33,10 +33,10 @@ class CustomField(Document):
# validate field
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype

validate_fields_for_doctype(self.dt)

frappe.clear_cache(doctype=self.dt)

validate_fields_for_doctype(self.dt)

# create property setter to emulate insert after
self.create_property_setter()

@@ -78,22 +78,27 @@ class CustomField(Document):

@frappe.whitelist()
def get_fields_label(doctype=None):
return [{"value": df.fieldname, "label": _(df.label)} for df in frappe.get_meta(doctype).get("fields")]
return [{"value": df.fieldname or "", "label": _(df.label or "")} for df in frappe.get_meta(doctype).get("fields")]

def create_custom_field_if_values_exist(doctype, df):
df = frappe._dict(df)
if df.fieldname in frappe.db.get_table_columns(doctype) and \
frappe.db.sql("""select count(*) from `tab{doctype}`
where ifnull({fieldname},'')!=''""".format(doctype=doctype, fieldname=df.fieldname))[0][0] and \
not frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df.fieldname}):
frappe.get_doc({
"doctype":"Custom Field",
"dt": doctype,
"permlevel": df.permlevel or 0,
"label": df.label,
"fieldname": df.fieldname,
"fieldtype": df.fieldtype,
"options": df.options,
"insert_after": df.insert_after
}).insert()

where ifnull({fieldname},'')!=''""".format(doctype=doctype, fieldname=df.fieldname))[0][0]:

create_custom_field(doctype, df)


def create_custom_field(doctype, df):
if not frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df.fieldname}):
frappe.get_doc({
"doctype":"Custom Field",
"dt": doctype,
"permlevel": df.get("permlevel") or 0,
"label": df.get("label"),
"fieldname": df.get("fieldname"),
"fieldtype": df.get("fieldtype"),
"options": df.get("options"),
"insert_after": df.get("insert_after"),
"print_hide": df.get("print_hide")
}).insert()

+ 10
- 0
frappe/core/doctype/custom_field/test_custom_field.py Näytä tiedosto

@@ -0,0 +1,10 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# See license.txt

import frappe
import unittest

test_records = frappe.get_test_records('Custom Field')

class TestCustomField(unittest.TestCase):
pass

+ 1
- 0
frappe/core/doctype/custom_field/test_records.json Näytä tiedosto

@@ -0,0 +1 @@
[]

+ 12
- 2
frappe/core/doctype/custom_script/custom_script.json Näytä tiedosto

@@ -1,6 +1,6 @@
{
"autoname": "CustomScript.####",
"creation": "2013-01-10 16:34:01.000000",
"creation": "2013-01-10 16:34:01",
"description": "Adds a custom script (client or server) to a DocType",
"docstatus": 0,
"doctype": "DocType",
@@ -8,6 +8,7 @@
{
"fieldname": "dt",
"fieldtype": "Link",
"in_list_view": 1,
"label": "DocType",
"oldfieldname": "dt",
"oldfieldtype": "Link",
@@ -19,6 +20,7 @@
"fieldname": "script_type",
"fieldtype": "Select",
"hidden": 1,
"in_list_view": 1,
"label": "Script Type",
"oldfieldname": "script_type",
"oldfieldtype": "Select",
@@ -29,16 +31,24 @@
{
"fieldname": "script",
"fieldtype": "Code",
"in_list_view": 1,
"label": "Script",
"oldfieldname": "script",
"oldfieldtype": "Code",
"options": "Script",
"permlevel": 0
},
{
"fieldname": "sample",
"fieldtype": "HTML",
"label": "Sample",
"options": "<h3>Custom Script Help</h3>\n<p>Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started</p>\n<pre><code>\n// additional validation on dates\ncur_frm.cscript.custom_validate = function(doc) {\n if (doc.from_date < get_today()) {\n msgprint(\"You can not select past date in From Date\");\n validated = false;\n }\n}\n\n// make a field read-only after saving\ncur_frm.cscript.custom_refresh = function(doc) {\n // use the __islocal value of doc, to check if the doc is saved or not\n cur_frm.set_df_property(\"myfield\", \"read_only\", doc.__islocal ? 0 : 1);\n}\n\n// addtional permission checking\ncur_frm.cscript.custom_validate = function(doc) {\n if(user==\"user1@example.com\" && doc.purpose!=\"Material Receipt\") {\n msgprint(\"You are only allowed Material Receipt\");\n validated = false;\n }\n}\n\n// calculate sales incentive\ncur_frm.cscript.custom_validate = function(doc) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(wn.model.get(\"Sales Team\", {parent:doc.name}), function(i, d) {\n\n // calculate incentive\n var incentive_percent = 2;\n if(doc.grand_total &gt; 400) incentive_percent = 4;\n\n // actual incentive\n d.incentives = flt(doc.grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n\n doc.total_incentive = total_incentive;\n}\n</code>\n</pre>",
"permlevel": 0
}
],
"icon": "icon-glass",
"idx": 1,
"modified": "2014-01-20 17:48:31.000000",
"modified": "2014-06-19 06:55:02.522204",
"modified_by": "Administrator",
"module": "Core",
"name": "Custom Script",


+ 23
- 36
frappe/core/doctype/customize_form/customize_form.js Näytä tiedosto

@@ -58,11 +58,11 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) {

frm.add_custom_button('Refresh Form', function() {
frm.script_manager.trigger("doc_type");
}, "icon-refresh");
}, "icon-refresh", "btn-default");

frm.add_custom_button('Reset to defaults', function() {
frappe.customize_form.confirm(__('Remove all customizations?'), frm);
}, "icon-eraser");
}, "icon-eraser", "btn-default");
}

// if(!frm.doc.doc_type) {
@@ -73,8 +73,10 @@ frappe.ui.form.on("Customize Form", "refresh", function(frm) {
// }

if(frappe.route_options) {
frappe.model.set_value("Customize Form", null, "doc_type", frappe.route_options.doctype)
frappe.route_options = null;
setTimeout(function() {
frm.set_value("doc_type", frappe.route_options.doctype);
frappe.route_options = null;
}, 1000);
}
});

@@ -83,40 +85,25 @@ frappe.customize_form.confirm = function(msg, frm) {

var d = new frappe.ui.Dialog({
title: 'Reset To Defaults',
width: 500
});

$y(d.body, {padding: '32px', textAlign: 'center'});

$a(d.body, 'div', '', '', msg);

var button_wrapper = $a(d.body, 'div');
$y(button_wrapper, {paddingTop: '15px'});

var proceed_btn = $btn(button_wrapper, 'Proceed', function() {
return frm.call({
doc: frm.doc,
method: "reset_to_defaults",
callback: function(r) {
if(r.exc) {
msgprint(r.exc);
} else {
frappe.customize_form.confirm.dialog.hide();
frappe.customize_form.clear_locals_and_refresh(frm);
fields: [
{fieldtype:"HTML", options:__("All customizations will be removed. Please confirm.")},
],
primary_action: function() {
return frm.call({
doc: frm.doc,
method: "reset_to_defaults",
callback: function(r) {
if(r.exc) {
msgprint(r.exc);
} else {
d.hide();
frappe.customize_form.clear_locals_and_refresh(frm);
}
}
}
});
});

$y(proceed_btn, {marginRight: '20px', fontWeight: 'bold'});

var cancel_btn = $btn(button_wrapper, 'Cancel', function() {
frappe.customize_form.confirm.dialog.hide();
});
}
});

$(cancel_btn).addClass('btn-small btn-info');
$y(cancel_btn, {fontWeight: 'bold'});

frappe.customize_form.confirm.dialog = d;
d.show();
}
@@ -159,7 +146,7 @@ frappe.customize_form.add_fields_help = function(frm) {
<td><b>Perm Level</b></td>\
<td>\
Assign a permission level to the field.<br />\
(Permissions can be managed via Setup &gt; Permission Manager)\
(Permissions can be managed via Setup &gt; Role Permissions Manager)\
</td>\
</tr>\
<tr>\


+ 2
- 27
frappe/core/doctype/customize_form/customize_form.json Näytä tiedosto

@@ -63,22 +63,6 @@
"fieldtype": "Column Break",
"permlevel": 0
},
{
"fieldname": "allow_print",
"fieldtype": "Check",
"label": "Hide Print",
"no_copy": 0,
"permlevel": 0,
"search_index": 0
},
{
"fieldname": "allow_email",
"fieldtype": "Check",
"label": "Hide Email",
"no_copy": 0,
"permlevel": 0,
"search_index": 0
},
{
"fieldname": "allow_copy",
"fieldtype": "Check",
@@ -88,16 +72,7 @@
"search_index": 0
},
{
"description": "Note: maximum attachment size = 1mb",
"fieldname": "allow_attach",
"fieldtype": "Check",
"label": "Allow Attach",
"no_copy": 0,
"permlevel": 0,
"search_index": 0
},
{
"depends_on": "eval:cint(doc.allow_attach)",
"depends_on": "",
"fieldname": "max_attachments",
"fieldtype": "Int",
"label": "Max Attachments",
@@ -126,7 +101,7 @@
"icon": "icon-glass",
"idx": 1,
"issingle": 1,
"modified": "2014-05-08 09:27:44.167026",
"modified": "2014-08-22 05:42:45.083260",
"modified_by": "Administrator",
"module": "Core",
"name": "Customize Form",


+ 17
- 15
frappe/core/doctype/customize_form/customize_form.py Näytä tiedosto

@@ -18,7 +18,6 @@ class CustomizeForm(Document):
'sort_order': 'Data',
'default_print_format': 'Data',
'read_only_onload': 'Check',
'allow_attach': 'Check',
'allow_copy': 'Check',
'max_attachments': 'Int'
}
@@ -32,7 +31,7 @@ class CustomizeForm(Document):
'width': 'Data',
'print_width': 'Data',
'reqd': 'Check',
'ignore_restrictions': 'Check',
'ignore_user_permissions': 'Check',
'in_filter': 'Check',
'in_list_view': 'Check',
'hidden': 'Check',
@@ -41,11 +40,12 @@ class CustomizeForm(Document):
'allow_on_submit': 'Check',
'depends_on': 'Data',
'description': 'Text',
'default': 'Text'
'default': 'Text',
'precision': 'Select'
}

allowed_fieldtype_change = (('Currency', 'Float'), ('Small Text', 'Data'),
('Text', 'Text Editor', 'Code'))
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
('Text', 'Text Editor', 'Code'), ('Data', 'Select'), ('Text', 'Small Text'))

def on_update(self):
frappe.db.sql("delete from tabSingles where doctype='Customize Form'")
@@ -187,6 +187,7 @@ class CustomizeForm(Document):
return

# create a new property setter
# ignore validation becuase it will be done at end
frappe.make_property_setter({
"doctype": self.doc_type,
"doctype_or_field": "DocField" if fieldname else "DocType",
@@ -194,7 +195,7 @@ class CustomizeForm(Document):
"property": property,
"value": value,
"property_type": property_type
})
}, ignore_validate=True)

def delete_existing_property_setter(self, property, fieldname=None):
# first delete existing property setter
@@ -204,14 +205,14 @@ class CustomizeForm(Document):
if existing_property_setter:
frappe.delete_doc("Property Setter", existing_property_setter)

def get_existing_property_value(self, property, fieldname=None):
def get_existing_property_value(self, property_name, fieldname=None):
# check if there is any need to make property setter!
if fieldname:
property_value = frappe.db.get_value("DocField", {"parent": self.doc_type,
"fieldname": fieldname}, property)
"fieldname": fieldname}, property_name)
else:
try:
property_value = frappe.db.get_value("DocType", self.doc_type, property)
property_value = frappe.db.get_value("DocType", self.doc_type, property_name)
except Exception, e:
if e.args[0]==1054:
property_value = None
@@ -221,18 +222,19 @@ class CustomizeForm(Document):
return property_value

def validate_fieldtype_change(self, df, old_value, new_value):
allowed = False
for allowed_changes in self.allowed_fieldtype_change:
if ((old_value in allowed_changes and new_value in allowed_changes)
or (old_value not in allowed_changes and new_value not in allowed_changes)):
continue
else:
frappe.throw(_("Fieldtype must be one of {0} in row {1}").format(", ".join([_(fieldtype) for fieldtype in allowed_changes]), df.idx))
if (old_value in allowed_changes and new_value in allowed_changes):
allowed = True
if not allowed:
frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx))

def reset_to_defaults(self):
if not self.doc_type:
return

frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s""", self.doc_type)
frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s
and ifnull(field_name, '')!='naming_series'""", self.doc_type)
frappe.clear_cache(doctype=self.doc_type)
self.fetch_to_customize()


+ 2
- 2
frappe/core/doctype/customize_form/test_customize_form.py Näytä tiedosto

@@ -47,7 +47,7 @@ class TestCustomizeForm(unittest.TestCase):

d = self.get_customize_form("User")
self.assertEquals(d.doc_type, "User")
self.assertEquals(len(d.get("customize_form_fields")), 53)
self.assertEquals(len(d.get("customize_form_fields")), 55)
self.assertEquals(d.get("customize_form_fields")[-1].fieldname, "test_custom_field")
self.assertEquals(d.get("customize_form_fields", {"fieldname": "location"})[0].in_list_view, 1)

@@ -112,7 +112,7 @@ class TestCustomizeForm(unittest.TestCase):

def test_save_customization_custom_field_property(self):
d = self.get_customize_form("User")
self.assertEquals(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), None)
self.assertEquals(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0)

custom_field = d.get("customize_form_fields", {"fieldname": "test_custom_field"})[0]
custom_field.reqd = 1


+ 134
- 82
frappe/core/doctype/customize_form_field/customize_form_field.json Näytä tiedosto

@@ -1,10 +1,17 @@
{
"allow_copy": 0,
"autoname": "DLF.#####",
"creation": "2013-02-22 01:27:32.000000",
"creation": "2013-02-22 01:27:32",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"label": "Label and Type",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "label",
"fieldtype": "Data",
@@ -19,6 +26,7 @@
"search_index": 1
},
{
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"hidden": 0,
@@ -26,7 +34,7 @@
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"permlevel": 0,
"print_hide": 0,
"reqd": 1,
@@ -46,6 +54,42 @@
"reqd": 0,
"search_index": 1
},
{
"fieldname": "reqd",
"fieldtype": "Check",
"hidden": 0,
"label": "Mandatory",
"oldfieldname": "reqd",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "50px",
"reqd": 0,
"search_index": 0,
"width": "50px"
},
{
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View",
"permlevel": 0
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"permlevel": 0,
"precision": ""
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n1\n2\n3\n4\n5\n6",
"permlevel": 0,
"precision": ""
},
{
"description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma",
"fieldname": "options",
@@ -60,6 +104,25 @@
"reqd": 0,
"search_index": 0
},
{
"fieldname": "permissions",
"fieldtype": "Section Break",
"label": "Permissions",
"permlevel": 0,
"precision": ""
},
{
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): <br>\nmyfield\neval:doc.myfield=='My Value'<br>\neval:doc.age>18",
"fieldname": "depends_on",
"fieldtype": "Data",
"hidden": 0,
"label": "Depends On",
"oldfieldname": "depends_on",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"reqd": 0
},
{
"default": "0",
"fieldname": "permlevel",
@@ -75,13 +138,12 @@
"search_index": 0
},
{
"fieldname": "width",
"fieldtype": "Data",
"fieldname": "hidden",
"fieldtype": "Check",
"hidden": 0,
"in_list_view": 1,
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data",
"label": "Hidden",
"oldfieldname": "hidden",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "50px",
@@ -90,111 +152,76 @@
"width": "50px"
},
{
"fieldname": "print_width",
"fieldtype": "Data",
"label": "Print Width",
"fieldname": "column_break_14",
"fieldtype": "Column Break",
"permlevel": 0,
"print_width": "50px",
"width": "50px"
"precision": ""
},
{
"fieldname": "reqd",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"hidden": 0,
"label": "Reqd",
"oldfieldname": "reqd",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "50px",
"reqd": 0,
"search_index": 0,
"width": "50px"
},
{
"fieldname": "ignore_restrictions",
"fieldtype": "Check",
"label": "Ignore Restrictions",
"label": "Ignore User Permissions",
"permlevel": 0
},
{
"fieldname": "in_filter",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"hidden": 0,
"label": "In Filter",
"oldfieldname": "in_filter",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "50px",
"reqd": 0,
"width": "50px"
},
{
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View",
"permlevel": 0
"reqd": 0
},
{
"fieldname": "hidden",
"fieldname": "report_hide",
"fieldtype": "Check",
"hidden": 0,
"label": "Hidden",
"oldfieldname": "hidden",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"print_width": "50px",
"reqd": 0,
"search_index": 0,
"width": "50px"
"reqd": 0
},
{
"fieldname": "print_hide",
"fieldtype": "Check",
"hidden": 0,
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check",
"fieldname": "display",
"fieldtype": "Section Break",
"label": "Display",
"permlevel": 0,
"print_hide": 0,
"reqd": 0,
"search_index": 0
"precision": ""
},
{
"fieldname": "report_hide",
"fieldtype": "Check",
"fieldname": "default",
"fieldtype": "Text",
"hidden": 0,
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check",
"label": "Default",
"oldfieldname": "default",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"reqd": 0
"reqd": 0,
"search_index": 0
},
{
"fieldname": "allow_on_submit",
"fieldname": "in_filter",
"fieldtype": "Check",
"hidden": 0,
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"label": "In Filter",
"oldfieldname": "in_filter",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"reqd": 0
"print_width": "50px",
"reqd": 0,
"width": "50px"
},
{
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): <br>\nmyfield\neval:doc.myfield=='My Value'<br>\neval:doc.age>18",
"fieldname": "depends_on",
"fieldtype": "Data",
"hidden": 0,
"label": "Depends On",
"oldfieldname": "depends_on",
"oldfieldtype": "Data",
"fieldname": "column_break_21",
"fieldtype": "Column Break",
"permlevel": 0,
"print_hide": 0,
"reqd": 0
"precision": ""
},
{
"fieldname": "description",
@@ -210,16 +237,40 @@
"width": "300px"
},
{
"fieldname": "default",
"fieldtype": "Text",
"fieldname": "print_hide",
"fieldtype": "Check",
"hidden": 0,
"label": "Default",
"oldfieldname": "default",
"oldfieldtype": "Text",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check",
"permlevel": 0,
"print_hide": 0,
"reqd": 0,
"search_index": 0
},
{
"description": "Print Width of the field, if the field is a column in a table",
"fieldname": "print_width",
"fieldtype": "Data",
"label": "Print Width",
"permlevel": 0,
"print_width": "50px",
"width": "50px"
},
{
"fieldname": "width",
"fieldtype": "Data",
"hidden": 0,
"in_list_view": 1,
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_width": "50px",
"reqd": 0,
"search_index": 0,
"width": "50px"
}
],
"hide_heading": 0,
@@ -227,10 +278,11 @@
"idx": 1,
"issingle": 0,
"istable": 1,
"modified": "2013-12-23 16:12:45.000000",
"modified": "2014-09-05 07:41:29.641454",
"modified_by": "Administrator",
"module": "Core",
"name": "Customize Form Field",
"owner": "Administrator",
"permissions": [],
"read_only": 0
}

+ 15
- 5
frappe/core/doctype/docfield/docfield.json Näytä tiedosto

@@ -34,7 +34,7 @@
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
"permlevel": 0,
"reqd": 1,
"search_index": 1
@@ -91,6 +91,16 @@
"fieldtype": "Column Break",
"permlevel": 0
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n1\n2\n3\n4\n5\n6",
"permlevel": 0,
"print_hide": 1
},
{
"description": "For Links, enter the DocType as range\nFor Select, enter list of Options separated by comma",
"fieldname": "options",
@@ -166,10 +176,10 @@
"permlevel": 0
},
{
"description": "User restrictions should not apply for this Link",
"fieldname": "ignore_restrictions",
"description": "User permissions should not apply for this Link",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore Restrictions",
"label": "Ignore User Permissions",
"permlevel": 0
},
{
@@ -304,7 +314,7 @@
"in_dialog": 1,
"issingle": 0,
"istable": 1,
"modified": "2014-04-24 15:56:23.561687",
"modified": "2014-09-05 07:41:05.956027",
"modified_by": "Administrator",
"module": "Core",
"name": "DocField",


+ 0
- 7
frappe/core/doctype/docperm/README.md Näytä tiedosto

@@ -1,7 +0,0 @@
Defines a permission rule for a DocType. The permission rule is set for a Role and a level and has permission for read, write, create, submit, cancel, amend and report.

#### Match

If a fieldname is set in `match` property, then the rule will only apply for those records that have a value for that fieldname which is one of the user's default values (user properties).

This is used to restrict users to view records belonging to a company in case of a multi-company system where the `company` field is present in most forms.

+ 24
- 15
frappe/core/doctype/docperm/docperm.json Näytä tiedosto

@@ -26,6 +26,13 @@
"search_index": 0,
"width": "150px"
},
{
"description": "Filter records based on User Permissions defined for a user",
"fieldname": "apply_user_permissions",
"fieldtype": "Check",
"label": "Apply User Permissions",
"permlevel": 0
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break",
@@ -46,6 +53,15 @@
"search_index": 0,
"width": "40px"
},
{
"depends_on": "",
"description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.",
"fieldname": "user_permission_doctypes",
"fieldtype": "Text",
"label": "User Permission DocTypes",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break",
@@ -171,15 +187,10 @@
"permlevel": 0
},
{
"fieldname": "print",
"description": "This role update User Permissions for a user",
"fieldname": "set_user_permissions",
"fieldtype": "Check",
"label": "Print",
"permlevel": 0
},
{
"fieldname": "email",
"fieldtype": "Check",
"label": "Email",
"label": "Set User Permissions",
"permlevel": 0
},
{
@@ -188,17 +199,15 @@
"permlevel": 0
},
{
"description": "Only restricted users can access",
"fieldname": "restricted",
"fieldname": "print",
"fieldtype": "Check",
"label": "Only Restricted Documents",
"label": "Print",
"permlevel": 0
},
{
"description": "This role can restrict users for accessing the record.",
"fieldname": "restrict",
"fieldname": "email",
"fieldtype": "Check",
"label": "Can Restrict Others",
"label": "Email",
"permlevel": 0
}
],
@@ -207,7 +216,7 @@
"idx": 1,
"issingle": 0,
"istable": 1,
"modified": "2014-05-01 05:20:48.162224",
"modified": "2014-08-26 01:43:31.499363",
"modified_by": "Administrator",
"module": "Core",
"name": "DocPerm",


frappe/core/doctype/doctype/doctype_template.py → frappe/core/doctype/doctype/boilerplate/controller.py Näytä tiedosto

@@ -1,4 +1,4 @@
# Copyright (c) 2013, {app_publisher}
# Copyright (c) 2013, {app_publisher} and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
@@ -6,4 +6,4 @@ import frappe
from frappe.model.document import Document

class {classname}(Document):
pass
pass

+ 34
- 0
frappe/core/doctype/doctype/boilerplate/controller_list.html Näytä tiedosto

@@ -0,0 +1,34 @@
<div class="row" style="max-height: 30px;">
<div class="col-xs-12">
<div class="text-ellipsis">
{{%= list.get_avatar_and_id(doc) %}}

<!-- sample text -->
<span style="margin-right: 8px;" class="filterable"
data-filter="text,=,{{%= doc.text %}}">
{{%= doc.text %}}</span>

<!-- sample icon -->
{{% if(doc.check) {{ %}}
<span style="margin-right: 8px;"
title="{{%= __("Title") %}}" class="filterable"
data-filter="check,=,Yes">
<i class="icon-icon text-muted"></i>
</span>
{{% }} %}}

<!-- sample label -->
<span class="label
label-{{%= frappe.utils.guess_style(doc.status) %}} filterable"
title="{{%= __("Title") %}}"
data-filter="status,=,{{%= doc.status %}}">
{{%= doc.status %}}
</span>
</div>
</div>
<!-- sample graph -->
<div class="col-xs-1 text-right">
{{% var completed = doc.completed, title = __("Completed") %}}
{{% include "templates/form_grid/includes/progress.html" %}}
</div>
</div>

+ 4
- 0
frappe/core/doctype/doctype/boilerplate/controller_list.js Näytä tiedosto

@@ -0,0 +1,4 @@
frappe.listview_settings['{doctype}'] = {{
add_fields: ["status"],
filters:[["status","=", "Open"]]
}};

+ 10
- 0
frappe/core/doctype/doctype/boilerplate/test_controller.py Näytä tiedosto

@@ -0,0 +1,10 @@
# Copyright (c) 2013, {app_publisher} and Contributors
# See license.txt

import frappe
import unittest

test_records = frappe.get_test_records('{doctype}')

class Test{classname}(unittest.TestCase):
pass

+ 6
- 0
frappe/core/doctype/doctype/boilerplate/test_records.json Näytä tiedosto

@@ -0,0 +1,6 @@
[
{{
"doctype": "{doctype}",
"name": "_Test {doctype} 1"
}}
]

+ 0
- 12
frappe/core/doctype/doctype/doctype.js Näytä tiedosto

@@ -11,18 +11,6 @@ $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) {
}
})

cur_frm.cscript.allow_attach = function(doc, cdt, cdn) {
if(doc.allow_attach) {
unhide_field('max_attachments');
} else {
hide_field('max_attachments');
}
}

cur_frm.cscript.onload = function(doc, cdt, cdn) {
this.allow_attach(doc, cdt, cdn);
}

cur_frm.cscript.refresh = function(doc, cdt, cdn) {
if(in_list(user_roles, 'System Manager') && !in_list(user_roles, 'Administrator')) {
// make the document read-only


+ 3
- 12
frappe/core/doctype/doctype/doctype.json Näytä tiedosto

@@ -1,6 +1,5 @@
{
"allow_attach": 0,
"allow_copy": 0,
"allow_copy": 0,
"autoname": "Prompt",
"creation": "2013-02-18 13:36:19",
"custom": 0,
@@ -276,18 +275,10 @@
"oldfieldtype": "Check",
"permlevel": 0
},
{
"fieldname": "allow_attach",
"fieldtype": "Check",
"label": "Allow Attach",
"oldfieldname": "allow_attach",
"oldfieldtype": "Check",
"permlevel": 0
},
{
"fieldname": "max_attachments",
"fieldtype": "Int",
"hidden": 1,
"hidden": 0,
"label": "Max Attachments",
"oldfieldname": "max_attachments",
"oldfieldtype": "Int",
@@ -345,7 +336,7 @@
"idx": 1,
"issingle": 0,
"istable": 0,
"modified": "2014-05-08 09:23:56.952829",
"modified": "2014-08-22 05:33:03.067964",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",


+ 120
- 36
frappe/core/doctype/doctype/doctype.py Näytä tiedosto

@@ -5,12 +5,18 @@ from __future__ import unicode_literals

import frappe
from frappe import _
import os

from frappe.utils import now, cint
from frappe.model import no_value_fields
from frappe.model.document import Document
from frappe.model.db_schema import type_map
from frappe.core.doctype.property_setter.property_setter import make_property_setter
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for
from frappe.modules import make_boilerplate

form_grid_templates = {
"fields": "templates/form_grid/fields.html"
}

class DocType(Document):
def validate(self):
@@ -22,7 +28,7 @@ class DocType(Document):
self.validate_series()
self.scrub_field_names()
self.validate_title_field()
validate_fields(self.get("fields"))
validate_fields(self)

if self.istable:
# no permission records for child table
@@ -41,7 +47,7 @@ class DocType(Document):
frappe.db.sql('UPDATE tabDocType SET modified=%s WHERE `name`=%s', (now(), p[0]))

def scrub_field_names(self):
restricted = ('name','parent','idx','owner','creation','modified','modified_by',
restricted = ('name','parent','creation','modified','modified_by',
'parentfield','parenttype',"file_list")
for d in self.get("fields"):
if d.fieldtype:
@@ -66,8 +72,11 @@ class DocType(Document):
if not autoname and self.get("fields", {"fieldname":"naming_series"}):
self.autoname = "naming_series:"

if autoname and (not autoname.startswith('field:')) and (not autoname.startswith('eval:')) \
and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')):
if autoname and (not autoname.startswith('field:')) \
and (not autoname.startswith('eval:')) \
and (not autoname in ('Prompt', 'hash')) \
and (not autoname.startswith('naming_series:')):

prefix = autoname.split('.')[0]
used_in = frappe.db.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name))
if used_in:
@@ -81,7 +90,7 @@ class DocType(Document):
make_module_and_roles(self)

from frappe import conf
if (not frappe.flags.in_import) and conf.get('developer_mode') or 0:
if not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode') or 0:
self.export_doc()
self.make_controller_template()

@@ -91,13 +100,9 @@ class DocType(Document):
module = load_doctype_module(self.name, self.module)
if hasattr(module, "on_doctype_update"):
module.on_doctype_update()
frappe.clear_cache(doctype=self.name)

def on_trash(self):
frappe.db.sql("delete from `tabCustom Field` where dt = %s", self.name)
frappe.db.sql("delete from `tabCustom Script` where dt = %s", self.name)
frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", self.name)
frappe.db.sql("delete from `tabReport` where ref_doctype=%s", self.name)
delete_notification_count_for(doctype=self.name)
frappe.clear_cache(doctype=self.name)

def before_rename(self, old, new, merge=False):
if merge:
@@ -109,6 +114,29 @@ class DocType(Document):
else:
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new))

def before_reload(self):
if not (self.issingle and self.istable):
self.preserve_naming_series_options_in_property_setter()

def preserve_naming_series_options_in_property_setter(self):
"""preserve naming_series as property setter if it does not exist"""
naming_series = self.get("fields", {"fieldname": "naming_series"})

if not naming_series:
return

# check if atleast 1 record exists
if not (frappe.db.table_exists("tab" + self.name) and frappe.db.sql("select name from `tab{}` limit 1".format(self.name))):
return

existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.name,
"property": "options", "field_name": "naming_series"})

if not existing_property_setter:
make_property_setter(self.name, "naming_series", "options", naming_series[0].options, "Text", validate_fields_for_doctype=False)
if naming_series[0].default:
make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False)

def export_doc(self):
from frappe.modules.export_file import export_to_files
export_to_files(record_list=[['DocType', self.name]])
@@ -118,22 +146,11 @@ class DocType(Document):
import_from_files(record_list=[[self.module, 'doctype', self.name]])

def make_controller_template(self):
from frappe.modules import get_doc_path, get_module_path, scrub

pypath = os.path.join(get_doc_path(self.module,
self.doctype, self.name), scrub(self.name) + '.py')

if not os.path.exists(pypath):
# get app publisher for copyright
app = frappe.local.module_app[frappe.scrub(self.module)]
if not app:
frappe.throw(_("App not found"))
app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0]
make_boilerplate("controller.py", self)

with open(pypath, 'w') as pyfile:
with open(os.path.join(get_module_path("core"), "doctype", "doctype",
"doctype_template.py"), 'r') as srcfile:
pyfile.write(srcfile.read().format(app_publisher=app_publisher, classname=self.name.replace(" ", "")))
if not (self.istable or self.issingle):
make_boilerplate("test_controller.py", self)
make_boilerplate("test_records.json", self)

def make_amendable(self):
"""
@@ -158,9 +175,10 @@ class DocType(Document):
return max_idx and max_idx[0][0] or 0

def validate_fields_for_doctype(doctype):
validate_fields(frappe.get_meta(doctype).get("fields"))
validate_fields(frappe.get_meta(doctype))

def validate_fields(fields):
# this is separate because it is also called via custom field
def validate_fields(meta):
def check_illegal_characters(fieldname):
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$',
'(', ')', '[', ']', '/']:
@@ -173,7 +191,7 @@ def validate_fields(fields):
frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(fieldname, ", ".join(duplicates)))

def check_illegal_mandatory(d):
if d.fieldtype in ('HTML', 'Button', 'Section Break', 'Column Break') and d.reqd:
if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd:
frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype))

def check_link_table_options(d):
@@ -203,6 +221,47 @@ def validate_fields(fields):
if d.in_list_view and d.fieldtype!="Image" and (d.fieldtype in no_value_fields):
frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx))

def check_dynamic_link_options(d):
if d.fieldtype=="Dynamic Link":
doctype_pointer = filter(lambda df: df.fieldname==d.options, fields)
if not doctype_pointer or (doctype_pointer[0].fieldtype!="Link") \
or (doctype_pointer[0].options!="DocType"):
frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'"))

def check_illegal_default(d):
if d.fieldtype == "Check" and d.default and d.default not in ('0', '1'):
frappe.throw(_("Default for 'Check' type of field must be either '0' or '1'"))

def check_precision(d):
if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6):
frappe.throw(_("Precision should be between 1 and 6"))

def check_fold(fields):
fold_exists = False
for i, f in enumerate(fields):
if f.fieldtype=="Fold":
if fold_exists:
frappe.throw(_("There can be only one Fold in a form"))
fold_exists = True
if i < len(fields)-1:
nxt = fields[i+1]
if nxt.fieldtype != "Section Break" \
or (nxt.fieldtype=="Section Break" and not nxt.label):
frappe.throw(_("Fold must come before a labelled Section Break"))
else:
frappe.throw(_("Fold can not be at the end of the form"))

def check_search_fields(meta):
if not meta.search_fields:
return

fieldname_list = [d.fieldname for d in fields]
for fieldname in (meta.search_fields or "").split(","):
fieldname = fieldname.strip()
if fieldname not in fieldname_list:
frappe.throw(_("Search Fields should contain valid fieldnames"))

fields = meta.get("fields")
for d in fields:
if not d.permlevel: d.permlevel = 0
if not d.fieldname:
@@ -211,13 +270,28 @@ def validate_fields(fields):
check_unique_fieldname(d.fieldname)
check_illegal_mandatory(d)
check_link_table_options(d)
check_dynamic_link_options(d)
check_hidden_and_mandatory(d)
check_in_list_view(d)
check_illegal_default(d)

check_min_items_in_list(fields)
check_fold(fields)
check_search_fields(meta)

def validate_permissions_for_doctype(doctype, for_remove=False):
validate_permissions(frappe.get_doc("DocType", doctype), for_remove)
doctype = frappe.get_doc("DocType", doctype)

if frappe.conf.developer_mode and not frappe.flags.in_test:
# save doctype
doctype.save()

else:
validate_permissions(doctype, for_remove)

# save permissions
for perm in doctype.get("permissions"):
perm.db_update()

def validate_permissions(doctype, for_remove=False):
permissions = doctype.get("permissions")
@@ -239,12 +313,13 @@ def validate_permissions(doctype, for_remove=False):
def check_double(d):
has_similar = False
for p in permissions:
if p.role==d.role and p.permlevel==d.permlevel and p.match==d.match and p!=d:
if (p.role==d.role and p.permlevel==d.permlevel
and p.apply_user_permissions==d.apply_user_permissions and p!=d):
has_similar = True
break

if has_similar:
frappe.throw(_("{0}: Only one rule allowed at a Role and Level").format(get_txt(d)))
frappe.throw(_("{0}: Only one rule allowed with the same Role, Level and Apply User Permissions").format(get_txt(d)))

def check_level_zero_is_set(d):
if cint(d.permlevel) > 0 and d.role != 'All':
@@ -281,9 +356,12 @@ def validate_permissions(doctype, for_remove=False):
d.set("import", 0)
d.set("export", 0)

if d.restrict:
frappe.msgprint(_("Restrict cannot be set for Single types"))
d.restrict = 0
for ptype, label in (
("set_user_permissions", _("Set User Permissions")),
("apply_user_permissions", _("Apply User Permissions"))):
if d.get(ptype):
d.set(ptype, 0)
frappe.msgprint(_("{0} cannot be set for Single types").format(label))

def check_if_submittable(d):
if d.submit and not issubmittable:
@@ -331,3 +409,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
pass
else:
raise

def init_list(doctype):
doc = frappe.get_meta(doctype)
make_boilerplate("controller_list.js", doc)
make_boilerplate("controller_list.html", doc)


frappe/core/doctype/social_login_keys/__init__.py → frappe/core/doctype/email_alert/__init__.py Näytä tiedosto


+ 41
- 0
frappe/core/doctype/email_alert/email_alert.js Näytä tiedosto

@@ -0,0 +1,41 @@
frappe.email_alert = {
setup_fieldname_select: function(frm) {
// get the doctype to update fields
if(!frm.doc.document_type) {
return;
}

frappe.model.with_doctype(frm.doc.document_type, function() {
var get_select_options = function(df) {
return {value: df.fieldname, label: df.fieldname + " (" + __(df.label) + ")"};
}

var fields = frappe.get_doc("DocType", frm.doc.document_type).fields;

var options = $.map(fields,
function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ?
null : get_select_options(d); });

// set value changed options
frm.set_df_property("value_changed", "options", [""].concat(options));

// set date changed options
frm.set_df_property("date_changed", "options", $.map(fields,
function(d) { return (d.fieldtype=="Date" || d.fieldtype=="Datetime") ?
get_select_options(d) : null; }));

// set email recipient options
frappe.meta.get_docfield("Email Alert Recipient", "email_by_document_field",
frm.doc.name).options = ["owner"].concat(options);

});
}
}

frappe.ui.form.on("Email Alert", "refresh", function(frm) {
frappe.email_alert.setup_fieldname_select(frm);
});

frappe.ui.form.on("Email Alert", "document_type", function(frm) {
frappe.email_alert.setup_fieldname_select(frm);
});

+ 152
- 0
frappe/core/doctype/email_alert/email_alert.json Näytä tiedosto

@@ -0,0 +1,152 @@
{
"autoname": "hash",
"creation": "2014-07-11 17:18:09.923399",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled",
"permlevel": 0
},
{
"fieldname": "filters",
"fieldtype": "Section Break",
"label": "Filters",
"permlevel": 0
},
{
"fieldname": "subject",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Subject",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Document Type",
"options": "DocType",
"permlevel": 0,
"reqd": 1,
"search_index": 1
},
{
"fieldname": "event",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Send Alert On",
"options": "\nNew\nSave\nSubmit\nCancel\nDate Change\nValue Change",
"permlevel": 0,
"reqd": 1,
"search_index": 1
},
{
"depends_on": "eval:doc.event==\"Date Change\"",
"description": "Send alert if date matches this field's value",
"fieldname": "date_changed",
"fieldtype": "Select",
"label": "Date Changed",
"permlevel": 0
},
{
"default": "0",
"depends_on": "eval:doc.event==\"Date Change\"",
"description": "[Optional] Send the email X days in advance of the specified date. 0 equals same day.",
"fieldname": "days_in_advance",
"fieldtype": "Int",
"label": "Days in Advance",
"permlevel": 0
},
{
"depends_on": "eval:doc.event==\"Value Change\"",
"description": "Send alert if this field's value changes",
"fieldname": "value_changed",
"fieldtype": "Select",
"label": "Value Changed",
"permlevel": 0
},
{
"depends_on": "",
"description": "Optional: The alert will be sent if this expression is true",
"fieldname": "condition",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Condition",
"permlevel": 0
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"fieldname": "html_7",
"fieldtype": "HTML",
"options": "<p><strong>Condition Examples:</strong></p>\n<pre>doc.status==\"Open\"\ndoc.due_date==nowdate()\ndoc.total > 40000\n</pre>\n<p><strong>Hints:</strong></p>\n<ol>\n<li>To check for an event every day, select \"Date Change\" in Event</li>\n<li>To send an alert if a particular value changes, select \"Value Change\"</li>\n</ol>",
"permlevel": 0
},
{
"fieldname": "column_break_5",
"fieldtype": "Section Break",
"label": "Recipients",
"permlevel": 0
},
{
"fieldname": "email_alert_recipients",
"fieldtype": "Table",
"label": "Email Alert Recipients",
"options": "Email Alert Recipient",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "message_sb",
"fieldtype": "Section Break",
"label": "Message",
"permlevel": 0
},
{
"fieldname": "message",
"fieldtype": "Text",
"label": "Message",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "message_examples",
"fieldtype": "HTML",
"label": "Message Examples",
"options": "<h5>Message Example (Markdown)</h5>\n<pre>Transaction {{ doc.name }} has exceeded Due Date. Please take relevant action\n\n#### Details\n\nCustomer: {{ doc.customer }}\nAmount: {{ doc.total_amount }}</pre>",
"permlevel": 0
}
],
"icon": "icon-envelope",
"modified": "2014-07-15 05:07:14.002351",
"modified_by": "Administrator",
"module": "Core",
"name": "Email Alert",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"export": 1,
"import": 0,
"permlevel": 0,
"read": 1,
"report": 1,
"role": "System Manager",
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "subject"
}

+ 103
- 0
frappe/core/doctype/email_alert/email_alert.py Näytä tiedosto

@@ -0,0 +1,103 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import validate_email_add, nowdate

class EmailAlert(Document):
def validate(self):
if self.event=="Date Change" and not self.date_changed:
frappe.throw(_("Please specify which date field must be checked"))

if self.event=="Value Change" and not self.value_changed:
frappe.throw(_("Please specify which value field must be checked"))

forbidden_document_types = ("Bulk Email",)
if self.document_type in forbidden_document_types:
frappe.throw(_("Cannot set Email Alert on Document Type {0}").format(self.document_type))

def trigger_daily_alerts():
trigger_email_alerts(None, "Date Change")

def trigger_email_alerts(doc, method=None):
if frappe.flags.in_import or frappe.flags.in_patch:
# don't send email alerts while syncing or patching
return

if method=="Date Change":
for alert in frappe.db.sql_list("""select name from `tabEmail Alert`
where event='Date Change' and enabled=1"""):

alert = frappe.get_doc("Email Alert", alert)

for name in frappe.db.sql_list("""select name from `tab{0}` where
DATE({1}) = ADDDATE(DATE(%s), INTERVAL %s DAY)""".format(alert.document_type, alert.date_changed),
(nowdate(), alert.days_in_advance or 0)):

evaluate_alert(frappe.get_doc(alert.document_type, name),
alert, "Date Change")
else:
if method in ("on_update", "validate") and doc.get("__in_insert"):
# don't call email alerts multiple times for inserts
# on insert only "New" type alert must be called
return

eevent = {
"on_update": "Save",
"after_insert": "New",
"validate": "Value Change",
"on_submit": "Submit",
"on_cancel": "Cancel",
}[method]

for alert in frappe.db.sql_list("""select name from `tabEmail Alert`
where document_type=%s and event=%s and enabled=1""", (doc.doctype, eevent)):
evaluate_alert(doc, alert, eevent)

def evaluate_alert(doc, alert, event):
if isinstance(alert, basestring):
alert = frappe.get_doc("Email Alert", alert)

context = {"doc": doc, "nowdate": nowdate}

if alert.condition:
if not eval(alert.condition, context):
return

if event=="Value Change" and not doc.is_new():
if doc.get(alert.value_changed) == frappe.db.get_value(doc.doctype,
doc.name, alert.value_changed):
return # value not changed

for recipient in alert.email_alert_recipients:
recipients = []
if recipient.condition:
if not eval(recipient.condition, context):
continue
if recipient.email_by_document_field:
if validate_email_add(doc.get(recipient.email_by_document_field)):
recipients.append(doc.get(recipient.email_by_document_field))
# else:
# print "invalid email"
if recipient.cc:
recipient.cc = recipient.cc.replace(",", "\n")
recipients = recipients + recipient.cc.split("\n")

if not recipients:
return

template = alert.message + footer
# send alert
frappe.sendmail(recipients=recipients, subject=alert.subject,
message= frappe.render_template(template, {"doc": doc, "alert":alert}),
bulk=True, ref_doctype = doc.doctype, ref_docname = doc.name)


footer = """<div style='margin-top: 20px; font-size: 80%; color: #888'>
This Email Alert <em>{{alert.subject}}</em> was autogenerated for
{{ doc.doctype }} <a href="/desk#Form/{{doc.doctype}}/{{doc.name}}">{{doc.name}}</a>.
To update, modify it, go to Setup > Email > <a href="/desk#List/Email Alert">Email Alert</a>
"""

+ 97
- 0
frappe/core/doctype/email_alert/test_email_alert.py Näytä tiedosto

@@ -0,0 +1,97 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# See license.txt

import frappe, frappe.utils, frappe.utils.scheduler
import unittest

test_records = frappe.get_test_records('Email Alert')

class TestEmailAlert(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabBulk Email`""")
frappe.set_user("test1@example.com")

def tearDown(self):
frappe.set_user("Administrator")

def test_new_and_save(self):
comment = frappe.new_doc("Comment")
comment.comment = "test"
comment.insert(ignore_permissions=True)

self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment",
"ref_docname": comment.name, "status":"Not Sent"}))

frappe.db.sql("""delete from `tabBulk Email`""")

comment.description = "test"
comment.save()

self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Comment",
"ref_docname": comment.name, "status":"Not Sent"}))

def test_condition(self):
event = frappe.new_doc("Event")
event.subject = "test",
event.event_type = "Private"
event.starts_on = "2014-06-06 12:00:00"
event.insert()

self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

event.event_type = "Public"
event.save()

self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

def test_value_changed(self):
event = frappe.new_doc("Event")
event.subject = "test",
event.event_type = "Private"
event.starts_on = "2014-06-06 12:00:00"
event.insert()

self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

event.subject = "test 1"
event.save()

self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

event.description = "test"
event.save()

self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

def test_date_changed(self):
event = frappe.new_doc("Event")
event.subject = "test",
event.event_type = "Private"
event.starts_on = "2014-01-01 12:00:00"
event.insert()

self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True)

# not today, so no alert
self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

event.starts_on = frappe.utils.add_days(frappe.utils.nowdate(), 2) + " 12:00:00"
event.save()

self.assertFalse(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True)

# today so show alert
self.assertTrue(frappe.db.get_value("Bulk Email", {"ref_doctype": "Event",
"ref_docname": event.name, "status":"Not Sent"}))

+ 56
- 0
frappe/core/doctype/email_alert/test_records.json Näytä tiedosto

@@ -0,0 +1,56 @@
[
{
"doctype": "Email Alert",
"subject":"_Test Email Alert 1",
"document_type": "Comment",
"event": "New",
"message": "New comment {{ doc.comment }} created",
"email_alert_recipients": [
{ "email_by_document_field": "owner" }
]
},
{
"doctype": "Email Alert",
"subject":"_Test Email Alert 2",
"document_type": "Comment",
"event": "Save",
"message": "New comment {{ doc.comment }} saved",
"email_alert_recipients": [
{ "email_by_document_field": "owner" }
]
},
{
"doctype": "Email Alert",
"subject":"_Test Email Alert 3",
"document_type": "Event",
"event": "Save",
"condition": "doc.event_type=='Public'",
"message": "A new public event {{ doc.subject }} on {{ doc.starts_on }} is created",
"email_alert_recipients": [
{ "email_by_document_field": "owner" }
]
},
{
"doctype": "Email Alert",
"subject":"_Test Email Alert 4",
"document_type": "Event",
"event": "Value Change",
"value_changed": "description",
"message": "Description changed",
"email_alert_recipients": [
{ "email_by_document_field": "owner" }
]
},
{
"doctype": "Email Alert",
"subject":"_Test Email Alert 5",
"document_type": "Event",
"event": "Date Change",
"date_changed": "starts_on",
"days_in_advance": 2,
"message": "Description changed",
"email_alert_recipients": [
{ "email_by_document_field": "owner" }
]
}
]

frappe/website/doctype/website_route/__init__.py → frappe/core/doctype/email_alert_recipient/__init__.py Näytä tiedosto


+ 42
- 0
frappe/core/doctype/email_alert_recipient/email_alert_recipient.json Näytä tiedosto

@@ -0,0 +1,42 @@
{
"creation": "2014-07-11 17:19:37.037109",
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"fields": [
{
"description": "Optional: Alert will only be sent if value is a valid email id.",
"fieldname": "email_by_document_field",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Email By Document Field",
"permlevel": 0
},
{
"description": "Optional: Always send to these ids. Each email id on a new row",
"fieldname": "cc",
"fieldtype": "Text",
"in_list_view": 1,
"label": "CC",
"permlevel": 0
},
{
"description": "Expression, Optional",
"fieldname": "condition",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Condition",
"permlevel": 0
}
],
"istable": 1,
"modified": "2014-07-11 17:54:53.298526",
"modified_by": "Administrator",
"module": "Core",
"name": "Email Alert Recipient",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

+ 9
- 0
frappe/core/doctype/email_alert_recipient/email_alert_recipient.py Näytä tiedosto

@@ -0,0 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document

class EmailAlertRecipient(Document):
pass

+ 9
- 0
frappe/core/doctype/event/event.js Näytä tiedosto

@@ -1,6 +1,15 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.ui.form.on("Event", "refresh", function(frm) {
if(frm.doc.ref_type && frm.doc.ref_name) {
frm.add_custom_button(__(frm.doc.ref_name), function() {
frappe.set_route("Form", frm.doc.ref_type, frm.doc.ref_name);
}, frappe.boot.doctype_icons[frm.doc.ref_type]);
}
});


cur_frm.cscript.repeat_on = function(doc, cdt, cdn) {
if(doc.repeat_on==="Every Day") {
$.each(["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"], function(i,v) {


+ 8
- 7
frappe/core/doctype/event/event.json Näytä tiedosto

@@ -1,6 +1,6 @@
{
"autoname": "EV.#####",
"creation": "2013-06-10 13:17:47.000000",
"creation": "2013-06-10 13:17:47",
"docstatus": 0,
"doctype": "DocType",
"fields": [
@@ -218,24 +218,26 @@
},
{
"fieldname": "ref_type",
"fieldtype": "Data",
"fieldtype": "Link",
"hidden": 0,
"label": "Ref Type",
"no_copy": 0,
"oldfieldname": "ref_type",
"oldfieldtype": "Data",
"options": "DocType",
"permlevel": 0,
"read_only": 1,
"read_only": 0,
"search_index": 0
},
{
"fieldname": "ref_name",
"fieldtype": "Data",
"fieldtype": "Dynamic Link",
"hidden": 0,
"label": "Ref Name",
"no_copy": 0,
"oldfieldname": "ref_name",
"oldfieldtype": "Data",
"options": "ref_type",
"permlevel": 0,
"read_only": 1,
"search_index": 0
@@ -244,14 +246,14 @@
"icon": "icon-calendar",
"idx": 1,
"in_create": 1,
"modified": "2014-01-24 13:00:01.000000",
"modified": "2014-06-20 06:40:05.415405",
"modified_by": "Administrator",
"module": "Core",
"name": "Event",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"apply_user_permissions": 1,
"create": 1,
"delete": 0,
"email": 1,
@@ -264,7 +266,6 @@
"write": 1
},
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,


+ 8
- 7
frappe/core/doctype/event/event.py Näytä tiedosto

@@ -15,7 +15,8 @@ class Event(Document):
if self.starts_on and self.ends_on and self.starts_on > self.ends_on:
frappe.msgprint(frappe._("Event end must be after start"), raise_exception=True)

def get_permission_query_conditions():
def get_permission_query_conditions(user):
if not user: user = frappe.session.user
return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s'
or exists(select * from `tabEvent User` where
`tabEvent User`.parent=tabEvent.name and `tabEvent User`.person='%(user)s')
@@ -23,18 +24,18 @@ def get_permission_query_conditions():
`tabEvent Role`.parent=tabEvent.name
and `tabEvent Role`.role in ('%(roles)s')))
""" % {
"user": frappe.session.user,
"roles": "', '".join(frappe.get_roles(frappe.session.user))
"user": user,
"roles": "', '".join(frappe.get_roles(user))
}

def has_permission(doc):
if doc.event_type=="Public" or doc.owner==frappe.session.user:
def has_permission(doc, user):
if doc.event_type=="Public" or doc.owner==user:
return True

if doc.get("event_individuals", {"person":frappe.session.user}):
if doc.get("event_individuals", {"person": user}):
return True

if doc.get("event_roles", {"role":("in", frappe.get_roles())}):
if doc.get("event_roles", {"role":("in", frappe.get_roles(user))}):
return True

return False


+ 55
- 1
frappe/core/doctype/event/test_event.py Näytä tiedosto

@@ -1,11 +1,12 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

"""Use blog post test to test permission restriction logic"""
"""Use blog post test to test user permissions logic"""

import frappe
import frappe.defaults
import unittest
import json

test_records = frappe.get_test_records('Event')

@@ -40,3 +41,56 @@ class TestEvent(unittest.TestCase):
self.assertTrue("_Test Event 1" in subjects)
self.assertTrue("_Test Event 3" in subjects)
self.assertFalse("_Test Event 2" in subjects)

def test_revert_logic(self):
ev = frappe.get_doc(test_records[0]).insert()
name = ev.name

frappe.delete_doc("Event", ev.name)

# insert again
ev = frappe.get_doc(test_records[0]).insert()

# the name should be same!
self.assertEquals(ev.name, name)

def test_assign(self):
from frappe.widgets.form.assign_to import add

ev = frappe.get_doc(test_records[0]).insert()

add({
"assign_to": "test@example.com",
"doctype": "Event",
"name": ev.name,
"description": "Test Assignment"
})

ev = frappe.get_doc("Event", ev.name)

self.assertEquals(ev._assign, json.dumps(["test@example.com"]))

# add another one
add({
"assign_to": "test1@example.com",
"doctype": "Event",
"name": ev.name,
"description": "Test Assignment"
})

ev = frappe.get_doc("Event", ev.name)

self.assertEquals(ev._assign, json.dumps(["test@example.com", "test1@example.com"]))

# close an assignment
todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name,
"owner": "test1@example.com"})
todo.status = "Closed"
todo.save()

ev = frappe.get_doc("Event", ev.name)
self.assertEquals(ev._assign, json.dumps(["test@example.com"]))

# cleanup
ev.delete()


+ 70
- 242
frappe/core/doctype/file_data/file_data.json Näytä tiedosto

@@ -1,255 +1,83 @@
{
"_last_update": null,
"_user_tags": null,
"allow_attach": null,
"allow_copy": null,
"allow_email": null,
"allow_import": 1,
"allow_print": null,
"allow_rename": null,
"allow_trash": null,
"autoname": "File.######",
"change_log": null,
"client_script": null,
"client_script_core": null,
"client_string": null,
"colour": null,
"creation": "2012-12-12 11:19:22",
"custom": null,
"default_print_format": null,
"description": null,
"docstatus": 0,
"doctype": "DocType",
"document_type": null,
"dt_template": null,
"allow_import": 1,
"autoname": "File.######",
"creation": "2012-12-12 11:19:22",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "file_name",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "File Name",
"no_column": null,
"no_copy": null,
"oldfieldname": "file_name",
"oldfieldtype": "Data",
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": 1,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "file_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "File Name",
"oldfieldname": "file_name",
"oldfieldtype": "Data",
"permlevel": 0,
"read_only": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "file_url",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "File URL",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": 1,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "file_url",
"fieldtype": "Data",
"in_list_view": 1,
"label": "File URL",
"permlevel": 0,
"read_only": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "Attached To DocType",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": "DocType",
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": 1,
"report_hide": null,
"reqd": null,
"search_index": 1,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Attached To DocType",
"options": "DocType",
"permlevel": 0,
"read_only": 1,
"search_index": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "attached_to_name",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "Attached To Name",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": 1,
"report_hide": null,
"reqd": null,
"search_index": 1,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "attached_to_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Attached To Name",
"permlevel": 0,
"read_only": 1,
"search_index": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "file_size",
"fieldtype": "Int",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "File Size",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": 1,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "file_size",
"fieldtype": "Int",
"in_list_view": 1,
"label": "File Size",
"permlevel": 0,
"read_only": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "content_hash",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Content Hash",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": 1,
"set_only_once": null,
"trigger": null,
"width": null
"fieldname": "content_hash",
"fieldtype": "Data",
"label": "Content Hash",
"permlevel": 0,
"search_index": 1
}
],
"hide_heading": null,
"hide_toolbar": null,
"icon": "icon-file",
"idx": 1,
"in_create": 1,
"in_dialog": null,
"is_submittable": null,
"is_transaction_doc": null,
"issingle": null,
"istable": null,
"max_attachments": null,
"menu_index": null,
"modified": "2014-04-16 09:28:02.979858",
"modified_by": "Administrator",
"module": "Core",
"name": "File Data",
"name_case": null,
"owner": "Administrator",
"parent": null,
"parent_node": null,
"parentfield": null,
"parenttype": null,
],
"icon": "icon-file",
"idx": 1,
"in_create": 1,
"modified": "2014-05-26 03:35:40.362967",
"modified_by": "Administrator",
"module": "Core",
"name": "File Data",
"owner": "Administrator",
"permissions": [
{
"amend": null,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": null,
"import": null,
"match": null,
"permlevel": 0,
"print": 1,
"read": 1,
"report": null,
"restrict": null,
"restricted": null,
"role": "System Manager",
"submit": null,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"role": "System Manager",
"write": 1
}
],
"plugin": null,
"print_outline": null,
"read_only": 0,
"read_only_onload": null,
"search_fields": null,
"section_style": null,
"server_code": null,
"server_code_compiled": null,
"server_code_core": null,
"server_code_error": null,
"show_in_menu": null,
"smallicon": null,
"subject": null,
"tag_fields": null,
"title_field": null,
"use_template": null,
"version": null
}
],
"read_only": 0
}

+ 16
- 16
frappe/core/doctype/file_data/file_data.py Näytä tiedosto

@@ -1,5 +1,5 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt

from __future__ import unicode_literals
"""
@@ -16,20 +16,20 @@ from frappe.utils.file_manager import delete_file_data_content
class FileData(Document):
def before_insert(self):
frappe.local.rollback_observers.append(self)
def on_update(self):
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile Data`
where content_hash=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype,
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.msgprint(frappe._("Same file has already been attached to the record"))
frappe.db.rollback()
raise frappe.DuplicateEntryError
if not getattr(self, "ignore_duplicate_entry_error", False):
# check duplicate assignement
n_records = frappe.db.sql("""select name from `tabFile Data`
where content_hash=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype,
self.attached_to_name))
if len(n_records) > 0:
self.duplicate_entry = n_records[0][0]
frappe.msgprint(frappe._("Same file has already been attached to the record"))
raise frappe.DuplicateEntryError

def on_trash(self):
if self.attached_to_name:
@@ -37,9 +37,9 @@ class FileData(Document):
try:
if not getattr(self, 'ignore_permissions', False) and \
not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name):
frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True)
except frappe.DoesNotExistError:
pass



+ 5
- 5
frappe/core/doctype/letter_head/letter_head.json Näytä tiedosto

@@ -1,6 +1,5 @@
{
"allow_attach": 1,
"autoname": "field:letter_head_name",
"autoname": "field:letter_head_name",
"creation": "2012-11-22 17:45:46",
"docstatus": 0,
"doctype": "DocType",
@@ -35,7 +34,8 @@
"label": "Is Default",
"oldfieldname": "is_default",
"oldfieldtype": "Check",
"permlevel": 0
"permlevel": 0,
"search_index": 1
},
{
"depends_on": "letter_head_name",
@@ -52,14 +52,13 @@
"icon": "icon-font",
"idx": 1,
"max_attachments": 3,
"modified": "2014-05-07 06:03:07.760995",
"modified": "2014-07-21 05:57:56.052191",
"modified_by": "Administrator",
"module": "Core",
"name": "Letter Head",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -72,6 +71,7 @@
"write": 1
},
{
"apply_user_permissions": 1,
"delete": 0,
"email": 0,
"permlevel": 0,


+ 5
- 113
frappe/core/doctype/module_def/module_def.json Näytä tiedosto

@@ -1,107 +1,35 @@
{
"_last_update": null,
"_user_tags": null,
"allow_attach": null,
"allow_copy": null,
"allow_email": null,
"allow_import": null,
"allow_print": null,
"allow_rename": 1,
"allow_trash": null,
"autoname": "field:module_name",
"change_log": null,
"client_script": null,
"client_script_core": null,
"client_string": null,
"colour": null,
"creation": "2013-01-10 16:34:03",
"custom": null,
"default_print_format": null,
"description": null,
"docstatus": 0,
"doctype": "DocType",
"document_type": null,
"dt_template": null,
"fields": [
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "module_name",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "Module Name",
"no_column": null,
"no_copy": null,
"oldfieldname": "module_name",
"oldfieldtype": "Data",
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
"permlevel": 0
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "app_name",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "App Name",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": 1,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
"reqd": 1
}
],
"hide_heading": null,
"hide_toolbar": null,
"icon": "icon-sitemap",
"idx": 1,
"in_create": null,
"in_dialog": null,
"is_submittable": null,
"is_transaction_doc": null,
"issingle": null,
"istable": null,
"max_attachments": null,
"menu_index": null,
"modified": "2014-04-07 13:00:27.894115",
"modified": "2014-06-12 01:00:52.304755",
"modified_by": "Administrator",
"module": "Core",
"name": "Module Def",
"name_case": null,
"owner": "Administrator",
"parent": null,
"parent_node": null,
"parentfield": null,
"parenttype": null,
"permissions": [
{
"amend": 0,
@@ -109,54 +37,18 @@
"create": 1,
"delete": 1,
"email": 1,
"export": null,
"import": null,
"match": null,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"restrict": null,
"restricted": null,
"role": "Administrator",
"submit": 0,
"write": 1
},
{
"amend": null,
"cancel": null,
"create": 1,
"delete": 1,
"email": null,
"export": null,
"import": null,
"match": null,
"permlevel": 0,
"print": null,
"read": 1,
"report": null,
"restrict": null,
"restricted": null,
"role": "System Manager",
"submit": null,
"write": 1
"role": "System Manager"
}
],
"plugin": null,
"print_outline": null,
"read_only": null,
"read_only_onload": null,
"search_fields": null,
"section_style": null,
"server_code": null,
"server_code_compiled": null,
"server_code_core": null,
"server_code_error": null,
"show_in_menu": null,
"smallicon": null,
"subject": null,
"tag_fields": null,
"title_field": null,
"use_template": null,
"version": null
]
}

+ 6
- 3
frappe/core/doctype/module_def/module_def.py Näytä tiedosto

@@ -8,13 +8,16 @@ from frappe.model.document import Document

class ModuleDef(Document):
def validate(self):
if not frappe.conf.get("developer_mode"):
return

modules = None
if not frappe.local.module_app.get(self.name):
if not frappe.local.module_app.get(frappe.scrub(self.name)):
with open(frappe.get_app_path(self.app_name, "modules.txt"), "r") as f:
content = f.read()
if not frappe.scrub(self.name) in content.splitlines():
if not self.name in content.splitlines():
modules = filter(None, content.splitlines())
modules.append(frappe.scrub(self.name))
modules.append(self.name)

if modules:
with open(frappe.get_app_path(self.app_name, "modules.txt"), "w") as f:


+ 43
- 11
frappe/core/doctype/notification_count/notification_count.py Näytä tiedosto

@@ -5,9 +5,11 @@

from __future__ import unicode_literals
import frappe
import MySQLdb
from frappe.model.document import Document

logger = frappe.get_logger()

class NotificationCount(Document):
pass

@@ -15,6 +17,7 @@ class NotificationCount(Document):
def get_notifications():
if frappe.flags.in_install_app:
return

config = get_notification_config()
can_read = frappe.user.get_can_read()
open_count_doctype = {}
@@ -32,34 +35,63 @@ def get_notifications():
open_count_doctype[d] = notification_count[d]
else:
result = frappe.get_list(d, fields=["count(*)"],
filters=[[d, key, "=", condition[key]]], as_list=True, limit_page_length=1)[0][0]

frappe.get_doc({"doctype":"Notification Count", "for_doctype":d,
"open_count":result}).insert(ignore_permissions=True)
filters=[[d, key, "=", condition[key]]], as_list=True)[0][0]

open_count_doctype[d] = result

try:
frappe.get_doc({"doctype":"Notification Count", "for_doctype":d,
"open_count":result}).insert(ignore_permissions=True)

except MySQLdb.OperationalError, e:
if e.args[0] != 1213:
raise

logger.error("Deadlock")

for m in config.for_module:
if m in notification_count:
open_count_module[m] = notification_count[m]
else:
open_count_module[m] = frappe.get_attr(config.for_module[m])()
frappe.get_doc({"doctype":"Notification Count", "for_doctype":m,
"open_count":open_count_module[m]}).insert(ignore_permissions=True)

try:
frappe.get_doc({"doctype":"Notification Count", "for_doctype":m,
"open_count":open_count_module[m]}).insert(ignore_permissions=True)

except MySQLdb.OperationalError, e:
if e.args[0] != 1213:
raise

logger.error("Deadlock")


return {
"open_count_doctype": open_count_doctype,
"open_count_module": open_count_module
}

def clear_notifications(user=None):
if frappe.flags.in_install_app=="frappe":
return

try:
if user:
frappe.db.sql("""delete from `tabNotification Count` where owner=%s""", (user,))
else:
frappe.db.sql("""delete from `tabNotification Count`""")

except MySQLdb.OperationalError, e:
if e.args[0] != 1213:
raise

logger.error("Deadlock")

def delete_notification_count_for(doctype):
if frappe.flags.in_import: return
frappe.db.sql("""delete from `tabNotification Count` where for_doctype = %s""", (doctype,))

def delete_event_notification_count():
delete_notification_count_for("Event")

def clear_doctype_notifications(doc, method=None):
def clear_doctype_notifications(doc, method=None, *args, **kwargs):
if frappe.flags.in_import:
return



+ 30
- 7
frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.json Näytä tiedosto

@@ -1,14 +1,28 @@
{
"allow_copy": 1,
"creation": "2014-03-03 19:48:01.000000",
"creation": "2014-03-03 19:48:01",
"description": "Email Settings for Outgoing and Incoming Emails.",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled",
"permlevel": 0
},
{
"depends_on": "eval:doc.enabled",
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"label": "Server & Credentials",
"permlevel": 0
},
{
"description": "SMTP Server (e.g. smtp.gmail.com)",
"fieldname": "mail_server",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Outgoing Mail Server",
"permlevel": 0
},
@@ -16,6 +30,7 @@
"description": "<a href=\"https://en.wikipedia.org/wiki/Transport_Layer_Security\" target=\"_blank\">[?]</a>",
"fieldname": "use_ssl",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Use TLS",
"permlevel": 0
},
@@ -23,6 +38,7 @@
"description": "If non standard port (e.g. 587)",
"fieldname": "mail_port",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Port",
"permlevel": 0
},
@@ -35,6 +51,7 @@
"description": "Set Login and Password if authentication is required.",
"fieldname": "mail_login",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Login Id",
"permlevel": 0
},
@@ -59,19 +76,25 @@
"permlevel": 0
},
{
"default": "1",
"description": "If checked, an email with an attached HTML format will be added to part of the EMail body as well as attachment. To only send as attachment, uncheck this.",
"fieldname": "send_print_in_body_and_attachment",
"fieldtype": "Check",
"label": "Send Print in Body and Attachment",
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"label": "Email Footer",
"permlevel": 0
},
{
"default": "<div style=\"padding: 7px; text-align: right; color: #888\"><small>Sent via \n\t<a style=\"color: #888\" href=\"http://frappe.io\">Frappe</a></div>",
"fieldname": "footer",
"fieldtype": "Text Editor",
"label": "",
"permlevel": 0,
"reqd": 0
}
],
"icon": "icon-cog",
"idx": 1,
"in_create": 1,
"issingle": 1,
"modified": "2014-03-03 20:20:09.000000",
"modified": "2014-07-17 08:08:00.483391",
"modified_by": "Administrator",
"module": "Core",
"name": "Outgoing Email Settings",


+ 13
- 6
frappe/core/doctype/outgoing_email_settings/outgoing_email_settings.py Näytä tiedosto

@@ -5,21 +5,28 @@

from __future__ import unicode_literals
import frappe

from frappe import _, throw
from frappe.utils import validate_email_add
from frappe.model.document import Document

class OutgoingEmailSettings(Document):
def validate(self):
if self.mail_server:
if self.auto_email_id and not validate_email_add(self.auto_email_id):
throw(_("{0} is not a valid email id").format(self.auto_email_id), frappe.InvalidEmailAddressError)

if self.mail_server and not frappe.local.flags.in_patch:
from frappe.utils import cint
from frappe.utils.email_lib.smtp import SMTPServer
smtpserver = SMTPServer(login = self.mail_login,
password = self.mail_password,
server = self.mail_server,
port = cint(self.mail_port),
use_ssl = self.use_ssl
use_ssl = cint(self.use_ssl)
)
# exceptions are handled in session connect
sess = smtpserver.sess
sess = smtpserver.sess

def get_mail_footer():
return frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "footer") or ""

+ 6
- 3
frappe/core/doctype/page/page.json Näytä tiedosto

@@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_rename": 1,
"autoname": "field:page_name",
"creation": "2012-12-20 17:16:49.000000",
"creation": "2012-12-20 17:16:49",
"docstatus": 0,
"doctype": "DocType",
"fields": [
@@ -16,6 +16,7 @@
{
"fieldname": "page_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Page Name",
"oldfieldname": "page_name",
"oldfieldtype": "Data",
@@ -25,12 +26,14 @@
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"permlevel": 0
},
{
"fieldname": "icon",
"fieldtype": "Data",
"in_list_view": 1,
"label": "icon",
"permlevel": 0
},
@@ -84,14 +87,13 @@
"idx": 1,
"issingle": 0,
"istable": 0,
"modified": "2013-12-30 13:48:02.000000",
"modified": "2014-05-27 03:49:14.476843",
"modified_by": "Administrator",
"module": "Core",
"name": "Page",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"create": 1,
"email": 1,
"permlevel": 0,
@@ -102,6 +104,7 @@
"write": 1
},
{
"apply_user_permissions": 1,
"email": 1,
"permlevel": 0,
"print": 1,


+ 17
- 9
frappe/core/doctype/print_format/print_format.js Näytä tiedosto

@@ -1,13 +1,21 @@
cur_frm.cscript.refresh = function (doc) {
frappe.ui.form.on("Print Format", "onload", function(frm) {
frm.add_fetch("doc_type", "module", "module");
});

frappe.ui.form.on("Print Format", "refresh", function(frm) {
frm.set_intro("");
if (user!="Administrator") {
if (doc.standard == 'Yes') {
cur_frm.toggle_enable(["html", "doc_type", "module"], false);
cur_frm.disable_save();
if (frm.doc.standard == 'Yes') {
frm.toggle_enable(["html", "doc_type", "module"], false);
frm.disable_save();
} else {
cur_frm.toggle_enable(["html", "doc_type", "module"], true);
cur_frm.enable_save();
frm.toggle_enable(["html", "doc_type", "module"], true);
frm.enable_save();
}
frm.toggle_enable("standard", false);
} else {
if(frm.doc.standard==="Yes") {
frm.set_intro(__("This is a standard format. To make changes, please copy it make make a new format."))
}

cur_frm.toggle_enable("standard", false);
}
}
})

+ 32
- 17
frappe/core/doctype/print_format/print_format.json Näytä tiedosto

@@ -1,12 +1,23 @@
{
"allow_attach": 0,
"allow_copy": 0,
"allow_copy": 0,
"allow_rename": 0,
"autoname": "Prompt",
"creation": "2013-01-23 19:54:43",
"docstatus": 0,
"doctype": "DocType",
"fields": [
{
"description": "Belongs to",
"fieldname": "doc_type",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "DocType",
"options": "DocType",
"permlevel": 0,
"reqd": 1,
"search_index": 0
},
{
"allow_on_submit": 0,
"fieldname": "module",
@@ -25,18 +36,6 @@
"reqd": 1,
"search_index": 1
},
{
"description": "Belongs to",
"fieldname": "doc_type",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "DocType",
"options": "DocType",
"permlevel": 0,
"reqd": 0,
"search_index": 0
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break",
@@ -63,11 +62,20 @@
"search_index": 1
},
{
"default": "Server",
"description": "Client-side formats are now deprecated",
"fieldname": "print_format_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Print Format Type",
"options": "Client\nServer",
"options": "Server\nClient",
"permlevel": 0,
"read_only": 0
},
{
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"permlevel": 0
},
{
@@ -77,7 +85,7 @@
},
{
"allow_on_submit": 0,
"depends_on": "eval:doc.print_format_type!=\"Server\"",
"depends_on": "",
"fieldname": "html",
"fieldtype": "Code",
"hidden": 0,
@@ -92,6 +100,13 @@
"report_hide": 0,
"reqd": 0,
"search_index": 0
},
{
"fieldname": "print_format_help",
"fieldtype": "HTML",
"label": "Print Format Help",
"options": "<h3>Print Format Help</h3>\n<hr>\n<h4>Introduction</h4>\n<p>Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the <code>doc</code> object which contains information about the document that is being formatted. You can also access common utilities via the <code>frappe</code> module.</p>\n<p>For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.</p>\n<hr>\n<h4>References</h4>\n<ol>\n\t<li><a href=\"http://jinja.pocoo.org/docs/templates/\" target=\"_blank\">Jinja Tempalting Language: Reference</a></li>\n\t<li><a href=\"http://getbootstrap.com\" target=\"_blank\">Bootstrap CSS Framework</a></li>\n</ol>\n<hr>\n<h4>Example</h4>\n<pre><code>&lt;h3&gt;{{ doc.select_print_heading or \"Invoice\" }}&lt;/h3&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-md-3 text-right\"&gt;Customer Name&lt;/div&gt;\n\t&lt;div class=\"col-md-9\"&gt;{{ doc.customer_name }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-md-3 text-right\"&gt;Date&lt;/div&gt;\n\t&lt;div class=\"col-md-9\"&gt;{{ doc.get_formatted(\"invoice_date\") }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;table class=\"table table-bordered\"&gt;\n\t&lt;tbody&gt;\n\t\t&lt;tr&gt;\n\t\t\t&lt;th&gt;Sr&lt;/th&gt;\n\t\t\t&lt;th&gt;Item Name&lt;/th&gt;\n\t\t\t&lt;th&gt;Description&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Qty&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Rate&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Amount&lt;/th&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- for row in doc.entries -%}\n\t\t&lt;tr&gt;\n\t\t\t&lt;td style=\"width: 3%;\"&gt;{{ row.idx }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 20%;\"&gt;\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t&lt;br&gt;Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 37%;\"&gt;\n\t\t\t\t&lt;div style=\"border: 0px;\"&gt;{{ row.description }}&lt;/div&gt;&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 10%; text-align: right;\"&gt;{{ row.qty }} {{ row.uom or row.stock_uom }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}&lt;/td&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- endfor -%}\n\t&lt;/tbody&gt;\n&lt;/table&gt;</code></pre>\n<hr>\n<h4>Common Functions</h4>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%\"><code>doc.get_formatted(\"[fieldname]\", [parent_doc])</code></td>\n\t\t\t<td>Get document value formatted as Date, Currency etc. Pass parent <code>doc</code> for curreny type fields.</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%\"><code>frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")</code></td>\n\t\t\t<td>Get value from another document.</td>\n\t\t</tr>\n\t</tbody>\n</table>\n",
"permlevel": 0
}
],
"hide_heading": 0,
@@ -103,7 +118,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2014-05-09 02:12:39.540952",
"modified": "2014-07-31 03:39:35.898711",
"modified_by": "Administrator",
"module": "Core",
"name": "Print Format",


+ 1
- 77
frappe/core/doctype/print_format/print_format.py Näytä tiedosto

@@ -2,11 +2,8 @@
# MIT License. See license.txt

from __future__ import unicode_literals
import frappe, os
import frappe
import frappe.utils
from frappe.modules import get_doc_path

standard_format = "templates/print_formats/standard.html"

from frappe.model.document import Document

@@ -38,76 +35,3 @@ class PrintFormat(Document):
if self.doc_type:
frappe.clear_cache(doctype=self.doc_type)

def get_args():
if not frappe.form_dict.format:
frappe.form_dict.format = standard_format
if not frappe.form_dict.doctype or not frappe.form_dict.name:
return {
"body": """<h1>Error</h1>
<p>Parameters doctype, name and format required</p>
<pre>%s</pre>""" % repr(frappe.form_dict)
}

doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name)
for ptype in ("read", "print"):
if not frappe.has_permission(doc.doctype, ptype, doc):
return {
"body": """<h1>Error</h1>
<p>No {ptype} permission</p>""".format(ptype=ptype)
}

return {
"body": get_html(doc),
"css": get_print_style(frappe.form_dict.style),
"comment": frappe.session.user
}

def get_html(doc, name=None, print_format=None):
from jinja2 import Environment

if isinstance(doc, basestring) and isinstance(name, basestring):
doc = frappe.get_doc(doc, name)

template = Environment().from_string(get_print_format_name(doc.doctype,
print_format or frappe.form_dict.format))
meta = frappe.get_meta(doc.doctype)

args = {
"doc": doc,
"meta": meta,
"frappe": frappe,
"utils": frappe.utils
}
html = template.render(args)
return html

def get_print_format_name(doctype, format_name):
if format_name==standard_format:
return format_name

# server, find template
path = os.path.join(get_doc_path(frappe.db.get_value("DocType", doctype, "module"),
"Print Format", format_name), format_name + ".html")
if os.path.exists(path):
with open(path, "r") as pffile:
return pffile.read()
else:
html = frappe.db.get_value("Print Format", format_name, "html")
if html:
return html
else:
return "No template found.\npath: " + path

def get_print_style(style=None):
if not style:
style = frappe.db.get_default("print_style") or "Standard"
path = os.path.join(get_doc_path("Core", "DocType", "Print Format"), "styles",
style.lower() + ".css")
if not os.path.exists(path):
if style!="Standard":
return get_print_style("Standard")
else:
return "/* Standard Style Missing ?? */"
else:
with open(path, 'r') as sfile:
return sfile.read()

+ 0
- 82
frappe/core/doctype/print_format/styles/classic.css Näytä tiedosto

@@ -1,82 +0,0 @@
/*
common style for whole page
This should include:
+ page size related settings
+ font family settings
+ line spacing settings
*/
@media screen {
body {
width: 8.3in;
}
}

html, body, div, span, td {
font-family: "Georgia", serif;
font-size: 12px;
}

body {
padding: 10px;
margin: auto;
font-size: 12px;
line-height: 150%;
}

.common {
font-family: "Georgia", serif !important;
font-size: 12px;
padding: 10px 0px;
}

table {
border-collapse: collapse;
width: 100%;
vertical-align: top;
}

table td {
padding: 2px 0px;
}

table.table-bordered td,
table.table-bordered th {
border: 1px solid #000;
padding: 2px;
vertical-align: top;
}

table h1, h2, h3, h4, h5, h6 {
padding: 0px;
margin: 0px;
}

table.header-table td {
vertical-align: top;
}

table.header-table thead {
border-bottom: 1px solid black;
}

table.header-table h3 {
color: gray;
}

table.header-table thead td {
padding: 5px 0px;
}

div.page-body table td:nth-child(6),
div.page-body table td:nth-child(7) {
text-align: right;
}

table.footer-table td {
vertical-align: top;
}

table.footer-table td table td:nth-child(2),
table.footer-table td table td:nth-child(3) {
text-align: right;
}

+ 0
- 97
frappe/core/doctype/print_format/styles/modern.css Näytä tiedosto

@@ -1,97 +0,0 @@
@media screen {
body {
width: 8.3in;
}
}

html, body, div, span, td {
font-family: "Helvetica", "Arial", sans-serif;
font-size: 12px;
}

body {
padding: 10px;
margin: auto;
font-size: 12px;
line-height: 150%;
}

.common {
font-family: "Helvetica", "Arial", sans-serif !important;
font-size: 12px;
padding: 10px 0px;
}

table {
border-collapse: collapse;
width: 100%;
vertical-align: top;
border-style: none !important;
}

table td {
padding: 2px 0px;
border-style: none !important;
}

table.table-bordered td,
table.table-bordered th {
padding: 2px;
vertical-align: top;
}

table h1, h2, h3, h4, h5, h6 {
padding: 0px;
margin: 0px;
}

table.header-table td {
vertical-align: top;
}

table.header-table h1 {
text-transform: uppercase;
color: white;
font-size: 55px;
font-style: italic;
}

table.header-table thead tr:nth-child(1) td {
height: 24px;
background-color: #696969;
vertical-align: middle;
padding: 12px 0px 0px 0px;
width: 100%;
}

div.page-body table td:nth-child(6),
div.page-body table td:nth-child(7) {
text-align: right;
}

div.page-body table tr td {
background-color: #DCDCDC !important;
}

div.page-body table th {
background-color: #696969 !important;
color: white !important;
}

table.footer-table td {
vertical-align: top;
}

table.footer-table td table td:nth-child(2),
table.footer-table td table td:nth-child(3) {
text-align: right;
}

table.footer-table tfoot td {
background-color: #696969;
height: 10px;
}

.imp-details {
background-color: #DCDCDC;
}

+ 0
- 355
frappe/core/doctype/print_format/styles/standard.css Näytä tiedosto

@@ -1,355 +0,0 @@
@media screen {
body {
width: 8.3in;
}
}
body {
padding: 10px;
margin: auto;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 20px;
color: #333333;
background-color: #ffffff;
}

* {
/*color: #000 !important;*/
text-shadow: none !important;
background: transparent !important;
box-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
.ir a:after,
a[href^="javascript:"]:after,
a[href^="#"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
@page {
margin: 0.5cm;
}
p,
h2,
h3 {
widows: 3;
orphans: 3;
}
h2,
h3 {
page-break-after: avoid;
}

h1,
h2,
h3,
h4,
h5,
h6 {
margin: 10px 0;
font-family: inherit;
font-weight: bold;
line-height: 20px;
color: inherit;
text-rendering: optimizelegibility;
}

table {
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
border-spacing: 0;
}

td {
vertical-align: top;
}

.table {
width: 100%;
margin-bottom: 20px;
}

.table th,
.table td {
padding: 8px;
line-height: 20px;
text-align: left;
vertical-align: top;
border-top: 1px solid #dddddd;
}

.table th {
font-weight: bold;
}

.table thead th {
vertical-align: bottom;
}

.table caption + thead tr:first-child th,
.table caption + thead tr:first-child td,
.table colgroup + thead tr:first-child th,
.table colgroup + thead tr:first-child td,
.table thead:first-child tr:first-child th,
.table thead:first-child tr:first-child td {
border-top: 0;
}

.table tbody + tbody {
border-top: 2px solid #dddddd;
}

.table .table {
background-color: #ffffff;
}

.table-condensed th,
.table-condensed td {
padding: 4px 5px;
}

.table-bordered {
border: 1px solid #dddddd;
border-collapse: separate;
*border-collapse: collapse;
border-left: 0;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}

.table-bordered th,
.table-bordered td {
border-left: 1px solid #dddddd;
}

.table-bordered caption + thead tr:first-child th,
.table-bordered caption + tbody tr:first-child th,
.table-bordered caption + tbody tr:first-child td,
.table-bordered colgroup + thead tr:first-child th,
.table-bordered colgroup + tbody tr:first-child th,
.table-bordered colgroup + tbody tr:first-child td,
.table-bordered thead:first-child tr:first-child th,
.table-bordered tbody:first-child tr:first-child th,
.table-bordered tbody:first-child tr:first-child td {
border-top: 0;
}

.table-bordered thead:first-child tr:first-child > th:first-child,
.table-bordered tbody:first-child tr:first-child > td:first-child {
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
}

.table-bordered thead:first-child tr:first-child > th:last-child,
.table-bordered tbody:first-child tr:first-child > td:last-child {
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
-moz-border-radius-topright: 4px;
}

.table-bordered thead:last-child tr:last-child > th:first-child,
.table-bordered tbody:last-child tr:last-child > td:first-child,
.table-bordered tfoot:last-child tr:last-child > td:first-child {
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
}

.table-bordered thead:last-child tr:last-child > th:last-child,
.table-bordered tbody:last-child tr:last-child > td:last-child,
.table-bordered tfoot:last-child tr:last-child > td:last-child {
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
-moz-border-radius-bottomright: 4px;
}

.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
-webkit-border-bottom-left-radius: 0;
border-bottom-left-radius: 0;
-moz-border-radius-bottomleft: 0;
}

.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0;
-moz-border-radius-bottomright: 0;
}

.table-bordered caption + thead tr:first-child th:first-child,
.table-bordered caption + tbody tr:first-child td:first-child,
.table-bordered colgroup + thead tr:first-child th:first-child,
.table-bordered colgroup + tbody tr:first-child td:first-child {
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
}

.table-bordered caption + thead tr:first-child th:last-child,
.table-bordered caption + tbody tr:first-child td:last-child,
.table-bordered colgroup + thead tr:first-child th:last-child,
.table-bordered colgroup + tbody tr:first-child td:last-child {
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
-moz-border-radius-topright: 4px;
}

.table-striped tbody > tr:nth-child(odd) > td,
.table-striped tbody > tr:nth-child(odd) > th {
background-color: #f9f9f9;
}

.table-hover tbody tr:hover td,
.table-hover tbody tr:hover th {
background-color: #f5f5f5;
}

table td[class*="span"],
table th[class*="span"],
.row-fluid table td[class*="span"],
.row-fluid table th[class*="span"] {
display: table-cell;
float: none;
margin-left: 0;
}

.table td.span1,
.table th.span1 {
float: none;
width: 44px;
margin-left: 0;
}

.table td.span2,
.table th.span2 {
float: none;
width: 124px;
margin-left: 0;
}

.table td.span3,
.table th.span3 {
float: none;
width: 204px;
margin-left: 0;
}

.table td.span4,
.table th.span4 {
float: none;
width: 284px;
margin-left: 0;
}

.table td.span5,
.table th.span5 {
float: none;
width: 364px;
margin-left: 0;
}

.table td.span6,
.table th.span6 {
float: none;
width: 444px;
margin-left: 0;
}

.table td.span7,
.table th.span7 {
float: none;
width: 524px;
margin-left: 0;
}

.table td.span8,
.table th.span8 {
float: none;
width: 604px;
margin-left: 0;
}

.table td.span9,
.table th.span9 {
float: none;
width: 684px;
margin-left: 0;
}

.table td.span10,
.table th.span10 {
float: none;
width: 764px;
margin-left: 0;
}

.table td.span11,
.table th.span11 {
float: none;
width: 844px;
margin-left: 0;
}

.table td.span12,
.table th.span12 {
float: none;
width: 924px;
margin-left: 0;
}

.table tbody tr.success td {
background-color: #dff0d8;
}

.table tbody tr.error td {
background-color: #f2dede;
}

.table tbody tr.warning td {
background-color: #fcf8e3;
}

.table tbody tr.info td {
background-color: #d9edf7;
}

.table-hover tbody tr.success:hover td {
background-color: #d0e9c6;
}

.table-hover tbody tr.error:hover td {
background-color: #ebcccc;
}

.table-hover tbody tr.warning:hover td {
background-color: #faf2cc;
}

.table-hover tbody tr.info:hover td {
background-color: #c4e3f3;
}

+ 29
- 0
frappe/core/doctype/print_format/test_print_format.py Näytä tiedosto

@@ -0,0 +1,29 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# See license.txt

import frappe
import unittest
import re

test_records = frappe.get_test_records('Print Format')

class TestPrintFormat(unittest.TestCase):
def test_print_user(self, style=None):
print_html = frappe.get_print_format("User", "Administrator", style=style)
self.assertTrue("<label>First Name</label>" in print_html)
self.assertTrue(re.findall('<div class="col-xs-7[\s]*">[\s]*Administrator[\s]*</div>', print_html))
return print_html

def test_print_user_standard(self):
print_html = self.test_print_user("Standard")
self.assertTrue(re.findall('\.print-format {[\s]*font-size: 9pt;', print_html))
self.assertFalse(re.findall('th {[\s]*background-color: #eee;[\s]*}', print_html))
self.assertFalse("font-family: serif;" in print_html)

def test_print_user_modern(self):
print_html = self.test_print_user("Modern")
self.assertTrue(re.findall('th {[\s]*background-color: #eee;[\s]*}', print_html))

def test_print_user_classic(self):
print_html = self.test_print_user("Classic")
self.assertTrue("font-family: serif;" in print_html)

+ 8
- 0
frappe/core/doctype/print_format/test_records.json Näytä tiedosto

@@ -0,0 +1,8 @@
[
{
"doctype": "Print Format",
"name": "_Test Print Format 1",
"module": "core",
"doc_type": "User"
}
]

frappe/website/doctype/website_template/__init__.py → frappe/core/doctype/print_settings/__init__.py Näytä tiedosto


+ 11
- 0
frappe/core/doctype/print_settings/print_settings.js Näytä tiedosto

@@ -0,0 +1,11 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

frappe.ui.form.on("Print Settings", "print_style", function (frm) {
frm.get_field("print_style_preview").html('<img src="/assets/frappe/images/help/print-style-' +
frm.doc.print_style.toLowerCase() + '.png" class="img-responsive">');
});

frappe.ui.form.on("Print Settings", "onload", function (frm) {
frm.script_manager.trigger("print_style");
});

+ 96
- 0
frappe/core/doctype/print_settings/print_settings.json Näytä tiedosto

@@ -0,0 +1,96 @@
{
"creation": "2014-07-17 06:54:20.782907",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"fieldname": "pdf_settings",
"fieldtype": "Section Break",
"label": "PDF Settings",
"permlevel": 0
},
{
"default": "1",
"description": "Send Email Print Attachments as PDF (Recommended)",
"fieldname": "send_print_as_pdf",
"fieldtype": "Check",
"label": "Send Print as PDF",
"permlevel": 0
},
{
"default": "A4",
"fieldname": "pdf_page_size",
"fieldtype": "Select",
"label": "PDF Page Size",
"options": "A4\nLetter",
"permlevel": 0
},
{
"fieldname": "print_style_section",
"fieldtype": "Section Break",
"label": "Print Style",
"permlevel": 0
},
{
"default": "",
"fieldname": "print_style",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Print Style",
"options": "Modern\nClassic\nStandard\nMonochrome",
"permlevel": 0
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"description": "In points. Default is 9.",
"fieldname": "font_size",
"fieldtype": "Float",
"label": "Font Size",
"permlevel": 0
},
{
"default": "1",
"description": "Print with Letterhead, unless unchecked in a particular Document",
"fieldname": "with_letterhead",
"fieldtype": "Check",
"label": "With Letterhead",
"permlevel": 0,
"reqd": 0
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"permlevel": 0
},
{
"fieldname": "print_style_preview",
"fieldtype": "HTML",
"label": "Print Style Preview",
"permlevel": 0
}
],
"icon": "icon-cog",
"issingle": 1,
"modified": "2014-08-05 09:03:02.337355",
"modified_by": "Administrator",
"module": "Core",
"name": "Print Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"permlevel": 0,
"read": 1,
"role": "System Manager",
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC"
}

+ 10
- 0
frappe/core/doctype/print_settings/print_settings.py Näytä tiedosto

@@ -0,0 +1,10 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document

class PrintSettings(Document):
def on_update(self):
frappe.clear_cache()

+ 5
- 3
frappe/core/doctype/property_setter/property_setter.json Näytä tiedosto

@@ -1,5 +1,5 @@
{
"creation": "2013-01-10 16:34:04.000000",
"creation": "2013-01-10 16:34:04",
"description": "Property Setter overrides a standard DocType or Field property",
"docstatus": 0,
"doctype": "DocType",
@@ -20,6 +20,7 @@
"depends_on": "eval:doc.__islocal",
"fieldname": "doctype_or_field",
"fieldtype": "Select",
"in_list_view": 1,
"label": "DocType or Field",
"options": "\nDocField\nDocType",
"permlevel": 0,
@@ -29,6 +30,7 @@
"description": "New value to be set",
"fieldname": "value",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Set Value",
"permlevel": 0
},
@@ -83,7 +85,7 @@
],
"icon": "icon-glass",
"idx": 1,
"modified": "2014-01-20 17:49:03.000000",
"modified": "2014-07-24 02:04:26.838056",
"modified_by": "Administrator",
"module": "Core",
"name": "Property Setter",
@@ -116,5 +118,5 @@
"write": 1
}
],
"search_fields": "doc_name,property"
"search_fields": "doc_type,property"
}

+ 29
- 25
frappe/core/doctype/property_setter/property_setter.py Näytä tiedosto

@@ -1,5 +1,5 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt

from __future__ import unicode_literals
import frappe
@@ -7,7 +7,6 @@ import frappe
from frappe.model.document import Document

class PropertySetter(Document):

def autoname(self):
self.name = self.doc_type + "-" \
+ (self.field_name and (self.field_name + "-") or "") \
@@ -21,47 +20,52 @@ class PropertySetter(Document):
and doc_type = %(doc_type)s
and ifnull(field_name,'') = ifnull(%(field_name)s, '')
and property = %(property)s""", self.get_valid_dict())
# clear cache
frappe.clear_cache(doctype = self.doc_type)
def get_property_list(self, dt):
return frappe.db.sql("""select fieldname, label, fieldtype
return frappe.db.sql("""select fieldname, label, fieldtype
from tabDocField
where parent=%s
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table')
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table', 'Fold')
and ifnull(fieldname, '') != ''
order by label asc""", dt, as_dict=1)
def get_setup_data(self):
return {
'doctypes': [d[0] for d in frappe.db.sql("select name from tabDocType")],
'dt_properties': self.get_property_list('DocType'),
'df_properties': self.get_property_list('DocField')
}
def get_field_ids(self):
return frappe.db.sql("select name, fieldtype, label, fieldname from tabDocField where parent=%s", self.doc_type, as_dict = 1)
def get_defaults(self):
if not self.field_name:
return frappe.db.sql("select * from `tabDocType` where name=%s", self.doc_type, as_dict = 1)[0]
else:
return frappe.db.sql("select * from `tabDocField` where fieldname=%s and parent=%s",
return frappe.db.sql("select * from `tabDocField` where fieldname=%s and parent=%s",
(self.field_name, self.doc_type), as_dict = 1)[0]
def on_update(self):
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
validate_fields_for_doctype(self.doc_type)
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False):
return frappe.get_doc({
"doctype":"Property Setter",
"doctype_or_field": for_doctype and "DocType" or "DocField",
"doc_type": doctype,
"field_name": fieldname,
"property": property,
"value": value,
"property_type": property_type
}).insert()
if not getattr(self, "ignore_validate", False) and getattr(self, "validate_fields_for_doctype", True):
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
validate_fields_for_doctype(self.doc_type)

def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False, validate_fields_for_doctype=True):
# WARNING: Ignores Permissions
property_setter = frappe.get_doc({
"doctype":"Property Setter",
"doctype_or_field": for_doctype and "DocType" or "DocField",
"doc_type": doctype,
"field_name": fieldname,
"property": property,
"value": value,
"property_type": property_type
})
property_setter.ignore_permissions = True
property_setter.validate_fields_for_doctype = validate_fields_for_doctype
property_setter.insert()
return property_setter

+ 8
- 0
frappe/core/doctype/report/boilerplate/controller.js Näytä tiedosto

@@ -0,0 +1,8 @@
// Copyright (c) 2013, {app_publisher} and contributors
// For license information, please see license.txt

frappe.query_reports["{name}"] = {{
"filters": [

]
}}

+ 9
- 0
frappe/core/doctype/report/boilerplate/controller.py Näytä tiedosto

@@ -0,0 +1,9 @@
# Copyright (c) 2013, {app_publisher} and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe

def execute(filters=None):
columns, data = [], []
return columns, data

+ 16
- 1
frappe/core/doctype/report/report.js Näytä tiedosto

@@ -11,7 +11,22 @@ cur_frm.cscript.refresh = function(doc) {
frappe.set_route("query-report", doc.name);
break;
}
}, "icon-table")
}, "icon-table");

if (doc.is_standard === "Yes") {
cur_frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() {
$.ajax({
url: "/api/resource/Report/" + encodeURIComponent(doc.name),
type: "POST",
data: {
run_method: "toggle_disable",
disable: doc.disabled ? 0 : 1
}
}).always(function() {
cur_frm.reload_doc();
});
}, doc.disabled ? "icon-ok" : "icon-off");
}

cur_frm.set_intro("");
switch(doc.report_type) {


+ 11
- 5
frappe/core/doctype/report/report.json Näytä tiedosto

@@ -61,6 +61,15 @@
"permlevel": 0,
"read_only": 0
},
{
"default": "1",
"depends_on": "eval:[\"Query Report\", \"Script Report\"].indexOf(doc.report_type)!==-1",
"fieldname": "apply_user_permissions",
"fieldtype": "Check",
"in_list_view": 0,
"label": "Apply User Permissions",
"permlevel": 0
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
@@ -101,14 +110,13 @@
],
"icon": "icon-table",
"idx": 1,
"modified": "2014-05-12 17:08:04.185601",
"modified": "2014-06-03 07:25:41.509885",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -121,7 +129,6 @@
"write": 1
},
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -134,7 +141,6 @@
"write": 1
},
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
@@ -147,7 +153,7 @@
"write": 1
},
{
"cancel": 0,
"apply_user_permissions": 1,
"delete": 0,
"email": 1,
"permlevel": 0,


+ 25
- 10
frappe/core/doctype/report/report.py Näytä tiedosto

@@ -3,35 +3,50 @@

from __future__ import unicode_literals
import frappe
from frappe import conf, _
from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
from frappe.modules import make_boilerplate

class Report(Document):
def validate(self):
"""only administrator can save standard report"""
if not self.module:
self.module = frappe.db.get_value("DocType", self.ref_doctype, "module")
if not self.is_standard:
self.is_standard = "No"
if frappe.session.user=="Administrator" and getattr(conf, 'developer_mode',0)==1:
if frappe.session.user=="Administrator" and getattr(frappe.local.conf, 'developer_mode',0)==1:
self.is_standard = "Yes"

if self.is_standard == "Yes" and frappe.session.user!="Administrator":
frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."),
frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."),
raise_exception=True)

if self.report_type in ("Query Report", "Script Report") \
and frappe.session.user!="Administrator":
frappe.msgprint(_("Only Administrator allowed to create Query / Script Reports"),
raise_exception=True)
def on_update(self):
self.export_doc()
def export_doc(self):
from frappe.modules.export_file import export_to_files
if self.is_standard == 'Yes' and (conf.get('developer_mode') or 0) == 1:
export_to_files(record_list=[['Report', self.name]],
if frappe.flags.in_import:
return

if self.is_standard == 'Yes' and (frappe.local.conf.get('developer_mode') or 0) == 1:
export_to_files(record_list=[['Report', self.name]],
record_module=self.module)

self.create_report_py()

def create_report_py(self):
if self.report_type == "Script Report":
make_boilerplate("controller.py", self, {"name": self.name})
make_boilerplate("controller.js", self, {"name": self.name})

@Document.whitelist
def toggle_disable(self, disable):
self.db_set("disabled", cint(disable))

+ 1
- 1
frappe/core/doctype/role/role.js Näytä tiedosto

@@ -2,7 +2,7 @@
// MIT License. See license.txt

cur_frm.cscript.refresh = function(doc) {
cur_frm.permission_manager = cur_frm.add_custom_button("Permission Manager", function() {
cur_frm.permission_manager = cur_frm.add_custom_button("Role Permissions Manager", function() {
frappe.route_options = {"role": doc.name};
frappe.set_route("permission-manager");
});


+ 3
- 3
frappe/core/doctype/role/role.json Näytä tiedosto

@@ -23,7 +23,7 @@
"idx": 1,
"issingle": 0,
"istable": 0,
"modified": "2014-05-02 06:35:09.528809",
"modified": "2014-08-05 05:24:42.185395",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",
@@ -31,7 +31,6 @@
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"email": 1,
"permlevel": 0,
@@ -43,8 +42,8 @@
"write": 1
},
{
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"permlevel": 0,
"print": 1,
@@ -55,6 +54,7 @@
"write": 1
},
{
"apply_user_permissions": 1,
"permlevel": 0,
"read": 1,
"role": "All"


+ 0
- 79
frappe/core/doctype/social_login_keys/social_login_keys.json Näytä tiedosto

@@ -1,79 +0,0 @@
{
"creation": "2014-03-04 08:29:52.000000",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"fieldname": "facebook",
"fieldtype": "Section Break",
"label": "Facebook",
"permlevel": 0
},
{
"fieldname": "facebook_client_id",
"fieldtype": "Data",
"label": "Facebook Client ID",
"permlevel": 0
},
{
"fieldname": "facebook_client_secret",
"fieldtype": "Data",
"label": "Facebook Client Secret",
"permlevel": 0
},
{
"fieldname": "google",
"fieldtype": "Section Break",
"label": "Google",
"permlevel": 0
},
{
"fieldname": "google_client_id",
"fieldtype": "Data",
"label": "Google Client ID",
"permlevel": 0
},
{
"fieldname": "google_client_secret",
"fieldtype": "Data",
"label": "Google Client Secret",
"permlevel": 0
},
{
"fieldname": "github",
"fieldtype": "Section Break",
"label": "GitHub",
"permlevel": 0
},
{
"fieldname": "github_client_id",
"fieldtype": "Data",
"label": "GitHub Client ID",
"permlevel": 0
},
{
"fieldname": "github_client_secret",
"fieldtype": "Data",
"label": "GitHub Client Secret",
"permlevel": 0
}
],
"icon": "icon-signin",
"idx": 1,
"issingle": 1,
"modified": "2014-03-04 08:47:32.000000",
"modified_by": "Administrator",
"module": "Core",
"name": "Social Login Keys",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"permlevel": 0,
"read": 1,
"role": "System Manager",
"write": 1
}
]
}

frappe/website/page/sitemap_browser/__init__.py → frappe/core/doctype/standard_reply/__init__.py Näytä tiedosto


+ 71
- 0
frappe/core/doctype/standard_reply/standard_reply.json Näytä tiedosto

@@ -0,0 +1,71 @@
{
"allow_import": 1,
"autoname": "field:subject",
"creation": "2014-06-19 05:20:26.331041",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Transaction",
"fields": [
{
"fieldname": "subject",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Subject",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "response",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Response",
"permlevel": 0,
"reqd": 1
},
{
"default": "user",
"fieldname": "owner",
"fieldtype": "Link",
"hidden": 1,
"label": "Owner",
"options": "User",
"permlevel": 0
}
],
"icon": "icon-comment",
"modified": "2014-06-19 05:45:09.855045",
"modified_by": "Administrator",
"module": "Core",
"name": "Standard Reply",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"permlevel": 0,
"read": 1,
"role": "All"
},
{
"apply_user_permissions": 1,
"create": 1,
"permlevel": 0,
"read": 1,
"role": "All",
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"permlevel": 0,
"read": 1,
"report": 1,
"role": "System Manager",
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC"
}

+ 9
- 0
frappe/core/doctype/standard_reply/standard_reply.py Näytä tiedosto

@@ -0,0 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document

class StandardReply(Document):
pass

+ 84
- 323
frappe/core/doctype/system_settings/system_settings.json Näytä tiedosto

@@ -1,339 +1,100 @@
{
"_last_update": null,
"_user_tags": null,
"allow_attach": null,
"allow_copy": null,
"allow_email": null,
"allow_import": null,
"allow_print": null,
"allow_rename": null,
"allow_trash": null,
"autoname": null,
"change_log": null,
"client_script": null,
"client_script_core": null,
"client_string": null,
"colour": null,
"creation": "2014-04-17 16:53:52.640856",
"custom": null,
"default_print_format": null,
"description": null,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"dt_template": null,
"creation": "2014-04-17 16:53:52.640856",
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"fields": [
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "localization",
"fieldtype": "Section Break",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Localization",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "localization",
"fieldtype": "Section Break",
"label": "Localization",
"permlevel": 0
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "language",
"fieldtype": "Select",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": 1,
"label": "Language",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": "Loading...",
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": 1,
"search_index": 0,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "language",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Language",
"options": "Loading...",
"permlevel": 0,
"reqd": 1,
"search_index": 0
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "time_zone",
"fieldtype": "Select",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Time Zone",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": 1,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "time_zone",
"fieldtype": "Select",
"label": "Time Zone",
"permlevel": 0,
"reqd": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "date_and_number_format",
"fieldtype": "Section Break",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Date and Number Format",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "date_and_number_format",
"fieldtype": "Section Break",
"label": "Date and Number Format",
"permlevel": 0
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "date_format",
"fieldtype": "Select",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Date Format",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\nmm/dd/yyyy\nmm-dd-yyyy",
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": 1,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "date_format",
"fieldtype": "Select",
"label": "Date Format",
"options": "yyyy-mm-dd\ndd-mm-yyyy\ndd/mm/yyyy\ndd.mm.yyyy\nmm/dd/yyyy\nmm-dd-yyyy",
"permlevel": 0,
"reqd": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "number_format",
"fieldtype": "Select",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Number Format",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": "#,###.##\n#.###,##\n# ###.##\n#,###.###\n#,##,###.##\n#.###\n#,###",
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": 1,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "number_format",
"fieldtype": "Select",
"label": "Number Format",
"options": "#,###.##\n#.###,##\n# ###.##\n# ###,##\n#'###.##\n#, ###.##\n#,##,###.##\n#,###.###\n#.###\n#,###",
"permlevel": 0,
"reqd": 1
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "float_precision",
"fieldtype": "Select",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Float Precision",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": "\n2\n3\n4\n5\n6",
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "float_precision",
"fieldtype": "Select",
"label": "Float Precision",
"options": "\n2\n3\n4\n5\n6",
"permlevel": 0
},
{
"allow_on_submit": null,
"default": null,
"depends_on": null,
"description": null,
"fieldname": "security",
"fieldtype": "Section Break",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Security",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": null,
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
},
"fieldname": "security",
"fieldtype": "Section Break",
"label": "Security",
"permlevel": 0
},
{
"allow_on_submit": null,
"default": "06:00",
"depends_on": null,
"description": "Session Expiry in Hours e.g. 06:00",
"fieldname": "session_expiry",
"fieldtype": "Data",
"hidden": null,
"ignore_restrictions": null,
"in_filter": null,
"in_list_view": null,
"label": "Session Expiry",
"no_column": null,
"no_copy": null,
"oldfieldname": null,
"oldfieldtype": null,
"options": "",
"permlevel": 0,
"print_hide": null,
"print_width": null,
"read_only": null,
"report_hide": null,
"reqd": null,
"search_index": null,
"set_only_once": null,
"trigger": null,
"width": null
"default": "06:00",
"description": "Session Expiry in Hours e.g. 06:00",
"fieldname": "session_expiry",
"fieldtype": "Data",
"label": "Session Expiry",
"options": "",
"permlevel": 0
},
{
"description": "Run scheduled jobs only if checked",
"fieldname": "enable_scheduler",
"fieldtype": "Check",
"in_list_view": 0,
"label": "Enable Scheduled Jobs",
"permlevel": 0
}
],
"hide_heading": null,
"hide_toolbar": null,
"icon": "icon-cog",
"idx": null,
"in_create": null,
"in_dialog": null,
"is_submittable": null,
"is_transaction_doc": null,
"issingle": 1,
"istable": null,
"max_attachments": null,
"menu_index": null,
"modified": "2014-04-17 17:52:27.046530",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
"name_case": "",
"owner": "Administrator",
"parent": null,
"parent_node": null,
"parentfield": null,
"parenttype": null,
],
"icon": "icon-cog",
"issingle": 1,
"modified": "2014-06-18 02:09:03.623094",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": null,
"cancel": null,
"create": 1,
"delete": null,
"email": null,
"export": null,
"import": null,
"match": null,
"permlevel": 0,
"print": null,
"read": 1,
"report": null,
"restrict": null,
"restricted": null,
"role": "System Manager",
"submit": null,
"create": 1,
"permlevel": 0,
"read": 1,
"role": "System Manager",
"write": 1
}
],
"plugin": null,
"print_outline": null,
"read_only": null,
"read_only_onload": null,
"search_fields": null,
"section_style": null,
"server_code": null,
"server_code_compiled": null,
"server_code_core": null,
"server_code_error": null,
"show_in_menu": null,
"smallicon": null,
"subject": null,
"tag_fields": null,
"title_field": null,
"use_template": null,
"version": null
}
]
}

+ 6
- 5
frappe/core/doctype/system_settings/system_settings.py Näytä tiedosto

@@ -2,11 +2,12 @@
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe, pytz
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.translate import get_lang_dict, set_default_language
from frappe.utils import cint
from frappe.utils.momentjs import get_all_timezones

class SystemSettings(Document):
def validate(self):
@@ -17,11 +18,11 @@ class SystemSettings(Document):

def on_update(self):
for df in self.meta.get("fields"):
if df.fieldtype in ("Select", "Data"):
if df.fieldtype in ("Select", "Data", "Check"):
frappe.db.set_default(df.fieldname, self.get(df.fieldname))

set_default_language(self.language)
if self.language:
set_default_language(self.language)

@frappe.whitelist()
def load():
@@ -39,7 +40,7 @@ def load():
languages.sort()

return {
"timezones": pytz.all_timezones,
"timezones": get_all_timezones(),
"languages": [""] + languages,
"defaults": defaults
}

+ 18
- 8
frappe/core/doctype/todo/todo.js Näytä tiedosto

@@ -1,13 +1,23 @@
// bind events

frappe.ui.form.on("ToDo", "refresh", function(frm) {
frm.add_custom_button((frm.doc.status=="Open" ? __("Close") : __("Re-open")), function() {
frm.set_value("status", frm.doc.status=="Open" ? "Closed" : "Open");
frm.save();
});
if(frm.doc.reference_type && frm.doc.reference_name) {
frm.set_intro('Reference: <a href="#Form/'+frm.doc.reference_type+'/'+frm.doc.reference_name+'">'
+ frm.doc.reference_name + '</a>');
frm.add_custom_button(__(frm.doc.reference_name), function() {
frappe.set_route("Form", frm.doc.reference_type, frm.doc.reference_name);
}, frappe.boot.doctype_icons[frm.doc.reference_type]);
}
});

if (!frm.doc.__islocal) {
if(frm.doc.status=="Open") {
frm.add_custom_button(__("Close"), function() {
frm.set_value("status", "Closed");
frm.save();
}, "icon-ok", "btn-success");
} else {
frm.add_custom_button(__("Re-open"), function() {
frm.set_value("status", "Open");
frm.save();
}, null, "btn-default");
}
}
});

+ 20
- 8
frappe/core/doctype/todo/todo.json Näytä tiedosto

@@ -1,9 +1,8 @@
{
"allow_attach": 0,
"allow_copy": 0,
"allow_copy": 0,
"allow_rename": 0,
"autoname": "TDI.########",
"creation": "2012-07-03 13:30:35.000000",
"creation": "2012-07-03 13:30:35",
"docstatus": 0,
"doctype": "DocType",
"fields": [
@@ -82,6 +81,16 @@
"reqd": 0,
"search_index": 0
},
{
"fieldname": "owner",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 0,
"label": "Assigned To",
"options": "User",
"permlevel": 0,
"reqd": 1
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
@@ -91,13 +100,14 @@
{
"allow_on_submit": 0,
"fieldname": "reference_type",
"fieldtype": "Data",
"fieldtype": "Link",
"hidden": 0,
"in_filter": 0,
"label": "Reference Type",
"no_copy": 0,
"oldfieldname": "reference_type",
"oldfieldtype": "Data",
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"report_hide": 0,
@@ -107,13 +117,14 @@
{
"allow_on_submit": 0,
"fieldname": "reference_name",
"fieldtype": "Data",
"fieldtype": "Dynamic Link",
"hidden": 0,
"in_filter": 0,
"label": "Reference Name",
"no_copy": 0,
"oldfieldname": "reference_name",
"oldfieldtype": "Data",
"options": "reference_type",
"permlevel": 0,
"print_hide": 0,
"report_hide": 0,
@@ -145,6 +156,7 @@
{
"fieldname": "assigned_by",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Assigned By",
"options": "User",
"permlevel": 0
@@ -158,14 +170,14 @@
"in_dialog": 0,
"issingle": 0,
"max_attachments": 0,
"modified": "2014-03-12 17:06:46.000000",
"modified": "2014-06-30 05:40:15.471434",
"modified_by": "Administrator",
"module": "Core",
"name": "ToDo",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"apply_user_permissions": 1,
"create": 1,
"delete": 0,
"email": 1,
@@ -174,7 +186,6 @@
"print": 1,
"read": 1,
"report": 1,
"restricted": 1,
"role": "All",
"submit": 0,
"write": 1
@@ -194,5 +205,6 @@
],
"read_only": 0,
"read_only_onload": 0,
"search_fields": "description, reference_type, reference_name",
"title_field": "description"
}

+ 56
- 18
frappe/core/doctype/todo/todo.py Näytä tiedosto

@@ -3,46 +3,84 @@

from __future__ import unicode_literals
import frappe
import json

from frappe.model.document import Document

class ToDo(Document):
def validate(self):
if self.is_new():
self.add_comment(frappe._("Assignment Added"))
self.add_comment(frappe._("Assigned to {0}").format(self.owner), "Assigned")
else:
cur_status = frappe.db.get_value("ToDo", self.name, "status")
if cur_status != self.status:
self.add_comment(frappe._("Assignment Status Changed"))
def add_comment(self, text):
self.add_comment(frappe._("Assignment Status Changed"), "Assignment Completed")

def on_update(self):
self.update_in_reference()

def on_trash(self):
self.update_in_reference()

def add_comment(self, text, comment_type):
if not self.reference_type and self.reference_name:
return
comment = frappe.get_doc({
frappe.get_doc({
"doctype":"Comment",
"comment_by": frappe.session.user,
"comment_type": comment_type,
"comment_doctype": self.reference_type,
"comment_docname": self.reference_name,
"comment": """<div>{text}:
"comment": """<div>{text}:
<a href='#Form/ToDo/{name}'>{status}: {description}</a></div>""".format(text=text,
status = frappe._(self.status),
name = self.name,
description = self.description)
}).insert(ignore_permissions=True)
# todo is viewable if either owner or assigned_to or System Manager in roles

def get_permission_query_conditions():
if "System Manager" in frappe.get_roles():
def update_in_reference(self):
if not (self.reference_type and self.reference_name):
return

try:
assignments = [d[0] for d in frappe.get_list("ToDo",
filters={
"reference_type": self.reference_type,
"reference_name": self.reference_name,
"status": "Open"
},
fields=["owner"], ignore_permissions=True, as_list=True)]

assignments.reverse()
frappe.db.set_value(self.reference_type, self.reference_name,
"_assign", json.dumps(assignments))

except Exception, e:
if e.args[0] == 1146 and frappe.flags.in_install:
# no table
return

elif e.args[0]==1054:
from frappe.model.db_schema import add_column
add_column(self.reference_type, "_assign", "Text")
self.update_in_reference()

else:
raise

# NOTE: todo is viewable if either owner or assigned_to or System Manager in roles

def get_permission_query_conditions(user):
if not user: user = frappe.session.user

if "System Manager" in frappe.get_roles(user):
return None
else:
return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=frappe.session.user)
def has_permission(doc):
if "System Manager" in frappe.get_roles():
return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=user)
def has_permission(doc, user):
if "System Manager" in frappe.get_roles(user):
return True
else:
return doc.owner==frappe.session.user or doc.assigned_by==frappe.session.user
return doc.owner==user or doc.assigned_by==user

+ 29
- 0
frappe/core/doctype/todo/todo_list.html Näytä tiedosto

@@ -0,0 +1,29 @@
<div class="row" style="max-height: 32px; margin-bottom: 3px;">
<div class="col-xs-11">
<div class="text-ellipsis">
{%= list.get_avatar_and_id(doc) %}
<span class="label label-{%= frappe.utils.guess_style(doc.status) %} filterable"
data-filter="status,=,{%= doc.status %}">{%= __(doc.status) %}</span>

<span class="label label-{%= frappe.utils.guess_style(doc.priority) %}
filterable"
data-filter="priority,=,{%= doc.priority %}">{%= __(doc.priority) %}</span>

{% if (doc.reference_name) { %}
{% var reference_href = ("#Form/" + encodeURIComponent(doc.reference_type) + "/"
+ encodeURIComponent(doc.reference_name)) %}
<a href="{%= reference_href %}" style="text-decoration: none;">
<span class="label label-default">
<i class="{%= frappe.boot.doctype_icons[doc.reference_type] %}"></i>
{%= doc.reference_name %}
</span>
</a>
{% } %}
</div>
</div>
<div class="col-xs-1 text-right" style="margin-bottom: 3px;">
<span class="filterable" data-filter="owner,=,{%= doc.owner %}">
{%= frappe.avatar(doc.assigned_to) %}
</span>
</div>
</div>

Some files were not shown because too many files changed in this diff

Ladataan…
Peruuta
Tallenna