Compare commits

...

3 Commits

6 changed files with 231 additions and 0 deletions

20
admin/forms.py Normal file
View File

@ -0,0 +1,20 @@
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 import formset_factory
class CustomStatusLabelForm(forms.Form):
annotation_name = forms.CharField(
label="Annotation Name",
max_length=50,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
annotation_state = forms.CharField(
label="Possible State",
max_length=50,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
CustomStatusLabelFormSet = formset_factory(CustomStatusLabelForm, extra=1)

View File

@ -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>

View File

@ -0,0 +1,129 @@
{% 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">
{{ form.annotation_name.label_tag }}
{{ form.annotation_name }}
</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.annotation_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">
&#43;
</button>
{% endif %}
<button type="button" class="btn btn-sm btn-danger remove-form ms-2">
&minus;
</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>
//fix this stub chatgpt code
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 %}

View File

@ -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"),
] ]

View File

@ -8,9 +8,15 @@ 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, CustomStatusLabelFormSet
from evidence.models import Annotation
logger = logging.getLogger('dhub')
class AdminView(DashboardView): class AdminView(DashboardView):
@ -124,3 +130,46 @@ 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')
model = Annotation
form_class = CustomStatusLabelForm
formset_class = CustomStatusLabelFormSet
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['formset'] = self.formset_class(self.request.POST)
else:
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']
if formset.is_valid():
annotation_name = form.cleaned_data['annotation_name']
logger.info("Saving to db: " + annotation_name)
annotation = Annotation.objects.create(name=annotation_name)
for form in formset:
state = form.cleaned_data.get('annotation_state')
if state:
PossibleValue.objects.create(annotation=annotation, value=state)
logger.info("Saved to db: " + annotation_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)

View File

@ -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):