Refactored handler, catch exceptions, response codesversion-14
@@ -16,6 +16,7 @@ import json | |||||
import semantic_version | import semantic_version | ||||
from frappe.core.doctype.print_format.print_format import get_html as get_print_html | from frappe.core.doctype.print_format.print_format import get_html as get_print_html | ||||
from .exceptions import * | |||||
local = Local() | local = Local() | ||||
@@ -71,7 +72,6 @@ form = form_dict = local("form_dict") | |||||
request = local("request") | request = local("request") | ||||
request_method = local("request_method") | request_method = local("request_method") | ||||
response = local("response") | response = local("response") | ||||
_response = local("_response") | |||||
session = local("session") | session = local("session") | ||||
user = local("user") | user = local("user") | ||||
flags = local("flags") | flags = local("flags") | ||||
@@ -149,21 +149,6 @@ def cache(): | |||||
_memc = MClient(['localhost:11211']) | _memc = MClient(['localhost:11211']) | ||||
return _memc | return _memc | ||||
class DuplicateEntryError(Exception): pass | |||||
class ValidationError(Exception): pass | |||||
class AuthenticationError(Exception): pass | |||||
class PermissionError(Exception): pass | |||||
class DataError(Exception): pass | |||||
class UnknownDomainError(Exception): pass | |||||
class SessionStopped(Exception): pass | |||||
class MappingMismatchError(ValidationError): pass | |||||
class InvalidStatusError(ValidationError): pass | |||||
class DoesNotExistError(ValidationError): pass | |||||
class MandatoryError(ValidationError): pass | |||||
class InvalidSignatureError(ValidationError): pass | |||||
class RateLimitExceededError(ValidationError): pass | |||||
class OutgoingEmailError(Exception): pass | |||||
def get_traceback(): | def get_traceback(): | ||||
import utils | import utils | ||||
return utils.get_traceback() | return utils.get_traceback() | ||||
@@ -562,8 +547,6 @@ def repsond_as_web_page(title, html): | |||||
local.response['type'] = 'page' | local.response['type'] = 'page' | ||||
local.response['page_name'] = 'message.html' | local.response['page_name'] = 'message.html' | ||||
return obj | |||||
def build_match_conditions(doctype, as_condition=True): | def build_match_conditions(doctype, as_condition=True): | ||||
import frappe.widgets.reportview | import frappe.widgets.reportview | ||||
return frappe.widgets.reportview.build_match_conditions(doctype, as_condition) | return frappe.widgets.reportview.build_match_conditions(doctype, as_condition) | ||||
@@ -5,7 +5,7 @@ import frappe | |||||
import frappe.handler | import frappe.handler | ||||
import frappe.client | import frappe.client | ||||
import frappe.widgets.reportview | import frappe.widgets.reportview | ||||
from frappe.utils.response import build_response, report_error | |||||
from frappe.utils.response import build_response | |||||
def handle(): | def handle(): | ||||
""" | """ | ||||
@@ -35,63 +35,56 @@ def handle(): | |||||
if len(parts) > 3: | if len(parts) > 3: | ||||
name = parts[3] | name = parts[3] | ||||
try: | |||||
if call=="method": | |||||
frappe.local.form_dict.cmd = doctype | |||||
frappe.handler.handle() | |||||
return | |||||
elif call=="resource": | |||||
if "run_method" in frappe.local.form_dict: | |||||
bean = frappe.bean(doctype, name) | |||||
if call=="method": | |||||
frappe.local.form_dict.cmd = doctype | |||||
return frappe.handler.handle() | |||||
elif call=="resource": | |||||
if "run_method" in frappe.local.form_dict: | |||||
bean = frappe.bean(doctype, name) | |||||
if frappe.local.request.method=="GET": | |||||
if not bean.has_permission("read"): | |||||
frappe.throw("No Permission", frappe.PermissionError) | |||||
bean.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) | |||||
if frappe.local.request.method=="POST": | |||||
if not bean.has_permission("write"): | |||||
frappe.throw("No Permission", frappe.PermissionError) | |||||
bean.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) | |||||
frappe.db.commit() | |||||
else: | |||||
if name: | |||||
if frappe.local.request.method=="GET": | if frappe.local.request.method=="GET": | ||||
if not bean.has_permission("read"): | |||||
frappe.throw("No Permission", frappe.PermissionError) | |||||
bean.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) | |||||
frappe.local.response.update({ | |||||
"doclist": frappe.client.get(doctype, | |||||
name)}) | |||||
if frappe.local.request.method=="POST": | if frappe.local.request.method=="POST": | ||||
if not bean.has_permission("write"): | |||||
frappe.throw("No Permission", frappe.PermissionError) | |||||
bean.run_method(frappe.local.form_dict.run_method, **frappe.local.form_dict) | |||||
frappe.local.response.update({ | |||||
"doclist": frappe.client.insert(frappe.local.form_dict.doclist)}) | |||||
frappe.db.commit() | frappe.db.commit() | ||||
else: | |||||
if name: | |||||
if frappe.local.request.method=="GET": | |||||
frappe.local.response.update({ | |||||
"doclist": frappe.client.get(doctype, | |||||
name)}) | |||||
if frappe.local.request.method=="POST": | |||||
frappe.local.response.update({ | |||||
"doclist": frappe.client.insert(frappe.local.form_dict.doclist)}) | |||||
frappe.db.commit() | |||||
if frappe.local.request.method=="PUT": | |||||
frappe.local.response.update({ | |||||
"doclist":frappe.client.save(frappe.local.form_dict.doclist)}) | |||||
frappe.db.commit() | |||||
if frappe.local.request.method=="DELETE": | |||||
frappe.client.delete(doctype, name) | |||||
frappe.local.response.message = "ok" | |||||
elif doctype: | |||||
if frappe.local.request.method=="GET": | |||||
frappe.local.response.update({ | |||||
"data": frappe.call(frappe.widgets.reportview.execute, | |||||
doctype, **frappe.local.form_dict)}) | |||||
else: | |||||
raise frappe.DoesNotExistError | |||||
else: | |||||
raise frappe.DoesNotExistError | |||||
except frappe.DoesNotExistError, e: | |||||
report_error(404) | |||||
except Exception, e: | |||||
report_error(500) | |||||
if frappe.local.request.method=="PUT": | |||||
frappe.local.response.update({ | |||||
"doclist":frappe.client.save(frappe.local.form_dict.doclist)}) | |||||
frappe.db.commit() | |||||
if frappe.local.request.method=="DELETE": | |||||
frappe.client.delete(doctype, name) | |||||
frappe.local.response.message = "ok" | |||||
elif doctype: | |||||
if frappe.local.request.method=="GET": | |||||
frappe.local.response.update({ | |||||
"data": frappe.call(frappe.widgets.reportview.execute, | |||||
doctype, **frappe.local.form_dict)}) | |||||
else: | |||||
raise frappe.DoesNotExistError | |||||
else: | |||||
raise frappe.DoesNotExistError | |||||
build_response() | |||||
return build_response("json") |
@@ -25,65 +25,82 @@ local_manager = LocalManager([frappe.local]) | |||||
_site = None | _site = None | ||||
_sites_path = os.environ.get("SITES_PATH", ".") | _sites_path = os.environ.get("SITES_PATH", ".") | ||||
def handle_session_stopped(): | |||||
res = Response("""<html> | |||||
<body style="background-color: #EEE;"> | |||||
<h3 style="width: 900px; background-color: #FFF; border: 2px solid #AAA; padding: 20px; font-family: Arial; margin: 20px auto"> | |||||
Updating. | |||||
We will be back in a few moments... | |||||
</h3> | |||||
</body> | |||||
</html>""") | |||||
res.status_code = 503 | |||||
res.content_type = 'text/html' | |||||
return res | |||||
@Request.application | @Request.application | ||||
def application(request): | def application(request): | ||||
frappe.local.request = request | frappe.local.request = request | ||||
response = None | |||||
try: | try: | ||||
site = _site or get_site_name(request.host) | |||||
frappe.init(site=site, sites_path=_sites_path) | |||||
rollback = True | |||||
if not frappe.local.conf: | |||||
# site does not exist | |||||
raise NotFound | |||||
init_site(request) | |||||
make_form_dict(request) | |||||
frappe.local.http_request = frappe.auth.HTTPRequest() | |||||
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() }) | |||||
frappe.local._response = Response() | |||||
frappe.http_request = frappe.auth.HTTPRequest() | |||||
if frappe.local.form_dict.cmd: | if frappe.local.form_dict.cmd: | ||||
frappe.handler.handle() | |||||
response = frappe.handler.handle() | |||||
elif frappe.request.path.startswith("/api/"): | elif frappe.request.path.startswith("/api/"): | ||||
frappe.api.handle() | |||||
response = frappe.api.handle() | |||||
elif frappe.request.path.startswith('/backups'): | elif frappe.request.path.startswith('/backups'): | ||||
frappe.utils.response.download_backup(request.path) | |||||
response = frappe.utils.response.download_backup(request.path) | |||||
elif frappe.local.request.method in ('GET', 'HEAD'): | elif frappe.local.request.method in ('GET', 'HEAD'): | ||||
frappe.website.render.render(frappe.request.path[1:]) | |||||
response = frappe.website.render.render(request.path) | |||||
else: | else: | ||||
raise NotFound | raise NotFound | ||||
except HTTPException, e: | except HTTPException, e: | ||||
return e | return e | ||||
except frappe.AuthenticationError, e: | |||||
frappe._response.status_code=401 | |||||
except frappe.SessionStopped, e: | except frappe.SessionStopped, e: | ||||
frappe.local._response = handle_session_stopped() | |||||
response = frappe.utils.response.handle_session_stopped() | |||||
except (frappe.AuthenticationError, | |||||
frappe.PermissionError, | |||||
frappe.DoesNotExistError, | |||||
frappe.DuplicateEntryError, | |||||
frappe.OutgoingEmailError, | |||||
frappe.ValidationError), e: | |||||
response = frappe.utils.response.report_error(e.http_status_code) | |||||
if e.__class__ == frappe.AuthenticationError: | |||||
frappe.local.login_manager.clear_cookies() | |||||
else: | |||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db: | |||||
frappe.db.commit() | |||||
rollback = False | |||||
finally: | finally: | ||||
_response = frappe.local._response | |||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback: | |||||
frappe.db.rollback() | |||||
# set cookies | |||||
if response: | |||||
frappe.local.cookie_manager.flush_cookies(response=response) | |||||
frappe.destroy() | frappe.destroy() | ||||
return response | |||||
def init_site(request): | |||||
site = _site or get_site_name(request.host) | |||||
frappe.init(site=site, sites_path=_sites_path) | |||||
if not frappe.local.conf: | |||||
# site does not exist | |||||
raise NotFound | |||||
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() }) | |||||
return _response | |||||
application = local_manager.make_middleware(application) | application = local_manager.make_middleware(application) | ||||
def serve(port=8000, profile=False, site=None, sites_path='.'): | def serve(port=8000, profile=False, site=None, sites_path='.'): | ||||
global application, _site, _sites_path | global application, _site, _sites_path | ||||
_site = site | _site = site | ||||
@@ -102,6 +119,6 @@ def serve(port=8000, profile=False, site=None, sites_path='.'): | |||||
application = StaticDataMiddleware(application, { | application = StaticDataMiddleware(application, { | ||||
'/files': os.path.abspath(sites_path) | '/files': os.path.abspath(sites_path) | ||||
}) | }) | ||||
run_simple('0.0.0.0', int(port), application, use_reloader=True, | run_simple('0.0.0.0', int(port), application, use_reloader=True, | ||||
use_debugger=True, use_evalex=True) | use_debugger=True, use_evalex=True) |
@@ -2,13 +2,15 @@ | |||||
# MIT License. See license.txt | # MIT License. See license.txt | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import datetime | |||||
import frappe | import frappe | ||||
import frappe.database | import frappe.database | ||||
import frappe.utils | import frappe.utils | ||||
import frappe.utils.user | import frappe.utils.user | ||||
from frappe import conf | from frappe import conf | ||||
from frappe.sessions import Session | from frappe.sessions import Session | ||||
from frappe.modules.patch_handler import check_session_stopped | |||||
class HTTPRequest: | class HTTPRequest: | ||||
def __init__(self): | def __init__(self): | ||||
@@ -33,11 +35,12 @@ class HTTPRequest: | |||||
# login | # login | ||||
frappe.local.login_manager = LoginManager() | frappe.local.login_manager = LoginManager() | ||||
# write out latest cookies | |||||
frappe.local.cookie_manager.init_cookies() | |||||
# check status | # check status | ||||
if frappe.db.get_global("__session_status")=='stop': | |||||
frappe.msgprint(frappe.db.get_global("__session_status_message")) | |||||
raise frappe.SessionStopped('Session Stopped') | |||||
check_session_stopped() | |||||
# load user | # load user | ||||
self.setup_user() | self.setup_user() | ||||
@@ -106,20 +109,23 @@ class LoginManager: | |||||
self.set_user_info() | self.set_user_info() | ||||
def set_user_info(self): | def set_user_info(self): | ||||
# set sid again | |||||
frappe.local.cookie_manager.init_cookies() | |||||
info = frappe.db.get_value("User", self.user, | info = frappe.db.get_value("User", self.user, | ||||
["user_type", "first_name", "last_name", "user_image"], as_dict=1) | ["user_type", "first_name", "last_name", "user_image"], as_dict=1) | ||||
if info.user_type=="Website User": | if info.user_type=="Website User": | ||||
frappe.local._response.set_cookie("system_user", "no") | |||||
frappe.local.cookie_manager.set_cookie("system_user", "no") | |||||
frappe.local.response["message"] = "No App" | frappe.local.response["message"] = "No App" | ||||
else: | else: | ||||
frappe.local._response.set_cookie("system_user", "yes") | |||||
frappe.local.cookie_manager.set_cookie("system_user", "yes") | |||||
frappe.local.response['message'] = 'Logged In' | frappe.local.response['message'] = 'Logged In' | ||||
full_name = " ".join(filter(None, [info.first_name, info.last_name])) | full_name = " ".join(filter(None, [info.first_name, info.last_name])) | ||||
frappe.response["full_name"] = full_name | frappe.response["full_name"] = full_name | ||||
frappe._response.set_cookie("full_name", full_name) | |||||
frappe._response.set_cookie("user_id", self.user) | |||||
frappe._response.set_cookie("user_image", info.user_image or "") | |||||
frappe.local.cookie_manager.set_cookie("full_name", full_name) | |||||
frappe.local.cookie_manager.set_cookie("user_id", self.user) | |||||
frappe.local.cookie_manager.set_cookie("user_image", info.user_image or "") | |||||
def make_session(self, resume=False): | def make_session(self, resume=False): | ||||
# start session | # start session | ||||
@@ -161,7 +167,7 @@ class LoginManager: | |||||
def run_trigger(self, event='on_login'): | def run_trigger(self, event='on_login'): | ||||
for method in frappe.get_hooks().get(event, []): | for method in frappe.get_hooks().get(event, []): | ||||
frappe.get_attr(method)(self) | |||||
frappe.call(frappe.get_attr(method), login_manager=self) | |||||
def validate_ip_address(self): | def validate_ip_address(self): | ||||
"""check if IP Address is valid""" | """check if IP Address is valid""" | ||||
@@ -212,44 +218,45 @@ class LoginManager: | |||||
clear_sessions(user) | clear_sessions(user) | ||||
if user == frappe.session.user: | if user == frappe.session.user: | ||||
frappe.session.sid = "" | |||||
frappe.local._response.delete_cookie("full_name") | |||||
frappe.local._response.delete_cookie("user_id") | |||||
frappe.local._response.delete_cookie("sid") | |||||
frappe.local._response.set_cookie("full_name", "") | |||||
frappe.local._response.set_cookie("user_id", "") | |||||
frappe.local._response.set_cookie("sid", "") | |||||
self.clear_cookies() | |||||
def clear_cookies(self): | |||||
frappe.session.sid = "" | |||||
frappe.local.cookie_manager.delete_cookie(["full_name", "user_id", "sid", "user_image", "system_user"]) | |||||
class CookieManager: | class CookieManager: | ||||
def __init__(self): | def __init__(self): | ||||
pass | |||||
self.cookies = {} | |||||
self.to_delete = [] | |||||
def set_cookies(self): | |||||
def init_cookies(self): | |||||
if not frappe.local.session.get('sid'): return | if not frappe.local.session.get('sid'): return | ||||
import datetime | |||||
# sid expires in 3 days | # sid expires in 3 days | ||||
expires = datetime.datetime.now() + datetime.timedelta(days=3) | expires = datetime.datetime.now() + datetime.timedelta(days=3) | ||||
if frappe.session.sid: | if frappe.session.sid: | ||||
frappe.local._response.set_cookie("sid", frappe.session.sid, expires = expires) | |||||
self.cookies["sid"] = {"value": frappe.session.sid, "expires": expires} | |||||
if frappe.session.session_country: | if frappe.session.session_country: | ||||
frappe.local._response.set_cookie('country', frappe.session.get("session_country")) | |||||
self.cookies["country"] = {"value": frappe.session.get("session_country")} | |||||
def set_cookie(self, key, value, expires=None): | |||||
self.cookies[key] = {"value": value, "expires": expires} | |||||
def delete_cookie(self, to_delete): | |||||
if not isinstance(to_delete, (list, tuple)): | |||||
to_delete = [to_delete] | |||||
def set_remember_me(self): | |||||
from frappe.utils import cint | |||||
self.to_delete.extend(to_delete) | |||||
if not cint(frappe.form_dict.get('remember_me')): return | |||||
def flush_cookies(self, response): | |||||
for key, opts in self.cookies.items(): | |||||
response.set_cookie(key, opts.get("value"), expires=opts.get("expires")) | |||||
# expires yesterday! | |||||
expires = datetime.datetime.now() + datetime.timedelta(days=-1) | |||||
for key in set(self.to_delete): | |||||
response.set_cookie(key, "", expires=expires) | |||||
remember_days = frappe.db.get_value('Control Panel', None, | |||||
'remember_for_days') or 7 | |||||
import datetime | |||||
expires = datetime.datetime.now() + \ | |||||
datetime.timedelta(days=remember_days) | |||||
frappe.local._response.set_cookie["remember_me"] = 1 | |||||
def _update_password(user, password): | def _update_password(user, password): | ||||
frappe.db.sql("""insert into __Auth (user, `password`) | frappe.db.sql("""insert into __Auth (user, `password`) | ||||
values (%s, password(%s)) | values (%s, password(%s)) | ||||
@@ -129,10 +129,16 @@ class DocType: | |||||
self.doc.doctype, self.doc.name), scrub(self.doc.name) + '.py') | self.doc.doctype, self.doc.name), scrub(self.doc.name) + '.py') | ||||
if not os.path.exists(pypath): | if not os.path.exists(pypath): | ||||
# get app publisher for copyright | |||||
app = frappe.local.module_app[frappe.scrub(self.doc.module)] | |||||
if not app: | |||||
frappe.throw("App not found!") | |||||
app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0] | |||||
with open(pypath, 'w') as pyfile: | with open(pypath, 'w') as pyfile: | ||||
with open(os.path.join(get_module_path("core"), "doctype", "doctype", | with open(os.path.join(get_module_path("core"), "doctype", "doctype", | ||||
"doctype_template.py"), 'r') as srcfile: | "doctype_template.py"), 'r') as srcfile: | ||||
pyfile.write(srcfile.read()) | |||||
pyfile.write(srcfile.read().format(app_publisher=app_publisher)) | |||||
def make_amendable(self): | def make_amendable(self): | ||||
""" | """ | ||||
@@ -1,5 +1,5 @@ | |||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||||
# GNU General Public License. See license.txt | |||||
# Copyright (c) 2013, {app_publisher} | |||||
# For license information, please see license.txt | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
@@ -0,0 +1,35 @@ | |||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
# BEWARE don't put anything in this file except exceptions | |||||
class ValidationError(Exception): | |||||
http_status_code = 417 | |||||
class AuthenticationError(Exception): | |||||
http_status_code = 401 | |||||
class PermissionError(Exception): | |||||
http_status_code = 403 | |||||
class DoesNotExistError(ValidationError): | |||||
http_status_code = 404 | |||||
class DuplicateEntryError(Exception): | |||||
http_status_code = 409 | |||||
class OutgoingEmailError(Exception): | |||||
http_status_code = 501 | |||||
class SessionStopped(Exception): | |||||
http_status_code = 503 | |||||
class DataError(Exception): pass | |||||
class UnknownDomainError(Exception): pass | |||||
class MappingMismatchError(ValidationError): pass | |||||
class InvalidStatusError(ValidationError): pass | |||||
class MandatoryError(ValidationError): pass | |||||
class InvalidSignatureError(ValidationError): pass | |||||
class RateLimitExceededError(ValidationError): pass |
@@ -8,7 +8,7 @@ import frappe.utils | |||||
import frappe.sessions | import frappe.sessions | ||||
import frappe.utils.file_manager | import frappe.utils.file_manager | ||||
import frappe.widgets.form.run_method | import frappe.widgets.form.run_method | ||||
from frappe.utils.response import build_response, report_error | |||||
from frappe.utils.response import build_response | |||||
@frappe.whitelist(allow_guest=True) | @frappe.whitelist(allow_guest=True) | ||||
def startup(): | def startup(): | ||||
@@ -64,33 +64,9 @@ def handle(): | |||||
cmd = frappe.local.form_dict.cmd | cmd = frappe.local.form_dict.cmd | ||||
if cmd!='login': | if cmd!='login': | ||||
status_codes = { | |||||
frappe.PermissionError: 403, | |||||
frappe.AuthenticationError: 401, | |||||
frappe.DoesNotExistError: 404, | |||||
frappe.DuplicateEntryError: 409, | |||||
frappe.SessionStopped: 503, | |||||
frappe.OutgoingEmailError: 501 | |||||
} | |||||
try: | |||||
execute_cmd(cmd) | |||||
except Exception, e: | |||||
report_error(status_codes.get(e.__class__, 500)) | |||||
else: | |||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db: | |||||
frappe.db.commit() | |||||
else: | |||||
# commit for login | |||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db: | |||||
frappe.db.commit() | |||||
execute_cmd(cmd) | |||||
build_response() | |||||
if frappe.db: | |||||
frappe.db.close() | |||||
if frappe._memc: | |||||
frappe._memc.disconnect_all() | |||||
return build_response("json") | |||||
def execute_cmd(cmd): | def execute_cmd(cmd): | ||||
"""execute a request as python module""" | """execute a request as python module""" | ||||
@@ -102,7 +78,6 @@ def execute_cmd(cmd): | |||||
raise frappe.PermissionError('Not Allowed, %s' % str(method)) | raise frappe.PermissionError('Not Allowed, %s' % str(method)) | ||||
else: | else: | ||||
if not method in frappe.whitelisted: | if not method in frappe.whitelisted: | ||||
frappe._response.status_code = 403 | |||||
frappe.msgprint('Not Allowed, %s' % str(method)) | frappe.msgprint('Not Allowed, %s' % str(method)) | ||||
raise frappe.PermissionError('Not Allowed, %s' % str(method)) | raise frappe.PermissionError('Not Allowed, %s' % str(method)) | ||||
@@ -1,6 +1,6 @@ | |||||
app_name = frappe | app_name = frappe | ||||
app_title = Frappe Framework | app_title = Frappe Framework | ||||
app_publisher = Web Notes Technologies | |||||
app_publisher = Web Notes Technologies Pvt. Ltd. and Contributors | |||||
app_description = Full Stack Web Application Framwork in Python | app_description = Full Stack Web Application Framwork in Python | ||||
app_icon = assets/frappe/images/frappe.svg | app_icon = assets/frappe/images/frappe.svg | ||||
app_version = 4.0.0-wip | app_version = 4.0.0-wip | ||||
@@ -114,6 +114,11 @@ def block_user(block): | |||||
frappe.db.set_global('__session_status', block and 'stop' or None) | frappe.db.set_global('__session_status', block and 'stop' or None) | ||||
frappe.db.set_global('__session_status_message', block and msg or None) | frappe.db.set_global('__session_status_message', block and msg or None) | ||||
frappe.db.commit() | frappe.db.commit() | ||||
def check_session_stopped(): | |||||
if frappe.db.get_global("__session_status")=='stop': | |||||
frappe.msgprint(frappe.db.get_global("__session_status_message")) | |||||
raise frappe.SessionStopped('Session Stopped') | |||||
def setup(): | def setup(): | ||||
frappe.db.sql("""CREATE TABLE IF NOT EXISTS `__PatchLog` ( | frappe.db.sql("""CREATE TABLE IF NOT EXISTS `__PatchLog` ( | ||||
@@ -30,10 +30,10 @@ frappe.avatar = function(user, large, title) { | |||||
}); | }); | ||||
} | } | ||||
frappe.ui.set_user_background = function(src) { | |||||
if(!src) src = "assets/frappe/images/ui/random-polygons.jpg"; | |||||
frappe.ui.set_user_background = function(src, size) { | |||||
if(!src) src = "assets/frappe/images/ui/wallpaper.jpg"; | |||||
frappe.dom.set_style(repl('#page-desktop { \ | frappe.dom.set_style(repl('#page-desktop { \ | ||||
background: url("%(src)s") center center fixed; \ | |||||
background: url("%(src)s") center center; \ | |||||
}', {src:src})) | }', {src:src})) | ||||
} | } | ||||
@@ -59,7 +59,7 @@ def clear_sessions(user=None, keep_current=False): | |||||
user = frappe.session.user | user = frappe.session.user | ||||
for sid in frappe.db.sql("""select sid from tabSessions where user=%s""", (user,)): | for sid in frappe.db.sql("""select sid from tabSessions where user=%s""", (user,)): | ||||
if keep_current and frappe.session.sid==sid[0]: | if keep_current and frappe.session.sid==sid[0]: | ||||
pass | |||||
continue | |||||
else: | else: | ||||
frappe.cache().delete_value("session:" + sid[0]) | frappe.cache().delete_value("session:" + sid[0]) | ||||
frappe.db.sql("""delete from tabSessions where sid=%s""", (sid[0],)) | frappe.db.sql("""delete from tabSessions where sid=%s""", (sid[0],)) | ||||
@@ -104,9 +104,6 @@ class Session: | |||||
# set local session | # set local session | ||||
frappe.local.session = self.data | frappe.local.session = self.data | ||||
# write out latest cookies | |||||
frappe.local.cookie_manager.set_cookies() | |||||
def start(self): | def start(self): | ||||
"""start a new session""" | """start a new session""" | ||||
# generate sid | # generate sid | ||||
@@ -102,7 +102,11 @@ login.login_handlers = { | |||||
} | } | ||||
}, | }, | ||||
401: function(xhr, data) { | 401: function(xhr, data) { | ||||
frappe.msgprint("Invalid Login"); | |||||
if(xhr.responseJSON) { | |||||
data = xhr.responseJSON; | |||||
} | |||||
var message = data._server_messages ? JSON.parse(data._server_messages).join("\n") : "Invalid Login"; | |||||
frappe.msgprint(message); | |||||
} | } | ||||
} | } | ||||
@@ -33,7 +33,7 @@ oauth2_providers = { | |||||
"redirect_uri": "/api/method/frappe.templates.pages.login.login_via_google", | "redirect_uri": "/api/method/frappe.templates.pages.login.login_via_google", | ||||
"auth_url_data": { | "auth_url_data": { | ||||
"scope": "https://www.googleapis.com/auth/userinfo.user https://www.googleapis.com/auth/userinfo.email", | |||||
"scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", | |||||
"response_type": "code" | "response_type": "code" | ||||
}, | }, | ||||
@@ -12,6 +12,7 @@ from frappe import _ | |||||
import frappe.utils | import frappe.utils | ||||
import frappe.sessions | import frappe.sessions | ||||
import frappe.model.utils | import frappe.model.utils | ||||
import werkzeug.utils | |||||
from werkzeug.local import LocalProxy | from werkzeug.local import LocalProxy | ||||
from werkzeug.wsgi import wrap_file | from werkzeug.wsgi import wrap_file | ||||
from werkzeug.wrappers import Response | from werkzeug.wrappers import Response | ||||
@@ -20,61 +21,49 @@ from werkzeug.exceptions import NotFound, Forbidden | |||||
def report_error(status_code): | def report_error(status_code): | ||||
if status_code!=404 or frappe.conf.logging: | if status_code!=404 or frappe.conf.logging: | ||||
frappe.errprint(frappe.utils.get_traceback()) | frappe.errprint(frappe.utils.get_traceback()) | ||||
frappe._response.status_code = status_code | |||||
if frappe.request_method == "POST": | |||||
frappe.db.rollback() | |||||
response = build_response("json") | |||||
response.status_code = status_code | |||||
return response | |||||
def build_response(): | |||||
print_map = { | |||||
'csv': print_csv, | |||||
'download': print_raw, | |||||
'json': print_json, | |||||
'page': print_page, | |||||
def build_response(response_type=None): | |||||
response_type_map = { | |||||
'csv': as_csv, | |||||
'download': as_raw, | |||||
'json': as_json, | |||||
'page': as_page, | |||||
'redirect': redirect | 'redirect': redirect | ||||
} | } | ||||
print_map.get(frappe.response.get('type'), print_json)() | |||||
def print_page(): | |||||
"""print web page""" | |||||
from frappe.website.render import render | |||||
render(frappe.response['page_name']) | |||||
def print_json(): | |||||
make_logs() | |||||
cleanup_docs() | |||||
frappe._response.headers["Content-Type"] = "text/json; charset: utf-8" | |||||
print_zip(json.dumps(frappe.local.response, default=json_handler, separators=(',',':'))) | |||||
def redirect(): | |||||
frappe._response.data = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> | |||||
<title>Redirecting...</title> | |||||
<h1>Redirecting...</h1> | |||||
<p>You should be redirected automatically to target URL: <a href="{location}">/</a>. | |||||
If not click the link.'""".format(location=frappe.response.location) | |||||
frappe._response.headers["Content-Type"] = "text/html; charset: utf-8" | |||||
frappe._response.status_code = frappe.response.status_code or 302 | |||||
frappe._response.location = frappe.response.location | |||||
return response_type_map[frappe.response.get('type') or response_type]() | |||||
def cleanup_docs(): | |||||
if frappe.response.get('docs') and type(frappe.response['docs'])!=dict: | |||||
frappe.response['docs'] = frappe.model.utils.compress(frappe.response['docs']) | |||||
def print_csv(): | |||||
frappe._response.headers["Content-Type"] = \ | |||||
def as_csv(): | |||||
response = Response() | |||||
response.headers["Content-Type"] = \ | |||||
"text/csv; charset: utf-8" | "text/csv; charset: utf-8" | ||||
frappe._response.headers["Content-Disposition"] = \ | |||||
response.headers["Content-Disposition"] = \ | |||||
"attachment; filename=%s.csv" % frappe.response['doctype'].replace(' ', '_') | "attachment; filename=%s.csv" % frappe.response['doctype'].replace(' ', '_') | ||||
frappe._response.data = frappe.response['result'] | |||||
response.data = frappe.response['result'] | |||||
return response | |||||
def print_raw(): | |||||
frappe._response.headers["Content-Type"] = \ | |||||
def as_raw(): | |||||
response = Response() | |||||
response.headers["Content-Type"] = \ | |||||
mimetypes.guess_type(frappe.response['filename'])[0] or "application/unknown" | mimetypes.guess_type(frappe.response['filename'])[0] or "application/unknown" | ||||
frappe._response.headers["Content-Disposition"] = \ | |||||
response.headers["Content-Disposition"] = \ | |||||
"filename=%s" % frappe.response['filename'].replace(' ', '_') | "filename=%s" % frappe.response['filename'].replace(' ', '_') | ||||
frappe._response.data = frappe.response['filecontent'] | |||||
response.data = frappe.response['filecontent'] | |||||
return response | |||||
def as_json(): | |||||
make_logs() | |||||
cleanup_docs() | |||||
response = Response() | |||||
response.headers["Content-Type"] = "text/json; charset: utf-8" | |||||
response = gzip(json.dumps(frappe.local.response, default=json_handler, separators=(',',':')), | |||||
response=response) | |||||
return response | |||||
def make_logs(): | def make_logs(): | ||||
"""make strings for msgprint and errprint""" | """make strings for msgprint and errprint""" | ||||
if frappe.error_log: | if frappe.error_log: | ||||
@@ -87,15 +76,31 @@ def make_logs(): | |||||
if frappe.debug_log and frappe.conf.get("logging") or False: | if frappe.debug_log and frappe.conf.get("logging") or False: | ||||
frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log) | frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log) | ||||
def print_zip(response): | |||||
response = response.encode('utf-8') | |||||
orig_len = len(response) | |||||
def cleanup_docs(): | |||||
if frappe.response.get('docs') and type(frappe.response['docs'])!=dict: | |||||
frappe.response['docs'] = frappe.model.utils.compress(frappe.response['docs']) | |||||
def gzip(data, response): | |||||
data = data.encode('utf-8') | |||||
orig_len = len(data) | |||||
if accept_gzip() and orig_len>512: | if accept_gzip() and orig_len>512: | ||||
response = compressBuf(response) | |||||
frappe._response.headers["Content-Encoding"] = "gzip" | |||||
data = compressBuf(data) | |||||
response.headers["Content-Encoding"] = "gzip" | |||||
frappe._response.headers["Content-Length"] = str(len(response)) | |||||
frappe._response.data = response | |||||
response.headers["Content-Length"] = str(len(data)) | |||||
response.data = data | |||||
return response | |||||
def accept_gzip(): | |||||
if "gzip" in frappe.get_request_header("HTTP_ACCEPT_ENCODING", ""): | |||||
return True | |||||
def compressBuf(buf): | |||||
zbuf = cStringIO.StringIO() | |||||
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 5) | |||||
zfile.write(buf) | |||||
zfile.close() | |||||
return zbuf.getvalue() | |||||
def json_handler(obj): | def json_handler(obj): | ||||
"""serialize non-serializable data for json""" | """serialize non-serializable data for json""" | ||||
@@ -108,31 +113,30 @@ def json_handler(obj): | |||||
else: | else: | ||||
raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \ | raise TypeError, """Object of type %s with value of %s is not JSON serializable""" % \ | ||||
(type(obj), repr(obj)) | (type(obj), repr(obj)) | ||||
def accept_gzip(): | |||||
if "gzip" in frappe.get_request_header("HTTP_ACCEPT_ENCODING", ""): | |||||
return True | |||||
def compressBuf(buf): | |||||
zbuf = cStringIO.StringIO() | |||||
zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf, compresslevel = 5) | |||||
zfile.write(buf) | |||||
zfile.close() | |||||
return zbuf.getvalue() | |||||
def as_page(): | |||||
"""print web page""" | |||||
from frappe.website.render import render | |||||
return render(frappe.response['page_name']) | |||||
def redirect(): | |||||
return werkzeug.utils.redirect(frappe.response.location) | |||||
def download_backup(path): | def download_backup(path): | ||||
try: | try: | ||||
frappe.only_for(("System Manager", "Administrator")) | frappe.only_for(("System Manager", "Administrator")) | ||||
except frappe.PermissionError: | except frappe.PermissionError: | ||||
raise Forbidden(_("You need to be logged in and have System Manager Role to be able to access backups.")) | raise Forbidden(_("You need to be logged in and have System Manager Role to be able to access backups.")) | ||||
send_private_file(path) | |||||
return send_private_file(path) | |||||
def send_private_file(path): | def send_private_file(path): | ||||
path = os.path.join(frappe.local.conf.get('private_path', 'private'), path.strip("/")) | path = os.path.join(frappe.local.conf.get('private_path', 'private'), path.strip("/")) | ||||
if frappe.local.request.headers.get('X-Use-X-Accel-Redirect'): | if frappe.local.request.headers.get('X-Use-X-Accel-Redirect'): | ||||
path = '/' + path | path = '/' + path | ||||
frappe.local._response.headers['X-Accel-Redirect'] = path | |||||
response = Response() | |||||
response.headers['X-Accel-Redirect'] = path | |||||
else: | else: | ||||
filename = os.path.basename(path) | filename = os.path.basename(path) | ||||
filepath = frappe.utils.get_site_path(path) | filepath = frappe.utils.get_site_path(path) | ||||
@@ -140,6 +144,22 @@ def send_private_file(path): | |||||
f = open(filepath, 'rb') | f = open(filepath, 'rb') | ||||
except IOError: | except IOError: | ||||
raise NotFound | raise NotFound | ||||
frappe.local._response = Response(wrap_file(frappe.local.request.environ, f)) | |||||
frappe.local._response.headers.add('Content-Disposition', 'attachment', filename=filename) | |||||
frappe.local._response.headers['Content-Type'] = mimetypes.guess_type(filename)[0] or 'application/octet-stream' | |||||
response = Response(wrap_file(frappe.local.request.environ, f)) | |||||
response.headers.add('Content-Disposition', 'attachment', filename=filename) | |||||
response.headers['Content-Type'] = mimetypes.guess_type(filename)[0] or 'application/octet-stream' | |||||
return response | |||||
def handle_session_stopped(): | |||||
response = Response("""<html> | |||||
<body style="background-color: #EEE;"> | |||||
<h3 style="width: 900px; background-color: #FFF; border: 2px solid #AAA; padding: 20px; font-family: Arial; margin: 20px auto"> | |||||
Updating. | |||||
We will be back in a few moments... | |||||
</h3> | |||||
</body> | |||||
</html>""") | |||||
response.status_code = 503 | |||||
response.content_type = 'text/html' | |||||
return res |
@@ -4,17 +4,18 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
import mimetypes, json | import mimetypes, json | ||||
from werkzeug.wrappers import Response | |||||
from frappe.website.context import get_context | from frappe.website.context import get_context | ||||
from frappe.website.utils import scrub_relative_urls, get_home_page, can_cache | from frappe.website.utils import scrub_relative_urls, get_home_page, can_cache | ||||
from frappe.website.permissions import get_access, clear_permissions | from frappe.website.permissions import get_access, clear_permissions | ||||
class PageNotFoundError(Exception): pass | class PageNotFoundError(Exception): pass | ||||
def render(path): | def render(path): | ||||
"""render html page""" | """render html page""" | ||||
path = resolve_path(path) | |||||
frappe.local.is_ajax = frappe.get_request_header("X-Requested-With")=="XMLHttpRequest" | |||||
path = resolve_path(path.lstrip("/")) | |||||
try: | try: | ||||
data = render_page(path) | data = render_page(path) | ||||
@@ -22,9 +23,12 @@ def render(path): | |||||
path = "error" | path = "error" | ||||
data = render_page(path) | data = render_page(path) | ||||
data = set_content_type(data, path) | |||||
frappe._response.data = data | |||||
frappe._response.headers[b"X-Page-Name"] = path.encode("utf-8") | |||||
# build response | |||||
response = Response() | |||||
response.data = set_content_type(response, data, path) | |||||
response.headers[b"X-Page-Name"] = path.encode("utf-8") | |||||
response.headers[b"X-From-Cache"] = frappe.local.response.from_cache or False | |||||
return response | |||||
def render_page(path): | def render_page(path): | ||||
"""get page html""" | """get page html""" | ||||
@@ -39,9 +43,7 @@ def render_page(path): | |||||
out = out.get("data") | out = out.get("data") | ||||
if out: | if out: | ||||
if hasattr(frappe, "_response"): | |||||
frappe._response.headers[b"X-From-Cache"] = True | |||||
frappe.local.response.from_cache = True | |||||
return out | return out | ||||
return build(path) | return build(path) | ||||
@@ -75,8 +77,7 @@ def build_page(path): | |||||
return html | return html | ||||
def is_ajax(): | def is_ajax(): | ||||
return (frappe.get_request_header("X-Requested-With")=="XMLHttpRequest" | |||||
if hasattr(frappe.local, "_response") else False) | |||||
return getattr(frappe.local, "is_ajax") | |||||
def resolve_path(path): | def resolve_path(path): | ||||
if not path: | if not path: | ||||
@@ -90,17 +91,17 @@ def resolve_path(path): | |||||
return path | return path | ||||
def set_content_type(data, path): | |||||
def set_content_type(response, data, path): | |||||
if isinstance(data, dict): | if isinstance(data, dict): | ||||
frappe._response.headers[b"Content-Type"] = b"application/json; charset: utf-8" | |||||
response.headers[b"Content-Type"] = b"application/json; charset: utf-8" | |||||
data = json.dumps(data) | data = json.dumps(data) | ||||
return data | return data | ||||
frappe._response.headers[b"Content-Type"] = b"text/html; charset: utf-8" | |||||
response.headers[b"Content-Type"] = b"text/html; charset: utf-8" | |||||
if "." in path and not path.endswith(".html"): | if "." in path and not path.endswith(".html"): | ||||
content_type, encoding = mimetypes.guess_type(path) | content_type, encoding = mimetypes.guess_type(path) | ||||
frappe._response.headers[b"Content-Type"] = content_type.encode("utf-8") | |||||
response.headers[b"Content-Type"] = content_type.encode("utf-8") | |||||
return data | return data | ||||