@@ -120,49 +120,10 @@ jobs: | |||
CI_BUILD_ID: ${{ github.run_id }} | |||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | |||
- name: Upload Coverage Data | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
id: upload-coverage-data | |||
run: | | |||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} | |||
cd ${GITHUB_WORKSPACE} | |||
pip3 install coverage==5.5 | |||
pip3 install coveralls==3.0.1 | |||
coveralls | |||
env: | |||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} | |||
COVERALLS_FLAG_NAME: run-${{ matrix.container }} | |||
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} | |||
COVERALLS_PARALLEL: true | |||
- run: echo ${{ steps.check-build.outputs.build }} > guess-the-fruit.txt | |||
- uses: actions/upload-artifact@v1 | |||
with: | |||
name: fruit | |||
path: guess-the-fruit.txt | |||
coveralls: | |||
name: Coverage Wrap Up | |||
needs: test | |||
container: python:3-slim | |||
runs-on: ubuntu-18.04 | |||
steps: | |||
- uses: actions/download-artifact@v1 | |||
- name: Upload coverage data | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
name: fruit | |||
- run: echo "WILDCARD=$(cat fruit/guess-the-fruit.txt)" >> $GITHUB_ENV | |||
- name: Clone | |||
if: ${{ env.WILDCARD == 'strawberry' }} | |||
uses: actions/checkout@v2 | |||
- name: Coveralls Finished | |||
if: ${{ env.WILDCARD == 'strawberry' }} | |||
run: | | |||
cd ${GITHUB_WORKSPACE} | |||
pip3 install coverage==5.5 | |||
pip3 install coveralls==3.0.1 | |||
coveralls --finish | |||
env: | |||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |||
name: MariaDB | |||
fail_ci_if_error: true | |||
files: /home/runner/frappe-bench/sites/coverage.xml | |||
verbose: true |
@@ -3,6 +3,8 @@ name: Server | |||
on: | |||
pull_request: | |||
workflow_dispatch: | |||
push: | |||
branches: [ develop ] | |||
concurrency: | |||
group: server-postgres-develop-${{ github.event.number }} | |||
@@ -116,7 +118,15 @@ jobs: | |||
- name: Run Tests | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator | |||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage | |||
env: | |||
CI_BUILD_ID: ${{ github.run_id }} | |||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io | |||
- name: Upload coverage data | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
name: Postgres | |||
fail_ci_if_error: true | |||
files: /home/runner/frappe-bench/sites/coverage.xml | |||
verbose: true |
@@ -2,7 +2,9 @@ pull_request_rules: | |||
- name: Auto-close PRs on stable branch | |||
conditions: | |||
- and: | |||
- author!=surajshetty3416 | |||
- and: | |||
- author!=surajshetty3416 | |||
- author!=gavindsouza | |||
- or: | |||
- base=version-13 | |||
- base=version-12 | |||
@@ -26,8 +26,8 @@ | |||
<a href='https://www.codetriage.com/frappe/frappe'> | |||
<img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'> | |||
</a> | |||
<a href='https://coveralls.io/github/frappe/frappe?branch=develop'> | |||
<img src='https://coveralls.io/repos/github/frappe/frappe/badge.svg?branch=develop'> | |||
<a href="https://codecov.io/gh/frappe/frappe"> | |||
<img src="https://codecov.io/gh/frappe/frappe/branch/develop/graph/badge.svg?token=XoTa679hIj"/> | |||
</a> | |||
</div> | |||
@@ -0,0 +1,9 @@ | |||
codecov: | |||
require_ci_to_pass: yes | |||
status: | |||
project: | |||
default: | |||
threshold: 0.5% | |||
comment: | |||
layout: "diff, flags, files" | |||
require_changes: true |
@@ -140,7 +140,11 @@ lang = local("lang") | |||
if typing.TYPE_CHECKING: | |||
from frappe.database.mariadb.database import MariaDBDatabase | |||
from frappe.database.postgres.database import PostgresDatabase | |||
from pypika import Query | |||
db: typing.Union[MariaDBDatabase, PostgresDatabase] | |||
qb: Query | |||
# end: static analysis hack | |||
def init(site, sites_path=None, new_site=False): | |||
@@ -58,4 +58,5 @@ class CodeCoverage(): | |||
def __exit__(self, exc_type, exc_value, traceback): | |||
if self.with_coverage: | |||
self.coverage.stop() | |||
self.coverage.save() | |||
self.coverage.save() | |||
self.coverage.xml_report() |
@@ -14,7 +14,7 @@ import frappe.model.meta | |||
from frappe import _ | |||
from time import time | |||
from frappe.utils import now, getdate, cast_fieldtype, get_datetime, get_table_name | |||
from frappe.utils import now, getdate, cast, get_datetime, get_table_name | |||
from frappe.model.utils.link_count import flush_local_link_count | |||
@@ -516,7 +516,6 @@ class Database(object): | |||
FROM `tabSingles` | |||
WHERE doctype = %s | |||
""", doctype) | |||
# result = _cast_result(doctype, result) | |||
dict_ = frappe._dict(result) | |||
@@ -557,7 +556,7 @@ class Database(object): | |||
if not df: | |||
frappe.throw(_('Invalid field name: {0}').format(frappe.bold(fieldname)), self.InvalidColumnName) | |||
val = cast_fieldtype(df.fieldtype, val) | |||
val = cast(df.fieldtype, val) | |||
self.value_cache[doctype][fieldname] = val | |||
@@ -1052,19 +1051,3 @@ def enqueue_jobs_after_commit(): | |||
q.enqueue_call(execute_job, timeout=job.get("timeout"), | |||
kwargs=job.get("queue_args")) | |||
frappe.flags.enqueue_after_commit = [] | |||
# Helpers | |||
def _cast_result(doctype, result): | |||
batch = [ ] | |||
try: | |||
for field, value in result: | |||
df = frappe.get_meta(doctype).get_field(field) | |||
if df: | |||
value = cast_fieldtype(df.fieldtype, value) | |||
batch.append(tuple([field, value])) | |||
except frappe.exceptions.DoesNotExistError: | |||
return result | |||
return tuple(batch) |
@@ -242,6 +242,7 @@ | |||
"label": "Parent Page" | |||
}, | |||
{ | |||
"default": "[]", | |||
"fieldname": "content", | |||
"fieldtype": "Long Text", | |||
"hidden": 1, | |||
@@ -265,7 +266,7 @@ | |||
} | |||
], | |||
"links": [], | |||
"modified": "2021-08-19 12:51:00.233017", | |||
"modified": "2021-08-30 18:47:18.227154", | |||
"modified_by": "Administrator", | |||
"module": "Desk", | |||
"name": "Workspace", | |||
@@ -62,7 +62,7 @@ class Workspace(Document): | |||
for link in self.links: | |||
link = link.as_dict() | |||
if link.type == "Card Break": | |||
if card_links and (not current_card['only_for'] or current_card['only_for'] == frappe.get_system_settings('country')): | |||
if card_links and (not current_card.get('only_for') or current_card.get('only_for') == frappe.get_system_settings('country')): | |||
current_card['links'] = card_links | |||
cards.append(current_card) | |||
@@ -969,7 +969,7 @@ class BaseDocument(object): | |||
return self.cast(val, df) | |||
def cast(self, value, df): | |||
return cast_fieldtype(df.fieldtype, value) | |||
return cast_fieldtype(df.fieldtype, value, show_warning=False) | |||
def _extract_images_from_text_editor(self): | |||
from frappe.core.doctype.file.file import extract_images_from_doc | |||
@@ -16,7 +16,7 @@ Example: | |||
''' | |||
from datetime import datetime | |||
import frappe, json, os | |||
from frappe.utils import cstr, cint, cast_fieldtype | |||
from frappe.utils import cstr, cint, cast | |||
from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields | |||
from frappe.model.document import Document | |||
from frappe.model.base_document import BaseDocument | |||
@@ -322,24 +322,24 @@ class Meta(Document): | |||
for ps in property_setters: | |||
if ps.doctype_or_field=='DocType': | |||
self.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) | |||
self.set(ps.property, cast(ps.property_type, ps.value)) | |||
elif ps.doctype_or_field=='DocField': | |||
for d in self.fields: | |||
if d.fieldname == ps.field_name: | |||
d.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) | |||
d.set(ps.property, cast(ps.property_type, ps.value)) | |||
break | |||
elif ps.doctype_or_field=='DocType Link': | |||
for d in self.links: | |||
if d.name == ps.row_name: | |||
d.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) | |||
d.set(ps.property, cast(ps.property_type, ps.value)) | |||
break | |||
elif ps.doctype_or_field=='DocType Action': | |||
for d in self.actions: | |||
if d.name == ps.row_name: | |||
d.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) | |||
d.set(ps.property, cast(ps.property_type, ps.value)) | |||
break | |||
def add_custom_links_and_actions(self): | |||
@@ -532,7 +532,7 @@ class Meta(Document): | |||
label = link.group, | |||
items = [link.parent_doctype or link.link_doctype] | |||
)) | |||
if not link.is_child_table: | |||
if link.link_fieldname != data.fieldname: | |||
if data.fieldname: | |||
@@ -285,10 +285,6 @@ frappe.Application = class Application { | |||
frappe.modules[page.module]=page; | |||
frappe.workspaces[frappe.router.slug(page.title)] = page; | |||
} | |||
if (!frappe.workspaces['home']) { | |||
// default workspace is settings for Frappe | |||
frappe.workspaces['home'] = frappe.workspaces[Object.keys(frappe.workspaces)[0]]; | |||
} | |||
} | |||
load_user_permissions() { | |||
@@ -786,6 +786,7 @@ export default class Grid { | |||
doctype: link_field.options, | |||
fieldname: link, | |||
qty_fieldname: qty, | |||
get_query: link_field.get_query, | |||
target: this, | |||
txt: "" | |||
}); | |||
@@ -96,10 +96,10 @@ frappe.ui.form.LinkSelector = class LinkSelector { | |||
.attr('data-value', v[0]) | |||
.click(function () { | |||
var value = $(this).attr("data-value"); | |||
var $link = this; | |||
if (me.target.is_grid) { | |||
// set in grid | |||
me.set_in_grid(value); | |||
// call search after value is set to get latest filtered results | |||
me.set_in_grid(value).then(() => me.search()); | |||
} else { | |||
if (me.target.doctype) | |||
me.target.parse_validate_and_set_in_model(value); | |||
@@ -110,8 +110,8 @@ frappe.ui.form.LinkSelector = class LinkSelector { | |||
me.dialog.hide(); | |||
} | |||
return false; | |||
}) | |||
}) | |||
}); | |||
}); | |||
} else { | |||
$('<p><br><span class="text-muted">' + __("No Results") + '</span>' | |||
+ (frappe.model.can_create(me.doctype) ? | |||
@@ -130,49 +130,56 @@ frappe.ui.form.LinkSelector = class LinkSelector { | |||
}, this.dialog.get_primary_btn()); | |||
} | |||
set_in_grid (value) { | |||
var me = this, updated = false; | |||
var d = null; | |||
if (this.qty_fieldname) { | |||
frappe.prompt({ | |||
fieldname: "qty", fieldtype: "Float", label: "Qty", | |||
"default": 1, reqd: 1 | |||
}, function (data) { | |||
$.each(me.target.frm.doc[me.target.df.fieldname] || [], function (i, d) { | |||
if (d[me.fieldname] === value) { | |||
frappe.model.set_value(d.doctype, d.name, me.qty_fieldname, data.qty); | |||
frappe.show_alert(__("Added {0} ({1})", [value, d[me.qty_fieldname]])); | |||
updated = true; | |||
return false; | |||
set_in_grid(value) { | |||
return new Promise((resolve) => { | |||
if (this.qty_fieldname) { | |||
frappe.prompt({ | |||
fieldname: "qty", | |||
fieldtype: "Float", | |||
label: "Qty", | |||
default: 1, | |||
reqd: 1 | |||
}, (data) => { | |||
let updated = (this.target.frm.doc[this.target.df.fieldname] || []).some(d => { | |||
if (d[this.fieldname] === value) { | |||
frappe.model.set_value(d.doctype, d.name, this.qty_fieldname, data.qty).then(() => { | |||
frappe.show_alert(__("Added {0} ({1})", [value, d[this.qty_fieldname]])); | |||
resolve(); | |||
}); | |||
return true; | |||
} | |||
}); | |||
if (!updated) { | |||
let d = null; | |||
frappe.run_serially([ | |||
() => d = this.target.add_new_row(), | |||
() => frappe.timeout(0.1), | |||
() => { | |||
let args = {}; | |||
args[this.fieldname] = value; | |||
args[this.qty_fieldname] = data.qty; | |||
return frappe.model.set_value(d.doctype, d.name, args); | |||
}, | |||
() => frappe.show_alert(__("Added {0} ({1})", [value, data.qty])), | |||
() => resolve() | |||
]); | |||
} | |||
}, __("Set Quantity"), __("Set")); | |||
} else if (this.dynamic_link_field) { | |||
let d = this.target.add_new_row(); | |||
frappe.model.set_value(d.doctype, d.name, this.dynamic_link_field, this.dynamic_link_reference); | |||
frappe.model.set_value(d.doctype, d.name, this.fieldname, value).then(() => { | |||
frappe.show_alert(__("{0} {1} added", [this.dynamic_link_reference, value])); | |||
resolve(); | |||
}); | |||
if (!updated) { | |||
frappe.run_serially([ | |||
() => { | |||
d = me.target.add_new_row(); | |||
}, | |||
() => frappe.timeout(0.1), | |||
() => { | |||
let args = {}; | |||
args[me.fieldname] = value; | |||
args[me.qty_fieldname] = data.qty; | |||
return frappe.model.set_value(d.doctype, d.name, args); | |||
}, | |||
() => frappe.show_alert(__("Added {0} ({1})", [value, data.qty])) | |||
]); | |||
} | |||
}, __("Set Quantity"), __("Set")); | |||
} else if (me.dynamic_link_field) { | |||
var d = me.target.add_new_row(); | |||
frappe.model.set_value(d.doctype, d.name, me.dynamic_link_field, me.dynamic_link_reference); | |||
frappe.model.set_value(d.doctype, d.name, me.fieldname, value); | |||
frappe.show_alert(__("{0} {1} added", [me.dynamic_link_reference, value])); | |||
} else { | |||
var d = me.target.add_new_row(); | |||
frappe.model.set_value(d.doctype, d.name, me.fieldname, value); | |||
frappe.show_alert(__("{0} added", [value])); | |||
} | |||
} else { | |||
let d = this.target.add_new_row(); | |||
frappe.model.set_value(d.doctype, d.name, this.fieldname, value).then(() => { | |||
frappe.show_alert(__("{0} added", [value])); | |||
resolve(); | |||
}); | |||
} | |||
}); | |||
} | |||
}; | |||
@@ -952,9 +952,16 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
get_indicator_html(doc) { | |||
const indicator = frappe.get_indicator(doc, this.doctype); | |||
// sequence is important | |||
const docstatus_description = [ | |||
__('Document is in draft state'), | |||
__('Document has been submitted'), | |||
__('Document has been cancelled') | |||
]; | |||
const title = docstatus_description[doc.docstatus || 0]; | |||
if (indicator) { | |||
return `<span class="indicator-pill ${indicator[1]} filterable ellipsis" | |||
data-filter='${indicator[2]}'> | |||
data-filter='${indicator[2]}' title='${title}'> | |||
<span class="ellipsis"> ${__(indicator[0])}</span> | |||
<span>`; | |||
} | |||
@@ -175,4 +175,4 @@ | |||
@keyframes blink { | |||
50% { opacity: 0.5; } | |||
} | |||
} |
@@ -6,12 +6,13 @@ import frappe | |||
from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url | |||
from frappe.utils import validate_url, validate_email_address | |||
from frappe.utils import ceil, floor | |||
from frappe.utils.data import validate_python_code | |||
from frappe.utils.data import cast, validate_python_code | |||
from PIL import Image | |||
from frappe.utils.image import strip_exif_data, optimize_image | |||
import io | |||
from mimetypes import guess_type | |||
from datetime import datetime, timedelta, date | |||
class TestFilters(unittest.TestCase): | |||
def test_simple_dict(self): | |||
@@ -93,6 +94,45 @@ class TestDataManipulation(unittest.TestCase): | |||
self.assertTrue('style="background-image: url(\'{0}/assets/frappe/bg.jpg\') !important"'.format(url) in html) | |||
self.assertTrue('<a href="mailto:test@example.com">email</a>' in html) | |||
class TestFieldCasting(unittest.TestCase): | |||
def test_str_types(self): | |||
STR_TYPES = ( | |||
"Data", "Text", "Small Text", "Long Text", "Text Editor", "Select", "Link", "Dynamic Link" | |||
) | |||
for fieldtype in STR_TYPES: | |||
self.assertIsInstance(cast(fieldtype, value=None), str) | |||
self.assertIsInstance(cast(fieldtype, value="12-12-2021"), str) | |||
self.assertIsInstance(cast(fieldtype, value=""), str) | |||
self.assertIsInstance(cast(fieldtype, value=[]), str) | |||
self.assertIsInstance(cast(fieldtype, value=set()), str) | |||
def test_float_types(self): | |||
FLOAT_TYPES = ("Currency", "Float", "Percent") | |||
for fieldtype in FLOAT_TYPES: | |||
self.assertIsInstance(cast(fieldtype, value=None), float) | |||
self.assertIsInstance(cast(fieldtype, value=1.12), float) | |||
self.assertIsInstance(cast(fieldtype, value=112), float) | |||
def test_int_types(self): | |||
INT_TYPES = ("Int", "Check") | |||
for fieldtype in INT_TYPES: | |||
self.assertIsInstance(cast(fieldtype, value=None), int) | |||
self.assertIsInstance(cast(fieldtype, value=1.12), int) | |||
self.assertIsInstance(cast(fieldtype, value=112), int) | |||
def test_datetime_types(self): | |||
self.assertIsInstance(cast("Datetime", value=None), datetime) | |||
self.assertIsInstance(cast("Datetime", value="12-2-22"), datetime) | |||
def test_date_types(self): | |||
self.assertIsInstance(cast("Date", value=None), date) | |||
self.assertIsInstance(cast("Date", value="12-12-2021"), date) | |||
def test_time_types(self): | |||
self.assertIsInstance(cast("Time", value=None), timedelta) | |||
self.assertIsInstance(cast("Time", value="12:03:34"), timedelta) | |||
class TestMathUtils(unittest.TestCase): | |||
def test_floor(self): | |||
from decimal import Decimal | |||
@@ -205,7 +245,6 @@ class TestImage(unittest.TestCase): | |||
self.assertLess(len(optimized_content), len(original_content)) | |||
class TestPythonExpressions(unittest.TestCase): | |||
def test_validation_for_good_python_expression(self): | |||
valid_expressions = [ | |||
"foo == bar", | |||
@@ -229,4 +268,4 @@ class TestPythonExpressions(unittest.TestCase): | |||
"oops = forgot_equals", | |||
] | |||
for expr in invalid_expressions: | |||
self.assertRaises(frappe.ValidationError, validate_python_code, expr) | |||
self.assertRaises(frappe.ValidationError, validate_python_code, expr) |
@@ -1,6 +1,7 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from typing import Optional | |||
import frappe | |||
import operator | |||
import json | |||
@@ -8,6 +9,7 @@ import re, datetime, math, time | |||
from code import compile_command | |||
from urllib.parse import quote, urljoin | |||
from frappe.desk.utils import slug | |||
from click import secho | |||
DATE_FORMAT = "%Y-%m-%d" | |||
TIME_FORMAT = "%H:%M:%S.%f" | |||
@@ -16,10 +18,10 @@ DATETIME_FORMAT = DATE_FORMAT + " " + TIME_FORMAT | |||
def is_invalid_date_string(date_string): | |||
# dateutil parser does not agree with dates like "0001-01-01" or "0000-00-00" | |||
return (not date_string) or (date_string or "").startswith(("0001-01-01", "0000-00-00")) | |||
return not isinstance(date_string, str) or ((not date_string) or (date_string or "").startswith(("0001-01-01", "0000-00-00"))) | |||
# datetime functions | |||
def getdate(string_date=None): | |||
def getdate(string_date: Optional[str] = None): | |||
""" | |||
Converts string date (yyyy-mm-dd) to datetime.date object. | |||
If no input is provided, current date is returned. | |||
@@ -67,6 +69,31 @@ def get_datetime(datetime_str=None): | |||
except ValueError: | |||
return parser.parse(datetime_str) | |||
def get_timedelta(time: Optional[str] = None) -> Optional[datetime.timedelta]: | |||
"""Return `datetime.timedelta` object from string value of a | |||
valid time format. Returns None if `time` is not a valid format | |||
Args: | |||
time (str): A valid time representation. This string is parsed | |||
using `dateutil.parser.parse`. Examples of valid inputs are: | |||
'0:0:0', '17:21:00', '2012-01-19 17:21:00'. Checkout | |||
https://dateutil.readthedocs.io/en/stable/parser.html#dateutil.parser.parse | |||
Returns: | |||
datetime.timedelta: Timedelta object equivalent of the passed `time` string | |||
""" | |||
from dateutil import parser | |||
time = time or "0:0:0" | |||
try: | |||
t = parser.parse(time) | |||
return datetime.timedelta( | |||
hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond | |||
) | |||
except Exception: | |||
return None | |||
def to_timedelta(time_str): | |||
from dateutil import parser | |||
@@ -505,7 +532,14 @@ def has_common(l1, l2): | |||
"""Returns truthy value if there are common elements in lists l1 and l2""" | |||
return set(l1) & set(l2) | |||
def cast_fieldtype(fieldtype, value): | |||
def cast_fieldtype(fieldtype, value, show_warning=True): | |||
if show_warning: | |||
message = ( | |||
"Function `frappe.utils.data.cast` has been deprecated in favour" | |||
" of `frappe.utils.data.cast`. Use the newer util for safer type casting." | |||
) | |||
secho(message, fg="yellow") | |||
if fieldtype in ("Currency", "Float", "Percent"): | |||
value = flt(value) | |||
@@ -527,6 +561,46 @@ def cast_fieldtype(fieldtype, value): | |||
return value | |||
def cast(fieldtype, value=None): | |||
"""Cast the value to the Python native object of the Frappe fieldtype provided. | |||
If value is None, the first/lowest value of the `fieldtype` will be returned. | |||
If value can't be cast as fieldtype due to an invalid input, None will be returned. | |||
Mapping of Python types => Frappe types: | |||
* str => ("Data", "Text", "Small Text", "Long Text", "Text Editor", "Select", "Link", "Dynamic Link") | |||
* float => ("Currency", "Float", "Percent") | |||
* int => ("Int", "Check") | |||
* datetime.datetime => ("Datetime",) | |||
* datetime.date => ("Date",) | |||
* datetime.time => ("Time",) | |||
""" | |||
if fieldtype in ("Currency", "Float", "Percent"): | |||
value = flt(value) | |||
elif fieldtype in ("Int", "Check"): | |||
value = cint(value) | |||
elif fieldtype in ("Data", "Text", "Small Text", "Long Text", | |||
"Text Editor", "Select", "Link", "Dynamic Link"): | |||
value = cstr(value) | |||
elif fieldtype == "Date": | |||
if value: | |||
value = getdate(value) | |||
else: | |||
value = datetime.datetime(1, 1, 1).date() | |||
elif fieldtype == "Datetime": | |||
if value: | |||
value = get_datetime(value) | |||
else: | |||
value = datetime.datetime(1, 1, 1) | |||
elif fieldtype == "Time": | |||
value = get_timedelta(value) | |||
return value | |||
def flt(s, precision=None): | |||
"""Convert to float (ignoring commas in string) | |||
@@ -1202,7 +1276,7 @@ def evaluate_filters(doc, filters): | |||
def compare(val1, condition, val2, fieldtype=None): | |||
ret = False | |||
if fieldtype: | |||
val2 = cast_fieldtype(fieldtype, val2) | |||
val2 = cast(fieldtype, val2) | |||
if condition in operator_map: | |||
ret = operator_map[condition](val1, val2) | |||
@@ -30,8 +30,14 @@ class NamespaceDict(frappe._dict): | |||
def safe_exec(script, _globals=None, _locals=None): | |||
# script reports must be enabled via site_config.json | |||
if not frappe.conf.server_script_enabled: | |||
# server scripts can be disabled via site_config.json | |||
# they are enabled by default | |||
if 'server_script_enabled' in frappe.conf: | |||
enabled = frappe.conf.server_script_enabled | |||
else: | |||
enabled = True | |||
if not enabled: | |||
frappe.throw(_('Please Enable Server Scripts'), ServerScriptNotEnabled) | |||
# build globals | |||
@@ -228,6 +234,7 @@ VALID_UTILS = ( | |||
"getdate", | |||
"get_datetime", | |||
"to_timedelta", | |||
"get_timedelta", | |||
"add_to_date", | |||
"add_days", | |||
"add_months", | |||