Compare commits
4 Commits
main
...
feature/ac
Author | SHA1 | Date |
---|---|---|
Thomas Nahuel Rusiecki | b1bf33ac83 | |
Thomas Nahuel Rusiecki | 3a93595396 | |
Thomas Nahuel Rusiecki | 7b554c1b1f | |
Thomas Nahuel Rusiecki | 25d7a0de63 |
|
@ -0,0 +1,41 @@
|
||||||
|
from django import forms
|
||||||
|
from utils.device import create_annotation, create_doc, create_index
|
||||||
|
from utils.save_snapshots import move_json, save_in_disk
|
||||||
|
from django.forms.formsets import BaseFormSet
|
||||||
|
from django.forms import formset_factory
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
class CustomStatusLabelForm(forms.Form):
|
||||||
|
label_name = forms.CharField(
|
||||||
|
label="Annotation Name",
|
||||||
|
max_length=50,
|
||||||
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomStatusValueForm(forms.Form):
|
||||||
|
label_state = forms.CharField(
|
||||||
|
label="Possible State",
|
||||||
|
max_length=50,
|
||||||
|
widget=forms.TextInput(attrs={'class': 'form-control'})
|
||||||
|
)
|
||||||
|
|
||||||
|
class CustomStatusValueFormSet(BaseFormSet):
|
||||||
|
|
||||||
|
"""Validation for inputs (no two values should be the same)"""
|
||||||
|
def clean(self):
|
||||||
|
if any(self.errors):
|
||||||
|
return
|
||||||
|
|
||||||
|
label = []
|
||||||
|
labels = []
|
||||||
|
for form in self.forms:
|
||||||
|
label = form.cleaned_data.get('label_state')
|
||||||
|
if label:
|
||||||
|
if label in labels:
|
||||||
|
raise ValidationError("Duplicate labels are not allowed.")
|
||||||
|
|
||||||
|
labels.append(label)
|
||||||
|
|
||||||
|
|
||||||
|
CustomStatusFormSet = formset_factory(CustomStatusValueForm ,formset = CustomStatusValueFormSet)
|
|
@ -10,9 +10,15 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
||||||
<a href="{% url 'admin:institution' user.institution.pk %}" class="btn btn-green-admin">
|
<a href="{% url 'admin:institution' user.institution.pk %}" class="btn btn-green-admin">
|
||||||
{% translate "Institution" %}
|
{% translate "Institution" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="{% url 'admin:reserved'%}" class="btn btn-green-admin">
|
||||||
|
{% translate "Reserved Annotations" %}
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="text-center">{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form role="form" method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
|
||||||
|
<h5 class="mt-4">{% translate "Status name" %}</h5>
|
||||||
|
{% bootstrap_field form.label_name show_label=False %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="mt-4">{% translate "Possible States" %}</h5>
|
||||||
|
<div id="formset">
|
||||||
|
{{ formset.management_form }}
|
||||||
|
{% for form in formset %}
|
||||||
|
<div class="row mb-3 formset-form">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.label_state show_label=False %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex align-items-center">
|
||||||
|
{% if forloop.first %}
|
||||||
|
<button type="button" class="btn btn-sm btn-success add-form">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<button type="button" class="btn btn-sm btn-danger remove-form ms-2">
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<a class="btn btn-grey" href="{% url 'admin:panel' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//TODO: change this to jquery formset plugin
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var formsetDiv = document.getElementById('formset');
|
||||||
|
var totalForms = document.getElementById('id_form-TOTAL_FORMS');
|
||||||
|
|
||||||
|
function updateElementIndex(el, prefix, index) {
|
||||||
|
var idRegex = new RegExp('(' + prefix + '-\\d+-)');
|
||||||
|
var replacement = prefix + '-' + index + '-';
|
||||||
|
if (el.id) el.id = el.id.replace(idRegex, replacement);
|
||||||
|
if (el.name) el.name = el.name.replace(idRegex, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addForm(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var formCount = parseInt(totalForms.value);
|
||||||
|
var newForm = document.querySelector('.formset-form').cloneNode(true);
|
||||||
|
|
||||||
|
newForm.querySelectorAll('input').forEach(function(input) {
|
||||||
|
updateElementIndex(input, 'form', formCount);
|
||||||
|
input.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove any hidden inputs (management form fields)
|
||||||
|
newForm.querySelectorAll('input[type="hidden"]').forEach(function(hiddenInput) {
|
||||||
|
hiddenInput.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach event listeners to the new buttons
|
||||||
|
var addButton = newForm.querySelector('.add-form');
|
||||||
|
var removeButton = newForm.querySelector('.remove-form');
|
||||||
|
|
||||||
|
addButton.addEventListener('click', addForm);
|
||||||
|
removeButton.addEventListener('click', removeForm);
|
||||||
|
|
||||||
|
// Ensure only the first form has the add button
|
||||||
|
formsetDiv.querySelectorAll('.add-form').forEach(function(button, index) {
|
||||||
|
if (index > 0) {
|
||||||
|
button.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
formsetDiv.appendChild(newForm);
|
||||||
|
totalForms.value = formCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeForm(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var formToRemove = e.target.closest('.formset-form');
|
||||||
|
formToRemove.remove();
|
||||||
|
|
||||||
|
var forms = formsetDiv.querySelectorAll('.formset-form');
|
||||||
|
totalForms.value = forms.length;
|
||||||
|
|
||||||
|
// Re-index form elements
|
||||||
|
forms.forEach(function(form, index) {
|
||||||
|
form.querySelectorAll('input').forEach(function(input) {
|
||||||
|
updateElementIndex(input, 'form', index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure only the first form has the add button
|
||||||
|
formsetDiv.querySelectorAll('.add-form').forEach(function(button, index) {
|
||||||
|
if (index > 0) {
|
||||||
|
button.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial event listeners
|
||||||
|
document.querySelectorAll('.add-form').forEach(function(button) {
|
||||||
|
button.addEventListener('click', addForm);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.remove-form').forEach(function(button) {
|
||||||
|
button.addEventListener('click', removeForm);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -10,4 +10,5 @@ urlpatterns = [
|
||||||
path("users/edit/<int:pk>", views.EditUserView.as_view(), name="edit_user"),
|
path("users/edit/<int:pk>", views.EditUserView.as_view(), name="edit_user"),
|
||||||
path("users/delete/<int:pk>", views.DeleteUserView.as_view(), name="delete_user"),
|
path("users/delete/<int:pk>", views.DeleteUserView.as_view(), name="delete_user"),
|
||||||
path("institution/<int:pk>", views.InstitutionView.as_view(), name="institution"),
|
path("institution/<int:pk>", views.InstitutionView.as_view(), name="institution"),
|
||||||
|
path("reserved", views.AddReservedAnnotationView.as_view(), name="reserved"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,9 +8,16 @@ from django.views.generic.edit import (
|
||||||
UpdateView,
|
UpdateView,
|
||||||
DeleteView,
|
DeleteView,
|
||||||
)
|
)
|
||||||
|
from django.views.generic import FormView
|
||||||
|
import logging
|
||||||
from dashboard.mixins import DashboardView, Http403
|
from dashboard.mixins import DashboardView, Http403
|
||||||
from user.models import User, Institution
|
from user.models import User, Institution
|
||||||
from admin.email import NotifyActivateUserByEmail
|
from admin.email import NotifyActivateUserByEmail
|
||||||
|
from admin.forms import CustomStatusLabelForm, CustomStatusValueForm, CustomStatusFormSet
|
||||||
|
from evidence.models import Annotation, AllowedValue
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
logger = logging.getLogger('dhub')
|
||||||
|
|
||||||
|
|
||||||
class AdminView(DashboardView):
|
class AdminView(DashboardView):
|
||||||
|
@ -124,3 +131,70 @@ class InstitutionView(AdminView, UpdateView):
|
||||||
self.object = self.request.user.institution
|
self.object = self.request.user.institution
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AddReservedAnnotationView(AdminView, FormView):
|
||||||
|
template_name = "reserved.html"
|
||||||
|
title = _("New Custom State Labels")
|
||||||
|
breadcrumb = "Admin / Custom State Labels (new name?)"
|
||||||
|
success_url = reverse_lazy('admin:panel')
|
||||||
|
|
||||||
|
|
||||||
|
form_class = CustomStatusLabelForm
|
||||||
|
formset_class = CustomStatusFormSet
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
if self.request.POST:
|
||||||
|
context['form'] = self.form_class(self.request.POST)
|
||||||
|
context['formset'] = self.formset_class(self.request.POST)
|
||||||
|
else:
|
||||||
|
context['form'] = self.form_class()
|
||||||
|
context['formset'] = self.formset_class()
|
||||||
|
|
||||||
|
context['subtitle'] = _("Add Custom Status Label")
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
context = self.get_context_data()
|
||||||
|
formset = context['formset']
|
||||||
|
form = context['form']
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
label_name = form.cleaned_data['label_name']
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
if formset.is_valid():
|
||||||
|
|
||||||
|
annotation = Annotation.objects.create(
|
||||||
|
uuid=uuid.uuid4(),
|
||||||
|
owner=self.request.user.institution,
|
||||||
|
user=self.request.user,
|
||||||
|
type=Annotation.Type.ADMIN,
|
||||||
|
key=label_name,
|
||||||
|
value=label_name
|
||||||
|
)
|
||||||
|
first_state = None
|
||||||
|
for form in formset:
|
||||||
|
state = form.cleaned_data.get('label_state')
|
||||||
|
if state and not first_state:
|
||||||
|
first_state = state
|
||||||
|
annotation.value = state
|
||||||
|
annotation.save()
|
||||||
|
if state:
|
||||||
|
AllowedValue.objects.create(
|
||||||
|
annotation=annotation,
|
||||||
|
value=state
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Saving custom label to db: " + label_name)
|
||||||
|
self.success_message = _("Custom status label has been added.")
|
||||||
|
self.success_url = reverse_lazy('admin:panel')
|
||||||
|
return super().form_valid(form)
|
||||||
|
else:
|
||||||
|
logger.error("Formset is not valid")
|
||||||
|
logger.error(formset.errors)
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Annotation(models.Model):
|
||||||
USER = 1, "User"
|
USER = 1, "User"
|
||||||
DOCUMENT = 2, "Document"
|
DOCUMENT = 2, "Document"
|
||||||
ERASE_SERVER = 3, "EraseServer"
|
ERASE_SERVER = 3, "EraseServer"
|
||||||
|
ADMIN = 4, "Admin"
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
uuid = models.UUIDField()
|
uuid = models.UUIDField()
|
||||||
|
@ -28,7 +29,32 @@ class Annotation(models.Model):
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
models.UniqueConstraint(fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
||||||
]
|
]
|
||||||
|
#TODO: check if this works properly
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
if self.type == self.Type.ADMIN:
|
||||||
|
if Annotation.objects.filter(type=self.Type.ADMIN, key=self.key).exists():
|
||||||
|
raise ValidationError(f"The key '{self.key}' is already reserved by admin.")
|
||||||
|
else:
|
||||||
|
if Annotation.objects.filter(type=self.Type.ADMIN, key=self.key).exists():
|
||||||
|
raise ValidationError(f"The key '{self.key}' is reserved by admin and cannot be used.")
|
||||||
|
|
||||||
|
class AllowedValue(models.Model):
|
||||||
|
annotation = models.ForeignKey(Annotation, on_delete=models.CASCADE, limit_choices_to={'type': Annotation.Type.ADMIN})
|
||||||
|
value = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
#This represents a change on a System-type Annotation
|
||||||
|
class Action(models.Model):
|
||||||
|
|
||||||
|
annotation = models.ForeignKey(Annotation, on_delete=models.CASCADE, limit_choices_to={'type': Annotation.Type.ADMIN})
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
comment = models.CharField(max_length=250)
|
||||||
|
|
||||||
class Evidence:
|
class Evidence:
|
||||||
def __init__(self, uuid):
|
def __init__(self, uuid):
|
||||||
|
|
Loading…
Reference in New Issue