Procházet zdrojové kódy

Merge branch 'develop' into twofactor

version-14
ckosiegbu před 8 roky
rodič
revize
3329618a14
100 změnil soubory, kde provedl 2258 přidání a 176 odebrání
  1. +19
    -2
      frappe/__init__.py
  2. +2
    -0
      frappe/async.py
  3. +1
    -1
      frappe/auth.py
  4. +10
    -5
      frappe/contacts/doctype/address/address.js
  5. +30
    -11
      frappe/contacts/doctype/address/address.py
  6. +2
    -2
      frappe/core/doctype/doctype/boilerplate/controller_list.js
  7. +23
    -0
      frappe/core/doctype/doctype/boilerplate/test_controller.js
  8. +4
    -0
      frappe/core/doctype/doctype/doctype.py
  9. +1
    -2
      frappe/core/doctype/domain_settings/domain_settings.py
  10. +32
    -1
      frappe/core/doctype/module_def/module_def.json
  11. +23
    -0
      frappe/core/doctype/test_runner/_test_test_runner.js
  12. +22
    -18
      frappe/core/doctype/test_runner/test_runner.js
  13. +31
    -1
      frappe/core/doctype/test_runner/test_runner.json
  14. +6
    -20
      frappe/core/doctype/test_runner/test_runner.py
  15. +1
    -1
      frappe/core/doctype/user/user.py
  16. +32
    -4
      frappe/core/doctype/version/test_version.py
  17. +7
    -4
      frappe/core/doctype/version/version.py
  18. +14
    -2
      frappe/database.py
  19. +2
    -10
      frappe/desk/calendar.py
  20. +8
    -5
      frappe/desk/doctype/event/test_records.json
  21. +47
    -1
      frappe/desk/notifications.py
  22. +0
    -15
      frappe/desk/page/setup_wizard/setup_wizard.js
  23. +1
    -1
      frappe/desk/query_report.py
  24. +8
    -2
      frappe/desk/reportview.py
  25. +0
    -0
      frappe/docs/assets/img/desk/__init__.py
  26. binární
      frappe/docs/assets/img/desk/bar_graph.png
  27. binární
      frappe/docs/assets/img/desk/line_graph.png
  28. +8
    -3
      frappe/docs/user/en/guides/automated-testing/qunit-testing.md
  29. +9
    -2
      frappe/docs/user/en/guides/basics/site_config.md
  30. +61
    -0
      frappe/docs/user/en/guides/desk/making_graphs.md
  31. +0
    -0
      frappe/docs/user/es/__init__.py
  32. +0
    -0
      frappe/docs/user/es/bench/__init_.py
  33. +0
    -0
      frappe/docs/user/es/bench/guides/__init__.py
  34. +28
    -0
      frappe/docs/user/es/bench/guides/adding-custom-domains.md
  35. +48
    -0
      frappe/docs/user/es/bench/guides/configuring-https.md
  36. +31
    -0
      frappe/docs/user/es/bench/guides/diagnosing-the-scheduler.md
  37. +1
    -0
      frappe/docs/user/es/bench/guides/index.md
  38. +11
    -0
      frappe/docs/user/es/bench/guides/index.txt
  39. +101
    -0
      frappe/docs/user/es/bench/guides/lets-encrypt-ssl-setup.md
  40. +74
    -0
      frappe/docs/user/es/bench/guides/manual-setup.md
  41. +39
    -0
      frappe/docs/user/es/bench/guides/settings-limits.md
  42. +57
    -0
      frappe/docs/user/es/bench/guides/setup-multitenancy.md
  43. +48
    -0
      frappe/docs/user/es/bench/guides/setup-production.md
  44. +1
    -0
      frappe/docs/user/es/bench/index.md
  45. +2
    -0
      frappe/docs/user/es/bench/index.txt
  46. +0
    -0
      frappe/docs/user/es/bench/resources/__init__.py
  47. +29
    -0
      frappe/docs/user/es/bench/resources/background-services.md
  48. +90
    -0
      frappe/docs/user/es/bench/resources/bench-commands-cheatsheet.md
  49. +31
    -0
      frappe/docs/user/es/bench/resources/bench-procfile.md
  50. +1
    -0
      frappe/docs/user/es/bench/resources/index.md
  51. +3
    -0
      frappe/docs/user/es/bench/resources/index.txt
  52. +0
    -0
      frappe/docs/user/es/guides/__init_.py
  53. +3
    -0
      frappe/docs/user/es/index.md
  54. +4
    -0
      frappe/docs/user/es/index.txt
  55. +0
    -0
      frappe/docs/user/es/tutorial/__init_.py
  56. +10
    -0
      frappe/docs/user/es/tutorial/app.md
  57. +71
    -0
      frappe/docs/user/es/tutorial/before.md
  58. +11
    -0
      frappe/docs/user/es/tutorial/bench.md
  59. +5
    -0
      frappe/docs/user/es/tutorial/conclusion.md
  60. +59
    -0
      frappe/docs/user/es/tutorial/controllers.md
  61. +31
    -0
      frappe/docs/user/es/tutorial/doctype-directory-structure.md
  62. +96
    -0
      frappe/docs/user/es/tutorial/doctypes.md
  63. +39
    -0
      frappe/docs/user/es/tutorial/form-client-scripting.md
  64. +34
    -0
      frappe/docs/user/es/tutorial/index.md
  65. +19
    -0
      frappe/docs/user/es/tutorial/index.txt
  66. +19
    -0
      frappe/docs/user/es/tutorial/models.md
  67. +71
    -0
      frappe/docs/user/es/tutorial/naming-and-linking.md
  68. +54
    -0
      frappe/docs/user/es/tutorial/new-app.md
  69. +7
    -0
      frappe/docs/user/es/tutorial/reports.md
  70. +14
    -0
      frappe/docs/user/es/tutorial/roles.md
  71. +68
    -0
      frappe/docs/user/es/tutorial/setting-up-the-site.md
  72. +9
    -0
      frappe/docs/user/es/tutorial/single-doctypes.md
  73. +31
    -0
      frappe/docs/user/es/tutorial/start.md
  74. +77
    -0
      frappe/docs/user/es/tutorial/task-runner.md
  75. +55
    -0
      frappe/docs/user/es/tutorial/users-and-records.md
  76. +64
    -0
      frappe/docs/user/es/tutorial/web-views.md
  77. +0
    -0
      frappe/docs/user/es/videos/__init_.py
  78. +9
    -0
      frappe/docs/user/es/videos/index.md
  79. +1
    -0
      frappe/docs/user/index.md
  80. +1
    -0
      frappe/docs/user/index.txt
  81. +22
    -29
      frappe/email/doctype/auto_email_report/auto_email_report.py
  82. +4
    -4
      frappe/email/doctype/email_group/email_group.py
  83. +40
    -16
      frappe/email/email_body.py
  84. +2
    -1
      frappe/email/queue.py
  85. +42
    -1
      frappe/email/test_email_body.py
  86. +1
    -1
      frappe/installer.py
  87. +5
    -2
      frappe/oauth.py
  88. +2
    -1
      frappe/patches.txt
  89. +0
    -0
      frappe/patches/v8_5/__init__.py
  90. +20
    -0
      frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py
  91. +2
    -0
      frappe/public/build.json
  92. +1
    -0
      frappe/public/css/common.css
  93. +12
    -0
      frappe/public/css/desk.css
  94. +151
    -0
      frappe/public/css/email.css
  95. +86
    -0
      frappe/public/css/form.css
  96. +2
    -0
      frappe/public/css/list.css
  97. +3
    -0
      frappe/public/css/slickgrid.css
  98. +0
    -0
      frappe/public/css/variables.css
  99. +1
    -0
      frappe/public/css/website.css
  100. +1
    -8
      frappe/public/js/frappe/dom.js

+ 19
- 2
frappe/__init__.py Zobrazit soubor

@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template

__version__ = '8.4.1'
__version__ = '8.5.7'
__title__ = "Frappe Framework"

local = Local()
@@ -380,7 +380,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
inline_images=None, template=None, args=None, header=False):
inline_images=None, template=None, args=None, header=None):
"""Send email using user's default **Email Account** or global default **Email Account**.


@@ -492,6 +492,7 @@ def clear_cache(user=None, doctype=None):
frappe.sessions.clear_cache()
translate.clear_cache()
reset_metadata_version()
clear_domainification_cache()
local.cache = {}
local.new_doc_templates = {}

@@ -1371,6 +1372,22 @@ def get_active_domains():

return active_domains

def get_active_modules():
""" get the active modules from Module Def"""
active_modules = cache().hget("modules", "active_modules") or None
if active_modules is None:
domains = get_active_domains()
modules = get_all("Module Def", filters={"restrict_to_domain": ("in", domains)})
active_modules = [module.name for module in modules]
cache().hset("modules", "active_modules", active_modules)

return active_modules

def clear_domainification_cache():
_cache = cache()
_cache.delete_key("domains", "active_domains")
_cache.delete_key("modules", "active_modules")

def get_system_settings(key):
if not local.system_settings.has_key(key):
local.system_settings.update({key: db.get_single_value('System Settings', key)})


+ 2
- 0
frappe/async.py Zobrazit soubor

@@ -165,6 +165,8 @@ def get_task_log_file_path(task_id, stream_type):

@frappe.whitelist(allow_guest=True)
def can_subscribe_doc(doctype, docname, sid):
if os.environ.get('CI'):
return True
from frappe.sessions import Session
from frappe.exceptions import PermissionError
session = Session(None, resume=True).get_session_data()


+ 1
- 1
frappe/auth.py Zobrazit soubor

@@ -494,7 +494,7 @@ def get_qr_svg_code(totp_uri):
from base64 import b64encode
url = qrcreate(totp_uri)
stream = StringIO()
url.svg(stream, scale=5)
url.svg(stream, scale=3)
svg = stream.getvalue().replace('\n','')
svg = b64encode(bytes(svg))
return svg

+ 10
- 5
frappe/contacts/doctype/address/address.js Zobrazit soubor

@@ -32,10 +32,15 @@ frappe.ui.form.on("Address", {
}
},
after_save: function() {
var last_route = frappe.route_history.slice(-2, -1)[0];
if(frappe.dynamic_link && frappe.dynamic_link.doc
&& frappe.dynamic_link.doc.name == last_route[2]){
frappe.set_route(last_route[0], last_route[1], last_route[2]);
}
frappe.run_serially([
() => frappe.timeout(1),
() => {
var last_route = frappe.route_history.slice(-2, -1)[0];
if(frappe.dynamic_link && frappe.dynamic_link.doc
&& frappe.dynamic_link.doc.name == last_route[2]){
frappe.set_route(last_route[0], last_route[1], last_route[2]);
}
}
]);
}
});

+ 30
- 11
frappe/contacts/doctype/address/address.py Zobrazit soubor

@@ -135,15 +135,34 @@ def get_list_context(context=None):
}

def get_address_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by = None):
from frappe.www.list import get_list
user = frappe.session.user
ignore_permissions = False
if is_website_user():
if not filters: filters = []
filters.append(("Address", "owner", "=", user))
ignore_permissions = True

return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)
from frappe.www.list import get_list
user = frappe.session.user
ignore_permissions = False
if is_website_user():
if not filters: filters = []
add_name = []
contact = frappe.db.sql("""
select
address.name
from
`tabDynamic Link` as link
join
`tabAddress` as address on link.parent = address.name
where
link.parenttype = 'Address' and
link_name in(
select
link.link_name from `tabContact` as contact
join
`tabDynamic Link` as link on contact.name = link.parent
where
contact.user = %s)""",(user))
for c in contact:
add_name.append(c[0])
filters.append(("Address", "name", "in", add_name))
ignore_permissions = True

return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)

def has_website_permission(doc, ptype, user, verbose=False):
"""Returns true if there is a related lead or contact related to this document"""
@@ -185,12 +204,12 @@ def get_shipping_address(company):
address_as_dict = address[0]
name, address_template = get_address_templates(address_as_dict)
return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict)
def get_company_address(company):
ret = frappe._dict()
ret.company_address = get_default_address('Company', company)
ret.company_address_display = get_address_display(ret.company_address)
return ret

def address_query(doctype, txt, searchfield, start, page_len, filters):


+ 2
- 2
frappe/core/doctype/doctype/boilerplate/controller_list.js Zobrazit soubor

@@ -1,5 +1,5 @@
/* eslint-disable */
frappe.listview_settings['{doctype}'] = {{
add_fields: ["status"],
filters:[["status","=", "Open"]]
// add_fields: ["status"],
// filters:[["status","=", "Open"]]
}};

+ 23
- 0
frappe/core/doctype/doctype/boilerplate/test_controller.js Zobrazit soubor

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: {doctype}", function (assert) {{
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially('{doctype}', [
// insert a new {doctype}
() => frappe.tests.make([
// values to be set
{{key: 'value'}}
]),
() => {{
assert.equal(cur_frm.doc.key, 'value');
}},
() => done()
]);

}});

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

@@ -337,6 +337,10 @@ class DocType(Document):

if not self.istable:
make_boilerplate("controller.js", self.as_dict())
#make_boilerplate("controller_list.js", self.as_dict())
if not os.path.exists(frappe.get_module_path(frappe.scrub(self.module),
'doctype', frappe.scrub(self.name), 'tests')):
make_boilerplate("test_controller.js", self.as_dict())

if self.has_web_view:
templates_path = frappe.get_module_path(frappe.scrub(self.module), 'doctype', frappe.scrub(self.name), 'templates')


+ 1
- 2
frappe/core/doctype/domain_settings/domain_settings.py Zobrazit soubor

@@ -8,5 +8,4 @@ from frappe.model.document import Document

class DomainSettings(Document):
def on_update(self):
cache = frappe.cache()
cache.delete_key("domains", "active_domains")
frappe.clear_domainification_cache()

+ 32
- 1
frappe/core/doctype/module_def/module_def.json Zobrazit soubor

@@ -71,6 +71,37 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "restrict_to_domain",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Restrict To Domain",
"length": 0,
"no_copy": 0,
"options": "Domain",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
@@ -84,7 +115,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-20 14:35:17.407968",
"modified": "2017-07-13 03:05:28.213656",
"modified_by": "Administrator",
"module": "Core",
"name": "Module Def",


+ 23
- 0
frappe/core/doctype/test_runner/_test_test_runner.js Zobrazit soubor

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: Test Runner", function (assert) {
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially('Test Runner', [
// insert a new Test Runner
() => frappe.tests.make([
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);

});

+ 22
- 18
frappe/core/doctype/test_runner/test_runner.js Zobrazit soubor

@@ -32,32 +32,33 @@ frappe.ui.form.on('Test Runner', {
frappe.dom.eval(f.script);
});

// if(frm.doc.module_name) {
// QUnit.module.only(frm.doc.module_name);
// }

QUnit.testDone(function(details) {
var result = {
"Module name": details.module,
"Test name": details.name,
"Assertions": {
"Total": details.total,
"Passed": details.passed,
"Failed": details.failed
},
"Skipped": details.skipped,
"Todo": details.todo,
"Runtime": details.runtime
};
// var result = {
// "Module name": details.module,
// "Test name": details.name,
// "Assertions": {
// "Total": details.total,
// "Passed": details.passed,
// "Failed": details.failed
// },
// "Skipped": details.skipped,
// "Todo": details.todo,
// "Runtime": details.runtime
// };

// eslint-disable-next-line
console.log(JSON.stringify(result, null, 2));
// console.log(JSON.stringify(result, null, 2));

details.assertions.map(a => {
// eslint-disable-next-line
console.log(`${a.result ? '✔' : '✗'} ${a.message}`);
});

});
QUnit.load();

QUnit.done(({ total, failed, passed, runtime }) => {
// flag for selenium that test is done
$('<div id="frappe-qunit-done"></div>').appendTo($('body'));

console.log( `Total: ${total}, Failed: ${failed}, Passed: ${passed}, Runtime: ${runtime}` ); // eslint-disable-line

@@ -67,6 +68,9 @@ frappe.ui.form.on('Test Runner', {
console.log('Tests Passed'); // eslint-disable-line
}
frappe.set_route('Form', 'Test Runner', 'Test Runner');

$('<div id="frappe-qunit-done"></div>').appendTo($('body'));

});
});



+ 31
- 1
frappe/core/doctype/test_runner/test_runner.json Zobrazit soubor

@@ -42,6 +42,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "app",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@@ -83,7 +113,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-07-12 23:16:15.910891",
"modified": "2017-07-19 03:22:33.221169",
"modified_by": "Administrator",
"module": "Core",
"name": "Test Runner",


+ 6
- 20
frappe/core/doctype/test_runner/test_runner.py Zobrazit soubor

@@ -13,37 +13,23 @@ class TestRunner(Document):
def get_test_js():
'''Get test + data for app, example: app/tests/ui/test_name.js'''
test_path = frappe.db.get_single_value('Test Runner', 'module_path')
test_js = []

# split
app, test_path = test_path.split(os.path.sep, 1)
test_js = get_test_data(app)

# full path
# now full path
test_path = frappe.get_app_path(app, test_path)

with open(test_path, 'r') as fileobj:
test_js.append(dict(
script = fileobj.read()
))
return test_js

def get_test_data(app):
'''Get the test fixtures from all js files in app/tests/ui/data'''
test_js = []

def add_file(path):
with open(path, 'r') as fileobj:
test_js.append(dict(
script = fileobj.read()
))

data_path = frappe.get_app_path(app, 'tests', 'ui', 'data')
if os.path.exists(data_path):
for fname in os.listdir(data_path):
if fname.endswith('.js'):
add_file(os.path.join(data_path, fname))

if app != 'frappe':
add_file(frappe.get_app_path('frappe', 'tests', 'ui', 'data', 'test_lib.js'))
# add test_lib.js
add_file(frappe.get_app_path('frappe', 'tests', 'ui', 'data', 'test_lib.js'))
add_file(test_path)

return test_js


+ 1
- 1
frappe/core/doctype/user/user.py Zobrazit soubor

@@ -279,7 +279,7 @@ class User(Document):
sender = frappe.session.user not in STANDARD_USERS and get_formatted_email(frappe.session.user) or None

frappe.sendmail(recipients=self.email, sender=sender, subject=subject,
template=template, args=args,
template=template, args=args, header=[subject, "green"],
delayed=(not now) if now!=None else self.flags.delay_emails, retry=3)

def a_system_manager_should_exist(self):


+ 32
- 4
frappe/core/doctype/version/test_version.py Zobrazit soubor

@@ -3,9 +3,37 @@
from __future__ import unicode_literals

import frappe
import unittest
test_records = frappe.get_test_records('Version')
import unittest, copy
from frappe.test_runner import make_test_objects
from frappe.core.doctype.version.version import get_diff

class TestVersion(unittest.TestCase):
pass
def test_get_diff(self):
test_records = make_test_objects('Event', reset = True)
old_doc = frappe.get_doc("Event", test_records[0])
new_doc = copy.deepcopy(old_doc)

old_doc.color = None

diff = get_diff(old_doc, new_doc)['changed']

self.assertEquals(get_fieldnames(diff)[0], 'color')
self.assertTrue(get_old_values(diff)[0] is None)
self.assertEquals(get_new_values(diff)[0], 'blue')

new_doc.starts_on = "2017-07-20"

diff = get_diff(old_doc, new_doc)['changed']

self.assertEquals(get_fieldnames(diff)[0], 'starts_on')
self.assertEquals(get_old_values(diff)[0], '01-01-2014 00:00:00')
self.assertEquals(get_new_values(diff)[0], '07-20-2017 00:00:00')

def get_fieldnames(change_array):
return [d[0] for d in change_array]

def get_old_values(change_array):
return [d[1] for d in change_array]

def get_new_values(change_array):
return [d[2] for d in change_array]

+ 7
- 4
frappe/core/doctype/version/version.py Zobrazit soubor

@@ -69,10 +69,13 @@ def get_diff(old, new, for_child=False):
if not d.name in new_row_by_name:
out.removed.append([df.fieldname, d.as_dict()])

elif (old_value != new_value
and old.get_formatted(df.fieldname) != new.get_formatted(df.fieldname)):
out.changed.append((df.fieldname, old.get_formatted(df.fieldname),
new.get_formatted(df.fieldname)))
elif (old_value != new_value):
# Check for None values
old_data = old.get_formatted(df.fieldname) if old_value else old_value
new_data = new.get_formatted(df.fieldname) if new_value else new_value

if old_data != new_data:
out.changed.append((df.fieldname, old_data, new_data))

# docstatus
if not for_child and old.docstatus != new.docstatus:


+ 14
- 2
frappe/database.py Zobrazit soubor

@@ -53,8 +53,20 @@ class Database:
def connect(self):
"""Connects to a database as set in `site_config.json`."""
warnings.filterwarnings('ignore', category=MySQLdb.Warning)
self._conn = MySQLdb.connect(user=self.user, host=self.host, passwd=self.password,
use_unicode=True, charset='utf8mb4')
usessl = 0
if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
usessl = 1
self.ssl = {
'ca':frappe.conf.db_ssl_ca,
'cert':frappe.conf.db_ssl_cert,
'key':frappe.conf.db_ssl_key
}
if usessl:
self._conn = MySQLdb.connect(user=self.user, host=self.host, passwd=self.password,
use_unicode=True, charset='utf8mb4', ssl=self.ssl)
else:
self._conn = MySQLdb.connect(user=self.user, host=self.host, passwd=self.password,
use_unicode=True, charset='utf8mb4')
self._conn.converter[246]=float
self._conn.converter[12]=get_datetime
self._conn.encoders[UnicodeWithAttrs] = self._conn.encoders[UnicodeType]


+ 2
- 10
frappe/desk/calendar.py Zobrazit soubor

@@ -19,16 +19,8 @@ def update_event(args, field_map):

def get_event_conditions(doctype, filters=None):
"""Returns SQL conditions with user permissions and filters for event queries"""
from frappe.desk.reportview import build_match_conditions
from frappe.desk.reportview import get_filters_cond
if not frappe.has_permission(doctype):
frappe.throw(_("Not Permitted"), frappe.PermissionError)

conditions = build_match_conditions(doctype)
conditions = conditions and (" and " + conditions) or ""
if filters:
filters = json.loads(filters)
for key in filters:
if filters[key]:
conditions += 'and `{0}` = "{1}"'.format(frappe.db.escape(key), frappe.db.escape(filters[key]))

return conditions
return get_filters_cond(doctype, filters, [], with_match_conditions = True)

+ 8
- 5
frappe/desk/doctype/event/test_records.json Zobrazit soubor

@@ -3,18 +3,21 @@
"doctype": "Event",
"subject":"_Test Event 1",
"starts_on": "2014-01-01",
"event_type": "Public"
"event_type": "Public",
"creation": "2014-01-01"
},
{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject":"_Test Event 2",
"event_type": "Private"
"starts_on": "2014-01-01",
"event_type": "Private",
"creation": "2014-01-01"
},
{
"doctype": "Event",
"starts_on": "2014-01-01",
"subject": "_Test Event 3",
"event_type": "Private"
"starts_on": "2014-02-01",
"event_type": "Private",
"creation": "2014-02-01"
}
]

+ 47
- 1
frappe/desk/notifications.py Zobrazit soubor

@@ -13,10 +13,12 @@ def get_notifications():
return

config = get_notification_config()

groups = config.get("for_doctype").keys() + config.get("for_module").keys()
cache = frappe.cache()

notification_count = {}
notification_percent = {}

for name in groups:
count = cache.hget("notification_count:" + name, frappe.session.user)
@@ -27,6 +29,7 @@ def get_notifications():
"open_count_doctype": get_notifications_for_doctypes(config, notification_count),
"open_count_module": get_notifications_for_modules(config, notification_count),
"open_count_other": get_notifications_for_other(config, notification_count),
"targets": get_notifications_for_targets(config, notification_percent),
"new_messages": get_new_messages()
}

@@ -111,6 +114,49 @@ def get_notifications_for_doctypes(config, notification_count):

return open_count_doctype

def get_notifications_for_targets(config, notification_percent):
"""Notifications for doc targets"""
can_read = frappe.get_user().get_can_read()
doc_target_percents = {}

# doc_target_percents = {
# "Company": {
# "Acme": 87,
# "RobotsRUs": 50,
# }, {}...
# }

for doctype in config.targets:
if doctype in can_read:
if doctype in notification_percent:
doc_target_percents[doctype] = notification_percent[doctype]
else:
doc_target_percents[doctype] = {}
d = config.targets[doctype]
condition = d["filters"]
target_field = d["target_field"]
value_field = d["value_field"]
try:
if isinstance(condition, dict):
doc_list = frappe.get_list(doctype, fields=["name", target_field, value_field],
filters=condition, limit_page_length = 100, ignore_ifnull=True)

except frappe.PermissionError:
frappe.clear_messages()
pass
except Exception as e:
if e.args[0]!=1412:
raise

else:
for doc in doc_list:
value = doc[value_field]
target = doc[target_field]
doc_target_percents[doctype][doc.name] = (value/target * 100) if value < target else 100

return doc_target_percents


def clear_notifications(user=None):
if frappe.flags.in_install:
return
@@ -163,7 +209,7 @@ def get_notification_config():
config = frappe._dict()
for notification_config in frappe.get_hooks().notification_config:
nc = frappe.get_attr(notification_config)()
for key in ("for_doctype", "for_module", "for_other"):
for key in ("for_doctype", "for_module", "for_other", "targets"):
config.setdefault(key, {})
config[key].update(nc.get(key, {}))
return config


+ 0
- 15
frappe/desk/page/setup_wizard/setup_wizard.js Zobrazit soubor

@@ -678,21 +678,6 @@ var utils = {
slide.get_field("timezone").set_input(frappe.wizard.values.timezone);
}

country_field.df.description = 'fetching country...';
country_field.set_description();

// get location from IP (unreliable)
frappe.call({
method:"frappe.desk.page.setup_wizard.setup_wizard.load_country",
callback: function(r) {
if(r.message) {
slide.get_field("country").set_input(r.message);
slide.get_input("country").trigger('change');
}
country_field.df.description = '';
country_field.set_description();
}
});
},

bind_language_events: function(slide) {


+ 1
- 1
frappe/desk/query_report.py Zobrazit soubor

@@ -208,7 +208,7 @@ def add_total_row(result, columns, meta = None):
total_row[i] = result[0][i]

for i in has_percent:
total_row[i] = total_row[i] / len(result)
total_row[i] = flt(total_row[i]) / len(result)

first_col_fieldtype = None
if isinstance(columns[0], basestring):


+ 8
- 2
frappe/desk/reportview.py Zobrazit soubor

@@ -334,7 +334,10 @@ def build_match_conditions(doctype, as_condition=True):
else:
return match_conditions

def get_filters_cond(doctype, filters, conditions, ignore_permissions=None):
def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with_match_conditions=False):
if isinstance(filters, basestring):
filters = json.loads(filters)

if filters:
flt = filters
if isinstance(filters, dict):
@@ -350,10 +353,13 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None):
query = DatabaseQuery(doctype)
query.filters = flt
query.conditions = conditions

if with_match_conditions:
query.build_match_conditions()

query.build_filter_conditions(flt, conditions, ignore_permissions)

cond = ' and ' + ' and '.join(query.conditions)
else:
cond = ''
return cond


+ 0
- 0
frappe/docs/assets/img/desk/__init__.py Zobrazit soubor


binární
frappe/docs/assets/img/desk/bar_graph.png Zobrazit soubor

Před Za
Šířka: 776  |  Výška: 296  |  Velikost: 29 KiB

binární
frappe/docs/assets/img/desk/line_graph.png Zobrazit soubor

Před Za
Šířka: 776  |  Výška: 295  |  Velikost: 39 KiB

+ 8
- 3
frappe/docs/user/en/guides/automated-testing/qunit-testing.md Zobrazit soubor

@@ -22,11 +22,16 @@ To run a Test Runner based test, use the `run-ui-tests` bench command by passing

This will pass the filename to `test_test_runner.py` that will load the required JS in the browser and execute the tests

### Adding Fixtures / Test Data
### Debugging Tests

You can also add data that you require for all tests in the `tests/ui/data` folder of your app. All the files in this folder will be loaded in the browser before running the test.
To debug a test, you can open it in the **Test Runner** from your UI and run it manually to see where it is exactly failing.

The file `frappe/tests/ui/data/test_lib.js`, which contains library functions for testing is always loaded.
### Test Sequence

In Frappé UI tests are run in a fixed sequence to ensure dependencies.

The sequence in which the tests will be run will be in `tests/ui/tests.txt`
file.

### Running All UI Tests



+ 9
- 2
frappe/docs/user/en/guides/basics/site_config.md Zobrazit soubor

@@ -20,13 +20,20 @@ Example:

### Optional Settings

- `db_host`: Database host if not `localhost`.
- `admin_password`: Default Password for "Administrator".
- `mute_emails`: Stops email sending if true.
- `deny_multiple_logins`: Stop users from having more than one active session.
- `root_password`: MariaDB root password.

### Defaut Outgoing Email Settings
### Remote Database Host Settings
- `db_host`: Database host if not `localhost`.

To connect to a remote database server using ssl, you must first configure the database host to accept SSL connections. An example of how to do this is available at https://www.digitalocean.com/community/tutorials/how-to-configure-ssl-tls-for-mysql-on-ubuntu-16-04. After you do the configuration, set the following three options. All options must be set for Frappe to attempt to connect using SSL.
- `db_ssl_ca`: Full path to the ca.pem file used for connecting to a database host using ssl. Example value is `"/etc/mysql/ssl/ca.pem"`.
- `db_ssl_cert`: Full path to the cert.pem file used for connecting to a database host using ssl. Example value is `"/etc/mysql/ssl/client-cert.pem"`.
- `db_ssl_key`: Full path to the key.pem file used for connecting to a database host using ssl. Example value is `"/etc/mysql/ssl/client-key.pem"`.

### Default Outgoing Email Settings

- `mail_server`: SMTP server hostname.
- `mail_port`: STMP port.


+ 61
- 0
frappe/docs/user/en/guides/desk/making_graphs.md Zobrazit soubor

@@ -0,0 +1,61 @@
# Making Graphs

The Frappe UI **Graph** object enables you to render simple line and bar graphs for a discreet set of data points. You can also set special checkpoint values and summary stats.

### Example: Line graph
Here's is an example of a simple sales graph:

render_graph: function() {
$('.form-graph').empty();

var months = ['Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
var values = [2410, 3100, 1700, 1200, 2700, 1600, 2740, 1000, 850, 1500, 400, 2013];

var goal = 2500;
var current_val = 2013;

new frappe.ui.Graph({
parent: $('.form-graph'),
width: 700,
height: 140,
mode: 'line-graph',

title: 'Sales',
subtitle: 'Monthly',
y_values: values,
x_points: months,

specific_values: [
{
name: "Goal",
line_type: "dashed", // "dashed" or "solid"
value: goal
},
],
summary_values: [
{
name: "This month",
color: 'green', // Indicator colors: 'grey', 'blue', 'red',
// 'green', 'orange', 'purple', 'darkgrey',
// 'black', 'yellow', 'lightblue'
value: '₹ ' + current_val
},
{
name: "Goal",
color: 'blue',
value: '₹ ' + goal
},
{
name: "Completed",
color: 'green',
value: (current_val/goal*100).toFixed(1) + "%"
}
]
});
},

<img src="{{docs_base_url}}/assets/img/desk/line_graph.png" class="screenshot">

Setting the mode to 'bar-graph':

<img src="{{docs_base_url}}/assets/img/desk/bar_graph.png" class="screenshot">

+ 0
- 0
frappe/docs/user/es/__init__.py Zobrazit soubor


+ 0
- 0
frappe/docs/user/es/bench/__init_.py Zobrazit soubor


+ 0
- 0
frappe/docs/user/es/bench/guides/__init__.py Zobrazit soubor


+ 28
- 0
frappe/docs/user/es/bench/guides/adding-custom-domains.md Zobrazit soubor

@@ -0,0 +1,28 @@
# Agregando dominios personalizados a su Site

Puedes agregar **multiples dominios personalizados** para un site, ejecutando el comando:

bench setup add-domain [dominio]

Al ejecutar el comando debes especificar para cual site quieres establecer el dominio personalizado.

También, puedes configurar el SSL para su dominio personalizado usando las opciones:

--ssl-certificate [ruta-al-certificado]
--ssl-certificate-key [ruta-a-la--clave-certificado]

Ejemplo:

bench setup add-domain custom.erpnext.com --ssl-certificate /etc/letsencrypt/live/erpnext.cert --ssl-certificate-key /etc/letsencrypt/live/erpnext.key

La configuración el dominio es almacenada en las configuraciones del site en su archivo site_config.json

"domains": [
{
"ssl_certificate": "/etc/letsencrypt/live/erpnext.cert",
"domain": "erpnext.com",
"ssl_certificate_key": "/etc/letsencrypt/live/erpnext.key"
}
],

**Luego debes regenerar las configuraciones de nginx ejecutando el comando `bench setup nginx` y reiniciando el servicio de nginx para que los cambios de los dominios tomen efecto**

+ 48
- 0
frappe/docs/user/es/bench/guides/configuring-https.md Zobrazit soubor

@@ -0,0 +1,48 @@
# Configurando HTTPS

### Obtener los archivos requeridos


Puede obtener un certificado SSL de una entidad emisora ​​de certificados de confianza o generar su propio certificado.
Para los certificados auto-firmados, el navegador mostrará una advertencia de que el certificado no es de confianza. [Aquí hay un tutorial para usar Let's Encrypt para obtener un certificado SSL gratuito](lets-encrypt-ssl-setup.html)

Los archivos obligatorios son:

* Certificado (Normalmente con extensión .crt)
* Clave privada descifrada

Si tienes varios certificados (primario e intermedios), tendrás que unirlos. Por ejemplo,

cat su_certificado.crt CA.crt >> certificate_bundle.crt

También asegúrese que su clave privada no sea legible. Generalmente, solo puede ser leída por root ya que normalmente es el dueño de la misma.

chown root private.key
chmod 600 private.key

### Mueva los dos archivos a una ruta confiable

mkdir /etc/nginx/conf.d/ssl
mv private.key /etc/nginx/conf.d/ssl/private.key
mv certificate_bundle.crt /etc/nginx/conf.d/ssl/certificate_bundle.crt

### Establecer configuraciones de nginx

Configura las rutas al certificado y la clave privada de su site.

bench set-ssl-certificate site1.local /etc/nginx/conf.d/ssl/certificate_bundle.crt
bench set-ssl-key site1.local /etc/nginx/conf.d/ssl/private.key

### Generar la configuració de nginx

bench setup nginx

### Reiniciar nginx

sudo service nginx reload

o

systemctl reload nginx # for CentOS 7

Ahora que tienes configurado el SSL, todo el tráfico HTTP va a ser redireccionado a HTTPS

+ 31
- 0
frappe/docs/user/es/bench/guides/diagnosing-the-scheduler.md Zobrazit soubor

@@ -0,0 +1,31 @@
<!-- markdown -->

En caso que estes experimentando inconvenientes con las tareas programadas, puedes ejecutar varios comandos para diagnosticar el problema.

### `bench doctor`

Esto va a mostrar en la consola lo siguiente en orden:
- El estado del Scheduler por site
- Número de Workers
- Tareas Pendientes


Salida deseada:

Workers online: 0
-----None Jobs-----

### `bench --site [site-name] show-pending-jobs`

Esto va a mostrar en la consola lo siguiente en orden:
- Cola
- Tareas dentro de Cola

Salida deseada:

-----Pending Jobs-----


### `bench purge-jobs`

Esto va a remover todas las tareas programadas de todas las colas.

+ 1
- 0
frappe/docs/user/es/bench/guides/index.md Zobrazit soubor

@@ -0,0 +1 @@
{index}

+ 11
- 0
frappe/docs/user/es/bench/guides/index.txt Zobrazit soubor

@@ -0,0 +1,11 @@
configuring-https
lets-encrypt-ssl-setup
diagnosing-the-scheduler
how-to-change-host-name-from-localhost
manual-setup
setup-multitenancy
setup-production
setup-ssl
stop-production-and-start-development
updating
setting-limits

+ 101
- 0
frappe/docs/user/es/bench/guides/lets-encrypt-ssl-setup.md Zobrazit soubor

@@ -0,0 +1,101 @@
# Uso de Let's Encrypt para configurar HTTPS

##Prerrequisitos

1. Necesitas tener una configuración Multitenant
2. Su sitio debería ser accesible a traves de un dominio válido
3. Necesitas permisos de administrados en el servidor

**Nota : Los certificados de Let's Encrypt expiran cada 3 meses**

## Usando el comando bench

Ejecutar:

sudo -H bench setup lets-encrypt [site-name]

Van a aparecer varios prompts, responde a todo. Este comando también va a agregar una entrada a crontab del usuario que esta intentando renovar el certificado cada mes.

### Dominios personalizados

Puedes configurar Let's Encrypt para [dominios personalizados](adding-custom-domains.html). Solo usando la opción `--custom-domain`

sudo -H bench setup lets-encrypt [site-name] --custom-domain [custom-domain]

### Renovar Certificados

Para la renovación manual de certificados puedes usar:

sudo bench renew-lets-encrypt

<hr>

## Método Manual
### Descarga el script apropiado de Certbot-auto en el directorio /opt

https://certbot.eff.org/

### Detener el servicio / proceso nginx

$ sudo service nginx stop

### Ejecutar Certbot

$ ./opt/certbot-auto certonly --standalone

Despues que letsencrypt se inicializa, vas a tener que llenar algunas informaciones. Los prompts pueden variar de si haz usado o no Let's Encrypt antes, pero vamos a guiarte en su primera vez.

En el prompt, ingresar la dirección de correo eléctronico que será usada para notificaciones y recuperación de claves perdidas:

![](https://assets.digitalocean.com/articles/letsencrypt/le-email.png)

Debes aceptar el acuerdo de subscripción de Let's Encrypt, selecciona Agree:

![](https://assets.digitalocean.com/articles/letsencrypt/le-agreement.png)

Luego ingresa el nombre de su dominio(s). Nota que si deseas un simple certificado para trabajar con
varios nombres de dominios (ejemplo: example.com y www.example.com), asegurate de incluirlos todos:

![](https://assets.digitalocean.com/articles/letsencrypt/le-domain.png)

### Archivos de certificados

Despues de obtener el certificado, va a tener los siguientes archivos PEM-encoded:

* **cert.pem**: El certificado de su dominio
* **chain.pem**: La cadena del certificado de Let's Encrypt
* **fullchain.pem**: cert.pem y chain.pem combinados
* **privkey.pem**: La clave privada de su certificado.

Estos certificados estan almacenados en el directorio `/etc/letsencrypt/live/example.com`

### Configurar los certificados para su site(s)

Vaya al archivo site_config.json del site donde tiene erpnext

$ cd frappe-bench/sites/{{nombre_sitio}}

Agrega las siguientes lineas al archivo site_config.json

"ssl_certificate": "/etc/letsencrypt/live/example.com/fullchain.pem",
"ssl_certificate_key": "/etc/letsencrypt/live/example.com/privkey.pem"


Regenerar las configuraciones de nginx

$ bench setup nginx

Reiniciar el servidor nginx

$ sudo service nginx restart

---

### Renovació Automática (experimental)

Accede como root o como un usuario con privileges de administrador, ejecuta `crontab -e` y presiona enter:


# renovar el certificado de letsencrypt todos los días primero de cada mes y recibe un email si el comando ha sido ejecutado
MAILTO="mail@example.com"
0 0 1-7 * * [ "$(date '+\%a')" = "Mon" ] && sudo service nginx stop && /opt/certbot-auto renew && sudo service nginx start

+ 74
- 0
frappe/docs/user/es/bench/guides/manual-setup.md Zobrazit soubor

@@ -0,0 +1,74 @@
Instalación Manual
--------------

Prerrequisitos,

* [Python 2.7](https://www.python.org/download/releases/2.7/)
* [MariaDB](https://mariadb.org/)
* [Redis](http://redis.io/topics/quickstart)
* [WKHTMLtoPDF con QT parcheado](http://wkhtmltopdf.org/downloads.html) (Requerido para la generación de pdf)

[Instalando los Prerrequisitos en OSX](https://github.com/frappe/bench/wiki/Installing-Bench-Pre-requisites-on-MacOSX)

Instalar el bench como usuario normal, **no root**

git clone https://github.com/frappe/bench bench-repo
sudo pip install -e bench-repo

Nota: Favor de no remover el directorio bench que el comando va a crear

Migrando desde una instalación existente
------------------------------------

Si deseas migrar desde ERPNext v3, sigue las siguientes instrucciones [aquí](https://github.com/frappe/bench/wiki/Migrating-from-ERPNext-version-3)

Si deseas migrar de una versión vieja del bench, sigue las instrucciones [aquí](https://github.com/frappe/bench/wiki/Migrating-from-old-bench)


Uso básico
===========

* Crea un nuevo bench

El comando init va a crear un directorio conteniendo el framework Frappe instalado.
Va a ser configurado para copias de seguridad periódicas y actualizaciones automáticas una vez por día.

bench init frappe-bench && cd frappe-bench

* Agregar aplicaciones

El comando get-app descarga e instala aplicaciones hechas en frappe. Ejemplos:

- [erpnext](https://github.com/frappe/erpnext)
- [erpnext_shopify](https://github.com/frappe/erpnext_shopify)
- [paypal_integration](https://github.com/frappe/paypal_integration)

bench get-app erpnext https://github.com/frappe/erpnext

* Agregar Site

Las aplicaciones Frappe son montadas en los Sites y por tanto tendras que crear por lo menos un site.
El comando new-site te permite crearlos.

bench new-site site1.local

* Iniciar bench

Para comenzar a utilizar el bench, usa el comando `bench start`

bench start

Para acceder a Frappe / ERPNext, abra su navegador favorito y escriba la ruta `localhost:8000`

El usuario por defecto es "Administrator" y la contraseña es la que específicaste al momento de crear el nuevo site.


Configurando ERPNext
==================

Para instalar ERPNext, ejecuta:
```
bench install-app erpnext
```

Ahora puedes usar `bench start` o [configurar el bench para uso en producción](setup-production.html)

+ 39
- 0
frappe/docs/user/es/bench/guides/settings-limits.md Zobrazit soubor

@@ -0,0 +1,39 @@
# Estableciendo límites para su sitio

La versión 7 de Frappe ha agregado soporte para la configuración de límites y restricciones para su site.
Estas restricciones están en el archivo `site_config.json` dentro de la carpeta del site.

{
"db_name": "xxxxxxxxxx",
"db_password": "xxxxxxxxxxxx",
"limits": {
"emails": 1500,
"space": 0.157,
"expiry": "2016-07-25",
"users": 1
}
}

Puedes establecer un límite ejecutando:

bench --site [nombre_sitio] set-limit [limite] [valor]

Puedes establecer varios límites al mismo tiempo ejecutando:

bench --site [nombre_sitio] set-limits --limit [limite] [valor] --limit [limite-2] [valor-2]

Los límites que puedes configurar son:

- **users** - Limita el número de usuarios por site.
- **emails** - Limita el número de correos enviados por mes desde un site.
- **space** - Limita el máximo número de espacio en GB que el site puede usar.
- **email_group** - Limia el número máximo de miembros en un grupo de correos.
- **expiry** - Fecha de expiración para el site. (YYYY-MM-DD en de comillas)

Ejemplo:

bench --site site1.local set-limit users 5

Puedes verificar el uso abriendo la página de "Usage Info" ubicada en el toolbar / AwesomeBar. Un límite solo va a mostrarse en la página si ha sido configurado.

<img class="screenshot" alt="Doctype Saved" src="{{docs_base_url}}/assets/img/usage_info.png">

+ 57
- 0
frappe/docs/user/es/bench/guides/setup-multitenancy.md Zobrazit soubor

@@ -0,0 +1,57 @@
Asumiento que tiene su primer site corriendo y ha realizado los
[pasos para producción](setup-production.html), esta sección explica como montar su segundo site (y más).
Su primer site se configuró como el site por defecto de forma automática. Puedes cambiarlo ejecutando el comando,

bench use nombre_site




Multitenancy basada en puertos
-----------------------

Puedes crear un nuevo site y ponerlo a escuchar por otro puerto (mientras que el primero corre en el puerto 80)

* Desactivar el multitenancy basada en DNS (una vez)

`bench config dns_multitenant off`

* Crea un nuevo site

`bench new-site site2name`

* Configura el puerto

`bench set-nginx-port site2name 82`

* Regenera las configuraciones de nginx

`bench setup nginx`

* Recarga el servicio de nginx

`sudo service nginx reload`


Multitenancy basada en DNS
----------------------

Puedes nombrar sus sites como los los nombre de dominio que van a rederigirse a ellos. Así, todos los sites agregados al bench van a correr en el mismo puerto y van a ser automáticamente seleccionados basados en el nombre del host.

Para convertir un site nuevo dentro de la multitenancy basada en DNS, realiza los siguientes pasos.

* Desactivar el multitenancy basada en DNS (una vez)

`bench config dns_multitenant on`

* Crea un nuevo site

`bench new-site site2name`

* Regenera las configuraciones de nginx

`bench setup nginx`

* Recarga el servicio de nginx

`sudo service nginx reload`

+ 48
- 0
frappe/docs/user/es/bench/guides/setup-production.md Zobrazit soubor

@@ -0,0 +1,48 @@
Puedes configurar el bench para producción configurando dos parametros, Supervisor y nginx. Si quieres volver a ponerlo en desarrollo debes ver [estos comandos](https://github.com/frappe/bench/wiki/Stopping-Production-and-starting-Development)

####Configuración para producción facíl
Estos pasos son automátizados si ejecutas `sudo bench setup production`


####Configuración manual para producción
Supervisor
----------
Supervisor se asegura de mantener el proceso que inició Frappe corriendo y lo reinicia en caso de cualquier inconveniente.
Puedes generar la configuración necesaria para supervisor ejecutando el comando `bench setup supervisor`.
La configuración va a estar disponible en la carpeta `config/supervisor.conf`. Luego puedes copiar/enlazar este archivo al directorio de configuración
de supervisor y reiniciar el servicio para que tome efecto de los cambios realizados.

Ejemplo,

```
bench setup supervisor
sudo ln -s `pwd`/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.conf
```

Nota: Para CentOS 7, la extensión debería ser `ini`, por lo que el comando sería
```
bench setup supervisor
sudo ln -s `pwd`/config/supervisor.conf /etc/supervisor/conf.d/frappe-bench.ini #para CentOS 7 solamente
```

El bench también necesita reiniciar el proceso manejado por supervisor cuando actualizar cualquier aplicación.
Para automatizarlo, vas a tener que agregar el usuario a sudoers ejecutando `sudo bench setup sudoers $(whoami)`.

Nginx
-----


Nginx es un servidor web y lo usamos para servir archivos estáticos y aponderar el resto de la
peticiones a frappe. Puedes generar las configuraciones necesarias para nginx usando el comando `bench setup nginx`.
La configuración va a estar almacenada en el archivo `config/nginx.conf`. Entonces puedes copiar/enlazar este archivo al directorio de
configuración de nginx y reiniar el servicio para poder ver si se han aplicado los cambios.

Ejemplo,

```
bench setup nginx
sudo ln -s `pwd`/config/nginx.conf /etc/nginx/conf.d/frappe-bench.conf
```

Nota: Cuando reinicias nginx despues de cualquier cambio en la configuración, podría fallar si tienes otra configuración con el bloque server para el puerto 80 (En la mayoría de veces la página princial de nginx). Vas a tener que deshabilitar esta configuración. Las rutas más probables donde podemos encontrarlo son `/etc/nginx/conf.d/default.conf` y
`/etc/nginx/conf.d/default`.

+ 1
- 0
frappe/docs/user/es/bench/index.md Zobrazit soubor

@@ -0,0 +1 @@
{index}

+ 2
- 0
frappe/docs/user/es/bench/index.txt Zobrazit soubor

@@ -0,0 +1,2 @@
guides
resources

+ 0
- 0
frappe/docs/user/es/bench/resources/__init__.py Zobrazit soubor


+ 29
- 0
frappe/docs/user/es/bench/resources/background-services.md Zobrazit soubor

@@ -0,0 +1,29 @@
Servicios Externos
-----------------

* MariaDB (Base de datos)
* Redis (Caché y background workers)
* nginx (para producción)
* supervisor (para producción)

Procesos de Frappe
----------------


* Servidor WSGI

* El servidor WSGI es responsable de responder a las peticiones HTTP.
En entornos de desarrollo (`bench serve` o `bench start`), El servidor WSGI Werkzeug es usado y en producción,
se usa gunicorn (automáticamente configurado en supervisor)

* Procesos de Redis Worker

* Los procesos de Celery se encargan de ejecutar tareas en background en Frappe.
Estos procesos son iniciados automáticamente cuando se ejecuta el comando `bench start` y
para producción se configuran en las configuraciones de supervisor.

* Procesos Scheduler

* Los procesos del Scheduler programan la lista de tareas programadas en Frappe.
Este proceso es iniciado automáticamente cuando se ejecuta el comando `bench start` y
para producción se configuran en las configuraciones de supervisor.

+ 90
- 0
frappe/docs/user/es/bench/resources/bench-commands-cheatsheet.md Zobrazit soubor

@@ -0,0 +1,90 @@
### Uso General
* `bench --version` - Muestra la versión del bench
* `bench src` - Muestra el directorio repo del bench
* `bench --help` - Muestra todos los comandos y ayudas
* `bench [command] --help` - Muestra la ayuda para un comando
* `bench init [bench-name]` - Crea un nuevo bench (Ejecutar desde Home)
* `bench --site [site-name] COMMAND` - Especificar un site para el comando
* `bench update` - Buscar los últimos cambios de bench-repo y todas las aplicaciones, aplica parches, crea los JS y CSS, y realiza la migración.
* `--pull` Hace un Pull a todas las aplicaciones en el bench
* `--patch` Ejecuta las migraciones para todos los sites en el bench
* `--build` Crea los JS y CSS para el bench
* `--bench` Actualiza el bench
* `--requirements` Actualiza los requerimientos
* `--restart-supervisor` Reinicia los procesos de supervisor despues de actualizar
* `--upgrade` Realiza migraciones mayores (Eg. ERPNext 6 -> 7)
* `--no-backup` No crea una copia de respaldo antes de actualizar
* `bench restart` Reinicia todos los servicios del bench
* `bench backup` Copia de respaldo
* `bench backup-all-sites` Copia de respaldo a todos los sites
* `--with-files` Copia de respaldo a los sites con los archivos
* `bench restore` Restaurar
* `--with-private-files` Restaura un site con todos los archivos privados (Ruta al archivo .tar)
* `--with-public-files` Restaura un site con todos los archivos públicos (Ruta al archivo .tar)
* `bench migrate` Leerá los archivos JSON y realizará los cambios en la base de datos.

###Configuración
* `bench config` - Cambiar las configuraciones del bench
* `auto_update [on/off]` Activa/Desactiva las actualizaciones automáticas para el bench
* `dns_multitenant [on/off]` Activa/Desactiva DNS Multitenancy
* `http_timeout` Establece un timeout para http
* `restart_supervisor_on_update` Activa/Desactiva el reinicio automático de supervisor
* `serve_default_site` Configurar nginx para que sirva el sitio predeterminado en...
* `update_bench_on_update` Activa/Desactiva las actualizaciones en un bench corriendo
* `bench setup` - Configurar componentes
* `auto-update` Añade un cronjob para actualizaciones automática del bench
* `backups ` Añade un cronjob para las copias de respaldo del bench
* `config ` sobreescribe o crea config.json
* `env ` Configurar un virtualenv para el bench
* `nginx ` generar configuraciones para nginx
* `procfile ` Configura el archivo Procfile para bench start
* `production ` Configura el bench para producción
* `redis ` genera las configuraciones para redis cache
* `socketio ` Configura las dependencias de node para el servidor socketio
* `sudoers ` Agrega comandos a la sudoers para su ejecución
* `supervisor ` Genera las configuraciones para supervisor
* `add-domain ` agrega un dominio personalizado para un site
* `firewall ` configura un firewall y bloquea todos los puertos en excepción el 22, 80 y 443
* `ssh-port ` cambia el puerto por defecto para conexiones ssh


###Desarrollo
* `bench new-app [app-name]` Crea una nueva app
* `bench get-app [repo-link]` - Descarga una app desde un repositorio git y la instala
* `bench install-app [app-name]` Instala aplicaciones existentes
* `bench remove-from-installed-apps [app-name]` Remueve aplicaciones de la liste de aplicaciones
* `bench uninstall-app [app-name]` Elimina la aplicación y todo lo relaciones a esa aplicación (Bench necesita estar corriendo)
* `bench remove-app [app-name]` Eliminar una aplicación completamente del bench
* `bench --site [sitename] --force reinstall ` Reiniciar con una base de datos nueva (Atención: Va a borrar la base de datos anterior)
* `bench new-site [sitename]` - Crea un nuevo site
* `--db-name` Nombre de la base de datos
* `--mariadb-root-username` Nombre de usuario de Root
* `--mariadb-root-password` Contraseña del usuario Root
* `--admin-password` Contraseña del usuario Administrator para un nuevo site
* `--verbose` Verbose
* `--force` Forzar la restauración si el site/base de datos existen.
* `--source_sql` Inicializar una base de datos con un archivo SQL
* `--install-app` Instalar una aplicación despues de haber instalado el bench
* `bench use [site]` Configura el site por defecto
* `bench drop-site` Elimina sites del disco y la base de datos completamente
* `--root-login`
* `--root-password`
* `bench set-config [key] [value]` Agrega valores clave-valor al archivo de configuración del site
* `bench console` Abre una consola de IPython en el virtualenv del bench
* `bench execute` Ejecuta un método dentro de una aplicación
* Eg : `bench execute frappe.utils.scheduler.enqueue_scheduler_events`
* `bench mysql` Abre una consola SQL
* `bench run-tests` Ejecuta las pruebas
* `--app` Nombre de la aplicación
* `--doctype` Especificar el DocType para cual correr las pruebas
* `--test` Pruebas específicas
* `--module` Ejecutar un módulo con pruebas en específico
* `--profile` Ejecutar un Python profiler en las pruebas
* `bench disable-production` Desactiva el entorno de producción


###Programador
* `bench enable-scheduler` - Habilita el Programador que ejecutará las tareas programadas
* `bench doctor` - Obtener informaciones de diagnóstico sobre los background workers
* `bench show-pending-jobs`- Obtener las tareas pendientes
* `bench purge-jobs` - Eliminar todas las tareas pendientes

+ 31
- 0
frappe/docs/user/es/bench/resources/bench-procfile.md Zobrazit soubor

@@ -0,0 +1,31 @@
`bench start` usa [honcho](http://honcho.readthedocs.org) para manejar múltiples procesos en **developer mode**.

### Procesos

Los diversos procesos que se necesitan para correr frappe son:

1. `bench start` - El servidor web.
4. `redis_cache` para cache (general)
5. `redis_queue` para manejar las cosas de los background workers
6. `redis_socketio` como un notificador de notificaciones para actualizaciones en tiempo real desde los background workers
7. `web` para el servidor web de frappe.
7. `socketio` para mensajes en tiempo real.
3. `schedule` para disparar tareas periódicas
3. `worker_*` redis workers para manejar trabajos aíncronos

Opcionalmente, si estas desarrollando en frappe puedes agregar:

`bench watch` para automáticamente construir la aplicación javascript desk.

### Ejemplo

redis_cache: redis-server config/redis_cache.conf
redis_socketio: redis-server config/redis_socketio.conf
redis_queue: redis-server config/redis_queue.conf
web: bench serve --port 8000
socketio: /usr/bin/node apps/frappe/socketio.js
watch: bench watch
schedule: bench schedule
worker_short: bench worker --queue short
worker_long: bench worker --queue long
worker_default: bench worker --queue default

+ 1
- 0
frappe/docs/user/es/bench/resources/index.md Zobrazit soubor

@@ -0,0 +1 @@
{index}

+ 3
- 0
frappe/docs/user/es/bench/resources/index.txt Zobrazit soubor

@@ -0,0 +1,3 @@
background-services
bench-commands-cheatsheet
bench-procfile

+ 0
- 0
frappe/docs/user/es/guides/__init_.py Zobrazit soubor


+ 3
- 0
frappe/docs/user/es/index.md Zobrazit soubor

@@ -0,0 +1,3 @@
# Desarrollo de aplicaciones con Frappe

{index}

+ 4
- 0
frappe/docs/user/es/index.txt Zobrazit soubor

@@ -0,0 +1,4 @@
tutorial
bench
guides
videos

+ 0
- 0
frappe/docs/user/es/tutorial/__init_.py Zobrazit soubor


+ 10
- 0
frappe/docs/user/es/tutorial/app.md Zobrazit soubor

@@ -0,0 +1,10 @@
# Qué es una aplicación

Una aplicación en Frappe es una aplicación estandar en Python. Puedes estructurar una aplicación hecha en Frappe de la misma forma que estructuras una aplicación en Python.
Para implementación, Frappe usa los Python Setuptools, lo que nos permite facilmente instalar la aplicación en cualquier computadora.

El Framework Frappe provee una interfaz WSGI y para el desarrollo puedes usar el servidor interno de frappe llamado Werkzeug. Para implementación en producción, recomendamos usar nginx y gunicorn.

Frappe tambien soporta la architectura multi-tenant. Esto significa que puedes correr varios "sitios" en su instalación, cada uno de ellos estará poniendo a disposición un conjunto de aplicaciones y usuarios. La base de datos para cada sitio es separada.

{next}

+ 71
- 0
frappe/docs/user/es/tutorial/before.md Zobrazit soubor

@@ -0,0 +1,71 @@
# Antes de empezar

<p class="lead">Una lista de recursos que te ayudaran a inicar con el desarrollo de aplicaciones usando Frappe.</p>

---

#### 1. Python

Frappe usa Python (v2.7) como lenguaje de parte del servidor. Es altamente recomendable aprender Python antes de iniciar a crear aplicaciones con Frappe.

Para escribir código de calidad del lado del servidor, también debes incluir pruebas automatizadas.

Recursos:
1. [Tutorial sobre Python de Codecademy](https://www.codecademy.com/learn/python)
1. [Tutorial Oficial de Python](https://docs.python.org/2.7/tutorial/index.html)
1. [Tutorial básico de Test-driven development](http://code.tutsplus.com/tutorials/beginning-test-driven-development-in-python--net-30137)

---

#### 2. MariaDB / MySQL

Para crear aplicaciones con frappe, debes entender los conceptops básicos del manejo de base de datos, como instalarlas, acceder, crear nueva base de datos, y hacer consultas básicas con SQL.

Recursos:
1. [Tutorial sobre SQL de Codecademy](https://www.codecademy.com/learn/learn-sql)
1. [Tutorial Básico de MySQL de DigitalOcean](https://www.digitalocean.com/community/tutorials/a-basic-mysql-tutorial)
1. [Introducción a MariaDB](https://mariadb.com/kb/en/mariadb/documentation/getting-started/)

---

#### 3. HTML / CSS

Si quieres construir interfaces de usuario usando Frappe, necesitas aprender los conceptops básicos de HTML / CSS y el framework de CSS Bootstrap.

Recursos:
1. [Tutorial sobre HTML/CSS de Codecademy](https://www.codecademy.com/learn/learn-html-css)
1. [Introducción a Bootstrap](https://getbootstrap.com/getting-started/)

---

#### 4. JavaScript and jQuery

Para modificar formularios y crear interfaces de usuarios interactivas, deberías aprender JavaScript y la librería JQuery.


Recursos:
1. [Tutorial sobre JavaScript de Codecademy](https://www.codecademy.com/learn/learn-javascript)
1. [Tutorial sobre jQuery de Codecademy](https://www.codecademy.com/learn/jquery)
---

#### 5. Manejar de plantillas Jinja

Si estas modificando plantillas de Impresión o Páginas Web, tienes que aprender a utilizar el manejar de plantillas Jinja. Es una forma facíl de crear páginas web dinámicas.

Recursos:
1. [Primer on Jinja Templating](https://realpython.com/blog/python/primer-on-jinja-templating/)
1. [Documentación oficial](http://jinja.pocoo.org/)

---

#### 6. Git and GitHub

Aprende como contribuir en un proyecto de código abierto usando Git y GitHub, dos increíbles herramientes que te ayudan a gestionar tu código y compartirlo con otros.

Recursos:
1. [Tutorial Básico de Git](https://try.github.io)
2. [Cómo contribuir al Código Abierto](https://opensource.guide/how-to-contribute/)

---

Cuando estes listo, puedes intentar [crear una aplicación simple]({{ docs_base_url }}/user/es/tutorial/app) usando Frappe.

+ 11
- 0
frappe/docs/user/es/tutorial/bench.md Zobrazit soubor

@@ -0,0 +1,11 @@
# Instalando el Frappe Bench

La forma más facíl de configurar frappe en un computador usando sitemas basados en Unix es usando frappe-bench. Lee las instrucciones detalladas acerca de como instalarlo usando Frappe Bench.

> [https://github.com/frappe/bench](https://github.com/frappe/bench)

Con Frappe Bench vas a poder configurar y hostear multiples aplicaciones y sitios, también va a configurar un entorno virtual de Python por lo que vas a tener un entorno apartado para correr sus aplicaciones (y no va a tener conflictos de versiones con otros entornos de desarrollo).

El comando `bench` va a ser instalado en su sistema para ayudarlo en la fase de desarrollo y el manejo de la aplicación.

{next}

+ 5
- 0
frappe/docs/user/es/tutorial/conclusion.md Zobrazit soubor

@@ -0,0 +1,5 @@
# Conclusión

Esperamos que esto te haya dado una idea de como son desarrolladas las aplicaicones en Frappe. El objetivo era de que de manera breve se tocaran varios de los aspectos del desarrollo de aplicaciones. Para obtener ayuda en inconvenientes o temas específicos, favor revisar el API.

Para ayuda, únete a la comunidad en el [canal de chat en Gitter](https://gitter.im/frappe/erpnext) o el [foro de desarrollo](https://discuss.erpnext.com)

+ 59
- 0
frappe/docs/user/es/tutorial/controllers.md Zobrazit soubor

@@ -0,0 +1,59 @@
# Controladores (Controllers)

El siguiente paso va a ser agregar métodos y eventos a los modelos. En la aplicación, debemos asegurar que si una Library Transaction es creada, el Article que se solicita debe estar en disponibilidad y el miembro que lo solicita debe tener una membresía (membership) válida.

Para esto, podemos escribir una validación que se verifique justo en el momento que una Library Transaction es guardada. Para lograrlo, abre el archivo `library_management/doctype/library_transaction/library_transaction.py`.

Este archivo es el controlador para el objeto Library Transaction. En este archivo puedes escribir métodos para:

1. `before_insert`
1. `validate` (Antes de insertar o actualizar)
1. `on_update` (Despues de guardar)
1. `on_submit` (Cuando el documento es presentado como sometido o presentado)
1. `on_cancel`
1. `on_trash` (antes de ser eliminado)

Puedes escribir métodos para estos eventos y estos van a ser llamados por el framework automóticamente cuando el documento pase por uno de esos estados.

Aquí les dejo el controlador completo:

from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document

class LibraryTransaction(Document):
def validate(self):
last_transaction = frappe.get_list("Library Transaction",
fields=["transaction_type", "transaction_date"],
filters = {
"article": self.article,
"transaction_date": ("<=", self.transaction_date),
"name": ("!=", self.name)
})
if self.transaction_type=="Issue":
msg = _("Article {0} {1} no ha sido marcado como retornado desde {2}")
if last_transaction and last_transaction[0].transaction_type=="Issue":
frappe.throw(msg.format(self.article, self.article_name,
last_transaction[0].transaction_date))
else:
if not last_transaction or last_transaction[0].transaction_type!="Issue":
frappe.throw(_("No puedes retornar un Article que no ha sido prestado."))

En este script:

1. Obtenemos la última transacción antes de la fecha de la transacción actual usando la funcion `frappe.get_list`
1. Si la última transacción es algo que no nos gusta, lanzamos una excepción usando `frappe.throw`
1. Usamos el método `_("texto")` para identificar las cadenas que pueden ser traducidas.

Verifica si sus validaciones funcionan creando nuevos registros.

<img class="screenshot" alt="Transaction" src="{{docs_base_url}}/assets/img/lib_trans.png">

#### Depurando

Para depurar, siempre mantener abierta su consola JS. Verifíca rastros de Javascript y del Servidor.

Siempre verifica su terminal para las excepciones. Cualquier **500 Internal Server Errors** va a ser mostrado en la terminal en la que está corriendo el servidor.

{next}

+ 31
- 0
frappe/docs/user/es/tutorial/doctype-directory-structure.md Zobrazit soubor

@@ -0,0 +1,31 @@
# Estructura de directorios de un DocType

Despues de guardar los DocTypes, revisa que los archivos `.json` y `.py` fuueron creados en módulo `apps/library_management/library_management`. La estructura de directorios despues de crear los modelos debería ser similar a la siguiente:

.
├── MANIFEST.in
├── README.md
├── library_management
..
│   ├── library_management
│   │   ├── __init__.py
│   │   └── doctype
│   │   ├── __init__.py
│   │   ├── article
│   │   │   ├── __init__.py
│   │   │   ├── article.json
│   │   │   └── article.py
│   │   ├── library_member
│   │   │   ├── __init__.py
│   │   │   ├── library_member.json
│   │   │   └── library_member.py
│   │   ├── library_membership
│   │   │   ├── __init__.py
│   │   │   ├── library_membership.json
│   │   │   └── library_membership.py
│   │   └── library_transaction
│   │   ├── __init__.py
│   │   ├── library_transaction.json
│   │   └── library_transaction.py

{next}

+ 96
- 0
frappe/docs/user/es/tutorial/doctypes.md Zobrazit soubor

@@ -0,0 +1,96 @@
# DocType

Despues de crear los Roles, vamos a crear los **DocTypes**

Para crear un nuevo **DocType**, ir a:

> Developer > Documents > Doctype > New

<img class="screenshot" alt="New Doctype" src="{{docs_base_url}}/assets/img/doctype_new.png">

En el DocType, primero el módulo, lo que en nuestro caso es **Library Management**

#### Agregando Campos

En la tabla de campos, puedes agregar los campos (propiedades) de el DocType (Article).

Los campos son mucho más que solo columnas en la base de datos, pueden ser:
Fields are much more than database columns, they can be:

1. Columnas en la base de datos
1. Ayudantes de diseño (definidores de secciones / columnas)
1. Tablas hijas (Tipo de dato Table)
1. HTML
1. Acciones (botones)
1. Adjuntos o Imagenes

Vamos a agregar los campos de el Article.

<img class="screenshot" alt="Adding Fields" src="{{docs_base_url}}/assets/img/doctype_adding_field.png">

Cuando agredas los campos, necesitas llenar el campo **Type**. **Label** es opcional para los Section Break y Column Break. **Name** (`fieldname`) es el nombre de la columna en la tabla de la base de datos y tambien el nombre de la propiedad para el controlador. Esto tiene que ser *code friendly*, i.e. Necesitas poner _ en lugar de " ". Si dejas en blanco este campo, se va a llenar automáticamente al momento de guardar.

Puedes establecer otras propiedades al campo como si es obligatorio o no, si es de solo lectura, etc.

Podemos agregar los siguientes campos:

1. Article Name (Data)
2. Author (Data)
3. Description
4. ISBN
5. Status (Select): Para los campos de tipo Select, vas a escribir las opciones. Escribe **Issued** y **Available** cada una en una linea diferente en la caja de texto de Options. Ver el diagrama más abajo.
6. Publisher (Data)
7. Language (Data)
8. Image (Adjuntar Imagen)


#### Agregar permisos

Despues de agregar los campos, dar click en hecho y agrega una nueva fila en la sección de Permission Roles. Por ahora, vamos a darle accesos Lectura, Escritura, Creación y Reportes al Role **Librarian**. Frappe cuenta con un sistema basados en el modelo de Roles finamente granulado. Puedes cambiar los permisos más adealante usando el **Role Permissions Manager** desde **Setup**.

<img class="screenshot" alt="Adding Permissions" src="{{docs_base_url}}/assets/img/doctype_adding_permission.png">

#### Guardando

Dar click en el botón de **Guardar**. Cuando el botón es clickeado, una ventana emergente le va a preguntar por el nombre. Vamos a darle el nombre de **Article** y guarda el DocType.

Ahora accede a mysql y verifica que en la base de datos que se ha creado una nueva tabla llamada tabArticle.

$ bench mysql
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 3931
Server version: 5.5.36-MariaDB-log Homebrew

Copyright (c) 2000, 2014, Oracle, Monty Program Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [library]> DESC tabArticle;
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| name | varchar(255) | NO | PRI | NULL | |
| creation | datetime(6) | YES | | NULL | |
| modified | datetime(6) | YES | | NULL | |
| modified_by | varchar(40) | YES | | NULL | |
| owner | varchar(60) | YES | | NULL | |
| docstatus | int(1) | YES | | 0 | |
| parent | varchar(255) | YES | MUL | NULL | |
| parentfield | varchar(255) | YES | | NULL | |
| parenttype | varchar(255) | YES | | NULL | |
| idx | int(8) | YES | | NULL | |
| article_name | varchar(255) | YES | | NULL | |
| status | varchar(255) | YES | | NULL | |
| description | text | YES | | NULL | |
| image | varchar(255) | YES | | NULL | |
| publisher | varchar(255) | YES | | NULL | |
| isbn | varchar(255) | YES | | NULL | |
| language | varchar(255) | YES | | NULL | |
| author | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+-------+
18 rows in set (0.00 sec)

Como puedes ver, junto con los DocFields, algunas columnas fueron agregadas a la tabla. Las importantes a notar son, la clave primaria, `name`, `owner`(El usuario que creo el registro),
`creation` y `modified` (timestamps para la creación y última modificación).

{next}

+ 39
- 0
frappe/docs/user/es/tutorial/form-client-scripting.md Zobrazit soubor

@@ -0,0 +1,39 @@
## Añadir Scripts a nuestros formularios

Ya que tenemos creado el sistema básico que funciona sin problemas sin escribir una linea de código. Vamos a escribir algunos scripts
para hablar la aplicación más interactiva y agregar validaciones para que el usuario no pueda introducir información erronea.

### Scripts del lado del Cliente

En el DocType **Library Transaction**, solo tenemos campo para el Nombre del miembro. No hemos creado dos campos. Esto podría ser dos campos (y probablemente debería), pero para los motivos del ejemplo, vamos a considerar que tenemos que implementarlo así. Para hacerlo vamos a tener que escribir un manejador de eventos para el evento que ocurre cuando el usuario selecciona el campo `library_member` y luego accede a la información del miembro desde el servidor usando el REST API y cambia los valores en el formulario.

Para empezar el script, en el directorio `library_management/doctype/library_transaction`, crea un nuevo archivo `library_transaction.js`.
Este archivo va a ser ejecutado automáticamente cuando la primer Library Transaction es abierta por el usuario. En este archivo, podemos establecer eventos y escribir otras funciones.

#### library_transaction.js

frappe.ui.form.on("Library Transaction", "library_member",
function(frm) {
frappe.call({
"method": "frappe.client.get",
args: {
doctype: "Library Member",
name: frm.doc.library_member
},
callback: function (data) {
frappe.model.set_value(frm.doctype,
frm.docname, "member_name",
data.message.first_name
+ (data.message.last_name ?
(" " + data.message.last_name) : ""))
}
})
});

1. **frappe.ui.form.on(*doctype*, *fieldname*, *handler*)** es usada para establecer un manejador de eventos cuando la propiedad library_member es seleccionada.
1. En el manejador, vamos a disparar una llamada AJAX a `frappe.client.get`. En respuesta obtenemos el objeto consultado en formato JSON. [Aprende más acerca del API](/frappe/user/en/guides/integration/rest_api).
1. Usando **frappe.model.set_value(*doctype*, *name*, *fieldname*, *value*)** cambiamos el valor en el formulario.

**Nota:** Para verificar si su script funciona, recuerda Recargar/Reload la página antes de probar el script. Los cambios realizados a los script del lado del Cliente no son automáticamente cargados nuevamente cuando estas en modo desarrollador.

{next}

+ 34
- 0
frappe/docs/user/es/tutorial/index.md Zobrazit soubor

@@ -0,0 +1,34 @@
# Tutorial sobre Frappe

En esta guía, vamos a mostrarte como crear una aplicación desde cero usando **Frappe**. Usando el ejemplo de un Sistema de Gestión de Librería. Vamos a cubrir:

1. Instalación
1. Creando una nueva App
1. Creando Modelos
1. Creando Usuarios y Registros
1. Creando Controladores
1. Creando Vistas Web
1. Configurando Hooks y Tareas

## Para Quién es este tutorial?

Esta guía esta orientada para desarrolladores de software que estan familiarizados con el proceso de como son creadas y servidas las aplicaciones web. El Framework Frappe está escrito en Python y usa MariaDB como base de datos y para la creación de las vistas web usa HTML/CSS/Javascript. Por lo que sería excelente si estas familiarizado con estas tecnologías.
Por lo menos, si nunca haz usado Python antes, deberías tomar un tutorial rápido antes de iniciar con este tutorial.

Frappe usa el sistema de gestión de versiones en GitHub. También, es importante estar familiarizado con los conceptos básicos de git y tener una cuenta en GitHub para manejar sus aplicaciones.

## Ejemplo

Para esta guía, vamos a crear una aplicación simple llamada **Library Management**. En esta aplicación vamos a tener los siguientes modelos (Permanecerán en inglés para que coincidan con las imagenes):

1. Article (Libro o cualquier otro artículo que pueda ser prestado)
1. Library Member
1. Library Transaction (Entrega o Retorno de un artículo)
1. Library Membership (Un período en el que un miembro esta permitido hacer una trasacción)
1. Library Management Setting (Configuraciones generales, como el tiempo que dura el prestamo de un artículo)

La interfaz de usuario (UI) para la aplicación va a ser el **Frappe Desk**, un entorno para UI basado en el navegador y viene integrado en Frappe donde los formularios son generados automáticamente desde los modelos y los roles y permisos son aplicados.

También, vamos a crear vistas webs para la librería donde los usuarios pueden buscar los artículos desde una página web.

{index}

+ 19
- 0
frappe/docs/user/es/tutorial/index.txt Zobrazit soubor

@@ -0,0 +1,19 @@
before
app
bench
new-app
setting-up-the-site
start
models
roles
doctypes
naming-and-linking
doctype-directory-structure
users-and-records
form-client-scripting
controllers
reports
web-views
single-doctypes
task-runner
conclusion

+ 19
- 0
frappe/docs/user/es/tutorial/models.md Zobrazit soubor

@@ -0,0 +1,19 @@
# Creando Modelos

El siguiente paso es crear los modelos que discutimos en la introducción. En Frappe, los modelos son llamados **DocTypes**. Puedes crear nuevos DocTypes desde el UI Escritorio de Frappe. **DocTypes** son creados de campos llamados **DocField** y los permisos basados en roles son integrados dentro de los modelos, estos son llamados **DocPerms**.

Cuando un DocType es guardado, se crea una nueva tabla en la base de datos. Esta tabla se nombra `tab[doctype]`.

Cuando creas un **DocType** una nueva carpeta es creada en el **Module** y un archivo JSON y una platilla de un controlador en Python son creados automáticamente. Cuando modificas un DocType, el archivo JSON es modificado y cada vez que se ejecuta `bench migrate`, sincroniza el archivo JSON con la tabla en la base de datos. Esto hace que sea más facíl reflejar los cambios hechos al esquema y migrarlo.

### Modo desarrollador

Para crear modelos, debes setear `developer_mode` a 1 en el archivo `site_config.json` ubicados en /sites/library y ejecuta el comando `bench clear-cache` o usa el menú de usuario en el Escritorio y da click en "Recargar/Reload" para que los cambios tomen efecto. Deberías poder ver la aplicación llamada "Developer" en su escritorio.

{
"db_name": "bcad64afbf",
"db_password": "v3qHDeVKvWVi7s97",
"developer_mode": 1
}

{next}

+ 71
- 0
frappe/docs/user/es/tutorial/naming-and-linking.md Zobrazit soubor

@@ -0,0 +1,71 @@
# Nombrando y Asociando DocType

Vamos a crear otro DocType y guardarlo:

1. Library Member (First Name, Last Name, Email Address, Phone, Address)

<img class="screenshot" alt="Doctype Saved" src="{{docs_base_url}}/assets/img/naming_doctype.png">


#### Nombrando DocTypes

Los DocTypes pueden ser nombrados en diferentes maneras:

1. Basados en un campo
1. Basados en una serie
1. A traves del controlador (vía código)
1. Con un promt

Esto puede ser seteado a traves del campo **Autoname**. Para el controlador, dejar en blanco.

> **Search Fields**: Un DocType puede ser nombrado por serie pero seguir teniendo la necesidad de ser buscado por nombre. En nuestro caso, el Article va ser buscado por el título o el nombre del autor. Por lo que vamos a poner esos campos en el campo de search.

<img class="screenshot" alt="Autonaming and Search Field" src="{{docs_base_url}}/assets/img/autoname_and_search_field.png">

#### Campo de Enlace y Campo Select

Las claves foraneas son específicadas en Frappe como campos **Link** (Enlace). El DocType debe ser mencionado en el area de texto de Options.

En nuestro ejemplo, en el DocType de Library Transaction,tenemos que enlazar los dos DocTypes de Library Member and the Article.

**Nota:** Recuerda que los campos de Enlace no son automáticamente establecidos como claves foraneas en la base de datos MariaDB, porque esto crearía un indice en la columna. Las validaciones de claves foraneas son realizadas por el Framework.

<img class="screenshot" alt="Link Field" src="{{docs_base_url}}/assets/img/link_field.png">

Por campos de tipo Select, como mencionamos antes, agrega varias opciones en la caja de texto **Options**, cada una en una nueva linea.

<img class="screenshot" alt="Select Field" src="{{docs_base_url}}/assets/img/select_field.png">

De manera similar continua haciendo los otros modelos.

#### Valores enlazados

Un patrón estandar es que cuando seleccionas un ID, dice **Library Member** en **Library Membership**, entonces el nombre y apellido del miembro deberian ser copiados en campos relevantes de el Doctype Library Membership Transaction.

Para hacer esto, podemos usar campos de solo lectura y en opciones, podemos especificar el nombre del link (enlace) y el campo o propiedad que deseas obtener. Para este ejempo en **Member First Name** podemos especificar `library_member.first_name`.

<img class="screenshot" alt="Fetch values" src="{{docs_base_url}}/assets/img/fetch.png">

### Completar los modelos

En la misma forma, puedes completar todos los modelos, todos los campos deben verse de esta manera

#### Article

<img class="screenshot" alt="Article" src="{{docs_base_url}}/assets/img/doctype_article.png">

#### Library Member

<img class="screenshot" alt="Library Member" src="{{docs_base_url}}/assets/img/doctype_lib_member.png">

#### Library Membership

<img class="screenshot" alt="Library Membership" src="{{docs_base_url}}/assets/img/doctype_lib_membership.png">

#### Library Transaction

<img class="screenshot" alt="Library Transaction" src="{{docs_base_url}}/assets/img/doctype_lib_trans.png">

> Asegurate de dar permiso a **Librarian** en cada DocType

{next}

+ 54
- 0
frappe/docs/user/es/tutorial/new-app.md Zobrazit soubor

@@ -0,0 +1,54 @@
# Creando una nueva aplicación

Una vez el bench esté instalado, vas a ver dos directorios principales, `apps` and `sites`. Todas las aplicaciones van a ser instaladas en apps.

Para crear una nueva aplicación, debes posicionarte en el directorio del bench y ejecutar `bench new-app {app_name}` y llenar los detalles de la aplicación. Esto a va crear los directorios y archivos necesarios para una aplicación.

$ bench new-app library_management
App Title (defaut: Lib Mgt): Library Management
App Description: App for managing Articles, Members, Memberships and Transactions for Libraries
App Publisher: Frappe
App Email: info@frappe.io
App Icon (default 'octicon octicon-file-directory'): octicon octicon-book
App Color (default 'grey'): #589494
App License (default 'MIT'): GNU General Public License

### Estructura de una aplicación

La aplicación va a ser creada en el directorio llamado `library_management` y va a tener la siguiente estructura:

.
├── MANIFEST.in
├── README.md
├── library_management
│   ├── __init__.py
│   ├── config
│   │   ├── __init__.py
│   │   └── desktop.py
│   ├── hooks.py
│   ├── library_management
│   │   └── __init__.py
│   ├── modules.txt
│   ├── patches.txt
│   └── templates
│   ├── __init__.py
│   ├── generators
│   │   └── __init__.py
│   ├── pages
│   │   └── __init__.py
│   └── statics
├── license.txt
├── requirements.txt
└── setup.py

1. `config` contiene la información de configuración de la aplicación.
1. `desktop.py` es donde los íconos del escritorio pueden ser agregados al mismo.
1. `hooks.py` es donde se configuran las integraciones con el entorno y otras aplicaciones.
1. `library_management` (dentro) es un **módulo** que está contenido. En Frappe, un **módulo** es donde los modelos y controladores se almacenan.
1. `modules.txt` contiene la lista de **módulos** en la aplicación. Cuando creas un nuevo módulo, es obligatorio que lo agregues a este archivo.
1. `patches.txt` es donde los patches para migraciones son establecidos. Son módulos de Python referenciados usando la nomenclatura de punto.
1. `templates` es el directorio donde son mantenidos las plantillas de vistas web. Plantillas para **Login** y otras páginas estandar estan contenidas en Frappe.
1. `generators` son donde las plantillas para los modelos son almacenadas, donde cada instancia de modelo tiene una ruta web separada, por ejemplo un **Blog Post** donde cada post tiene una url única. En Frappe, el manejador de plantillas utilizado es Jinja2.
1. `pages` es donde las rutas simples son almacenadas. Por ejemplo para un tipo de página "/blog".

{next}

+ 7
- 0
frappe/docs/user/es/tutorial/reports.md Zobrazit soubor

@@ -0,0 +1,7 @@
# Reportes

Puedes dar click en el texto que dice Reportes en el panel lateral izquierdo para ver los registros de manera tabulada.

<img class="screenshot" alt="Report" src="{{docs_base_url}}/assets/img/report.png">

{next}

+ 14
- 0
frappe/docs/user/es/tutorial/roles.md Zobrazit soubor

@@ -0,0 +1,14 @@
# Creando Roles

Antes de crear los Modelos, debemos crear los Roles que van a establecer los permisos en el Modelo. En nuestro ejemplo vamos a tener dos Roles:

1. Librarian
1. Library Member

Para crear un nuevo Role, ir a:

> Setup > Users > Role > New

<img class="screenshot" alt="Adding Roles" src="{{docs_base_url}}/assets/img/roles_creation.png">

{next}

+ 68
- 0
frappe/docs/user/es/tutorial/setting-up-the-site.md Zobrazit soubor

@@ -0,0 +1,68 @@
# Configurando el Site

Vamos a crear un nuevo Site llamado `library`.

*Nota: Antes de crear cualquier Site, necesitas hacer unos cambios en su instalación de MariaDB.*
*Copia la siguiente configuración por defecto de ERPNext en su archivo `my.cnf`.*

[mysqld]
innodb-file-format=barracuda
innodb-file-per-table=1
innodb-large-prefix=1
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

[mysql]
default-character-set = utf8mb4

Ahora puedes instalar un nuevo site, ejecutando el comando `bench new-site library`.

La ejecución del comando anterior va a generar una nueva base de datos, un directorio en la carpeta sites y va a instalar `frappe` (el cual también es una aplicación!) en el nuevo site.
La aplicación `frappe` tiene dos módulos integrados que son **Core** y **Website**. El módulo Core contiene los modelos básicos para la aplicación. Frappe es un Framework con muchas funcionalidades incluidas y viene con muchos modelos integrados. Estos modelos son llamados **DocTypes**. Vamos a ver más de esto en lo adelante.

$ bench new-site library
MySQL root password:
Installing frappe...
Updating frappe : [========================================]
Updating country info : [========================================]
Set Administrator password:
Re-enter Administrator password:
Installing fixtures...
*** Scheduler is disabled ***

### Estructura de un Site

Un nuevo directorio ha sido creado dentro de la carpeta `sites` llamado `library`. La estructura siguiente es la que trae por defecto un site.

.
├── locks
├── private
│   └── backups
├── public
│   └── files
└── site_config.json

1. `public/files` es donde se almacenan los archivos subidos por los usuarios.
1. `private/backups` es donde se almacenan los backups o copias de respaldo.
1. `site_config.json` es donde todas las configuraciones a nivel de sites son almacenadas.

### Configurando un Site por defecto

En caso que tengas varios sites en tu Bench, debes usar `bench use [nombre_site]` para especificar el site por defecto.

Ejemplo:

$ bench use library

### Instalar Aplicaciones

Ahora vamos a instalar nuestra aplicación `library_management` en nuestro site `library`.

1. Instalar la aplicación library_management en el site library se logra ejecutando el siguiente comando: `bench --site [nombre_site] install-app [nombre_app]`

Ejemplo:

$ bench --site library install-app library_management

{next}

+ 9
- 0
frappe/docs/user/es/tutorial/single-doctypes.md Zobrazit soubor

@@ -0,0 +1,9 @@
# DocTypes Simples

Una aplicación normalmente va a tener una página de configuración. En nuestra aplicación, podemos definir una página donde específiquemos el período de prestamos. Necesitaremos almacenar esta propiedad. En Frappe, esto puede lograrse usando los DocType de tipo **Simple**. Un DocType Simple es como el patrón Singleton en Java. Es un objeto con tan solo una instancia. Vamos a llamarlo **Library Managment Settings**.

Para crear un Single DocType, marca el checkbox **Is Single**.

<img class="screenshot" alt="Single Doctypes" src="{{docs_base_url}}/assets/img/tab_single.png">

{next}

+ 31
- 0
frappe/docs/user/es/tutorial/start.md Zobrazit soubor

@@ -0,0 +1,31 @@
# Iniciando el Bench

Ahora podemos acceder y verificar que todo esta funcionando de forma correcta.

Para iniciar el servidor de desarrollo, ejecuta `bench start`.

$ bench start
13:58:51 web.1 | started with pid 22135
13:58:51 worker.1 | started with pid 22136
13:58:51 workerbeat.1 | started with pid 22137
13:58:52 web.1 | * Running on http://0.0.0.0:8000/
13:58:52 web.1 | * Restarting with reloader
13:58:52 workerbeat.1 | [2014-09-17 13:58:52,343: INFO/MainProcess] beat: Starting...

Ahora abre tu navegador y ve a la dirección `http://localhost:8000`. Deberías ver la páagina de inicio de sesión si todo salió bien.:

<img class="screenshot" alt="Login Screen" src="{{docs_base_url}}/assets/img/login.png">

Ahora accede con :

Login ID: **Administrator**

Password : **Usa la contraseña que creaste durante la instalación**

Cuando accedas, deberías poder ver la página de inicio (Desk).

<img class="screenshot" alt="Desk" src="{{docs_base_url}}/assets/img/desk.png">

Como puedes ver, el sistema básico de Frappe viene con algunas aplicaciones preinstaladas como To Do, File Manager etc. Estas aplicaciones pueden integrarse en el flujo de trabajo de su aplicació a medida que avancemos.

{next}

+ 77
- 0
frappe/docs/user/es/tutorial/task-runner.md Zobrazit soubor

@@ -0,0 +1,77 @@
# Tareas Programadas

Finalmente, una aplicación también tiene que mandar notificaciones de email y hacer otros tipos de tareas programadas. En Frappe, si tienes el bench configurado, el programador de tareas es configurado vía Celery usando Redis Queue.

Para agregar un nuevo manejador(Handler) de tareas, ir a `hooks.py` y agrega un nuevo manejador. Los manejadores (Handlers) por defecto son `all`, `daily`, `weekly`, `monthly`. El manejador `all` es llamado cada 3 minutos por defecto.

# Tareas Programadas
# ---------------

scheduler_events = {
"daily": [
"library_management.tasks.daily"
],
}

Aquí hacemos referencia a una función Python que va a ser ejecutada diariamente. Vamos a ver como se ve esta función:

# Copyright (c) 2013, Frappe
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
from frappe.utils import datediff, nowdate, format_date, add_days

def daily():
loan_period = frappe.db.get_value("Library Management Settings",
None, "loan_period")

overdue = get_overdue(loan_period)

for member, items in overdue.iteritems():
content = """<h2>Following Items are Overdue</h2>
<p>Please return them as soon as possible</p><ol>"""

for i in items:
content += "<li>{0} ({1}) due on {2}</li>".format(i.article_name,
i.article,
format_date(add_days(i.transaction_date, loan_period)))

content += "</ol>"

recipient = frappe.db.get_value("Library Member", member, "email_id")
frappe.sendmail(recipients=[recipient],
sender="test@example.com",
subject="Library Articles Overdue", content=content, bulk=True)

def get_overdue(loan_period):
# check for overdue articles
today = nowdate()

overdue_by_member = {}
articles_transacted = []

for d in frappe.db.sql("""select name, article, article_name,
library_member, member_name
from `tabLibrary Transaction`
order by transaction_date desc, modified desc""", as_dict=1):

if d.article in articles_transacted:
continue

if d.transaction_type=="Issue" and \
datediff(today, d.transaction_date) > loan_period:
overdue_by_member.setdefault(d.library_member, [])
overdue_by_member[d.library_member].append(d)

articles_transacted.append(d.article)

Podemos pegar el código anterior en cualquier módulo de Python que sea accesible. La ruta es definida en `hooks.py`, por lo que para nuestro propósito vamos a poner el código en el archivo `library_management/tasks.py`.

Nota:

1. Obtenemos el período de prestamo desde **Library Management Settings** usando la función `frappe.db.get_value`.
1. Ejecutamos una consulta en la base de datos usando la función `frappe.db.sql`
1. Los Email son enviados usando `frappe.sendmail`

{next}

+ 55
- 0
frappe/docs/user/es/tutorial/users-and-records.md Zobrazit soubor

@@ -0,0 +1,55 @@
# Creando Usuarios y Registros

Teniendo los modelos creados, podemos empezar a crear registros usando la interfaz gráfica de usuario de Frappe. No necesitas crear vistas! Las vistas en Frappe son automáticamente creadas basadas en las propiedades del DocType.

### 4.1 Creando Usuarios

Para crear registros, primero vamos a crear un Usuario. Para crear un usuario, ir a:

> Setup > Users > User > New

Crea un nuevo Usuario y llena los campos de nombre, primer nombre y nueva contraseña.

Luego dale los Roles de Librarian y de Library Member a este usuario.

<img class="screenshot" alt="Add User Roles" src="{{docs_base_url}}/assets/img/add_user_roles.png">

Ahora cierra sesión y accede usando las credenciales del nuevo usuario.

### 4.2 Creando Registros

Debes ver un ícono del módulo de Library Management. Dar click en el ícono para entrar a la página del módulo:

<img class="screenshot" alt="Library Management Module" src="{{docs_base_url}}/assets/img/lib_management_module.png">

Aquí puedes ver los DocTypes que fueron creados para la aplicación. Vamos a comenzar a crear nuevos registros.

Primero vamos a crear un nuevo Article:

<img class="screenshot" alt="New Article" src="{{docs_base_url}}/assets/img/new_article_blank.png">

Aquí vas a ver que los DocTypes que haz creado han sido renderizados como un formulario. Las validaciones y las otras restricciones también están aplicadas según se diseñaron. Vamos a llenar los datos de un Article.

<img class="screenshot" alt="New Article" src="{{docs_base_url}}/assets/img/new_article.png">

Puedes agregar una imagen si deseas.

<img class="screenshot" alt="Attach Image" src="{{docs_base_url}}/assets/img/attach_image.gif">

Ahora vamos a crear un nuevo miembro:

<img class="screenshot" alt="New Library Member" src="{{docs_base_url}}/assets/img/new_member.png">

Despues de esto, crearemos una nueva membresía (membership) para el miembro.

Si recuerdas, aquí hemos específicado los valores del nombre y apellido del miembro directamente desde el registro del miembro tan pronto selecciones el miembro id, los nombres serán actualizados.

<img class="screenshot" alt="New Library Membership" src="{{docs_base_url}}/assets/img/new_lib_membership.png">

Como puedes ver la fecha tiene un formato de año-mes-día lo cual es una fecha del sistema. Para seleccionar o cambiar la fecha, tiempo y formatos de números, ir a:

> Setup > Settings > System Settings

<img class="screenshot" alt="System Settings" src="{{docs_base_url}}/assets/img/system_settings.png">

{next}

+ 64
- 0
frappe/docs/user/es/tutorial/web-views.md Zobrazit soubor

@@ -0,0 +1,64 @@
# Vistas Web (Web Views)

Frappe tiene dos entornos principales, El escritorio y la Web. El escritorio es una interfaz de usuario controlada con una excelente aplicación AJAX y la web es mas plantillas de HTML tradicionales dispuestas para consumo público. Vistas Web pueden también ser generadas para crear vistas controladas para los usuarios que puedes acceder al sistema pero aún así no tener acceso al escritorio.

En Frappe, Las vistas web son manejadas por plantillas que estan usualmente en el directorio `templates`. Hay dos tipos principales de plantillas.

1. Pages: Estos son plantillas Jinja donde una vista existe solo para una ruta. ejemplo. `/blog`.
2. Generators: Estas son plantiallas donde cada instancia de un DocType tiene una ruta diferente `/blog/a-blog`, `blog/b-blog` etc.
3. Lists and Views: Estos son listas y vistan estandares con la ruta `[doctype]/[name]` y son renderizadas basándose en los permisos.

### Vista Web Estandar

> Esta funcionalidad sigue bajo desarrollo.

Vamos a ver las Vistas web estandar:

Si estas logueado como el usuario de prueba, ve a `/article` y deberías ver la lista de artículos.

<img class="screenshot" alt="web list" src="{{docs_base_url}}/assets/img/web-list.png">

Da click en uno de los artículos y vas a ver una vista web por defecto

<img class="screenshot" alt="web view" src="{{docs_base_url}}/assets/img/web-view.png">

Si deseas hacer una mejor vista para la lista de artículos, crea un archivo llamado `row_template.html` en el directorio `library_management/templates/includes/list/`.
Aquí hay un archivo de ejemplo:

{% raw %}<div class="row">
<div class="col-sm-4">
<a href="/Article/{{ doc.name }}">
<img src="{{ doc.image }}"
class="img-responsive" style="max-height: 200px">
</a>
</div>
<div class="col-sm-4">
<a href="/Article/{{ doc.name }}"><h4>{{ doc.article_name }}</h4></a>
<p>{{ doc.author }}</p>
<p>{{ (doc.description[:200] + "...")
if doc.description|len > 200 else doc.description }}</p>
<p class="text-muted">Publisher: {{ doc.publisher }}</p>
</div>
</div>{% endraw %}

Aquí, vas a tener todas las propiedades de un artículo en el objeto `doc`.

La lista actualizada debe lucir de esta manera!

<img class="screenshot" alt="new web list" src="{{docs_base_url}}/assets/img/web-list-new.png">

#### Página de Inicio

Frappe también tiene vistas para el registro de usuarios que incluye opciones de registro usando Google, Facebook y GitHub. Cuando un usuario se registra vía la web, no tiene acceso a la interfaz del Escritorio por defecto.

> Para permitirles a los usuarios acceso al Escritorio, debes especificar que el usuario es de tipo "System User" en Setup > User

Para usuario que no son de tipo System User, podemos especificar una página de inicio por defecto a traves de `hooks.py` basándonos en Role.

Cuando miembros acceden al sistema, deben ser redireccionados a la página `article`, para configurar esto modifica el archivo `library_management/hooks.py` y agrega lo siguiente:

role_home_page = {
"Library Member": "article"
}

{next}

+ 0
- 0
frappe/docs/user/es/videos/__init_.py Zobrazit soubor


+ 9
- 0
frappe/docs/user/es/videos/index.md Zobrazit soubor

@@ -0,0 +1,9 @@
# Videos Tutoriales acerca del Framework Frappe

Este video tutorial de 10 videos va a enseñarte como crear aplicaciones complejas en Frappe.

Prerrequisitos: <a href="{{ docs_base_url }}/user/es/tutorial/before.html" target="_blank">Debes tener conocimientos básicos de Python, Javascript y MySQl antes de empezar este tutorial.</a>

---

<iframe width="670" height="376" src="https://www.youtube.com/embed/videoseries?list=PL3lFfCEoMxvzHtsZHFJ4T3n5yMM3nGJ1W" frameborder="0" allowfullscreen></iframe>

+ 1
- 0
frappe/docs/user/index.md Zobrazit soubor

@@ -5,3 +5,4 @@ Select your language
1. [English]({{docs_base_url}}/user/en)
1. [Français]({{docs_base_url}}/user/fr)
1. [Português]({{docs_base_url}}/user/pt)
1. [Español]({{docs_base_url}}/user/es)

+ 1
- 0
frappe/docs/user/index.txt Zobrazit soubor

@@ -1,3 +1,4 @@
en
fr
pt
es

+ 22
- 29
frappe/email/doctype/auto_email_report/auto_email_report.py Zobrazit soubor

@@ -8,6 +8,7 @@ from frappe import _
from frappe.model.document import Document
from datetime import timedelta
import frappe.utils
from frappe.utils import now, global_date_format, format_time
from frappe.utils.xlsxutils import make_xlsx
from frappe.utils.csvutils import to_csv

@@ -76,16 +77,28 @@ class AutoEmailReport(Document):
return xlsx_file.getvalue()

elif self.format == 'CSV':
spreadsheet_data = self.get_spreadsheet_data(columns, data)
spreadsheet_data = self.get_spreadsheet_data(columns, data)
return to_csv(spreadsheet_data)

else:
frappe.throw(_('Invalid Output Format'))

def get_html_table(self, columns, data):
return frappe.render_template('frappe/templates/includes/print_table.html', {
def get_html_table(self, columns=None, data=None):

date_time = global_date_format(now()) + ' ' + format_time(now())
report_doctype = frappe.db.get_value('Report', self.report, 'ref_doctype')

return frappe.render_template('frappe/templates/emails/auto_email_report.html', {
'title': self.name,
'description': self.description,
'date_time': date_time,
'columns': columns,
'data': data
'data': data,
'report_url': frappe.utils.get_url_to_report(self.report,
self.report_type, report_doctype),
'report_name': self.report,
'edit_report_settings': frappe.utils.get_link_to_form('Auto Email Report',
self.name)
})

@staticmethod
@@ -111,29 +124,17 @@ class AutoEmailReport(Document):
return

attachments = None
message = '<p>{0}</p>'.format(_('{0} generated on {1}')\
.format(frappe.bold(self.name),
frappe.utils.format_datetime(frappe.utils.now_datetime())))

if self.description:
message += '<hr style="margin: 15px 0px;">' + self.description

if self.format=='HTML':
message += '<hr>' + data
if self.format == "HTML":
message = data
else:
message = self.get_html_table()

if not self.format=='HTML':
attachments = [{
'fname': self.get_file_name(),
'fcontent': data
}]

report_doctype = frappe.db.get_value('Report', self.report, 'ref_doctype')
report_footer = frappe.render_template(self.get_report_footer(),
dict(report_url = frappe.utils.get_url_to_report(self.report, self.report_type, report_doctype),
report_name = self.report,
edit_report_settings = frappe.utils.get_link_to_form('Auto Email Report', self.name)))

message += report_footer

frappe.sendmail(
recipients = self.email_to.split(),
subject = self.name,
@@ -141,14 +142,6 @@ class AutoEmailReport(Document):
attachments = attachments
)

def get_report_footer(self):
return """<hr style="margin: 30px 0px 15px 0px;">
<p style="font-size: 9px;">
View report in your browser:
<a href= {{report_url}} target="_blank">{{report_name}}</a><br><br>
Edit Auto Email Report Settings: {{edit_report_settings}}
</p>"""

@frappe.whitelist()
def download(name):
'''Download report locally'''


+ 4
- 4
frappe/email/doctype/email_group/email_group.py Zobrazit soubor

@@ -69,15 +69,15 @@ def add_subscribers(name, email_list):
count = 0
for email in email_list:
email = email.strip()
valid = validate_email_add(email, False)
parsed_email = validate_email_add(email, False)

if valid:
if parsed_email:
if not frappe.db.get_value("Email Group Member",
{"email_group": name, "email": email}):
{"email_group": name, "email": parsed_email}):
frappe.get_doc({
"doctype": "Email Group Member",
"email_group": name,
"email": email
"email": parsed_email
}).insert(ignore_permissions = frappe.flags.ignore_permissions)

count += 1


+ 40
- 16
frappe/email/email_body.py Zobrazit soubor

@@ -15,7 +15,7 @@ from email.mime.multipart import MIMEMultipart
def get_email(recipients, sender='', msg='', subject='[No Subject]',
text_content = None, footer=None, print_html=None, formatted=None, attachments=None,
content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None,
inline_images=[], header=False):
inline_images=[], header=None):
""" Prepare an email with the following format:
- multipart/mixed
- multipart/alternative
@@ -76,7 +76,7 @@ class EMail:
self.email_account = email_account or get_outgoing_email_account()

def set_html(self, message, text_content = None, footer=None, print_html=None,
formatted=None, inline_images=None, header=False):
formatted=None, inline_images=None, header=None):
"""Attach message in the html portion of multipart/alternative"""
if not formatted:
formatted = get_formatted_html(self.subject, message, footer, print_html,
@@ -233,12 +233,12 @@ class EMail:
self.make()
return self.msg_root.as_string()

def get_formatted_html(subject, message, footer=None, print_html=None, email_account=None, header=False):
def get_formatted_html(subject, message, footer=None, print_html=None, email_account=None, header=None):
if not email_account:
email_account = get_outgoing_email_account(False)

rendered_email = frappe.get_template("templates/emails/standard.html").render({
"header": get_header() if header else None,
"header": get_header(header),
"content": message,
"signature": get_signature(email_account),
"footer": get_footer(email_account, footer),
@@ -247,7 +247,27 @@ def get_formatted_html(subject, message, footer=None, print_html=None, email_acc
"subject": subject
})

return scrub_urls(rendered_email)
sanitized_html = scrub_urls(rendered_email)
transformed_html = inline_style_in_html(sanitized_html)
return transformed_html

def inline_style_in_html(html):
''' Convert email.css and html to inline-styled html
'''
from premailer import Premailer

apps = frappe.get_installed_apps()

css_files = []
for app in apps:
path = 'assets/{0}/css/email.css'.format(app)
if os.path.exists(os.path.abspath(path)):
css_files.append(path)

p = Premailer(html=html, external_styles=css_files, strip_important=False)

return p.transform()


def add_attachment(fname, fcontent, content_type=None,
parent=None, content_id=None, inline=False):
@@ -396,23 +416,27 @@ def get_filecontent_from_path(path):
return None


def get_header():
def get_header(header=None):
""" Build header from template """
from frappe.utils.jinja import get_email_from_template

default_brand_image = 'assets/frappe/images/favicon.png' # svg doesn't work in email
email_brand_image = frappe.get_hooks('email_brand_image')
if len(email_brand_image):
email_brand_image = email_brand_image[-1]
else:
email_brand_image = default_brand_image
if not header: return None

if isinstance(header, basestring):
# header = 'My Title'
header = [header, None]
if len(header) == 1:
# header = ['My Title']
header.append(None)
# header = ['My Title', 'orange']
title, indicator = header

email_brand_image = default_brand_image
brand_text = frappe.get_hooks('app_title')[-1]
if not title:
title = frappe.get_hooks('app_title')[-1]

email_header, text = get_email_from_template('email_header', {
'brand_image': email_brand_image,
'brand_text': brand_text
'header_title': title,
'indicator': indicator
})

return email_header

+ 2
- 1
frappe/email/queue.py Zobrazit soubor

@@ -23,7 +23,7 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content=
attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None,
expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None,
queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None,
header=False):
header=None):
"""Add email to sending queue (Email Queue)

:param recipients: List of recipients.
@@ -495,6 +495,7 @@ def prepare_message(email, recipient, recipients_list):
'fcontent': fcontent,
'parent': msg_obj
})
attachment.pop("fid", None)
add_attachment(**attachment)

return msg_obj.as_string()


+ 42
- 1
frappe/email/test_email_body.py Zobrazit soubor

@@ -3,7 +3,8 @@
from __future__ import unicode_literals

import frappe, unittest, os, base64
from frappe.email.email_body import replace_filename_with_cid, get_email
from frappe.email.email_body import (replace_filename_with_cid,
get_email, inline_style_in_html, get_header)

class TestEmailBody(unittest.TestCase):
def setUp(self):
@@ -95,6 +96,46 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
'''.format(inline_images[0].get('content_id'))
self.assertEquals(message, processed_message)

def test_inline_styling(self):
html = '''
<h3>Hi John</h3>
<p>This is a test email</p>
'''
transformed_html = '''
<h3>Hi John</h3>
<p style="margin:1em 0 !important">This is a test email</p>
'''
self.assertTrue(transformed_html in inline_style_in_html(html))

def test_email_header(self):
email_html = '''
<h3>Hey John Doe!</h3>
<p>This is embedded image you asked for</p>
'''
email_string = get_email(
recipients=['test@example.com'],
sender='me@example.com',
subject='Test Subject',
content=email_html,
header=['Email Title', 'green']
).as_string()

self.assertTrue('''<span class=3D"indicator indicator-green" style=3D"background-color:#98=
d85b; border-radius:8px; display:inline-block; height:8px; margin-right:5px=
; width:8px" bgcolor=3D"#98d85b" height=3D"8" width=3D"8"></span>''' in email_string)
self.assertTrue('<span>Email Title</span>' in email_string)

def test_get_email_header(self):
html = get_header(['This is test', 'orange'])
self.assertTrue('<span class="indicator indicator-orange"></span>' in html)
self.assertTrue('<span>This is test</span>' in html)

html = get_header(['This is another test'])
self.assertTrue('<span>This is another test</span>' in html)

html = get_header('This is string')
self.assertTrue('<span>This is string</span>' in html)


def fixed_column_width(string, chunk_size):
parts = [string[0+i:chunk_size+i] for i in range(0, len(string), chunk_size)]

+ 1
- 1
frappe/installer.py Zobrazit soubor

@@ -125,7 +125,7 @@ def install_app(name, verbose=False, set_as_patched=True):
frappe.msgprint(_("App {0} already installed").format(name))
return

print("Installing {0}...".format(name))
print("\nInstalling {0}...".format(name))

if name != "frappe":
frappe.only_for("System Manager")


+ 5
- 2
frappe/oauth.py Zobrazit soubor

@@ -1,5 +1,6 @@
from __future__ import print_function
import frappe, urllib
import pytz

from frappe import _
from urlparse import parse_qs, urlparse
@@ -227,8 +228,10 @@ class OAuthWebRequestValidator(RequestValidator):

def validate_bearer_token(self, token, scopes, request):
# Remember to check expiration and scope membership
otoken = frappe.get_doc("OAuth Bearer Token", token) #{"access_token": str(token)})
is_token_valid = (frappe.utils.datetime.datetime.now() < otoken.expiration_time) \
otoken = frappe.get_doc("OAuth Bearer Token", token)
token_expiration_local = otoken.expiration_time.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone()))
token_expiration_utc = token_expiration_local.astimezone(pytz.utc)
is_token_valid = (frappe.utils.datetime.datetime.utcnow().replace(tzinfo=pytz.utc) < token_expiration_utc) \
and otoken.status != "Revoked"
client_scopes = frappe.db.get_value("OAuth Client", otoken.client, 'scopes').split(get_url_delimiter())
are_scopes_valid = True


+ 2
- 1
frappe/patches.txt Zobrazit soubor

@@ -187,4 +187,5 @@ frappe.patches.v8_0.update_gender_and_salutation
execute:frappe.db.sql('update tabReport set module="Desk" where name="ToDo"')
frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings
frappe.patches.v8_1.update_format_options_in_auto_email_report
frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists
frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists
frappe.patches.v8_5.delete_email_group_member_with_invalid_emails

+ 0
- 0
frappe/patches/v8_5/__init__.py Zobrazit soubor


+ 20
- 0
frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py Zobrazit soubor

@@ -0,0 +1,20 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt

from __future__ import unicode_literals
import frappe
from frappe.utils import validate_email_add

def execute():
''' update/delete the email group member with the wrong email '''

email_group_members = frappe.get_all("Email Group Member", fields=["name", "email"])
for member in email_group_members:
validated_email = validate_email_add(member.email)
if (validated_email==member.email):
pass
else:
try:
frappe.db.set_value("Email Group Member", member.name, "email", validated_email)
except Exception:
frappe.delete_doc(doctype="Email Group Member", name=member.name, force=1, ignore_permissions=True)

+ 2
- 0
frappe/public/build.json Zobrazit soubor

@@ -125,6 +125,7 @@
"public/js/frappe/misc/common.js",
"public/js/frappe/misc/pretty_date.js",
"public/js/frappe/misc/utils.js",
"public/js/frappe/misc/test_utils.js",
"public/js/frappe/misc/tools.js",
"public/js/frappe/misc/datetime.js",
"public/js/frappe/misc/number_format.js",
@@ -161,6 +162,7 @@
"public/js/frappe/query_string.js",

"public/js/frappe/ui/charts.js",
"public/js/frappe/ui/graph.js",
"public/js/frappe/misc/rating_icons.html",

"public/js/frappe/feedback.js"


+ 1
- 0
frappe/public/css/common.css Zobrazit soubor

@@ -89,6 +89,7 @@ kbd {
}
.dropdown-menu > li > a {
padding: 14px;
white-space: normal;
}
.dropdown-menu {
min-width: 200px;


+ 12
- 0
frappe/public/css/desk.css Zobrazit soubor

@@ -89,6 +89,7 @@ kbd {
}
.dropdown-menu > li > a {
padding: 14px;
white-space: normal;
}
.dropdown-menu {
min-width: 200px;
@@ -508,6 +509,17 @@ fieldset[disabled] .form-control {
cursor: pointer;
margin-right: 10px;
}
a.progress-small .progress-chart {
width: 60px;
margin-top: 4px;
float: right;
}
a.progress-small .progress {
margin-bottom: 0;
}
a.progress-small .progress-bar {
background-color: #98d85b;
}
/* on small screens, show only icons on top */
@media (max-width: 767px) {
.module-view-layout .nav-stacked > li {


+ 151
- 0
frappe/public/css/email.css Zobrazit soubor

@@ -0,0 +1,151 @@
/* csslint ignore:start */
body {
line-height: 1.5;
color: #36414C;
}
p {
margin: 1em 0 !important;
}
hr {
border-top: 1px solid #d1d8dd;
}
.body-table {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.body-table td {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.email-header,
.email-body,
.email-footer {
width: 100% !important;
min-width: 100% !important;
}
.email-body {
font-size: 14px;
}
.email-footer {
border-top: 1px solid #d1d8dd;
font-size: 12px;
}
.email-header {
border: 1px solid #d1d8dd;
border-radius: 4px 4px 0 0;
}
.email-header .brand-image {
width: 24px;
height: 24px;
display: block;
}
.email-header-title {
font-weight: bold;
}
.body-table.has-header .email-body {
border: 1px solid #d1d8dd;
border-radius: 0 0 4px 4px;
border-top: none;
}
.body-table.has-header .email-footer {
border-top: none;
}
.btn {
text-decoration: none;
padding: 7px 10px;
font-size: 12px;
border: 1px solid;
border-radius: 3px;
}
.btn.btn-default {
color: #fff;
background-color: #f0f4f7;
border-color: transparent;
}
.btn.btn-primary {
color: #fff;
background-color: #5E64FF;
border-color: #444bff;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table td,
.table th {
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #d1d8dd;
text-align: left;
}
.table th {
font-weight: bold;
}
.table > thead > tr > th {
vertical-align: middle;
border-bottom: 2px solid #d1d8dd;
}
.table > thead:first-child > tr:first-child > th {
border-top: none;
}
.table.table-bordered {
border: 1px solid #d1d8dd;
}
.table.table-bordered td,
.table.table-bordered th {
border: 1px solid #d1d8dd;
}
.more-info {
font-size: 80% !important;
color: #8D99A6 !important;
border-top: 1px solid #EBEFF2;
padding-top: 10px;
}
.text-right {
text-align: right !important;
}
.text-center {
text-align: center !important;
}
.text-muted {
color: #8D99A6 !important;
}
.text-extra-muted {
color: #d1d8dd !important;
}
.text-regular {
font-size: 14px;
}
.text-medium {
font-size: 12px;
}
.text-small {
font-size: 10px;
}
.indicator {
width: 8px;
height: 8px;
border-radius: 8px;
background-color: #b8c2cc;
display: inline-block;
margin-right: 5px;
}
.indicator.indicator-blue {
background-color: #5e64ff;
}
.indicator.indicator-green {
background-color: #98d85b;
}
.indicator.indicator-orange {
background-color: #ffa00a;
}
.indicator.indicator-red {
background-color: #ff5858;
}
.indicator.indicator-yellow {
background-color: #FEEF72;
}
/* auto email report */
.report-title {
margin-bottom: 20px;
}
/* csslint ignore:end */

+ 86
- 0
frappe/public/css/form.css Zobrazit soubor

@@ -642,6 +642,92 @@ select.form-control {
box-shadow: none;
}
}
/* goals */
.goals-page-container {
background-color: #fafbfc;
padding-top: 1px;
}
.goals-page-container .goal-container {
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
border-radius: 2px;
padding: 10px;
margin: 10px;
}
.graph-container .graphics {
margin-top: 10px;
padding: 10px 0px;
}
.graph-container .stats-group {
display: flex;
justify-content: space-around;
flex: 1;
}
.graph-container .stats-container {
display: flex;
justify-content: space-around;
}
.graph-container .stats-container .stats {
padding-bottom: 15px;
}
.graph-container .stats-container .stats-title {
color: #8D99A6;
}
.graph-container .stats-container .stats-value {
font-size: 20px;
font-weight: 300;
}
.graph-container .stats-container .stats-description {
font-size: 12px;
color: #8D99A6;
}
.graph-container .stats-container .graph-data .stats-value {
color: #98d85b;
}
.bar-graph .axis,
.line-graph .axis {
font-size: 10px;
fill: #6a737d;
}
.bar-graph .axis line,
.line-graph .axis line {
stroke: rgba(27, 31, 35, 0.1);
}
.data-points circle {
fill: #28a745;
stroke: #fff;
stroke-width: 2;
}
.data-points g.mini {
fill: #98d85b;
}
.data-points path {
fill: none;
stroke: #28a745;
stroke-opacity: 1;
stroke-width: 2px;
}
.line-graph .path {
fill: none;
stroke: #28a745;
stroke-opacity: 1;
stroke-width: 2px;
}
line.dashed {
stroke-dasharray: 5,3;
}
.tick.x-axis-label {
display: block;
}
.tick .specific-value {
text-anchor: start;
}
.tick .y-value-text {
text-anchor: end;
}
.tick .x-value-text {
text-anchor: middle;
}
body[data-route^="Form/Communication"] textarea[data-fieldname="subject"] {
height: 80px !important;
}

+ 2
- 0
frappe/public/css/list.css Zobrazit soubor

@@ -232,6 +232,7 @@
padding: 2px 4px;
font-weight: normal;
background-color: #F0F4F7;
white-space: normal;
}
.taggle_list .taggle:hover {
padding: 2px 15px 2px 4px;
@@ -448,6 +449,7 @@
.list-item__content--activity {
justify-content: flex-end;
margin-right: 5px;
min-width: 110px;
}
.list-item__content--activity .list-row-modified,
.list-item__content--activity .avatar-small {


+ 3
- 0
frappe/public/css/slickgrid.css Zobrazit soubor

@@ -54,3 +54,6 @@
.slick-row.odd .slick-cell {
background-color: #fafbfc;
}
.frappe-rtl .slick-wrapper {
direction: ltr;
}

+ 0
- 0
frappe/public/css/variables.css Zobrazit soubor


+ 1
- 0
frappe/public/css/website.css Zobrazit soubor

@@ -89,6 +89,7 @@ kbd {
}
.dropdown-menu > li > a {
padding: 14px;
white-space: normal;
}
.dropdown-menu {
min-width: 200px;


+ 1
- 8
frappe/public/js/frappe/dom.js Zobrazit soubor

@@ -219,13 +219,6 @@ frappe.get_modal = function(title, content) {
return $(frappe.render_template("modal", {title:title, content:content})).appendTo(document.body);
};

frappe._in = function(source, target) {
// returns true if source is in target and both are not empty / falsy
if(!source) return false;
if(!target) return false;
return (target.indexOf(source) !== -1);
};

// add <option> list to <select>
(function($) {
$.fn.add_options = function(options_list) {
@@ -306,4 +299,4 @@ frappe._in = function(source, target) {
}
return this;
}
})(jQuery);
})(jQuery);

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

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