mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge branch 'workflow-improvements' into dev
This commit is contained in:
commit
9f20175cd3
161
src/documents/actions.py
Executable file
161
src/documents/actions.py
Executable file
@ -0,0 +1,161 @@
|
|||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.admin import helpers
|
||||||
|
from django.contrib.admin.utils import model_ngettext
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
|
||||||
|
from documents.models import Tag, Correspondent
|
||||||
|
|
||||||
|
|
||||||
|
def add_tag_to_selected(modeladmin, request, queryset):
|
||||||
|
opts = modeladmin.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
|
||||||
|
if not modeladmin.has_change_permission(request):
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
if request.POST.get('post'):
|
||||||
|
n = queryset.count()
|
||||||
|
tag = Tag.objects.get(id=request.POST.get('tag_id'))
|
||||||
|
if n:
|
||||||
|
for obj in queryset:
|
||||||
|
obj.tags.add(tag)
|
||||||
|
obj_display = str(obj)
|
||||||
|
modeladmin.log_change(request, obj, obj_display)
|
||||||
|
modeladmin.message_user(request, "Successfully added tag %(tag)s to %(count)d %(items)s." % {
|
||||||
|
"tag": tag.name, "count": n, "items": model_ngettext(modeladmin.opts, n)
|
||||||
|
}, messages.SUCCESS)
|
||||||
|
|
||||||
|
# Return None to display the change list page again.
|
||||||
|
return None
|
||||||
|
|
||||||
|
title = "Add tag to multiple documents"
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
modeladmin.admin_site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
queryset=queryset,
|
||||||
|
opts=opts,
|
||||||
|
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
media=modeladmin.media,
|
||||||
|
action="add_tag_to_selected",
|
||||||
|
tags=Tag.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
request.current_app = modeladmin.admin_site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,
|
||||||
|
"admin/%s/%s/mass_modify_tag.html" % (app_label, opts.model_name)
|
||||||
|
, context)
|
||||||
|
|
||||||
|
|
||||||
|
add_tag_to_selected.short_description = "Add tag to selected documents"
|
||||||
|
|
||||||
|
|
||||||
|
def remove_tag_from_selected(modeladmin, request, queryset):
|
||||||
|
opts = modeladmin.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
|
||||||
|
if not modeladmin.has_change_permission(request):
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
if request.POST.get('post'):
|
||||||
|
n = queryset.count()
|
||||||
|
tag = Tag.objects.get(id=request.POST.get('tag_id'))
|
||||||
|
if n:
|
||||||
|
for obj in queryset:
|
||||||
|
obj.tags.remove(tag)
|
||||||
|
obj_display = str(obj)
|
||||||
|
modeladmin.log_change(request, obj, obj_display)
|
||||||
|
modeladmin.message_user(request, "Successfully removed tag %(tag)s from %(count)d %(items)s." % {
|
||||||
|
"tag": tag.name, "count": n, "items": model_ngettext(modeladmin.opts, n)
|
||||||
|
}, messages.SUCCESS)
|
||||||
|
|
||||||
|
# Return None to display the change list page again.
|
||||||
|
return None
|
||||||
|
|
||||||
|
title = "Remove tag from multiple documents"
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
modeladmin.admin_site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
queryset=queryset,
|
||||||
|
opts=opts,
|
||||||
|
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
media=modeladmin.media,
|
||||||
|
action="remove_tag_from_selected",
|
||||||
|
tags=Tag.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
request.current_app = modeladmin.admin_site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,
|
||||||
|
"admin/%s/%s/mass_modify_tag.html" % (app_label, opts.model_name)
|
||||||
|
, context)
|
||||||
|
|
||||||
|
|
||||||
|
remove_tag_from_selected.short_description = "Remove tag from selected documents"
|
||||||
|
|
||||||
|
|
||||||
|
def set_correspondent_on_selected(modeladmin, request, queryset):
|
||||||
|
opts = modeladmin.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
|
||||||
|
if not modeladmin.has_change_permission(request):
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
if request.POST.get('post'):
|
||||||
|
n = queryset.count()
|
||||||
|
correspondent = Correspondent.objects.get(id=request.POST.get('correspondent_id'))
|
||||||
|
if n:
|
||||||
|
for obj in queryset:
|
||||||
|
obj_display = str(obj)
|
||||||
|
modeladmin.log_change(request, obj, obj_display)
|
||||||
|
queryset.update(correspondent=correspondent)
|
||||||
|
modeladmin.message_user(request, "Successfully set correspondent %(correspondent)s on %(count)d %(items)s." % {
|
||||||
|
"correspondent": correspondent.name, "count": n, "items": model_ngettext(modeladmin.opts, n)
|
||||||
|
}, messages.SUCCESS)
|
||||||
|
|
||||||
|
# Return None to display the change list page again.
|
||||||
|
return None
|
||||||
|
|
||||||
|
title = "Set correspondent on multiple documents"
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
modeladmin.admin_site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
queryset=queryset,
|
||||||
|
opts=opts,
|
||||||
|
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
media=modeladmin.media,
|
||||||
|
correspondents=Correspondent.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
request.current_app = modeladmin.admin_site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,
|
||||||
|
"admin/%s/%s/set_correspondent.html" % (app_label, opts.model_name)
|
||||||
|
, context)
|
||||||
|
|
||||||
|
|
||||||
|
set_correspondent_on_selected.short_description = "Set correspondent on selected documents"
|
||||||
|
|
||||||
|
|
||||||
|
def remove_correspondent_from_selected(modeladmin, request, queryset):
|
||||||
|
if not modeladmin.has_change_permission(request):
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
n = queryset.count()
|
||||||
|
if n:
|
||||||
|
for obj in queryset:
|
||||||
|
obj_display = str(obj)
|
||||||
|
modeladmin.log_change(request, obj, obj_display)
|
||||||
|
queryset.update(correspondent=None)
|
||||||
|
modeladmin.message_user(request, "Successfully removed correspondent from %(count)d %(items)s." % {
|
||||||
|
"count": n, "items": model_ngettext(modeladmin.opts, n)
|
||||||
|
}, messages.SUCCESS)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
remove_correspondent_from_selected.short_description = "Remove correspondent from selected documents"
|
@ -7,6 +7,8 @@ from django.urls import reverse
|
|||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
from documents.actions import add_tag_to_selected, remove_tag_from_selected, set_correspondent_on_selected, \
|
||||||
|
remove_correspondent_from_selected
|
||||||
from .models import Correspondent, Tag, Document, Log
|
from .models import Correspondent, Tag, Document, Log
|
||||||
|
|
||||||
|
|
||||||
@ -110,6 +112,14 @@ class CorrespondentAdmin(CommonAdmin):
|
|||||||
list_filter = ("matching_algorithm",)
|
list_filter = ("matching_algorithm",)
|
||||||
list_editable = ("match", "matching_algorithm")
|
list_editable = ("match", "matching_algorithm")
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
for document in Document.objects.filter(correspondent__isnull=True).exclude(tags__is_archived_tag=True):
|
||||||
|
if obj.matches(document.content):
|
||||||
|
document.correspondent = obj
|
||||||
|
document.save(update_fields=("correspondent",))
|
||||||
|
|
||||||
def document_count(self, obj):
|
def document_count(self, obj):
|
||||||
return obj.documents.count()
|
return obj.documents.count()
|
||||||
|
|
||||||
@ -121,6 +131,13 @@ class TagAdmin(CommonAdmin):
|
|||||||
list_filter = ("colour", "matching_algorithm")
|
list_filter = ("colour", "matching_algorithm")
|
||||||
list_editable = ("colour", "match", "matching_algorithm")
|
list_editable = ("colour", "match", "matching_algorithm")
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
for document in Document.objects.all().exclude(tags__is_archived_tag=True):
|
||||||
|
if obj.matches(document.content):
|
||||||
|
document.tags.add(obj)
|
||||||
|
|
||||||
def document_count(self, obj):
|
def document_count(self, obj):
|
||||||
return obj.documents.count()
|
return obj.documents.count()
|
||||||
|
|
||||||
@ -130,17 +147,20 @@ class DocumentAdmin(CommonAdmin):
|
|||||||
class Media:
|
class Media:
|
||||||
css = {
|
css = {
|
||||||
"all": ("paperless.css",)
|
"all": ("paperless.css",)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
search_fields = ("correspondent__name", "title", "content", "tags__name")
|
search_fields = ("correspondent__name", "title", "content", "tags__name")
|
||||||
readonly_fields = ("added",)
|
readonly_fields = ("added",)
|
||||||
list_display = ("title", "created", "added", "thumbnail", "correspondent",
|
list_display = ("title", "created", "added", "thumbnail", "correspondent",
|
||||||
"tags_")
|
"tags_", "archive_serial_number")
|
||||||
list_filter = ("tags", "correspondent", FinancialYearFilter,
|
list_filter = ("tags", "correspondent", FinancialYearFilter,
|
||||||
MonthListFilter)
|
MonthListFilter)
|
||||||
|
|
||||||
ordering = ["-created", "correspondent"]
|
ordering = ["-created", "correspondent"]
|
||||||
|
|
||||||
|
actions = [add_tag_to_selected, remove_tag_from_selected, set_correspondent_on_selected, remove_correspondent_from_selected]
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
def has_add_permission(self, request):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
2
src/documents/management/commands/document_correspondents.py
Normal file → Executable file
2
src/documents/management/commands/document_correspondents.py
Normal file → Executable file
@ -41,7 +41,7 @@ class Command(Renderable, BaseCommand):
|
|||||||
|
|
||||||
self.verbosity = options["verbosity"]
|
self.verbosity = options["verbosity"]
|
||||||
|
|
||||||
for document in Document.objects.filter(correspondent__isnull=True):
|
for document in Document.objects.filter(correspondent__isnull=True).exclude(tags__is_archived_tag=True):
|
||||||
|
|
||||||
potential_correspondents = list(
|
potential_correspondents = list(
|
||||||
Correspondent.match_all(document.content))
|
Correspondent.match_all(document.content))
|
||||||
|
2
src/documents/management/commands/document_retagger.py
Normal file → Executable file
2
src/documents/management/commands/document_retagger.py
Normal file → Executable file
@ -22,7 +22,7 @@ class Command(Renderable, BaseCommand):
|
|||||||
|
|
||||||
self.verbosity = options["verbosity"]
|
self.verbosity = options["verbosity"]
|
||||||
|
|
||||||
for document in Document.objects.all():
|
for document in Document.objects.all().exclude(tags__is_archived_tag=True):
|
||||||
|
|
||||||
tags = Tag.objects.exclude(
|
tags = Tag.objects.exclude(
|
||||||
pk__in=document.tags.values_list("pk", flat=True))
|
pk__in=document.tags.values_list("pk", flat=True))
|
||||||
|
34
src/documents/migrations/0022_workflow_improvements.py
Executable file
34
src/documents/migrations/0022_workflow_improvements.py
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.10 on 2018-02-04 13:07
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0021_document_storage_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='document',
|
||||||
|
name='archive_serial_number',
|
||||||
|
field=models.IntegerField(unique=True, blank=True, null=True, db_index=True),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='is_inbox_tag',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='tag',
|
||||||
|
name='is_archived_tag',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
@ -180,6 +180,14 @@ class Tag(MatchingModel):
|
|||||||
|
|
||||||
colour = models.PositiveIntegerField(choices=COLOURS, default=1)
|
colour = models.PositiveIntegerField(choices=COLOURS, default=1)
|
||||||
|
|
||||||
|
is_inbox_tag = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags.")
|
||||||
|
|
||||||
|
is_archived_tag = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Marks this tag as an archive tag: All documents tagged with archive tags will never be modified automatically (i.e., modifying tags by matching rules)")
|
||||||
|
|
||||||
|
|
||||||
class Document(models.Model):
|
class Document(models.Model):
|
||||||
|
|
||||||
@ -247,6 +255,13 @@ class Document(models.Model):
|
|||||||
added = models.DateTimeField(
|
added = models.DateTimeField(
|
||||||
default=timezone.now, editable=False, db_index=True)
|
default=timezone.now, editable=False, db_index=True)
|
||||||
|
|
||||||
|
archive_serial_number = models.IntegerField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
unique=True,
|
||||||
|
db_index=True,
|
||||||
|
help_text="The position of this document in your physical document archive.")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("correspondent", "title")
|
ordering = ("correspondent", "title")
|
||||||
|
|
||||||
|
2
src/documents/signals/handlers.py
Normal file → Executable file
2
src/documents/signals/handlers.py
Normal file → Executable file
@ -47,7 +47,7 @@ def set_correspondent(sender, document=None, logging_group=None, **kwargs):
|
|||||||
def set_tags(sender, document=None, logging_group=None, **kwargs):
|
def set_tags(sender, document=None, logging_group=None, **kwargs):
|
||||||
|
|
||||||
current_tags = set(document.tags.all())
|
current_tags = set(document.tags.all())
|
||||||
relevant_tags = set(Tag.match_all(document.content)) - current_tags
|
relevant_tags = (set(Tag.match_all(document.content)) | set(Tag.objects.filter(is_inbox_tag=True))) - current_tags
|
||||||
|
|
||||||
if not relevant_tags:
|
if not relevant_tags:
|
||||||
return
|
return
|
||||||
|
0
src/documents/templates/admin/documents/document/change_form.html
Normal file → Executable file
0
src/documents/templates/admin/documents/document/change_form.html
Normal file → Executable file
18
src/documents/templates/admin/documents/document/change_list_results.html
Normal file → Executable file
18
src/documents/templates/admin/documents/document/change_list_results.html
Normal file → Executable file
@ -25,6 +25,7 @@
|
|||||||
border-radius: 2%;
|
border-radius: 2%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.result .header {
|
.result .header {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@ -79,6 +80,15 @@
|
|||||||
.result .image img {
|
.result .image img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.result .footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border-left: 1px solid #cccccc;
|
||||||
|
border-top: 1px solid #cccccc;
|
||||||
|
padding: 4px 10px 4px 10px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
margin-right: 260px;
|
margin-right: 260px;
|
||||||
@ -152,7 +162,8 @@
|
|||||||
{# 4: Image #}
|
{# 4: Image #}
|
||||||
{# 5: Correspondent #}
|
{# 5: Correspondent #}
|
||||||
{# 6: Tags #}
|
{# 6: Tags #}
|
||||||
{# 7: Document edit url #}
|
{# 7: Archive serial number #}
|
||||||
|
{# 8: Document edit url #}
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="result">
|
<div class="result">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@ -166,7 +177,7 @@
|
|||||||
selection would not be possible with mouse click + drag. Instead,
|
selection would not be possible with mouse click + drag. Instead,
|
||||||
the underlying link would be dragged.
|
the underlying link would be dragged.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
<div class="headerLink" onclick="location.href='{{ result.7 }}';"></div>
|
<div class="headerLink" onclick="location.href='{{ result.8 }}';"></div>
|
||||||
<div class="checkbox">{{ result.0 }}</div>
|
<div class="checkbox">{{ result.0 }}</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
{{ result.5 }}
|
{{ result.5 }}
|
||||||
@ -178,6 +189,9 @@
|
|||||||
<div class="date">{{ result.2 }}</div>
|
<div class="date">{{ result.2 }}</div>
|
||||||
<div style="clear: both;"></div>
|
<div style="clear: both;"></div>
|
||||||
<div class="image">{{ result.4 }}</div>
|
<div class="image">{{ result.4 }}</div>
|
||||||
|
{# Only show the archive serial number if it is set on the document. #}
|
||||||
|
{# checking for >-< (i.e., will a dash be displayed) doesn't feel like a very good solution to me. #}
|
||||||
|
{% if '>-<' not in result.7 %}<div class="footer">#{{ result.7 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
46
src/documents/templates/admin/documents/document/mass_modify_tag.html
Executable file
46
src/documents/templates/admin/documents/document/mass_modify_tag.html
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load i18n l10n admin_urls static %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{{ block.super }}
|
||||||
|
{{ media }}
|
||||||
|
<script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||||
|
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
||||||
|
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||||
|
› {{title}}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Please select the tag.</p>
|
||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
<div>
|
||||||
|
{% for obj in queryset %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}"/>
|
||||||
|
{% endfor %}
|
||||||
|
<p>
|
||||||
|
<select name="tag_id">
|
||||||
|
{% for tag in tags %}
|
||||||
|
<option value="{{tag.id}}">{{tag.name}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input type="hidden" name="action" value="{{action}}"/>
|
||||||
|
<input type="hidden" name="post" value="yes"/>
|
||||||
|
<p>
|
||||||
|
<input type="submit" value="{% trans " Confirm" %}" />
|
||||||
|
<a href="#" class="button cancel-link">{% trans "Go back" %}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
46
src/documents/templates/admin/documents/document/set_correspondent.html
Executable file
46
src/documents/templates/admin/documents/document/set_correspondent.html
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load i18n l10n admin_urls static %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{{ block.super }}
|
||||||
|
{{ media }}
|
||||||
|
<script type="text/javascript" src="{% static 'admin/js/cancel.js' %}"></script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||||
|
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
||||||
|
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||||
|
› {{title}}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Please select the correspondent.</p>
|
||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
<div>
|
||||||
|
{% for obj in queryset %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}"/>
|
||||||
|
{% endfor %}
|
||||||
|
<p>
|
||||||
|
<select name="correspondent_id">
|
||||||
|
{% for correspondent in correspondents %}
|
||||||
|
<option value="{{correspondent.id}}">{{correspondent.name}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input type="hidden" name="action" value="set_correspondent_on_selected"/>
|
||||||
|
<input type="hidden" name="post" value="yes"/>
|
||||||
|
<p>
|
||||||
|
<input type="submit" value="{% trans " Confirm" %}" />
|
||||||
|
<a href="#" class="button cancel-link">{% trans "Go back" %}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user