mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-01-20 22:24:24 -06:00
Compare commits
7 Commits
feature-47
...
v2.20.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1aa76e4ce | ||
|
|
5381bc5907 | ||
|
|
771f3f150a | ||
|
|
ecfeff5054 | ||
|
|
37477d391e | ||
|
|
2f1cd31e31 | ||
|
|
742c136773 |
1
.github/release-drafter.yml
vendored
1
.github/release-drafter.yml
vendored
@@ -44,6 +44,7 @@ include-labels:
|
|||||||
- 'notable'
|
- 'notable'
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- 'skip-changelog'
|
- 'skip-changelog'
|
||||||
|
filter-by-commitish: true
|
||||||
category-template: '### $TITLE'
|
category-template: '### $TITLE'
|
||||||
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
||||||
change-title-escapes: '\<*_&#@'
|
change-title-escapes: '\<*_&#@'
|
||||||
|
|||||||
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -617,6 +617,7 @@ jobs:
|
|||||||
version: ${{ steps.get_version.outputs.version }}
|
version: ${{ steps.get_version.outputs.version }}
|
||||||
prerelease: ${{ steps.get_version.outputs.prerelease }}
|
prerelease: ${{ steps.get_version.outputs.prerelease }}
|
||||||
publish: true # ensures release is not marked as draft
|
publish: true # ensures release is not marked as draft
|
||||||
|
committish: ${{ github.sha }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Upload release archive
|
- name: Upload release archive
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "paperless-ngx"
|
name = "paperless-ngx"
|
||||||
version = "2.20.4"
|
version = "2.20.5"
|
||||||
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperless-ngx-ui",
|
"name": "paperless-ngx-ui",
|
||||||
"version": "2.20.4",
|
"version": "2.20.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ describe('WorkflowEditDialogComponent', () => {
|
|||||||
expect(component.object.actions.length).toEqual(2)
|
expect(component.object.actions.length).toEqual(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update order and remove ids from actions on drag n drop', () => {
|
it('should update order on drag n drop', () => {
|
||||||
const action1 = workflow.actions[0]
|
const action1 = workflow.actions[0]
|
||||||
const action2 = workflow.actions[1]
|
const action2 = workflow.actions[1]
|
||||||
component.object = workflow
|
component.object = workflow
|
||||||
@@ -261,8 +261,6 @@ describe('WorkflowEditDialogComponent', () => {
|
|||||||
WorkflowAction[]
|
WorkflowAction[]
|
||||||
>)
|
>)
|
||||||
expect(component.object.actions).toEqual([action2, action1])
|
expect(component.object.actions).toEqual([action2, action1])
|
||||||
expect(action1.id).toBeNull()
|
|
||||||
expect(action2.id).toBeNull()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not include auto matching in algorithms', () => {
|
it('should not include auto matching in algorithms', () => {
|
||||||
|
|||||||
@@ -1283,11 +1283,6 @@ export class WorkflowEditDialogComponent
|
|||||||
const actionField = this.actionFields.at(event.previousIndex)
|
const actionField = this.actionFields.at(event.previousIndex)
|
||||||
this.actionFields.removeAt(event.previousIndex)
|
this.actionFields.removeAt(event.previousIndex)
|
||||||
this.actionFields.insert(event.currentIndex, actionField)
|
this.actionFields.insert(event.currentIndex, actionField)
|
||||||
// removing id will effectively re-create the actions in this order
|
|
||||||
this.object.actions.forEach((a) => (a.id = null))
|
|
||||||
this.actionFields.controls.forEach((c) =>
|
|
||||||
c.get('id').setValue(null, { emitEvent: false })
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
||||||
<div class="tag-option-row d-flex align-items-center">
|
<div class="tag-option-row d-flex align-items-center" [class.w-auto]="!getTag(item.id)?.parent">
|
||||||
@if (item.id && tags) {
|
@if (item.id && tags) {
|
||||||
@if (getTag(item.id)?.parent) {
|
@if (getTag(item.id)?.parent) {
|
||||||
<i-bs name="list-nested" class="me-1"></i-bs>
|
<i-bs name="list-nested" class="me-1"></i-bs>
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dropdown hierarchy reveal for ng-select options
|
// Dropdown hierarchy reveal for ng-select options
|
||||||
::ng-deep .ng-dropdown-panel .ng-option {
|
:host ::ng-deep .ng-dropdown-panel .ng-option {
|
||||||
overflow-x: scroll;
|
overflow-x: auto !important;
|
||||||
|
|
||||||
.tag-option-row {
|
.tag-option-row {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -41,12 +41,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
|
:host ::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
|
||||||
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
|
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-indicator,
|
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-indicator,
|
||||||
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
|
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const environment = {
|
|||||||
apiVersion: '9', // match src/paperless/settings.py
|
apiVersion: '9', // match src/paperless/settings.py
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
tag: 'prod',
|
tag: 'prod',
|
||||||
version: '2.20.4',
|
version: '2.20.5',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||||
|
|||||||
28
src/documents/migrations/1075_workflowaction_order.py
Normal file
28
src/documents/migrations/1075_workflowaction_order.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-14 16:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import F
|
||||||
|
|
||||||
|
|
||||||
|
def populate_action_order(apps, schema_editor):
|
||||||
|
WorkflowAction = apps.get_model("documents", "WorkflowAction")
|
||||||
|
WorkflowAction.objects.all().update(order=F("id"))
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("documents", "1074_workflowrun_deleted_at_workflowrun_restored_at_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflowaction",
|
||||||
|
name="order",
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name="order"),
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
populate_action_order,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1294,6 +1294,8 @@ class WorkflowAction(models.Model):
|
|||||||
default=WorkflowActionType.ASSIGNMENT,
|
default=WorkflowActionType.ASSIGNMENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
order = models.PositiveIntegerField(_("order"), default=0)
|
||||||
|
|
||||||
assign_title = models.TextField(
|
assign_title = models.TextField(
|
||||||
_("assign title"),
|
_("assign title"),
|
||||||
null=True,
|
null=True,
|
||||||
|
|||||||
@@ -2562,7 +2562,8 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
set_triggers.append(trigger_instance)
|
set_triggers.append(trigger_instance)
|
||||||
|
|
||||||
if actions is not None and actions is not serializers.empty:
|
if actions is not None and actions is not serializers.empty:
|
||||||
for action in actions:
|
for index, action in enumerate(actions):
|
||||||
|
action["order"] = index
|
||||||
assign_tags = action.pop("assign_tags", None)
|
assign_tags = action.pop("assign_tags", None)
|
||||||
assign_view_users = action.pop("assign_view_users", None)
|
assign_view_users = action.pop("assign_view_users", None)
|
||||||
assign_view_groups = action.pop("assign_view_groups", None)
|
assign_view_groups = action.pop("assign_view_groups", None)
|
||||||
@@ -2689,6 +2690,16 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
data = super().to_representation(instance)
|
||||||
|
actions = instance.actions.order_by("order", "pk")
|
||||||
|
data["actions"] = WorkflowActionSerializer(
|
||||||
|
actions,
|
||||||
|
many=True,
|
||||||
|
context=self.context,
|
||||||
|
).data
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class TrashSerializer(SerializerWithPerms):
|
class TrashSerializer(SerializerWithPerms):
|
||||||
documents = serializers.ListField(
|
documents = serializers.ListField(
|
||||||
|
|||||||
@@ -769,7 +769,7 @@ def run_workflows(
|
|||||||
|
|
||||||
if matching.document_matches_workflow(document, workflow, trigger_type):
|
if matching.document_matches_workflow(document, workflow, trigger_type):
|
||||||
action: WorkflowAction
|
action: WorkflowAction
|
||||||
for action in workflow.actions.all():
|
for action in workflow.actions.order_by("order", "pk"):
|
||||||
message = f"Applying {action} from {workflow}"
|
message = f"Applying {action} from {workflow}"
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
logger.info(message, extra={"group": logging_group})
|
logger.info(message, extra={"group": logging_group})
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ def get_workflows_for_trigger(
|
|||||||
wrap it in a list; otherwise fetch enabled workflows for the trigger with
|
wrap it in a list; otherwise fetch enabled workflows for the trigger with
|
||||||
the prefetches used by the runner.
|
the prefetches used by the runner.
|
||||||
"""
|
"""
|
||||||
if workflow_to_run is not None:
|
|
||||||
return [workflow_to_run]
|
|
||||||
|
|
||||||
annotated_actions = (
|
annotated_actions = (
|
||||||
WorkflowAction.objects.select_related(
|
WorkflowAction.objects.select_related(
|
||||||
"assign_correspondent",
|
"assign_correspondent",
|
||||||
@@ -105,10 +102,25 @@ def get_workflows_for_trigger(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
action_prefetch = Prefetch(
|
||||||
|
"actions",
|
||||||
|
queryset=annotated_actions.order_by("order", "pk"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if workflow_to_run is not None:
|
||||||
|
return (
|
||||||
|
Workflow.objects.filter(pk=workflow_to_run.pk)
|
||||||
|
.prefetch_related(
|
||||||
|
action_prefetch,
|
||||||
|
"triggers",
|
||||||
|
)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Workflow.objects.filter(enabled=True, triggers__type=trigger_type)
|
Workflow.objects.filter(enabled=True, triggers__type=trigger_type)
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
Prefetch("actions", queryset=annotated_actions),
|
action_prefetch,
|
||||||
"triggers",
|
"triggers",
|
||||||
)
|
)
|
||||||
.order_by("order")
|
.order_by("order")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
__version__: Final[tuple[int, int, int]] = (2, 20, 4)
|
__version__: Final[tuple[int, int, int]] = (2, 20, 5)
|
||||||
# Version string like X.Y.Z
|
# Version string like X.Y.Z
|
||||||
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
||||||
# Version string like X.Y
|
# Version string like X.Y
|
||||||
|
|||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -2115,7 +2115,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paperless-ngx"
|
name = "paperless-ngx"
|
||||||
version = "2.20.4"
|
version = "2.20.5"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
|
|||||||
Reference in New Issue
Block a user