Procházet zdrojové kódy

-webnotes +frappe 💥

version-14
Rushabh Mehta před 11 roky
rodič
revize
024ddfcc5b
100 změnil soubory, kde provedl 4446 přidání a 19 odebrání
  1. +19
    -19
      MANIFEST.in
  2. +0
    -0
      frappe/.no_timestamps
  3. +607
    -0
      frappe/__init__.py
  4. +97
    -0
      frappe/api.py
  5. +102
    -0
      frappe/app.py
  6. +262
    -0
      frappe/auth.py
  7. +139
    -0
      frappe/boot.py
  8. +138
    -0
      frappe/build.py
  9. +823
    -0
      frappe/cli.py
  10. +138
    -0
      frappe/client.py
  11. +0
    -0
      frappe/config/__init__.py
  12. +45
    -0
      frappe/config/desktop.py
  13. +145
    -0
      frappe/config/setup.py
  14. +0
    -0
      frappe/core/.no_timestamps
  15. +0
    -0
      frappe/core/README.md
  16. +0
    -0
      frappe/core/__init__.py
  17. +0
    -0
      frappe/core/doctype/__init__.py
  18. +0
    -0
      frappe/core/doctype/bulk_email/README.md
  19. +0
    -0
      frappe/core/doctype/bulk_email/__init__.py
  20. +9
    -0
      frappe/core/doctype/bulk_email/bulk_email.py
  21. +0
    -0
      frappe/core/doctype/bulk_email/bulk_email.txt
  22. +0
    -0
      frappe/core/doctype/comment/README.md
  23. +0
    -0
      frappe/core/doctype/comment/__init__.py
  24. +70
    -0
      frappe/core/doctype/comment/comment.py
  25. +0
    -0
      frappe/core/doctype/comment/comment.txt
  26. +52
    -0
      frappe/core/doctype/comment/test_comment.py
  27. +0
    -0
      frappe/core/doctype/communication/README.md
  28. +0
    -0
      frappe/core/doctype/communication/__init__.py
  29. +10
    -0
      frappe/core/doctype/communication/communication.js
  30. +159
    -0
      frappe/core/doctype/communication/communication.py
  31. +0
    -0
      frappe/core/doctype/communication/communication.txt
  32. +0
    -0
      frappe/core/doctype/control_panel/README.md
  33. +0
    -0
      frappe/core/doctype/control_panel/__init__.py
  34. +18
    -0
      frappe/core/doctype/control_panel/control_panel.py
  35. +0
    -0
      frappe/core/doctype/control_panel/control_panel.txt
  36. +0
    -0
      frappe/core/doctype/custom_field/README.md
  37. +0
    -0
      frappe/core/doctype/custom_field/__init__.py
  38. +73
    -0
      frappe/core/doctype/custom_field/custom_field.js
  39. +127
    -0
      frappe/core/doctype/custom_field/custom_field.py
  40. +0
    -0
      frappe/core/doctype/custom_field/custom_field.txt
  41. +0
    -0
      frappe/core/doctype/custom_script/README.md
  42. +0
    -0
      frappe/core/doctype/custom_script/__init__.py
  43. +19
    -0
      frappe/core/doctype/custom_script/custom_script.py
  44. +0
    -0
      frappe/core/doctype/custom_script/custom_script.txt
  45. +0
    -0
      frappe/core/doctype/customize_form/README.md
  46. +0
    -0
      frappe/core/doctype/customize_form/__init__.py
  47. +241
    -0
      frappe/core/doctype/customize_form/customize_form.js
  48. +351
    -0
      frappe/core/doctype/customize_form/customize_form.py
  49. +0
    -0
      frappe/core/doctype/customize_form/customize_form.txt
  50. +0
    -0
      frappe/core/doctype/customize_form_field/__init__.py
  51. +9
    -0
      frappe/core/doctype/customize_form_field/customize_form_field.py
  52. +0
    -0
      frappe/core/doctype/customize_form_field/customize_form_field.txt
  53. +0
    -0
      frappe/core/doctype/default_home_page/__init__.py
  54. +9
    -0
      frappe/core/doctype/default_home_page/default_home_page.py
  55. +0
    -0
      frappe/core/doctype/default_home_page/default_home_page.txt
  56. +0
    -0
      frappe/core/doctype/defaultvalue/README.md
  57. +0
    -0
      frappe/core/doctype/defaultvalue/__init__.py
  58. +22
    -0
      frappe/core/doctype/defaultvalue/defaultvalue.py
  59. +0
    -0
      frappe/core/doctype/defaultvalue/defaultvalue.txt
  60. +0
    -0
      frappe/core/doctype/docfield/README.md
  61. +0
    -0
      frappe/core/doctype/docfield/__init__.py
  62. +9
    -0
      frappe/core/doctype/docfield/docfield.py
  63. +0
    -0
      frappe/core/doctype/docfield/docfield.txt
  64. +0
    -0
      frappe/core/doctype/docperm/README.md
  65. +0
    -0
      frappe/core/doctype/docperm/__init__.py
  66. +9
    -0
      frappe/core/doctype/docperm/docperm.py
  67. +0
    -0
      frappe/core/doctype/docperm/docperm.txt
  68. +0
    -0
      frappe/core/doctype/doctype/README.md
  69. +0
    -0
      frappe/core/doctype/doctype/__init__.py
  70. +0
    -0
      frappe/core/doctype/doctype/doctype.js
  71. +350
    -0
      frappe/core/doctype/doctype/doctype.py
  72. +0
    -0
      frappe/core/doctype/doctype/doctype.txt
  73. +11
    -0
      frappe/core/doctype/doctype/doctype_template.py
  74. +0
    -0
      frappe/core/doctype/event/README.md
  75. +0
    -0
      frappe/core/doctype/event/__init__.py
  76. +0
    -0
      frappe/core/doctype/event/event.js
  77. +185
    -0
      frappe/core/doctype/event/event.py
  78. +0
    -0
      frappe/core/doctype/event/event.txt
  79. +15
    -0
      frappe/core/doctype/event/event_calendar.js
  80. +67
    -0
      frappe/core/doctype/event/test_event.py
  81. +0
    -0
      frappe/core/doctype/event_role/README.md
  82. +0
    -0
      frappe/core/doctype/event_role/__init__.py
  83. +9
    -0
      frappe/core/doctype/event_role/event_role.py
  84. +0
    -0
      frappe/core/doctype/event_role/event_role.txt
  85. +0
    -0
      frappe/core/doctype/event_user/README.md
  86. +0
    -0
      frappe/core/doctype/event_user/__init__.py
  87. +9
    -0
      frappe/core/doctype/event_user/event_user.py
  88. +0
    -0
      frappe/core/doctype/event_user/event_user.txt
  89. +0
    -0
      frappe/core/doctype/file_data/README.md
  90. +0
    -0
      frappe/core/doctype/file_data/__init__.py
  91. +57
    -0
      frappe/core/doctype/file_data/file_data.py
  92. +0
    -0
      frappe/core/doctype/file_data/file_data.txt
  93. +0
    -0
      frappe/core/doctype/letter_head/README.md
  94. +0
    -0
      frappe/core/doctype/letter_head/__init__.py
  95. +11
    -0
      frappe/core/doctype/letter_head/letter_head.js
  96. +30
    -0
      frappe/core/doctype/letter_head/letter_head.py
  97. +0
    -0
      frappe/core/doctype/letter_head/letter_head.txt
  98. +0
    -0
      frappe/core/doctype/letter_head/test_letter_head.py
  99. +0
    -0
      frappe/core/doctype/module_def/README.md
  100. +0
    -0
      frappe/core/doctype/module_def/__init__.py

+ 19
- 19
MANIFEST.in Zobrazit soubor

@@ -4,23 +4,23 @@ include *.json
include *.md
include *.py
include *.txt
recursive-include webnotes *.css
recursive-include webnotes *.dat
recursive-include webnotes *.eot
recursive-include webnotes *.gif
recursive-include webnotes *.html
recursive-include webnotes *.jpg
recursive-include webnotes *.js
recursive-include webnotes *.json
recursive-include webnotes *.md
recursive-include webnotes *.otf
recursive-include webnotes *.png
recursive-include webnotes *.py
recursive-include webnotes *.sql
recursive-include webnotes *.svg
recursive-include webnotes *.swf
recursive-include webnotes *.ttf
recursive-include webnotes *.woff
recursive-include webnotes *.xml
recursive-include webnotes *.txt
recursive-include frappe *.css
recursive-include frappe *.dat
recursive-include frappe *.eot
recursive-include frappe *.gif
recursive-include frappe *.html
recursive-include frappe *.jpg
recursive-include frappe *.js
recursive-include frappe *.json
recursive-include frappe *.md
recursive-include frappe *.otf
recursive-include frappe *.png
recursive-include frappe *.py
recursive-include frappe *.sql
recursive-include frappe *.svg
recursive-include frappe *.swf
recursive-include frappe *.ttf
recursive-include frappe *.woff
recursive-include frappe *.xml
recursive-include frappe *.txt
recursive-exclude * *.pyc

webnotes/.no_timestamps → frappe/.no_timestamps Zobrazit soubor


+ 607
- 0
frappe/__init__.py Zobrazit soubor

@@ -0,0 +1,607 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
"""
globals attached to frappe module
+ some utility functions that should probably be moved
"""

from __future__ import unicode_literals

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

import os, sys, importlib, inspect
import json
import semantic_version

from frappe.core.doctype.print_format.print_format import get_html as get_print_html

local = Local()

class _dict(dict):
"""dict like object that exposes keys as attributes"""
def __getattr__(self, key):
ret = self.get(key)
if not ret and key.startswith("__"):
raise AttributeError()
return ret
def __setattr__(self, key, value):
self[key] = value
def __getstate__(self):
return self
def __setstate__(self, d):
self.update(d)
def update(self, d):
"""update and return self -- the missing dict feature in python"""
super(_dict, self).update(d)
return self
def copy(self):
return _dict(dict(self).copy())

def __getattr__(self, key):
return local.get("key", None)

def _(msg):
"""translate object in current lang, if exists"""
if local.lang == "en":
return msg

from frappe.translate import get_full_dict
return get_full_dict(local.lang).get(msg, msg)

def get_lang_dict(fortype, name=None):
if local.lang=="en":
return {}
from frappe.translate import get_dict
return get_dict(fortype, name)
def set_user_lang(user, user_language=None):
from frappe.translate import get_lang_dict
if not user_language:
user_language = conn.get_value("Profile", user, "language")
if user_language:
lang_dict = get_lang_dict()
if user_language in lang_dict:
local.lang = lang_dict[user_language]

# local-globals
conn = local("conn")
conf = local("conf")
form = form_dict = local("form_dict")
request = local("request")
request_method = local("request_method")
response = local("response")
_response = local("_response")
session = local("session")
user = local("user")
flags = local("flags")
restrictions = local("restrictions")

error_log = local("error_log")
debug_log = local("debug_log")
message_log = local("message_log")

lang = local("lang")
def init(site, sites_path=None):
if getattr(local, "initialised", None):
return

if not sites_path:
sites_path = '.'
local.error_log = []
local.site = site
local.sites_path = sites_path
local.site_path = os.path.join(sites_path, site)
local.message_log = []
local.debug_log = []
local.response = _dict({})
local.lang = "en"
local.request_method = request.method if request else None
local.conf = _dict(get_site_config())
local.initialised = True
local.flags = _dict({})
local.rollback_observers = []
local.module_app = None
local.app_modules = None
local.user = None
local.restrictions = None
local.user_perms = {}
local.test_objects = {}
local.jenv = None
local.jloader =None

setup_module_map()

def get_site_config():
config = {}
sites_config_filepath = os.path.join(local.sites_path, "site_config.json")
site_config_filepath = os.path.join(local.site_path, "site_config.json")
if os.path.exists(sites_config_filepath):
config = get_file_json(sites_config_filepath)
if os.path.exists(site_config_filepath):
config.update(get_file_json(site_config_filepath))
return _dict(config)

def destroy():
"""closes connection and releases werkzeug local"""
if conn:
conn.close()
release_local(local)

_memc = None

# memcache
def cache():
global _memc
if not _memc:
from frappe.memc import MClient
_memc = MClient(['localhost:11211'])
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():
import utils
return utils.get_traceback()

def errprint(msg):
from utils import cstr
if not request:
print cstr(msg)
error_log.append(cstr(msg))

def log(msg):
if not request:
if conf.get("logging") or False:
print repr(msg)
from utils import cstr
debug_log.append(cstr(msg))

def msgprint(msg, small=0, raise_exception=0, as_table=False):
def _raise_exception():
if raise_exception:
if flags.rollback_on_exception:
conn.rollback()
import inspect
if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
raise raise_exception, msg
else:
raise ValidationError, msg
if flags.mute_messages:
_raise_exception()
return
from utils import cstr
if as_table and type(msg) in (list, tuple):
msg = '<table border="1px" style="border-collapse: collapse" cellpadding="2px">' + ''.join(['<tr>'+''.join(['<td>%s</td>' % c for c in r])+'</tr>' for r in msg]) + '</table>'
if flags.print_messages:
print "Message: " + repr(msg)
message_log.append((small and '__small:' or '')+cstr(msg or ''))
_raise_exception()

def throw(msg, exc=ValidationError):
msgprint(msg, raise_exception=exc)

def create_folder(path):
if not os.path.exists(path): os.makedirs(path)
def connect(site=None, db_name=None):
from db import Database
if site:
init(site)
local.conn = Database(user=db_name or local.conf.db_name)
local.response = _dict()
local.form_dict = _dict()
local.session = _dict()
set_user("Administrator")

def set_user(username):
import frappe.profile
local.session["user"] = username
local.user = frappe.profile.Profile(username)
local.restrictions = None
local.user_perms = {}

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

def sendmail(recipients=[], sender="", subject="No Subject", message="No Message", as_markdown=False):
import frappe.utils.email_lib
if as_markdown:
frappe.utils.email_lib.sendmail_md(recipients, sender=sender, subject=subject, msg=message)
else:
frappe.utils.email_lib.sendmail(recipients, sender=sender, subject=subject, msg=message)

logger = None
whitelisted = []
guest_methods = []
def whitelist(allow_guest=False):
"""
decorator for whitelisting a function
Note: if the function is allowed to be accessed by a guest user,
it must explicitly be marked as allow_guest=True
for specific roles, set allow_roles = ['Administrator'] etc.
"""
def innerfn(fn):
global whitelisted, guest_methods
whitelisted.append(fn)
if allow_guest:
guest_methods.append(fn)

return fn
return innerfn
def only_for(roles):
if not isinstance(roles, (tuple, list)):
roles = (roles,)
roles = set(roles)
myroles = set(get_roles())
if not roles.intersection(myroles):
raise PermissionError
def clear_cache(user=None, doctype=None):
"""clear cache"""
import frappe.sessions
if doctype:
import frappe.model.doctype
frappe.model.doctype.clear_cache(doctype)
reset_metadata_version()
elif user:
frappe.sessions.clear_cache(user)
else: # everything
import translate
frappe.sessions.clear_cache()
translate.clear_cache()
reset_metadata_version()

def get_roles(username=None):
import frappe.profile
if not local.session:
return ["Guest"]
elif not username or username==local.session.user:
return local.user.get_roles()
else:
return frappe.profile.Profile(username).get_roles()
def has_permission(doctype, ptype="read", refdoc=None):
import frappe.permissions
return frappe.permissions.has_permission(doctype, ptype, refdoc)
def clear_perms(doctype):
conn.sql("""delete from tabDocPerm where parent=%s""", doctype)

def reset_perms(doctype):
clear_perms(doctype)
reload_doc(conn.get_value("DocType", doctype, "module"),
"DocType", doctype, force=True)

def generate_hash(txt=None):
"""Generates random hash for session id"""
import hashlib, time
return hashlib.sha224((txt or "") + str(time.time())).hexdigest()

def reset_metadata_version():
v = generate_hash()
cache().set_value("metadata_version", v)
return v

def get_obj(dt = None, dn = None, doc=None, doclist=None, with_children = True):
from frappe.model.code import get_obj
return get_obj(dt, dn, doc, doclist, with_children)

def doc(doctype=None, name=None, fielddata=None):
from frappe.model.doc import Document
return Document(doctype, name, fielddata)

def new_doc(doctype, parent_doc=None, parentfield=None):
from frappe.model.create_new import get_new_doc
return get_new_doc(doctype, parent_doc, parentfield)

def new_bean(doctype):
from frappe.model.create_new import get_new_doc
return bean([get_new_doc(doctype)])

def doclist(lst=None):
from frappe.model.doclist import DocList
return DocList(lst)

def bean(doctype=None, name=None, copy=None):
"""return an instance of the object, wrapped as a Bean (frappe.model.bean)"""
from frappe.model.bean import Bean
if copy:
return Bean(copy_doclist(copy))
else:
return Bean(doctype, name)

def set_value(doctype, docname, fieldname, value):
import frappe.client
return frappe.client.set_value(doctype, docname, fieldname, value)

