ソースを参照

[fix] merge conflicts

version-14
Saurabh 7年前
コミット
33644ebfb2
16個のファイルの変更249行の追加90行の削除
  1. +1
    -1
      frappe/__init__.py
  2. +2
    -2
      frappe/core/doctype/user_permission/user_permission.json
  3. +4
    -4
      frappe/data_migration/doctype/data_migration_connector/connectors/base.py
  4. +40
    -1
      frappe/data_migration/doctype/data_migration_connector/data_migration_connector.js
  5. +36
    -4
      frappe/data_migration/doctype/data_migration_connector/data_migration_connector.json
  6. +74
    -8
      frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py
  7. +13
    -5
      frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py
  8. +4
    -2
      frappe/data_migration/doctype/data_migration_plan/data_migration_plan.js
  9. +43
    -54
      frappe/data_migration/doctype/data_migration_run/data_migration_run.py
  10. +5
    -5
      frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py
  11. +0
    -1
      frappe/email/email_body.py
  12. +4
    -0
      frappe/model/base_document.py
  13. +3
    -2
      frappe/modules/utils.py
  14. +2
    -1
      frappe/patches.txt
  15. +1
    -0
      frappe/tests/test_domainification.py
  16. +17
    -0
      frappe/utils/data.py

+ 1
- 1
frappe/__init__.py ファイルの表示

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


__version__ = '9.2.2'
__version__ = '9.2.3'
__title__ = "Frappe Framework" __title__ = "Frappe Framework"


local = Local() local = Local()


+ 2
- 2
frappe/core/doctype/user_permission/user_permission.json ファイルの表示

@@ -148,7 +148,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-24 13:25:33.258794",
"modified": "2017-10-26 09:51:47.663104",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "User Permission", "name": "User Permission",
@@ -176,7 +176,7 @@
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,


+ 4
- 4
frappe/data_migration/doctype/data_migration_connector/connectors/base.py ファイルの表示

@@ -5,19 +5,19 @@ from frappe.utils.password import get_decrypted_password
class BaseConnection(with_metaclass(ABCMeta)): class BaseConnection(with_metaclass(ABCMeta)):


@abstractmethod @abstractmethod
def get(self):
def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10):
pass pass


@abstractmethod @abstractmethod
def insert(self):
def insert(self, doctype, doc):
pass pass


@abstractmethod @abstractmethod
def update(self):
def update(self, doctype, doc, migration_id):
pass pass


@abstractmethod @abstractmethod
def delete(self):
def delete(self, doctype, migration_id):
pass pass


def get_password(self): def get_password(self):

+ 40
- 1
frappe/data_migration/doctype/data_migration_connector/data_migration_connector.js ファイルの表示

@@ -2,7 +2,46 @@
// For license information, please see license.txt // For license information, please see license.txt


frappe.ui.form.on('Data Migration Connector', { frappe.ui.form.on('Data Migration Connector', {
refresh: function() {
onload(frm) {
if(frappe.boot.developer_mode) {
frm.add_custom_button(__('New Connection'), () => frm.events.new_connection(frm));
}
},
new_connection(frm) {
const d = new frappe.ui.Dialog({
title: __('New Connection'),
fields: [
{ label: __('Module'), fieldtype: 'Link', options: 'Module Def', reqd: 1 },
{ label: __('Connection Name'), fieldtype: 'Data', description: 'For e.g: Shopify Connection', reqd: 1 },
],
primary_action_label: __('Create'),
primary_action: (values) => {
let { module, connection_name } = values;


frm.events.create_new_connection(module, connection_name)
.then(r => {
if (r.message) {
const connector_name = connection_name
.replace('connection', 'Connector')
.replace('Connection', 'Connector')
.trim();

frm.set_value('connector_name', connector_name);
frm.set_value('connector_type', 'Custom');
frm.set_value('python_module', r.message);
frm.save();
frappe.show_alert(__(`New module created ${r.message}`));
d.hide();
}
});
}
});

d.show();
},
create_new_connection(module, connection_name) {
return frappe.call('frappe.data_migration.doctype.data_migration_connector.data_migration_connector.create_new_connection', {
module, connection_name
});
} }
}); });

+ 36
- 4
frappe/data_migration/doctype/data_migration_connector/data_migration_connector.json ファイルの表示

@@ -49,6 +49,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:!doc.is_custom",
"fieldname": "connector_type", "fieldname": "connector_type",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@@ -61,7 +62,7 @@
"label": "Connector Type", "label": "Connector Type",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Frappe\nPostgres",
"options": "\nFrappe\nPostgres\nCustom",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@@ -80,6 +81,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.connector_type == 'Custom'",
"fieldname": "python_module", "fieldname": "python_module",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@@ -110,7 +112,37 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "localhost",
"fieldname": "authentication_credentials",
"fieldtype": "Section Break",
"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": "Authentication Credentials",
"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,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "hostname", "fieldname": "hostname",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@@ -130,7 +162,7 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
@@ -236,7 +268,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-08 14:34:30.603690",
"modified": "2017-10-26 12:03:40.646348",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Data Migration", "module": "Data Migration",
"name": "Data Migration Connector", "name": "Data Migration Connector",


+ 74
- 8
frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py ファイルの表示

@@ -3,9 +3,11 @@
# For license information, please see license.txt # For license information, please see license.txt


from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
import frappe, os
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.modules.export_file import create_init_py
from .connectors.base import BaseConnection
from .connectors.postgres import PostGresConnection from .connectors.postgres import PostGresConnection
from .connectors.frappe_connection import FrappeConnection from .connectors.frappe_connection import FrappeConnection


@@ -16,14 +18,14 @@ class DataMigrationConnector(Document):


if self.python_module: if self.python_module:
try: try:
frappe.get_module(self.python_module)
get_connection_class(self.python_module)
except: except:
frappe.throw(frappe._('Invalid module path')) frappe.throw(frappe._('Invalid module path'))


def get_connection(self): def get_connection(self):
if self.python_module: if self.python_module:
module = frappe.get_module(self.python_module)
return module.get_connection(self)
_class = get_connection_class(self.python_module)
return _class(self)
else: else:
if self.connector_type == 'Frappe': if self.connector_type == 'Frappe':
self.connection = FrappeConnection(self) self.connection = FrappeConnection(self)
@@ -32,8 +34,72 @@ class DataMigrationConnector(Document):


return self.connection return self.connection


def get_objects(self, object_type, condition=None, selection="*"):
return self.connector.get_objects(object_type, condition, selection)
@frappe.whitelist()
def create_new_connection(module, connection_name):
if not frappe.conf.get('developer_mode'):
frappe.msgprint(_('Please enable developer mode to create new connection'))
return
# create folder
module_path = frappe.get_module_path(module)
connectors_folder = os.path.join(module_path, 'connectors')
frappe.create_folder(connectors_folder)


def get_join_objects(self, object_type, join_type, primary_key):
return self.connector.get_join_objects(object_type, join_type, primary_key)
# create init py
create_init_py(module_path, 'connectors', '')

connection_class = connection_name.replace(' ', '')
file_name = frappe.scrub(connection_name) + '.py'
file_path = os.path.join(module_path, 'connectors', file_name)

# create boilerplate file
with open(file_path, 'w') as f:
f.write(connection_boilerplate.format(connection_class=connection_class))

# get python module string from file_path
app_name = frappe.db.get_value('Module Def', module, 'app_name')
python_module = os.path.relpath(
file_path, '../apps/{0}'.format(app_name)).replace(os.path.sep, '.')[:-3]

return python_module

def get_connection_class(python_module):
filename = python_module.rsplit('.', 1)[-1]
classname = frappe.unscrub(filename).replace(' ', '')
module = frappe.get_module(python_module)

raise_error = False
if hasattr(module, classname):
_class = getattr(module, classname)
if not issubclass(_class, BaseConnection):
raise_error = True
else:
raise_error = True

if raise_error:
raise ImportError(filename)

return _class

connection_boilerplate = """from __future__ import unicode_literals
from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection

class {connection_class}(BaseConnection):
def __init__(self, connector):
# self.connector = connector
# self.connection = YourModule(self.connector.username, self.get_password())
# self.name_field = 'id'
pass

def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10):
pass

def insert(self, doctype, doc):
pass

def update(self, doctype, doc, migration_id):
pass

def delete(self, doctype, migration_id):
pass

"""

+ 13
- 5
frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py ファイルの表示

@@ -5,6 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import get_source_value


class DataMigrationMapping(Document): class DataMigrationMapping(Document):
def get_filters(self): def get_filters(self):
@@ -26,6 +27,7 @@ class DataMigrationMapping(Document):
return fields return fields


def get_mapped_record(self, doc): def get_mapped_record(self, doc):
'''Build a mapped record using information from the fields table'''
mapped = frappe._dict() mapped = frappe._dict()


key_fieldname = 'remote_fieldname' key_fieldname = 'remote_fieldname'
@@ -35,13 +37,19 @@ class DataMigrationMapping(Document):
key_fieldname, value_fieldname = value_fieldname, key_fieldname key_fieldname, value_fieldname = value_fieldname, key_fieldname


for field_map in self.fields: for field_map in self.fields:
key = get_source_value(field_map, key_fieldname)

if not field_map.is_child_table: if not field_map.is_child_table:
# field to field mapping
value = get_value_from_fieldname(field_map, value_fieldname, doc) value = get_value_from_fieldname(field_map, value_fieldname, doc)
mapped[field_map.get(key_fieldname)] = value
else: else:
# child table mapping
mapping_name = field_map.child_table_mapping mapping_name = field_map.child_table_mapping
value = get_mapped_child_records(mapping_name, doc.get(field_map.get(value_fieldname)))
mapped[field_map.get(key_fieldname)] = value
value = get_mapped_child_records(mapping_name,
doc.get(get_source_value(field_map, value_fieldname)))

mapped[key] = value

return mapped return mapped


def get_mapped_child_records(mapping_name, child_docs): def get_mapped_child_records(mapping_name, child_docs):
@@ -53,12 +61,12 @@ def get_mapped_child_records(mapping_name, child_docs):
return mapped_child_docs return mapped_child_docs


def get_value_from_fieldname(field_map, fieldname_field, doc): def get_value_from_fieldname(field_map, fieldname_field, doc):
field_name = field_map.get(fieldname_field)
field_name = get_source_value(field_map, fieldname_field)


if field_name.startswith('eval:'): if field_name.startswith('eval:'):
value = frappe.safe_eval(field_name[5:], dict(frappe=frappe)) value = frappe.safe_eval(field_name[5:], dict(frappe=frappe))
elif field_name[0] in ('"', "'"): elif field_name[0] in ('"', "'"):
value = field_name[1:-1] value = field_name[1:-1]
else: else:
value = doc.get(field_name)
value = get_source_value(doc, field_name)
return value return value

+ 4
- 2
frappe/data_migration/doctype/data_migration_plan/data_migration_plan.js ファイルの表示

@@ -2,7 +2,9 @@
// For license information, please see license.txt // For license information, please see license.txt


frappe.ui.form.on('Data Migration Plan', { frappe.ui.form.on('Data Migration Plan', {
refresh: function() {

onload(frm) {
frm.add_custom_button(__('Run'), () => frappe.new_doc('Data Migration Run', {
data_migration_plan: frm.doc.name
}));
} }
}); });

+ 43
- 54
frappe/data_migration/doctype/data_migration_run/data_migration_run.py ファイルの表示

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe, json, math import frappe, json, math
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.utils import get_source_value


class DataMigrationRun(Document): class DataMigrationRun(Document):


@@ -233,7 +234,8 @@ class DataMigrationRun(Document):
def get_or_filters(self, mapping): def get_or_filters(self, mapping):
or_filters = self.get_last_modified_condition() or_filters = self.get_last_modified_condition()


# include docs whose migration_id_field is not set
# docs whose migration_id_field is not set
# failed in the previous run, include those too
or_filters.update({ or_filters.update({
mapping.migration_id_field: ('=', '') mapping.migration_id_field: ('=', '')
}) })
@@ -268,9 +270,6 @@ class DataMigrationRun(Document):
connection = self.get_connection() connection = self.get_connection()
data = self.get_new_local_data() data = self.get_new_local_data()


push_insert = self.get_log('push_insert', 0)
push_failed = self.get_log('push_failed', [])

for d in data: for d in data:
# pre process before insert # pre process before insert
doc = self.pre_process_doc(d) doc = self.pre_process_doc(d)
@@ -282,12 +281,11 @@ class DataMigrationRun(Document):
mapping.migration_id_field, response_doc[connection.name_field], mapping.migration_id_field, response_doc[connection.name_field],
update_modified=False) update_modified=False)
frappe.db.commit() frappe.db.commit()
self.set_log('push_insert', push_insert + 1)
self.update_log('push_insert', 1)
# post process after insert # post process after insert
self.post_process_doc(local_doc=d, remote_doc=response_doc) self.post_process_doc(local_doc=d, remote_doc=response_doc)
except Exception: except Exception:
push_failed.append(d.as_json())
self.set_log('push_failed', push_failed)
self.update_log('push_failed', d.name)


# update page_start # update page_start
self.db_set('current_mapping_start', self.db_set('current_mapping_start',
@@ -308,9 +306,6 @@ class DataMigrationRun(Document):
connection = self.get_connection() connection = self.get_connection()
data = self.get_updated_local_data() data = self.get_updated_local_data()


push_update = self.get_log('push_update', 0)
push_failed = self.get_log('push_failed', [])

for d in data: for d in data:
migration_id_value = d.get(mapping.migration_id_field) migration_id_value = d.get(mapping.migration_id_field)
# pre process before update # pre process before update
@@ -318,12 +313,11 @@ class DataMigrationRun(Document):
doc = mapping.get_mapped_record(doc) doc = mapping.get_mapped_record(doc)
try: try:
response_doc = connection.update(mapping.remote_objectname, doc, migration_id_value) response_doc = connection.update(mapping.remote_objectname, doc, migration_id_value)
self.set_log('push_update', push_update + 1)
self.update_log('push_update', 1)
# post process after update # post process after update
self.post_process_doc(local_doc=d, remote_doc=response_doc) self.post_process_doc(local_doc=d, remote_doc=response_doc)
except Exception: except Exception:
push_failed.append(d.as_json())
self.set_log('push_failed', push_failed)
self.update_log('push_failed', d.name)


# update page_start # update page_start
self.db_set('current_mapping_start', self.db_set('current_mapping_start',
@@ -344,9 +338,6 @@ class DataMigrationRun(Document):
connection = self.get_connection() connection = self.get_connection()
data = self.get_deleted_local_data() data = self.get_deleted_local_data()


push_delete = self.get_log('push_delete', 0)
push_failed = self.get_log('push_failed', [])

for d in data: for d in data:
# Deleted Document also has a custom field for migration_id # Deleted Document also has a custom field for migration_id
migration_id_value = d.get(mapping.migration_id_field) migration_id_value = d.get(mapping.migration_id_field)
@@ -354,12 +345,11 @@ class DataMigrationRun(Document):
self.pre_process_doc(d) self.pre_process_doc(d)
try: try:
response_doc = connection.delete(mapping.remote_objectname, migration_id_value) response_doc = connection.delete(mapping.remote_objectname, migration_id_value)
self.set_log('push_delete', push_delete + 1)
self.update_log('push_delete', 1)
# post process only when action is success # post process only when action is success
self.post_process_doc(local_doc=d, remote_doc=response_doc) self.post_process_doc(local_doc=d, remote_doc=response_doc)
except Exception: except Exception:
push_failed.append(d.as_json())
self.set_log('push_failed', push_failed)
self.update_log('push_failed', d.name)


# update page_start # update page_start
self.db_set('current_mapping_start', self.db_set('current_mapping_start',
@@ -377,46 +367,32 @@ class DataMigrationRun(Document):
mapping = self.get_mapping(self.current_mapping) mapping = self.get_mapping(self.current_mapping)
data = self.get_remote_data() data = self.get_remote_data()


pull_insert = self.get_log('pull_insert', 0)
pull_update = self.get_log('pull_update', 0)
pull_failed = self.get_log('pull_failed', [])

def get_migration_id_value(source, key):
value = None
try:
value = source[key]
except:
value = getattr(source, key)
return value

for d in data: for d in data:
migration_id_value = get_migration_id_value(d, connection.name_field)
migration_id_value = get_source_value(d, connection.name_field)
doc = self.pre_process_doc(d) doc = self.pre_process_doc(d)
doc = mapping.get_mapped_record(doc) doc = mapping.get_mapped_record(doc)


if migration_id_value: if migration_id_value:
if not local_doc_exists(mapping, migration_id_value):
# insert new local doc
local_doc = insert_local_doc(mapping, doc)

self.set_log('pull_insert', pull_insert + 1)
# set migration id
frappe.db.set_value(mapping.local_doctype, local_doc.name,
mapping.migration_id_field, migration_id_value,
update_modified=False)
frappe.db.commit()
else:
# update doc
local_doc = update_local_doc(mapping, doc, migration_id_value)
self.set_log('pull_update', pull_update + 1)

if local_doc:
# post process doc after success
self.post_process_doc(remote_doc=d, local_doc=local_doc)
else:
# failed, append to log
pull_failed.append(d)
self.set_log('pull_failed', pull_failed)
try:
if not local_doc_exists(mapping, migration_id_value):
# insert new local doc
local_doc = insert_local_doc(mapping, doc)

self.update_log('pull_insert', 1)
# set migration id
frappe.db.set_value(mapping.local_doctype, local_doc.name,
mapping.migration_id_field, migration_id_value,
update_modified=False)
frappe.db.commit()
else:
# update doc
local_doc = update_local_doc(mapping, doc, migration_id_value)
self.update_log('pull_update', 1)
# post process doc after success
self.post_process_doc(remote_doc=d, local_doc=local_doc)
except Exception:
# failed, append to log
self.update_log('pull_failed', migration_id_value)


if len(data) < mapping.page_length: if len(data) < mapping.page_length:
# last page, done with pull # last page, done with pull
@@ -436,6 +412,19 @@ class DataMigrationRun(Document):
value = json.dumps(value) if '_failed' in key else value value = json.dumps(value) if '_failed' in key else value
self.db_set(key, value) self.db_set(key, value)


def update_log(self, key, value=None):
'''
Helper for updating logs,
push_failed and pull_failed are stored as json,
other keys are stored as int
'''
if '_failed' in key:
# json
self.set_log(key, self.get_log(key, []) + [value])
else:
# int
self.set_log(key, self.get_log(key, 0) + (value or 1))

def get_log(self, key, default=None): def get_log(self, key, default=None):
value = self.db_get(key) value = self.db_get(key)
if '_failed' in key: if '_failed' in key:


+ 5
- 5
frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py ファイルの表示

@@ -8,13 +8,13 @@ class TestDataMigrationRun(unittest.TestCase):
def test_run(self): def test_run(self):
create_plan() create_plan()


description = 'Data migration todo'
description = 'data migration todo'
new_todo = frappe.get_doc({ new_todo = frappe.get_doc({
'doctype': 'ToDo', 'doctype': 'ToDo',
'description': description 'description': description
}).insert() }).insert()


event_subject = 'Data migration event'
event_subject = 'data migration event'
frappe.get_doc(dict( frappe.get_doc(dict(
doctype='Event', doctype='Event',
subject=event_subject, subject=event_subject,
@@ -62,7 +62,6 @@ class TestDataMigrationRun(unittest.TestCase):


# Update # Update
self.assertEqual(run.db_get('status'), 'Success') self.assertEqual(run.db_get('status'), 'Success')
self.assertEqual(run.db_get('push_update'), 1)
self.assertEqual(run.db_get('pull_update'), 1) self.assertEqual(run.db_get('pull_update'), 1)


def create_plan(): def create_plan():
@@ -76,7 +75,8 @@ def create_plan():
'fields': [ 'fields': [
{ 'remote_fieldname': 'subject', 'local_fieldname': 'description' }, { 'remote_fieldname': 'subject', 'local_fieldname': 'description' },
{ 'remote_fieldname': 'starts_on', 'local_fieldname': 'eval:frappe.utils.get_datetime_str(frappe.utils.get_datetime())' } { 'remote_fieldname': 'starts_on', 'local_fieldname': 'eval:frappe.utils.get_datetime_str(frappe.utils.get_datetime())' }
]
],
'condition': '{"description": "data migration todo" }'
}).insert() }).insert()


frappe.get_doc({ frappe.get_doc({
@@ -87,7 +87,7 @@ def create_plan():
'local_doctype': 'ToDo', 'local_doctype': 'ToDo',
'local_primary_key': 'name', 'local_primary_key': 'name',
'mapping_type': 'Pull', 'mapping_type': 'Pull',
'condition': '{"subject": "Data migration event"}',
'condition': '{"subject": "data migration event" }',
'fields': [ 'fields': [
{ 'remote_fieldname': 'subject', 'local_fieldname': 'description' } { 'remote_fieldname': 'subject', 'local_fieldname': 'description' }
] ]


+ 0
- 1
frappe/email/email_body.py ファイルの表示

@@ -423,7 +423,6 @@ def get_filecontent_from_path(path):


return filecontent return filecontent
else: else:
print(full_path + ' doesn\'t exists')
return None return None






+ 4
- 0
frappe/model/base_document.py ファイルの表示

@@ -530,6 +530,10 @@ class BaseDocument(object):
if frappe.flags.in_install: if frappe.flags.in_install:
return return


if self.meta.issingle:
# single doctype value type is mediumtext
return

for fieldname, value in iteritems(self.get_valid_dict()): for fieldname, value in iteritems(self.get_valid_dict()):
df = self.meta.get_field(fieldname) df = self.meta.get_field(fieldname)
if df and df.fieldtype in type_map and type_map[df.fieldtype][0]=="varchar": if df and df.fieldtype in type_map and type_map[df.fieldtype][0]=="varchar":


+ 3
- 2
frappe/modules/utils.py ファイルの表示

@@ -100,8 +100,9 @@ def sync_customizations_for_doctype(data):
update_schema = False update_schema = False


def sync(key, custom_doctype, doctype_fieldname): def sync(key, custom_doctype, doctype_fieldname):
frappe.db.sql('delete from `tab{0}` where `{1}`=%s'.format(custom_doctype, doctype_fieldname),
doctype)
doctypes = list(set(map(lambda row: row.get(doctype_fieldname), data[key])))
frappe.db.sql('delete from `tab{0}` where `{1}` in ({2})'.format(
custom_doctype, doctype_fieldname, ",".join(["'%s'" % dt for dt in doctypes])))


for d in data[key]: for d in data[key]:
d['doctype'] = custom_doctype d['doctype'] = custom_doctype


+ 2
- 1
frappe/patches.txt ファイルの表示

@@ -194,4 +194,5 @@ frappe.patches.v8_x.update_user_permission
frappe.patches.v8_5.patch_event_colors frappe.patches.v8_5.patch_event_colors
frappe.patches.v8_10.delete_static_web_page_from_global_search frappe.patches.v8_10.delete_static_web_page_from_global_search
frappe.patches.v8_x.add_bgn_xaf_xof_currencies frappe.patches.v8_x.add_bgn_xaf_xof_currencies
frappe.patches.v9_1.add_sms_sender_name_as_parameters
frappe.patches.v9_1.add_sms_sender_name_as_parameters
execute:frappe.get_single('Domain Settings').save()

+ 1
- 0
frappe/tests/test_domainification.py ファイルの表示

@@ -23,6 +23,7 @@ class TestDomainification(unittest.TestCase):
frappe.db.sql("delete from `tabHas Role` where role='_Test Role'") frappe.db.sql("delete from `tabHas Role` where role='_Test Role'")
frappe.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')") frappe.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')")
frappe.delete_doc('DocType', 'Test Domainification') frappe.delete_doc('DocType', 'Test Domainification')
self.remove_from_active_domains(remove_all=True)


def add_active_domain(self, domain): def add_active_domain(self, domain):
""" add domain in active domain """ """ add domain in active domain """


+ 17
- 0
frappe/utils/data.py ファイルの表示

@@ -14,6 +14,7 @@ from num2words import num2words
from six.moves import html_parser as HTMLParser from six.moves import html_parser as HTMLParser
from six.moves.urllib.parse import quote, urljoin from six.moves.urllib.parse import quote, urljoin
from html2text import html2text from html2text import html2text
from markdown2 import markdown, MarkdownError
from six import iteritems, text_type, string_types, integer_types from six import iteritems, text_type, string_types, integer_types


DATE_FORMAT = "%Y-%m-%d" DATE_FORMAT = "%Y-%m-%d"
@@ -854,3 +855,19 @@ def to_markdown(html):
pass pass


return text return text

def to_html(markdown_text):
html = None
try:
html = markdown(markdown_text)
except MarkdownError:
pass

return html

def get_source_value(source, key):
'''Get value from source (object or dict) based on key'''
if isinstance(source, dict):
return source.get(key)
else:
return getattr(source, key)

読み込み中…
キャンセル
保存