feat: implement document management views, forms, templates, and URL routing
This commit is contained in:
parent
061fb6dc62
commit
ff4042b333
5 changed files with 145 additions and 2 deletions
|
|
@ -31,6 +31,21 @@ class TagField(forms.CharField):
|
|||
return [item.strip() for item in value.split(",") if item.strip()]
|
||||
|
||||
|
||||
class DocumentUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ManagedDocument
|
||||
fields = ["title", "description"]
|
||||
widgets = {
|
||||
"title": forms.TextInput(attrs={"class": "form-control"}),
|
||||
"description": forms.Textarea(attrs={"class": "form-control", "rows": 4}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["title"].widget.attrs.setdefault("class", "form-control")
|
||||
self.fields["description"].widget.attrs.setdefault("class", "form-control")
|
||||
|
||||
|
||||
class ManagedDocumentForm(forms.ModelForm):
|
||||
file = forms.FileField(label=_("File"))
|
||||
comment = forms.CharField(label=_("Comment"), required=False, widget=forms.Textarea(attrs={"rows": 2}))
|
||||
|
|
|
|||
|
|
@ -19,10 +19,22 @@
|
|||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="mb-1">{{ document.title }}</h2>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<h2 class="mb-1">{{ document.title }}</h2>
|
||||
{% if can_edit_document %}
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary py-0 px-2" data-bs-toggle="modal" data-bs-target="#editDocumentModal" title="{% translate 'Modifier le titre et la description' %}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-muted mb-0">{{ document.description|default:_("No description provided") }}</p>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
{% if can_edit_document %}
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#editDocumentModal">
|
||||
<i class="bi bi-pencil"></i> {% translate "Modifier" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if document.georeference_coords %}
|
||||
<a class="btn btn-outline-success" href="{% url 'documents:map_view' document.pk %}" target="_blank">
|
||||
<i class="bi bi-map"></i> {% translate "Afficher sur la carte" %}
|
||||
|
|
@ -698,6 +710,41 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_edit_document %}
|
||||
<!-- Modal de modification du document (titre et description) -->
|
||||
<div class="modal fade" id="editDocumentModal" tabindex="-1" aria-labelledby="editDocumentModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<form method="post" action="{% url 'documents:edit' document.pk %}">
|
||||
{% csrf_token %}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editDocumentModalLabel">
|
||||
<i class="bi bi-pencil-square text-primary"></i> {% translate "Modifier le document" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="id_doc_title" class="form-label fw-bold">{% translate "Titre" %} <span class="text-danger">*</span></label>
|
||||
<input type="text" name="title" id="id_doc_title" class="form-control" value="{{ document.title }}" required maxlength="255">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="id_doc_description" class="form-label fw-bold">{% translate "Description" %}</label>
|
||||
<textarea name="description" id="id_doc_description" class="form-control" rows="4" placeholder="{% translate 'Description facultative...' %}">{{ document.description }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% translate "Annuler" %}</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg"></i> {% translate "Enregistrer" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.border-hover-primary:hover {
|
||||
border-color: #0d6efd !important;
|
||||
|
|
|
|||
|
|
@ -808,4 +808,52 @@ class PublicAccessLinkTests(TestCase):
|
|||
url = reverse("documents:folder_public", args=[self.folder_link.token])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Public Folder")
|
||||
self.assertContains(response, "Public Folder")
|
||||
|
||||
|
||||
class DocumentUpdateViewTests(TestCase):
|
||||
def setUp(self):
|
||||
self.owner = User.objects.create_user(username="doc_owner", password="pwd")
|
||||
UserConfig.objects.create(user=self.owner, is_intern=True)
|
||||
|
||||
self.other_user = User.objects.create_user(username="doc_other", password="pwd")
|
||||
UserConfig.objects.create(user=self.other_user, is_intern=True)
|
||||
|
||||
self.document = ManagedDocument.objects.create(
|
||||
title="Original Title",
|
||||
description="Original Description",
|
||||
created_by=self.owner,
|
||||
)
|
||||
|
||||
def test_owner_can_update_title_and_description(self):
|
||||
self.client.force_login(self.owner)
|
||||
url = reverse("documents:edit", args=[self.document.pk])
|
||||
response = self.client.post(url, {
|
||||
"title": "Updated Title",
|
||||
"description": "Updated Description with new details",
|
||||
})
|
||||
self.assertRedirects(response, self.document.get_absolute_url())
|
||||
self.document.refresh_from_db()
|
||||
self.assertEqual(self.document.title, "Updated Title")
|
||||
self.assertEqual(self.document.description, "Updated Description with new details")
|
||||
|
||||
def test_unauthorized_user_cannot_update_document(self):
|
||||
self.client.force_login(self.other_user)
|
||||
url = reverse("documents:edit", args=[self.document.pk])
|
||||
response = self.client.post(url, {
|
||||
"title": "Hacked Title",
|
||||
"description": "Hacked Description",
|
||||
})
|
||||
# Should raise 404/403 or PermissionDenied
|
||||
self.assertIn(response.status_code, [403, 404])
|
||||
self.document.refresh_from_db()
|
||||
self.assertEqual(self.document.title, "Original Title")
|
||||
self.assertEqual(self.document.description, "Original Description")
|
||||
|
||||
def test_detail_view_includes_edit_modal_for_authorized_user(self):
|
||||
self.client.force_login(self.owner)
|
||||
response = self.client.get(self.document.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "editDocumentModal")
|
||||
self.assertContains(response, 'value="Original Title"')
|
||||
|
||||
|
|
@ -46,6 +46,7 @@ urlpatterns = [
|
|||
name="folder_access_link_delete",
|
||||
),
|
||||
path("<int:pk>/", views.DocumentDetailView.as_view(), name="detail"),
|
||||
path("<int:pk>/edit/", views.DocumentUpdateView.as_view(), name="edit"),
|
||||
path("<int:pk>/add-version/", views.AddVersionView.as_view(), name="add_version"),
|
||||
path("<int:pk>/version/<int:version_id>/delete/", views.DeleteVersionView.as_view(), name="delete_version"),
|
||||
path("<int:pk>/share/", views.ShareDocumentView.as_view(), name="share"),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from .forms import (
|
|||
DocumentFolderForm,
|
||||
DocumentFolderShareForm,
|
||||
DocumentShareForm,
|
||||
DocumentUpdateForm,
|
||||
DocumentValidationForm,
|
||||
DocumentVersionForm,
|
||||
ManagedDocumentForm,
|
||||
|
|
@ -832,6 +833,35 @@ class DocumentDeleteView(DocumentsAppAccessMixin, LoginRequiredMixin, View):
|
|||
return redirect(success_url)
|
||||
|
||||
|
||||
class DocumentUpdateView(DocumentsAppAccessMixin, LoginRequiredMixin, View):
|
||||
"""Permet de modifier le titre et la description d'un document."""
|
||||
|
||||
def get_document(self, pk, user):
|
||||
document = get_object_or_404(ManagedDocument.objects.all(), pk=pk)
|
||||
if not (
|
||||
user_is_documents_admin(user)
|
||||
or user_has_document_permission(
|
||||
user, document, required_permission=ManagedDocument.PERMISSION_EDIT
|
||||
)
|
||||
):
|
||||
raise PermissionDenied
|
||||
return document
|
||||
|
||||
def post(self, request, pk):
|
||||
document = self.get_document(pk, request.user)
|
||||
form = DocumentUpdateForm(request.POST, instance=document)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("Document updated successfully."))
|
||||
else:
|
||||
messages.error(request, _("Error updating document."))
|
||||
return redirect(document.get_absolute_url())
|
||||
|
||||
def get(self, request, pk):
|
||||
document = self.get_document(pk, request.user)
|
||||
return redirect(document.get_absolute_url())
|
||||
|
||||
|
||||
class DocumentFolderShareView(DocumentsAppAccessMixin, LoginRequiredMixin, View):
|
||||
template_name = "documents/documentfolder_share.html"
|
||||
|
||||
|
|
@ -1081,6 +1111,8 @@ class DocumentDetailView(DocumentsAppAccessMixin, LoginRequiredMixin, DetailView
|
|||
pk__in=existing_tag_ids
|
||||
).order_by("name_fr")
|
||||
|
||||
context["edit_form"] = DocumentUpdateForm(instance=document)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue