* feat: relative timeframe filters * fix: resolve syntax errors * fix: Translated optionsversion-14
@@ -0,0 +1,5 @@ | |||
{ | |||
"name": "Using fixtures to represent data", | |||
"email": "hello@cypress.io", | |||
"body": "Fixtures are a great way to mock data for responses to routes" | |||
} |
@@ -0,0 +1,50 @@ | |||
context('Relative Timeframe', () => { | |||
beforeEach(() => { | |||
cy.login('Administrator', 'qwe'); | |||
cy.visit('/desk'); | |||
}); | |||
before(() => { | |||
cy.login('Administrator', 'qwe'); | |||
cy.visit('/desk'); | |||
cy.window().its('frappe').then(frappe => { | |||
frappe.call("frappe.tests.test_utils.create_todo_records"); | |||
}); | |||
}); | |||
it('set relative filter for Previous and check list', () => { | |||
cy.visit('/desk#List/ToDo/List'); | |||
cy.get('.list-row:contains("this is fourth todo")').should('exist'); | |||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click(); | |||
cy.get('.fieldname-select-area').should('exist'); | |||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 }); | |||
cy.get('select.condition.form-control').select("Previous"); | |||
cy.get('.filter-field select.input-with-feedback.form-control').select("1 week"); | |||
cy.server(); | |||
cy.route({ | |||
method: 'POST', | |||
url: '/' | |||
}).as('applyFilter'); | |||
cy.get('.filter-box .btn:contains("Apply")').click(); | |||
cy.wait('@applyFilter'); | |||
cy.get('.list-row-container').its('length').should('eq', 1); | |||
cy.get('.list-row-container').should('contain', 'this is second todo'); | |||
cy.get('.remove-filter.btn').click(); | |||
}); | |||
it('set relative filter for Next and check list', () => { | |||
cy.visit('/desk#List/ToDo/List'); | |||
cy.get('.list-row:contains("this is fourth todo")').should('exist'); | |||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click(); | |||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 }); | |||
cy.get('select.condition.form-control').select("Next"); | |||
cy.get('.filter-field select.input-with-feedback.form-control').select("1 week"); | |||
cy.server(); | |||
cy.route({ | |||
method: 'POST', | |||
url: '/' | |||
}).as('applyFilter'); | |||
cy.get('.filter-box .btn:contains("Apply")').click(); | |||
cy.wait('@applyFilter'); | |||
cy.get('.list-row-container').its('length').should('eq', 1); | |||
cy.get('.list-row').should('contain', 'this is first todo'); | |||
cy.get('.remove-filter.btn').click(); | |||
}); | |||
}); |
@@ -16,7 +16,7 @@ import frappe, json, copy, re | |||
from frappe.model import optional_fields | |||
from frappe.client import check_parent_permission | |||
from frappe.model.utils.user_settings import get_user_settings, update_user_settings | |||
from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr | |||
from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, nowdate | |||
class DatabaseQuery(object): | |||
def __init__(self, doctype, user=None): | |||
@@ -402,6 +402,35 @@ class DatabaseQuery(object): | |||
if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): | |||
can_be_null = False | |||
if f.operator.lower() in ('previous', 'next'): | |||
if f.operator.lower() == "previous": | |||
if f.value == "1 week": | |||
date_range = [add_to_date(nowdate(), days=-7), nowdate()] | |||
elif f.value == "1 month": | |||
date_range = [add_to_date(nowdate(), months=-1), nowdate()] | |||
elif f.value == "3 months": | |||
date_range = [add_to_date(nowdate(), months=-3), nowdate()] | |||
elif f.value == "6 months": | |||
date_range = [add_to_date(nowdate(), months=-6), nowdate()] | |||
elif f.value == "1 year": | |||
date_range = [add_to_date(nowdate(), years=-1), nowdate()] | |||
elif f.operator.lower() == "next": | |||
if f.value == "1 week": | |||
date_range = [nowdate(), add_to_date(nowdate(), days=7)] | |||
elif f.value == "1 month": | |||
date_range = [nowdate(), add_to_date(nowdate(), months=1)] | |||
elif f.value == "3 months": | |||
date_range = [nowdate(), add_to_date(nowdate(), months=3)] | |||
elif f.value == "6 months": | |||
date_range = [nowdate(), add_to_date(nowdate(), months=6)] | |||
elif f.value == "1 year": | |||
date_range = [nowdate(), add_to_date(nowdate(), years=1)] | |||
if df.fieldtype=="Datetime": | |||
date_range = [frappe.db.format_datetime(date_range[0]), frappe.db.format_datetime(date_range[1])] | |||
f.operator = "Between" | |||
f.value = date_range | |||
fallback = "'0001-01-01 00:00:00'" | |||
if f.operator in ('>', '<') and (f.fieldname in ('creation', 'modified')): | |||
value = cstr(f.value) | |||
fallback = "NULL" | |||
@@ -18,6 +18,8 @@ | |||
<option value="not descendants of">{%= __("Not Descendants Of") %}</option> | |||
<option value="ancestors of">{%= __("Ancestors Of") %}</option> | |||
<option value="not ancestors of">{%= __("Not Ancestors Of") %}</option> | |||
<option value="Previous">{%=__("Previous")%}</option> | |||
<option value="Next">{%=__("Next")%}</option> | |||
</select> | |||
</div> | |||
<div class="col-sm-6 col-xs-12"> | |||
@@ -19,16 +19,18 @@ frappe.ui.Filter = class { | |||
["<=", "<="], | |||
["Between", __("Between")], | |||
["descendants of", __("Descendants Of")], | |||
["ancestors of", __("Ancestors Of")] | |||
["ancestors of", __("Ancestors Of")], | |||
["Previous", __("Previous")], | |||
["Next", __("Next")] | |||
]; | |||
this.invalid_condition_map = { | |||
Date: ['like', 'not like'], | |||
Datetime: ['like', 'not like'], | |||
Data: ['Between'], | |||
Select: ["Between", "<=", ">=", "<", ">"], | |||
Link: ["Between"], | |||
Currency: ["Between"], | |||
Color: ["Between"] | |||
Data: ['Between', 'Previous', 'Next'], | |||
Select: ['like', 'not like'], | |||
Link: ["Between", 'Previous', 'Next'], | |||
Currency: ["Between", 'Previous', 'Next'], | |||
Color: ["Between", 'Previous', 'Next'] | |||
}; | |||
this.make(); | |||
this.make_select(); | |||
@@ -182,6 +184,32 @@ frappe.ui.Filter = class { | |||
this.fieldselect.selected_doctype = doctype; | |||
this.fieldselect.selected_fieldname = fieldname; | |||
if(["Previous", "Next"].includes(condition) && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { | |||
df.fieldtype = 'Select'; | |||
df.options = [ | |||
{ | |||
label: __('1 week'), | |||
value: '1 week' | |||
}, | |||
{ | |||
label: __('1 month'), | |||
value: '1 month' | |||
}, | |||
{ | |||
label: __('3 months'), | |||
value: '3 months' | |||
}, | |||
{ | |||
label: __('6 months'), | |||
value: '6 months' | |||
}, | |||
{ | |||
label: __('1 year'), | |||
value: '1 year' | |||
} | |||
]; | |||
} | |||
this.make_field(df, cur.fieldtype); | |||
} | |||
@@ -3,9 +3,10 @@ | |||
from __future__ import unicode_literals | |||
import unittest | |||
import frappe | |||
from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url | |||
from frappe.utils import ceil, floor | |||
from frappe.utils import ceil, floor, now, add_to_date | |||
class TestFilters(unittest.TestCase): | |||
def test_simple_dict(self): | |||
@@ -122,3 +123,26 @@ class TestHTMLUtils(unittest.TestCase): | |||
clean = clean_email_html(sample) | |||
self.assertTrue('<h1>Hello</h1>' in clean) | |||
self.assertTrue('<a href="http://test.com">text</a>' in clean) | |||
@frappe.whitelist() | |||
def create_todo_records(): | |||
frappe.get_doc({ | |||
"doctype": "ToDo", | |||
"date": add_to_date(now(), days=3), | |||
"description": "this is first todo" | |||
}).insert() | |||
frappe.get_doc({ | |||
"doctype": "ToDo", | |||
"date": add_to_date(now(), days=-3), | |||
"description": "this is second todo" | |||
}).insert() | |||
frappe.get_doc({ | |||
"doctype": "ToDo", | |||
"date": add_to_date(now(), months=2), | |||
"description": "this is third todo" | |||
}).insert() | |||
frappe.get_doc({ | |||
"doctype": "ToDo", | |||
"date": add_to_date(now(), months=-2), | |||
"description": "this is fourth todo" | |||
}).insert() |
@@ -855,7 +855,7 @@ def get_filter(doctype, f): | |||
f.operator = "=" | |||
valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", | |||
"between", "descendants of", "ancestors of", "not descendants of", "not ancestors of") | |||
"between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "previous", "next") | |||
if f.operator.lower() not in valid_operators: | |||
frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators))) | |||
@@ -1004,4 +1004,4 @@ def get_source_value(source, key): | |||
def is_subset(list_a, list_b): | |||
'''Returns whether list_a is a subset of list_b''' | |||
return len(list(set(list_a) & set(list_b))) == len(list_a) | |||
return len(list(set(list_a) & set(list_b))) == len(list_a) |