def get_doclist(doctype, name=None):
return bean(doctype, name).doclist

def get_doctype(doctype, processed=False):
import frappe.model.doctype
return frappe.model.doctype.get(doctype, processed)

def delete_doc(doctype=None, name=None, doclist = None, force=0, ignore_doctypes=None,
for_reload=False, ignore_permissions=False):
import frappe.model.delete_doc
if not ignore_doctypes:
ignore_doctypes = []
if isinstance(name, list):
for n in name:
frappe.model.delete_doc.delete_doc(doctype, n, doclist, force, ignore_doctypes,
for_reload, ignore_permissions)
else:
frappe.model.delete_doc.delete_doc(doctype, name, doclist, force, ignore_doctypes,
for_reload, ignore_permissions)

def reload_doc(module, dt=None, dn=None, force=False):
import frappe.modules
return frappe.modules.reload_doc(module, dt, dn, force=force)

def rename_doc(doctype, old, new, debug=0, force=False, merge=False, ignore_permissions=False):
from frappe.model.rename_doc import rename_doc
return rename_doc(doctype, old, new, force=force, merge=merge, ignore_permissions=ignore_permissions)

def insert(doclist):
import frappe.model
return frappe.model.insert(doclist)

def get_module(modulename):
return importlib.import_module(modulename)

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

def get_module_path(module, *joins):
module = scrub(module)
return get_pymodule_path(local.module_app[module] + "." + module, *joins)

def get_app_path(app_name, *joins):
return get_pymodule_path(app_name, *joins)

def get_pymodule_path(modulename, *joins):
joins = [scrub(part) for part in joins]
return os.path.join(os.path.dirname(get_module(scrub(modulename)).__file__), *joins)
def get_module_list(app_name):
return get_file_items(os.path.join(os.path.dirname(get_module(app_name).__file__), "modules.txt"))

def get_all_apps(with_frappe=False):
apps = get_file_items(os.path.join(local.sites_path, "apps.txt")) \
+ get_file_items(os.path.join(local.site_path, "apps.txt"))
if with_frappe:
apps.insert(0, 'frappe')
return apps

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

def get_hooks(hook=None, app_name=None):
def load_app_hooks(app_name=None):
hooks = {}
for app in [app_name] if app_name else get_installed_apps():
for item in get_file_items(get_pymodule_path(app, "hooks.txt")):
key, value = item.split("=", 1)
key, value = key.strip(), value.strip()
hooks.setdefault(key, [])
hooks[key].append(value)
return hooks
if app_name:
hooks = _dict(load_app_hooks(app_name))
else:
hooks = _dict(cache().get_value("app_hooks", load_app_hooks))
if hook:
return hooks.get(hook) or []
else:
return hooks

def setup_module_map():
_cache = cache()
if conf.db_name:
local.app_modules = _cache.get_value("app_modules")
local.module_app = _cache.get_value("module_app")
if not local.app_modules:
local.module_app, local.app_modules = {}, {}
for app in get_all_apps(True):
local.app_modules.setdefault(app, [])
for module in get_module_list(app):
local.module_app[module] = app
local.app_modules[app].append(module)
if conf.db_name:
_cache.set_value("app_modules", local.app_modules)
_cache.set_value("module_app", local.module_app)
def get_file_items(path):
content = read_file(path)
if content:
return [p.strip() for p in content.splitlines() if p.strip() and not p.startswith("#")]
else:
return []

def get_file_json(path):
with open(path, 'r') as f:
return json.load(f)

def read_file(path):
if os.path.exists(path):
with open(path, "r") as f:
return unicode(f.read(), encoding="utf-8")
else:
return None

def get_attr(method_string):
modulename = '.'.join(method_string.split('.')[:-1])
methodname = method_string.split('.')[-1]
return getattr(get_module(modulename), methodname)
def call(fn, *args, **kwargs):
if hasattr(fn, 'fnargs'):
fnargs = fn.fnargs
else:
fnargs, varargs, varkw, defaults = inspect.getargspec(fn)

newargs = {}
for a in fnargs:
if a in kwargs:
newargs[a] = kwargs.get(a)
return fn(*args, **newargs)

def make_property_setter(args):
args = _dict(args)
bean([{
'doctype': "Property Setter",
'doctype_or_field': args.doctype_or_field or "DocField",
'doc_type': args.doctype,
'field_name': args.fieldname,
'property': args.property,
'value': args.value,
'property_type': args.property_type or "Data",
'__islocal': 1
}]).save()

def get_application_home_page(user='Guest'):
"""get home page for user"""
hpl = conn.sql("""select home_page
from `tabDefault Home Page`
where parent='Control Panel'
and role in ('%s') order by idx asc limit 1""" % "', '".join(get_roles(user)))
if hpl:
return hpl[0][0]
else:
return conn.get_value("Control Panel", None, "home_page")

def copy_doclist(in_doclist):
new_doclist = []
parent_doc = None
for i, d in enumerate(in_doclist):
is_dict = False
if isinstance(d, dict):
is_dict = True
values = _dict(d.copy())
else:
values = _dict(d.fields.copy())
newd = new_doc(values.doctype, parent_doc=(None if i==0 else parent_doc), parentfield=values.parentfield)
newd.fields.update(values)
if i==0:
parent_doc = newd
new_doclist.append(newd.fields if is_dict else newd)
return doclist(new_doclist)

def compare(val1, condition, val2):
import frappe.utils
return frappe.utils.compare(val1, condition, val2)

def repsond_as_web_page(title, html):
local.message_title = title
local.message = "<h3>" + title + "</h3>" + html
local.response['type'] = 'page'
local.response['page_name'] = 'message.html'
return obj

def build_match_conditions(doctype, fields=None, as_condition=True):
import frappe.widgets.reportview
return frappe.widgets.reportview.build_match_conditions(doctype, fields, as_condition)

def get_list(doctype, filters=None, fields=None, docstatus=None,
group_by=None, order_by=None, limit_start=0, limit_page_length=None,
as_list=False, debug=False):
import frappe.widgets.reportview
return frappe.widgets.reportview.execute(doctype, filters=filters, fields=fields, docstatus=docstatus,
group_by=group_by, order_by=order_by, limit_start=limit_start, limit_page_length=limit_page_length,
as_list=as_list, debug=debug)

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

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

jenv.globals.update({
"frappe": sys.modules[__name__],
"frappe.utils": frappe.utils,
"_": _
})
local.jenv = jenv
return local.jenv
def get_jloader():
if not local.jloader:
from jinja2 import ChoiceLoader, PackageLoader

apps = get_installed_apps()
apps.remove("frappe")
local.jloader = ChoiceLoader([PackageLoader(app, ".") \
for app in apps + ["frappe"]])
return local.jloader
def set_filters(jenv):
from frappe.utils import global_date_format
from frappe.website.utils import get_hex_shade
from markdown2 import markdown
from json import dumps
jenv.filters["global_date_format"] = global_date_format
jenv.filters["markdown"] = markdown
jenv.filters["json"] = dumps
jenv.filters["get_hex_shade"] = get_hex_shade
# load jenv_filters from hooks.txt
for app in get_all_apps(True):
for jenv_filter in (get_hooks(app_name=app).jenv_filter or []):
filter_name, filter_function = jenv_filter.split(":")
jenv.filters[filter_name] = get_attr(filter_function)

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

+ 97
- 0
frappe/api.py Zobrazit soubor

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

import frappe
import frappe.handler
import frappe.client
import frappe.widgets.reportview
from frappe.utils.response import build_response, report_error

def handle():
"""
/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
"""
parts = frappe.request.path[1:].split("/")
call = doctype = name = None
if len(parts) > 1:
call = parts[1]
if len(parts) > 2:
doctype = parts[2]

if len(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 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.conn.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.conn.commit()
if frappe.local.request.method=="PUT":
frappe.local.response.update({
"doclist":frappe.client.save(frappe.local.form_dict.doclist)})
frappe.conn.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)
build_response()

+ 102
- 0
frappe/app.py Zobrazit soubor

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

import sys, os
import json

from werkzeug.wrappers import Request, Response
from werkzeug.local import LocalManager
from werkzeug.wsgi import SharedDataMiddleware
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.contrib.profiler import ProfilerMiddleware

import mimetypes
import frappe
import frappe.handler
import frappe.auth
import frappe.api
import frappe.website.render
from frappe.utils import get_site_name

local_manager = LocalManager([frappe.local])

_site = None

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
def application(request):
frappe.local.request = request
try:
site = _site or get_site_name(request.host)
frappe.init(site=site)
if not frappe.local.conf:
# site does not exist
raise NotFound
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:
frappe.handler.handle()
elif frappe.request.path.startswith("/api/"):
frappe.api.handle()
elif frappe.local.request.method in ('GET', 'HEAD'):
frappe.website.render.render(frappe.request.path[1:])
else:
raise NotFound

except HTTPException, e:
return e
except frappe.AuthenticationError, e:
frappe._response.status_code=401
except frappe.SessionStopped, e:
frappe.local._response = handle_session_stopped()
finally:
if frappe.conn:
frappe.conn.close()
return frappe.local._response

application = local_manager.make_middleware(application)

def serve(port=8000, profile=False, site=None):
global application, _site
_site = site
from werkzeug.serving import run_simple

if profile:
application = ProfilerMiddleware(application)

if not os.environ.get('NO_STATICS'):
application = SharedDataMiddleware(application, {
'/assets': 'assets',
})
if site:
application = SharedDataMiddleware(application, {
'/files': os.path.join(site, 'public', 'files')
})

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

+ 262
- 0
frappe/auth.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe
import frappe.db
import frappe.utils
import frappe.profile
from frappe import conf
from frappe.sessions import Session


class HTTPRequest:
def __init__(self):
# Get Environment variables
self.domain = frappe.request.host
if self.domain and self.domain.startswith('www.'):
self.domain = self.domain[4:]

# language
self.set_lang(frappe.get_request_header('HTTP_ACCEPT_LANGUAGE'))
# load cookies
frappe.local.cookie_manager = CookieManager()
# override request method. All request to be of type POST, but if _type == "POST" then commit
if frappe.form_dict.get("_type"):
frappe.local.request_method = frappe.form_dict.get("_type")
del frappe.form_dict["_type"]

# set db
self.connect()

# login
frappe.local.login_manager = LoginManager()

# check status
if frappe.conn.get_global("__session_status")=='stop':
frappe.msgprint(frappe.conn.get_global("__session_status_message"))
raise frappe.SessionStopped('Session Stopped')

# load profile
self.setup_profile()

# run login triggers
if frappe.form_dict.get('cmd')=='login':
frappe.local.login_manager.run_trigger('on_session_creation')

def set_lang(self, lang):
import translate
lang_list = translate.get_all_languages() or []

if not lang:
return
if ";" in lang: # not considering weightage
lang = lang.split(";")[0]
if "," in lang:
lang = lang.split(",")
else:
lang = [lang]
for l in lang:
code = l.strip()
if code in lang_list:
frappe.local.lang = code
return
# check if parent language (pt) is setup, if variant (pt-BR)
if "-" in code:
code = code.split("-")[0]
if code in lang_list:
frappe.local.lang = code
return
def setup_profile(self):
frappe.local.user = frappe.profile.Profile()

def get_db_name(self):
"""get database name from conf"""
return conf.db_name

def connect(self, ac_name = None):
"""connect to db, from ac_name or db_name"""
frappe.local.conn = frappe.db.Database(user = self.get_db_name(), \
password = getattr(conf,'db_password', ''))

class LoginManager:
def __init__(self):
self.user = None
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login":
self.login()
else:
self.make_session(resume=True)

def login(self):
# clear cache
frappe.clear_cache(user = frappe.form_dict.get('usr'))
self.authenticate()
self.post_login()

def post_login(self):
self.run_trigger('on_login')
self.validate_ip_address()
self.validate_hour()
self.make_session()
self.set_user_info()
def set_user_info(self):
info = frappe.conn.get_value("Profile", self.user,
["user_type", "first_name", "last_name", "user_image"], as_dict=1)
if info.user_type=="Website User":
frappe.local._response.set_cookie("system_user", "no")
frappe.local.response["message"] = "No App"
else:
frappe.local._response.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._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 "")
def make_session(self, resume=False):
# start session
frappe.local.session_obj = Session(user=self.user, resume=resume)
# reset user if changed to Guest
self.user = frappe.local.session_obj.user
frappe.local.session = frappe.local.session_obj.data
def authenticate(self, user=None, pwd=None):
if not (user and pwd):
user, pwd = frappe.form_dict.get('usr'), frappe.form_dict.get('pwd')
if not (user and pwd):
self.fail('Incomplete login details')
self.check_if_enabled(user)
self.user = self.check_password(user, pwd)
def check_if_enabled(self, user):
"""raise exception if user not enabled"""
from frappe.utils import cint
if user=='Administrator': return
if not cint(frappe.conn.get_value('Profile', user, 'enabled')):
self.fail('User disabled or missing')

def check_password(self, user, pwd):
"""check password"""
user = frappe.conn.sql("""select `user` from __Auth where `user`=%s
and `password`=password(%s)""", (user, pwd))
if not user:
self.fail('Incorrect password')
else:
return user[0][0] # in correct case
def fail(self, message):
frappe.local.response['message'] = message
raise frappe.AuthenticationError
def run_trigger(self, method='on_login'):
for method in frappe.get_hooks().get("method", []):
frappe.get_attr(method)(self)
def validate_ip_address(self):
"""check if IP Address is valid"""
ip_list = frappe.conn.get_value('Profile', self.user, 'restrict_ip', ignore=True)
if not ip_list:
return

ip_list = ip_list.replace(",", "\n").split('\n')
ip_list = [i.strip() for i in ip_list]

for ip in ip_list:
if frappe.get_request_header('REMOTE_ADDR', '').startswith(ip) or frappe.get_request_header('X-Forwarded-For', '').startswith(ip):
return
frappe.msgprint('Not allowed from this IP Address')
raise frappe.AuthenticationError

def validate_hour(self):
"""check if user is logging in during restricted hours"""
login_before = int(frappe.conn.get_value('Profile', self.user, 'login_before', ignore=True) or 0)
login_after = int(frappe.conn.get_value('Profile', self.user, 'login_after', ignore=True) or 0)
if not (login_before or login_after):
return
from frappe.utils import now_datetime
current_hour = int(now_datetime().strftime('%H'))
if login_before and current_hour > login_before:
frappe.msgprint('Not allowed to login after restricted hour', raise_exception=1)

if login_after and current_hour < login_after:
frappe.msgprint('Not allowed to login before restricted hour', raise_exception=1)
def login_as_guest(self):
"""login as guest"""
self.user = 'Guest'
self.post_login()

def logout(self, arg='', user=None):
if not user: user = frappe.session.user
self.run_trigger('on_logout')
if user in ['demo@erpnext.com', 'Administrator']:
frappe.conn.sql('delete from tabSessions where sid=%s', frappe.session.get('sid'))
frappe.cache().delete_value("session:" + frappe.session.get("sid"))
else:
from frappe.sessions import clear_sessions
clear_sessions(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", "")

class CookieManager:
def __init__(self):
pass
def set_cookies(self):
if not frappe.local.session.get('sid'): return
import datetime

# sid expires in 3 days
expires = datetime.datetime.now() + datetime.timedelta(days=3)
if frappe.session.sid:
frappe.local._response.set_cookie("sid", frappe.session.sid, expires = expires)
if frappe.session.session_country:
frappe.local._response.set_cookie('country', frappe.session.get("session_country"))
def set_remember_me(self):
from frappe.utils import cint
if not cint(frappe.form_dict.get('remember_me')): return
remember_days = frappe.conn.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):
frappe.conn.sql("""insert into __Auth (user, `password`)
values (%s, password(%s))
on duplicate key update `password`=password(%s)""", (user,
password, password))
@frappe.whitelist()
def get_logged_user():
return frappe.session.user

+ 139
- 0
frappe/boot.py Zobrazit soubor

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

from __future__ import unicode_literals
"""
bootstrap client session
"""

import frappe
import frappe.defaults
import frappe.model.doc
import frappe.widgets.page
import json

def get_bootinfo():
"""build and return boot info"""
bootinfo = frappe._dict()
hooks = frappe.get_hooks()
doclist = []

# profile
get_profile(bootinfo)
# control panel
cp = frappe.model.doc.getsingle('Control Panel')
# system info
bootinfo['control_panel'] = frappe._dict(cp.copy())
bootinfo['sysdefaults'] = frappe.defaults.get_defaults()
bootinfo['server_date'] = frappe.utils.nowdate()
bootinfo["send_print_in_body_and_attachment"] = frappe.conn.get_value("Email Settings",
None, "send_print_in_body_and_attachment")

if frappe.session['user'] != 'Guest':
bootinfo['user_info'] = get_fullnames()
bootinfo['sid'] = frappe.session['sid'];
# home page
bootinfo.modules = {}
for app in frappe.get_installed_apps():
try:
bootinfo.modules.update(frappe.get_attr(app + ".config.desktop.data") or {})
except ImportError, e:
pass

