From 3854bcad04bc905cc9c1d4b79d0fc6388d2d055e Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Sat, 25 Jun 2016 17:41:19 +0530 Subject: [PATCH] [fix] limits variables and how usage is stored within limits + additional fixes --- frappe/__init__.py | 2 +- frappe/boot.py | 5 + frappe/commands/site.py | 23 +-- frappe/core/doctype/file/file.py | 65 +------ frappe/core/doctype/file/test_file.py | 15 +- frappe/core/doctype/user/test_user.py | 8 +- frappe/core/doctype/user/user.py | 33 +++- frappe/core/page/usage_info/usage_info.js | 32 ++- frappe/desk/page/setup_wizard/setup_wizard.js | 7 +- frappe/desk/page/setup_wizard/setup_wizard.py | 11 +- .../email/doctype/email_queue/email_queue.py | 12 +- frappe/email/queue.py | 14 +- frappe/hooks.py | 12 +- frappe/limits.py | 184 ++++++++++++++---- frappe/public/js/frappe/setup_wizard.js | 18 -- frappe/public/js/frappe/toolbar.js | 57 +++--- frappe/tests/test_scheduler.py | 6 +- frappe/utils/user.py | 35 ---- frappe/website/render.py | 2 +- 19 files changed, 286 insertions(+), 255 deletions(-) delete mode 100755 frappe/public/js/frappe/setup_wizard.js diff --git a/frappe/__init__.py b/frappe/__init__.py index cb012350f3..fac5b1fdcd 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1227,4 +1227,4 @@ def logger(module=None, with_more_info=True): return get_logger(module or __name__, with_more_info=with_more_info) def get_desk_link(doctype, name): - return '{2} {1}'.format(doctype, name, _(doctype)) \ No newline at end of file + return '{2} {1}'.format(doctype, name, _(doctype)) diff --git a/frappe/boot.py b/frappe/boot.py index f2cf2eeedd..1f8e3f3279 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -12,6 +12,7 @@ import frappe.desk.desk_page from frappe.desk.form.load import get_meta_bundle from frappe.utils.change_log import get_versions from frappe.translate import get_lang_dict +from frappe.limits import get_limits, get_expiry_message def get_bootinfo(): """build and return boot info""" @@ -65,6 +66,10 @@ def get_bootinfo(): bootinfo.calendars = sorted(frappe.get_hooks("calendars")) bootinfo.lang_dict = get_lang_dict() + # limits + bootinfo.limits = get_limits() + bootinfo.expiry_message = get_expiry_message() + return bootinfo def load_conf_settings(bootinfo): diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 7382012c4b..2c7b2ed470 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -4,8 +4,7 @@ import hashlib, os import frappe from frappe.commands import pass_context, get_site from frappe.commands.scheduler import _is_scheduler_enabled -from frappe.commands import get_site -from frappe.limits import set_limits, get_limits +from frappe.limits import update_limits, get_limits from frappe.installer import update_site_config @click.command('new-site') @@ -345,18 +344,19 @@ def set_limit(context, site, limit, value): site = get_site(context) with frappe.init_site(site): - if limit == 'expiry': + if limit=='expiry': try: datetime.datetime.strptime(value, '%Y-%m-%d') except ValueError: raise ValueError("Incorrect data format, should be YYYY-MM-DD") - else: - limit += '_limit' - # Space can be float, while other should be integers - value = float(value) if limit == 'space_limit' else int(value) - set_limits({limit : value}) + elif limit=='space': + value = float(value) + + else: + value = int(value) + update_limits({ limit : value }) @click.command('clear-limit') @click.option('--site', help='site name') @@ -369,14 +369,11 @@ def clear_limit(context, site, limit): site = get_site(context) with frappe.init_site(site): - if not limit == 'expiry': - limit += '_limit' - _clear_limit(limit) # Remove limits from the site_config, if it's empty - cur_limits = get_limits() - if not cur_limits: + limits = get_limits() + if not limits: update_site_config('limits', 'None', validate=False) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 7699baae1d..415ba5ab5e 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -11,7 +11,7 @@ naming for same name files: file.gif, file-1.gif, file-2.gif etc import frappe import json import urllib -import os, subprocess +import os import requests import requests.exceptions import StringIO @@ -20,8 +20,7 @@ import mimetypes, imghdr from frappe.utils.file_manager import delete_file_data_content, get_content_hash, get_random_filename from frappe import _ from frappe.utils.nestedset import NestedSet -from frappe.limits import get_limits, set_limits -from frappe.utils import strip, get_url, get_files_path, flt +from frappe.utils import strip, get_files_path from PIL import Image, ImageOps class FolderNotEmpty(frappe.ValidationError): pass @@ -352,63 +351,3 @@ def check_file_permission(file_url): raise frappe.PermissionError -def validate_space_limit(file_size): - """Stop from writing file if max space limit is reached""" - from frappe.installer import update_site_config - from frappe.utils import cint - from frappe.utils.file_manager import MaxFileSizeReachedError - - frappe_limits = get_limits() - - if not frappe_limits.has_key('space_limit'): - return - - # In Gigabytes - space_limit = flt(flt(frappe_limits['space_limit']) * 1024, 2) - - # in Kilobytes - used_space = flt(frappe_limits['files_size']) + flt(frappe_limits['backup_size']) + flt(frappe_limits['database_size']) - file_size = file_size / (1024.0**2) - - # Stop from attaching file - if flt(used_space + file_size, 2) > space_limit: - frappe.throw(_("You have exceeded the max space of {0} for your plan. {1} or {2}.").format( - "{0}MB".format(cint(space_limit)) if (space_limit < 1024) else "{0}GB".format(frappe_limits['space_limit']), - '{0}'.format(_("Click here to check your usage")), - '{0}'.format(_("upgrade to a higher plan")), - ), MaxFileSizeReachedError) - - # update files size in frappe subscription - new_files_size = flt(frappe_limits['files_size']) + file_size - set_limits({'files_size': file_size}) - -def update_sizes(): - from frappe.installer import update_site_config - # public files - files_path = frappe.get_site_path("public", "files") - files_size = flt(subprocess.check_output(['du', '-ms', files_path]).split()[0]) - - # private files - files_path = frappe.get_site_path("private", "files") - if os.path.exists(files_path): - files_size += flt(subprocess.check_output(['du', '-ms', files_path]).split()[0]) - - # backups - backup_path = frappe.get_site_path("private", "backups") - backup_size = subprocess.check_output(['du', '-ms', backup_path]).split()[0] - - database_size = get_database_size() - - set_limits({'files_size': files_size, - 'backup_size': backup_size, - 'database_size': database_size}) - -def get_database_size(): - db_name = frappe.conf.db_name - - # This query will get the database size in MB - db_size = frappe.db.sql(''' - SELECT table_schema "database_name", sum( data_length + index_length ) / 1024 / 1024 "database_size" - FROM information_schema.TABLES WHERE table_schema = %s GROUP BY table_schema''', db_name, as_dict=True) - - return db_size[0].get('database_size') diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 67d0b1f610..d346d38709 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -98,10 +98,17 @@ class TestFile(unittest.TestCase): def test_file_upload_limit(self): from frappe.utils.file_manager import MaxFileSizeReachedError - from frappe.limits import set_limits, clear_limit + from frappe.limits import update_limits, clear_limit from frappe import _dict - set_limits({'space_limit': 1, 'files_size': (1024 * 1024), 'database_size': 0, 'backup_size': 0}) + update_limits({ + 'space': 1, + 'space_usage': { + 'files_size': (1024 * 1024), + 'database_size': 0, + 'backup_size': 0 + } + }) # Rebuild the frappe.local.conf to take up the changes from site_config frappe.local.conf = _dict(frappe.get_site_config()) @@ -110,5 +117,5 @@ class TestFile(unittest.TestCase): 'This files test for max space usage', "", "", self.get_folder("Test Folder 2", "Home").name) # Scrub the site_config and rebuild frappe.local.conf - clear_limit("space_limit") - frappe.local.conf = _dict(frappe.get_site_config()) \ No newline at end of file + clear_limit("space") + frappe.local.conf = _dict(frappe.get_site_config()) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 32169b9a6e..ce61e99110 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -8,7 +8,7 @@ import requests from frappe.model.delete_doc import delete_doc from frappe.utils.data import today, add_to_date from frappe import _dict -from frappe.limits import SiteExpiredError, set_limits, clear_limit +from frappe.limits import SiteExpiredError, update_limits, clear_limit from frappe.utils import get_url from frappe.installer import update_site_config @@ -82,7 +82,7 @@ class TestUser(unittest.TestCase): def test_user_limit_for_site(self): from frappe.core.doctype.user.user import get_total_users - set_limits({'user_limit': get_total_users()}) + update_limits({'users': get_total_users()}) # reload site config from frappe import _dict @@ -99,10 +99,10 @@ class TestUser(unittest.TestCase): frappe.delete_doc('User', 'test_max_users@example.com') # Clear the user limit - clear_limit('user_limit') + clear_limit('users') def test_site_expiry(self): - set_limits({'expiry': add_to_date(today(), days=-1)}) + update_limits({'expiry': add_to_date(today(), days=-1)}) frappe.local.conf = _dict(frappe.get_site_config()) frappe.db.commit() diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 4362865828..e19558655b 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe.model.document import Document from frappe.utils import cint, has_gravatar, format_datetime, now_datetime, get_formatted_email from frappe import throw, msgprint, _ from frappe.utils.password import update_password as _update_password @@ -11,10 +12,11 @@ from frappe.utils.user import get_system_managers import frappe.permissions import frappe.share import re +from frappe.limits import get_limits STANDARD_USERS = ("Guest", "Administrator") -from frappe.model.document import Document +class MaxUsersReachedError(frappe.ValidationError): pass class User(Document): __new_password = None @@ -54,6 +56,7 @@ class User(Document): self.remove_all_roles_for_guest() self.validate_username() self.remove_disabled_roles() + self.validate_user_limit() if self.language == "Loading...": self.language = None @@ -383,6 +386,34 @@ class User(Document): """Returns list of modules blocked for that user""" return [d.module for d in self.block_modules] if self.block_modules else [] + def validate_user_limit(self): + ''' + Validate if user limit has been reached for System Users + Checked in 'Validate' event as we don't want welcome email sent if max users are exceeded. + ''' + + if self.user_type == "Website User": + return + + if not self.enabled: + # don't validate max users when saving a disabled user + return + + limits = get_limits() + if not limits.users: + # no limits defined + return + + total_users = get_total_users() + if self.is_new(): + # get_total_users gets existing users in database + # a new record isn't inserted yet, so adding 1 + total_users += 1 + + if total_users > limits.users: + frappe.throw(_("Sorry. You have reached the maximum user limit for your subscription. You can either disable an existing user or buy a higher subscription plan."), + MaxUsersReachedError) + @frappe.whitelist() def get_timezones(): import pytz diff --git a/frappe/core/page/usage_info/usage_info.js b/frappe/core/page/usage_info/usage_info.js index 36d45b3eff..f61f15bbbf 100644 --- a/frappe/core/page/usage_info/usage_info.js +++ b/frappe/core/page/usage_info/usage_info.js @@ -6,32 +6,24 @@ frappe.pages['usage-info'].on_page_load = function(wrapper) { }); frappe.call({ - method: "frappe.limits.get_usage_data", + method: "frappe.limits.get_usage_info", callback: function(r) { - var doc = r.message; - if(!doc.database_size) doc.database_size = 26; - if(!doc.files_size) doc.files_size = 1; - if(!doc.backup_size) doc.backup_size = 1; - - if(typeof doc.space_limit !== "undefined") - { - doc.max = flt(doc.space_limit * 1024); + var usage_info = r.message; + if (!usage_info) { + // nothing to show + // TODO improve this + return; } - doc.total = (doc.database_size + doc.files_size + doc.backup_size); - doc.users = keys(frappe.boot.user_info).length - 2; - doc.today = frappe.datetime.get_today() - doc.total_days = frappe.datetime.get_day_diff(doc.expiry, doc.creation) - doc.used_days = frappe.datetime.get_day_diff(doc.today, doc.creation) $(frappe.render_template("usage_info", doc)).appendTo(page.main); - var btn_text = doc.user_limit == 1 ? __("Upgrade") : __("Renew / Upgrade"); + var btn_text = usage_info.limits.users == 1 ? __("Upgrade") : __("Renew / Upgrade"); - if(doc.limits_upgrade_link) { - page.set_primary_action(btn_text, function() { - frappe.set_route("upgrade"); - }); - } + if(usage_info.upgrade_link) { + page.set_primary_action(btn_text, function() { + window.open(usage_info.upgrade_link); + }); + } } }); diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index c6f2e87a95..b045107ab8 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -421,7 +421,7 @@ function load_frappe_slides() { }, - frappe.wiz.user= { + frappe.wiz.user = { app_name: "frappe", title: __("The First User: You"), icon: "icon-user", @@ -465,5 +465,8 @@ frappe.wiz.on("before_load", function() { // add welcome slide frappe.wiz.add_slide(frappe.wiz.welcome); frappe.wiz.add_slide(frappe.wiz.region); - frappe.wiz.add_slide(frappe.wiz.user); + + if (!(frappe.boot.limits && frappe.boot.limits.users===1)) { + frappe.wiz.add_slide(frappe.wiz.user); + } }); diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 419648a0ef..2d3255a73a 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -163,10 +163,7 @@ def prettify_args(args): return pretty_args def email_setup_wizard_exception(traceback, args): - from frappe.limits import get_limits - frappe_limits = get_limits() - - if not frappe_limits.get('setup_wizard_exception_email'): + if not frappe.local.conf.setup_wizard_exception_email: return pretty_args = prettify_args(args) @@ -210,12 +207,8 @@ def email_setup_wizard_exception(traceback, args): headers=frappe.local.request.headers, accept_languages=", ".join(frappe.local.request.accept_languages.values())) - frappe.sendmail(recipients=frappe_limits.get('setup_wizard_exception_email'), + frappe.sendmail(recipients=frappe.local.conf.setup_wizard_exception_email, sender=frappe.session.user, subject="Exception in Setup Wizard - {}".format(frappe.local.site), message=message) - -def set_setup_complete(*args): - from frappe.limits import set_limits - set_limits({'setup_complete' : 1 , 'creation': frappe.utils.today()}) diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index ceab0518db..fdae50a9ea 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -4,12 +4,20 @@ from __future__ import unicode_literals import frappe - +from frappe import _ from frappe.model.document import Document from frappe.email.queue import send_one +from frappe.limits import get_limits class EmailQueue(Document): - pass + def on_trash(self): + self.prevent_email_queue_delete() + + def prevent_email_queue_delete(self): + '''If email limit is set, don't allow users to delete Email Queue record''' + if get_limits().emails and frappe.session.user != 'Administrator': + frappe.throw(_('Only Administrator can delete Email Queue')) + @frappe.whitelist() def retry_sending(name): diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 76681ba471..e4ca335098 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -147,15 +147,18 @@ def check_email_limit(recipients): or frappe.flags.in_test): # get count of mails sent this month - this_month = frappe.db.sql("""select count(name) from `tabEmail Queue` where - status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0] + this_month = get_emails_sent_this_month() - monthly_email_limit = frappe.conf.get('monthly_email_limit') or 500 + monthly_email_limit = frappe.conf.get('limits', {}).get('emails') or 500 if (this_month + len(recipients)) > monthly_email_limit: throw(_("Cannot send this email. You have crossed the sending limit of {0} emails for this month.").format(monthly_email_limit), EmailLimitCrossedError) +def get_emails_sent_this_month(): + return frappe.db.sql("""select count(name) from `tabEmail Queue` where + status='Sent' and MONTH(creation)=MONTH(CURDATE())""")[0][0] + def get_unsubscribe_link(reference_doctype, reference_name, email, recipients, expose_recipients, show_as_cc, unsubscribe_method, unsubscribe_params, unsubscribe_message): @@ -334,8 +337,3 @@ def clear_outbox(): """Remove mails older than 31 days in Outbox. Called daily via scheduler.""" frappe.db.sql("""delete from `tabEmail Queue` where datediff(now(), creation) > 31""") - -def prevent_email_queue_delete(doc, method): - from frappe.limits import get_limits - if frappe.session.user != 'Administrator' and get_limits().get('block_bulk_email_delete'): - frappe.throw(_('Only Administrator can delete Email Queue')) \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index cda7deb5da..9ff4d59160 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -14,7 +14,6 @@ app_email = "info@frappe.io" before_install = "frappe.utils.install.before_install" after_install = "frappe.utils.install.after_install" -extend_bootinfo = "frappe.limits.load_limits" page_js = { "setup-wizard": "public/js/frappe/setup_wizard.js" @@ -96,12 +95,6 @@ standard_queries = { } doc_events = { - "User": { - "validate": "frappe.utils.user.validate_user_limit" - }, - "Email Queue": { - "on_trash": "frappe.email.queue.prevent_email_queue_delete" - }, "*": { "after_insert": "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", "validate": "frappe.email.doctype.email_alert.email_alert.trigger_email_alerts", @@ -141,7 +134,7 @@ scheduler_events = { "frappe.async.remove_old_task_logs", "frappe.utils.scheduler.disable_scheduler_on_expiry", "frappe.utils.scheduler.restrict_scheduler_events_if_dormant", - "frappe.core.doctype.file.file.update_sizes" + "frappe.limits.update_space_usage" ], "daily_long": [ @@ -180,5 +173,4 @@ bot_parsers = [ ] setup_wizard_exception = "frappe.desk.page.setup_wizard.setup_wizard.email_setup_wizard_exception" -setup_wizard_success = "frappe.desk.page.setup_wizard.setup_wizard.set_setup_complete" -before_write_file = "frappe.core.doctype.file.file.validate_space_limit" +before_write_file = "frappe.limits.validate_space_limit" diff --git a/frappe/limits.py b/frappe/limits.py index 7ab60d35b8..5b9e508b9f 100755 --- a/frappe/limits.py +++ b/frappe/limits.py @@ -1,25 +1,34 @@ from __future__ import unicode_literals import frappe -from frappe.utils import now_datetime, getdate +from frappe import _ +from frappe.utils import now_datetime, getdate, flt, cint, get_fullname from frappe.installer import update_site_config from frappe.utils.data import formatdate -from frappe import _ +from frappe.utils.user import get_enabled_system_users +import os, subprocess, urlparse, urllib class SiteExpiredError(frappe.ValidationError): pass EXPIRY_WARNING_DAYS = 10 -def load_limits(bootinfo): - bootinfo["frappe_limits"] = get_limits() - bootinfo["expiry_message"] = get_expiry_message() +def check_if_expired(): + """check if account is expired. If expired, do not allow login""" + if not has_expired(): + return + # if expired, stop user from logging in + expires_on = formatdate(get_limits().get("expiry")) + support_email = get_limits().get("support_email") or _("your provider") + frappe.throw(_("""Your subscription expired on {0}. + To extend please send an email to {1}""").format(expires_on, support_email), + SiteExpiredError) def has_expired(): if frappe.session.user=="Administrator": return False - expires_on = get_limits().get("expiry") + expires_on = get_limits().expiry if not expires_on: return False @@ -28,18 +37,6 @@ def has_expired(): return True -def check_if_expired(): - """check if account is expired. If expired, do not allow login""" - if not has_expired(): - return - # if expired, stop user from logging in - expires_on = formatdate(get_limits().get("expiry")) - support_email = get_limits().get("support_email") or _("your provider") - - frappe.throw(_("""Your subscription expired on {0}. - To extend please send an email to {1}""").format(expires_on, support_email), - SiteExpiredError) - def get_expiry_message(): if "System Manager" not in frappe.get_roles(): return "" @@ -67,30 +64,147 @@ def get_expiry_message(): return message +@frappe.whitelist() +def get_usage_info(): + '''Get data to show for Usage Info''' + # imported here to prevent circular import + from frappe.email.queue import get_emails_sent_this_month + + limits = get_limits() + if not (limits and any([limits.users, limits.space, limits.emails, limits.expiry])): + # no limits! + return + + limits.space = limits.space * 1024.0 # to MB + if not limits.space_usage: + # hack! to show some progress + limits.space_usage = { + 'database_size': 26, + 'files_size': 1, + 'backup_size': 1, + 'total': 28 + } + + usage_info = frappe._dict({ + 'limits': limits, + 'enabled_users': get_enabled_system_users(), + 'emails_sent': get_emails_sent_this_month(), + 'space_usage': limits.space_usage['total'], + }) + + if limits.expiry: + usage_info['days_to_expiry'] = (getdate(limits.expiry) - getdate()).days + + if limits.upgrade_link: + usage_info['upgrade_link'] = get_upgrade_link(limits.upgrade_link) + + return usage_info + +def get_upgrade_link(upgrade_link): + parts = urlparse.urlsplit(upgrade_link) + params = dict(urlparse.parse_qsl(parts.query)) + params.update({ + 'site': frappe.local.site, + 'email': frappe.session.user, + 'fullname': get_fullname() + }) + + parts.query = urllib.urlencode(params) + url = urlparse.urlunparse(parts) + return url def get_limits(): - return frappe.get_conf().get("limits") or {} + ''' + "limits": { + "users": 1, + "space": 0.5, # in GB + "emails": 1000 # per month + "expiry": "2099-12-31" + } + ''' + return frappe._dict(frappe.local.conf.limits or {}) + +def update_limits(key, value): + '''Add/Update limit in site_config''' + limits = get_limits() + if isinstance(key, dict): + limits.update(key) + else: + limits[key] = value -@frappe.whitelist() -def get_usage_data(): + update_site_config("limits", limits, validate=False) + frappe.conf.limits = limits + +def clear_limit(key): + '''Remove a limit option from site_config''' limits = get_limits() - day = frappe.utils.add_months(frappe.utils.today(), -1) - limits["emails_sent"] = frappe.db.count("Email Queue", filters={'creation': ['>', day]}) - return limits + if key in limits: + del limits[key] + + update_site_config("limits", limits, validate=False) + frappe.conf.limits = limits + +def validate_space_limit(file_size): + """Stop from writing file if max space limit is reached""" + from frappe.utils.file_manager import MaxFileSizeReachedError + + limits = get_limits() + if not limits.space: + return + + # to MB + space_limit = flt(limits.space * 1024.0, 2) + + # in MB + usage = frappe._dict(limits.space_usage or {}) + if not usage: + # first time + usage = frappe._dict(update_space_usage()) + + file_size = file_size / (1024.0 ** 2) + + if flt(usage.total + file_size, 2) > space_limit: + # Stop from attaching file + frappe.throw(_("You have exceeded the max space of {0} for your plan. {1}.").format( + "{0}MB".format(cint(space_limit)) if (space_limit < 1024) else "{0}GB".format(limits.space), + '{0}'.format(_("Click here to check your usage or upgrade to a higher plan"))), + MaxFileSizeReachedError) + + # update files size in frappe subscription + usage.files_size = flt(usage.files_size) + file_size + update_limits({ 'space_usage': usage }) + +def update_space_usage(): + # public and private files + files_size = get_folder_size(frappe.get_site_path("public", "files")) + files_size += get_folder_size(frappe.get_site_path("private", "files")) + + backup_size = get_folder_size(frappe.get_site_path("private", "backups")) + database_size = get_database_size() + + usage = { + 'files_size': files_size, + 'backup_size': backup_size, + 'database_size': database_size, + 'total': flt(flt(files_size) + flt(backup_size) + flt(database_size), 2) + } + update_limits({ 'space_usage': usage }) -def set_limits(limits): - # Add/Update current config options in site_config - frappe_limits = get_limits() or {} - for key in limits.keys(): - frappe_limits[key] = limits[key] + return usage - update_site_config("limits", frappe_limits, validate=False) +def get_folder_size(path): + '''Returns folder size in MB if it exists''' + if os.path.exists(path): + return flt(subprocess.check_output(['du', '-ms', path]).split()[0]) +def get_database_size(): + '''Returns approximate database size in MB''' + db_name = frappe.conf.db_name -def clear_limit(limit): - frappe_limits = get_limits() - if limit in frappe_limits: - del frappe_limits[limit] + # This query will get the database size in MB + db_size = frappe.db.sql(''' + SELECT table_schema "database_name", sum( data_length + index_length ) / 1024 / 1024 "database_size" + FROM information_schema.TABLES WHERE table_schema = %s GROUP BY table_schema''', db_name, as_dict=True) - update_site_config("limits", frappe_limits, validate=False) + return db_size[0].get('database_size') diff --git a/frappe/public/js/frappe/setup_wizard.js b/frappe/public/js/frappe/setup_wizard.js deleted file mode 100755 index 66d6795c9b..0000000000 --- a/frappe/public/js/frappe/setup_wizard.js +++ /dev/null @@ -1,18 +0,0 @@ -frappe.wiz.on("after_load", function() { - if (frappe.boot.frappe_limits.user_limit===1) { - // remove users slide - var users_slide; - for (var i in frappe.wiz.wizard.slides) { - var slide = frappe.wiz.wizard.slides[i]; - if (slide.title === frappe.wiz.user.title) { - users_slide = i; - break; - } - } - - if (users_slide >= 0) { - frappe.wiz.wizard.slides.splice(users_slide, 1); - } - - } -}); diff --git a/frappe/public/js/frappe/toolbar.js b/frappe/public/js/frappe/toolbar.js index d034052563..a032571d27 100755 --- a/frappe/public/js/frappe/toolbar.js +++ b/frappe/public/js/frappe/toolbar.js @@ -5,51 +5,56 @@ $(document).on("toolbar_setup", function() { var help_links = []; var support_link = "#upgrade"; var chat_link = "#upgrade"; - frappe_limits = frappe.boot.frappe_limits + var limits = frappe.boot.limits; if(frappe.boot.expiry_message) { frappe.msgprint(frappe.boot.expiry_message) } - if(frappe_limits.support_email || frappe_limits.support_chat) { + if(limits.support_email || limits.support_chat) { help_links.push('
  • '); } - if(frappe_limits.support_email) { - support_link = 'mailto:'+frappe.boot.frappe_limits.support_email; + if(limits.support_email) { + support_link = 'mailto:'+frappe.boot.limits.support_email; help_links.push('
  • ' + frappe._('Email Support') + '
  • '); - } - - if(frappe_limits.support_chat) { - chat_link = frappe_limits.support_chat; - help_links.push('
  • ' + frappe._('Chat Support') + '
  • '); } + if(limits.support_chat) { + help_links.push('
  • ' + frappe._('Chat Support') + '
  • '); + } $(help_links.join("\n")).insertBefore($("#toolbar-user").find(".divider:last")); - if(frappe_limits.space_limit || frappe_limits.user_limit || frappe_limits.expiry || frappe_limits.email_limit) - { - help_links = []; - help_links.push('
  • ' + frappe._('Usage Info') + '
  • '); - help_links.push('
  • '); + if(limits.space || limits.users || limits.expiry || limits.emails) { + help_links = []; + help_links.push('
  • ' + frappe._('Usage Info') + '
  • '); + help_links.push('
  • '); + $(help_links.join("\n")).insertBefore($("#toolbar-user").find("li:first")); } - $(help_links.join("\n")).insertBefore($("#toolbar-user").find("li:first")); }); frappe.get_form_sidebar_extension = function() { - var fs = frappe.boot.frappe_limits; - if(!fs.sidebar_usage_html) { - fs.total_used = flt(fs.database_size + fs.backup_size + fs.files_size); - fs.total_used_percent = cint(fs.total_used / flt(fs.max_space * 1024) * 100); - - var template = ''; - fs.sidebar_usage_html = frappe.render(template, {fs:fs}, "form_sidebar_usage"); + var limits = frappe.boot.limits; + if (!limits.usage) { limits.usage = {}; } + var usage = limits.usage; + + if(!usage.sidebar_usage_html) { + if (limits.space) { + usage.total_used = flt(usage.database_size) + flt(usage.backup_size) + flt(usage.files_size); + usage.total_used_percent = cint(usage.total_used / flt(limits.space * 1024) * 100); + + var template = ''; + usage.sidebar_usage_html = frappe.render(template, { 'usage': usage }, "form_sidebar_usage"); + + } else { + usage.sidebar_usage_html = ''; + } } - return fs.sidebar_usage_html; + return usage.sidebar_usage_html; } diff --git a/frappe/tests/test_scheduler.py b/frappe/tests/test_scheduler.py index 06a60c3b7c..7f0cdc3397 100644 --- a/frappe/tests/test_scheduler.py +++ b/frappe/tests/test_scheduler.py @@ -7,7 +7,7 @@ from frappe.utils.scheduler import (enqueue_applicable_events, restrict_schedule from frappe import _dict from frappe.utils.background_jobs import enqueue from frappe.utils import now_datetime, today, add_days, add_to_date -from frappe.limits import set_limits, clear_limit +from frappe.limits import update_limits, clear_limit import frappe import json, time @@ -56,7 +56,7 @@ class TestScheduler(TestCase): def test_restrict_scheduler_events(self): frappe.set_user("Administrator") user = frappe.get_doc("User", "Administrator") - dormant_date = add_days(today(), -5) + dormant_date = add_days(today(), -5) user.last_active = dormant_date user.save() @@ -69,7 +69,7 @@ class TestScheduler(TestCase): def test_disable_scheduler_on_expiry(self): - set_limits({'expiry': add_to_date(today(), days=-1)}) + update_limits({'expiry': add_to_date(today(), days=-1)}) frappe.local.conf = _dict(frappe.get_site_config()) user = frappe.new_doc('User') diff --git a/frappe/utils/user.py b/frappe/utils/user.py index 3ef3c012e3..09de44a931 100755 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -8,8 +8,6 @@ from frappe import _dict import frappe.share from frappe import _ -class MaxUsersReachedError(frappe.ValidationError): pass - class UserPermissions: """ A user permission object can be accessed as `frappe.get_user()` @@ -307,36 +305,3 @@ def get_users(): }) return users - - -def validate_user_limit(doc, method): - """ - This is called using validate hook, because welcome email is sent in on_update. - We don't want welcome email sent if max users are exceeded. - """ - from frappe.limits import get_limits - from frappe.core.doctype.user.user import get_total_users - frappe_limits = get_limits() - - if doc.user_type == "Website User": - return - - if not doc.enabled: - # don't validate max users when saving a disabled user - return - - user_limit = frappe_limits.get("user_limit") if frappe_limits else None - - if not user_limit: - return - - total_users = get_total_users() - - if doc.is_new(): - # get_total_users gets existing users in database - # a new record isn't inserted yet, so adding 1 - total_users += 1 - - if total_users > user_limit: - frappe.throw(_("Sorry. You have reached the maximum user limit for your subscription. You can either disable an existing user or buy a higher subscription plan."), - MaxUsersReachedError) diff --git a/frappe/website/render.py b/frappe/website/render.py index 448ed9a8c8..b8fab78920 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -56,7 +56,7 @@ def render(path, http_status_code=None): except frappe.Redirect, e: return build_response(path, "", 301, { - "Location": frappe.flags.redirect_location, + "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), "Cache-Control": "no-store, no-cache, must-revalidate" })