Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tp2000 1471 task workflow #1298

Draft
wants to merge 73 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
340439c
App initial commit.
paulpepper-trade Aug 29, 2024
301644a
TP2000-1472 Revise task-related models (#1288)
dalecannon Sep 19, 2024
ee4871d
TP2000-1473 Register task-related models in admin site (#1289)
dalecannon Sep 20, 2024
9faac8b
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Sep 20, 2024
0595097
Merge branch 'master' into TP2000-1471--task-workflow
dalecannon Oct 29, 2024
de2733c
Implement prototype UI for tasks (#1308)
dalecannon Oct 31, 2024
ddfea35
TP2000-1487: Create Subtask Form (#1319)
marya-shariq Nov 11, 2024
ead85bc
TP2000-1540, TP2000-1541, TP2000-1546 & TP2000-1553 queues, workflows…
paulpepper-trade Nov 12, 2024
4d7dda9
TP2000-1543 Prototype workflow template detail view (#1322)
dalecannon Nov 12, 2024
77760e5
PR template tweaks
paulpepper-trade Nov 12, 2024
80c0b80
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Nov 12, 2024
5ec8f38
Black formatting
paulpepper-trade Nov 12, 2024
d1d18ed
TP2000-1545 Add task template reordering capability to workflow temp…
dalecannon Nov 14, 2024
84e3c16
TP2000-48 & TP2000-1549 Task template create and detail views (#1326)
paulpepper-trade Nov 15, 2024
f2bead0
TP2000-1551 Task template update views and forms (#1327)
paulpepper-trade Nov 15, 2024
d103846
Fix typo in macro import
paulpepper-trade Nov 15, 2024
d0466d4
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Nov 15, 2024
01db672
TP2000-1569 Embellish create subtask journey (#1325)
LaurenMullally Nov 18, 2024
baf7f61
Add support view support for task workflow deletion (#1329)
paulpepper-trade Nov 18, 2024
f4f351c
TP2000-1544 Prototype workflow template create & update views (#1328)
dalecannon Nov 18, 2024
bc787f8
Fix double quotes lint error from create subtask form pr
LaurenMullally Nov 18, 2024
a28e7bd
TP2000-1573 Implement workflow template delete view (#1330)
dalecannon Nov 19, 2024
2ff7858
Tp 2000 1489 delete subtask (#1332)
marya-shariq Nov 21, 2024
e9a4546
deleted comments
marya-shariq Nov 21, 2024
993ec64
TP2000-1488 Edit Subtasks (#1334)
LaurenMullally Nov 25, 2024
f55394a
Tp2000 1589 - temporary list views tile (#1338)
marya-shariq Nov 25, 2024
5884cfc
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Nov 25, 2024
7dfe059
TP2000-1590 TaskWorkflow summary info support (#1337)
paulpepper-trade Nov 25, 2024
997d36a
Update TaskWorkflowTemplate.create_task_workflow() method and unit te…
dalecannon Nov 28, 2024
4540d83
TP2000-1556 Implement workflow create view (#1340)
dalecannon Nov 28, 2024
2ea9f5c
TP2000-1555 Implement workflow detail view (#1341)
dalecannon Nov 28, 2024
708944c
TP2000-1602 Implement workflow delete view (#1342)
dalecannon Nov 28, 2024
02044ba
Factor out workflow item management helper functions into view mixin …
dalecannon Dec 4, 2024
2bafa60
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Dec 5, 2024
442fd3e
Tp2000 1592 workflow template list view (#1348)
marya-shariq Dec 5, 2024
363bced
TP2000-1582 Enable customisable queue field name on QueueItem subcla…
dalecannon Dec 6, 2024
ec10b14
Include user param in form.save() call (#1350)
paulpepper-trade Dec 6, 2024
3e67a87
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Dec 9, 2024
821d5e8
TP2000-1557 Implement workflow edit view (#1347)
dalecannon Dec 10, 2024
d9937d1
TP2000-1580 Task workflow combined list view pt2 (#1353)
paulpepper-trade Dec 10, 2024
31cafe9
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Dec 10, 2024
206cc7d
TP2000-1615 Various errors (#1357)
paulpepper-trade Dec 17, 2024
ab3854f
Tp2000 1594 ordering of workflow templates list view (#1356)
marya-shariq Dec 17, 2024
10f14de
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Dec 17, 2024
dd663ac
Add migrations.
paulpepper-trade Dec 17, 2024
5824196
Fix failing test (#1362)
paulpepper-trade Dec 17, 2024
4f054b6
Fix UI bug (#1364)
paulpepper-trade Dec 18, 2024
d1c3505
TP2000-1600 Add Subtask filter to Task admin view (#1344)
LaurenMullally Dec 19, 2024
1f94a81
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Dec 23, 2024
82a1f69
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Jan 2, 2025
9b99475
Add simple TaskWorkflowAdmin view (#1373)
LaurenMullally Jan 3, 2025
755fac6
Tp2000 1542 workflow template admin support (#1374)
marya-shariq Jan 9, 2025
69e65e5
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Jan 10, 2025
c6c0a43
TP2000-1652 Enhance workflow, workflow template admin views (#1381)
dalecannon Jan 15, 2025
b60a3c9
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Jan 15, 2025
6d9e368
TP2000-1658 Correct usage of select_for_update() in QueueItem instan…
dalecannon Jan 16, 2025
676edc5
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Jan 23, 2025
12f4297
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Feb 5, 2025
64a4db5
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Feb 5, 2025
fab1fab
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Feb 11, 2025
2064d09
TP2000-1583 Add task assignee update support (#1407)
paulpepper-trade Feb 11, 2025
6590627
Renumber clashing migration
paulpepper-trade Feb 11, 2025
50d8c6a
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Feb 11, 2025
761d076
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Feb 11, 2025
16ba584
Tp2000 1616 workflow task create view (#1363)
marya-shariq Feb 12, 2025
b1401a8
TP2000-1653 Enhance AutoCompleteField to support a broader range of …
dalecannon Feb 12, 2025
56f1d4b
Restrict factory to workbasket assignees
paulpepper-trade Feb 12, 2025
6f75f4f
Merge branch 'master' into TP2000-1471--task-workflow
paulpepper-trade Feb 12, 2025
280ee81
TP2000-1651 Fix sorting on list views (#1380)
dalecannon Feb 13, 2025
562a3c1
Pre-sort objects in view sorting tests to guarantee consistent ordering
dalecannon Feb 13, 2025
380d87b
UI text changes for new 'tickets' tile on homepage (#1414)
marya-shariq Feb 20, 2025
48ef119
references to subtasks removed from UI
marya-shariq Feb 20, 2025
b64597d
Revert "references to subtasks removed from UI"
marya-shariq Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# TP-??? Your PR title here
# TP2000-??? Your PR title here
<!---
<--- 50 characters
* Include the JIRA ticket number, eg TP-123, to automatically link.
* Use 50 characters maximum.
* Do not end with a full-stop.
Expand All @@ -22,12 +23,12 @@ What is this PR doing, e.g. implementations, algorithms, etc.?
--->

<!---
Optionally let reviewers know they need to run migrations or update dependencies before
Let reviewers know they need to run migrations or update dependencies before
testing by adding the following section:
## Checklist
- Requires migrations?
- Requires dependency updates?
--->
## Checklist
- Requires migrations? Yes / No
- Requires dependency updates? Yes / No

<!---
Links to relevant material
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,16 @@
]) or "" }}
{% endfor %}

{% set base_url = url('certificate-ui-detail-measures', kwargs={"certificate_type__sid":object.certificate_type.sid, "sid":object.sid}) %}

{% set commodity_code %}
{{ create_sortable_anchor(request, "goods_nomenclature", "Commodity code", base_url) }}
{{ create_sortable_anchor(request, "Commodity code", sorting_urls["goods_nomenclature"]) }}
{% endset %}

{% set geo_area %}
{{ create_sortable_anchor(request, "geo_area", "Geographical area", base_url) }}
{{ create_sortable_anchor(request, "Geographical area", sorting_urls["geo_area"]) }}
{% endset %}

{% set start_date %}
{{ create_sortable_anchor(request, "start_date", "Start date", base_url) }}
{{ create_sortable_anchor(request, "Start date", sorting_urls["start_date"]) }}
{% endset %}

{% if paginator.count > 0 %}
Expand Down
1 change: 1 addition & 0 deletions certificates/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def test_certificate_detail_measures_view_sorting_commodity(valid_user_client):
)
measures.append(measure)
commodity_codes = [measure.goods_nomenclature.item_id for measure in measures]
commodity_codes.sort()

url = reverse(
"certificate-ui-detail-measures",
Expand Down
1 change: 1 addition & 0 deletions certificates/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class CertificateDetailMeasures(SortingMixin, WithPaginationListMixin, ListView)
paginate_by = 20
sort_by_fields = ["goods_nomenclature", "geo_area", "start_date"]
custom_sorting = {
"goods_nomenclature": "goods_nomenclature__item_id",
"geo_area": "geographical_area__area_id",
"start_date": "valid_between",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,20 @@
]) or "" }}
{% endfor %}

{% set base_url = url('commodity-ui-detail-measures-declarable', args=[commodity.sid]) %}

{% set comm_code %}
{{ create_sortable_anchor(request, "commodity", "Commodity code", base_url) }}
{{ create_sortable_anchor(request, "Commodity code", sorting_urls["commodity"]) }}
{% endset %}

{% set measure_type %}
{{ create_sortable_anchor(request, "measure_type", "Measure type", base_url) }}
{{ create_sortable_anchor(request, "Measure type", sorting_urls["measure_type"]) }}
{% endset %}

{% set geo_area %}
{{ create_sortable_anchor(request, "geo_area", "Geographical area", base_url) }}
{{ create_sortable_anchor(request, "Geographical area", sorting_urls["geo_area"]) }}
{% endset %}

{% set start_date %}
{{ create_sortable_anchor(request, "start_date", "Start date", base_url) }}
{{ create_sortable_anchor(request, "Start date", sorting_urls["start_date"]) }}
{% endset %}

{% if paginator.count > 0 %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,16 @@
]) or "" }}
{% endfor %}

{% set base_url = url('commodity-ui-detail-measures-as-defined', args=[commodity.sid]) %}

{% set measure_type %}
{{ create_sortable_anchor(request, "measure_type", "Measure type", base_url) }}
{{ create_sortable_anchor(request, "Measure type", sorting_urls["measure_type"]) }}
{% endset %}

{% set geo_area %}
{{ create_sortable_anchor(request, "geo_area", "Geographical area", base_url) }}
{{ create_sortable_anchor(request, "Geographical area", sorting_urls["geo_area"]) }}
{% endset %}

{% set start_date %}
{{ create_sortable_anchor(request, "start_date", "Start date", base_url) }}
{{ create_sortable_anchor(request, "Start date", sorting_urls["start_date"]) }}
{% endset %}

{% if paginator.count > 0 %}
Expand Down
48 changes: 40 additions & 8 deletions common/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from dateutil.relativedelta import relativedelta
from django.contrib.postgres.fields import DateRangeField
from django.contrib.postgres.fields import DateTimeRangeField
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.expressions import RawSQL
from django.forms import ModelChoiceField
Expand Down Expand Up @@ -146,20 +147,51 @@ class TaricDateTimeRangeField(DateTimeRangeField):


class AutoCompleteField(ModelChoiceField):
def __init__(self, *args, **kwargs):
qs = kwargs["queryset"]
prefix = getattr(qs.model, "url_pattern_name_prefix", None)
if not prefix:
prefix = qs.model._meta.model_name
"""
A form field that provides an AutoComplete widget for selecting a model
instance.

Args:
- queryset (QuerySet): A queryset of model instances that will populate the valid choices for the field.
- label (str): (Optional) A label for the field.
- help_text (str): (Optional) Help text for the field.
- url_pattern_name (str): (Optional) A custom pattern name to use for resolving the API source URL of AutoCompleteWidget.
- attrs (dict): (Optional) Additional attributes to pass to AutoCompleteWidget, e.g {"min-length": 2}.
"""

def __init__(self, queryset, url_pattern_name=None, *args, **kwargs):
self.widget = AutocompleteWidget(
attrs={
"label": kwargs.get("label", ""),
"help_text": kwargs.get("help_text", ""),
"source_url": reverse_lazy(f"{prefix}-list"),
"source_url": reverse_lazy(
self.get_url_pattern_name(queryset, url_pattern_name),
),
**kwargs.pop("attrs", {}),
},
)
super().__init__(*args, **kwargs)
super().__init__(queryset=queryset, *args, **kwargs)

def get_url_pattern_name(self, queryset, url_pattern_name: str | None) -> str:
"""
Determines the URL pattern name to use for resolving the API source URL
of AutocompleteWidget.

If a custom name isn't provided, the name will be based on the
attributes of the model derived from the queryset.
"""

if url_pattern_name is not None:
return url_pattern_name

prefix = getattr(queryset.model, "url_pattern_name_prefix", None)
if not prefix:
prefix = queryset.model._meta.model_name

return f"{prefix}-list"

def prepare_value(self, value):
return self.to_python(value)
try:
return self.to_python(value)
except ValidationError:
return None
19 changes: 19 additions & 0 deletions common/jinja2/common/homepage.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,25 @@
</article>
{% endif %}


{% if request.user.groups.filter(name='Tariff Managers').exists() or request.user.is_superuser %}
<article class="masonry-card">
<h3 class="govuk-heading-m">Tickets</h3>
<p class="govuk-body"></p>
<a href="{{ url('workflow:task-workflow-ui-list') }}"
class="govuk-link"
>Tickets</a>
</p>
{% if request.user.is_superuser %}
<p class="govuk-body"></p>
<a href="{{ url('workflow:task-workflow-template-ui-list') }}"
class="govuk-link"
>Ticket templates</a>
</p>
{% endif %}
</article>
{% endif %}

<article class="masonry-card">
<h3 class="govuk-heading-m">Resources</h3>
<p class="govuk-body">
Expand Down
22 changes: 6 additions & 16 deletions common/jinja2/components/create_sortable_anchor.jinja
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@

{% macro create_sortable_anchor(request, sort_by, title, base_url, query_params=False, fragment="") %}

{% if query_params %}
{% set query_string_prefix = "&"%}
{% else %}
{% set query_string_prefix = "?"%}
{% endif %}

{% if request.GET.sort_by == sort_by and request.GET.ordered == "desc" %}

<a class="govuk-link govuk-link--no-visited-state sort-icon--down" href="{{ base_url }}{{query_string_prefix}}sort_by={{ sort_by }}&ordered=asc{{ fragment }}">
{{ title }} <img src="{{ static('common/images/sort_icon.svg') }}" alt="">
</a>
{% macro create_sortable_anchor(request, title, url, fragment="") %}
{% if request.GET.ordered == "desc" %}
<a class="govuk-link govuk-link--no-visited-state sort-icon--down" href="{{ url }}{{ fragment }}">
{{ title }} <img src="{{ static('common/images/sort_icon.svg') }}" alt="">
</a>
{% else %}
<a class="govuk-link govuk-link--no-visited-state sort-icon--up" href="{{ base_url }}{{query_string_prefix}}sort_by={{ sort_by }}&ordered=desc{{ fragment }}">
<a class="govuk-link govuk-link--no-visited-state sort-icon--up" href="{{ url }}{{ fragment }}">
{{ title }} <img src="{{ static('common/images/sort_icon.svg') }}" alt="">
</a>
{% endif %}

{% endmacro %}
2 changes: 2 additions & 0 deletions common/jinja2/layouts/detail.jinja
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{% extends "layouts/layout.jinja" %}

{% from "components/tabs/macro.njk" import govukTabs %}
{% from "components/breadcrumbs.jinja" import breadcrumbs %}

{% block breadcrumb %}
Expand Down
21 changes: 21 additions & 0 deletions common/migrations/0014_alter_user_managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.18 on 2025-02-11 15:48

from django.db import migrations

import common.models.user


class Migration(migrations.Migration):

dependencies = [
("common", "0013_versiongroup_common_vers_current_04c358_idx"),
]

operations = [
migrations.AlterModelManagers(
name="user",
managers=[
("objects", common.models.user.TamatoUserManager()),
],
),
]
35 changes: 35 additions & 0 deletions common/models/mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Mixins for models."""

from django.db import models
from django.db.models import signals


class TimestampedMixin(models.Model):
Expand All @@ -11,3 +12,37 @@ class TimestampedMixin(models.Model):

class Meta:
abstract = True


class WithSignalManagerMixin:
"""A mixin that overrides default Manager methods to send pre_save signals
for model instances."""

def bulk_create(self, objs, **kwargs) -> list:
for obj in objs:
signals.pre_save.send(sender=self.model, instance=obj)
return super().bulk_create(objs, **kwargs)


class WithSignalQuerysetMixin:
"""A mixin that overrides default QuerySet methods to send pre_save signals
for model instances."""

def update(self, **kwargs) -> int:
old_instances_map = {}
pk_list = []
for old_instance in self.iterator():
old_instances_map[old_instance.pk] = old_instance
pk_list.append(old_instance.pk)

rows_updated = super().update(**kwargs)

for new_instance in self.model.objects.filter(pk__in=pk_list):
old_instance = old_instances_map.get(new_instance.pk)
signals.pre_save.send(
sender=self.model,
instance=new_instance,
old_instance=old_instance,
)

return rows_updated
22 changes: 22 additions & 0 deletions common/models/user.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager
from django.db import models


class UserQuerySet(models.QuerySet):
def active_tms(self) -> models.QuerySet | list["User"]:
"""Return a QuerySet of active users that may take the tariff manager
role."""
return (
self.filter(
models.Q(groups__name__in=["Tariff Managers", "Tariff Lead Profile"])
| models.Q(is_superuser=True),
)
.filter(is_active=True)
.distinct()
.order_by("first_name", "last_name")
)


class TamatoUserManager(UserManager.from_queryset(UserQuerySet)):
use_in_migrations = True


class User(AbstractUser):
"""Custom user model."""

objects = TamatoUserManager()

current_workbasket = models.ForeignKey(
"workbaskets.WorkBasket",
on_delete=models.SET_NULL,
Expand Down
2 changes: 2 additions & 0 deletions common/static/common/js/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import initTapDebounce from "./buttonDebounce";
import { setupQuotaOriginFormset } from "./components/QuotaOriginFormset/index";
import { setupGeoAreaForm } from "./components/GeoAreaForm/index";
import { setupWorkbasketUserAssignment } from "./components/WorkbasketUserAssignment/index";
import { setupTaskUserAssignment } from "./components/TaskUserAssignment/index";
import { initMasonry } from "./masonry";

showHideCheckboxes();
Expand All @@ -46,4 +47,5 @@ initTapDebounce();
setupQuotaOriginFormset();
setupGeoAreaForm();
setupWorkbasketUserAssignment();
setupTaskUserAssignment();
initMasonry();
Loading
Loading