From f60a110702da1677c76b1c489fdd06a5825c0453 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Wed, 15 Apr 2026 18:23:00 +0200 Subject: [PATCH 01/33] update dashboard and Tile, remove helper component --- .../js/project/components/areas/Dashboard.js | 130 +++++++++++++----- .../js/project/components/helper/Tile.js | 39 ++++-- .../js/project/components/helper/TileGrid.js | 32 ----- .../js/project/components/helper/index.js | 5 +- 4 files changed, 127 insertions(+), 79 deletions(-) delete mode 100644 rdmo/projects/assets/js/project/components/helper/TileGrid.js diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index 48577001c2..dec9f33227 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -1,45 +1,113 @@ -import React, { useState } from 'react' +import React from 'react' +import { useDispatch } from 'react-redux' -import Tooltip from 'rdmo/core/assets/js/_bs53/components/Tooltip' - -import { TileGrid } from '../helper' +import { navigateDashboard } from '../../actions/projectActions' +import { Tile } from '../helper' const Dashboard = () => { - const [tileSize, setTileSize] = useState('normal') - - const toggleSize = () => { - setTileSize((prevSize) => { - if (prevSize === 'compact') return 'normal' - if (prevSize === 'normal') return 'fullWidth' - return 'compact' - }) - } - - const tiles = [ - { title: 'Tile 1', content:

Content 1

}, - { title: 'Tile 2', content:

Content 2

}, - { title: 'Tile 3', content:

Content 3

}, - { title: 'Tile 4', content:

Content 4

}, - { title: 'Tile 5', content:

Content 5

}, - { title: 'Tile 6', content:

Content 6

}, + const dispatch = useDispatch() + const tasks = [ + { + title: 'Recommendation: Consider reuse scenarios', + progress: '0 / 2', + date: '14 August 2024', + isDone: false, + }, + { + title: 'Consider Open Access Policy', + progress: '1 / 3', + date: '15 May 2024', + isDone: false, + }, + { + title: 'Consider Open Access Policy', + progress: '3 / 3', + date: '15 May 2024', + isDone: true, + }, ] return (

{gettext('Dashboard')}

-
- +

{gettext('Create your data management plan')}

+
+ dispatch(navigateDashboard({ area: 'interview'}))} + > +

{gettext('Fill out the selected questionnaire as completely as possible.')}

+
+ dispatch(navigateDashboard({ area: 'documents'}))} + > +

{gettext('Export your data management plan in various formats.')}

+
+
+ +

{gettext('Tasks')}

+
+ { + tasks.map((task, index) => ( + +
+
+ { + task.isDone ? ( + + ) : ( + + ) + } +
- +
+
+ {task.title} +
+ +
+
+ + {task.progress} +
+ +
+ + {task.date} +
+
+
+
+
+ )) + } +
-
- TITLE} placement="top"> - TOOLTIP - -
+

{gettext('More actions')}

+
+ dispatch(navigateDashboard({ area: 'memberships'}))} + size="compact" + > +

{gettext('Invite additional people to collaborate on creating your data management plan.')}

+
+ dispatch(navigateDashboard({ area: 'snapshots'}))} + size="compact" + > +

{gettext('Save a snapshot to view or restore later.')}

+
) diff --git a/rdmo/projects/assets/js/project/components/helper/Tile.js b/rdmo/projects/assets/js/project/components/helper/Tile.js index e4a53c2b0d..ed593d32f3 100644 --- a/rdmo/projects/assets/js/project/components/helper/Tile.js +++ b/rdmo/projects/assets/js/project/components/helper/Tile.js @@ -1,26 +1,34 @@ import React from 'react' import PropTypes from 'prop-types' -const Tile = ({ title, children, className = '', size = 'normal', style = 'normal', onClick }) => { +const Tile = ({ title, label, buttonLabel, children, className = '', size = 'normal', onClick }) => { const sizeClasses = { compact: 'col-12 col-md-4', // 3 tiles per row normal: 'col-12 col-md-6', // 2 tiles per row fullWidth: 'col-12', // 1 tile per row } - const tileStyleClass = style === 'warning' ? 'tile-warning' : '' - return ( -
- {title &&

{title}

} -
-
- {children} -
+
+
+ {label &&
{label}
} + {title &&

{title}

} +
{children}
+ + { + onClick && buttonLabel && ( +
+ +
+ ) + }
) @@ -28,10 +36,11 @@ const Tile = ({ title, children, className = '', size = 'normal', style = 'norma Tile.propTypes = { title: PropTypes.string, - children: PropTypes.node.isRequired, + buttonLabel: PropTypes.node, + children: PropTypes.node, className: PropTypes.string, + label: PropTypes.node, size: PropTypes.oneOf(['compact', 'normal', 'fullWidth']), - style: PropTypes.oneOf(['normal', 'warning']), onClick: PropTypes.func, } diff --git a/rdmo/projects/assets/js/project/components/helper/TileGrid.js b/rdmo/projects/assets/js/project/components/helper/TileGrid.js deleted file mode 100644 index 61a1974e19..0000000000 --- a/rdmo/projects/assets/js/project/components/helper/TileGrid.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import Tile from './Tile' - -const TileGrid = ({ tiles, size = 'normal' }) => { - return ( -
-
- { - tiles.map((tile, index) => ( - - {tile.content} - - )) - } -
-
- ) -} - -TileGrid.propTypes = { - tiles: PropTypes.arrayOf( - PropTypes.shape({ - title: PropTypes.string.isRequired, - content: PropTypes.node.isRequired, - }) - ).isRequired, - size: PropTypes.oneOf(['compact', 'normal', 'fullWidth']), -} - -export default TileGrid diff --git a/rdmo/projects/assets/js/project/components/helper/index.js b/rdmo/projects/assets/js/project/components/helper/index.js index fafb2dc329..84ad480e3a 100644 --- a/rdmo/projects/assets/js/project/components/helper/index.js +++ b/rdmo/projects/assets/js/project/components/helper/index.js @@ -1,2 +1,5 @@ +export { default as ExportsDropdown } from './ExportsDropdown' +export { default as SnapshotsDropdown } from './SnapshotsDropdown' export { default as Tile } from './Tile' -export { default as TileGrid } from './TileGrid' +export { default as View } from './View' +export { default as ViewTile } from './ViewTile' From 7d766ee9dbce5330e8dad5ffba4fc4c6b0f9ba94 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Wed, 15 Apr 2026 18:39:04 +0200 Subject: [PATCH 02/33] change tasks handling --- .../js/project/components/areas/Dashboard.js | 82 +++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index dec9f33227..41ac7137fb 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { useDispatch } from 'react-redux' import { navigateDashboard } from '../../actions/projectActions' @@ -6,26 +6,58 @@ import { Tile } from '../helper' const Dashboard = () => { const dispatch = useDispatch() - const tasks = [ + // const tasks = [ + // { + // title: 'Recommendation: Consider reuse scenarios', + // progress: '0 / 2', + // date: '14 August 2024', + // isDone: false, + // }, + // { + // title: 'Consider Open Access Policy', + // progress: '1 / 3', + // date: '15 May 2024', + // isDone: false, + // }, + // { + // title: 'Consider Open Access Policy', + // progress: '3 / 3', + // date: '15 May 2024', + // isDone: true, + // }, + // ] + + const [tasks, setTasks] = useState([ { + id: 1, title: 'Recommendation: Consider reuse scenarios', progress: '0 / 2', date: '14 August 2024', isDone: false, }, { + id: 2, title: 'Consider Open Access Policy', progress: '1 / 3', date: '15 May 2024', isDone: false, }, { + id: 3, title: 'Consider Open Access Policy', progress: '3 / 3', date: '15 May 2024', isDone: true, }, - ] + ]) + + const toggleTaskDone = (taskId) => { + setTasks((prevTasks) => + prevTasks.map((task) => + task.id === taskId ? { ...task, isDone: !task.isDone } : task + ) + ) + } return (
@@ -36,7 +68,7 @@ const Dashboard = () => { dispatch(navigateDashboard({ area: 'interview'}))} >

{gettext('Fill out the selected questionnaire as completely as possible.')}

@@ -51,7 +83,7 @@ const Dashboard = () => {
-

{gettext('Tasks')}

+ {/*

{gettext('Tasks')}

{ tasks.map((task, index) => ( @@ -67,6 +99,46 @@ const Dashboard = () => { }
+
+
+ {task.title} +
+ +
+
+ + {task.progress} +
+ +
+ + {task.date} +
+
+
+
+ + )) + } +
*/} + +

{gettext('Tasks')}

+
+ { + tasks.map((task) => ( + +
+
+ +
+
{task.title} From 625768589cf4e1a15cfb311e751de175c4828366 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Tue, 21 Apr 2026 14:54:29 +0200 Subject: [PATCH 03/33] add card click and invisible tasks --- .../js/project/components/areas/Dashboard.js | 100 ++++++++++++------ .../js/project/components/helper/Tile.js | 15 ++- 2 files changed, 78 insertions(+), 37 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index 41ac7137fb..5b50feb5d9 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -6,26 +6,9 @@ import { Tile } from '../helper' const Dashboard = () => { const dispatch = useDispatch() - // const tasks = [ - // { - // title: 'Recommendation: Consider reuse scenarios', - // progress: '0 / 2', - // date: '14 August 2024', - // isDone: false, - // }, - // { - // title: 'Consider Open Access Policy', - // progress: '1 / 3', - // date: '15 May 2024', - // isDone: false, - // }, - // { - // title: 'Consider Open Access Policy', - // progress: '3 / 3', - // date: '15 May 2024', - // isDone: true, - // }, - // ] + + const [selectedTask, setSelectedTask] = useState(null) + const [showClosedTasks, setShowClosedTasks] = useState(false) const [tasks, setTasks] = useState([ { @@ -51,6 +34,10 @@ const Dashboard = () => { }, ]) + const visibleTasks = tasks + .filter((task) => showClosedTasks || !task.isDone) + .sort((a, b) => Number(a.isDone) - Number(b.isDone)) + const toggleTaskDone = (taskId) => { setTasks((prevTasks) => prevTasks.map((task) => @@ -83,20 +70,21 @@ const Dashboard = () => {
- {/*

{gettext('Tasks')}

+ {/*

{gettext('Tasks')}

{ - tasks.map((task, index) => ( - + tasks.map((task) => ( +
- { - task.isDone ? ( - - ) : ( - - ) - } +
@@ -121,18 +109,40 @@ const Dashboard = () => { )) }
*/} -

{gettext('Tasks')}

+ +
+ setShowClosedTasks((prev) => !prev)} + /> + +
+
{ - tasks.map((task) => ( - + visibleTasks.map((task) => ( + setSelectedTask(task)} + >
+ { + selectedTask && ( +
+
+
+
+
{selectedTask.title}
+
+
+

{gettext('Task popup placeholder')}

+

{selectedTask.date}

+
+
+
+
+ ) + }
) } diff --git a/rdmo/projects/assets/js/project/components/helper/Tile.js b/rdmo/projects/assets/js/project/components/helper/Tile.js index ed593d32f3..ccb0fe1f1c 100644 --- a/rdmo/projects/assets/js/project/components/helper/Tile.js +++ b/rdmo/projects/assets/js/project/components/helper/Tile.js @@ -1,7 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' -const Tile = ({ title, label, buttonLabel, children, className = '', size = 'normal', onClick }) => { +const Tile = ({ title, label, buttonLabel, children, className = '', size = 'normal', onClick, onCardClick }) => { const sizeClasses = { compact: 'col-12 col-md-4', // 3 tiles per row normal: 'col-12 col-md-6', // 2 tiles per row @@ -10,7 +10,9 @@ const Tile = ({ title, label, buttonLabel, children, className = '', size = 'nor return (
+ className={`card card-tile mb-4 rounded-3 ${sizeClasses[size]} ${className}`} + onClick={onCardClick} + style={onCardClick ? { cursor: 'pointer' } : undefined} >
{label &&
{label}
} {title &&

{title}

} @@ -22,7 +24,13 @@ const Tile = ({ title, label, buttonLabel, children, className = '', size = 'nor @@ -42,6 +50,7 @@ Tile.propTypes = { label: PropTypes.node, size: PropTypes.oneOf(['compact', 'normal', 'fullWidth']), onClick: PropTypes.func, + onCardClick: PropTypes.func } export default Tile From be66b7de2671e0416679d9579151d05ca81df4ac Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Wed, 6 May 2026 15:53:11 +0200 Subject: [PATCH 04/33] add properties to issue serializer --- rdmo/projects/models/issue.py | 11 +++++++---- rdmo/projects/serializers/v1/__init__.py | 12 ++++++++++-- rdmo/projects/viewsets.py | 4 ++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/rdmo/projects/models/issue.py b/rdmo/projects/models/issue.py index 3e4f574191..218ad0fc44 100644 --- a/rdmo/projects/models/issue.py +++ b/rdmo/projects/models/issue.py @@ -49,10 +49,13 @@ def __str__(self): def get_absolute_url(self): return reverse('project', kwargs={'pk': self.project.pk}) - def resolve(self, values): - for condition in self.task.conditions.all(): - if condition.resolve(values): - return True + @property + def resolve(self): + values = getattr(self.project, '_prefetched_current_values', None) + if values is None: + values = self.project.values.filter(snapshot=None) + + return any(condition.resolve(values) for condition in self.task.conditions.all()) @property def dates(self): diff --git a/rdmo/projects/serializers/v1/__init__.py b/rdmo/projects/serializers/v1/__init__.py index 9bc8c2ba3f..e1a3a75e37 100644 --- a/rdmo/projects/serializers/v1/__init__.py +++ b/rdmo/projects/serializers/v1/__init__.py @@ -544,6 +544,8 @@ class ProjectIssueSerializer(serializers.ModelSerializer): task = ProjectIssueTaskSerializer(read_only=True) resources = ProjectIssueResourceSerializer(read_only=True, many=True) + resolve = serializers.BooleanField(read_only=True) + dates = serializers.ReadOnlyField() class Meta: model = Issue @@ -551,7 +553,9 @@ class Meta: 'id', 'task', 'status', - 'resources' + 'resolve', + 'resources', + 'dates' ) @@ -744,6 +748,8 @@ class IssueSerializer(serializers.ModelSerializer): project = serializers.PrimaryKeyRelatedField(read_only=True) task = IssueTaskSerializer(read_only=True) resources = IssueResourceSerializer(read_only=True, many=True) + resolve = serializers.BooleanField(read_only=True) + dates = serializers.ReadOnlyField() class Meta: model = Issue @@ -752,7 +758,9 @@ class Meta: 'project', 'task', 'status', - 'resources' + 'resolve', + 'resources', + 'dates' ) diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py index 200f41b3c4..d9e085c793 100644 --- a/rdmo/projects/viewsets.py +++ b/rdmo/projects/viewsets.py @@ -834,7 +834,7 @@ class ProjectIssueViewSet(ProjectNestedViewSetMixin, ListModelMixin, RetrieveMod ) def get_queryset(self): - return Issue.objects.filter(project=self.project).prefetch_related('resources') + return Issue.objects.filter(project=self.project).prefetch_related('resources').select_related('task') class ProjectSnapshotViewSet(ProjectNestedViewSetMixin, ModelViewSet): @@ -1162,7 +1162,7 @@ class IssueViewSet(ReadOnlyModelViewSet): ) def get_queryset(self): - return Issue.objects.filter_user(self.request.user).prefetch_related('resources') + return Issue.objects.filter_user(self.request.user).prefetch_related('resources').select_related('task') class SnapshotViewSet(ReadOnlyModelViewSet): From 72b5b553cdb0b4e81295c2b112b32392449532cf Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Wed, 6 May 2026 16:25:12 +0200 Subject: [PATCH 05/33] extend tests --- rdmo/projects/tests/test_viewset_issue.py | 17 ++++++++++++----- .../tests/test_viewset_project_issue.py | 15 +++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/rdmo/projects/tests/test_viewset_issue.py b/rdmo/projects/tests/test_viewset_issue.py index 112b99214d..767e385f45 100644 --- a/rdmo/projects/tests/test_viewset_issue.py +++ b/rdmo/projects/tests/test_viewset_issue.py @@ -49,14 +49,18 @@ def test_list(db, client, username, password): if password: assert response.status_code == 200 - assert isinstance(response.json(), list) + response_items = response.json() + assert isinstance(response_items, list) if username == 'user': - assert sorted([item['id'] for item in response.json()]) == issues_visible + assert sorted([item['id'] for item in response_items]) == issues_visible else: values_list = Issue.objects.filter(project__in=view_issue_permission_map.get(username, [])) \ .order_by('id').values_list('id', flat=True) - assert sorted([item['id'] for item in response.json()]) == list(values_list) + assert sorted([item['id'] for item in response_items]) == list(values_list) + + assert all(isinstance(item['resolve'], bool) for item in response_items) + assert all(isinstance(item['dates'], list) for item in response_items) else: assert response.status_code == 401 @@ -72,8 +76,11 @@ def test_detail(db, client, username, password, issue_id): if issue.project.id in view_issue_permission_map.get(username, []): assert response.status_code == 200 - assert isinstance(response.json(), dict) - assert response.json().get('id') == issue_id + response_data = response.json() + assert isinstance(response_data, dict) + assert response_data['id'] == issue_id + assert isinstance(response_data['resolve'], bool) + assert isinstance(response_data['dates'], list) elif password: assert response.status_code == 404 else: diff --git a/rdmo/projects/tests/test_viewset_project_issue.py b/rdmo/projects/tests/test_viewset_project_issue.py index a986543533..8dc52307a0 100644 --- a/rdmo/projects/tests/test_viewset_project_issue.py +++ b/rdmo/projects/tests/test_viewset_project_issue.py @@ -63,12 +63,16 @@ def test_list(db, client, username, password, project_id): if project_id in view_issue_permission_map.get(username, []): assert response.status_code == 200 + response_items = response.json() if username == 'user': - assert sorted([item['id'] for item in response.json()]) == issues_visible + assert sorted([item['id'] for item in response_items]) == issues_visible else: values_list = Issue.objects.filter(project_id=project_id) \ .order_by('id').values_list('id', flat=True) - assert sorted([item['id'] for item in response.json()]) == list(values_list) + assert sorted([item['id'] for item in response_items]) == list(values_list) + + assert all(isinstance(item['resolve'], bool) for item in response_items) + assert all(isinstance(item['dates'], list) for item in response_items) else: assert response.status_code == 404 @@ -84,8 +88,11 @@ def test_detail(db, client, username, password, issue_id): if issue.project_id in view_issue_permission_map.get(username, []): assert response.status_code == 200 - assert isinstance(response.json(), dict) - assert response.json().get('id') == issue_id + response_data = response.json() + assert isinstance(response_data, dict) + assert response_data['id'] == issue_id + assert isinstance(response_data['resolve'], bool) + assert isinstance(response_data['dates'], list) else: assert response.status_code == 404 From 26dc21eb9704668cf8db087fddf1789cd71c31a0 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Wed, 6 May 2026 16:29:44 +0200 Subject: [PATCH 06/33] fix property --- rdmo/projects/models/issue.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rdmo/projects/models/issue.py b/rdmo/projects/models/issue.py index 218ad0fc44..0dc1dee20d 100644 --- a/rdmo/projects/models/issue.py +++ b/rdmo/projects/models/issue.py @@ -51,9 +51,7 @@ def get_absolute_url(self): @property def resolve(self): - values = getattr(self.project, '_prefetched_current_values', None) - if values is None: - values = self.project.values.filter(snapshot=None) + values = self.project.values.filter(snapshot=None) return any(condition.resolve(values) for condition in self.task.conditions.all()) From d5cc9576761c9c68165bd44d5b88e593f5bc9043 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Wed, 6 May 2026 17:38:11 +0200 Subject: [PATCH 07/33] update Dashboard code --- .../js/project/components/areas/Dashboard.js | 328 ++++++++---------- 1 file changed, 152 insertions(+), 176 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index 5b50feb5d9..a7959ab7d5 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -1,212 +1,188 @@ import React, { useState } from 'react' -import { useDispatch } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { navigateDashboard } from '../../actions/projectActions' import { Tile } from '../helper' const Dashboard = () => { const dispatch = useDispatch() + const issues = useSelector((state) => state.project.project.tasks) ?? [] - const [selectedTask, setSelectedTask] = useState(null) + const [selectedTaskIssue, setSelectedTaskIssue] = useState(null) const [showClosedTasks, setShowClosedTasks] = useState(false) - const [tasks, setTasks] = useState([ - { - id: 1, - title: 'Recommendation: Consider reuse scenarios', - progress: '0 / 2', - date: '14 August 2024', - isDone: false, - }, - { - id: 2, - title: 'Consider Open Access Policy', - progress: '1 / 3', - date: '15 May 2024', - isDone: false, - }, - { - id: 3, - title: 'Consider Open Access Policy', - progress: '3 / 3', - date: '15 May 2024', - isDone: true, - }, - ]) - - const visibleTasks = tasks - .filter((task) => showClosedTasks || !task.isDone) - .sort((a, b) => Number(a.isDone) - Number(b.isDone)) - - const toggleTaskDone = (taskId) => { - setTasks((prevTasks) => - prevTasks.map((task) => - task.id === taskId ? { ...task, isDone: !task.isDone } : task - ) - ) + const isClosed = (issue) => issue.status === 'closed' + const isActive = (issue) => ['open', 'in_progress'].includes(issue.status) + const getTaskType = (issue) => issue.task?.task_type + + const stepIssues = issues.filter((issue) => + getTaskType(issue) === 'step' && isActive(issue) + ) + + const taskIssues = issues.filter((issue) => + getTaskType(issue) === 'task' + ) + + const visibleTaskIssues = taskIssues + .filter((issue) => showClosedTasks || !isClosed(issue)) + .sort((a, b) => Number(isClosed(a)) - Number(isClosed(b))) + + const guidanceIssues = issues.filter((issue) => + getTaskType(issue) === 'guidance' && isActive(issue) + ) + + const toggleTaskDone = (issueId) => { + // local placeholder until backend POST/PATCH exists + console.log('toggle issue', issueId) } return (

{gettext('Dashboard')}

-

{gettext('Create your data management plan')}

-
- dispatch(navigateDashboard({ area: 'interview'}))} - > -

{gettext('Fill out the selected questionnaire as completely as possible.')}

-
- dispatch(navigateDashboard({ area: 'documents'}))} - > -

{gettext('Export your data management plan in various formats.')}

-
-
- - {/*

{gettext('Tasks')}

-
- { - tasks.map((task) => ( - -
-
- -
+

{issue.task.text}

+ + )) + } +
+ + ) + } -
-
- {task.title} -
- -
-
- - {task.progress} -
- -
- - {task.date} -
-
-
-
- - )) - } -
*/} -

{gettext('Tasks')}

- -
- setShowClosedTasks((prev) => !prev)} - /> - -
- -
- { - visibleTasks.map((task) => ( - setSelectedTask(task)} - > -
-
- +
+ +
+
+ {issue.task.title} +
+ +
+
+ + {issue.resolve ? '1 / 1' : '0 / 1'} +
+ +
+ + {issue.dates?.[0] ?? ''} +
+
+
+
+
+ ) + }) + } +
+ + ) + } + + { + guidanceIssues.length > 0 && ( + <> +

{gettext('More actions')}

+
+ { + guidanceIssues.map((issue) => ( + { - e.stopPropagation() - toggleTaskDone(task.id) - } + issue.task.task_area ? ( + () => dispatch(navigateDashboard({ area: issue.task.task_area })) + ) : undefined } - aria-label={task.isDone ? 'Mark task as not done' : 'Mark task as done'} + size="compact" > - - -
+

{issue.task.text}

+ + )) + } +
+ + ) + } -
-
- {task.title} -
- -
-
- - {task.progress} -
- -
- - {task.date} -
-
-
-
-
- )) - } -
- -

{gettext('More actions')}

-
- dispatch(navigateDashboard({ area: 'memberships'}))} - size="compact" - > -

{gettext('Invite additional people to collaborate on creating your data management plan.')}

-
- dispatch(navigateDashboard({ area: 'snapshots'}))} - size="compact" - > -

{gettext('Save a snapshot to view or restore later.')}

-
-
{ - selectedTask && ( + selectedTaskIssue && (
-
{selectedTask.title}
+
{selectedTaskIssue.task.title}
-

{gettext('Task popup placeholder')}

-

{selectedTask.date}

+

{selectedTaskIssue.task.text}

+

{selectedTaskIssue.dates?.[0]}

From 7cbd0c787628eeb81b11e1a12acf44d5c4fe0186 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 7 May 2026 19:34:29 +0200 Subject: [PATCH 08/33] Use Modal component and add tasks fixtures --- .../js/project/components/areas/Dashboard.js | 36 +-- testing/fixtures/tasks.json | 300 ++++++++++++++++++ 2 files changed, 316 insertions(+), 20 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index a7959ab7d5..d8620eff53 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -1,12 +1,19 @@ import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' +import Modal from 'rdmo/core/assets/js/_bs53/components/Modal' + import { navigateDashboard } from '../../actions/projectActions' import { Tile } from '../helper' const Dashboard = () => { const dispatch = useDispatch() - const issues = useSelector((state) => state.project.project.tasks) ?? [] + // const issues = useSelector((state) => state.project.project.tasks) ?? [] + const allIssues = useSelector((state) => state.project.project.tasks) ?? [] + // const resolvedIssues = issues.filter((issue) => issue.resolve === true) + // console.log('resolved issues', resolvedIssues) + const issues = allIssues.filter((issue) => issue.resolve === true) + console.log('resolved issues', issues) const [selectedTaskIssue, setSelectedTaskIssue] = useState(null) const [showClosedTasks, setShowClosedTasks] = useState(false) @@ -166,27 +173,16 @@ const Dashboard = () => { ) } - { selectedTaskIssue && ( -
-
-
-
-
{selectedTaskIssue.task.title}
-
-
-

{selectedTaskIssue.task.text}

-

{selectedTaskIssue.dates?.[0]}

-
-
-
-
+ setSelectedTaskIssue(null)} + > +

{selectedTaskIssue.task.text}

+

{selectedTaskIssue.dates?.[0]}

+
) }
diff --git a/testing/fixtures/tasks.json b/testing/fixtures/tasks.json index dd3dc686a6..bf48a438ff 100644 --- a/testing/fixtures/tasks.json +++ b/testing/fixtures/tasks.json @@ -154,5 +154,305 @@ "groups": [], "conditions": [] } + }, + { + "model": "tasks.task", + "pk": 16, + "fields": { + "uri": "http://example.com/terms/tasks/step0", + "uri_prefix": "http://example.com/terms", + "uri_path": "step0", + "comment": "", + "locked": false, + "order": 0, + "available": true, + "catalogs": [], + "sites": [ + 3, + 1, + 2 + ], + "editors": [ + 3, + 1, + 2 + ], + "groups": [], + "task_type": "step", + "task_area": "interview", + "start_attribute": null, + "end_attribute": null, + "days_before": null, + "days_after": null, + "conditions": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "title_lang1": "Answer questions", + "title_lang2": "Fragen beantworten", + "title_lang3": "", + "title_lang4": "", + "title_lang5": "", + "text_lang1": "Fill out the selected questionnaire as completely as possible.", + "text_lang2": "Füllen Sie den ausgewählten Fragebogen soweit es Ihnen möglich ist aus.", + "text_lang3": "", + "text_lang4": "", + "text_lang5": "" + } + }, + { + "model": "tasks.task", + "pk": 17, + "fields": { + "uri": "http://example.com/terms/tasks/step1", + "uri_prefix": "http://example.com/terms", + "uri_path": "step1", + "comment": "", + "locked": false, + "order": 1, + "available": true, + "catalogs": [], + "sites": [ + 3, + 1, + 2 + ], + "editors": [ + 3, + 1, + 2 + ], + "groups": [], + "task_type": "step", + "task_area": "documents", + "start_attribute": null, + "end_attribute": null, + "days_before": null, + "days_after": null, + "conditions": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "title_lang1": "Export data management plan", + "title_lang2": "Datenmanagementplan exportieren", + "title_lang3": "", + "title_lang4": "", + "title_lang5": "", + "text_lang1": "Export your data management plan in various formats.", + "text_lang2": "Exportieren Sie Ihren Datenmanagementplan in verschiedenen Formaten.", + "text_lang3": "", + "text_lang4": "", + "text_lang5": "" + } + }, + { + "model": "tasks.task", + "pk": 18, + "fields": { + "uri": "http://example.com/terms/tasks/guidance0", + "uri_prefix": "http://example.com/terms", + "uri_path": "guidance0", + "comment": "", + "locked": false, + "order": 0, + "available": true, + "catalogs": [], + "sites": [ + 3, + 1, + 2 + ], + "editors": [ + 3, + 1, + 2 + ], + "groups": [], + "task_type": "guidance", + "task_area": "memberships", + "start_attribute": null, + "end_attribute": null, + "days_before": null, + "days_after": null, + "conditions": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "title_lang1": "Invite team members", + "title_lang2": "Teammitglieder einladen", + "title_lang3": "", + "title_lang4": "", + "title_lang5": "", + "text_lang1": "Invite additional people to collaborate on creating your data management plan.", + "text_lang2": "Laden Sie weitere Personen ein, an der Erstellung Ihres Datenmanagementplans mitzuwirken.", + "text_lang3": "", + "text_lang4": "", + "text_lang5": "" + } + }, + { + "model": "tasks.task", + "pk": 19, + "fields": { + "uri": "http://example.com/terms/tasks/guidance1", + "uri_prefix": "http://example.com/terms", + "uri_path": "guidance1", + "comment": "", + "locked": false, + "order": 1, + "available": true, + "catalogs": [], + "sites": [ + 3, + 1, + 2 + ], + "editors": [ + 3, + 1, + 2 + ], + "groups": [], + "task_type": "guidance", + "task_area": "snapshots", + "start_attribute": null, + "end_attribute": null, + "days_before": null, + "days_after": null, + "conditions": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "title_lang1": "Create snapshot", + "title_lang2": "Zwischenstand speichern", + "title_lang3": "", + "title_lang4": "", + "title_lang5": "", + "text_lang1": "Save a snapshot to view or restore later.", + "text_lang2": "Speichern Sie einen Zwischenstand, um ihn später wieder zu betrachten oder wiederherzustellen.", + "text_lang3": "", + "text_lang4": "", + "text_lang5": "" + } + }, + { + "model": "tasks.task", + "pk": 20, + "fields": { + "uri": "http://example.com/terms/tasks/recommendation0", + "uri_prefix": "http://example.com/terms", + "uri_path": "recommendation0", + "comment": "", + "locked": false, + "order": 0, + "available": true, + "catalogs": [], + "sites": [ + 3, + 1, + 2 + ], + "editors": [ + 3, + 1, + 2 + ], + "groups": [], + "task_type": "recommendation", + "task_area": "", + "start_attribute": null, + "end_attribute": null, + "days_before": null, + "days_after": null, + "conditions": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "title_lang1": "Consider reuse scenarios", + "title_lang2": "Nachnutzungsszenarien betrachten", + "title_lang3": "", + "title_lang4": "", + "title_lang5": "", + "text_lang1": "", + "text_lang2": "", + "text_lang3": "", + "text_lang4": "", + "text_lang5": "" + } } ] From 0df9449e109b0c44397e3f1ead7095206e40ca95 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 7 May 2026 20:04:55 +0200 Subject: [PATCH 09/33] Extend tests --- rdmo/projects/tests/helpers/sync/constants.py | 3 +++ .../tests/test_command_sync_projects.py | 18 +++++++++++------- .../tests/test_handlers_project_save.py | 7 +++++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/rdmo/projects/tests/helpers/sync/constants.py b/rdmo/projects/tests/helpers/sync/constants.py index 1c8c7117be..d4eba99bfc 100644 --- a/rdmo/projects/tests/helpers/sync/constants.py +++ b/rdmo/projects/tests/helpers/sync/constants.py @@ -3,3 +3,6 @@ U_title = "Sync U{}" one_two_three = (1, 2, 3) # will be used for creating P1, V1, C1, P2, ..etc. + +# Extra task fixtures added to rdmo/testing/fixtures/tasks.json +extra_task_ids = (16, 17, 18, 19, 20) diff --git a/rdmo/projects/tests/test_command_sync_projects.py b/rdmo/projects/tests/test_command_sync_projects.py index 623722a535..51953f581a 100644 --- a/rdmo/projects/tests/test_command_sync_projects.py +++ b/rdmo/projects/tests/test_command_sync_projects.py @@ -8,6 +8,8 @@ from rdmo.projects.tests.helpers.sync.assert_project_views_or_tasks import ( assert_all_projects_are_synced_with_instance_m2m_field, ) +from rdmo.projects.tests.helpers.sync.constants import extra_task_ids +from rdmo.tasks.models import Task PROJECT_SHOW_TEMPLATE = 'Project "{}" [id={}]:' @@ -17,6 +19,7 @@ def test_command_sync_projects_for_tasks(settings): # Arrange: pre-linked projects and catalog-based task relationships P, _, T = arrange_projects_catalogs_and_tasks() + extra_tasks = set(Task.objects.filter(pk__in=extra_task_ids)) # Arrange: project.tasks are in a random initial state P[1].tasks.set([T[1], T[2], T[3]]) @@ -26,10 +29,10 @@ def test_command_sync_projects_for_tasks(settings): # === Act: run the management command for task sync === call_command('sync_projects', '--tasks') - # === Assert: each project should only be linked to tasks with matching catalogs === - assert set(P[1].tasks.all()) == {T[1]} - assert set(P[2].tasks.all()) == {T[2]} - assert set(P[3].tasks.all()) == {T[3]} + # === Assert: each project should only be linked to tasks with matching catalogs and sites === + assert set(P[1].tasks.all()) == {T[1]} | extra_tasks + assert set(P[2].tasks.all()) == {T[2]} | extra_tasks + assert set(P[3].tasks.all()) == {T[3]} | extra_tasks # Additional assertion using your existing helper for task in T.values(): @@ -88,6 +91,7 @@ def test_command_sync_projects_for_tasks_and_views_with_show(settings, capsys): # Arrange task and view state P1, _, T = arrange_projects_catalogs_and_tasks() P2, _, V = arrange_projects_catalogs_and_views() + extra_tasks = set(Task.objects.filter(pk__in=extra_task_ids)) # Arrange random desynced state P1[1].tasks.set([T[1], T[2], T[3]]) @@ -102,9 +106,9 @@ def test_command_sync_projects_for_tasks_and_views_with_show(settings, capsys): call_command('sync_projects', '--tasks', '--views', '--show') # === Assert: state is fully synced - assert set(P1[1].tasks.all()) == {T[1]} - assert set(P1[2].tasks.all()) == {T[2]} - assert set(P1[3].tasks.all()) == {T[3]} + assert set(P1[1].tasks.all()) == {T[1]} | extra_tasks + assert set(P1[2].tasks.all()) == {T[2]} | extra_tasks + assert set(P1[3].tasks.all()) == {T[3]} | extra_tasks assert set(P2[1].views.all()) == {V[1]} assert set(P2[2].views.all()) == {V[2]} diff --git a/rdmo/projects/tests/test_handlers_project_save.py b/rdmo/projects/tests/test_handlers_project_save.py index f94643277a..eae60bb464 100644 --- a/rdmo/projects/tests/test_handlers_project_save.py +++ b/rdmo/projects/tests/test_handlers_project_save.py @@ -2,6 +2,8 @@ from rdmo.projects.tests.helpers.sync.arrange_project_tasks import arrange_projects_catalogs_and_tasks from rdmo.projects.tests.helpers.sync.arrange_project_views import arrange_projects_catalogs_and_views +from rdmo.projects.tests.helpers.sync.constants import extra_task_ids +from rdmo.tasks.models import Task @pytest.mark.django_db @@ -9,6 +11,7 @@ def test_project_tasks_sync_when_changing_a_catalog_on_a_project(settings): settings.PROJECT_TASKS_SYNC = True P, C, T = arrange_projects_catalogs_and_tasks() + extra_tasks = set(Task.objects.filter(pk__in=extra_task_ids)) # === Initial state === # P1 (with C1) has V1, etc.. assert set(P[1].tasks.all()) == {T[1]} @@ -20,8 +23,8 @@ def test_project_tasks_sync_when_changing_a_catalog_on_a_project(settings): P[2].catalog = C[1] P[2].save() # Assert: T1 and T2 were swapped - assert set(P[1].tasks.all()) == {T[2]} - assert set(P[2].tasks.all()) == {T[1]} + assert set(P[1].tasks.all()) == {T[2]} | extra_tasks + assert set(P[2].tasks.all()) == {T[1]} | extra_tasks assert set(P[3].tasks.all()) == {T[3]} From 036ea40eef8c1db7c641efe71ab33ab470b26932 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Fri, 8 May 2026 13:47:41 +0200 Subject: [PATCH 10/33] Limit resolve checks to tasks --- rdmo/projects/models/issue.py | 5 +- testing/fixtures/tasks.json | 100 ++-------------------------------- 2 files changed, 9 insertions(+), 96 deletions(-) diff --git a/rdmo/projects/models/issue.py b/rdmo/projects/models/issue.py index 0dc1dee20d..cc7d855ee7 100644 --- a/rdmo/projects/models/issue.py +++ b/rdmo/projects/models/issue.py @@ -5,6 +5,7 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ +from rdmo.tasks.constants import TaskTypes from rdmo.tasks.models import Task from ..managers import IssueManager @@ -53,7 +54,9 @@ def get_absolute_url(self): def resolve(self): values = self.project.values.filter(snapshot=None) - return any(condition.resolve(values) for condition in self.task.conditions.all()) + if self.task.task_type == TaskTypes.TASK: + return any(condition.resolve(values) for condition in self.task.conditions.all()) + return True @property def dates(self): diff --git a/testing/fixtures/tasks.json b/testing/fixtures/tasks.json index bf48a438ff..19499a31ff 100644 --- a/testing/fixtures/tasks.json +++ b/testing/fixtures/tasks.json @@ -184,25 +184,7 @@ "end_attribute": null, "days_before": null, "days_after": null, - "conditions": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - 17, - 18 - ], + "conditions": [], "title_lang1": "Answer questions", "title_lang2": "Fragen beantworten", "title_lang3": "", @@ -244,25 +226,7 @@ "end_attribute": null, "days_before": null, "days_after": null, - "conditions": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - 17, - 18 - ], + "conditions": [], "title_lang1": "Export data management plan", "title_lang2": "Datenmanagementplan exportieren", "title_lang3": "", @@ -304,25 +268,7 @@ "end_attribute": null, "days_before": null, "days_after": null, - "conditions": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - 17, - 18 - ], + "conditions": [], "title_lang1": "Invite team members", "title_lang2": "Teammitglieder einladen", "title_lang3": "", @@ -364,25 +310,7 @@ "end_attribute": null, "days_before": null, "days_after": null, - "conditions": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - 17, - 18 - ], + "conditions": [], "title_lang1": "Create snapshot", "title_lang2": "Zwischenstand speichern", "title_lang3": "", @@ -424,25 +352,7 @@ "end_attribute": null, "days_before": null, "days_after": null, - "conditions": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 13, - 14, - 15, - 16, - 17, - 18 - ], + "conditions": [], "title_lang1": "Consider reuse scenarios", "title_lang2": "Nachnutzungsszenarien betrachten", "title_lang3": "", From 5968e32e1790a73005142a1b7e05144b2857f6cd Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Fri, 8 May 2026 17:08:59 +0200 Subject: [PATCH 11/33] Add render function --- .../js/project/components/areas/Dashboard.js | 132 ++++++++++-------- 1 file changed, 77 insertions(+), 55 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index d8620eff53..a18c71a4f1 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -17,6 +17,7 @@ const Dashboard = () => { const [selectedTaskIssue, setSelectedTaskIssue] = useState(null) const [showClosedTasks, setShowClosedTasks] = useState(false) + const [showClosedRecommendations, setShowClosedRecommendations] = useState(false) const isClosed = (issue) => issue.status === 'closed' const isActive = (issue) => ['open', 'in_progress'].includes(issue.status) @@ -32,7 +33,13 @@ const Dashboard = () => { const visibleTaskIssues = taskIssues .filter((issue) => showClosedTasks || !isClosed(issue)) - .sort((a, b) => Number(isClosed(a)) - Number(isClosed(b))) + + const recommendationIssues = issues.filter((issue) => + getTaskType(issue) === 'recommendation' + ) + + const visibleRecommendationIssues = recommendationIssues + .filter((issue) => showClosedRecommendations || !isClosed(issue)) const guidanceIssues = issues.filter((issue) => getTaskType(issue) === 'guidance' && isActive(issue) @@ -43,6 +50,53 @@ const Dashboard = () => { console.log('toggle issue', issueId) } + const renderIssueTiles = (visibleIssues) => ( +
+ { + visibleIssues.map((issue) => { + const closed = isClosed(issue) + + return ( + setSelectedTaskIssue(issue)} + > +
+
+ +
+ +
+
+ {issue.task.title} +
+ +
+ + {issue.dates?.[0] ?? ''} +
+
+
+
+ ) + }) + } +
+ ) + return (

{gettext('Dashboard')}

@@ -53,11 +107,11 @@ const Dashboard = () => {

{gettext('Create your data management plan')}

{ - stepIssues.map((issue, index) => ( + stepIssues.sort((a, b) => a.task.order - b.task.order).map((issue, index) => ( { ) } - { taskIssues.length > 0 && ( <> @@ -91,62 +144,31 @@ const Dashboard = () => { {gettext('Show closed tasks')}
+ {renderIssueTiles(visibleTaskIssues)} + + ) + } + { + recommendationIssues.length > 0 && ( + <> +

{gettext('Recommendations')}

-
- { - visibleTaskIssues.map((issue) => { - const closed = isClosed(issue) - - return ( - setSelectedTaskIssue(issue)} - > -
-
- -
- -
-
- {issue.task.title} -
- -
-
- - {issue.resolve ? '1 / 1' : '0 / 1'} -
- -
- - {issue.dates?.[0] ?? ''} -
-
-
-
-
- ) - }) - } +
+ setShowClosedRecommendations((prev) => !prev)} + /> +
+ {renderIssueTiles(visibleRecommendationIssues)} ) } - { guidanceIssues.length > 0 && ( <> From 74c8234f7d1e79996164eae661766c5d2befb852 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 11 May 2026 11:07:52 +0200 Subject: [PATCH 12/33] Improve sort and status --- .../assets/js/project/components/areas/Dashboard.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index a18c71a4f1..f07b0e3a56 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -20,12 +20,12 @@ const Dashboard = () => { const [showClosedRecommendations, setShowClosedRecommendations] = useState(false) const isClosed = (issue) => issue.status === 'closed' - const isActive = (issue) => ['open', 'in_progress'].includes(issue.status) + // const isActive = (issue) => ['open', 'in_progress'].includes(issue.status) const getTaskType = (issue) => issue.task?.task_type const stepIssues = issues.filter((issue) => - getTaskType(issue) === 'step' && isActive(issue) - ) + getTaskType(issue) === 'step' + ).sort((a, b) => a.task.order - b.task.order) const taskIssues = issues.filter((issue) => getTaskType(issue) === 'task' @@ -42,8 +42,8 @@ const Dashboard = () => { .filter((issue) => showClosedRecommendations || !isClosed(issue)) const guidanceIssues = issues.filter((issue) => - getTaskType(issue) === 'guidance' && isActive(issue) - ) + getTaskType(issue) === 'guidance' + ).sort((a, b) => a.task.order - b.task.order) const toggleTaskDone = (issueId) => { // local placeholder until backend POST/PATCH exists @@ -107,7 +107,7 @@ const Dashboard = () => {

{gettext('Create your data management plan')}

{ - stepIssues.sort((a, b) => a.task.order - b.task.order).map((issue, index) => ( + stepIssues.map((issue, index) => ( Date: Mon, 11 May 2026 18:07:35 +0200 Subject: [PATCH 13/33] Add update task functionality --- rdmo/core/settings.py | 2 +- .../assets/js/project/actions/actionTypes.js | 4 + .../js/project/actions/projectActions.js | 35 ++++++++ .../assets/js/project/api/ProjectApi.js | 4 + .../js/project/components/areas/Dashboard.js | 81 ++++++++++++++----- .../assets/js/project/store/configureStore.js | 18 +++-- rdmo/projects/serializers/v1/__init__.py | 17 ++++ 7 files changed, 133 insertions(+), 28 deletions(-) diff --git a/rdmo/core/settings.py b/rdmo/core/settings.py index 7f19109742..2ce59a521c 100644 --- a/rdmo/core/settings.py +++ b/rdmo/core/settings.py @@ -412,7 +412,7 @@ PROJECT_SEND_INVITE = True PROJECT_VIEWS_SYNC = False -PROJECT_TASKS_SYNC = False +PROJECT_TASKS_SYNC = True PROJECT_SELECT_CATALOG = 'radio' diff --git a/rdmo/projects/assets/js/project/actions/actionTypes.js b/rdmo/projects/assets/js/project/actions/actionTypes.js index ab19f12323..a415a96bd0 100644 --- a/rdmo/projects/assets/js/project/actions/actionTypes.js +++ b/rdmo/projects/assets/js/project/actions/actionTypes.js @@ -74,3 +74,7 @@ export const DELETE_PROJECT_VISIBILITY_ERROR = 'DELETE_PROJECT_VISIBILITY_ERROR' export const FETCH_PROJECT_VISIBILITY_INIT = 'FETCH_PROJECT_VISIBILITY_INIT' export const FETCH_PROJECT_VISIBILITY_SUCCESS = 'FETCH_PROJECT_VISIBILITY_SUCCESS' export const FETCH_PROJECT_VISIBILITY_ERROR = 'FETCH_PROJECT_VISIBILITY_ERROR' + +export const UPDATE_PROJECT_TASK_INIT = 'UPDATE_PROJECT_TASK_INIT' +export const UPDATE_PROJECT_TASK_SUCCESS = 'UPDATE_PROJECT_TASK_SUCCESS' +export const UPDATE_PROJECT_TASK_ERROR = 'UPDATE_PROJECT_TASK_ERROR' diff --git a/rdmo/projects/assets/js/project/actions/projectActions.js b/rdmo/projects/assets/js/project/actions/projectActions.js index 10b6f8c26c..260f82b7de 100644 --- a/rdmo/projects/assets/js/project/actions/projectActions.js +++ b/rdmo/projects/assets/js/project/actions/projectActions.js @@ -222,6 +222,41 @@ export function deleteProjectVisibility() { } } +// project task + +export function updateProjectTask(issueId, data) { + return function (dispatch, getState) { + dispatch(addToPending('updateProjectTask')) + dispatch({ type: actionTypes.UPDATE_PROJECT_TASK_INIT }) + + return ProjectApi.updateProjectTask(projectId, issueId, data) + .then(() => { + const state = getState() + const currentBundle = state.project.project + + return ProjectApi.fetchProjectTasks(projectId) + .then((tasks) => ({ currentBundle, tasks })) + }) + .then(({ currentBundle, tasks }) => { + const updatedBundle = { + ...currentBundle, + tasks, + } + + dispatch(removeFromPending('updateProjectTask')) + dispatch({ + type: actionTypes.UPDATE_PROJECT_SUCCESS, + project: updatedBundle, + }) + }) + .catch((error) => { + dispatch(removeFromPending('updateProjectTask')) + dispatch({ type: actionTypes.UPDATE_PROJECT_TASK_ERROR, error }) + throw error + }) + } +} + // memberships / invites / leave export function fetchProjectInvites() { diff --git a/rdmo/projects/assets/js/project/api/ProjectApi.js b/rdmo/projects/assets/js/project/api/ProjectApi.js index 4e8302b024..2276fd4285 100644 --- a/rdmo/projects/assets/js/project/api/ProjectApi.js +++ b/rdmo/projects/assets/js/project/api/ProjectApi.js @@ -26,6 +26,10 @@ export default class ProjectApi extends BaseApi { return this.get(`/api/v1/projects/projects/${projectId}/issues/`) } + static updateProjectTask(projectId, issueId, data) { + return this.put(`/api/v1/projects/projects/${projectId}/issues/${issueId}/`, data) + } + static fetchProjectMemberships(projectId) { return this.get(`/api/v1/projects/projects/${projectId}/memberships/`) } diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index f07b0e3a56..fb0176efe4 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -1,26 +1,38 @@ import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import Modal from 'rdmo/core/assets/js/_bs53/components/Modal' +import { Modal } from 'rdmo/core/assets/js/_bs53/components' +import * as configActions from 'rdmo/core/assets/js/actions/configActions' -import { navigateDashboard } from '../../actions/projectActions' +import Select from 'rdmo/core/assets/js/components/forms/Select' + +import { navigateDashboard, updateProjectTask } from '../../actions/projectActions' import { Tile } from '../helper' const Dashboard = () => { const dispatch = useDispatch() - // const issues = useSelector((state) => state.project.project.tasks) ?? [] - const allIssues = useSelector((state) => state.project.project.tasks) ?? [] - // const resolvedIssues = issues.filter((issue) => issue.resolve === true) - // console.log('resolved issues', resolvedIssues) - const issues = allIssues.filter((issue) => issue.resolve === true) - console.log('resolved issues', issues) - + const config = useSelector(state => state.config) + const issues = useSelector((state) => state.project.project.tasks) ?? [] + // const allIssues = useSelector((state) => state.project.project.tasks) ?? [] + const resolvedIssues = issues.filter((issue) => issue.resolve === true) + console.log('resolved issues', resolvedIssues) + // const issues = allIssues.filter((issue) => issue.resolve === true) + // console.log('resolved issues', issues) + + console.log('all issues', issues) + const statusOptions = [ + { value: 'open', label: gettext('Open') }, + { value: 'closed', label: gettext('Closed') }, + { value: 'in_progress', label: gettext('In progress') }, + ] + + const { showClosedTasks, showClosedRecommendations } = config + + console.log('showClosedTasks', showClosedTasks) + console.log('showClosedRecommendations', showClosedRecommendations) const [selectedTaskIssue, setSelectedTaskIssue] = useState(null) - const [showClosedTasks, setShowClosedTasks] = useState(false) - const [showClosedRecommendations, setShowClosedRecommendations] = useState(false) const isClosed = (issue) => issue.status === 'closed' - // const isActive = (issue) => ['open', 'in_progress'].includes(issue.status) const getTaskType = (issue) => issue.task?.task_type const stepIssues = issues.filter((issue) => @@ -33,6 +45,7 @@ const Dashboard = () => { const visibleTaskIssues = taskIssues .filter((issue) => showClosedTasks || !isClosed(issue)) + .sort((a, b) => Number(isClosed(a)) - Number(isClosed(b))) const recommendationIssues = issues.filter((issue) => getTaskType(issue) === 'recommendation' @@ -40,14 +53,17 @@ const Dashboard = () => { const visibleRecommendationIssues = recommendationIssues .filter((issue) => showClosedRecommendations || !isClosed(issue)) + .sort((a, b) => Number(isClosed(a)) - Number(isClosed(b))) const guidanceIssues = issues.filter((issue) => getTaskType(issue) === 'guidance' ).sort((a, b) => a.task.order - b.task.order) - const toggleTaskDone = (issueId) => { - // local placeholder until backend POST/PATCH exists + const toggleTaskDone = (issueId, currentStatus) => { console.log('toggle issue', issueId) + dispatch(updateProjectTask(issueId, { + status: currentStatus === 'closed' ? 'open' : 'closed' + })) } const renderIssueTiles = (visibleIssues) => ( @@ -55,7 +71,6 @@ const Dashboard = () => { { visibleIssues.map((issue) => { const closed = isClosed(issue) - return ( { onClick={ (e) => { e.stopPropagation() - toggleTaskDone(issue.id) + toggleTaskDone(issue.id, issue.status) } } aria-label={closed ? 'Mark task as not done' : 'Mark task as done'} @@ -78,12 +93,10 @@ const Dashboard = () => {
-
{issue.task.title}
-
{issue.dates?.[0] ?? ''} @@ -138,7 +151,7 @@ const Dashboard = () => { type="checkbox" id="showClosedTasks" checked={showClosedTasks} - onChange={() => setShowClosedTasks((prev) => !prev)} + onChange={() => dispatch(configActions.updateConfig('showClosedTasks', !showClosedTasks))} />
@@ -201,9 +219,28 @@ const Dashboard = () => { show title={selectedTaskIssue.task.title} onClose={() => setSelectedTaskIssue(null)} + size="modal-lg" > +

{selectedTaskIssue.id}

{selectedTaskIssue.task.text}

+ {/*

{selectedTaskIssue.status}

*/} + dispatch(configActions.updateConfig('showClosedTasks', !showClosedTasks))} - /> - -
- {renderIssueTiles(visibleTaskIssues)} - - ) - } - { - recommendationIssues.length > 0 && ( - <> -

{gettext('Recommendations')}

- -
- dispatch(configActions.updateConfig( - 'showClosedRecommendations', - !showClosedRecommendations - )) - } - /> - -
- {renderIssueTiles(visibleRecommendationIssues)} - - ) - } - { - guidanceIssues.length > 0 && ( - <> -

{gettext('More actions')}

-
- { - guidanceIssues.map((issue) => ( - dispatch(navigateDashboard({ area: issue.task.task_area })) - ) : undefined +
+ + ) + } + { + taskIssues.length > 0 && ( + <> +

{gettext('Tasks')}

+ +
+ dispatch(configActions.updateConfig('showClosedTasks', !showClosedTasks))} + /> + +
+ {renderIssueTiles(visibleTaskIssues)} + + ) + } + { + recommendationIssues.length > 0 && ( + <> +

{gettext('Recommendations')}

+ +
+ dispatch(configActions.updateConfig( + 'showClosedRecommendations', + !showClosedRecommendations + )) + } + /> + +
+ {renderIssueTiles(visibleRecommendationIssues)} + + ) + } + { + guidanceIssues.length > 0 && ( + <> +

{gettext('More actions')}

+
+ { + guidanceIssues.map((issue) => ( + dispatch(navigateDashboard({ area: issue.task.task_area })) + ) : undefined + } + size="compact" + > +

{issue.task.text}

+
+ )) } - size="compact" - > -

{issue.task.text}

- - )) - } -
+
+ + ) + } + { + selectedTaskIssue && ( + setSelectedTaskIssue(null)} + size="modal-lg" + > +

{selectedTaskIssue.id}

+

{selectedTaskIssue.task.text}

+ {/*

{selectedTaskIssue.status}

*/} + { - dispatch(updateProjectTask(selectedTaskIssue.id, { status })) - setSelectedTaskIssue({ - ...selectedTaskIssue, - status, - }) - } - } - /> -

{`URI: ${selectedTaskIssue.task.uri}`}

-

{selectedTaskIssue.dates?.[0]}

-

{`Condition Uris: ${(selectedTaskIssue.task.condition_uris ?? []).join(', ')}`}

-
- ) - }
) } From 9e331ab7935dab9d205d6973da3fcdabcd0a3f58 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Tue, 19 May 2026 18:33:22 +0200 Subject: [PATCH 16/33] Improve step button --- .../js/project/components/areas/Dashboard.js | 43 ++++++++++++------- .../js/project/components/helper/Tile.js | 27 ++++++++++-- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index cc6e646e2e..ab213e656a 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -40,6 +40,8 @@ const Dashboard = () => { getTaskType(issue) === 'step' ).sort((a, b) => a.task.order - b.task.order) + const activeStepIssue = stepIssues.find((issue) => !isClosed(issue)) + const taskIssues = issues.filter((issue) => getTaskType(issue) === 'task' ) @@ -124,21 +126,31 @@ const Dashboard = () => {

{gettext('Create your data management plan')}

{ - stepIssues.map((issue, index) => ( - dispatch(navigateDashboard({ area: issue.task.task_area })) - ) : undefined - } - > -

{issue.task.text}

-
- )) + stepIssues.map((issue, index) => { + const isActiveStep = activeStepIssue?.id === issue.id + return ( + { + dispatch(navigateDashboard({ area: issue.task.task_area })) + if (isActiveStep) { + dispatch(updateProjectTask(issue.id, { status: 'closed'})) + } + } + ) : undefined + } + > +

{issue.task.text}

+
+ ) + }) }
@@ -202,6 +214,7 @@ const Dashboard = () => { key={issue.id} title={issue.task.title} buttonLabel={issue.task.task_area_display} + buttonIconClassName="bi bi-arrow-right" onClick={ issue.task.task_area ? ( () => dispatch(navigateDashboard({ area: issue.task.task_area })) diff --git a/rdmo/projects/assets/js/project/components/helper/Tile.js b/rdmo/projects/assets/js/project/components/helper/Tile.js index ccb0fe1f1c..1c1b161ebf 100644 --- a/rdmo/projects/assets/js/project/components/helper/Tile.js +++ b/rdmo/projects/assets/js/project/components/helper/Tile.js @@ -1,7 +1,18 @@ import React from 'react' import PropTypes from 'prop-types' -const Tile = ({ title, label, buttonLabel, children, className = '', size = 'normal', onClick, onCardClick }) => { +const Tile = ({ + title, + label, + buttonLabel, + buttonClassName = 'btn-outline-primary', + buttonIconClassName, + children, + className = '', + size = 'normal', + onClick, + onCardClick +}) => { const sizeClasses = { compact: 'col-12 col-md-4', // 3 tiles per row normal: 'col-12 col-md-6', // 2 tiles per row @@ -23,8 +34,7 @@ const Tile = ({ title, label, buttonLabel, children, className = '', size = 'nor
) @@ -45,6 +62,8 @@ const Tile = ({ title, label, buttonLabel, children, className = '', size = 'nor Tile.propTypes = { title: PropTypes.string, buttonLabel: PropTypes.node, + buttonClassName: PropTypes.string, + buttonIconClassName: PropTypes.string, children: PropTypes.node, className: PropTypes.string, label: PropTypes.node, From ca7412cd057f6e569ed577591cde81433256fa6b Mon Sep 17 00:00:00 2001 From: Jochen Klar Date: Tue, 19 May 2026 17:33:25 +0200 Subject: [PATCH 17/33] Add questions to ProjectIssueSerializer and IssueSerializer --- rdmo/projects/models/issue.py | 16 +++++++ rdmo/projects/serializers/v1/__init__.py | 59 ++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/rdmo/projects/models/issue.py b/rdmo/projects/models/issue.py index cc7d855ee7..ad8ccb0993 100644 --- a/rdmo/projects/models/issue.py +++ b/rdmo/projects/models/issue.py @@ -97,6 +97,22 @@ def dates(self): return dates + @property + def questions(self): + # collect all source_ids from all conditions of the task of this issue + source_ids = { + condition.source_id + for condition in self.task.conditions.all() + } + + # prefetch the catalog and filter all questions for the source_ids and all pages for those questions + self.project.catalog.prefetch_elements() + questions = list(filter(lambda q: q.source_id in source_ids, self.project.catalog.questions)) + for question in questions: + question.page_list = list(filter(lambda p: question in p.elements, self.project.catalog.pages)) + + return questions + class IssueResource(models.Model): diff --git a/rdmo/projects/serializers/v1/__init__.py b/rdmo/projects/serializers/v1/__init__.py index 95e4a30692..1fb2ebff2a 100644 --- a/rdmo/projects/serializers/v1/__init__.py +++ b/rdmo/projects/serializers/v1/__init__.py @@ -12,7 +12,7 @@ from rdmo.conditions.serializers.v1 import ConditionSerializer from rdmo.core.serializers import TranslationSerializerMixin from rdmo.domain.models import Attribute -from rdmo.questions.models import Catalog +from rdmo.questions.models import Catalog, Page, Question from rdmo.services.validators import ProviderValidator from rdmo.tasks.models import Task from rdmo.views.models import View @@ -549,10 +549,35 @@ class Meta: ) +class ProjectIssuePageSerializer(serializers.ModelSerializer): + + class Meta: + model = Page + fields = ( + 'id', + 'title', + 'help' + ) + +class ProjectIssueQuestionSerializer(serializers.ModelSerializer): + + pages = ProjectIssuePageSerializer(source='page_list', read_only=True, many=True) + + class Meta: + model = Question + fields = ( + 'id', + 'text', + 'help', + 'pages' + ) + + class ProjectIssueSerializer(serializers.ModelSerializer): task = ProjectIssueTaskSerializer(read_only=True) resources = ProjectIssueResourceSerializer(read_only=True, many=True) + questions = ProjectIssueQuestionSerializer(read_only=True, many=True) resolve = serializers.BooleanField(read_only=True) dates = serializers.ReadOnlyField() @@ -564,7 +589,8 @@ class Meta: 'status', 'resolve', 'resources', - 'dates' + 'dates', + 'questions', ) @@ -760,11 +786,37 @@ class Meta: ) +class IssuePageSerializer(serializers.ModelSerializer): + + class Meta: + model = Page + fields = ( + 'id', + 'title', + 'help' + ) + + +class IssueQuestionSerializer(serializers.ModelSerializer): + + pages = ProjectIssuePageSerializer(source='page_list', read_only=True, many=True) + + class Meta: + model = Question + fields = ( + 'id', + 'text', + 'help', + 'pages' + ) + + class IssueSerializer(serializers.ModelSerializer): project = serializers.PrimaryKeyRelatedField(read_only=True) task = IssueTaskSerializer(read_only=True) resources = IssueResourceSerializer(read_only=True, many=True) + questions = IssueQuestionSerializer(read_only=True, many=True) resolve = serializers.BooleanField(read_only=True) dates = serializers.ReadOnlyField() @@ -777,7 +829,8 @@ class Meta: 'status', 'resolve', 'resources', - 'dates' + 'dates', + 'questions' ) From 92fa145ab0a45496abf56ae71720c040209feddd Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Tue, 19 May 2026 19:30:48 +0200 Subject: [PATCH 18/33] Fix e2e test --- rdmo/projects/tests/e2e/test_frontend_project_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdmo/projects/tests/e2e/test_frontend_project_detail.py b/rdmo/projects/tests/e2e/test_frontend_project_detail.py index 6ce15166c5..5a44eda174 100644 --- a/rdmo/projects/tests/e2e/test_frontend_project_detail.py +++ b/rdmo/projects/tests/e2e/test_frontend_project_detail.py @@ -18,5 +18,5 @@ def test_project_detail_page(page: Page): page.screenshot(path="screenshots/projects/project-detail.png", full_page=True) # Assert project detail page - expect(page.get_by_text("Test")).to_be_visible() + expect(page.get_by_role("heading", name="Test")).to_be_visible() expect(page.get_by_text("Interview")).to_be_visible() From 16c173a83d9ea15f1b58651703cf11b465cdebe0 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Tue, 19 May 2026 20:23:38 +0200 Subject: [PATCH 19/33] Remove editors from new tasks fixtures to prevent tests from failing --- testing/fixtures/tasks.json | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/testing/fixtures/tasks.json b/testing/fixtures/tasks.json index 19499a31ff..871946130d 100644 --- a/testing/fixtures/tasks.json +++ b/testing/fixtures/tasks.json @@ -172,11 +172,7 @@ 1, 2 ], - "editors": [ - 3, - 1, - 2 - ], + "editors": [], "groups": [], "task_type": "step", "task_area": "interview", @@ -214,11 +210,7 @@ 1, 2 ], - "editors": [ - 3, - 1, - 2 - ], + "editors": [], "groups": [], "task_type": "step", "task_area": "documents", @@ -256,11 +248,7 @@ 1, 2 ], - "editors": [ - 3, - 1, - 2 - ], + "editors": [], "groups": [], "task_type": "guidance", "task_area": "memberships", @@ -298,11 +286,7 @@ 1, 2 ], - "editors": [ - 3, - 1, - 2 - ], + "editors": [], "groups": [], "task_type": "guidance", "task_area": "snapshots", @@ -340,11 +324,7 @@ 1, 2 ], - "editors": [ - 3, - 1, - 2 - ], + "editors": [], "groups": [], "task_type": "recommendation", "task_area": "", From 6cedb777453a042645b629ce9948831f3f12db53 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 21 May 2026 16:27:01 +0200 Subject: [PATCH 20/33] Fix questions in issue model --- rdmo/projects/models/issue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdmo/projects/models/issue.py b/rdmo/projects/models/issue.py index ad8ccb0993..da7341170a 100644 --- a/rdmo/projects/models/issue.py +++ b/rdmo/projects/models/issue.py @@ -107,7 +107,7 @@ def questions(self): # prefetch the catalog and filter all questions for the source_ids and all pages for those questions self.project.catalog.prefetch_elements() - questions = list(filter(lambda q: q.source_id in source_ids, self.project.catalog.questions)) + questions = list(filter(lambda q: q.attribute_id in source_ids, self.project.catalog.questions)) for question in questions: question.page_list = list(filter(lambda p: question in p.elements, self.project.catalog.pages)) From 094593640af176c8702409d19d5488c1a86eedd8 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 21 May 2026 16:54:14 +0200 Subject: [PATCH 21/33] Add questions to frontend display --- .../js/project/components/areas/Dashboard.js | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index ab213e656a..c3754c6b9c 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' import { Modal } from 'rdmo/core/assets/js/_bs53/components' import * as configActions from 'rdmo/core/assets/js/actions/configActions' +import { baseUrl } from 'rdmo/core/assets/js/utils/meta' import Select from 'rdmo/core/assets/js/components/forms/Select' @@ -256,9 +257,36 @@ const Dashboard = () => { } } /> -

{`URI: ${selectedTaskIssue.task.uri}`}

+ { + selectedTaskIssue.questions?.length > 0 && ( +
+
{gettext('Origin')}
+ +
    + { + selectedTaskIssue.questions.map((question) => ( +
  • +
    {question.text}
    + + { + question.pages?.map((page) => ( + + {page.title} + + )) + } +
  • + )) + } +
+
+ ) + }

{selectedTaskIssue.dates?.[0]}

-

{`Condition Uris: ${(selectedTaskIssue.task.condition_uris ?? []).join(', ')}`}

) } From 05f56b198bdefd70bb623fce4ebb85104fdd8f9e Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 21 May 2026 18:34:06 +0200 Subject: [PATCH 22/33] Extend useFormattedDateTime, tweak questions and date display --- .../assets/js/hooks/useFormattedDateTime.js | 18 ++- .../js/project/components/areas/Dashboard.js | 107 ++++++++++++------ 2 files changed, 83 insertions(+), 42 deletions(-) diff --git a/rdmo/core/assets/js/hooks/useFormattedDateTime.js b/rdmo/core/assets/js/hooks/useFormattedDateTime.js index 4986a4dfcf..82a2e732af 100644 --- a/rdmo/core/assets/js/hooks/useFormattedDateTime.js +++ b/rdmo/core/assets/js/hooks/useFormattedDateTime.js @@ -1,4 +1,4 @@ -import { format } from 'date-fns' +import { format, parseISO } from 'date-fns' import { de, enUS } from 'date-fns/locale' const getLocaleObject = (language) => { @@ -6,15 +6,21 @@ const getLocaleObject = (language) => { } const FORMAT_STRINGS = { - en: 'MMM d, yyyy, h:mm a', - de: 'd. MMM yyyy, H:mm', + dateTime: { + en: 'MMM d, yyyy, h:mm a', + de: 'd. MMM yyyy, H:mm', + }, + dateOnly: { + en: 'MMM d, yyyy', + de: 'd. MMM yyyy', + }, } -export const useFormattedDateTime = (date, language) => { +export const useFormattedDateTime = (date, language, formatType = 'dateTime') => { const locale = getLocaleObject(language) - const formatString = language === 'de' ? FORMAT_STRINGS.de : FORMAT_STRINGS.en + const formatString = FORMAT_STRINGS[formatType][language] ?? FORMAT_STRINGS[formatType].en - return format(new Date(date), formatString, { locale }) + return format(parseISO(date), formatString, { locale }) } export default useFormattedDateTime diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index c3754c6b9c..9fc9cfd02b 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -3,11 +3,14 @@ import { useDispatch, useSelector } from 'react-redux' import { Modal } from 'rdmo/core/assets/js/_bs53/components' import * as configActions from 'rdmo/core/assets/js/actions/configActions' +import { useFormattedDateTime } from 'rdmo/core/assets/js/hooks' +import { language } from 'rdmo/core/assets/js/utils' import { baseUrl } from 'rdmo/core/assets/js/utils/meta' import Select from 'rdmo/core/assets/js/components/forms/Select' import { navigateDashboard, updateProjectTask } from '../../actions/projectActions' +import { projectId } from '../../utils/meta' import { Tile } from '../helper' const Dashboard = () => { @@ -15,13 +18,21 @@ const Dashboard = () => { const config = useSelector(state => state.config) const perms = useSelector(state => state.project.project.project.permissions) ?? {} const issues = useSelector((state) => state.project.project.tasks) ?? [] + // Mock dates for testing + // issues.forEach((issue) => { + // issue.dates = [ + // ['2017-12-31', '2017-04-03'], + // ['2017-04-03'], + // ['2017-04-04'], + // ] + // }) // const allIssues = useSelector((state) => state.project.project.tasks) ?? [] const resolvedIssues = issues.filter((issue) => issue.resolve === true) console.log('resolved issues', resolvedIssues) // const issues = allIssues.filter((issue) => issue.resolve === true) // console.log('resolved issues', issues) - console.log('all issues', issues) + const statusOptions = [ { value: 'open', label: gettext('Open') }, { value: 'closed', label: gettext('Closed') }, @@ -30,8 +41,6 @@ const Dashboard = () => { const { showClosedTasks, showClosedRecommendations } = config - console.log('showClosedTasks', showClosedTasks) - console.log('showClosedRecommendations', showClosedRecommendations) const [selectedTaskIssue, setSelectedTaskIssue] = useState(null) const isClosed = (issue) => issue.status === 'closed' @@ -64,12 +73,15 @@ const Dashboard = () => { ).sort((a, b) => a.task.order - b.task.order) const toggleTaskDone = (issueId, currentStatus) => { - console.log('toggle issue', issueId) dispatch(updateProjectTask(issueId, { status: currentStatus === 'closed' ? 'open' : 'closed' })) } + const renderDate = (date) => ( + date.map((dateValue) => useFormattedDateTime(dateValue, language, 'dateOnly')).join(' - ') + ) + const renderIssueTiles = (visibleIssues) => (
{ @@ -102,10 +114,14 @@ const Dashboard = () => {
{issue.task.title}
-
- - {issue.dates?.[0] ?? ''} -
+ { + issue.dates?.length > 0 && ( +
+ + {renderDate(issue.dates[0])} +
+ ) + }
@@ -257,36 +273,55 @@ const Dashboard = () => { } } /> - { - selectedTaskIssue.questions?.length > 0 && ( -
-
{gettext('Origin')}
+
+
+ { + selectedTaskIssue.questions?.length > 0 && ( + <> +
{gettext('Questions')}
-
    - { - selectedTaskIssue.questions.map((question) => ( -
  • -
    {question.text}
    + { + selectedTaskIssue.questions.map((question) => ( +
    +
    {question.text}
    - { - question.pages?.map((page) => ( - - {page.title} - - )) - } -
  • - )) - } -
-
- ) - } -

{selectedTaskIssue.dates?.[0]}

+ { + question.pages?.map((page) => { + const url = `${baseUrl}/projects/${projectId}/interview/${page.id}/` + + return ( +
+ {url} +
+ ) + }) + } +
+ )) + } + + ) + } +
+ +
+ { + selectedTaskIssue.dates?.length > 0 && ( + <> +
{gettext('Dates')}
+ + { + selectedTaskIssue.dates.map((date, index) => ( +
+ {renderDate(date)} +
+ )) + } + + ) + } +
+
) } From aa11db0737c24e03c2cede9db760e244f2f6f932 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 11 Jun 2026 18:23:06 +0200 Subject: [PATCH 23/33] Add ProjectIssueTaskConditionSerializer, add attribute field to ProjectIssueQuestionSerializer --- .../js/project/components/areas/Dashboard.js | 47 ++++++++++++------- .../js/project/components/helper/Tile.js | 2 +- rdmo/projects/serializers/v1/__init__.py | 34 ++++++++------ 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js index 9fc9cfd02b..3cb4ed9424 100644 --- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js +++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js @@ -17,21 +17,19 @@ const Dashboard = () => { const dispatch = useDispatch() const config = useSelector(state => state.config) const perms = useSelector(state => state.project.project.project.permissions) ?? {} - const issues = useSelector((state) => state.project.project.tasks) ?? [] + + const allIssues = useSelector((state) => state.project.project.tasks) ?? [] + /* Show only issues that resolve */ + const issues = allIssues.filter((issue) => issue.resolve === true) + // Mock dates for testing // issues.forEach((issue) => { // issue.dates = [ - // ['2017-12-31', '2017-04-03'], + // ['2017-04-03', '2017-12-31'], // ['2017-04-03'], // ['2017-04-04'], // ] // }) - // const allIssues = useSelector((state) => state.project.project.tasks) ?? [] - const resolvedIssues = issues.filter((issue) => issue.resolve === true) - console.log('resolved issues', resolvedIssues) - // const issues = allIssues.filter((issue) => issue.resolve === true) - // console.log('resolved issues', issues) - console.log('all issues', issues) const statusOptions = [ { value: 'open', label: gettext('Open') }, @@ -56,6 +54,8 @@ const Dashboard = () => { getTaskType(issue) === 'task' ) + // console.log('task issues', taskIssues) + const visibleTaskIssues = taskIssues .filter((issue) => showClosedTasks || !isClosed(issue)) .sort((a, b) => Number(isClosed(a)) - Number(isClosed(b))) @@ -63,6 +63,7 @@ const Dashboard = () => { const recommendationIssues = issues.filter((issue) => getTaskType(issue) === 'recommendation' ) + // console.log('recommendation issues', recommendationIssues) const visibleRecommendationIssues = recommendationIssues .filter((issue) => showClosedRecommendations || !isClosed(issue)) @@ -82,7 +83,7 @@ const Dashboard = () => { date.map((dateValue) => useFormattedDateTime(dateValue, language, 'dateOnly')).join(' - ') ) - const renderIssueTiles = (visibleIssues) => ( + const renderTaskIssueTiles = (visibleIssues) => (
{ visibleIssues.map((issue) => { @@ -190,7 +191,7 @@ const Dashboard = () => { {gettext('Show closed tasks')}
- {renderIssueTiles(visibleTaskIssues)} + {renderTaskIssueTiles(visibleTaskIssues)} ) } @@ -216,7 +217,7 @@ const Dashboard = () => { {gettext('Show closed tasks')}
- {renderIssueTiles(visibleRecommendationIssues)} + {renderTaskIssueTiles(visibleRecommendationIssues)} ) } @@ -255,7 +256,7 @@ const Dashboard = () => { onClose={() => setSelectedTaskIssue(null)} size="modal-lg" > -

{selectedTaskIssue.id}

+ {/*

{selectedTaskIssue.id}

*/}

{selectedTaskIssue.task.text}

{/*

{selectedTaskIssue.status}

*/} { } />
-
+
0 ? 'col-md-8' : 'col-md-12'}> {/* questions */} { selectedTaskIssue.questions?.length > 0 && ( @@ -317,9 +316,9 @@ const Dashboard = () => { }
-
- { - selectedTaskIssue.dates?.length > 0 && ( + { + selectedTaskIssue.dates?.length > 0 && ( +
<>
{gettext('Dates')}
@@ -331,9 +330,9 @@ const Dashboard = () => { )) } - ) - } -
+
+ ) + }
) From a0ec5b1035a711b6bf22e12f8108f7fe825fc0bf Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Thu, 11 Jun 2026 19:34:22 +0200 Subject: [PATCH 25/33] Use models.TextChoices for RelationTypes and fix models and viewets accordingly --- rdmo/conditions/constants.py | 14 ++++++++++++ rdmo/conditions/models.py | 43 ++++++++++-------------------------- rdmo/conditions/viewsets.py | 3 ++- 3 files changed, 28 insertions(+), 32 deletions(-) create mode 100644 rdmo/conditions/constants.py diff --git a/rdmo/conditions/constants.py b/rdmo/conditions/constants.py new file mode 100644 index 0000000000..f489107d49 --- /dev/null +++ b/rdmo/conditions/constants.py @@ -0,0 +1,14 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class RelationTypes(models.TextChoices): + RELATION_EQUAL = 'eq', _('is equal to (==)') + RELATION_NOT_EQUAL = 'neq', _('is not equal to (!=)') + RELATION_CONTAINS = 'contains', _('contains') + RELATION_GREATER_THAN = 'gt', _('is greater than (>)') + RELATION_GREATER_THAN_EQUAL = 'gte', _('is greater than or equal (>=)') + RELATION_LESSER_THAN = 'lt', _('is lesser than (<)') + RELATION_LESSER_THAN_EQUAL = 'lte', _('is lesser than or equal (<=)') + RELATION_EMPTY = 'empty', _('is empty') + RELATION_NOT_EMPTY = 'notempty', _('is not empty') diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 04f1723ce3..3abb52d73c 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -6,29 +6,10 @@ from rdmo.core.utils import join_url from rdmo.domain.models import Attribute +from .constants import RelationTypes -class Condition(models.Model): - RELATION_EQUAL = 'eq' - RELATION_NOT_EQUAL = 'neq' - RELATION_CONTAINS = 'contains' - RELATION_GREATER_THAN = 'gt' - RELATION_GREATER_THAN_EQUAL = 'gte' - RELATION_LESSER_THAN = 'lt' - RELATION_LESSER_THAN_EQUAL = 'lte' - RELATION_EMPTY = 'empty' - RELATION_NOT_EMPTY = 'notempty' - RELATION_CHOICES = ( - (RELATION_EQUAL, 'is equal to (==)'), - (RELATION_NOT_EQUAL, 'is not equal to (!=)'), - (RELATION_CONTAINS, 'contains'), - (RELATION_GREATER_THAN, 'is greater than (>)'), - (RELATION_GREATER_THAN_EQUAL, 'is greater than or equal (>=)'), - (RELATION_LESSER_THAN, 'is lesser than (<)'), - (RELATION_LESSER_THAN_EQUAL, 'is lesser than or equal (<=)'), - (RELATION_EMPTY, 'is empty'), - (RELATION_NOT_EMPTY, 'is not empty'), - ) +class Condition(models.Model): uri = models.URLField( max_length=800, blank=True, @@ -67,7 +48,7 @@ class Condition(models.Model): help_text=_('The attribute of the value for this condition.') ) relation = models.CharField( - max_length=8, choices=RELATION_CHOICES, + max_length=8, choices=RelationTypes, verbose_name=_('Relation'), help_text=_('The relation this condition is using.') ) @@ -134,31 +115,31 @@ def resolve(self, values, set_prefix=None, set_index=None): set_prefix, set_index = rpartition[0], int(rpartition[2]) return self.resolve(values, set_prefix, set_index) - if self.relation == self.RELATION_EQUAL: + if self.relation == RelationTypes.RELATION_EQUAL: return self._resolve_equal(source_values) - elif self.relation == self.RELATION_NOT_EQUAL: + elif self.relation == RelationTypes.RELATION_NOT_EQUAL: return not self._resolve_equal(source_values) - elif self.relation == self.RELATION_CONTAINS: + elif self.relation == RelationTypes.RELATION_CONTAINS: return self._resolve_contains(source_values) - elif self.relation == self.RELATION_GREATER_THAN: + elif self.relation == RelationTypes.RELATION_GREATER_THAN: return self._resolve_greater_than(source_values) - elif self.relation == self.RELATION_GREATER_THAN_EQUAL: + elif self.relation == RelationTypes.RELATION_GREATER_THAN_EQUAL: return self._resolve_greater_than_equal(source_values) - elif self.relation == self.RELATION_LESSER_THAN: + elif self.relation == RelationTypes.RELATION_LESSER_THAN: return self._resolve_lesser_than(source_values) - elif self.relation == self.RELATION_LESSER_THAN_EQUAL: + elif self.relation == RelationTypes.RELATION_LESSER_THAN_EQUAL: return self._resolve_lesser_than_equal(source_values) - elif self.relation == self.RELATION_EMPTY: + elif self.relation == RelationTypes.RELATION_EMPTY: return not self._resolve_not_empty(source_values) - elif self.relation == self.RELATION_NOT_EMPTY: + elif self.relation == RelationTypes.RELATION_NOT_EMPTY: return self._resolve_not_empty(source_values) else: diff --git a/rdmo/conditions/viewsets.py b/rdmo/conditions/viewsets.py index ec10620e58..609ed0db84 100644 --- a/rdmo/conditions/viewsets.py +++ b/rdmo/conditions/viewsets.py @@ -11,6 +11,7 @@ from rdmo.core.utils import is_truthy, render_to_format from rdmo.core.views import ChoicesViewSet +from .constants import RelationTypes from .models import Condition from .renderers import ConditionRenderer from .serializers.export import ConditionExportSerializer @@ -77,4 +78,4 @@ def get_export_renderer_context(self, request): class RelationViewSet(ChoicesViewSet): permission_classes = (IsAuthenticated, ) - queryset = Condition.RELATION_CHOICES + queryset = RelationTypes.choices From 42f7beb6551b9309c940563d80cc0efac89d638f Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 15 Jun 2026 11:12:55 +0200 Subject: [PATCH 26/33] Fix typo --- rdmo/conditions/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rdmo/conditions/constants.py b/rdmo/conditions/constants.py index f489107d49..e158e78abc 100644 --- a/rdmo/conditions/constants.py +++ b/rdmo/conditions/constants.py @@ -8,7 +8,7 @@ class RelationTypes(models.TextChoices): RELATION_CONTAINS = 'contains', _('contains') RELATION_GREATER_THAN = 'gt', _('is greater than (>)') RELATION_GREATER_THAN_EQUAL = 'gte', _('is greater than or equal (>=)') - RELATION_LESSER_THAN = 'lt', _('is lesser than (<)') - RELATION_LESSER_THAN_EQUAL = 'lte', _('is lesser than or equal (<=)') + RELATION_LESS_THAN = 'lt', _('is less than (<)') + RELATION_LESS_THAN_EQUAL = 'lte', _('is less than or equal (<=)') RELATION_EMPTY = 'empty', _('is empty') RELATION_NOT_EMPTY = 'notempty', _('is not empty') From 938f6703191ac24804041bc3e0618cfb5043e837 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 15 Jun 2026 11:23:33 +0200 Subject: [PATCH 27/33] Fix typo again --- rdmo/conditions/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 3abb52d73c..712cb78da1 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -130,11 +130,11 @@ def resolve(self, values, set_prefix=None, set_index=None): elif self.relation == RelationTypes.RELATION_GREATER_THAN_EQUAL: return self._resolve_greater_than_equal(source_values) - elif self.relation == RelationTypes.RELATION_LESSER_THAN: - return self._resolve_lesser_than(source_values) + elif self.relation == RelationTypes.RELATION_LESS_THAN: + return self._resolve_less_than(source_values) - elif self.relation == RelationTypes.RELATION_LESSER_THAN_EQUAL: - return self._resolve_lesser_than_equal(source_values) + elif self.relation == RelationTypes.RELATION_LESS_THAN_EQUAL: + return self._resolve_less_than_equal(source_values) elif self.relation == RelationTypes.RELATION_EMPTY: return not self._resolve_not_empty(source_values) From 18ea762eb0e81c1e2d41b90003d0eff1c0e62e1b Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 15 Jun 2026 11:29:18 +0200 Subject: [PATCH 28/33] Fix test and typo again --- rdmo/conditions/models.py | 4 ++-- rdmo/projects/tests/test_navigation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 712cb78da1..34b66979ad 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -186,7 +186,7 @@ def _resolve_greater_than_equal(self, values): return False - def _resolve_lesser_than(self, values): + def _resolve_less_than(self, values): for value in values: try: @@ -197,7 +197,7 @@ def _resolve_lesser_than(self, values): return False - def _resolve_lesser_than_equal(self, values): + def _resolve_less_than_equal(self, values): for value in values: try: diff --git a/rdmo/projects/tests/test_navigation.py b/rdmo/projects/tests/test_navigation.py index 9352971fd5..28da65637f 100644 --- a/rdmo/projects/tests/test_navigation.py +++ b/rdmo/projects/tests/test_navigation.py @@ -31,8 +31,8 @@ 'http://example.com/terms/questions/catalog/conditions/text_equal': (1, 1, True), 'http://example.com/terms/questions/catalog/conditions/text_greater_than': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/text_greater_than_equal': (0, 0, False), - 'http://example.com/terms/questions/catalog/conditions/text_lesser_than': (0, 0, False), - 'http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal': (0, 0, False), + 'http://example.com/terms/questions/catalog/conditions/text_less_than': (0, 0, False), + 'http://example.com/terms/questions/catalog/conditions/text_less_than_equal': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/text_not_empty': (1, 1, True), 'http://example.com/terms/questions/catalog/conditions/text_not_equal': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/option_empty': (0, 0, False), From 729395c80ac80d7b9430fe9cdcd80b5721ce4af2 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 15 Jun 2026 12:19:47 +0200 Subject: [PATCH 29/33] Remove wrong fixes --- rdmo/conditions/models.py | 8 ++++---- rdmo/projects/tests/test_navigation.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 34b66979ad..43001b8142 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -131,10 +131,10 @@ def resolve(self, values, set_prefix=None, set_index=None): return self._resolve_greater_than_equal(source_values) elif self.relation == RelationTypes.RELATION_LESS_THAN: - return self._resolve_less_than(source_values) + return self._resolve_lesser_than(source_values) elif self.relation == RelationTypes.RELATION_LESS_THAN_EQUAL: - return self._resolve_less_than_equal(source_values) + return self._resolve_lesser_than_equal(source_values) elif self.relation == RelationTypes.RELATION_EMPTY: return not self._resolve_not_empty(source_values) @@ -186,7 +186,7 @@ def _resolve_greater_than_equal(self, values): return False - def _resolve_less_than(self, values): + def _resolve_lesser_than(self, values): for value in values: try: @@ -197,7 +197,7 @@ def _resolve_less_than(self, values): return False - def _resolve_less_than_equal(self, values): + def _resolve_lesser_than_equal(self, values): for value in values: try: diff --git a/rdmo/projects/tests/test_navigation.py b/rdmo/projects/tests/test_navigation.py index 28da65637f..9352971fd5 100644 --- a/rdmo/projects/tests/test_navigation.py +++ b/rdmo/projects/tests/test_navigation.py @@ -31,8 +31,8 @@ 'http://example.com/terms/questions/catalog/conditions/text_equal': (1, 1, True), 'http://example.com/terms/questions/catalog/conditions/text_greater_than': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/text_greater_than_equal': (0, 0, False), - 'http://example.com/terms/questions/catalog/conditions/text_less_than': (0, 0, False), - 'http://example.com/terms/questions/catalog/conditions/text_less_than_equal': (0, 0, False), + 'http://example.com/terms/questions/catalog/conditions/text_lesser_than': (0, 0, False), + 'http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/text_not_empty': (1, 1, True), 'http://example.com/terms/questions/catalog/conditions/text_not_equal': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/option_empty': (0, 0, False), From c75476fa6cf3f5bb46c75708b0e0a937c08fbbe3 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 15 Jun 2026 12:53:42 +0200 Subject: [PATCH 30/33] Leave constants the same --- rdmo/conditions/constants.py | 4 ++-- rdmo/conditions/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rdmo/conditions/constants.py b/rdmo/conditions/constants.py index e158e78abc..d92b09c80f 100644 --- a/rdmo/conditions/constants.py +++ b/rdmo/conditions/constants.py @@ -8,7 +8,7 @@ class RelationTypes(models.TextChoices): RELATION_CONTAINS = 'contains', _('contains') RELATION_GREATER_THAN = 'gt', _('is greater than (>)') RELATION_GREATER_THAN_EQUAL = 'gte', _('is greater than or equal (>=)') - RELATION_LESS_THAN = 'lt', _('is less than (<)') - RELATION_LESS_THAN_EQUAL = 'lte', _('is less than or equal (<=)') + RELATION_LESSER_THAN = 'lt', _('is less than (<)') + RELATION_LESSER_THAN_EQUAL = 'lte', _('is less than or equal (<=)') RELATION_EMPTY = 'empty', _('is empty') RELATION_NOT_EMPTY = 'notempty', _('is not empty') diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 43001b8142..3abb52d73c 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -130,10 +130,10 @@ def resolve(self, values, set_prefix=None, set_index=None): elif self.relation == RelationTypes.RELATION_GREATER_THAN_EQUAL: return self._resolve_greater_than_equal(source_values) - elif self.relation == RelationTypes.RELATION_LESS_THAN: + elif self.relation == RelationTypes.RELATION_LESSER_THAN: return self._resolve_lesser_than(source_values) - elif self.relation == RelationTypes.RELATION_LESS_THAN_EQUAL: + elif self.relation == RelationTypes.RELATION_LESSER_THAN_EQUAL: return self._resolve_lesser_than_equal(source_values) elif self.relation == RelationTypes.RELATION_EMPTY: From f842f534de4a4864eb7ed7f7aecab6ead6723ebe Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Mon, 15 Jun 2026 13:55:29 +0200 Subject: [PATCH 31/33] Add migrations --- rdmo/conditions/constants.py | 4 ++-- .../0026_alter_condition_relation.py | 18 ++++++++++++++++++ rdmo/conditions/models.py | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 rdmo/conditions/migrations/0026_alter_condition_relation.py diff --git a/rdmo/conditions/constants.py b/rdmo/conditions/constants.py index d92b09c80f..e158e78abc 100644 --- a/rdmo/conditions/constants.py +++ b/rdmo/conditions/constants.py @@ -8,7 +8,7 @@ class RelationTypes(models.TextChoices): RELATION_CONTAINS = 'contains', _('contains') RELATION_GREATER_THAN = 'gt', _('is greater than (>)') RELATION_GREATER_THAN_EQUAL = 'gte', _('is greater than or equal (>=)') - RELATION_LESSER_THAN = 'lt', _('is less than (<)') - RELATION_LESSER_THAN_EQUAL = 'lte', _('is less than or equal (<=)') + RELATION_LESS_THAN = 'lt', _('is less than (<)') + RELATION_LESS_THAN_EQUAL = 'lte', _('is less than or equal (<=)') RELATION_EMPTY = 'empty', _('is empty') RELATION_NOT_EMPTY = 'notempty', _('is not empty') diff --git a/rdmo/conditions/migrations/0026_alter_condition_relation.py b/rdmo/conditions/migrations/0026_alter_condition_relation.py new file mode 100644 index 0000000000..6d6baef4dd --- /dev/null +++ b/rdmo/conditions/migrations/0026_alter_condition_relation.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.14 on 2026-06-15 11:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('conditions', '0025_alter_condition_uri_path'), + ] + + operations = [ + migrations.AlterField( + model_name='condition', + name='relation', + field=models.CharField(choices=[('eq', 'is equal to (==)'), ('neq', 'is not equal to (!=)'), ('contains', 'contains'), ('gt', 'is greater than (>)'), ('gte', 'is greater than or equal (>=)'), ('lt', 'is less than (<)'), ('lte', 'is less than or equal (<=)'), ('empty', 'is empty'), ('notempty', 'is not empty')], help_text='The relation this condition is using.', max_length=8, verbose_name='Relation'), + ), + ] diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 3abb52d73c..43001b8142 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -130,10 +130,10 @@ def resolve(self, values, set_prefix=None, set_index=None): elif self.relation == RelationTypes.RELATION_GREATER_THAN_EQUAL: return self._resolve_greater_than_equal(source_values) - elif self.relation == RelationTypes.RELATION_LESSER_THAN: + elif self.relation == RelationTypes.RELATION_LESS_THAN: return self._resolve_lesser_than(source_values) - elif self.relation == RelationTypes.RELATION_LESSER_THAN_EQUAL: + elif self.relation == RelationTypes.RELATION_LESS_THAN_EQUAL: return self._resolve_lesser_than_equal(source_values) elif self.relation == RelationTypes.RELATION_EMPTY: From 25adc8bec69726b7598309f6987c79a75fe366dc Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Tue, 16 Jun 2026 09:54:29 +0200 Subject: [PATCH 32/33] Replace lesser_than with less_than --- rdmo/conditions/models.py | 8 ++--- rdmo/projects/tests/test_navigation.py | 4 +-- testing/export/project.html | 6 ++-- testing/fixtures/conditions.json | 8 ++--- testing/fixtures/questions.json | 24 ++++++------- testing/import/catalogs.json | 36 +++++++++---------- testing/xml/elements/catalogs.xml | 36 +++++++++---------- testing/xml/elements/conditions.xml | 8 ++--- testing/xml/elements/legacy/conditions.xml | 8 ++--- testing/xml/elements/legacy/questions.xml | 40 +++++++++++----------- testing/xml/elements/pages.xml | 32 ++++++++--------- testing/xml/elements/questions.xml | 16 ++++----- testing/xml/elements/sections.xml | 36 +++++++++---------- 13 files changed, 131 insertions(+), 131 deletions(-) diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py index 43001b8142..34b66979ad 100644 --- a/rdmo/conditions/models.py +++ b/rdmo/conditions/models.py @@ -131,10 +131,10 @@ def resolve(self, values, set_prefix=None, set_index=None): return self._resolve_greater_than_equal(source_values) elif self.relation == RelationTypes.RELATION_LESS_THAN: - return self._resolve_lesser_than(source_values) + return self._resolve_less_than(source_values) elif self.relation == RelationTypes.RELATION_LESS_THAN_EQUAL: - return self._resolve_lesser_than_equal(source_values) + return self._resolve_less_than_equal(source_values) elif self.relation == RelationTypes.RELATION_EMPTY: return not self._resolve_not_empty(source_values) @@ -186,7 +186,7 @@ def _resolve_greater_than_equal(self, values): return False - def _resolve_lesser_than(self, values): + def _resolve_less_than(self, values): for value in values: try: @@ -197,7 +197,7 @@ def _resolve_lesser_than(self, values): return False - def _resolve_lesser_than_equal(self, values): + def _resolve_less_than_equal(self, values): for value in values: try: diff --git a/rdmo/projects/tests/test_navigation.py b/rdmo/projects/tests/test_navigation.py index 9352971fd5..28da65637f 100644 --- a/rdmo/projects/tests/test_navigation.py +++ b/rdmo/projects/tests/test_navigation.py @@ -31,8 +31,8 @@ 'http://example.com/terms/questions/catalog/conditions/text_equal': (1, 1, True), 'http://example.com/terms/questions/catalog/conditions/text_greater_than': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/text_greater_than_equal': (0, 0, False), - 'http://example.com/terms/questions/catalog/conditions/text_lesser_than': (0, 0, False), - 'http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal': (0, 0, False), + 'http://example.com/terms/questions/catalog/conditions/text_less_than': (0, 0, False), + 'http://example.com/terms/questions/catalog/conditions/text_less_than_equal': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/text_not_empty': (1, 1, True), 'http://example.com/terms/questions/catalog/conditions/text_not_equal': (0, 0, False), 'http://example.com/terms/questions/catalog/conditions/option_empty': (0, 0, False), diff --git a/testing/export/project.html b/testing/export/project.html index 2094cb854e..033f10fc6f 100644 --- a/testing/export/project.html +++ b/testing/export/project.html @@ -531,9 +531,9 @@

Text IV

Text V

text_greater_than_equal?

Text VI

-

text_lesser_than?

+

text_less_than?

Text VII

-

text_lesser_than_equal?

+

text_less_than_equal?

Text VIII

text_not_empty?

@@ -683,4 +683,4 @@

Email

Phone

Phone

- \ No newline at end of file + diff --git a/testing/fixtures/conditions.json b/testing/fixtures/conditions.json index 8d4172436a..17a919091c 100644 --- a/testing/fixtures/conditions.json +++ b/testing/fixtures/conditions.json @@ -83,9 +83,9 @@ "model": "conditions.condition", "pk": 6, "fields": { - "uri": "http://example.com/terms/conditions/text_lesser_than_0", + "uri": "http://example.com/terms/conditions/text_less_than_0", "uri_prefix": "http://example.com/terms", - "uri_path": "text_lesser_than_0", + "uri_path": "text_less_than_0", "comment": "", "locked": false, "source": 81, @@ -99,9 +99,9 @@ "model": "conditions.condition", "pk": 7, "fields": { - "uri": "http://example.com/terms/conditions/text_lesser_than_equal_0", + "uri": "http://example.com/terms/conditions/text_less_than_equal_0", "uri_prefix": "http://example.com/terms", - "uri_path": "text_lesser_than_equal_0", + "uri_path": "text_less_than_equal_0", "comment": "", "locked": false, "source": 81, diff --git a/testing/fixtures/questions.json b/testing/fixtures/questions.json index 99d5a3c1c4..ddd6370c8f 100644 --- a/testing/fixtures/questions.json +++ b/testing/fixtures/questions.json @@ -1108,9 +1108,9 @@ "fields": { "created": "2018-07-23T12:03:46.973Z", "updated": "2018-11-29T15:16:13.956Z", - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than", + "uri_path": "catalog/conditions/text_less_than", "comment": "", "locked": false, "attribute": null, @@ -1147,9 +1147,9 @@ "fields": { "created": "2018-07-23T12:04:00.417Z", "updated": "2018-11-29T15:16:21.769Z", - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than_equal", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than_equal", + "uri_path": "catalog/conditions/text_less_than_equal", "comment": "", "locked": false, "attribute": null, @@ -5930,9 +5930,9 @@ "fields": { "created": "2018-07-23T12:03:46.973Z", "updated": "2018-11-29T15:16:13.959Z", - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than/text_lesser_than", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than/text_less_than", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than/text_lesser_than", + "uri_path": "catalog/conditions/text_less_than/text_less_than", "comment": "", "locked": false, "attribute": 81, @@ -5943,8 +5943,8 @@ "help_lang3": "", "help_lang4": "", "help_lang5": "", - "text_lang1": "text_lesser_than?", - "text_lang2": "text_lesser_than?", + "text_lang1": "text_less_than?", + "text_lang2": "text_less_than?", "text_lang3": "", "text_lang4": "", "text_lang5": "", @@ -5978,9 +5978,9 @@ "fields": { "created": "2018-07-23T12:04:00.417Z", "updated": "2018-11-29T15:16:21.771Z", - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal/text_lesser_than_equal", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than_equal/text_less_than_equal", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than_equal/text_lesser_than_equal", + "uri_path": "catalog/conditions/text_less_than_equal/text_less_than_equal", "comment": "", "locked": false, "attribute": 81, @@ -5991,8 +5991,8 @@ "help_lang3": "", "help_lang4": "", "help_lang5": "", - "text_lang1": "text_lesser_than_equal?", - "text_lang2": "text_lesser_than_equal?", + "text_lang1": "text_less_than_equal?", + "text_lang2": "text_less_than_equal?", "text_lang3": "", "text_lang4": "", "text_lang5": "", diff --git a/testing/import/catalogs.json b/testing/import/catalogs.json index 80fe497edb..7db03c43f5 100644 --- a/testing/import/catalogs.json +++ b/testing/import/catalogs.json @@ -3501,11 +3501,11 @@ ] }, { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than/text_lesser_than", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than/text_less_than", "type": "questions", "model": "questions.question", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than/text_lesser_than", + "uri_path": "catalog/conditions/text_less_than/text_less_than", "comment": null, "attribute": { "uri": "http://example.com/terms/domain/conditions/text", @@ -3514,12 +3514,12 @@ "is_collection": "False", "is_optional": "False", "help_en": null, - "text_en": "text_lesser_than?", + "text_en": "text_less_than?", "default_text_en": null, "verbose_name_en": null, "verbose_name_plural_en": null, "help_de": null, - "text_de": "text_lesser_than?", + "text_de": "text_less_than?", "default_text_de": null, "verbose_name_de": null, "verbose_name_plural_de": null, @@ -3536,11 +3536,11 @@ "conditions": null }, { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than", "type": "pages", "model": "questions.page", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than", + "uri_path": "catalog/conditions/text_less_than", "comment": null, "attribute": null, "is_collection": "False", @@ -3555,24 +3555,24 @@ "questionsets": null, "questions": [ { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than/text_lesser_than", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than/text_less_than", "type": "questions", "order": "0" } ], "conditions": [ { - "uri": "http://example.com/terms/conditions/text_lesser_than_0", + "uri": "http://example.com/terms/conditions/text_less_than_0", "type": "conditions" } ] }, { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal/text_lesser_than_equal", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than_equal/text_less_than_equal", "type": "questions", "model": "questions.question", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than_equal/text_lesser_than_equal", + "uri_path": "catalog/conditions/text_less_than_equal/text_less_than_equal", "comment": null, "attribute": { "uri": "http://example.com/terms/domain/conditions/text", @@ -3581,12 +3581,12 @@ "is_collection": "False", "is_optional": "False", "help_en": null, - "text_en": "text_lesser_than_equal?", + "text_en": "text_less_than_equal?", "default_text_en": null, "verbose_name_en": null, "verbose_name_plural_en": null, "help_de": null, - "text_de": "text_lesser_than_equal?", + "text_de": "text_less_than_equal?", "default_text_de": null, "verbose_name_de": null, "verbose_name_plural_de": null, @@ -3603,11 +3603,11 @@ "conditions": null }, { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than_equal", "type": "pages", "model": "questions.page", "uri_prefix": "http://example.com/terms", - "uri_path": "catalog/conditions/text_lesser_than_equal", + "uri_path": "catalog/conditions/text_less_than_equal", "comment": null, "attribute": null, "is_collection": "False", @@ -3622,14 +3622,14 @@ "questionsets": null, "questions": [ { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal/text_lesser_than_equal", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than_equal/text_less_than_equal", "type": "questions", "order": "0" } ], "conditions": [ { - "uri": "http://example.com/terms/conditions/text_lesser_than_equal_0", + "uri": "http://example.com/terms/conditions/text_less_than_equal_0", "type": "conditions" } ] @@ -4529,12 +4529,12 @@ "order": "15" }, { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than", "type": "pages", "order": "16" }, { - "uri": "http://example.com/terms/questions/catalog/conditions/text_lesser_than_equal", + "uri": "http://example.com/terms/questions/catalog/conditions/text_less_than_equal", "type": "pages", "order": "17" }, diff --git a/testing/xml/elements/catalogs.xml b/testing/xml/elements/catalogs.xml index 21a3d38683..478da5fafa 100644 --- a/testing/xml/elements/catalogs.xml +++ b/testing/xml/elements/catalogs.xml @@ -2272,8 +2272,8 @@ - - + + @@ -2622,9 +2622,9 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than + catalog/conditions/text_less_than False @@ -2638,26 +2638,26 @@ - + - + - + http://example.com/terms - catalog/conditions/text_lesser_than/text_lesser_than + catalog/conditions/text_less_than/text_less_than False False - text_lesser_than? + text_less_than? - text_lesser_than? + text_less_than? @@ -2673,9 +2673,9 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than_equal + catalog/conditions/text_less_than_equal False @@ -2689,26 +2689,26 @@ - + - + - + http://example.com/terms - catalog/conditions/text_lesser_than_equal/text_lesser_than_equal + catalog/conditions/text_less_than_equal/text_less_than_equal False False - text_lesser_than_equal? + text_less_than_equal? - text_lesser_than_equal? + text_less_than_equal? diff --git a/testing/xml/elements/conditions.xml b/testing/xml/elements/conditions.xml index 8e248e570f..cd01654c39 100644 --- a/testing/xml/elements/conditions.xml +++ b/testing/xml/elements/conditions.xml @@ -99,18 +99,18 @@ 0 - + http://example.com/terms - text_lesser_than_0 + text_less_than_0 lt 0 - + http://example.com/terms - text_lesser_than_equal_0 + text_less_than_equal_0 lte diff --git a/testing/xml/elements/legacy/conditions.xml b/testing/xml/elements/legacy/conditions.xml index f98ca16150..b0473b0395 100644 --- a/testing/xml/elements/legacy/conditions.xml +++ b/testing/xml/elements/legacy/conditions.xml @@ -99,18 +99,18 @@ 0 - + http://example.com/terms - text_lesser_than_0 + text_less_than_0 lt 0 - + http://example.com/terms - text_lesser_than_equal_0 + text_less_than_equal_0 lte diff --git a/testing/xml/elements/legacy/questions.xml b/testing/xml/elements/legacy/questions.xml index ab66d78ad4..ea11a2c43d 100644 --- a/testing/xml/elements/legacy/questions.xml +++ b/testing/xml/elements/legacy/questions.xml @@ -2734,10 +2734,10 @@ - + http://example.com/terms - text_lesser_than - catalog/conditions/text_lesser_than + text_less_than + catalog/conditions/text_less_than
@@ -2753,26 +2753,26 @@ - + - + http://example.com/terms - text_lesser_than - catalog/conditions/text_lesser_than/text_lesser_than + text_less_than + catalog/conditions/text_less_than/text_less_than - + False False 0 - text_lesser_than? + text_less_than? - text_lesser_than? + text_less_than? @@ -2788,10 +2788,10 @@ - + http://example.com/terms - text_lesser_than_equal - catalog/conditions/text_lesser_than_equal + text_less_than_equal + catalog/conditions/text_less_than_equal
@@ -2807,26 +2807,26 @@ - + - + http://example.com/terms - text_lesser_than_equal - catalog/conditions/text_lesser_than_equal/text_lesser_than_equal + text_less_than_equal + catalog/conditions/text_less_than_equal/text_less_than_equal - + False False 0 - text_lesser_than_equal? + text_less_than_equal? - text_lesser_than_equal? + text_less_than_equal? diff --git a/testing/xml/elements/pages.xml b/testing/xml/elements/pages.xml index a78d1c1e15..bb5206cc60 100644 --- a/testing/xml/elements/pages.xml +++ b/testing/xml/elements/pages.xml @@ -1493,9 +1493,9 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than + catalog/conditions/text_less_than False @@ -1509,26 +1509,26 @@ - + - + - + http://example.com/terms - catalog/conditions/text_lesser_than/text_lesser_than + catalog/conditions/text_less_than/text_less_than False False - text_lesser_than? + text_less_than? - text_lesser_than? + text_less_than? @@ -1544,9 +1544,9 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than_equal + catalog/conditions/text_less_than_equal False @@ -1560,26 +1560,26 @@ - + - + - + http://example.com/terms - catalog/conditions/text_lesser_than_equal/text_lesser_than_equal + catalog/conditions/text_less_than_equal/text_less_than_equal False False - text_lesser_than_equal? + text_less_than_equal? - text_lesser_than_equal? + text_less_than_equal? diff --git a/testing/xml/elements/questions.xml b/testing/xml/elements/questions.xml index c55ca56dd2..bc0c565563 100644 --- a/testing/xml/elements/questions.xml +++ b/testing/xml/elements/questions.xml @@ -924,20 +924,20 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than_equal/text_lesser_than_equal + catalog/conditions/text_less_than_equal/text_less_than_equal False False - text_lesser_than_equal? + text_less_than_equal? - text_lesser_than_equal? + text_less_than_equal? @@ -953,20 +953,20 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than/text_lesser_than + catalog/conditions/text_less_than/text_less_than False False - text_lesser_than? + text_less_than? - text_lesser_than? + text_less_than? diff --git a/testing/xml/elements/sections.xml b/testing/xml/elements/sections.xml index 81d6c5ad29..d55a8c5d13 100644 --- a/testing/xml/elements/sections.xml +++ b/testing/xml/elements/sections.xml @@ -722,8 +722,8 @@ - - + + @@ -1072,9 +1072,9 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than + catalog/conditions/text_less_than False @@ -1088,26 +1088,26 @@ - + - + - + http://example.com/terms - catalog/conditions/text_lesser_than/text_lesser_than + catalog/conditions/text_less_than/text_less_than False False - text_lesser_than? + text_less_than? - text_lesser_than? + text_less_than? @@ -1123,9 +1123,9 @@ - + http://example.com/terms - catalog/conditions/text_lesser_than_equal + catalog/conditions/text_less_than_equal False @@ -1139,26 +1139,26 @@ - + - + - + http://example.com/terms - catalog/conditions/text_lesser_than_equal/text_lesser_than_equal + catalog/conditions/text_less_than_equal/text_less_than_equal False False - text_lesser_than_equal? + text_less_than_equal? - text_lesser_than_equal? + text_less_than_equal? From 952481cf2e0edc882984c45f7e04cd868f208c35 Mon Sep 17 00:00:00 2001 From: Claudia Malzer Date: Tue, 16 Jun 2026 11:26:40 +0200 Subject: [PATCH 33/33] Add translation --- rdmo/conditions/constants.py | 4 ++-- rdmo/conditions/migrations/0026_alter_condition_relation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rdmo/conditions/constants.py b/rdmo/conditions/constants.py index e158e78abc..025428b2c6 100644 --- a/rdmo/conditions/constants.py +++ b/rdmo/conditions/constants.py @@ -7,8 +7,8 @@ class RelationTypes(models.TextChoices): RELATION_NOT_EQUAL = 'neq', _('is not equal to (!=)') RELATION_CONTAINS = 'contains', _('contains') RELATION_GREATER_THAN = 'gt', _('is greater than (>)') - RELATION_GREATER_THAN_EQUAL = 'gte', _('is greater than or equal (>=)') + RELATION_GREATER_THAN_EQUAL = 'gte', _('is greater than or equal to (>=)') RELATION_LESS_THAN = 'lt', _('is less than (<)') - RELATION_LESS_THAN_EQUAL = 'lte', _('is less than or equal (<=)') + RELATION_LESS_THAN_EQUAL = 'lte', _('is less than or equal to (<=)') RELATION_EMPTY = 'empty', _('is empty') RELATION_NOT_EMPTY = 'notempty', _('is not empty') diff --git a/rdmo/conditions/migrations/0026_alter_condition_relation.py b/rdmo/conditions/migrations/0026_alter_condition_relation.py index 6d6baef4dd..36909b8414 100644 --- a/rdmo/conditions/migrations/0026_alter_condition_relation.py +++ b/rdmo/conditions/migrations/0026_alter_condition_relation.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.14 on 2026-06-15 11:51 +# Generated by Django 5.2.14 on 2026-06-16 09:25 from django.db import migrations, models @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='condition', name='relation', - field=models.CharField(choices=[('eq', 'is equal to (==)'), ('neq', 'is not equal to (!=)'), ('contains', 'contains'), ('gt', 'is greater than (>)'), ('gte', 'is greater than or equal (>=)'), ('lt', 'is less than (<)'), ('lte', 'is less than or equal (<=)'), ('empty', 'is empty'), ('notempty', 'is not empty')], help_text='The relation this condition is using.', max_length=8, verbose_name='Relation'), + field=models.CharField(choices=[('eq', 'is equal to (==)'), ('neq', 'is not equal to (!=)'), ('contains', 'contains'), ('gt', 'is greater than (>)'), ('gte', 'is greater than or equal to (>=)'), ('lt', 'is less than (<)'), ('lte', 'is less than or equal to (<=)'), ('empty', 'is empty'), ('notempty', 'is not empty')], help_text='The relation this condition is using.', max_length=8, verbose_name='Relation'), ), ]