Commit 136ec9e2 authored by Pratik Vyas's avatar Pratik Vyas
Browse files

Merge branch 'develop'

parents 1466720a 1022a9b4
Showing with 1717 additions and 1261 deletions
+1717 -1261
......@@ -8,3 +8,4 @@ locale
*.egg-info
dist/
build/
docs/
......@@ -7,33 +7,26 @@ services:
- mysql
install:
- sudo service mysql stop
- sudo apt-get install python-software-properties
- sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
- sudo add-apt-repository 'deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main'
- sudo apt-get update
- sudo apt-get purge -y mysql-common
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev
- ./ci/fix-mariadb.sh
- sudo apt-get install xfonts-75dpi xfonts-base -y
- wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.2.1/wkhtmltox-0.12.2.1_linux-precise-amd64.deb
- sudo dpkg -i wkhtmltox-0.12.2.1_linux-precise-amd64.deb
- CFLAGS=-O0 pip install -r requirements.txt
- pip install --editable .
- wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh
- sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis
- sudo service redis-server start
- rm $TRAVIS_BUILD_DIR/.git/shallow
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
script:
- cd ./test_sites/
- frappe --use test_site
- frappe --reinstall
- frappe -b
- frappe --build_website
- frappe --serve_test &
- frappe --verbose --run_tests
- cd ~/frappe-bench
- bench use test_site
- bench reinstall
- bench build
- bench build-website
- bench serve &
- sleep 10
- bench --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
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
# Contributing to Frappe / ERPNext
### Update 16-Sep-14
Please send pull requests to branch v5.0
## 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.
......@@ -17,7 +13,7 @@ We only accept issues that are bug reports or feature requests. Bugs must be iso
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.
1. **Security Issues:** If you are reporting a security issue, please send a private email to <info@frappe.io>.
### Feature Requests
......
## Frappe framework includes these public works
### Javascript / CSS
- Bootstrap: MIT License, (c) Twitter Inc, https://getbootstrap.com
- JQuery: MIT License, (c) JQuery Foundation, http://jquery.org/license
- JQuery UI: MIT License / GPL 2, (c) JQuery Foundation, https://jqueryui.com/about
- JQuery UI Bootstrap Theme: MIT / GPL 2, (c) Addy Osmani, http://addyosmani.github.com/jquery-ui-bootstrap
- QUnit: MIT License, (c) JQuery Foundation, http://jquery.org/license
- jquery.event.drag, MIT License, (c) 2010 Three Dub Media - http://threedubmedia.com
- JQuery Cookie Plugin, MIT / GPL 2, (c) 2011, Klaus Hartl
- JQuery Time Picker, MIT License, (c) 2013 Trent Richardson, http://trentrichardson.com/examples/timepicker
- JQuery Hotkeys Plugin, MIT License, (c) 2010, John Resig
- prettydate.js, MIT License, (c) 2011, John Resig
- jquery.flot.downsample, MIT License, (c) 2013, Sveinn Steinarsson
- JQuery Resize Event, MIT License, (c) 2010 "Cowboy" Ben Alman
- excanvas.js, Apache License Version 2.0, (c) 2006 Google Inc
- showdown.js - Javascript Markdown, BSD-style Open Source License, (c) 2007 John Fraser
- Beautify HTML - MIT License, (c) 2007-2013 Einar Lielmanis and contributors.
- JQuery Gantt - MIT License, http://taitems.github.com/jQuery.Gantt/
- SlickGrid - MIT License, https://github.com/mleibman/SlickGrid
- MomentJS - MIT License, https://github.com/moment/moment
- JSColor - LGPL, (c) Jan Odvarko, http://jscolor.com
- FullCalendar - MIT License, (c) 2013 Adam Shaw, http://fullcalendar.io/license/
- Sortable - MIT License (c) 2013-2015 Lebedev Konstantin http://rubaxa.github.io/Sortable/
### Python
- minify.js - MIT License, (c) 2002 Douglas Crockford
### Icon Fonts
- Font Awesome - http://fontawesome.io/
- Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
- Code License: MIT (http://choosealicense.com/licenses/mit/)
- Octicons (c) GitHub Inc, https://octicons.github.com/
- Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
- Code License: MIT (http://choosealicense.com/licenses/mit/)
- Ionicons - MIT License, http://ionicons.com/
### IP Address Database
- GeoIP: (c) 2014 MaxMind, http://dev.maxmind.com/geoip/geoip2/downloadable/
### Wallpaper
- Version 5 Wallpaper: http://magdeleine.co/photo-nick-west-n-139/ (Public Domain)
---
Last updated: 1st Jan 2015
This diff is collapsed.
from __future__ import unicode_literals
__version__ = "4.14.3"
__version__ = "v5.0.0"
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
......@@ -6,25 +6,32 @@ import json
import frappe
import frappe.handler
import frappe.client
import frappe.widgets.reportview
import frappe.desk.reportview
from frappe.utils.response import build_response
from frappe import _
def handle():
"""
/api/method/{methodname} will call a whitelisted method
/api/resource/{doctype} will query a table
Handler for `/api` methods
### Examples:
`/api/method/{methodname}` will call a whitelisted method
`/api/resource/{doctype}` will query a table
examples:
?fields=["name", "owner"]
?filters=[["Task", "name", "like", "%005"]]
?limit_start=0
?limit_page_length=20
/api/resource/{doctype}/{name} will point to a resource
GET will return doclist
POST will insert
PUT will update
DELETE will delete
/api/resource/{doctype}/{name}?run_method={method} will run a whitelisted controller method
- `?fields=["name", "owner"]`
- `?filters=[["Task", "name", "like", "%005"]]`
- `?limit_start=0`
- `?limit_page_length=20`
`/api/resource/{doctype}/{name}` will point to a resource
`GET` will return doclist
`POST` will insert
`PUT` will update
`DELETE` will delete
`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method
"""
parts = frappe.request.path[1:].split("/",3)
call = doctype = name = None
......@@ -71,10 +78,15 @@ def handle():
if frappe.local.request.method=="PUT":
data = json.loads(frappe.local.form_dict.data)
doc = frappe.get_doc(doctype, name)
if "flags" in data:
del data["flags"]
# Not checking permissions here because it's checked in doc.save
doc.update(data)
frappe.local.response.update({
"data": doc.save().as_dict()
"data": doc.save().as_dict()
})
frappe.db.commit()
......@@ -90,8 +102,9 @@ def handle():
if frappe.local.request.method=="GET":
if frappe.local.form_dict.get('fields'):
frappe.local.form_dict['fields'] = json.loads(frappe.local.form_dict['fields'])
frappe.local.form_dict.setdefault('limit_page_length', 20)
frappe.local.response.update({
"data": frappe.call(frappe.widgets.reportview.execute,
"data": frappe.call(frappe.client.get_list,
doctype, **frappe.local.form_dict)})
if frappe.local.request.method=="POST":
......
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
......@@ -77,9 +77,9 @@ def application(request):
# 1205 = lock wait timeout
# 1213 = deadlock
# code 409 represents conflict
http_status_code = 409
http_status_code = 508
if frappe.local.is_ajax:
if frappe.local.is_ajax or 'application/json' in request.headers.get('Accept', ''):
response = frappe.utils.response.report_error(http_status_code)
else:
frappe.respond_as_web_page("Server Error",
......@@ -129,6 +129,10 @@ def make_form_dict(request):
frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \
for k, v in (request.form or request.args).iteritems() })
if "_" in frappe.local.form_dict:
# _ is passed by $.ajax so that the request is not cached by the browser. So, remove _ from form_dict
frappe.local.form_dict.pop("_")
application = local_manager.make_middleware(application)
def serve(port=8000, profile=False, site=None, sites_path='.'):
......
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
......@@ -22,10 +22,17 @@ 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'
if frappe.get_request_header('X-Forwarded-For'):
frappe.local.request_ip = frappe.get_request_header('X-Forwarded-For')
elif frappe.get_request_header('REMOTE_ADDR'):
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR')
else:
frappe.local.request_ip = '127.0.0.1'
# language
self.set_lang(frappe.get_request_header('HTTP_ACCEPT_LANGUAGE'))
self.set_lang(frappe.request.accept_languages.values())
# load cookies
frappe.local.cookie_manager = CookieManager()
......@@ -47,19 +54,22 @@ class HTTPRequest:
# check status
check_session_stopped()
# load user
self.setup_user()
# run login triggers
if frappe.form_dict.get('cmd')=='login':
frappe.local.login_manager.run_trigger('on_session_creation')
self.clear_active_sessions()
def clear_active_sessions(self):
if not frappe.conf.get("deny_multiple_sessions"):
return
def set_lang(self, lang):
from frappe.translate import guess_language_from_http_header
frappe.local.lang = guess_language_from_http_header(lang)
if frappe.session.user != "Guest":
clear_sessions(frappe.session.user, keep_current=True)
def setup_user(self):
frappe.local.user = frappe.utils.user.User()
def set_lang(self, lang_codes):
from frappe.translate import guess_language
frappe.local.lang = guess_language(lang_codes)
def get_db_name(self):
"""get database name from conf"""
......@@ -73,6 +83,10 @@ class HTTPRequest:
class LoginManager:
def __init__(self):
self.user = None
self.info = None
self.full_name = None
self.user_type = None
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login":
self.login()
else:
......@@ -85,6 +99,11 @@ class LoginManager:
self.post_login()
def post_login(self):
self.info = frappe.db.get_value("User", self.user,
["user_type", "first_name", "last_name", "user_image"], as_dict=1)
self.full_name = " ".join(filter(None, [self.info.first_name, self.info.last_name]))
self.user_type = self.info.user_type
self.run_trigger('on_login')
self.validate_ip_address()
self.validate_hour()
......@@ -95,24 +114,22 @@ class LoginManager:
# set sid again
frappe.local.cookie_manager.init_cookies()
info = frappe.db.get_value("User", self.user,
["user_type", "first_name", "last_name", "user_image"], as_dict=1)
if info.user_type=="Website User":
if self.info.user_type=="Website User":
frappe.local.cookie_manager.set_cookie("system_user", "no")
frappe.local.response["message"] = "No App"
else:
frappe.local.cookie_manager.set_cookie("system_user", "yes")
frappe.local.response['message'] = 'Logged In'
full_name = " ".join(filter(None, [info.first_name, info.last_name]))
frappe.response["full_name"] = full_name
frappe.local.cookie_manager.set_cookie("full_name", full_name)
frappe.response["full_name"] = self.full_name
frappe.local.cookie_manager.set_cookie("full_name", self.full_name)
frappe.local.cookie_manager.set_cookie("user_id", self.user)
frappe.local.cookie_manager.set_cookie("user_image", info.user_image or "")
frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "")
def make_session(self, resume=False):
# start session
frappe.local.session_obj = Session(user=self.user, resume=resume)
frappe.local.session_obj = Session(user=self.user, resume=resume,
full_name=self.full_name, user_type=self.user_type)
# reset user if changed to Guest
self.user = frappe.local.session_obj.user
......@@ -175,7 +192,7 @@ class LoginManager:
return
from frappe.utils import now_datetime
current_hour = int(now_datetime().strftime('%H'))
current_hour = int(now_datetime(user=frappe.form_dict.get('usr')).strftime('%H'))
if login_before and current_hour > login_before:
frappe.throw(_("Login not allowed at this time"), frappe.AuthenticationError)
......@@ -185,7 +202,10 @@ class LoginManager:
def login_as_guest(self):
"""login as guest"""
self.user = 'Guest'
self.login_as("Guest")
def login_as(self, user):
self.user = user
self.post_login()
def logout(self, arg='', user=None):
......
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
......@@ -8,8 +8,10 @@ bootstrap client session
import frappe
import frappe.defaults
import frappe.widgets.page
from frappe.utils import get_gravatar
import frappe.desk.desk_page
from frappe.utils import get_gravatar, get_url
from frappe.desk.form.load import get_meta_bundle
from frappe.change_log import get_versions
def get_bootinfo():
"""build and return boot info"""
......@@ -43,15 +45,14 @@ def get_bootinfo():
bootinfo.hidden_modules = frappe.db.get_global("hidden_modules")
bootinfo.doctype_icons = dict(frappe.db.sql("""select name, icon from
tabDocType where ifnull(icon,'')!=''"""))
bootinfo.doctype_icons.update(dict(frappe.db.sql("""select name, icon from
tabPage where ifnull(icon,'')!=''""")))
bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType where ifnull(issingle,0)=1""")
add_home_page(bootinfo, doclist)
add_allowed_pages(bootinfo)
bootinfo.page_info = get_allowed_pages()
load_translations(bootinfo)
add_timezone_info(bootinfo)
load_conf_settings(bootinfo)
load_print(bootinfo, doclist)
doclist.extend(get_meta_bundle("Page"))
# ipinfo
if frappe.session['data'].get('ipinfo'):
......@@ -65,8 +66,10 @@ def get_bootinfo():
if bootinfo.lang:
bootinfo.lang = unicode(bootinfo.lang)
bootinfo['versions'] = {k: v['version'] for k, v in get_versions().items()}
bootinfo.error_report_email = frappe.get_hooks("error_report_email")
bootinfo.default_background_image = get_url("/assets/frappe/images/ui/into-the-dawn.jpg")
return bootinfo
......@@ -75,9 +78,10 @@ def load_conf_settings(bootinfo):
for key in ['developer_mode']:
if key in conf: bootinfo[key] = conf.get(key)
def add_allowed_pages(bootinfo):
def get_allowed_pages():
roles = frappe.get_roles()
bootinfo.page_info = {}
page_info = {}
for p in frappe.db.sql("""select distinct
tabPage.name, tabPage.modified, tabPage.title
from `tabPage Role`, `tabPage`
......@@ -85,7 +89,7 @@ def add_allowed_pages(bootinfo):
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}
page_info[p.name] = {"modified":p.modified, "title":p.title}
# pages where role is not set are also allowed
for p in frappe.db.sql("""select name, modified, title
......@@ -93,7 +97,9 @@ def add_allowed_pages(bootinfo):
(select count(*) from `tabPage Role`
where `tabPage Role`.parent=tabPage.name) = 0""", as_dict=1):
bootinfo.page_info[p.name] = {"modified":p.modified, "title":p.title}
page_info[p.name] = {"modified":p.modified, "title":p.title}
return page_info
def load_translations(bootinfo):
if frappe.local.lang != 'en':
......@@ -106,7 +112,7 @@ def get_fullnames():
concat(ifnull(first_name, ''),
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)
from tabUser where ifnull(enabled, 0)=1 and user_type!="Website User" """, as_dict=1)
d = {}
for r in ret:
......@@ -116,15 +122,9 @@ def get_fullnames():
return d
def get_startup_js():
startup_js = []
for method in frappe.get_hooks().startup_js or []:
startup_js.append(frappe.get_attr(method)() or "")
return "\n".join(startup_js)
def get_user(bootinfo):
"""get user info"""
bootinfo.user = frappe.user.load_user()
bootinfo.user = frappe.get_user().load_user()
def add_home_page(bootinfo, docs):
"""load home page"""
......@@ -132,10 +132,10 @@ def add_home_page(bootinfo, docs):
return
home_page = frappe.db.get_default("desktop:home_page")
try:
page = frappe.widgets.page.get(home_page)
page = frappe.desk.desk_page.get(home_page)
except (frappe.DoesNotExistError, frappe.PermissionError):
frappe.message_log.pop()
page = frappe.widgets.page.get('desktop')
page = frappe.desk.desk_page.get('desktop')
bootinfo['home_page'] = page.name
docs.append(page)
......
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
......@@ -8,21 +8,38 @@ from frappe.utils.minify import JavascriptMinify
Build the `public` folders and setup languages
"""
import os, sys, frappe, json, shutil
from cssmin import cssmin
import os, frappe, json, shutil, re
# from cssmin import cssmin
app_paths = None
def setup():
global app_paths
pymodules = []
for app in frappe.get_all_apps(True):
try:
pymodules.append(frappe.get_module(app))
except ImportError: pass
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules]
def bundle(no_compress, make_copy=False, verbose=False):
"""concat / minify js files"""
# build js files
setup()
make_asset_dirs(make_copy=make_copy)
build(no_compress, verbose)
def watch(no_compress):
"""watch and rebuild if necessary"""
setup()
import time
compile_less()
build(no_compress=True)
while True:
compile_less()
if files_dirty():
build(no_compress=True)
......@@ -61,8 +78,6 @@ def build(no_compress=False, verbose=False):
def get_build_maps():
"""get all build.jsons with absolute paths"""
# framework js and css files
pymodules = [frappe.get_module(app) for app in frappe.get_all_apps(True)]
app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules]
build_maps = {}
for app_path in app_paths:
......@@ -81,7 +96,7 @@ def get_build_maps():
source_paths.append(s)
build_maps[target] = source_paths
except Exception, e:
except Exception:
print path
raise
......@@ -118,14 +133,12 @@ def pack(target, sources, no_compress, 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)
outtxt += html_to_js_template(f, data)
else:
outtxt += ('\n/*\n *\t%s\n */' % f)
outtxt += '\n' + data + '\n'
except Exception, e:
except Exception:
print "--Error in:" + f + "--"
print frappe.get_traceback()
......@@ -138,6 +151,16 @@ def pack(target, sources, no_compress, verbose):
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024)))
def html_to_js_template(path, content):
# remove whitespace to a single space
content = re.sub("\s+", " ", content).replace("'", "\'")
# strip comments
content = re.sub("(<!--.*?-->)", "", content)
return """frappe.templates["{key}"] = '{content}';\n""".format(\
key=path.rsplit("/", 1)[-1][:-5], content=content)
def files_dirty():
for target, sources in get_build_maps().iteritems():
for f in sources:
......@@ -149,3 +172,20 @@ def files_dirty():
else:
return False
def compile_less():
for path in app_paths:
less_path = os.path.join(path, "public", "less")
if os.path.exists(less_path):
for fname in os.listdir(less_path):
if fname.endswith(".less") and fname != "variables.less":
fpath = os.path.join(less_path, fname)
mtime = os.path.getmtime(fpath)
if fpath in timestamps and mtime == timestamps[fpath]:
continue
timestamps[fpath] = mtime
print "compiling {0}".format(fpath)
css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css")
os.system("which lessc && lessc {0} > {1}".format(fpath, css_path))
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals, absolute_import
......
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import os
import json
from semantic_version import Version
import frappe
from frappe.utils import cstr
def get_change_log(user=None):
if not user: user = frappe.session.user
last_known_versions = frappe._dict(json.loads(frappe.db.get_value("User", user, "last_known_versions") or "{}"))
current_versions = get_versions()
if not last_known_versions:
update_last_known_versions()
return []
change_log = []
for app, opts in current_versions.items():
from_version = last_known_versions.get(app, {}).get("version") or "0.0.1"
to_version = opts["version"]
if from_version != to_version:
app_change_log = get_change_log_for_app(app, from_version=from_version, to_version=to_version)
if app_change_log:
change_log.append({
"title": opts["title"],
"description": opts["description"],
"version": to_version,
"change_log": app_change_log
})
return change_log
def get_change_log_for_app(app, from_version, to_version):
change_log_folder = os.path.join(frappe.get_app_path(app), "change_log")
if not os.path.exists(change_log_folder):
return
from_version = Version(from_version)
to_version = Version(to_version)
# remove pre-release part
to_version.prerelease = None
major_version_folders = ["v{0}".format(i) for i in xrange(from_version.major, to_version.major + 1)]
app_change_log = []
for folder in os.listdir(change_log_folder):
if folder in major_version_folders:
for file in os.listdir(os.path.join(change_log_folder, folder)):
version = Version(os.path.splitext(file)[0][1:].replace("_", "."))
if from_version < version <= to_version:
file_path = os.path.join(change_log_folder, folder, file)
content = frappe.read_file(file_path)
app_change_log.append([version, content])
app_change_log = sorted(app_change_log, key=lambda d: d[0], reverse=True)
# convert version to string and send
return [[cstr(d[0]), d[1]] for d in app_change_log]
@frappe.whitelist()
def update_last_known_versions():
frappe.db.set_value("User", frappe.session.user, "last_known_versions", json.dumps(get_versions()), update_modified=False)
@frappe.whitelist()
def get_versions():
"""Get versions of all installed apps.
Example:
{
"frappe": {
"title": "Frappe Framework",
"version": "5.0.0"
}
}"""
versions = {}
for app in frappe.get_installed_apps(sort=True):
versions[app] = {
"title": frappe.get_hooks("app_title", app_name=app),
"description": frappe.get_hooks("app_description", app_name=app)
}
try:
versions[app]["version"] = frappe.get_attr(app + ".__version__")
except AttributeError:
versions[app]["version"] = '0.0.1'
return versions
### Version 5
Please see https://frappe.io/version-5
Changes include:
1. New Visual Design
1. Custom DocTypes
1. Email Accounts
1. Email Replies and Notifications
1. Print Format Builder
1. Document Sharing
This diff is collapsed.
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
......@@ -8,6 +8,12 @@ import frappe.model
import frappe.utils
import json, os
@frappe.whitelist()
def get_list(doctype, fields=None, filters=None, order_by=None,
limit_start=None, limit_page_length=20):
return frappe.get_list(doctype, fields=fields, filters=filters, order_by=order_by,
limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=False)
@frappe.whitelist()
def get(doctype, name=None, filters=None):
if filters and not name:
......@@ -26,9 +32,19 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False):
if not frappe.has_permission(doctype):
frappe.throw(_("Not permitted"), frappe.PermissionError)
if fieldname and fieldname.startswith("["):
try:
filters = json.loads(filters)
except ValueError:
# name passed, not json
pass
try:
fieldname = json.loads(fieldname)
return frappe.db.get_value(doctype, json.loads(filters), fieldname, as_dict=as_dict, debug=debug)
except ValueError:
# name passed, not json
pass
return frappe.db.get_value(doctype, filters, fieldname, as_dict=as_dict, debug=debug)
@frappe.whitelist()
def set_value(doctype, name, fieldname, value):
......@@ -36,7 +52,7 @@ def set_value(doctype, name, fieldname, value):
frappe.throw(_("Cannot edit standard fields"))
doc = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
if doc and doc.parent:
if doc and doc.parent and doc.parenttype:
doc = frappe.get_doc(doc.parenttype, doc.parent)
child = doc.getone({"doctype": doctype, "name": name})
child.set(fieldname, value)
......@@ -53,30 +69,26 @@ def set_value(doctype, name, fieldname, value):
return doc.as_dict()
@frappe.whitelist()
def insert(doclist):
if isinstance(doclist, basestring):
doclist = json.loads(doclist)
if isinstance(doclist, dict):
doclist = [doclist]
def insert(doc=None):
if isinstance(doc, basestring):
doc = json.loads(doc)
if doclist[0].get("parent") and doclist[0].get("parenttype"):
if doc.get("parent") and doc.get("parenttype"):
# inserting a child record
d = doclist[0]
doc = frappe.get_doc(d["parenttype"], d["parent"])
doc.append(d)
doc.save()
return [d]
parent = frappe.get_doc(doc.parenttype, doc.parent)
parent.append(doc)
parent.save()
return parent.as_dict()
else:
doc = frappe.get_doc(doclist).insert()
doc = frappe.get_doc(doc).insert()
return doc.as_dict()
@frappe.whitelist()
def save(doclist):
if isinstance(doclist, basestring):
doclist = json.loads(doclist)
def save(doc):
if isinstance(doc, basestring):
doc = json.loads(doc)
doc = frappe.get_doc(doclist).save()
doc = frappe.get_doc(doc).save()
return doc.as_dict()
@frappe.whitelist()
......@@ -85,14 +97,14 @@ def rename_doc(doctype, old_name, new_name, merge=False):
return new_name
@frappe.whitelist()
def submit(doclist):
if isinstance(doclist, basestring):
doclist = json.loads(doclist)
def submit(doc):
if isinstance(doc, basestring):
doc = json.loads(doc)
doclistobj = frappe.get_doc(doclist)
doclistobj.submit()
doc = frappe.get_doc(doc)
doc.submit()
return doclistobj.as_dict()
return doc.as_dict()
@frappe.whitelist()
def cancel(doctype, name):
......
This diff is collapsed.
......@@ -3,9 +3,18 @@ from frappe import _
def get_data():
return {
"Activity": {
"color": "#e67e22",
"icon": "icon-play",
"icon": "octicon octicon-pulse",
"label": _("Activity"),
"link": "activity",
"type": "page"
},
"Calendar": {
"color": "#2980b9",
"icon": "icon-calendar",
"icon": "octicon octicon-calendar",
"label": _("Calendar"),
"link": "Calendar/Event",
"type": "view"
......@@ -13,6 +22,7 @@ def get_data():
"Messages": {
"color": "#9b59b6",
"icon": "icon-comments",
"icon": "octicon octicon-comment-discussion",
"label": _("Messages"),
"link": "messages",
"type": "page"
......@@ -20,19 +30,31 @@ def get_data():
"To Do": {
"color": "#f1c40f",
"icon": "icon-check",
"icon": "octicon octicon-check",
"label": _("To Do"),
"link": "List/ToDo",
"doctype": "ToDo",
"type": "list"
},
"Notes": {
"color": "#95a5a6",
"doctype": "Note",
"icon": "icon-file-alt",
"icon": "octicon octicon-file-text",
"label": _("Notes"),
"link": "List/Note",
"type": "list"
},
"Website": {
"color": "#16a085",
"icon": "icon-globe",
"icon": "octicon octicon-globe",
"type": "module"
},
"Installer": {
"color": "#888",
"color": "#5ac8fb",
"icon": "icon-download",
"icon": "octicon octicon-cloud-download",
"link": "applications",
"type": "page",
"label": _("Installer")
......@@ -40,11 +62,13 @@ def get_data():
"Setup": {
"color": "#bdc3c7",
"icon": "icon-wrench",
"icon": "octicon octicon-settings",
"type": "module"
},
"Core": {
"color": "#589494",
"icon": "icon-cog",
"icon": "octicon octicon-file-binary",
"type": "module",
"system_manager": 1
},
......
from __future__ import unicode_literals
from frappe import _
from frappe.widgets.moduleview import add_setup_section
from frappe.desk.moduleview import add_setup_section
def get_data():
data = [
{
"label": _("Users and Permissions"),
"label": _("Users"),
"icon": "icon-group",
"items": [
{
......@@ -17,7 +17,13 @@ def get_data():
"type": "doctype",
"name": "Role",
"description": _("User Roles")
},
}
]
},
{
"label": _("Permissions"),
"icon": "icon-lock",
"items": [
{
"type": "page",
"name": "permission-manager",
......@@ -39,6 +45,13 @@ def get_data():
"icon": "icon-eye-open",
"name": "Permitted Documents For User",
"description": _("Check which Documents are readable by a User")
},
{
"type": "report",
"doctype": "DocShare",
"icon": "icon-share",
"name": "Document Share Report",
"description": _("Report of all document shares")
}
]
},
......@@ -93,60 +106,66 @@ def get_data():
]
},
{
"label": _("Workflow"),
"icon": "icon-random",
"label": _("Email"),
"icon": "icon-envelope",
"items": [
{
"type": "doctype",
"name": "Workflow",
"description": _("Define workflows for forms.")
"name": "Email Account",
"description": _("Add / Manage Email Accounts.")
},
{
"type": "doctype",
"name": "Workflow State",
"description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
"name": "Email Alert",
"description": _("Setup Email Alert based on various criteria.")
},
{
"type": "doctype",
"name": "Workflow Action",
"description": _("Actions for workflow (e.g. Approve, Cancel).")
"name": "Standard Reply",
"description": _("Standard replies to common queries.")
},
]
},
{
"label": _("Email"),
"icon": "icon-envelope",
"label": _("Printing"),
"icon": "icon-print",
"items": [
{
"type": "doctype",
"name": "Outgoing Email Settings",
"description": _("Set outgoing mail server.")
"type": "page",
"label": "Print Format Builder",
"name": "print-format-builder",
"description": _("Drag and Drop tool to build and customize Print Formats.")
},
{
"type": "doctype",
"name": "Email Alert",
"description": _("Setup Email Alert based on various criteria.")
"name": "Print Settings",
"description": _("Set default format, page size, print style etc.")
},
{
"type": "doctype",
"name": "Standard Reply",
"description": _("Standard replies to common queries.")
"name": "Print Format",
"description": _("Customized HTML Templates for printing transactions.")
},
]
},
{
"label": _("Printing and Branding"),
"icon": "icon-print",
"label": _("Workflow"),
"icon": "icon-random",
"items": [
{
"type": "doctype",
"name": "Print Settings",
"description": _("Set default format, page size, print style etc.")
"name": "Workflow",
"description": _("Define workflows for forms.")
},
{
"type": "doctype",
"name": "Print Format",
"description": _("Customized HTML Templates for printing transctions.")
"name": "Workflow State",
"description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
},
{
"type": "doctype",
"name": "Workflow Action",
"description": _("Actions for workflow (e.g. Approve, Cancel).")
},
]
},
......@@ -169,7 +188,13 @@ def get_data():
"type": "doctype",
"name": "Custom Script",
"description": _("Add custom javascript to forms.")
},
{
"type": "doctype",
"name": "DocType",
"description": _("Add custom forms.")
}
]
},
{
......
......@@ -27,16 +27,6 @@ def get_data():
"name": "Blogger",
"description": _("User ID of a blog writer."),
},
{
"type": "doctype",
"name": "Website Group",
"description": _("Web Site Forum Page."),
},
{
"type": "doctype",
"name": "Post",
"description": _("List of Web Site Forum's Posts."),
},
{
"type": "doctype",
"name": "Website Slideshow",
......@@ -85,8 +75,8 @@ def get_data():
},
{
"type": "doctype",
"name": "Website Page Permission",
"description": _("Define read, write, admin permissions for a Website Page."),
"name": "Website Theme",
"description": _("List of themes for Website."),
},
{
"type": "doctype",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment