Merge branch 'develop' into set-property-after-alert-fix
This commit is contained in:
commit
7a25cc2ae9
20 changed files with 240 additions and 138 deletions
51
.github/workflows/server-mariadb-tests.yml
vendored
51
.github/workflows/server-mariadb-tests.yml
vendored
|
|
@ -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
|
||||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v2
|
||||
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
|
||||
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
|
||||
|
|
|
|||
12
.github/workflows/server-postgres-tests.yml
vendored
12
.github/workflows/server-postgres-tests.yml
vendored
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
9
codecov.yml
Normal file
9
codecov.yml
Normal file
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue