SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit 0fd17d86 authored by George Katsikas's avatar George Katsikas :goat:
Browse files

add orphaned subsidyattachment list view

related to #218
parent 7c5a4f48
No related branches found
No related tags found
No related merge requests found
...@@ -286,6 +286,91 @@ class SubsidyPaymentForm(forms.ModelForm): ...@@ -286,6 +286,91 @@ class SubsidyPaymentForm(forms.ModelForm):
return instance return instance
class SubsidyAttachmentInlineLinkForm(forms.ModelForm):
class Meta:
model = SubsidyAttachment
fields = [
"subsidy",
]
filename = forms.CharField(
label="Filename",
required=True,
)
subsidy = forms.ModelChoiceField(
queryset=Subsidy.objects.all(),
widget=autocomplete.ModelSelect2(
url=reverse_lazy("finances:subsidy_autocomplete"),
attrs={
"data-html": True,
"style": "width: 100%",
},
),
help_text=("Start typing, and select from the popup."),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
Div(Field("filename"), css_class="col-6 col"),
Div(Field("subsidy"), css_class="col-6 col"),
css_class="row mb-0",
)
)
self.fields["filename"].initial = self.instance.filename
def clean(self):
orphaned = self.cleaned_data["subsidy"] is None
filename = self.cleaned_data["filename"]
# Allow misnamed orphans
if orphaned:
return
filename_regex = (
"^SciPost_"
"[0-9]{4,}(-[0-9]{4,})?_[A-Z]{2,}_[\w]+_"
"(Agreement|Invoice|ProofOfPayment|Other)"
"(-[0-9]{2,})?(_[\w]+)?\.(pdf|docx|png)$"
)
pattern = re.compile(filename_regex)
if not pattern.match(filename):
self.add_error(
"filename",
"The filename does not match the required regex pattern "
f"'{filename_regex}'",
)
def save(self, commit=True):
instance: "SubsidyAttachment" = super().save(commit=False)
filename = self.cleaned_data["filename"]
old_relative_path = instance.attachment.name
new_relative_path = instance.attachment.name.replace(
instance.filename, filename
)
try:
instance.attachment.storage.save(new_relative_path, instance.attachment)
instance.attachment.storage.delete(old_relative_path)
instance.attachment.name = new_relative_path
except Exception as e:
self.add_error(
"filename",
f"An error occurred while renaming the file: {e}",
)
if commit:
instance.save()
return instance
class SubsidyAttachmentForm(forms.ModelForm): class SubsidyAttachmentForm(forms.ModelForm):
class Meta: class Meta:
model = SubsidyAttachment model = SubsidyAttachment
...@@ -317,7 +402,7 @@ class SubsidyAttachmentForm(forms.ModelForm): ...@@ -317,7 +402,7 @@ class SubsidyAttachmentForm(forms.ModelForm):
def clean(self): def clean(self):
orphaned = self.cleaned_data["subsidy"] is None orphaned = self.cleaned_data["subsidy"] is None
attachment = self.cleaned_data["attachment"] attachment_filename = self.cleaned_data["attachment"].name.split("/")[-1]
# Allow misnamed orphans # Allow misnamed orphans
if orphaned: if orphaned:
...@@ -330,7 +415,9 @@ class SubsidyAttachmentForm(forms.ModelForm): ...@@ -330,7 +415,9 @@ class SubsidyAttachmentForm(forms.ModelForm):
"(-[0-9]{2,})?(_[\w]+)?\.(pdf|docx|png)$" "(-[0-9]{2,})?(_[\w]+)?\.(pdf|docx|png)$"
) )
pattern = re.compile(filename_regex) pattern = re.compile(filename_regex)
if not pattern.match(attachment.name):
#
if not pattern.match(attachment_filename):
self.add_error( self.add_error(
"attachment", "attachment",
"The filename does not match the required regex pattern " "The filename does not match the required regex pattern "
......
...@@ -29,3 +29,6 @@ class SubsidyAttachmentQuerySet(models.QuerySet): ...@@ -29,3 +29,6 @@ class SubsidyAttachmentQuerySet(models.QuerySet):
def proofs_of_payment(self): def proofs_of_payment(self):
return self.filter(kind=self.model.KIND_PROOF_OF_PAYMENT) return self.filter(kind=self.model.KIND_PROOF_OF_PAYMENT)
def orphaned(self):
return self.filter(subsidy__isnull=True)
{% load crispy_forms_tags %}
<form hx-post="{% url "finances:_hx_subsidyattachment_link_form" attachment_id=attachment.id %}"
hx-trigger="change delay:1000ms"
hx-swap="outerHTML">
{% crispy form %}
</form>
<div class="row border-bottom">
<div class="col-auto d-flex flex-column justify-content-between">
<span>{{ attachment.get_kind_display }}</span>
<span>{{ attachment.date|date:"SHORT_DATE_FORMAT" }}</span>
<a href="{% url 'finances:subsidyattachment_update' pk=attachment.id %}"><span class="text-warning">Update</span></a>
<a href="{% url 'finances:subsidy_attachment' attachment_id=attachment.id %}">View</a>
</div>
<div class="col"
hx-get="{% url "finances:_hx_subsidyattachment_link_form" attachment_id=attachment.id %}"
hx-trigger="revealed once">
<div class="spinner-grow spinner-grow-sm ms-2"
role="status"
aria-hidden="true"></div>
</div>
</div>
{% for attachment in page_obj %}
{% include 'finances/_hx_subsidyattachment_list_item.html' %}
{% empty %}
<tr id="orphaned-subsidies-results-load-next" hx-swap-oob="true">
<td colspan="12" class="text-center p-0">
<div class="p-2 d-flex justify-content-center">
<strong>No orphaned SubsidyAttachments could be found</strong>
</div>
</td>
</tr>
{% endfor %}
{% if page_obj.has_next %}
<tr id="orphaned-subsidies-results-load-next"
class="htmx-indicator"
hx-swap-oob="true"
hx-post="{% url 'finances:_hx_subsidyattachment_list_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}?page={{ page_obj.next_page_number }}"
hx-target="#orphaned-subsidies-results"
hx-include="#orphaned-subsidies-form"
hx-trigger="revealed"
hx-swap="beforeend"
hx-indicator="#orphaned-subsidies-results-load-next">
<td colspan="12" class="text-center p-0">
<div class="p-2 bg-primary bg-opacity-25 d-flex justify-content-center">
<strong>Loading page {{ page_obj.next_page_number }} out of {{ page_obj.paginator.num_pages }}</strong>
<div class="spinner-grow spinner-grow-sm ms-2"
role="status"
aria-hidden="true"></div>
</div>
</td>
</tr>
{% else %}
<tr id="orphaned-subsidies-results-load-next" hx-swap-oob="true">
<td colspan="12" class="text-center p-0">
<div class="p-2 d-flex justify-content-center">
<strong>All SubsidyAttachments loaded</strong>
</div>
</td>
</tr>
{% endif %}
{{ form_media }}
...@@ -29,6 +29,9 @@ ...@@ -29,6 +29,9 @@
<li> <li>
<a href="{% url 'finances:subsidyattachment_create' %}">Add a SubsidyAttachment</a> <a href="{% url 'finances:subsidyattachment_create' %}">Add a SubsidyAttachment</a>
</li> </li>
<li>
<a href="{% url 'finances:subsidyattachment_orphaned_list' %}">Link orphaned SubsidyAttachments</a>
</li>
<li> <li>
<a href="{% url 'finances:subsidies_old' %}" target="_blank">Go to the old list page</a> <a href="{% url 'finances:subsidies_old' %}" target="_blank">Go to the old list page</a>
</li> </li>
......
{% extends 'finances/base.html' %}
{% load user_groups %}
{% load crispy_forms_tags %}
{% block breadcrumb_items %}
{{ block.super }}
<span class="breadcrumb-item">Orphaned SubsidyAttachments</span>
{% endblock %}
{% block meta_description %}
{{ block.super }} Orphaned SubsidyAttachment List
{% endblock meta_description %}
{% block pagetitle %}
: Orphaned SubsidyAttachments
{% endblock pagetitle %}
{% block content %}
{% is_ed_admin request.user as is_ed_admin %}
<h1 class="highlight">Orphaned SubsidyAttachment List</h1>
<div id="orphaned-subsidies-results"
hx-get="{% url 'finances:_hx_subsidyattachment_list_page' %}?page=1"
hx-trigger="load once"></div>
{% endblock content %}
...@@ -139,6 +139,21 @@ urlpatterns = [ ...@@ -139,6 +139,21 @@ urlpatterns = [
views.subsidy_attachment, views.subsidy_attachment,
name="subsidy_attachment", name="subsidy_attachment",
), ),
path(
"subsidies/attachments/orphaned/",
views.subsidyattachment_orphaned_list,
name="subsidyattachment_orphaned_list",
),
path(
"subsidies/attachments/orphaned/_hx_list_page",
views._hx_subsidyattachment_list_page,
name="_hx_subsidyattachment_list_page",
),
path(
"subsidies/attachments/_hx_link_form/<int:attachment_id>",
views._hx_subsidyattachment_link_form,
name="_hx_subsidyattachment_link_form",
),
# Timesheets # Timesheets
path("timesheets", views.timesheets, name="timesheets"), path("timesheets", views.timesheets, name="timesheets"),
path("timesheets/detailed", views.timesheets_detailed, name="timesheets_detailed"), path("timesheets/detailed", views.timesheets_detailed, name="timesheets_detailed"),
......
...@@ -8,6 +8,7 @@ import mimetypes ...@@ -8,6 +8,7 @@ import mimetypes
from dal import autocomplete from dal import autocomplete
from django.db.models import Q from django.db.models import Q
from django.template.response import TemplateResponse
from django.utils.html import format_html from django.utils.html import format_html
import matplotlib import matplotlib
...@@ -16,7 +17,7 @@ import matplotlib.pyplot as plt ...@@ -16,7 +17,7 @@ import matplotlib.pyplot as plt
import io, base64 import io, base64
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator from django.core.paginator import Paginator
...@@ -29,6 +30,7 @@ from django.views.generic.edit import CreateView, UpdateView, DeleteView ...@@ -29,6 +30,7 @@ from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from .forms import ( from .forms import (
SubsidyAttachmentInlineLinkForm,
SubsidyForm, SubsidyForm,
SubsidySearchForm, SubsidySearchForm,
SubsidyPaymentForm, SubsidyPaymentForm,
...@@ -529,9 +531,10 @@ class SubsidyAttachmentUpdateView(PermissionsMixin, UpdateView): ...@@ -529,9 +531,10 @@ class SubsidyAttachmentUpdateView(PermissionsMixin, UpdateView):
return context return context
def get_success_url(self): def get_success_url(self):
return reverse_lazy( if subsidy := self.object.subsidy:
"finances:subsidy_details", kwargs={"pk": self.object.subsidy.id} return reverse_lazy("finances:subsidy_details", kwargs={"pk": subsidy.id})
)
return reverse_lazy("finances:subsidies")
class SubsidyAttachmentDeleteView(PermissionsMixin, DeleteView): class SubsidyAttachmentDeleteView(PermissionsMixin, DeleteView):
...@@ -543,9 +546,48 @@ class SubsidyAttachmentDeleteView(PermissionsMixin, DeleteView): ...@@ -543,9 +546,48 @@ class SubsidyAttachmentDeleteView(PermissionsMixin, DeleteView):
model = SubsidyAttachment model = SubsidyAttachment
def get_success_url(self): def get_success_url(self):
return reverse_lazy( if subsidy := self.object.subsidy:
"finances:subsidy_details", kwargs={"pk": self.object.subsidy.id} return reverse_lazy("finances:subsidy_details", kwargs={"pk": subsidy.id})
)
return reverse_lazy("finances:subsidies")
@login_required()
@permission_required("scipost.can_manage_subsidies", raise_exception=True)
def subsidyattachment_orphaned_list(request):
return TemplateResponse(
request, "finances/subsidyattachment_orphaned_list.html", {}
)
def _hx_subsidyattachment_list_page(request):
attachments = SubsidyAttachment.objects.orphaned()
paginator = Paginator(attachments, 16)
page_nr = request.GET.get("page")
page_obj = paginator.get_page(page_nr)
count = paginator.count
start_index = page_obj.start_index
context = {
"count": count,
"page_obj": page_obj,
"start_index": start_index,
"form_media": SubsidyAttachmentForm().media,
}
return render(request, "finances/_hx_subsidyattachment_list_page.html", context)
def _hx_subsidyattachment_link_form(request, attachment_id):
attachment = get_object_or_404(SubsidyAttachment, pk=attachment_id)
form = SubsidyAttachmentInlineLinkForm(request.POST or None, instance=attachment)
if form.is_valid():
form.save()
context = {
"attachment": attachment,
"form": form,
}
return render(request, "finances/_hx_subsidyattachment_link_form.html", context)
def subsidy_attachment(request, attachment_id): def subsidy_attachment(request, attachment_id):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment