1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-15 09:34:13 +00:00

Resolve "Add new filter types 'is after today' and 'is before today'"

This commit is contained in:
Davide Silvestri 2022-09-06 15:42:16 +00:00
parent 70467f511d
commit 69ac205fbe
8 changed files with 347 additions and 101 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database/view
changelog.md
web-frontend
locales
modules/database
test/unit/database

View file

@ -228,7 +228,9 @@ class DatabaseConfig(AppConfig):
BooleanViewFilterType,
ContainsNotViewFilterType,
ContainsViewFilterType,
DateAfterTodayViewFilterType,
DateAfterViewFilterType,
DateBeforeTodayViewFilterType,
DateBeforeViewFilterType,
DateEqualsCurrentMonthViewFilterType,
DateEqualsCurrentWeekViewFilterType,
@ -273,6 +275,8 @@ class DatabaseConfig(AppConfig):
view_filter_type_registry.register(DateAfterViewFilterType())
view_filter_type_registry.register(DateNotEqualViewFilterType())
view_filter_type_registry.register(DateEqualsTodayViewFilterType())
view_filter_type_registry.register(DateBeforeTodayViewFilterType())
view_filter_type_registry.register(DateAfterTodayViewFilterType())
view_filter_type_registry.register(DateEqualsDaysAgoViewFilterType())
view_filter_type_registry.register(DateEqualsMonthsAgoViewFilterType())
view_filter_type_registry.register(DateEqualsYearsAgoViewFilterType())

View file

@ -1,6 +1,7 @@
from datetime import datetime, timedelta
from datetime import datetime, time, timedelta
from decimal import Decimal
from math import ceil, floor
from typing import Dict
from django.contrib.postgres.aggregates.general import ArrayAgg
from django.db.models import DateTimeField, IntegerField, Q
@ -453,12 +454,33 @@ class DateAfterViewFilterType(BaseDateFieldLookupFilterType):
query_field_lookup = "__gt"
class DateEqualsTodayViewFilterType(ViewFilterType):
class DateCompareTodayViewFilterType(ViewFilterType):
"""
The today filter checks if the field value matches with today's date.
The today filter checks if the field value matches the defined operator with
today's date.
"""
type = "date_equals_today"
@property
def type(self) -> str:
"""
Returns the type of the filter (e.g. 'date_equals_today' for a
view_filter that filters for today).
"""
raise NotImplementedError
def make_query_dict(self, field_name: str, now: datetime) -> Dict:
"""
Creates a query dict for the specific view_filter, given the field name
based on today's date.
:param field_name: The field name to use in the query dict.
:param now: The current date.
:return: The query dict.
"""
raise NotImplementedError
compatible_field_types = [
DateFieldType.type,
LastModifiedFieldType.type,
@ -467,7 +489,6 @@ class DateEqualsTodayViewFilterType(ViewFilterType):
BaserowFormulaDateType.type,
),
]
query_for = ["year", "month", "day"]
def get_filter(self, field_name, value, model_field, field):
timezone_string = value if value in all_timezones else "UTC"
@ -475,28 +496,53 @@ class DateEqualsTodayViewFilterType(ViewFilterType):
field_has_timezone = hasattr(field, "timezone")
now = datetime.utcnow().astimezone(timezone_object)
def make_query_dict(query_field_name):
query_dict = dict()
if "year" in self.query_for:
query_dict[f"{query_field_name}__year"] = now.year
if "month" in self.query_for:
query_dict[f"{query_field_name}__month"] = now.month
if "week" in self.query_for:
week = now.isocalendar()[1]
query_dict[f"{query_field_name}__week"] = week
if "day" in self.query_for:
query_dict[f"{query_field_name}__day"] = now.day
return query_dict
if field_has_timezone:
tmp_field_name = f"{field_name}_timezone_{timezone_string}"
return AnnotatedQ(
annotation={f"{tmp_field_name}": Timezone(field_name, timezone_string)},
q=make_query_dict(tmp_field_name),
q=self.make_query_dict(tmp_field_name, now),
)
else:
return Q(**make_query_dict(field_name))
return Q(**self.make_query_dict(field_name, now))
class DateEqualsTodayViewFilterType(DateCompareTodayViewFilterType):
"""
The today filter checks if the field value matches with today's date.
"""
type = "date_equals_today"
def make_query_dict(self, field_name, now):
return {
f"{field_name}__day": now.day,
f"{field_name}__month": now.month,
f"{field_name}__year": now.year,
}
class DateBeforeTodayViewFilterType(DateCompareTodayViewFilterType):
"""
The before today filter checks if the field value is before today's date.
"""
type = "date_before_today"
def make_query_dict(self, field_name, now):
min_today = datetime.combine(now, time.min)
return {f"{field_name}__lt": min_today}
class DateAfterTodayViewFilterType(DateCompareTodayViewFilterType):
"""
The after today filter checks if the field value is after today's date.
"""
type = "date_after_today"
def make_query_dict(self, field_name, now):
max_today = datetime.combine(now, time.max)
return {f"{field_name}__gt": max_today}
class DateEqualsXAgoViewFilterType(ViewFilterType):
@ -623,34 +669,49 @@ class DateEqualsYearsAgoViewFilterType(DateEqualsXAgoViewFilterType):
return now + relativedelta(years=-x_units_ago)
class DateEqualsCurrentWeekViewFilterType(DateEqualsTodayViewFilterType):
class DateEqualsCurrentWeekViewFilterType(DateCompareTodayViewFilterType):
"""
The current week filter works as a subset of today filter and checks if the
field value falls into current week.
"""
type = "date_equals_week"
query_for = ["year", "week"]
def make_query_dict(self, field_name, now):
week_of_year = now.isocalendar()[1]
return {
f"{field_name}__week": week_of_year,
f"{field_name}__year": now.year,
}
class DateEqualsCurrentMonthViewFilterType(DateEqualsTodayViewFilterType):
class DateEqualsCurrentMonthViewFilterType(DateCompareTodayViewFilterType):
"""
The current month filter works as a subset of today filter and checks if the
field value falls into current month.
"""
type = "date_equals_month"
query_for = ["year", "month"]
def make_query_dict(self, field_name, now):
return {
f"{field_name}__month": now.month,
f"{field_name}__year": now.year,
}
class DateEqualsCurrentYearViewFilterType(DateEqualsTodayViewFilterType):
class DateEqualsCurrentYearViewFilterType(DateCompareTodayViewFilterType):
"""
The current month filter works as a subset of today filter and checks if the
field value falls into current year.
"""
type = "date_equals_year"
query_for = ["year"]
def make_query_dict(self, field_name, now):
return {
f"{field_name}__year": now.year,
}
class DateNotEqualViewFilterType(NotViewFilterTypeMixin, DateEqualViewFilterType):

View file

@ -2383,7 +2383,7 @@ def test_last_modified_year_filter_type(data_fixture):
@pytest.mark.django_db
def test_date_day_week_month_year_filter_type(data_fixture):
def test_date_equals_day_week_month_year_filter_type(data_fixture):
user = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
@ -2496,6 +2496,75 @@ def test_date_day_week_month_year_filter_type(data_fixture):
assert row_3.id in ids
@pytest.mark.django_db
def test_date_before_after_today_filter_type(data_fixture):
user = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
date_field = data_fixture.create_date_field(table=table)
handler = ViewHandler()
model = table.get_model()
row_1 = model.objects.create(
**{
f"field_{date_field.id}": date(2021, 2, 1),
}
)
row_2 = model.objects.create(
**{
f"field_{date_field.id}": date(2021, 1, 1),
}
)
row_3 = model.objects.create(
**{
f"field_{date_field.id}": date(2021, 1, 2),
}
)
row_4 = model.objects.create(
**{
f"field_{date_field.id}": date(2021, 1, 4),
}
)
row_5 = model.objects.create(
**{
f"field_{date_field.id}": None,
}
)
row_6 = model.objects.create(
**{
f"field_{date_field.id}": date(2010, 1, 1),
}
)
row_7 = model.objects.create(
**{
f"field_{date_field.id}": date(2020, 12, 31),
}
)
view_filter = data_fixture.create_view_filter(
view=grid_view, field=date_field, type="date_before_today", value="UTC"
)
with freeze_time("2021-01-02 01:01"):
ids = [
r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()
]
assert len(ids) == 3
assert ids == [row_2.id, row_6.id, row_7.id]
view_filter.type = "date_after_today"
view_filter.save()
ids = [
r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()
]
assert len(ids) == 2
assert ids == [row_1.id, row_4.id]
@pytest.mark.django_db
def test_date_not_equal_filter_type(data_fixture):
user = data_fixture.create_user()

View file