bootinfo.hidden_modules = frappe.conn.get_global("hidden_modules")
bootinfo.doctype_icons = dict(frappe.conn.sql("""select name, icon from
tabDocType where ifnull(icon,'')!=''"""))
bootinfo.doctype_icons.update(dict(frappe.conn.sql("""select name, icon from
tabPage where ifnull(icon,'')!=''""")))
add_home_page(bootinfo, doclist)
add_allowed_pages(bootinfo)
load_translations(bootinfo)
load_conf_settings(bootinfo)
load_startup_js(bootinfo)

# ipinfo
if frappe.session['data'].get('ipinfo'):
bootinfo['ipinfo'] = frappe.session['data']['ipinfo']
# add docs
bootinfo['docs'] = doclist
for method in hooks.boot_session or []:
frappe.get_attr(method)(bootinfo)
from frappe.model.utils import compress
bootinfo['docs'] = compress(bootinfo['docs'])

if bootinfo.lang:
bootinfo.lang = unicode(bootinfo.lang)
bootinfo.metadata_version = frappe.cache().get_value("metadata_version")
if not bootinfo.metadata_version:
bootinfo.metadata_version = frappe.reset_metadata_version()
return bootinfo

def load_conf_settings(bootinfo):
from frappe import conf
for key in ['developer_mode']:
if key in conf: bootinfo[key] = conf.get(key)

def add_allowed_pages(bootinfo):
bootinfo.page_info = dict(frappe.conn.sql("""select distinct parent, modified from `tabPage Role`
where role in ('%s')""" % "', '".join(frappe.get_roles())))

def load_translations(bootinfo):
frappe.set_user_lang(frappe.session.user)
if frappe.lang != 'en':
bootinfo["__messages"] = frappe.get_lang_dict("include")
bootinfo["lang"] = frappe.lang

def get_fullnames():
"""map of user fullnames"""
ret = frappe.conn.sql("""select name,
concat(ifnull(first_name, ''),
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')),
user_image, gender, email
from tabProfile where ifnull(enabled, 0)=1""", as_list=1)
d = {}
for r in ret:
if not r[2]:
r[2] = '/assets/frappe/images/ui/avatar.png'
else:
r[2] = r[2]
d[r[0]]= {'fullname': r[1], 'image': r[2], 'gender': r[3],
'email': r[4] or r[0]}

return d

def load_startup_js(bootinfo):
bootinfo.startup_js = ""
for method in frappe.get_hooks().startup_js or []:
bootinfo.startup_js += frappe.get_attr(method)()
def get_profile(bootinfo):
"""get profile info"""
bootinfo['profile'] = frappe.user.load_profile()
def add_home_page(bootinfo, doclist):
"""load home page"""

if frappe.session.user=="Guest":
return
home_page = frappe.get_application_home_page(frappe.session.user)

try:
page_doclist = frappe.widgets.page.get(home_page)
except (frappe.DoesNotExistError, frappe.PermissionError), e:
page_doclist = frappe.widgets.page.get('desktop')
bootinfo['home_page_html'] = page_doclist[0].content
bootinfo['home_page'] = page_doclist[0].name
doclist += page_doclist

+ 138
- 0
frappe/build.py Zobrazit soubor

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

from __future__ import unicode_literals
from frappe.utils.minify import JavascriptMinify

"""
Build the `public` folders and setup languages
"""

import os, sys, frappe, json, shutil
from cssmin import cssmin
import frappe.translate

def bundle(no_compress):
"""concat / minify js files"""
# build js files
make_asset_dirs()
build(no_compress)
frappe.translate.clear_cache()
def watch(no_compress):
"""watch and rebuild if necessary"""
import time
build(no_compress=True)

while True:
if files_dirty():
build(no_compress=True)
time.sleep(3)

def make_asset_dirs():
assets_path = os.path.join(frappe.local.sites_path, "assets")
site_public_path = os.path.join(frappe.local.site_path, 'public')
for dir_path in [
os.path.join(assets_path, 'js'),
os.path.join(assets_path, 'css')]:
if not os.path.exists(dir_path):
os.makedirs(dir_path)
# symlink app/public > assets/app
for app_name in frappe.get_all_apps(True):
pymodule = frappe.get_module(app_name)
source = os.path.join(os.path.abspath(os.path.dirname(pymodule.__file__)), 'public')
target = os.path.join(assets_path, app_name)

if not os.path.exists(target) and os.path.exists(source):
os.symlink(os.path.abspath(source), target)

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

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

shutil.copy(os.path.join(os.path.dirname(os.path.abspath(frappe.__file__)), 'data', 'languages.txt'), frappe.local.sites_path)
# reset_app_html()

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:
path = os.path.join(app_path, 'public', 'build.json')
if os.path.exists(path):
with open(path) as f:
try:
for target, sources in json.loads(f.read()).iteritems():
# update app path
source_paths = []
for source in sources:
if isinstance(source, list):
s = frappe.get_pymodule_path(source[0], *source[1].split("/"))
else:
s = os.path.join(app_path, source)
source_paths.append(s)
build_maps[target] = source_paths
except Exception, e:
print path
raise
return build_maps

timestamps = {}

def pack(target, sources, no_compress):
from cStringIO import StringIO
outtype, outtxt = target.split(".")[-1], ''
jsm = JavascriptMinify()
for f in sources:
suffix = None
if ':' in f: f, suffix = f.split(':')
if not os.path.exists(f) or os.path.isdir(f): continue
timestamps[f] = os.path.getmtime(f)
try:
with open(f, 'r') as sourcefile:
data = unicode(sourcefile.read(), 'utf-8', errors='ignore')
if outtype=="js" and (not no_compress) and suffix!="concat" and (".min." not in f):
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
jsm.minify(tmpin, tmpout)
outtxt += unicode(tmpout.getvalue() or '', 'utf-8').strip('\n') + ';'
else:
outtxt += ('\n/*\n *\t%s\n */' % f)
outtxt += '\n' + data + '\n'
except Exception, e:
print "--Error in:" + f + "--"
print frappe.get_traceback()

if not no_compress and outtype == 'css':
pass
#outtxt = cssmin(outtxt)
with open(target, 'w') as f:
f.write(outtxt.encode("utf-8"))
print "Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024)))

def files_dirty():
for target, sources in get_build_maps().iteritems():
for f in sources:
if ':' in f: f, suffix = f.split(':')
if not os.path.exists(f) or os.path.isdir(f): continue
if os.path.getmtime(f) != timestamps.get(f):
print f + ' dirty'
return True
else:
return False

+ 823
- 0
frappe/cli.py Zobrazit soubor

@@ -0,0 +1,823 @@
#!/usr/bin/env python2.7

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

from __future__ import unicode_literals
import sys, os

import frappe

site_arg_optional = []

def main():
parsed_args = frappe._dict(vars(setup_parser()))
fn = get_function(parsed_args)
if not parsed_args.get("sites_path"):
parsed_args["sites_path"] = "."
if not parsed_args.get("make_app"):
if parsed_args.get("site")=="all":
for site in get_sites(parsed_args["sites_path"]):
args = parsed_args.copy()
args["site"] = site
frappe.init(site)
run(fn, args)
else:
if not fn in site_arg_optional:
if not parsed_args.get("site") and os.path.exists("currentsite.txt"):
with open("currentsite.txt", "r") as sitefile:
site = sitefile.read()
else:
site = parsed_args.get("site")

if not site:
print "Site argument required"
exit(1)

if fn != 'install' and not os.path.exists(site):
print "Did not find folder '{}'. Are you in sites folder?".format(parsed_args.get("site"))
exit(1)
frappe.init(site)
run(fn, parsed_args)
else:
run(fn, parsed_args)

def cmd(fn):
def new_fn(*args, **kwargs):
import inspect
fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
new_kwargs = {}
for i, a in enumerate(fnargs):
# should not pass an argument more than once
if i >= len(args) and a in kwargs:
new_kwargs[a] = kwargs.get(a)
return fn(*args, **new_kwargs)
return new_fn

def run(fn, args):
if isinstance(args.get(fn), (list, tuple)):
out = globals().get(fn)(*args.get(fn), **args)
else:
out = globals().get(fn)(**args)
return out
def get_function(args):
for fn, val in args.items():
if (val or isinstance(val, list)) and globals().get(fn):
return fn
def get_sites(sites_path=None):
import os
if not sites_path:
sites_path = '.'
return [site for site in os.listdir(sites_path)
if not os.path.islink(os.path.join(sites_path, site))
and os.path.isdir(os.path.join(sites_path, site))
and not site in ('assets',)]
def setup_parser():
import argparse
parser = argparse.ArgumentParser(description="Run frappe utility functions")
setup_install(parser)
setup_utilities(parser)
setup_translation(parser)
setup_test(parser)
parser.add_argument("site", nargs="?")
# common
parser.add_argument("-f", "--force", default=False, action="store_true",
help="Force execution where applicable (look for [-f] in help)")
parser.add_argument("-v", "--verbose", default=False, action="store_true", dest="verbose",
help="Show verbose output where applicable")
return parser.parse_args()
def setup_install(parser):
parser.add_argument("--make_app", default=False, action="store_true",
help="Make a new application with boilerplate")
parser.add_argument("--install", metavar="DB-NAME", nargs=1,
help="Install a new db")
parser.add_argument("--install_app", metavar="APP-NAME", nargs=1,
help="Install a new app")
parser.add_argument("--root-password", nargs=1,
help="Root password for new app")
parser.add_argument("--reinstall", default=False, action="store_true",
help="Install a fresh app in db_name specified in conf.py")
parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2,
help="Restore from an sql file")
parser.add_argument("--add_system_manager", nargs="+",
metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles")

def setup_test(parser):
parser.add_argument("--run_tests", default=False, action="store_true",
help="Run tests options [-d doctype], [-m module]")
parser.add_argument("--app", metavar="APP-NAME", nargs=1,
help="Run command for specified app")
parser.add_argument("-d", "--doctype", metavar="DOCTYPE", nargs=1,
help="Run command for specified doctype")
parser.add_argument("-m", "--module", metavar="MODULE", nargs=1,
help="Run command for specified module")

def setup_utilities(parser):
# update
parser.add_argument("-u", "--update", nargs="*", metavar=("REMOTE", "BRANCH"),
help="Perform git pull, run patches, sync schema and rebuild files/translations")
parser.add_argument("--reload_gunicorn", default=False, action="store_true", help="reload gunicorn on update")
parser.add_argument("--patch", nargs=1, metavar="PATCH-MODULE",
help="Run a particular patch [-f]")
parser.add_argument("-l", "--latest", default=False, action="store_true",
help="Run patches, sync schema and rebuild files/translations")
parser.add_argument("--sync_all", default=False, action="store_true",
help="Reload all doctypes, pages, etc. using txt files [-f]")
parser.add_argument("--update_all_sites", nargs="*", metavar=("REMOTE", "BRANCH"),
help="Perform git pull, run patches, sync schema and rebuild files/translations")
parser.add_argument("--reload_doc", nargs=3,
metavar=('"MODULE"', '"DOCTYPE"', '"DOCNAME"'))
# build
parser.add_argument("-b", "--build", default=False, action="store_true",
help="Minify + concatenate JS and CSS files, build translations")
parser.add_argument("-w", "--watch", default=False, action="store_true",
help="Watch and concatenate JS and CSS files as and when they change")
# misc
parser.add_argument("--backup", default=False, action="store_true",
help="Take backup of database in backup folder [--with_files]")
parser.add_argument("--move", default=False, action="store_true",
help="Move site to different directory defined by --dest_dir")
parser.add_argument("--dest_dir", nargs=1, metavar="DEST-DIR",
help="Move site to different directory")
parser.add_argument("--with_files", default=False, action="store_true",
help="Also take backup of files")
parser.add_argument("--domain", nargs="*",
help="Get or set domain in Website Settings")
parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"),
help="Create new conf.py file")
parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE",
help="Create new conf.py file")
parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs=1,
help="Set administrator password")
parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin")
parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site")
parser.add_argument("--serve", action="store_true", help="Run development server")
parser.add_argument("--profile", action="store_true", help="enable profiling in development server")
parser.add_argument("--smtp", action="store_true", help="Run smtp debug server",
dest="smtp_debug_server")
parser.add_argument("--python", action="store_true", help="get python shell for a site")
parser.add_argument("--flush_memcache", action="store_true", help="flush memcached")
parser.add_argument("--ipython", action="store_true", help="get ipython shell for a site")
parser.add_argument("--get_site_status", action="store_true", help="Get site details")
parser.add_argument("--update_site_config", nargs=1,
metavar="site-CONFIG-JSON",
help="Update site_config.json for a given site")
parser.add_argument("--port", default=8000, type=int, help="port for development server")
parser.add_argument("--use", action="store_true", help="Set current site for development.")
# clear
parser.add_argument("--clear_web", default=False, action="store_true",
help="Clear website cache")
parser.add_argument("--build_sitemap", default=False, action="store_true",
help="Build Website Sitemap")
parser.add_argument("--sync_statics", default=False, action="store_true",
help="Sync files from templates/statics to Web Pages")
parser.add_argument("--clear_cache", default=False, action="store_true",
help="Clear cache, doctype cache and defaults")
parser.add_argument("--reset_perms", default=False, action="store_true",
help="Reset permissions for all doctypes")
# scheduler
parser.add_argument("--run_scheduler", default=False, action="store_true",
help="Trigger scheduler")
parser.add_argument("--run_scheduler_event", nargs=1,
metavar="all | daily | weekly | monthly",
help="Run a scheduler event")
# replace
parser.add_argument("--replace", nargs=3,
metavar=("SEARCH-REGEX", "REPLACE-BY", "FILE-EXTN"),
help="Multi-file search-replace [-f]")
# import/export
parser.add_argument("--export_doc", nargs=2, metavar=('"DOCTYPE"', '"DOCNAME"'))
parser.add_argument("--export_doclist", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"),
help="""Export doclist as json to the given path, use '-' as name for Singles.""")
parser.add_argument("--export_csv", nargs=2, metavar=("DOCTYPE", "PATH"),
help="""Dump DocType as csv""")
parser.add_argument("--import_doclist", nargs=1, metavar="PATH",
help="""Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported""")
def setup_translation(parser):
parser.add_argument("--build_message_files", default=False, action="store_true",
help="Build message files for translation.")
parser.add_argument("--get_untranslated", nargs=2, metavar=("LANG-CODE", "TARGET-FILE-PATH"),
help="""Get untranslated strings for lang.""")
parser.add_argument("--update_translations", nargs=3,
metavar=("LANG-CODE", "UNTRANSLATED-FILE-PATH", "TRANSLATED-FILE-PATH"),
help="""Update translated strings.""")

# methods
@cmd
def make_app():
from frappe.utils.boilerplate import make_boilerplate
make_boilerplate()

@cmd
def use():
with open("currentsite.txt", "w") as sitefile:
sitefile.write(frappe.local.site)

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

@cmd
def install_app(app_name, verbose=False):
from frappe.installer import install_app
frappe.connect()
install_app(app_name, verbose=verbose)
frappe.destroy()

@cmd
def reinstall(verbose=True):
install(db_name=frappe.conf.db_name, verbose=verbose, force=True, reinstall=True)

@cmd
def restore(db_name, source_sql, verbose=True, force=False):
install(db_name, source_sql=source_sql, verbose=verbose, force=force)

@cmd
def install_fixtures():
from frappe.install_lib.install import install_fixtures
install_fixtures()
frappe.destroy()

@cmd
def add_system_manager(email, first_name=None, last_name=None):
frappe.connect()
frappe.profile.add_system_manager(email, first_name, last_name)
frappe.conn.commit()
frappe.destroy()
# utilities

@cmd
def update(remote=None, branch=None, reload_gunicorn=False):
pull(remote=remote, branch=branch)

# maybe there are new framework changes, any consequences?
reload(frappe)
build()
latest()
if reload_gunicorn:
import subprocess
subprocess.check_output("killall -HUP gunicorn".split())

@cmd
def latest(verbose=True):
import frappe.modules.patch_handler
import frappe.model.sync
from frappe.website import rebuild_config
frappe.connect()
try:
# run patches
frappe.local.patch_log_list = []
frappe.modules.patch_handler.run_all()
if verbose:
print "\n".join(frappe.local.patch_log_list)
# sync
frappe.model.sync.sync_all()
# build website config if any changes in templates etc.
rebuild_config()
except frappe.modules.patch_handler.PatchError, e:
print "\n".join(frappe.local.patch_log_list)
raise
finally:
frappe.destroy()

@cmd
def sync_all(force=False):
import frappe.model.sync
frappe.connect()
frappe.model.sync.sync_all(force=force)
frappe.destroy()

@cmd
def patch(patch_module, force=False):
import frappe.modules.patch_handler
frappe.connect()
frappe.local.patch_log_list = []
frappe.modules.patch_handler.run_single(patch_module, force=force)
print "\n".join(frappe.local.patch_log_list)
frappe.destroy()
@cmd
def update_all_sites(remote=None, branch=None, verbose=True):
pull(remote, branch)
# maybe there are new framework changes, any consequences?
reload(frappe)
build()
for site in get_sites():
frappe.init(site)
latest(verbose=verbose)

@cmd
def reload_doc(module, doctype, docname, force=False):
frappe.connect()
frappe.reload_doc(module, doctype, docname, force=force)
frappe.conn.commit()
frappe.destroy()

@cmd
def build():
import frappe.build
import frappe
frappe.build.bundle(False)

@cmd
def watch():
import frappe.build
frappe.build.watch(True)

@cmd
def backup(with_files=False, verbose=True, backup_path_db=None, backup_path_files=None):
from frappe.utils.backups import scheduled_backup
frappe.connect()
odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files)
if verbose:
from frappe.utils import now
print "database backup taken -", odb.backup_path_db, "- on", now()
if with_files:
print "files backup taken -", odb.backup_path_files, "- on", now()
frappe.destroy()
return odb

@cmd
def move(dest_dir=None):
import os
if not dest_dir:
raise Exception, "--dest_dir is required for --move"
if not os.path.isdir(dest_dir):
raise Exception, "destination is not a directory or does not exist"

old_path = frappe.utils.get_site()
new_path = os.path.join(dest_dir, site)

# check if site dump of same name already exists
site_dump_exists = True
count = 0
while site_dump_exists:
final_new_path = new_path + (count and str(count) or "")
site_dump_exists = os.path.exists(final_new_path)
count = int(count or 0) + 1

os.rename(old_path, final_new_path)
frappe.destroy()
return os.path.basename(final_new_path)

@cmd
def domain(host_url=None):
frappe.connect()
if host_url:
frappe.conn.set_value("Website Settings", None, "subdomain", host_url)
frappe.conn.commit()
else:
print frappe.conn.get_value("Website Settings", None, "subdomain")
frappe.destroy()

@cmd
def make_conf(db_name=None, db_password=None, site_config=None):
from frappe.install_lib.install import make_conf
make_conf(db_name=db_name, db_password=db_password, site_config=site_config)
@cmd
def make_custom_server_script(doctype):
from frappe.core.doctype.custom_script.custom_script import make_custom_server_script_file
frappe.connect()
make_custom_server_script_file(doctype)
frappe.destroy()

# clear
@cmd
def clear_cache():
import frappe.sessions
frappe.connect()
frappe.clear_cache()
frappe.destroy()

@cmd
def clear_web():
import frappe.website.render
frappe.connect()
frappe.website.render.clear_cache()
frappe.destroy()

@cmd
def build_sitemap():
from frappe.website import rebuild_config
frappe.connect()
rebuild_config()
frappe.destroy()
@cmd
def sync_statics():
from frappe.website.doctype.web_page import web_page
frappe.connect()
web_page.sync_statics()
frappe.conn.commit()
frappe.destroy()
@cmd
def reset_perms():
frappe.connect()
for d in frappe.conn.sql_list("""select name from `tabDocType`
where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""):
frappe.clear_cache(doctype=d)
frappe.reset_perms(d)
frappe.destroy()

# scheduler
@cmd
def run_scheduler():
from frappe.utils.file_lock import create_lock, delete_lock
import frappe.utils.scheduler
if create_lock('scheduler'):
frappe.connect()
print frappe.utils.scheduler.execute()
delete_lock('scheduler')
frappe.destroy()

@cmd
def run_scheduler_event(event):
import frappe.utils.scheduler
frappe.connect()
print frappe.utils.scheduler.trigger(event)
frappe.destroy()
# replace
@cmd
def replace(search_regex, replacement, extn, force=False):
print search_regex, replacement, extn
replace_code('.', search_regex, replacement, extn, force=force)
# import/export
@cmd
def export_doc(doctype, docname):
import frappe.modules
frappe.connect()
frappe.modules.export_doc(doctype, docname)
frappe.destroy()

@cmd
def export_doclist(doctype, name, path):
from frappe.core.page.data_import_tool import data_import_tool
frappe.connect()
data_import_tool.export_json(doctype, name, path)
frappe.destroy()
@cmd
def export_csv(doctype, path):
from frappe.core.page.data_import_tool import data_import_tool
frappe.connect()
data_import_tool.export_csv(doctype, path)
frappe.destroy()

@cmd
def import_doclist(path, force=False):
from frappe.core.page.data_import_tool import data_import_tool
frappe.connect()
data_import_tool.import_doclist(path, overwrite=force)
frappe.destroy()
# translation
@cmd
def build_message_files():
import frappe.translate
frappe.connect()
frappe.translate.rebuild_all_translation_files()
frappe.destroy()

@cmd
def get_untranslated(lang, untranslated_file):
import frappe.translate
frappe.connect()
frappe.translate.get_untranslated(lang, untranslated_file)
frappe.destroy()

@cmd
def update_translations(lang, untranslated_file, translated_file):
import frappe.translate
frappe.connect()
frappe.translate.update_translations(lang, untranslated_file, translated_file)
frappe.destroy()

# git
@cmd
def git(param):
if isinstance(param, (list, tuple)):
param = " ".join(param)
import os
os.system("""cd lib && git %s""" % param)
os.system("""cd app && git %s""" % param)

def get_remote_and_branch(remote=None, branch=None):
if not (remote and branch):
if not frappe.conf.branch:
raise Exception("Please specify remote and branch")
remote = remote or "origin"
branch = branch or frappe.conf.branch
frappe.destroy()
return remote, branch

@cmd
def pull(remote=None, branch=None):
remote, branch = get_remote_and_branch(remote, branch)
git(("pull", remote, branch))

@cmd
def push(remote=None, branch=None):
remote, branch = get_remote_and_branch(remote, branch)
git(("push", remote, branch))

@cmd
def status():
git("status")

@cmd
def commit(message):
git("""commit -a -m "%s" """ % message.replace('"', '\"'))

@cmd
def checkout(branch):
git(("checkout", branch))

@cmd
def set_admin_password(admin_password):
import frappe
frappe.connect()
frappe.conn.sql("""update __Auth set `password`=password(%s)
where user='Administrator'""", (admin_password,))
frappe.conn.commit()
frappe.destroy()

@cmd
def mysql():
import frappe
import commands, os
msq = commands.getoutput('which mysql')
os.execv(msq, [msq, '-u', frappe.conf.db_name, '-p'+frappe.conf.db_password, frappe.conf.db_name, '-h', frappe.conf.db_host or "localhost", "-A"])
frappe.destroy()

@cmd
def python(site):
import frappe
import commands, os
python = commands.getoutput('which python')
if site:
os.environ["site"] = site
os.environ["PYTHONSTARTUP"] = os.path.join(os.path.dirname(frappe.__file__), "pythonrc.py")
os.execv(python, [python])
frappe.destroy()

@cmd
def ipython():
import frappe
frappe.connect()
import IPython
IPython.embed()

@cmd
def smtp_debug_server():
import commands, os
python = commands.getoutput('which python')
os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"])

@cmd
def run_tests(app=None, module=None, doctype=None, verbose=False):
import frappe.test_runner
ret = frappe.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose)
if len(ret.failures) > 0 or len(ret.errors) > 0:
exit(1)

@cmd
def serve(port=8000, profile=False):
import frappe.app
frappe.app.serve(port=port, profile=profile, site=frappe.local.site)
@cmd
def request(args):
import frappe.handler
import frappe.api
frappe.connect()
if "?" in args:
frappe.local.form_dict = frappe._dict([a.split("=") for a in args.split("?")[-1].split("&")])
else:
frappe.local.form_dict = frappe._dict()
if args.startswith("/api/method"):
frappe.local.form_dict.cmd = args.split("?")[0].split("/")[-1]
frappe.handler.execute_cmd(frappe.form_dict.cmd)
print frappe.response
frappe.destroy()

@cmd
def flush_memcache():
frappe.cache().flush_all()


def replace_code(start, txt1, txt2, extn, search=None, force=False):
"""replace all txt1 by txt2 in files with extension (extn)"""
import frappe.utils
import os, re
esc = frappe.utils.make_esc('[]')
if not search: search = esc(txt1)
for wt in os.walk(start, followlinks=1):
for fn in wt[2]:
if fn.split('.')[-1]==extn:
fpath = os.path.join(wt[0], fn)
with open(fpath, 'r') as f:
content = f.read()
if re.search(search, content):
res = search_replace_with_prompt(fpath, txt1, txt2, force)
if res == 'skip':
return 'skip'


def search_replace_with_prompt(fpath, txt1, txt2, force=False):
""" Search and replace all txt1 by txt2 in the file with confirmation"""
from termcolor import colored
with open(fpath, 'r') as f:
content = f.readlines()

tmp = []
for c in content:
if c.find(txt1) != -1:
print fpath
print colored(txt1, 'red').join(c[:-1].split(txt1))
a = ''
if force:
c = c.replace(txt1, txt2)
else:
while a.lower() not in ['y', 'n', 'skip']:
a = raw_input('Do you want to Change [y/n/skip]?')
if a.lower() == 'y':
c = c.replace(txt1, txt2)
elif a.lower() == 'skip':
return 'skip'
tmp.append(c)

with open(fpath, 'w') as f:
f.write(''.join(tmp))
print colored('Updated', 'green')

@cmd
def get_site_status(verbose=False):
import frappe
import frappe.utils
from frappe.profile import get_system_managers
from frappe.core.doctype.profile.profile import get_total_users, get_active_users, \
get_website_users, get_active_website_users
import json
frappe.connect()
ret = {
'last_backup_on': frappe.local.conf.last_backup_on,
'active_users': get_active_users(),
'total_users': get_total_users(),
'active_website_users': get_active_website_users(),
'website_users': get_website_users(),
'system_managers': "\n".join(get_system_managers()),
'default_company': frappe.conn.get_default("company"),
'disk_usage': frappe.utils.get_disk_usage(),
'working_directory': frappe.local.site_path
}
# country, timezone, industry
control_panel_details = frappe.conn.get_value("Control Panel", "Control Panel",
["country", "time_zone", "industry"], as_dict=True)
if control_panel_details:
ret.update(control_panel_details)
# basic usage/progress analytics
for doctype in ("Company", "Customer", "Item", "Quotation", "Sales Invoice",
"Journal Voucher", "Stock Ledger Entry"):
key = doctype.lower().replace(" ", "_") + "_exists"
ret[key] = 1 if frappe.conn.count(doctype) else 0
frappe.destroy()
if verbose:
print json.dumps(ret, indent=1, sort_keys=True)
return ret

@cmd
def update_site_config(site_config, verbose=False):
import json
if isinstance(site_config, basestring):
site_config = json.loads(site_config)
config = frappe.get_site_config()
config.update(site_config)
site_config_path = os.path.join(frappe.local.site_path, "site_config.json")
with open(site_config_path, "w") as f:
json.dump(config, f, indent=1, sort_keys=True)
frappe.destroy()

@cmd
def bump(repo, bump_type):

import json
assert repo in ['lib', 'app']
assert bump_type in ['minor', 'major', 'patch']

def validate(repo_path):
import git
repo = git.Repo(repo_path)
if repo.active_branch != 'master':
raise Exception, "Current branch not master in {}".format(repo_path)

def bump_version(version, version_type):
import semantic_version
v = semantic_version.Version(version)
if version_type == 'minor':
v.minor += 1
elif version_type == 'major':
v.major += 1
elif version_type == 'patch':
v.patch += 1
return unicode(v)
def add_tag(repo_path, version):
import git
repo = git.Repo(repo_path)
repo.index.add(['config.json'])
repo.index.commit('bumped to version {}'.format(version))
repo.create_tag('v' + version, repo.head)
def update_framework_requirement(version):
with open('app/config.json') as f:
config = json.load(f)
config['requires_framework_version'] = '==' + version
with open('app/config.json', 'w') as f:
json.dump(config, f, indent=1, sort_keys=True)

validate('lib/')
validate('app/')

if repo == 'app':
with open('app/config.json') as f:
config = json.load(f)
new_version = bump_version(config['app_version'], bump_type)
config['app_version'] = new_version
with open('app/config.json', 'w') as f:
json.dump(config, f, indent=1, sort_keys=True)
add_tag('app/', new_version)

elif repo == 'lib':
with open('lib/config.json') as f:
config = json.load(f)
new_version = bump_version(config['framework_version'], bump_type)
config['framework_version'] = new_version
with open('lib/config.json', 'w') as f:
json.dump(config, f, indent=1, sort_keys=True)
add_tag('lib/', new_version)

update_framework_requirement(new_version)

bump('app', bump_type)

if __name__=="__main__":
main()

+ 138
- 0
frappe/client.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe
from frappe import _
import frappe.model
import frappe.utils
import json, os

@frappe.whitelist()
def get(doctype, name=None, filters=None):
if filters and not name:
name = frappe.conn.get_value(doctype, json.loads(filters))
if not name:
raise Exception, "No document found for given filters"
return [d.fields for d in frappe.bean(doctype, name).doclist]

@frappe.whitelist()
def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False):
if not frappe.has_permission(doctype):
frappe.msgprint("No Permission", raise_exception=True)
if fieldname and fieldname.startswith("["):
fieldname = json.loads(fieldname)
return frappe.conn.get_value(doctype, json.loads(filters), fieldname, as_dict=as_dict, debug=debug)

@frappe.whitelist()
def set_value(doctype, name, fieldname, value):
if fieldname in frappe.model.default_fields:
frappe.throw(_("Cannot edit standard fields"))
doc = frappe.conn.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
if doc and doc.parent:
bean = frappe.bean(doc.parenttype, doc.parent)
child = bean.doclist.getone({"doctype": doctype, "name": name})
child.fields[fieldname] = value
else:
bean = frappe.bean(doctype, name)
bean.doc.fields[fieldname] = value
bean.save()
return [d.fields for d in bean.doclist]

@frappe.whitelist()
def insert(doclist):
if isinstance(doclist, basestring):
doclist = json.loads(doclist)
doclist[0]["__islocal"] = 1
return save(doclist)

@frappe.whitelist()
def save(doclist):
if isinstance(doclist, basestring):
doclist = json.loads(doclist)

doclistobj = frappe.bean(doclist)
doclistobj.save()
return [d.fields for d in doclist]

@frappe.whitelist()
def rename_doc(doctype, old_name, new_name, merge=False):
new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge)
return new_name

@frappe.whitelist()
def submit(doclist):
if isinstance(doclist, basestring):
doclist = json.loads(doclist)

doclistobj = frappe.bean(doclist)
doclistobj.submit()

return [d.fields for d in doclist]

@frappe.whitelist()
def cancel(doctype, name):
wrapper = frappe.bean(doctype, name)
wrapper.cancel()
return [d.fields for d in wrapper.doclist]

@frappe.whitelist()
def delete(doctype, name):
frappe.delete_doc(doctype, name)

@frappe.whitelist()
def set_default(key, value, parent=None):
"""set a user default value"""
frappe.conn.set_default(key, value, parent or frappe.session.user)
frappe.clear_cache(user=frappe.session.user)

@frappe.whitelist()
def make_width_property_setter():
doclist = json.loads(frappe.form_dict.doclist)
if doclist[0]["doctype"]=="Property Setter" and doclist[0]["property"]=="width":
bean = frappe.bean(doclist)
bean.ignore_permissions = True
bean.insert()

@frappe.whitelist()
def bulk_update(docs):
docs = json.loads(docs)
failed_docs = []
for doc in docs:
try:
ddoc = {key: val for key, val in doc.iteritems() if key not in ['doctype', 'docname']}
doctype = doc['doctype']
docname = doc['docname']
bean = frappe.bean(doctype, docname)
bean.doc.update(ddoc)
bean.save()
except:
failed_docs.append({
'doc': doc,
'exc': frappe.utils.get_traceback()
})
return {'failed_docs': failed_docs}

@frappe.whitelist()
def has_permission(doctype, docname, perm_type="read"):
# perm_type can be one of read, write, create, submit, cancel, report
return {"has_permission": frappe.has_permission(doctype, perm_type.lower(), docname)}
@frappe.whitelist()
def get_js(src):
contentpath = os.path.join(frappe.local.sites_path, src)
with open(contentpath, "r") as srcfile:
code = srcfile.read()
if frappe.local.lang != "en":
code += "\n\n$.extend(wn._messages, {})".format(json.dumps(\
frappe.get_lang_dict("jsfile", contentpath)))
return code

webnotes/config/__init__.py → frappe/config/__init__.py Zobrazit soubor


+ 45
- 0
frappe/config/desktop.py Zobrazit soubor

@@ -0,0 +1,45 @@
from frappe import _

data = {
"Calendar": {
"color": "#2980b9",
"icon": "icon-calendar",
"label": _("Calendar"),
"link": "Calendar/Event",
"type": "view"
},
"Messages": {
"color": "#9b59b6",
"icon": "icon-comments",
"label": _("Messages"),
"link": "messages",
"type": "page"
},
"To Do": {
"color": "#f1c40f",
"icon": "icon-check",
"label": _("To Do"),
"link": "List/ToDo",
"doctype": "ToDo",
"type": "list"
},
"Website": {
"color": "#16a085",
"icon": "icon-globe",
"link": "website-home",
"type": "module"
},
"Installer": {
"color": "#888",
"icon": "icon-download",
"link": "applications",
"type": "page",
"label": _("Installer")
},
"Setup": {
"color": "#bdc3c7",
"icon": "icon-wrench",
"link": "Setup",
"type": "setup"
},
}

+ 145
- 0
frappe/config/setup.py Zobrazit soubor

@@ -0,0 +1,145 @@
from frappe import _

data = [
{
"label": _("Users and Permissions"),
"icon": "icon-group",
"items": [
{
"type": "doctype",
"name": "Profile",
"description": _("System and Website Users")
},
{
"type": "doctype",
"name": "Role",
"description": _("User Roles")
},
{
"type": "page",
"name": "permission-manager",
"label": "Permission Manager",
"icon": "icon-lock",
"description": _("Set Permissions on Document Types and Roles")
},
{
"type": "page",
"name": "user-properties",
"label": "User Properties",
"icon": "icon-user",
"description": _("Set Defaults and Restrictions for Users")
}
]
},
{
"label": _("Tools"),
"icon": "icon-wrench",
"items": [
{
"type": "page",
"name": "data-import-tool",
"label": _("Import / Export Data"),
"icon": "icon-upload",
"description": _("Import / Export Data from .csv files.")
},
{
"type": "page",
"name": "modules_setup",
"label": _("Show / Hide Modules"),
"icon": "icon-upload",
"description": _("Show or hide modules globally.")
},
{
"type": "doctype",
"name": "Naming Series",
"description": _("Set numbering series for transactions.")
},
{
"type": "doctype",
"name": "Rename Tool",
"description": _("Rename many items by uploading a .csv file.")
},
{
"type": "doctype",
"name": "File Data",
"description": _("Manage uploaded files.")
}
]
},
{
"label": _("Workflow"),
"icon": "icon-random",
"items": [
{
"type": "doctype",
"name": "Workflow",
"description": _("Define workflows for forms.")
},
{
"type": "doctype",
"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).")
},
]
},
{
"label": _("Email"),
"icon": "icon-envelope",
"items": [
{
"type": "doctype",
"name": "Email Settings",
"description": _("Set outgoing mail server.")
},
]
},
{
"label": _("Customize"),
"icon": "icon-glass",
"items": [
{
"type": "doctype",
"name": "Customize Form",
"description": _("Change field properties (hide, readonly, permission etc.)")
},
{
"type": "doctype",
"name": "Custom Field",
"description": _("Add fields to forms.")
},
{
"type": "doctype",
"name": "Custom Script",
"description": _("Add custom javascript to forms.")
}
]
},
{
"label": _("System"),
"icon": "icon-cog",
"items": [
{
"type": "page",
"name": "applications",
"label": _("Application Installer"),
"description": _("Install Applications."),
"icon": "icon-download"
},
{
"type": "doctype",
"name": "Backup Manager",
"description": _("Manage cloud backups on Dropbox")
},
{
"type": "doctype",
"name": "Scheduler Log",
"description": _("Log of error on automated events (scheduler).")
},
]
}
]

webnotes/core/.no_timestamps → frappe/core/.no_timestamps Zobrazit soubor


webnotes/core/README.md → frappe/core/README.md Zobrazit soubor


webnotes/core/__init__.py → frappe/core/__init__.py Zobrazit soubor


webnotes/core/doctype/__init__.py → frappe/core/doctype/__init__.py Zobrazit soubor


webnotes/core/doctype/bulk_email/README.md → frappe/core/doctype/bulk_email/README.md Zobrazit soubor


webnotes/core/doctype/bulk_email/__init__.py → frappe/core/doctype/bulk_email/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/bulk_email/bulk_email.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/bulk_email/bulk_email.txt → frappe/core/doctype/bulk_email/bulk_email.txt Zobrazit soubor


webnotes/core/doctype/comment/README.md → frappe/core/doctype/comment/README.md Zobrazit soubor


webnotes/core/doctype/comment/__init__.py → frappe/core/doctype/comment/__init__.py Zobrazit soubor


+ 70
- 0
frappe/core/doctype/comment/comment.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe, json

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def validate(self):
if frappe.conn.sql("""select count(*) from tabComment where comment_doctype=%s
and comment_docname=%s""", (self.doc.doctype, self.doc.name))[0][0] >= 50:
frappe.msgprint("Max Comments reached!", raise_exception=True)
def on_update(self):
self.update_comment_in_doc()
def update_comment_in_doc(self):
if self.doc.comment_doctype and self.doc.comment_docname and self.doc.comment:
try:
_comments = self.get_comments_from_parent()
updated = False
for c in _comments:
if c.get("name")==self.doc.name:
c["comment"] = self.doc.comment
updated = True
if not updated:
_comments.append({
"comment": self.doc.comment,
"by": self.doc.comment_by or self.doc.owner,
"name":self.doc.name
})
self.update_comments_in_parent(_comments)
except Exception, e:
if e.args[0]==1054:
from frappe.model.db_schema import add_column
add_column(self.doc.comment_doctype, "_comments", "Text")
self.update_comment_in_doc()
elif e.args[0]==1146:
# no table
pass
else:
raise
def get_comments_from_parent(self):
_comments = frappe.conn.get_value(self.doc.comment_doctype,
self.doc.comment_docname, "_comments") or "[]"
return json.loads(_comments)
def update_comments_in_parent(self, _comments):
# use sql, so that we do not mess with the timestamp
frappe.conn.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.doc.comment_doctype,
"%s", "%s"), (json.dumps(_comments), self.doc.comment_docname))
def on_trash(self):
_comments = self.get_comments_from_parent()
for c in _comments:
if c.get("name")==self.doc.name:
_comments.remove(c)
self.update_comments_in_parent(_comments)
def on_doctype_update():
if not frappe.conn.sql("""show index from `tabComment`
where Key_name="comment_doctype_docname_index" """):
frappe.conn.commit()
frappe.conn.sql("""alter table `tabComment`
add index comment_doctype_docname_index(comment_doctype, comment_docname)""")

webnotes/core/doctype/comment/comment.txt → frappe/core/doctype/comment/comment.txt Zobrazit soubor


+ 52
- 0
frappe/core/doctype/comment/test_comment.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe, unittest, json

# commented due to commits -- only run when comments are modified

# class TestComment(unittest.TestCase):
# def setUp(self):
# self.cleanup()
# self.test_rec = frappe.bean({
# "doctype":"Event",
# "subject":"__Comment Test Event",
# "event_type": "Private",
# "starts_on": "2011-01-01 10:00:00",
# "ends_on": "2011-01-01 10:00:00",
# }).insert()
#
# def tearDown(self):
# self.cleanup()
#
# def cleanup(self):
# frappe.conn.sql("""delete from tabEvent where subject='__Comment Test Event'""")
# frappe.conn.sql("""delete from tabComment where comment='__Test Comment'""")
# frappe.conn.commit()
# if "_comments" in frappe.conn.get_table_columns("Event"):
# frappe.conn.commit()
# frappe.conn.sql("""alter table `tabEvent` drop column `_comments`""")
#
# def test_add_comment(self):
# self.comment = frappe.bean({
# "doctype":"Comment",
# "comment_doctype": self.test_rec.doc.doctype,
# "comment_docname": self.test_rec.doc.name,
# "comment": "__Test Comment"
# }).insert()
#
# test_rec = frappe.doc(self.test_rec.doc.doctype, self.test_rec.doc.name)
# _comments = json.loads(test_rec.get("_comments"))
# self.assertTrue(_comments[0].get("comment")=="__Test Comment")
#
# def test_remove_comment(self):
# self.test_add_comment()
# frappe.delete_doc("Comment", self.comment.doc.name)
# test_rec = frappe.doc(self.test_rec.doc.doctype, self.test_rec.doc.name)
# _comments = json.loads(test_rec.get("_comments"))
# self.assertEqual(len(_comments), 0)
#
#
# if __name__=="__main__":
# unittest.main()

webnotes/core/doctype/communication/README.md → frappe/core/doctype/communication/README.md Zobrazit soubor


webnotes/core/doctype/communication/__init__.py → frappe/core/doctype/communication/__init__.py Zobrazit soubor


+ 10
- 0
frappe/core/doctype/communication/communication.js Zobrazit soubor

@@ -0,0 +1,10 @@
cur_frm.cscript.onload = function(doc) {
cur_frm.fields_dict.user.get_query = function() {
return {
query: "frappe.core.doctype.communication.communication.get_user"
}
};
if(doc.content)
doc.content = frappe.utils.remove_script_and_style(doc.content);
}

+ 159
- 0
frappe/core/doctype/communication/communication.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe
import json
import urllib
from email.utils import formataddr
from frappe.website.utils import is_signup_enabled
from frappe.utils import get_url, cstr
from frappe.utils.email_lib.email_body import get_email
from frappe.utils.email_lib.smtp import send
from frappe.utils import scrub_urls

class DocType():
def __init__(self, doc, doclist=None):
self.doc = doc
self.doclist = doclist
def get_parent_bean(self):
return frappe.bean(self.doc.parenttype, self.doc.parent)
def update_parent(self):
"""update status of parent Lead or Contact based on who is replying"""
observer = self.get_parent_bean().get_attr("on_communication")
if observer:
observer()
def on_update(self):
self.update_parent()

@frappe.whitelist()
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None):
if doctype and name and not frappe.has_permission(doctype, "email", name):
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
doctype=doctype, name=name))
_make(doctype=doctype, name=name, content=content, subject=subject, sent_or_received=sent_or_received,
sender=sender, recipients=recipients, communication_medium=communication_medium, send_email=send_email,
print_html=print_html, attachments=attachments, send_me_a_copy=send_me_a_copy, set_lead=set_lead,
date=date)
def _make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, attachments='[]', send_me_a_copy=False, set_lead=True, date=None):
# add to Communication
sent_via = None
# since we are using fullname and email,
# if the fullname has any incompatible characters,formataddr can deal with it
try:
sender = json.loads(sender)
except ValueError:
pass
if isinstance(sender, (tuple, list)) and len(sender)==2:
sender = formataddr(sender)
comm = frappe.new_bean('Communication')
d = comm.doc
d.subject = subject
d.content = content
d.sent_or_received = sent_or_received
d.sender = sender or frappe.conn.get_value("Profile", frappe.session.user, "email")
d.recipients = recipients
# add as child
sent_via = frappe.get_obj(doctype, name)
d.parent = name
d.parenttype = doctype
d.parentfield = "communications"

if date:
d.communication_date = date

d.communication_medium = communication_medium
comm.ignore_permissions = True
comm.insert()
if send_email:
d = comm.doc
send_comm_email(d, name, sent_via, print_html, attachments, send_me_a_copy)

@frappe.whitelist()
def get_customer_supplier(args=None):
"""
Get Customer/Supplier, given a contact, if a unique match exists
"""
if not args: args = frappe.local.form_dict
if not args.get('contact'):
raise Exception, "Please specify a contact to fetch Customer/Supplier"
result = frappe.conn.sql("""\
select customer, supplier
from `tabContact`
where name = %s""", args.get('contact'), as_dict=1)
if result and len(result)==1 and (result[0]['customer'] or result[0]['supplier']):
return {
'fieldname': result[0]['customer'] and 'customer' or 'supplier',
'value': result[0]['customer'] or result[0]['supplier']
}
return {}

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

if sent_via:
if hasattr(sent_via, "get_sender"):
d.sender = sent_via.get_sender(d) or d.sender
if hasattr(sent_via, "get_subject"):
d.subject = sent_via.get_subject(d)
if hasattr(sent_via, "get_content"):
d.content = sent_via.get_content(d)
footer = set_portal_link(sent_via, d)
send_print_in_body = frappe.conn.get_value("Email Settings", None, "send_print_in_body_and_attachment")
if not send_print_in_body:
d.content += "<p>Please see attachment for document details.</p>"
mail = get_email(d.recipients, sender=d.sender, subject=d.subject,
msg=d.content, footer=footer, print_html=print_html if send_print_in_body else None)
if send_me_a_copy:
mail.cc.append(frappe.conn.get_value("Profile", frappe.session.user, "email"))
if print_html:
print_html = scrub_urls(print_html)
mail.add_attachment(name.replace(' ','').replace('/','-') + '.html', print_html)

for a in json.loads(attachments):
try:
mail.attach_file(a)
except IOError, e:
frappe.msgprint("""Unable to find attachment %s. Please resend without attaching this file.""" % a,
raise_exception=True)
send(mail)
def set_portal_link(sent_via, comm):
"""set portal link in footer"""

footer = None

if is_signup_enabled() and hasattr(sent_via, "get_portal_page"):
portal_page = sent_via.get_portal_page()
if portal_page:
is_valid_recipient = cstr(sent_via.doc.email or sent_via.doc.email_id or
sent_via.doc.contact_email) in comm.recipients
if is_valid_recipient:
url = "%s/%s?name=%s" % (get_url(), portal_page, urllib.quote(sent_via.doc.name))
footer = """<!-- Portal Link --><hr>
<a href="%s" target="_blank">View this on our website</a>""" % url
return footer

webnotes/core/doctype/communication/communication.txt → frappe/core/doctype/communication/communication.txt Zobrazit soubor


webnotes/core/doctype/control_panel/README.md → frappe/core/doctype/control_panel/README.md Zobrazit soubor


webnotes/core/doctype/control_panel/__init__.py → frappe/core/doctype/control_panel/__init__.py Zobrazit soubor


+ 18
- 0
frappe/core/doctype/control_panel/control_panel.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

from frappe import form, msgprint
import frappe.defaults

class DocType:
def __init__(self, doc, doclist):
self.doc = doc
self.doclist = doclist

def on_update(self):
# clear cache on save
frappe.cache().delete_value('time_zone')
frappe.clear_cache()

webnotes/core/doctype/control_panel/control_panel.txt → frappe/core/doctype/control_panel/control_panel.txt Zobrazit soubor


webnotes/core/doctype/custom_field/README.md → frappe/core/doctype/custom_field/README.md Zobrazit soubor


webnotes/core/doctype/custom_field/__init__.py → frappe/core/doctype/custom_field/__init__.py Zobrazit soubor


+ 73
- 0
frappe/core/doctype/custom_field/custom_field.js Zobrazit soubor

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

//168
// Refresh
// --------

cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.toggle_enable('dt', doc.__islocal);
cur_frm.cscript.dt(doc, cdt, cdn);
cur_frm.toggle_reqd('label', !doc.fieldname);
}


cur_frm.cscript.has_special_chars = function(t) {
var iChars = "!@#$%^&*()+=-[]\\\';,./{}|\":<>?";
for (var i = 0; i < t.length; i++) {
if (iChars.indexOf(t.charAt(i)) != -1) {
return true;
}
}
return false;
}


// Label
// ------
cur_frm.cscript.label = function(doc){
if(doc.label && cur_frm.cscript.has_special_chars(doc.label)){
cur_frm.fields_dict['label_help'].disp_area.innerHTML = '<font color = "red">Special Characters are not allowed</font>';
doc.label = '';
refresh_field('label');
}
else
cur_frm.fields_dict['label_help'].disp_area.innerHTML = '';
}


cur_frm.fields_dict['dt'].get_query = function(doc, dt, dn) {
filters = [
['DocType', 'issingle', '=', 0],
];
if(user!=="Administrator") {
filters.push(['DocType', 'module', '!=', 'Core'])
}
return filters
}

cur_frm.cscript.fieldtype = function(doc, dt, dn) {
if(doc.fieldtype == 'Link') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter name of the document you want this field to be linked to in <b>Options</b>.<br> Eg.: Customer';
else if(doc.fieldtype == 'Select') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter values in <b>Options</b> separated by enter. <br>Eg.: <b>Field:</b> Country <br><b>Options:</b><br>China<br>India<br>United States<br><br><b> OR </b><br>You can also link it to existing Documents.<br>Eg.: <b>link:</b>Customer';
else cur_frm.fields_dict['options_help'].disp_area.innerHTML = '';
}


cur_frm.cscript.dt = function(doc, dt, dn) {
if(!doc.dt) {
set_field_options('insert_after', '');
return;
}
return frappe.call({
method: 'frappe.core.doctype.custom_field.custom_field.get_fields_label',
args: { doctype: doc.dt, fieldname: doc.fieldname },
callback: function(r, rt) {
doc = locals[doc.doctype][doc.name];
var insert_after_val = null;
if(doc.insert_after) {
insert_after_val = doc.insert_after;
}
set_field_options('insert_after', r.message, insert_after_val);
}
});
}

+ 127
- 0
frappe/core/doctype/custom_field/custom_field.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe
from frappe.utils import cint, cstr
from frappe import _

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def autoname(self):
self.set_fieldname()
self.doc.name = self.doc.dt + "-" + self.doc.fieldname

def set_fieldname(self):
if not self.doc.fieldname:
if not self.doc.label:
frappe.throw(_("Label is mandatory"))
# remove special characters from fieldname
self.doc.fieldname = filter(lambda x: x.isdigit() or x.isalpha() or '_',
cstr(self.doc.label).lower().replace(' ','_'))

def validate(self):
from frappe.model.doctype import get
temp_doclist = get(self.doc.dt).get_parent_doclist()
# set idx
if not self.doc.idx:
max_idx = max(d.idx for d in temp_doclist if d.doctype=='DocField')
self.doc.idx = cint(max_idx) + 1
def on_update(self):
# validate field
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype

validate_fields_for_doctype(self.doc.dt)

frappe.clear_cache(doctype=self.doc.dt)
# create property setter to emulate insert after
self.create_property_setter()

# update the schema
from frappe.model.db_schema import updatedb
updatedb(self.doc.dt)

def on_trash(self):
# delete property setter entries
frappe.conn.sql("""\
DELETE FROM `tabProperty Setter`
WHERE doc_type = %s
AND field_name = %s""",
(self.doc.dt, self.doc.fieldname))

frappe.clear_cache(doctype=self.doc.dt)

def create_property_setter(self):
if not self.doc.insert_after: return
idx_label_list, field_list = get_fields_label(self.doc.dt, 0)
label_index = idx_label_list.index(self.doc.insert_after)
if label_index==-1: return

prev_field = field_list[label_index]
frappe.conn.sql("""\
DELETE FROM `tabProperty Setter`
WHERE doc_type = %s
AND field_name = %s
AND property = 'previous_field'""", (self.doc.dt, self.doc.fieldname))
frappe.make_property_setter({
"doctype":self.doc.dt,
"fieldname": self.doc.fieldname,
"property": "previous_field",
"value": prev_field
})
@frappe.whitelist()
def get_fields_label(dt=None, form=1):
"""
if form=1: Returns a string of field labels separated by \n
if form=0: Returns lists of field labels and field names
"""
import frappe
from frappe.utils import cstr
import frappe.model.doctype
fieldname = None
if not dt:
dt = frappe.form_dict.get('doctype')
fieldname = frappe.form_dict.get('fieldname')
if not dt: return ""
doclist = frappe.model.doctype.get(dt)
docfields = sorted(doclist.get({"parent": dt, "doctype": "DocField"}),
key=lambda d: d.idx)
if fieldname:
idx_label_list = [cstr(d.label) or cstr(d.fieldname) or cstr(d.fieldtype)
for d in docfields if d.fieldname != fieldname]
else:
idx_label_list = [cstr(d.label) or cstr(d.fieldname) or cstr(d.fieldtype)
for d in docfields]
if form:
return "\n".join(idx_label_list)
else:
# return idx_label_list, field_list
field_list = [cstr(d.fieldname) for d in docfields]
return idx_label_list, field_list

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

webnotes/core/doctype/custom_field/custom_field.txt → frappe/core/doctype/custom_field/custom_field.txt Zobrazit soubor


webnotes/core/doctype/custom_script/README.md → frappe/core/doctype/custom_script/README.md Zobrazit soubor


webnotes/core/doctype/custom_script/__init__.py → frappe/core/doctype/custom_script/__init__.py Zobrazit soubor


+ 19
- 0
frappe/core/doctype/custom_script/custom_script.py Zobrazit soubor

@@ -0,0 +1,19 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import cstr

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def autoname(self):
self.doc.name = self.doc.dt + "-" + self.doc.script_type

def on_update(self):
frappe.clear_cache(doctype=self.doc.dt)
def on_trash(self):
frappe.clear_cache(doctype=self.doc.dt)


webnotes/core/doctype/custom_script/custom_script.txt → frappe/core/doctype/custom_script/custom_script.txt Zobrazit soubor


webnotes/core/doctype/customize_form/README.md → frappe/core/doctype/customize_form/README.md Zobrazit soubor


webnotes/core/doctype/customize_form/__init__.py → frappe/core/doctype/customize_form/__init__.py Zobrazit soubor


+ 241
- 0
frappe/core/doctype/customize_form/customize_form.js Zobrazit soubor

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

$(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) {
if(grid_row.doc && grid_row.doc.fieldtype=="Section Break") {
$(grid_row.row).css({"font-weight": "bold"});
}
})

cur_frm.cscript.doc_type = function() {
return cur_frm.call({
method: "get",
doc: cur_frm.doc,
callback: function(r) {
cur_frm.refresh();
}
});
}

cur_frm.cscript.onload = function(doc, dt, dn) {
cur_frm.fields_dict.fields.grid.cannot_add_rows = true;
cur_frm.add_fields_help();
}

cur_frm.fields_dict.doc_type.get_query = function(doc, dt, dn) {
return{
filters:[
['DocType', 'issingle', '=', 0],
['DocType', 'in_create', '=', 0],
['DocType', 'name', 'not in', 'DocType, DocField, DocPerm, Profile, Role, UserRole,\
Page, Page Role, Module Def, Print Format, Report, Customize Form, Customize Form Field']
]
}
}

cur_frm.cscript.refresh = function() {
cur_frm.disable_save();
cur_frm.frm_head.appframe.iconbar.clear("1");

cur_frm.appframe.set_title_right("Update", function() {
if(cur_frm.doc.doc_type) {
return cur_frm.call({
doc: cur_frm.doc,
method: "post",
callback: function(r) {
if(!r.exc && r.server_messages) {
cur_frm.script_manager.trigger("doc_type");
cur_frm.frm_head.set_label(['Updated', 'label-success']);
}
}
});
}
});
cur_frm.add_custom_button('Refresh Form', function() {
cur_frm.script_manager.trigger("doc_type");
}, "icon-refresh");
cur_frm.add_custom_button('Reset to defaults', function() {
cur_frm.confirm('This will <b>remove the customizations</b> defined for this form.<br /><br />'
+ 'Are you sure you want to <i>reset to defaults</i>?', cur_frm.doc, cur_frm.doctype, cur_frm.docname);
}, "icon-eraser");

if(!cur_frm.doc.doc_type) {
var frm_head = cur_frm.frm_head.appframe;
$(frm_head.buttons['Update']).prop('disabled', true);
$(frm_head.buttons['Refresh Form']).prop('disabled', true);
$(frm_head.buttons['Reset to defaults']).prop('disabled', true);
}

cur_frm.cscript.hide_allow_attach(cur_frm.doc);
if(frappe.route_options) {
frappe.model.set_value("Customize Form", null, "doc_type", frappe.route_options.doctype)
frappe.route_options = null;
}
}

cur_frm.cscript.hide_allow_attach = function(doc) {
var allow_attach_list = ['Website Settings', 'Web Page', 'Timesheet', 'Ticket',
'Support Ticket', 'Supplier', 'Style Settings', 'Stock Reconciliation',
'Stock Entry', 'Serial No', 'Sales Order', 'Sales Invoice',
'Quotation', 'Question', 'Purchase Receipt', 'Purchase Order',
'Project', 'Profile', 'Production Order', 'Product', 'Print Format',
'Price List', 'Purchase Invoice', 'Page', 'Module Def',
'Maintenance Visit', 'Maintenance Schedule', 'Letter Head',
'Leave Application', 'Lead', 'Journal Voucher', 'Item', 'Material Request',
'Expense Claim', 'Opportunity', 'Employee', 'Delivery Note',
'Customer Issue', 'Customer', 'Contact Us Settings', 'Company',
'Blog Post', 'BOM', 'About Us Settings', 'Batch'];
if(inList(allow_attach_list, doc.doc_type)) {
unhide_field('allow_attach');
} else {
hide_field('allow_attach');
}
}

cur_frm.confirm = function(msg, doc, dt, dn) {
var d = new frappe.ui.Dialog({
title: 'Reset To Defaults',
width: 500
});

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

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

var button_wrapper = $a(d.body, 'div');
$y(button_wrapper, {paddingTop: '15px'});
var proceed_btn = $btn(button_wrapper, 'Proceed', function() {
return cur_frm.call({
doc: cur_frm.doc,
method: "delete",
callback: function(r) {
if(r.exc) {
msgprint(r.exc);
} else {
cur_frm.confirm.dialog.hide();
cur_frm.refresh();
cur_frm.frm_head.set_label(['Saved', 'label-success']);
}
}
});
});

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

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

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

cur_frm.confirm.dialog = d;
d.show();
}


cur_frm.add_fields_help = function() {
$(cur_frm.grids[0].parent).before(
'<div style="padding: 10px">\
<a id="fields_help" class="link_type">Help</a>\
</div>');
$('#fields_help').click(function() {
var d = new frappe.ui.Dialog({
title: 'Help: Field Properties',
width: 600
});

var help =
"<table cellspacing='25'>\
<tr>\
<td><b>Label</b></td>\
<td>Set the display label for the field</td>\
</tr>\
<tr>\
<td><b>Type</b></td>\
<td>Change type of field. (Currently, Type change is \
allowed among 'Currency and Float')</td>\
</tr>\
<tr>\
<td width='25%'><b>Options</b></td>\
<td width='75%'>Specify the value of the field</td>\
</tr>\
<tr>\
<td><b>Perm Level</b></td>\
<td>\
Assign a permission level to the field.<br />\
(Permissions can be managed via Setup &gt; Permission Manager)\
</td>\
</tr>\
<tr>\
<td><b>Width</b></td>\
<td>\
Width of the input box<br />\
Example: <i>120px</i>\
</td>\
</tr>\
<tr>\
<td><b>Reqd</b></td>\
<td>Mark the field as Mandatory</td>\
</tr>\
<tr>\
<td><b>In Filter</b></td>\
<td>Use the field to filter records</td>\
</tr>\
<tr>\
<td><b>Hidden</b></td>\
<td>Hide field in form</td>\
</tr>\
<tr>\
<td><b>Print Hide</b></td>\
<td>Hide field in Standard Print Format</td>\
</tr>\
<tr>\
<td><b>Report Hide</b></td>\
<td>Hide field in Report Builder</td>\
</tr>\
<tr>\
<td><b>Allow on Submit</b></td>\
<td>Allow field to remain editable even after submission</td>\
</tr>\
<tr>\
<td><b>Depends On</b></td>\
<td>\
Show field if a condition is met<br />\
Example: <code>eval:doc.status=='Cancelled'</code>\
on a field like \"reason_for_cancellation\" will reveal \
\"Reason for Cancellation\" only if the record is Cancelled.\
</td>\
</tr>\
<tr>\
<td><b>Description</b></td>\
<td>Show a description below the field</td>\
</tr>\
<tr>\
<td><b>Default</b></td>\
<td>Specify a default value</td>\
</tr>\
<tr>\
<td></td>\
<td><a class='link_type' \
onclick='cur_frm.fields_help_dialog.hide()'\
style='color:grey'>Press Esc to close</a>\
</td>\
</tr>\
</table>"
$y(d.body, {padding: '32px', textAlign: 'center', lineHeight: '200%'});

$a(d.body, 'div', '', {textAlign: 'left'}, help);

d.show();

cur_frm.fields_help_dialog = d;

});
}

+ 351
- 0
frappe/core/doctype/customize_form/customize_form.py Zobrazit soubor

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

from __future__ import unicode_literals
"""
Customize Form is a Single DocType used to mask the Property Setter
Thus providing a better UI from user perspective
"""
import frappe, json
from frappe.utils import cstr

class DocType:
def __init__(self, doc, doclist=[]):
self.doc, self.doclist = doc, doclist
self.doctype_properties = [
'search_fields',
'default_print_format',
'read_only_onload',
'allow_print',
'allow_email',
'allow_copy',
'allow_attach',
'max_attachments'
]
self.docfield_properties = [
'idx',
'label',
'fieldtype',
'fieldname',
'options',
'permlevel',
'width',
'print_width',
'reqd',
'ignore_restrictions',
'in_filter',
'in_list_view',
'hidden',
'print_hide',
'report_hide',
'allow_on_submit',
'depends_on',
'description',
'default',
'name',
]

self.property_restrictions = {
'fieldtype': [['Currency', 'Float'], ['Small Text', 'Data'], ['Text', 'Text Editor', 'Code']],
}


def get(self):
"""
Gets DocFields applied with Property Setter customizations via Customize Form Field
"""
self.clear()

if self.doc.doc_type:
from frappe.model.doc import addchild

for d in self.get_ref_doclist():
if d.doctype=='DocField':
new = addchild(self.doc, 'fields', 'Customize Form Field',
self.doclist)
self.set(
{
'list': self.docfield_properties,
'doc' : d,
'doc_to_set': new
}
)
elif d.doctype=='DocType':
self.set({ 'list': self.doctype_properties, 'doc': d })


def get_ref_doclist(self):
"""
* Gets doclist of type self.doc.doc_type
* Applies property setter properties on the doclist
* returns the modified doclist
"""
from frappe.model.doctype import get
ref_doclist = get(self.doc.doc_type)
ref_doclist = frappe.doclist([ref_doclist[0]]
+ ref_doclist.get({"parent": self.doc.doc_type}))

return ref_doclist


def clear(self):
"""
Clear fields in the doc
"""
# Clear table before adding new doctype's fields
self.doclist = self.doc.clear_table(self.doclist, 'fields')
self.set({ 'list': self.doctype_properties, 'value': None })
def set(self, args):
"""
Set a list of attributes of a doc to a value
or to attribute values of a doc passed
args can contain:
* list --> list of attributes to set
* doc_to_set --> defaults to self.doc
* value --> to set all attributes to one value eg. None
* doc --> copy attributes from doc to doc_to_set
"""
if not 'doc_to_set' in args:
args['doc_to_set'] = self.doc

if 'list' in args:
if 'value' in args:
for f in args['list']:
args['doc_to_set'].fields[f] = None
elif 'doc' in args:
for f in args['list']:
args['doc_to_set'].fields[f] = args['doc'].fields.get(f)
else:
frappe.msgprint("Please specify args['list'] to set", raise_exception=1)


def post(self):
"""
Save diff between Customize Form Bean and DocType Bean as property setter entries
"""
if self.doc.doc_type:
from frappe.model import doc
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
this_doclist = frappe.doclist([self.doc] + self.doclist)
ref_doclist = self.get_ref_doclist()
dt_doclist = doc.get('DocType', self.doc.doc_type)
# get a list of property setter docs
self.idx_dirty = False
diff_list = self.diff(this_doclist, ref_doclist, dt_doclist)
if self.idx_dirty:
self.make_idx_property_setter(this_doclist, diff_list)
self.set_properties(diff_list)

validate_fields_for_doctype(self.doc.doc_type)

frappe.clear_cache(doctype=self.doc.doc_type)
frappe.msgprint("Updated")


def diff(self, new_dl, ref_dl, dt_dl):
"""
Get difference between new_dl doclist and ref_dl doclist
then check how it differs from dt_dl i.e. default doclist
"""
import re
self.defaults = self.get_defaults()
diff_list = []
for new_d in new_dl:
for ref_d in ref_dl:
if ref_d.doctype == 'DocField' and new_d.name == ref_d.name:
for prop in self.docfield_properties:
# do not set forbidden properties like idx
if prop=="idx":
if ref_d.idx != new_d.idx:
self.idx_dirty = True
continue
# check if its custom field:
if ref_d.get("__custom_field"):
# update custom field
if self.has_property_changed(ref_d, new_d, prop):
# using set_value not bean because validations are called
# in the end anyways
frappe.conn.set_value("Custom Field", ref_d.name, prop, new_d.get(prop))
else:
d = self.prepare_to_set(prop, new_d, ref_d, dt_dl)
if d: diff_list.append(d)
break
elif ref_d.doctype == 'DocType' and new_d.doctype == 'Customize Form':
for prop in self.doctype_properties:
d = self.prepare_to_set(prop, new_d, ref_d, dt_dl)
if d: diff_list.append(d)
break

return diff_list


def get_defaults(self):
"""
Get fieldtype and default value for properties of a field
"""
df_defaults = frappe.conn.sql("""
SELECT fieldname, fieldtype, `default`, label
FROM `tabDocField`
WHERE parent='DocField' or parent='DocType'""", as_dict=1)
defaults = {}
for d in df_defaults:
defaults[d['fieldname']] = d
defaults['idx'] = {'fieldname' : 'idx', 'fieldtype' : 'Int', 'default' : 1, 'label' : 'idx'}
defaults['previous_field'] = {'fieldname' : 'previous_field', 'fieldtype' : 'Data', 'default' : None, 'label' : 'Previous Field'}
return defaults


def has_property_changed(self, ref_d, new_d, prop):
return new_d.fields.get(prop) != ref_d.fields.get(prop) \
and not \
( \
new_d.fields.get(prop) in [None, 0] \
and ref_d.fields.get(prop) in [None, 0] \
) and not \
( \
new_d.fields.get(prop) in [None, ''] \
and ref_d.fields.get(prop) in [None, ''] \
)
def prepare_to_set(self, prop, new_d, ref_d, dt_doclist, delete=0):
"""
Prepares docs of property setter
sets delete property if it is required to be deleted
"""
# Check if property has changed compared to when it was loaded
if self.has_property_changed(ref_d, new_d, prop):
#frappe.msgprint("new: " + str(new_d.fields[prop]) + " | old: " + str(ref_d.fields[prop]))
# Check if the new property is same as that in original doctype
# If yes, we need to delete the property setter entry
for dt_d in dt_doclist:
if dt_d.name == ref_d.name \
and (new_d.fields.get(prop) == dt_d.fields.get(prop) \
or \
( \
new_d.fields.get(prop) in [None, 0] \
and dt_d.fields.get(prop) in [None, 0] \
) or \
( \
new_d.fields.get(prop) in [None, ''] \
and dt_d.fields.get(prop) in [None, ''] \
)):
delete = 1
break
value = new_d.fields.get(prop)
if prop in self.property_restrictions:
allow_change = False
for restrict_list in self.property_restrictions.get(prop):
if value in restrict_list and \
ref_d.fields.get(prop) in restrict_list:
allow_change = True
break
if not allow_change:
frappe.msgprint("""\
You cannot change '%s' of '%s' from '%s' to '%s'.
%s can only be changed among %s.
<i>Ignoring this change and saving.</i>""" % \
(self.defaults.get(prop, {}).get("label") or prop,
new_d.fields.get("label") or new_d.fields.get("idx"),
ref_d.fields.get(prop), value,
self.defaults.get(prop, {}).get("label") or prop,
" -or- ".join([", ".join(r) for r in \
self.property_restrictions.get(prop)])), raise_exception=True)
return None

# If the above conditions are fulfilled,
# create a property setter doc, but dont save it yet.
from frappe.model.doc import Document
d = Document('Property Setter')
d.doctype_or_field = ref_d.doctype=='DocField' and 'DocField' or 'DocType'
d.doc_type = self.doc.doc_type
d.field_name = ref_d.fieldname
d.property = prop
d.value = value
d.property_type = self.defaults[prop]['fieldtype']
#d.default_value = self.defaults[prop]['default']
if delete: d.delete = 1
if d.select_item:
d.select_item = self.remove_forbidden(d.select_item)
# return the property setter doc
return d

else: return None


def make_idx_property_setter(self, doclist, diff_list):
fields = []
doclist.sort(lambda a, b: a.idx < b.idx)
for d in doclist:
if d.doctype=="Customize Form Field":
fields.append(d.fieldname)
d = frappe.doc('Property Setter')
d.doctype_or_field = 'DocType'
d.doc_type = self.doc.doc_type
d.property = "_idx"
d.value = json.dumps(fields)
d.property_type = "Text"
diff_list.append(d)

def set_properties(self, ps_doclist):
"""
* Delete a property setter entry
+ if it already exists
+ if marked for deletion
* Save the property setter doc in the list
"""
for d in ps_doclist:
# Delete existing property setter entry
if not d.fields.get("field_name"):
frappe.conn.sql("""
DELETE FROM `tabProperty Setter`
WHERE doc_type = %(doc_type)s
AND property = %(property)s""", d.fields)
else:
frappe.conn.sql("""
DELETE FROM `tabProperty Setter`
WHERE doc_type = %(doc_type)s
AND field_name = %(field_name)s
AND property = %(property)s""", d.fields)
# Save the property setter doc if not marked for deletion i.e. delete=0
if not d.delete:
d.insert()

def delete(self):
"""
Deletes all property setter entries for the selected doctype
and resets it to standard
"""
if self.doc.doc_type:
frappe.conn.sql("""
DELETE FROM `tabProperty Setter`
WHERE doc_type = %s""", self.doc.doc_type)
frappe.clear_cache(doctype=self.doc.doc_type)

self.get()

def remove_forbidden(self, string):
"""
Replace forbidden characters with a space
"""
forbidden = ['%', "'", '"', '#', '*', '?', '`']
for f in forbidden:
string.replace(f, ' ')

webnotes/core/doctype/customize_form/customize_form.txt → frappe/core/doctype/customize_form/customize_form.txt Zobrazit soubor


webnotes/core/doctype/customize_form_field/__init__.py → frappe/core/doctype/customize_form_field/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/customize_form_field/customize_form_field.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/customize_form_field/customize_form_field.txt → frappe/core/doctype/customize_form_field/customize_form_field.txt Zobrazit soubor


webnotes/core/doctype/default_home_page/__init__.py → frappe/core/doctype/default_home_page/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/default_home_page/default_home_page.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/default_home_page/default_home_page.txt → frappe/core/doctype/default_home_page/default_home_page.txt Zobrazit soubor


webnotes/core/doctype/defaultvalue/README.md → frappe/core/doctype/defaultvalue/README.md Zobrazit soubor


webnotes/core/doctype/defaultvalue/__init__.py → frappe/core/doctype/defaultvalue/__init__.py Zobrazit soubor


+ 22
- 0
frappe/core/doctype/defaultvalue/defaultvalue.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def on_doctype_update():
if not frappe.conn.sql("""show index from `tabDefaultValue`
where Key_name="defaultvalue_parent_defkey_index" """):
frappe.conn.commit()
frappe.conn.sql("""alter table `tabDefaultValue`
add index defaultvalue_parent_defkey_index(parent, defkey)""")

if not frappe.conn.sql("""show index from `tabDefaultValue`
where Key_name="defaultvalue_parent_parenttype_index" """):
frappe.conn.commit()
frappe.conn.sql("""alter table `tabDefaultValue`
add index defaultvalue_parent_parenttype_index(parent, parenttype)""")

webnotes/core/doctype/defaultvalue/defaultvalue.txt → frappe/core/doctype/defaultvalue/defaultvalue.txt Zobrazit soubor


webnotes/core/doctype/docfield/README.md → frappe/core/doctype/docfield/README.md Zobrazit soubor


webnotes/core/doctype/docfield/__init__.py → frappe/core/doctype/docfield/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/docfield/docfield.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/docfield/docfield.txt → frappe/core/doctype/docfield/docfield.txt Zobrazit soubor


webnotes/core/doctype/docperm/README.md → frappe/core/doctype/docperm/README.md Zobrazit soubor


webnotes/core/doctype/docperm/__init__.py → frappe/core/doctype/docperm/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/docperm/docperm.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/docperm/docperm.txt → frappe/core/doctype/docperm/docperm.txt Zobrazit soubor


webnotes/core/doctype/doctype/README.md → frappe/core/doctype/doctype/README.md Zobrazit soubor


webnotes/core/doctype/doctype/__init__.py → frappe/core/doctype/doctype/__init__.py Zobrazit soubor


webnotes/core/doctype/doctype/doctype.js → frappe/core/doctype/doctype/doctype.js Zobrazit soubor


+ 350
- 0
frappe/core/doctype/doctype/doctype.py Zobrazit soubor

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

from __future__ import unicode_literals

import frappe
from frappe import msgprint, _
import os

from frappe.utils import now, cint
from frappe.model import no_value_fields

class DocType:
def __init__(self, doc=None, doclist=[]):
self.doc = doc
self.doclist = doclist

def validate(self):
if not frappe.conf.get("developer_mode"):
frappe.throw("Not in Developer Mode! Set in site_config.json")
for c in [".", "/", "#", "&", "=", ":", "'", '"']:
if c in self.doc.name:
frappe.msgprint(c + " not allowed in name", raise_exception=1)
self.validate_series()
self.scrub_field_names()
self.validate_title_field()
validate_fields(self.doclist.get({"doctype":"DocField"}))
validate_permissions(self.doclist.get({"doctype":"DocPerm"}))
self.make_amendable()
self.check_link_replacement_error()

def change_modified_of_parent(self):
if frappe.flags.in_import:
return
parent_list = frappe.conn.sql("""SELECT parent
from tabDocField where fieldtype="Table" and options="%s" """ % self.doc.name)
for p in parent_list:
frappe.conn.sql('''UPDATE tabDocType SET modified="%s"
WHERE `name`="%s"''' % (now(), p[0]))

def scrub_field_names(self):
restricted = ('name','parent','idx','owner','creation','modified','modified_by',
'parentfield','parenttype',"file_list")
for d in self.doclist:
if d.parent and d.fieldtype:
if (not d.fieldname):
if d.label:
d.fieldname = d.label.strip().lower().replace(' ','_')
if d.fieldname in restricted:
d.fieldname = d.fieldname + '1'
else:
d.fieldname = d.fieldtype.lower().replace(" ","_") + "_" + str(d.idx)
def validate_title_field(self):
if self.doc.title_field and \
self.doc.title_field not in [d.fieldname for d in self.doclist.get({"doctype":"DocField"})]:
frappe.throw(_("Title field must be a valid fieldname"))
def validate_series(self, autoname=None, name=None):
if not autoname: autoname = self.doc.autoname
if not name: name = self.doc.name
if not autoname and self.doclist.get({"fieldname":"naming_series"}):
self.doc.autoname = "naming_series:"
if autoname and (not autoname.startswith('field:')) and (not autoname.startswith('eval:')) \
and (not autoname=='Prompt') and (not autoname.startswith('naming_series:')):
prefix = autoname.split('.')[0]
used_in = frappe.conn.sql('select name from tabDocType where substring_index(autoname, ".", 1) = %s and name!=%s', (prefix, name))
if used_in:
msgprint('<b>Series already in use:</b> The series "%s" is already used in "%s"' % (prefix, used_in[0][0]), raise_exception=1)

def on_update(self):
from frappe.model.db_schema import updatedb
updatedb(self.doc.name)

self.change_modified_of_parent()
make_module_and_roles(self.doclist)
from frappe import conf
if (not frappe.flags.in_import) and conf.get('developer_mode') or 0:
self.export_doc()
self.make_controller_template()
# update index
if not self.doc.custom:
from frappe.model.code import load_doctype_module
module = load_doctype_module( self.doc.name, self.doc.module)
if hasattr(module, "on_doctype_update"):
module.on_doctype_update()
frappe.clear_cache(doctype=self.doc.name)

def check_link_replacement_error(self):
for d in self.doclist.get({"doctype":"DocField", "fieldtype":"Select"}):
if (frappe.conn.get_value("DocField", d.name, "options") or "").startswith("link:") \
and not d.options.startswith("link:"):
frappe.msgprint("link: type Select fields are getting replaced. Please check for %s" % d.label,
raise_exception=True)

def on_trash(self):
frappe.conn.sql("delete from `tabCustom Field` where dt = %s", self.doc.name)
frappe.conn.sql("delete from `tabCustom Script` where dt = %s", self.doc.name)
frappe.conn.sql("delete from `tabProperty Setter` where doc_type = %s", self.doc.name)
frappe.conn.sql("delete from `tabReport` where ref_doctype=%s", self.doc.name)
def before_rename(self, old, new, merge=False):
if merge:
frappe.throw(_("DocType can not be merged"))
def after_rename(self, old, new, merge=False):
if self.doc.issingle:
frappe.conn.sql("""update tabSingles set doctype=%s where doctype=%s""", (new, old))
else:
frappe.conn.sql("rename table `tab%s` to `tab%s`" % (old, new))
def export_doc(self):
from frappe.modules.export_file import export_to_files
export_to_files(record_list=[['DocType', self.doc.name]])
def import_doc(self):
from frappe.modules.import_module import import_from_files
import_from_files(record_list=[[self.doc.module, 'doctype', self.doc.name]])

def make_controller_template(self):
from frappe.modules import get_doc_path, get_module_path, scrub
pypath = os.path.join(get_doc_path(self.doc.module,
self.doc.doctype, self.doc.name), scrub(self.doc.name) + '.py')

if not os.path.exists(pypath):
with open(pypath, 'w') as pyfile:
with open(os.path.join(get_module_path("core"), "doctype", "doctype",
"doctype_template.py"), 'r') as srcfile:
pyfile.write(srcfile.read())
def make_amendable(self):
"""
if is_submittable is set, add amended_from docfields
"""
if self.doc.is_submittable:
if not frappe.conn.sql("""select name from tabDocField
where fieldname = 'amended_from' and parent = %s""", self.doc.name):
new = self.doc.addchild('fields', 'DocField', self.doclist)
new.label = 'Amended From'
new.fieldtype = 'Link'
new.fieldname = 'amended_from'
new.options = self.doc.name
new.permlevel = 0
new.read_only = 1
new.print_hide = 1
new.no_copy = 1
new.idx = self.get_max_idx() + 1
def get_max_idx(self):
max_idx = frappe.conn.sql("""select max(idx) from `tabDocField` where parent = %s""",
self.doc.name)
return max_idx and max_idx[0][0] or 0

def validate_fields_for_doctype(doctype):
from frappe.model.doctype import get
validate_fields(get(doctype, cached=False).get({"parent":doctype,
"doctype":"DocField"}))
def validate_fields(fields):
def check_illegal_characters(fieldname):
for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$',
'(', ')', '[', ']', '/']:
if c in fieldname:
frappe.msgprint("'%s' not allowed in fieldname (%s)" % (c, fieldname))
def check_unique_fieldname(fieldname):
duplicates = filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))
if len(duplicates) > 1:
frappe.msgprint('Fieldname <b>%s</b> appears more than once in rows (%s). Please rectify' \
% (fieldname, ', '.join(duplicates)), raise_exception=1)
def check_illegal_mandatory(d):
if d.fieldtype in ('HTML', 'Button', 'Section Break', 'Column Break') and d.reqd:
frappe.msgprint('%(label)s [%(fieldtype)s] cannot be mandatory' % d.fields,
raise_exception=1)
def check_link_table_options(d):
if d.fieldtype in ("Link", "Table"):
if not d.options:
frappe.msgprint("""#%(idx)s %(label)s: Options must be specified for Link and Table type fields""" % d.fields,
raise_exception=1)
if d.options=="[Select]":
return
if d.options != d.parent and not frappe.conn.exists("DocType", d.options):
frappe.msgprint("""#%(idx)s %(label)s: Options %(options)s must be a valid "DocType" for Link and Table type fields""" % d.fields,
raise_exception=1)

def check_hidden_and_mandatory(d):
if d.hidden and d.reqd and not d.default:
frappe.msgprint("""#%(idx)s %(label)s: Cannot be hidden and mandatory (reqd) without default""" % d.fields,
raise_exception=True)

def check_max_items_in_list(fields):
count = 0
for d in fields:
if d.in_list_view: count+=1
if count > 5:
frappe.msgprint("""Max 5 Fields can be set as 'In List View', please unselect a field before selecting a new one.""")
def check_width(d):
if d.fieldtype == "Currency" and cint(d.width) < 100:
frappe.msgprint("Minimum width for FieldType 'Currency' is 100px", raise_exception=1)

def check_in_list_view(d):
if d.in_list_view and d.fieldtype!="Image" and (d.fieldtype in no_value_fields):
frappe.msgprint("'In List View' not allowed for field of type '%s'" % d.fieldtype, raise_exception=1)

for d in fields:
if not d.permlevel: d.permlevel = 0
if not d.fieldname:
frappe.msgprint("Fieldname is mandatory in row %s" % d.idx, raise_exception=1)
check_illegal_characters(d.fieldname)
check_unique_fieldname(d.fieldname)
check_illegal_mandatory(d)
check_link_table_options(d)
check_hidden_and_mandatory(d)
check_in_list_view(d)

def validate_permissions_for_doctype(doctype, for_remove=False):
from frappe.model.doctype import get
validate_permissions(get(doctype, cached=False).get({"parent":doctype,
"doctype":"DocPerm"}), for_remove)

def validate_permissions(permissions, for_remove=False):
doctype = permissions and permissions[0].parent
issingle = issubmittable = isimportable = False
if doctype and not doctype.startswith("New DocType"):
values = frappe.conn.get_value("DocType", doctype,
["issingle", "is_submittable", "allow_import"], as_dict=True)
issingle = cint(values.issingle)
issubmittable = cint(values.is_submittable)
isimportable = cint(values.allow_import)

def get_txt(d):
return "For %s (level %s) in %s, row #%s:" % (d.role, d.permlevel, d.parent, d.idx)
def check_atleast_one_set(d):
if not d.read and not d.write and not d.submit and not d.cancel and not d.create:
frappe.msgprint(get_txt(d) + " Atleast one of Read, Write, Create, Submit, Cancel must be set.",
raise_exception=True)
def check_double(d):
similar = permissions.get({
"role":d.role,
"permlevel":d.permlevel,
"match": d.match
})
if len(similar) > 1:
frappe.msgprint(get_txt(d) + " Only one rule allowed for a particular Role and Level.",
raise_exception=True)
def check_level_zero_is_set(d):
if cint(d.permlevel) > 0 and d.role != 'All':
if not permissions.get({"role": d.role, "permlevel": 0}):
frappe.msgprint(get_txt(d) + " Higher level permissions are meaningless if level 0 permission is not set.",
raise_exception=True)
if d.create or d.submit or d.cancel or d.amend or d.match:
frappe.msgprint("Create, Submit, Cancel, Amend, Match has no meaning at level " + d.permlevel,
raise_exception=True)
def check_permission_dependency(d):
if d.write and not d.read:
frappe.msgprint(get_txt(d) + " Cannot set Write permission if Read is not set.",
raise_exception=True)
if d.cancel and not d.submit:
frappe.msgprint(get_txt(d) + " Cannot set Cancel permission if Submit is not set.",
raise_exception=True)
if (d.submit or d.cancel or d.amend) and not d.write:
frappe.msgprint(get_txt(d) + " Cannot set Submit, Cancel, Amend permission if Write is not set.",
raise_exception=True)
if d.amend and not d.write:
frappe.msgprint(get_txt(d) + " Cannot set Amend if Cancel is not set.",
raise_exception=True)
if (d.fields.get("import") or d.export) and not d.report:
frappe.msgprint(get_txt(d) + " Cannot set Import or Export permission if Report is not set.",
raise_exception=True)
if d.fields.get("import") and not d.create:
frappe.msgprint(get_txt(d) + " Cannot set Import if Create is not set.",
raise_exception=True)
def remove_rights_for_single(d):
if not issingle:
return
if d.report:
frappe.msgprint("{doctype} {meaningless}".format(doctype=doctype,
meaningless=_("is a single DocType, permission of type Report is meaningless.")))
d.report = 0
d.fields["import"] = 0
d.fields["export"] = 0
if d.restrict:
frappe.msgprint("{doctype} {meaningless}".format(doctype=doctype,
meaningless=_("is a single DocType, permission of type Restrict is meaningless.")))
d.restrict = 0
def check_if_submittable(d):
if d.submit and not issubmittable:
frappe.msgprint(doctype + " is not Submittable, cannot assign submit rights.",
raise_exception=True)
elif d.amend and not issubmittable:
frappe.msgprint(doctype + " is not Submittable, cannot assign amend rights.",
raise_exception=True)
def check_if_importable(d):
if d.fields.get("import") and not isimportable:
frappe.throw("{doctype}: {not_importable}".format(doctype=doctype,
not_importable=_("is not allowed to be imported, cannot assign import rights.")))
for d in permissions:
if not d.permlevel:
d.permlevel=0
check_atleast_one_set(d)
if not for_remove:
check_double(d)
check_permission_dependency(d)
check_if_submittable(d)
check_if_importable(d)
check_level_zero_is_set(d)
remove_rights_for_single(d)

def make_module_and_roles(doclist, perm_doctype="DocPerm"):
try:
if not frappe.conn.exists("Module Def", doclist[0].module):
m = frappe.bean({"doctype": "Module Def", "module_name": doclist[0].module})
m.insert()
default_roles = ["Administrator", "Guest", "All"]
roles = [p.role for p in doclist.get({"doctype": perm_doctype})] + default_roles
for role in list(set(roles)):
if not frappe.conn.exists("Role", role):
r = frappe.bean({"doctype": "Role", "role_name": role})
r.doc.role_name = role
r.insert()
except frappe.DoesNotExistError, e:
pass
except frappe.SQLError, e:
if e.args[0]==1146:
pass
else:
raise

webnotes/core/doctype/doctype/doctype.txt → frappe/core/doctype/doctype/doctype.txt Zobrazit soubor


+ 11
- 0
frappe/core/doctype/doctype/doctype_template.py Zobrazit soubor

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

# For license information, please see license.txt

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/event/README.md → frappe/core/doctype/event/README.md Zobrazit soubor


webnotes/core/doctype/event/__init__.py → frappe/core/doctype/event/__init__.py Zobrazit soubor


webnotes/core/doctype/event/event.js → frappe/core/doctype/event/event.js Zobrazit soubor


+ 185
- 0
frappe/core/doctype/event/event.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

from frappe.utils import getdate, cint, add_months, date_diff, add_days, nowdate

weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def validate(self):
if self.doc.starts_on and self.doc.ends_on and self.doc.starts_on > self.doc.ends_on:
frappe.msgprint(frappe._("Event End must be after Start"), raise_exception=True)
def get_permission_query_conditions():
return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s'
or exists(select * from `tabEvent User` where
`tabEvent User`.parent=tabEvent.name and `tabEvent User`.person='%(user)s')
or exists(select * from `tabEvent Role` where
`tabEvent Role`.parent=tabEvent.name
and `tabEvent Role`.role in ('%(roles)s')))
""" % {
"user": frappe.session.user,
"roles": "', '".join(frappe.get_roles(frappe.session.user))
}

def has_permission(doc):
if doc.event_type=="Public" or doc.owner==frappe.session.user:
return True
# need full doclist to check roles and users
bean = frappe.bean("Event", doc.name)
if len(bean.doclist)==1:
return False
if bean.doclist.get({"doctype":"Event User", "person":frappe.session.user}):
return True
if bean.doclist.get({"doctype":"Event Role", "role":("in", frappe.get_roles())}):
return True
return False


def send_event_digest():
today = nowdate()
for user in frappe.conn.sql("""select name, email, language
from tabProfile where ifnull(enabled,0)=1
and user_type='System User' and name not in ('Guest', 'Administrator')""", as_dict=1):
events = get_events(today, today, user.name, for_reminder=True)
if events:
text = ""
frappe.set_user_lang(user.name, user.language)

text = "<h3>" + frappe._("Events In Today's Calendar") + "</h3>"
for e in events:
if e.all_day:
e.starts_on = "All Day"
text += "<h4>%(starts_on)s: %(subject)s</h4><p>%(description)s</p>" % e

text += '<p style="color: #888; font-size: 80%; margin-top: 20px; padding-top: 10px; border-top: 1px solid #eee;">'\
+ frappe._("Daily Event Digest is sent for Calendar Events where reminders are set.")+'</p>'

from frappe.utils.email_lib import sendmail
sendmail(recipients=user.email, subject=frappe._("Upcoming Events for Today"),
msg = text)
@frappe.whitelist()
def get_events(start, end, user=None, for_reminder=False):
if not user:
user = frappe.session.user
roles = frappe.get_roles(user)
events = frappe.conn.sql("""select name, subject, description,
starts_on, ends_on, owner, all_day, event_type, repeat_this_event, repeat_on,
monday, tuesday, wednesday, thursday, friday, saturday, sunday
from tabEvent where ((
(date(starts_on) between date('%(start)s') and date('%(end)s'))
or (date(ends_on) between date('%(start)s') and date('%(end)s'))
or (date(starts_on) <= date('%(start)s') and date(ends_on) >= date('%(end)s'))
) or (
date(starts_on) <= date('%(start)s') and ifnull(repeat_this_event,0)=1 and
ifnull(repeat_till, "3000-01-01") > date('%(start)s')
))
%(reminder_condition)s
and (event_type='Public' or owner='%(user)s'
or exists(select * from `tabEvent User` where
`tabEvent User`.parent=tabEvent.name and person='%(user)s')
or exists(select * from `tabEvent Role` where
`tabEvent Role`.parent=tabEvent.name
and `tabEvent Role`.role in ('%(roles)s')))
order by starts_on""" % {
"start": start,
"end": end,
"reminder_condition": "and ifnull(send_reminder,0)=1" if for_reminder else "",
"user": user,
"roles": "', '".join(roles)
}, as_dict=1)
# process recurring events
start = start.split(" ")[0]
end = end.split(" ")[0]
add_events = []
remove_events = []
def add_event(e, date):
new_event = e.copy()
new_event.starts_on = date + " " + e.starts_on.split(" ")[1]
if e.ends_on:
new_event.ends_on = date + " " + e.ends_on.split(" ")[1]
add_events.append(new_event)
for e in events:
if e.repeat_this_event:
event_start, time_str = e.starts_on.split(" ")
if e.repeat_on=="Every Year":
start_year = cint(start.split("-")[0])
end_year = cint(end.split("-")[0])
event_start = "-".join(event_start.split("-")[1:])
# repeat for all years in period
for year in range(start_year, end_year+1):
date = str(year) + "-" + event_start
if date >= start and date <= end:
add_event(e, date)
remove_events.append(e)

if e.repeat_on=="Every Month":
date = start.split("-")[0] + "-" + start.split("-")[1] + "-" + event_start.split("-")[2]
# last day of month issue, start from prev month!
try:
getdate(date)
except ValueError:
date = date.split("-")
date = date[0] + "-" + str(cint(date[1]) - 1) + "-" + date[2]
start_from = date
for i in xrange(int(date_diff(end, start) / 30) + 3):
if date >= start and date <= end and date >= event_start:
add_event(e, date)
date = add_months(start_from, i+1)

remove_events.append(e)

if e.repeat_on=="Every Week":
weekday = getdate(event_start).weekday()
# monday is 0
start_weekday = getdate(start).weekday()
# start from nearest weeday after last monday
date = add_days(start, weekday - start_weekday)
for cnt in xrange(int(date_diff(end, start) / 7) + 3):
if date >= start and date <= end and date >= event_start:
add_event(e, date)

date = add_days(date, 7)
remove_events.append(e)

if e.repeat_on=="Every Day":
for cnt in xrange(date_diff(end, start) + 1):
date = add_days(start, cnt)
if date >= event_start and date <= end \
and e[weekdays[getdate(date).weekday()]]:
add_event(e, date)
remove_events.append(e)

for e in remove_events:
events.remove(e)
events = events + add_events
for e in events:
# remove weekday properties (to reduce message size)
for w in weekdays:
del e[w]
return events

webnotes/core/doctype/event/event.txt → frappe/core/doctype/event/event.txt Zobrazit soubor


+ 15
- 0
frappe/core/doctype/event/event_calendar.js Zobrazit soubor

@@ -0,0 +1,15 @@
frappe.views.calendar["Event"] = {
field_map: {
"start": "starts_on",
"end": "ends_on",
"id": "name",
"allDay": "all_day",
"title": "subject",
"status": "event_type",
},
style_map: {
"Public": "success",
"Private": "info"
},
get_events_method: "frappe.core.doctype.event.event.get_events"
}

+ 67
- 0
frappe/core/doctype/event/test_event.py Zobrazit soubor

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

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

test_records = [
[{
"doctype": "Event",
"subject":"_Test Event 1",
"starts_on": "2014-01-01",
"event_type": "Public",
}],
[{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject":"_Test Event 2",
"event_type": "Private",
}],
[{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject":"_Test Event 3",
"event_type": "Private",
}, {
"doctype": "Event User",
"parentfield": "event_individuals",
"person": "test1@example.com"
}],
]

import frappe
import frappe.defaults
import unittest

class TestEvent(unittest.TestCase):
# def setUp(self):
# profile = frappe.bean("Profile", "test1@example.com")
# profile.get_controller().add_roles("Website Manager")

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

def test_allowed_public(self):
frappe.set_user("test1@example.com")
doc = frappe.doc("Event", frappe.conn.get_value("Event", {"subject":"_Test Event 1"}))
self.assertTrue(frappe.has_permission("Event", refdoc=doc))
def test_not_allowed_private(self):
frappe.set_user("test1@example.com")
doc = frappe.doc("Event", frappe.conn.get_value("Event", {"subject":"_Test Event 2"}))
self.assertFalse(frappe.has_permission("Event", refdoc=doc))

def test_allowed_private_if_in_event_user(self):
frappe.set_user("test1@example.com")
doc = frappe.doc("Event", frappe.conn.get_value("Event", {"subject":"_Test Event 3"}))
self.assertTrue(frappe.has_permission("Event", refdoc=doc))
def test_event_list(self):
frappe.set_user("test1@example.com")
res = frappe.get_list("Event", filters=[["Event", "subject", "like", "_Test Event%"]], fields=["name", "subject"])
self.assertEquals(len(res), 2)
subjects = [r.subject for r in res]
self.assertTrue("_Test Event 1" in subjects)
self.assertTrue("_Test Event 3" in subjects)
self.assertFalse("_Test Event 2" in subjects)

webnotes/core/doctype/event_role/README.md → frappe/core/doctype/event_role/README.md Zobrazit soubor


webnotes/core/doctype/event_role/__init__.py → frappe/core/doctype/event_role/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/event_role/event_role.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/event_role/event_role.txt → frappe/core/doctype/event_role/event_role.txt Zobrazit soubor


webnotes/core/doctype/event_user/README.md → frappe/core/doctype/event_user/README.md Zobrazit soubor


webnotes/core/doctype/event_user/__init__.py → frappe/core/doctype/event_user/__init__.py Zobrazit soubor


+ 9
- 0
frappe/core/doctype/event_user/event_user.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe

class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl

webnotes/core/doctype/event_user/event_user.txt → frappe/core/doctype/event_user/event_user.txt Zobrazit soubor


webnotes/core/doctype/file_data/README.md → frappe/core/doctype/file_data/README.md Zobrazit soubor


webnotes/core/doctype/file_data/__init__.py → frappe/core/doctype/file_data/__init__.py Zobrazit soubor


+ 57
- 0
frappe/core/doctype/file_data/file_data.py Zobrazit soubor

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

from __future__ import unicode_literals
"""
record of files

naming for same name files: file.gif, file-1.gif, file-2.gif etc
"""

import frappe, frappe.utils, os
from frappe import conf

class DocType():
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def before_insert(self):
frappe.local.rollback_observers.append(self)
def on_update(self):
# check duplicate assignement
n_records = frappe.conn.sql("""select name from `tabFile Data`
where file_name=%s
and name!=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.doc.file_name, self.doc.name, self.doc.attached_to_doctype,
self.doc.attached_to_name))
if len(n_records) > 0:
self.doc.duplicate_entry = n_records[0][0]
frappe.msgprint(frappe._("Same file has already been attached to the record"))
frappe.conn.rollback()
raise frappe.DuplicateEntryError

def on_trash(self):
if self.doc.attached_to_name:
# check persmission
try:
if not frappe.has_permission(self.doc.attached_to_doctype,
"write", self.doc.attached_to_name):
frappe.msgprint(frappe._("No permission to write / remove."),
raise_exception=True)
except frappe.DoesNotExistError:
pass

# if file not attached to any other record, delete it
if self.doc.file_name and not frappe.conn.count("File Data",
{"file_name": self.doc.file_name, "name": ["!=", self.doc.name]}):
if self.doc.file_name.startswith("files/"):
path = frappe.utils.get_site_path("public", self.doc.file_name)
else:
path = frappe.utils.get_site_path("public", "files", self.doc.file_name)
if os.path.exists(path):
os.remove(path)

def on_rollback(self):
self.on_trash()

webnotes/core/doctype/file_data/file_data.txt → frappe/core/doctype/file_data/file_data.txt Zobrazit soubor


webnotes/core/doctype/letter_head/README.md → frappe/core/doctype/letter_head/README.md Zobrazit soubor


webnotes/core/doctype/letter_head/__init__.py → frappe/core/doctype/letter_head/__init__.py Zobrazit soubor


+ 11
- 0
frappe/core/doctype/letter_head/letter_head.js Zobrazit soubor

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

cur_frm.cscript.refresh = function(doc) {
cur_frm.set_intro("");
if(doc.__islocal) {
cur_frm.set_intro(frappe._("Step 1: Set the name and save."));
} else if(!cur_frm.doc.content) {
cur_frm.set_intro(frappe._("Step 2: Set Letterhead content."));
}
}

+ 30
- 0
frappe/core/doctype/letter_head/letter_head.py Zobrazit soubor

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

from __future__ import unicode_literals
import frappe


class DocType:
def __init__(self, doc, doclist=[]):
self.doc = doc
self.doclist = doclist
def validate(self):
self.set_as_default()
# clear the cache so that the new letter head is uploaded
frappe.clear_cache()
def set_as_default(self):
from frappe.utils import set_default
if not self.doc.is_default:
if not frappe.conn.sql("""select count(*) from `tabLetter Head` where ifnull(is_default,0)=1"""):
self.doc.is_default = 1
if self.doc.is_default:
frappe.conn.sql("update `tabLetter Head` set is_default=0 where name != %s",
self.doc.name)
set_default('letter_head', self.doc.name)

# update control panel - so it loads new letter directly
frappe.conn.set_value('Control Panel', None, 'letter_head', self.doc.content)

webnotes/core/doctype/letter_head/letter_head.txt → frappe/core/doctype/letter_head/letter_head.txt Zobrazit soubor


webnotes/core/doctype/letter_head/test_letter_head.py → frappe/core/doctype/letter_head/test_letter_head.py Zobrazit soubor


webnotes/core/doctype/module_def/README.md → frappe/core/doctype/module_def/README.md Zobrazit soubor


webnotes/core/doctype/module_def/__init__.py → frappe/core/doctype/module_def/__init__.py Zobrazit soubor


Některé soubory nejsou zobrazny, neboť je v této revizi změněno mnoho souborů

Načítá se…
Zrušit
Uložit