diff --git a/frappe/__init__.py b/frappe/__init__.py index ace5746720..1942111218 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.1.62' +__version__ = '10.1.64' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/client.py b/frappe/client.py index 1695b39d3a..c8ec3a7156 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -28,7 +28,7 @@ def get_list(doctype, fields=None, filters=None, order_by=None, :param limit_start: Start at this index :param limit_page_length: Number of records to be returned (default 20)''' if frappe.is_table(doctype): - check_parent_permission(parent) + check_parent_permission(parent, doctype) 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) @@ -41,7 +41,7 @@ def get(doctype, name=None, filters=None, parent=None): :param name: return document of this `name` :param filters: If name is not set, filter by these values and return the first match''' if frappe.is_table(doctype): - check_parent_permission(parent) + check_parent_permission(parent, doctype) if filters and not name: name = frappe.db.get_value(doctype, json.loads(filters)) @@ -62,7 +62,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren :param fieldname: Field to be returned (default `name`) :param filters: dict or string for identifying the record''' if frappe.is_table(doctype): - check_parent_permission(parent) + check_parent_permission(parent, doctype) if not frappe.has_permission(doctype): frappe.throw(_("No permission for {0}".format(doctype)), frappe.PermissionError) @@ -314,9 +314,14 @@ def get_time_zone(): '''Returns default time zone''' return {"time_zone": frappe.defaults.get_defaults().get("time_zone")} -def check_parent_permission(parent): +def check_parent_permission(parent, child_doctype): if parent: + # User may pass fake parent and get the information from the child table + if child_doctype and not frappe.db.exists('DocField', + {'parent': parent, 'options': child_doctype}): + raise frappe.PermissionError + if frappe.permissions.has_permission(parent): return # Either parent not passed or the user doesn't have permission on parent doctype of child table! - raise frappe.PermissionError + raise frappe.PermissionError \ No newline at end of file diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 0dbc83848f..27d0b1c742 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -455,8 +455,10 @@ def _set_limits(context, site, limits): frappe.connect() new_limits = {} for limit, value in limits: - if limit not in ('daily_emails', 'emails', 'space', 'users', 'email_group', - 'expiry', 'support_email', 'support_chat', 'upgrade_url'): + if limit not in ('daily_emails', 'emails', 'space', 'users', 'email_group', 'currency', + 'expiry', 'support_email', 'support_chat', 'upgrade_url', 'subscription_id', + 'subscription_type', 'current_plan', 'subscription_base_price', 'upgrade_plan', + 'upgrade_base_price'): frappe.throw(_('Invalid limit {0}').format(limit)) if limit=='expiry' and value: @@ -465,7 +467,7 @@ def _set_limits(context, site, limits): except ValueError: raise ValueError("Incorrect data format, should be YYYY-MM-DD") - elif limit=='space': + elif limit in ('space', 'subscription_base_price', 'upgrade_base_price'): value = float(value) elif limit in ('users', 'emails', 'email_group', 'daily_emails'): diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index 4942d98375..a75c54f33b 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -108,7 +108,7 @@ class AutoEmailReport(Document): new_row = [] out.append(new_row) for df in columns: - if not row.get(df.fieldname): continue + if not row.has_key(df.fieldname): continue new_row.append(frappe.format(row[df.fieldname], df, row)) return out diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index d561517558..ed3b901084 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -14,6 +14,7 @@ import frappe.permissions from frappe.utils import flt, cint, getdate, get_datetime, get_time, make_filter_tuple, get_filter, add_to_date from frappe import _ from frappe.model import optional_fields +from frappe.client import check_parent_permission from frappe.model.utils.user_settings import get_user_settings, update_user_settings from datetime import datetime @@ -619,6 +620,19 @@ def get_list(doctype, *args, **kwargs): '''wrapper for DatabaseQuery''' kwargs.pop('cmd', None) kwargs.pop('ignore_permissions', None) + + # If doctype is child table + if frappe.is_table(doctype): + # Example frappe.db.get_list('Purchase Receipt Item', {'parent': 'Purchase Receipt'}) + # Here purchase receipt is the parent doctype of the child doctype Purchase Receipt Item + + if not kwargs.get('parent'): + frappe.flags.error_message = _('Parent is required to get child table data') + raise frappe.PermissionError(doctype) + + check_parent_permission(kwargs.get('parent'), doctype) + del kwargs['parent'] + return DatabaseQuery(doctype).execute(None, *args, **kwargs) def is_parent_only_filter(doctype, filters): diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 732b85456b..961a147ec6 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -78,6 +78,7 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F frappe.delete_doc(doctype, old) frappe.clear_cache() + frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype', doctype=doctype) return new diff --git a/frappe/public/js/frappe/ui/toolbar/search.js b/frappe/public/js/frappe/ui/toolbar/search.js index dec4f38795..293989ca61 100644 --- a/frappe/public/js/frappe/ui/toolbar/search.js +++ b/frappe/public/js/frappe/ui/toolbar/search.js @@ -357,7 +357,7 @@ frappe.search.SearchDialog = Class.extend({ no_results_status: (keyword) => __("

No results found for '" + keyword + "' in Global Search

"), get_results: function(keywords, callback) { - var start = 0, limit = 100; + var start = 0, limit = 1000; var results = frappe.search.utils.get_nav_results(keywords); frappe.search.utils.get_global_results(keywords, start, limit) .then(function(global_results) { diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index 05af4c99fb..41d8ba20bc 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -122,16 +122,16 @@ class RedisWrapper(redis.Redis): pass def lpush(self, key, value): - super(redis.Redis, self).lpush(self.make_key(key), value) + super(RedisWrapper, self).lpush(self.make_key(key), value) def rpush(self, key, value): - super(redis.Redis, self).rpush(self.make_key(key), value) + super(RedisWrapper, self).rpush(self.make_key(key), value) def lpop(self, key): - return super(redis.Redis, self).lpop(self.make_key(key)) + return super(RedisWrapper, self).lpop(self.make_key(key)) def llen(self, key): - return super(redis.Redis, self).llen(self.make_key(key)) + return super(RedisWrapper, self).llen(self.make_key(key)) def hset(self, name, key, value, shared=False): _name = self.make_key(name, shared=shared) @@ -143,14 +143,14 @@ class RedisWrapper(redis.Redis): # set in redis try: - super(redis.Redis, self).hset(_name, + super(RedisWrapper, self).hset(_name, key, pickle.dumps(value)) except redis.exceptions.ConnectionError: pass def hgetall(self, name): return {key: pickle.loads(value) for key, value in - iteritems(super(redis.Redis, self).hgetall(self.make_key(name)))} + iteritems(super(RedisWrapper, self).hgetall(self.make_key(name)))} def hget(self, name, key, generator=None, shared=False): _name = self.make_key(name, shared=shared) @@ -162,7 +162,7 @@ class RedisWrapper(redis.Redis): value = None try: - value = super(redis.Redis, self).hget(_name, key) + value = super(RedisWrapper, self).hget(_name, key) except redis.exceptions.ConnectionError: pass @@ -184,7 +184,7 @@ class RedisWrapper(redis.Redis): if key in frappe.local.cache[_name]: del frappe.local.cache[_name][key] try: - super(redis.Redis, self).hdel(_name, key) + super(RedisWrapper, self).hdel(_name, key) except redis.exceptions.ConnectionError: pass @@ -196,31 +196,31 @@ class RedisWrapper(redis.Redis): def hkeys(self, name): try: - return super(redis.Redis, self).hkeys(self.make_key(name)) + return super(RedisWrapper, self).hkeys(self.make_key(name)) except redis.exceptions.ConnectionError: return [] def sadd(self, name, *values): """Add a member/members to a given set""" - super(redis.Redis, self).sadd(self.make_key(name), *values) + super(RedisWrapper, self).sadd(self.make_key(name), *values) def srem(self, name, *values): """Remove a specific member/list of members from the set""" - super(redis.Redis, self).srem(self.make_key(name), *values) + super(RedisWrapper, self).srem(self.make_key(name), *values) def sismember(self, name, value): """Returns True or False based on if a given value is present in the set""" - return super(redis.Redis, self).sismember(self.make_key(name), value) + return super(RedisWrapper, self).sismember(self.make_key(name), value) def spop(self, name): """Removes and returns a random member from the set""" - return super(redis.Redis, self).spop(self.make_key(name)) + return super(RedisWrapper, self).spop(self.make_key(name)) def srandmember(self, name, count=None): """Returns a random member from the set""" - return super(redis.Redis, self).srandmember(self.make_key(name)) + return super(RedisWrapper, self).srandmember(self.make_key(name)) def smembers(self, name): """Return all members of the set""" - return super(redis.Redis, self).smembers(self.make_key(name)) + return super(RedisWrapper, self).smembers(self.make_key(name)) diff --git a/requirements.txt b/requirements.txt index f5b80fb1a1..91e57e2f19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,53 +1,53 @@ -boto3 -chardet -cssmin +boto3==1.9.45 +chardet==3.0.4 +cssmin==0.2.0 dropbox==7.3.1 -gunicorn -httplib2 -jinja2 -markdown2 -markupsafe -PyMySQL -python-geoip -python-geoip-geolite2 -python-dateutil -pytz -six -termcolor -werkzeug -semantic_version +gunicorn==19.9.0 +httplib2==0.12.0 +jinja2==2.10 +markdown2==2.3.6 +markupsafe==1.1.0 +PyMySQL==0.9.2 +python-geoip==1.2 +python-geoip-geolite2==2015.303 +python-dateutil==2.7.5 +pytz==2018.7 +six==1.11.0 +termcolor==1.1.0 +werkzeug==0.14.1 +semantic_version==2.6.0 rauth>=0.6.2 -requests +requests==2.20.1 redis==2.10.6 -selenium -babel -ipython +selenium==3.141.0 +babel==2.6.0 +ipython==5.8.0 html2text==2016.9.19 -email_reply_parser -click +email_reply_parser==0.5.9 +click==7.0 num2words==0.5.5 watchdog==0.8.0 bleach==2.1.4 -bleach-whitelist -Pillow -beautifulsoup4 -rq -schedule -cryptography -pyopenssl -ndg-httpsclient -pyasn1 -zxcvbn-python -unittest-xml-reporting -oauthlib -PyJWT -PyPDF2 -openpyxl -pyotp -pyqrcode -pypng -premailer -croniter -googlemaps +bleach-whitelist==0.0.9 +Pillow==5.3.0 +beautifulsoup4==4.6.3 +rq==0.12.0 +schedule==0.5.0 +cryptography==2.4.1 +pyopenssl==18.0.0 +ndg-httpsclient==0.5.1 +pyasn1==0.4.4 +zxcvbn-python==4.4.24 +unittest-xml-reporting==2.2.0 +oauthlib==2.1.0 +PyJWT==1.6.4 +PyPDF2==1.26.0 +openpyxl==2.5.10 +pyotp==2.2.7 +pyqrcode==1.2.1 +pypng==0.0.18 +premailer==3.2.0 +croniter==0.3.26 +googlemaps==3.0.2 urllib3==1.23 GitPython==2.1.11