@ -43,6 +43,7 @@ For example:
* Resolve an issue with uploading a file via a URL when it contains a querystring. [#1034](https://gitlab.com/bramw/baserow/-/issues/1034)
* Resolve an invalid URL in the "Backend URL mis-configuration detected" error message. [#967](https://gitlab.com/bramw/baserow/-/merge_requests/967)
* Fixed broken call grouping when getting linked row names from server.
* Add new filter types 'is after today' and 'is before today'. [#1093](https://gitlab.com/bramw/baserow/-/issues/1093)
### Refactors
* Fix view and fields getting out of date on realtime updates. [#1112](https://gitlab.com/bramw/baserow/-/issues/1112)

View file

@ -135,6 +135,8 @@
"isAfterDate": "is after date",
"isNotDate": "is not date",
"isToday": "is today",
"beforeToday": "is before today",
"afterToday": "is after today",
"isDaysAgo": "is days ago",
"isMonthsAgo": "is months ago",
"isYearsAgo": "is years ago",
@ -355,4 +357,4 @@
"invalidUrlEnvVarTitle": "Invalid {name}",
"invalidUrlEnvVarDescription": "The {name} environment variable has been set to an invalid value. Your site admin must change {name} to a valid URL starting with http:// or https:// and then restart Baserow to fix this error."
}
}
}

View file

@ -41,6 +41,8 @@ import {
EmptyViewFilterType,
NotEmptyViewFilterType,
DateEqualsTodayViewFilterType,
DateBeforeTodayViewFilterType,
DateAfterTodayViewFilterType,
DateEqualsDaysAgoViewFilterType,
DateEqualsMonthsAgoViewFilterType,
DateEqualsYearsAgoViewFilterType,
@ -233,6 +235,14 @@ export default (context) => {
'viewFilter',
new DateEqualsTodayViewFilterType(context)
)
app.$registry.register(
'viewFilter',
new DateBeforeTodayViewFilterType(context)
)
app.$registry.register(
'viewFilter',
new DateAfterTodayViewFilterType(context)
)
app.$registry.register(
'viewFilter',
new DateEqualsDaysAgoViewFilterType(context)

View file

@ -588,14 +588,20 @@ export class DateNotEqualViewFilterType extends ViewFilterType {
}
}
export class DateEqualsTodayViewFilterType extends ViewFilterType {
/**
* Base class for compare dates with today.
*/
export class DateCompareTodayViewFilterType extends ViewFilterType {
static getType() {
return 'date_equals_today'
throw new Error('Not implemented')
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.isToday')
throw new Error('Not implemented')
}
getCompareFunction() {
throw new Error('Not implemented')
}
getInputComponent() {
@ -630,21 +636,118 @@ export class DateEqualsTodayViewFilterType extends ViewFilterType {
matches(rowValue, filterValue, field) {
if (rowValue === null) {
rowValue = ''
return false
}
const sliceLength = this.getSliceLength()
const format = 'YYYY-MM-DD'.slice(0, sliceLength)
const today = moment().tz(filterValue).format(format)
if (field.timezone) {
rowValue = moment.utc(rowValue).tz(field.timezone).format(format)
rowValue = moment.utc(rowValue).tz(field.timezone)
} else {
rowValue = rowValue.toString().toLowerCase().trim()
rowValue = rowValue.slice(0, sliceLength)
rowValue = moment.utc(rowValue.slice(0, this.getSliceLength()))
}
return rowValue === today
const today = moment().tz(filterValue)
return this.getCompareFunction(rowValue, today)
}
}
export class DateEqualsTodayViewFilterType extends DateCompareTodayViewFilterType {
static getType() {
return 'date_equals_today'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.isToday')
}
getCompareFunction(value, today) {
const minTime = today.clone().startOf('day')
const maxtime = today.clone().endOf('day')
return value.isBetween(minTime, maxtime, null, '[]')
}
}
export class DateBeforeTodayViewFilterType extends DateCompareTodayViewFilterType {
static getType() {
return 'date_before_today'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.beforeToday')
}
getCompareFunction(value, today) {
const minTime = today.clone().startOf('day')
return value.isBefore(minTime)
}
}
export class DateAfterTodayViewFilterType extends DateCompareTodayViewFilterType {
static getType() {
return 'date_after_today'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.afterToday')
}
getCompareFunction(value, today) {
const maxtime = today.clone().endOf('day')
return value.isAfter(maxtime)
}
}
export class DateEqualsCurrentWeekViewFilterType extends DateCompareTodayViewFilterType {
static getType() {
return 'date_equals_week'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.inThisWeek')
}
getCompareFunction(value, today) {
const firstDay = today.clone().startOf('isoWeek')
const lastDay = today.clone().endOf('isoWeek')
return value.isBetween(firstDay, lastDay, null, '[]')
}
}
export class DateEqualsCurrentMonthViewFilterType extends DateCompareTodayViewFilterType {
static getType() {
return 'date_equals_month'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.inThisMonth')
}
getCompareFunction(value, today) {
const firstDay = today.clone().startOf('month')
const lastDay = today.clone().endOf('month')
return value.isBetween(firstDay, lastDay, null, '[]')
}
}
export class DateEqualsCurrentYearViewFilterType extends DateEqualsTodayViewFilterType {
static getType() {
return 'date_equals_year'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.inThisYear')
}
getCompareFunction(value, today) {
const firstDay = today.clone().startOf('year')
const lastDay = today.clone().endOf('year')
return value.isBetween(firstDay, lastDay, null, '[]')
}
}
@ -802,66 +905,6 @@ export class DateEqualsYearsAgoViewFilterType extends DateEqualsXAgoViewFilterTy
}
}
export class DateEqualsCurrentWeekViewFilterType extends DateEqualsTodayViewFilterType {
static getType() {
return 'date_equals_week'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.inThisWeek')
}
matches(rowValue, filterValue, field) {
if (rowValue === null) {
rowValue = ''
}
// If the field.timezone property is set (last modified and created on),
// the original value is in UTC0 and must be converted to the timezone of the filter.
// If the field.timezone property is not set (date+time), the original value is already
// in the right timezone because it's naive, so it doesn't have to be converted,
// but it must be marked as the same timezone.
rowValue = moment.utc(rowValue).tz(filterValue, !field.timezone)
const today = moment().tz(filterValue)
const firstDay = today.clone().startOf('isoWeek')
const lastDay = today.clone().endOf('isoWeek')
return rowValue.isBetween(firstDay, lastDay, null, '[]')
}
}
export class DateEqualsCurrentMonthViewFilterType extends DateEqualsTodayViewFilterType {
static getType() {
return 'date_equals_month'
}
getSliceLength() {
return 7
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.inThisMonth')
}
}
export class DateEqualsCurrentYearViewFilterType extends DateEqualsTodayViewFilterType {
static getType() {
return 'date_equals_year'
}
getSliceLength() {
return 4
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.inThisYear')
}
}
export class DateEqualsDayOfMonthViewFilterType extends ViewFilterType {
static getType() {
return 'date_equals_day_of_month'

View file

@ -2,7 +2,9 @@ import { TestApp } from '@baserow/test/helpers/testApp'
import moment from '@baserow/modules/core/moment'
import {
DateBeforeViewFilterType,
DateBeforeTodayViewFilterType,
DateAfterViewFilterType,
DateAfterTodayViewFilterType,
DateEqualViewFilterType,
DateNotEqualViewFilterType,
DateEqualsTodayViewFilterType,
@ -302,6 +304,42 @@ const dateToday = [
},
]
const dateBeforeToday = [
{
rowValue: moment().utc().format(),
filterValue: 'Europe/Berlin',
expected: false,
},
{
rowValue: moment().subtract(1, 'day').utc().format(),
filterValue: 'Europe/Berlin',
expected: true,
},
{
rowValue: moment().add(1, 'day').utc().format(),
filterValue: 'Europe/Berlin',
expected: false,
},
]
const dateAfterToday = [
{
rowValue: moment().utc().format(),
filterValue: 'Europe/Berlin',
expected: false,
},
{
rowValue: moment().subtract(1, 'day').utc().format(),
filterValue: 'Europe/Berlin',
expected: false,
},
{
rowValue: moment().add(1, 'day').utc().format(),
filterValue: 'Europe/Berlin',
expected: true,
},
]
const dateInThisWeek = [
{
rowValue: '2022-05-29',
@ -798,6 +836,24 @@ describe('All Tests', () => {
expect(result).toBe(values.expected)
})
test.each(dateBeforeToday)('DateBeforeToday', (values) => {
const result = new DateBeforeTodayViewFilterType({ app: testApp }).matches(
values.rowValue,
values.filterValue,
{}
)
expect(result).toBe(values.expected)
})
test.each(dateAfterToday)('DateAfterToday', (values) => {
const result = new DateAfterTodayViewFilterType({ app: testApp }).matches(
values.rowValue,
values.filterValue,
{}
)
expect(result).toBe(values.expected)
})
test.each(dateDaysAgo)('DateDaysAgo', (values) => {
const result = new DateEqualsDaysAgoViewFilterType({
app: testApp,