Merge branch 'main' into bugfix/170_180

This commit is contained in:
cayop 2025-02-20 16:31:40 +00:00
commit bef3fb6cf9
24 changed files with 528 additions and 84 deletions

View file

@ -0,0 +1,173 @@
{% extends "base.html" %}
{% load i18n django_bootstrap5 %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-end">
<button type="button" class="btn btn-green-admin" data-bs-toggle="modal" data-bs-target="#addLotTagModal">
{% trans "Add" %}
</button>
</div>
</div>
<div class="row mt-4">
<div class="col">
{% if lot_tags_edit %}
<table class="table table-hover table-bordered align-middle">
<thead class="table-light">
<tr>
<th scope="col">{% trans "Lot Group Name" %}
</th>
<th scope="col" width="15%" class="text-center">{% trans "Actions" %}
</th>
</tr>
</thead>
<tbody id="sortable_list">
{% for tag in lot_tags_edit %}
<tr>
<td class="font-monospace">
{{ tag.name }}
</td>
<!-- action buttons -->
<td>
<div class="btn-group float-end">
<button
type="button"
class="btn btn-sm btn-outline-info d-flex align-items-center"
data-bs-toggle="modal" data-bs-target="#editLotTagModal{{ tag.id }}">
<i class="bi bi-pencil me-1"></i>
{% trans 'Edit' %}
</button>
<button
type="button" class="btn btn-sm btn-outline-danger d-flex align-items-center"
data-bs-toggle="modal"
data-bs-target="#deleteLotTagModal{{ tag.id }}" >
<i class="bi bi-trash me-1"></i>
{% trans 'Delete' %}
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-primary text-center mt-5" role="alert">
{% trans "No Lot Groups found on current organization" %}
</div>
{% endif %}
</div>
</div>
<!-- add lot tag Modal -->
<div class="modal fade" id="addLotTagModal" tabindex="-1" aria-labelledby="addLoTagModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addLotTagModalLabel">{% trans "Add Lot Group" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'admin:add_lot_tag' %}">
{% csrf_token %}
<div class="mb-3">
<label for="lotTagInput" class="form-label">{% trans "Tag" %}</label>
<input type="text" class="form-control" id="lotTagInput" name="name" maxlength="50" required>
<div class="form-text">{% trans "Maximum 50 characters." %}</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Add Lot tag" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Edit Lot Group Modals -->
{% for tag in lot_tags_edit %}
<div class="modal fade" id="editLotTagModal{{ tag.id }}" tabindex="-1" aria-labelledby="editLotTagModalLabel{{ tag.id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="{% url 'admin:edit_lot_tag' tag.id %}">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="editLotTagModalLabel{{ tag.id }}">
{% trans "Edit Lot Group" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="editLotTagInput{{ tag.id }}" class="form-label">{% trans "Tag" %}</label>
<input type="text" class="form-control" id="editLotTagInput{{ tag.id }}" name="name" maxlength="50" value="{{ tag.name }}" required>
<div class="form-text">{% trans "Maximum 50 characters." %}</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-green-admin">{% trans "Save Changes" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endfor %}
<!-- delete lot tag definition Modal -->
{% for tag in lot_tags_edit %}
<div class="modal fade" id="deleteLotTagModal{{ tag.id }}" tabindex="-1" aria-labelledby="deleteLotTagModalLabel{{ tag.id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="deleteLotTagModalLabel{{ tag.id }}">
{% trans "Delete Lot Group" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div>
<div class="modal-body">
{% if tag.lot_set.first %}
<div class="alert alert-warning text-center" role="alert">
{% trans "Failed to remove Lot Group, it is not empty" %}
</div>
{% endif %}
<div class="d-flex align-items-center border rounded p-3 mt-3">
<div>
<p class="mb-0 fw-bold">{{ tag.name }}</p>
</div>
</div>
</div>
<div class="modal-footer">
<form method="post" action="{% url 'admin:delete_lot_tag' tag.pk %}">
{% csrf_token %}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% trans "Cancel" %}
</button>
{% if tag.lot_set.first %}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% trans "Delete" %}
</button>
{% else %}
<button type="submit" class="btn btn-danger">
{% trans "Delete" %}
</button>
{% endif %}
</form>
</div>
</div>
</div>
</div>
{% endfor %}
{% endblock %}

View file

@ -15,4 +15,8 @@ urlpatterns = [
path("states/delete/<int:pk>", views.DeleteStateDefinitionView.as_view(), name='delete_state_definition'),
path("states/update_order/", views.UpdateStateOrderView.as_view(), name='update_state_order'),
path("states/edit/<int:pk>/", views.UpdateStateDefinitionView.as_view(), name='edit_state_definition'),
path("lot/", views.LotTagPanelView.as_view(), name="tag_panel"),
path("lot/add", views.AddLotTagView.as_view(), name="add_lot_tag"),
path("lot/delete/<int:pk>", views.DeleteLotTagView.as_view(), name='delete_lot_tag'),
path("lot/edit/<int:pk>/", views.UpdateLotTagView.as_view(), name='edit_lot_tag'),
]

View file

@ -18,6 +18,7 @@ from admin.forms import OrderingStateForm
from user.models import User, Institution
from admin.email import NotifyActivateUserByEmail
from action.models import StateDefinition
from lot.models import LotTag
class AdminView(DashboardView):
@ -112,6 +113,99 @@ class EditUserView(AdminView, UpdateView):
return kwargs
class LotTagPanelView(AdminView, TemplateView):
template_name = "lot_tag_panel.html"
title = _("Lot Groups Panel")
breadcrumb = _("admin / Lot Groups Panel")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
lot_tags = LotTag.objects.filter(
owner=self.request.user.institution
)
context.update({"lot_tags_edit": lot_tags})
return context
class AddLotTagView(AdminView, CreateView):
template_name = "lot_tag_panel.html"
title = _("New lot group Definition")
breadcrumb = "Admin / New lot tag"
success_url = reverse_lazy('admin:tag_panel')
model = LotTag
fields = ('name',)
def form_valid(self, form):
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
name = form.instance.name
if LotTag.objects.filter(name=name).first():
msg = _(f"The name '{name}' exist.")
messages.error(self.request, msg)
return redirect(self.success_url)
response = super().form_valid(form)
messages.success(self.request, _("Lot Group successfully added."))
return response
class DeleteLotTagView(AdminView, DeleteView):
model = LotTag
success_url = reverse_lazy('admin:tag_panel')
def post(self, request, *args, **kwargs):
pk = kwargs.get('pk')
self.object = get_object_or_404(
self.model,
owner=self.request.user.institution,
pk=pk
)
if self.object.lot_set.first():
msg = _('This group have lots. Impossible to delete.')
messages.warning(self.request, msg)
return redirect(reverse_lazy('admin:tag_panel'))
if self.object.inbox:
msg = f"The lot group '{self.object.name}'"
msg += " is INBOX, so it cannot be deleted, only renamed."
messages.error(self.request, msg)
return redirect(self.success_url)
response = super().delete(request, *args, **kwargs)
msg = _('Lot Group has been deleted.')
messages.success(self.request, msg)
return response
class UpdateLotTagView(AdminView, UpdateView):
model = LotTag
template_name = 'lot_tag_panel.html'
fields = ['name']
success_url = reverse_lazy('admin:tag_panel')
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.object = get_object_or_404(
self.model,
owner=self.request.user.institution,
pk=pk
)
return super().get_form_kwargs()
def form_valid(self, form):
name = form.instance.name
if LotTag.objects.filter(name=name).first():
msg = _(f"The name '{name}' exist.")
messages.error(self.request, msg)
return redirect(self.success_url)
response = super().form_valid(form)
msg = _("Lot Group updated successfully.")
messages.success(self.request, msg)
return response
class InstitutionView(AdminView, UpdateView):
template_name = "institution.html"
title = _("Edit institution")
@ -124,7 +218,8 @@ class InstitutionView(AdminView, UpdateView):
"logo",
"location",
"responsable_person",
"supervisor_person"
"supervisor_person",
"algorithm"
)
def get_form_kwargs(self):
@ -180,14 +275,11 @@ class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessM
def get_success_message(self, cleaned_data):
return f'State definition: {self.object.state}, has been deleted'
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
#only an admin of current institution can delete
if not object.institution == self.request.user.institution:
def form_valid(self, form):
if not self.object.institution == self.request.user.institution:
raise Http404
return super().delete(request, *args, **kwargs)
return super().form_valid(form)
class UpdateStateOrderView(AdminView, TemplateView):

View file

@ -8,6 +8,7 @@ from django.views.generic.base import TemplateView
from device.models import Device
from evidence.models import SystemProperty
from lot.models import LotTag
from action.models import StateDefinition
class Http403(PermissionDenied):
@ -32,7 +33,12 @@ class DashboardView(LoginRequiredMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
lot_tags = LotTag.objects.filter(
owner=self.request.user.institution,
inbox=False
)
context.update({
"inbox": LotTag.objects.get(inbox=True).name,
"commit_id": settings.COMMIT,
'title': self.title,
'subtitle': self.subtitle,
@ -41,7 +47,7 @@ class DashboardView(LoginRequiredMixin):
'section': self.section,
'path': resolve(self.request.path).url_name,
'user': self.request.user,
'lot_tags': LotTag.objects.filter(owner=self.request.user.institution)
'lot_tags': lot_tags
})
return context

View file

@ -82,11 +82,11 @@
<ul class="nav flex-column">
{% if user.is_admin %}
<li class="nav-item">
<a class="admin {% if path in 'panel users states_panel edit_user delete_user new_user institution' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_admin" aria-expanded="false" aria-controls="ul_admin" href="javascript:void()">
<a class="admin {% if path in 'panel users states_panel tag_panel edit_user delete_user new_user institution' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_admin" aria-expanded="false" aria-controls="ul_admin" href="javascript:void()">
<i class="bi bi-person-fill-gear icon_sidebar"></i>
{% trans 'Admin' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'panel institution users edit_user new_user delete_user states_panel' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu">
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'panel users tag_panel states_panel' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path in 'panel institution' %} active2{% endif %}" href="{% url 'admin:panel' %}">
{% trans 'Panel' %}
@ -100,33 +100,44 @@
<li class="nav-item">
<a class="nav-link{% if path == 'states_panel' %} active2{% endif %}" href="{% url 'admin:states_panel' %}">
{% trans 'States' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'tag_panel' %} active2{% endif %}" href="{% url 'admin:tag_panel' %}">
{% trans 'Lot Groups' %}
</a>
</li>
</ul>
</li>
{% endif %}
<li class="nav-item">
<a class="admin {% if path == 'unassigned_devices' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_devices" aria-expanded="false" aria-controls="ul_devices" href="javascript:void()">
<a class="admin {% if path in 'all_device' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_device" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()">
<i class="bi bi-laptop icon_sidebar"></i>
{% trans 'Devices' %}
{% trans 'Device' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path == 'unassigned_devices' %}expanded{% else %}collapse{% endif %}" id="ul_devices" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'unassigned_devices' %} active2{% endif %}" href="{% url 'dashboard:unassigned_devices' %}">
{% trans 'Unassigned devices' %}
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'all_device' %}expanded{% else %}collapse{% endif %}" id="ul_device" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'all_device' %} active2{% endif %}" href="{% url 'dashboard:all_device' %}">
{% trans 'All' %}
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a class="admin {% if path == 'tag' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_lots" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()">
<a class="admin {% if path == 'tags' or path == 'lot' or path in 'unassigned dashboard' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_lots" aria-expanded="false" aria-controls="ul_lots" href="javascript:void()">
<i class="bi bi-database icon_sidebar"></i>
{% trans 'Lots' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path == 'tag' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path == 'tags' or path == 'lot' or path in 'unassigned dashboard' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'unassigned' %} active2{% endif %}" href="{% url 'dashboard:unassigned' %}">
{{ inbox }}
</a>
</li>
{% for tag in lot_tags %}
<li class="nav-items">
<a class="nav-link{% if path == 'tag' %} active2{% endif %}" href="{% url 'lot:tag' tag.id %}">
<a class="nav-link{% if path == 'tags' %} active2{% endif %}" href="{% url 'lot:tags' tag.id %}">
{{ tag.name }}
</a>
</li>
@ -134,37 +145,29 @@
</ul>
</li>
<li class="nav-item">
<a class="admin {% if path in 'upload list' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_evidences" aria-expanded="false" aria-controls="ul_evidences" href="javascript:void()">
<a class="admin {% if path in 'upload list import add' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_evidences" aria-expanded="false" aria-controls="ul_evidences" href="javascript:void()">
<i class="bi bi-usb-drive icon_sidebar"></i>
{% trans 'Evidences' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'upload list' %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'upload' %} active2{% endif %}" href="{% url 'evidence:upload' %}">
{% trans 'Upload one' %}
</a>
</li>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'upload list import add' %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'list' %} active2{% endif %}" href="{% url 'evidence:list' %}">
{% trans 'Old evidences' %}
{% trans 'List of evidences' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'upload' %} active2{% endif %}" href="{% url 'evidence:upload' %}">
{% trans 'Upload with JSON file' %}
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a class="admin {% if path in 'import add' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_placeholders" aria-expanded="false" aria-controls="ul_placeholders" href="javascript:void()">
<i class="bi-menu-button-wide icon_sidebar"></i>
{% trans 'Placeholders' %}
</a>
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'import add' %}expanded{% else %}collapse{% endif %}" id="ul_placeholders" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'import' %} active2{% endif %}" href="{% url 'evidence:import' %}">
{% trans 'Upload Spreadsheet' %}
{% trans 'Upload with Spreadsheet' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'add' %} active2{% endif %}" href="{% url 'device:add' %}">
{% trans 'Create one' %}
{% trans 'Upload with Web Form' %}
</a>
</li>
</ul>

View file

@ -25,6 +25,9 @@
<div class="dataTable-container">
<form method="post">
{% csrf_token %}
Lot actions: <button class="btn btn-green-admin" type="submit" name="url" value="{% url 'lot:add_devices' %}">Add</button> <button class="btn btn-green-admin" type="submit" value="{% url 'lot:del_devices' %}" name="url">Remove</button>
<table class="table">
<thead>
<tr>
@ -43,6 +46,9 @@
<th scope="col" data-sortable="">
model
</th>
<th scope="col" data-sortable="">
updated
</th>
</tr>
</thead>
{% for dev in devices %}
@ -69,11 +75,13 @@
{{ dev.model }}
{% endif %}
</td>
<td>
{{ dev.updated }}
</td>
</tr>
</tbody>
{% endfor %}
</table>
<button class="btn btn-green-admin" type="submit" value="{% url 'lot:del_devices' %}" name="url">Remove</button> <button class="btn btn-green-admin" type="submit" name="url" value="{% url 'lot:add_devices' %}">add</button>
</form>
</div>
<div class="row mt-3">

View file

@ -1,10 +1,11 @@
from django.urls import path
from dashboard import views
from dashboard import views
app_name = 'dashboard'
urlpatterns = [
path("", views.UnassignedDevicesView.as_view(), name="unassigned_devices"),
path("", views.UnassignedDevicesView.as_view(), name="unassigned"),
path("all", views.AllDevicesView.as_view(), name="all_device"),
path("<int:pk>/", views.LotDashboardView.as_view(), name="lot"),
path("search", views.SearchView.as_view(), name="search"),
]

View file

@ -22,6 +22,16 @@ class UnassignedDevicesView(InventaryMixin):
return Device.get_unassigned(self.request.user.institution, offset, limit)
class AllDevicesView(InventaryMixin):
template_name = "unassigned_devices.html"
section = "All"
title = _("All Devices")
breadcrumb = "Devices / All Devices"
def get_devices(self, user, offset, limit):
return Device.get_all(self.request.user.institution, offset, limit)
class LotDashboardView(InventaryMixin, DetailsMixin):
template_name = "unassigned_devices.html"
section = "dashboard_lot"

View file

@ -136,6 +136,87 @@ class Device:
self.lots = [
x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
@classmethod
def get_all(cls, institution, offset=0, limit=None):
sql = """
WITH RankedProperties AS (
SELECT
t1.value,
t1.key,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = '{algorithm}' THEN 2
END,
t1.created DESC
) AS row_num
FROM evidence_systemproperty AS t1
WHERE t1.owner_id = {institution}
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT DISTINCT
value
FROM
RankedProperties
WHERE
row_num = 1
""".format(
institution=institution.id,
algorithm=institution.algorithm,
)
if limit:
sql += " limit {} offset {}".format(int(limit), int(offset))
sql += ";"
annotations = []
with connection.cursor() as cursor:
cursor.execute(sql)
annotations = cursor.fetchall()
devices = [cls(id=x[0]) for x in annotations]
count = cls.get_all_count(institution)
return devices, count
@classmethod
def get_all_count(cls, institution):
sql = """
WITH RankedProperties AS (
SELECT
t1.value,
t1.key,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = '{algorithm}' THEN 2
END,
t1.created DESC
) AS row_num
FROM evidence_systemproperty AS t1
WHERE t1.owner_id = {institution}
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT
COUNT(DISTINCT value)
FROM
RankedProperties
WHERE
row_num = 1
""".format(
institution=institution.id,
algorithm=institution.algorithm
)
with connection.cursor() as cursor:
cursor.execute(sql)
return cursor.fetchall()[0][0]
@classmethod
def get_unassigned(cls, institution, offset=0, limit=None):
@ -149,8 +230,7 @@ class Device:
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'ereuse24' THEN 2
ELSE 3
WHEN t1.key = '{algorithm}' THEN 2
END,
t1.created DESC
) AS row_num
@ -158,6 +238,7 @@ class Device:
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
WHERE t2.device_id IS NULL
AND t1.owner_id = {institution}
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT DISTINCT
value
@ -167,6 +248,7 @@ class Device:
row_num = 1
""".format(
institution=institution.id,
algorithm=institution.algorithm
)
if limit:
sql += " limit {} offset {}".format(int(limit), int(offset))
@ -195,8 +277,7 @@ class Device:
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'ereuse24' THEN 2
ELSE 3
WHEN t1.key = '{algorithm}' THEN 2
END,
t1.created DESC
) AS row_num
@ -204,6 +285,7 @@ class Device:
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
WHERE t2.device_id IS NULL
AND t1.owner_id = {institution}
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT
COUNT(DISTINCT value)
@ -213,6 +295,7 @@ class Device:
row_num = 1
""".format(
institution=institution.id,
algorithm=institution.algorithm
)
with connection.cursor() as cursor:
cursor.execute(sql)
@ -230,16 +313,14 @@ class Device:
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'ereuse24' THEN 2
ELSE 3
WHEN t1.key = '{algorithm}' THEN 2
END,
t1.created DESC
) AS row_num
FROM evidence_systemproperty AS t1
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
WHERE t2.device_id IS NULL
AND t1.owner_id = {institution}
WHERE t1.owner_id = {institution}
AND t1.uuid = '{uuid}'
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT DISTINCT
value
@ -250,6 +331,7 @@ class Device:
""".format(
uuid=uuid.replace("-", ""),
institution=institution.id,
algorithm=institution.algorithm,
)
properties = []
@ -274,6 +356,12 @@ class Device:
self.get_last_evidence()
return self.last_evidence.get_manufacturer()
@property
def updated(self):
"""get timestamp from last evidence created"""
self.get_last_evidence()
return self.last_evidence.created
@property
def serial_number(self):
self.get_last_evidence()

View file

@ -78,7 +78,7 @@
{% endfor %}
</div>
<div class="container">
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>

View file

@ -40,7 +40,7 @@ class NewDeviceView(DashboardView, FormView):
template_name = "new_device.html"
title = _("New Device")
breadcrumb = "Device / New Device"
success_url = reverse_lazy('dashboard:unassigned_devices')
success_url = reverse_lazy('dashboard:unassigned')
form_class = DeviceFormSet
def form_valid(self, form):

View file

@ -27,7 +27,7 @@ class BuildMix:
hid = ""
for f in algorithm:
if hasattr(self, f):
hid += getattr(self, f)
hid += getattr(self, f) or ''
return hid
def generate_chids(self):

View file

@ -11,8 +11,8 @@
<!-- override invalid-feedback class -->
<style>
.invalid-feedback {
color: #670000;
font-size: 1rem;
color: #670000;
font-size: 1rem;
}
</style>
@ -22,7 +22,7 @@
{% bootstrap_form form alert_error_type="none" error_css_class="alert alert-danger alert-icon alert-icon-border" %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>

View file

@ -17,18 +17,18 @@ class LoginView(auth_views.LoginView):
template_name = 'login.html'
extra_context = {
'title': _('Login'),
'success_url': reverse_lazy('dashboard:unassigned_devices'),
'success_url': reverse_lazy('dashboard:unassigned'),
'commit_id': settings.COMMIT,
}
def get(self, request, *args, **kwargs):
self.extra_context['success_url'] = request.GET.get(
'next',
reverse_lazy('dashboard:unassigned_devices')
reverse_lazy('dashboard:unassigned')
)
if not self.request.user.is_anonymous:
return redirect(reverse_lazy('dashboard:unassigned_devices'))
return redirect(reverse_lazy('dashboard:unassigned'))
return super().get(request, *args, **kwargs)
def form_valid(self, form):
@ -72,4 +72,3 @@ class PasswordResetView(auth_views.PasswordResetView):
except Exception as err:
logger.error(err)
return HttpResponseRedirect(self.success_url)

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-02-17 10:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('lot', '0006_lotproperty_property_unique_type_key_lot'),
]
operations = [
migrations.AddField(
model_name='lottag',
name='inbox',
field=models.BooleanField(default=False),
),
]

View file

@ -15,6 +15,7 @@ class LotTag(models.Model):
name = models.CharField(max_length=STR_SIZE, blank=False, null=False)
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
inbox = models.BooleanField(default=False)
def __str__(self):
return self.name
@ -45,7 +46,7 @@ class Lot(models.Model):
for d in DeviceLot.objects.filter(lot=self, device_id=v):
d.delete()
class LotProperty (Property):
class LotProperty(Property):
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
class Type(models.IntegerChoices):

View file

@ -30,7 +30,7 @@
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
</div>

View file

@ -11,20 +11,26 @@
<form method="post">
{% csrf_token %}
{% for tag in lot_tags %}
<div class="row">
<div class="col-lg-3 col-md-4 label ">{{ tag }}</div>
</div>
{% for lot in lots %}
{% if lot.type == tag %}
<div class="row">
<div class="col-lg-3 col-md-4 label "><input type="checkbox" name="lots" value="{{ lot.id }}" /></div>
<div class="col-lg-3 col-md-4 label ">{{ lot.name }}</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}
<table class="table">
<thead>
<tr>
<th>Select</th>
<th>Lot Group / Lot</th>
</tr>
</thead>
<tbody>
{% for tag in lot_tags %}
{% for lot in lots %}
{% if lot.type == tag %}
<tr>
<td><input type="checkbox" name="lots" value="{{ lot.id }}" /></td>
<td>{{ tag }}/{{ lot.name }}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
<button class="btn btn-green-admin" type="submit">Save</button>
</form>

View file

@ -24,7 +24,7 @@
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>

View file

@ -9,7 +9,7 @@ urlpatterns = [
path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"),
path("add/devices/", views.AddToLotView.as_view(), name="add_devices"),
path("del/devices/", views.DelToLotView.as_view(), name="del_devices"),
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
path("group/<int:pk>/", views.LotsTagsView.as_view(), name="tags"),
path("<int:pk>/property", views.LotPropertiesView.as_view(), name="properties"),
path("<int:pk>/property/add", views.AddLotPropertyView.as_view(), name="add_property"),
path("<int:pk>/property/update", views.UpdateLotPropertyView.as_view(), name="update_property"),

View file

@ -18,7 +18,7 @@ class NewLotView(DashboardView, CreateView):
template_name = "new_lot.html"
title = _("New lot")
breadcrumb = "lot / New lot"
success_url = reverse_lazy('dashboard:unassigned_devices')
success_url = reverse_lazy('dashboard:unassigned')
model = Lot
fields = (
"type",
@ -28,6 +28,14 @@ class NewLotView(DashboardView, CreateView):
"closed",
)
def get_form(self):
form = super().get_form()
form.fields["type"].queryset = LotTag.objects.filter(
owner=self.request.user.institution,
inbox=False
)
return form
def form_valid(self, form):
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
@ -39,7 +47,7 @@ class DeleteLotView(DashboardView, DeleteView):
template_name = "delete_lot.html"
title = _("Delete lot")
breadcrumb = "lot / Delete lot"
success_url = reverse_lazy('dashboard:unassigned_devices')
success_url = reverse_lazy('dashboard:unassigned')
model = Lot
fields = (
"type",
@ -58,7 +66,7 @@ class EditLotView(DashboardView, UpdateView):
template_name = "new_lot.html"
title = _("Edit lot")
breadcrumb = "Lot / Edit lot"
success_url = reverse_lazy('dashboard:unassigned_devices')
success_url = reverse_lazy('dashboard:unassigned')
model = Lot
fields = (
"type",
@ -84,7 +92,7 @@ class AddToLotView(DashboardView, FormView):
template_name = "list_lots.html"
title = _("Add to lots")
breadcrumb = "lot / add to lots"
success_url = reverse_lazy('dashboard:unassigned_devices')
success_url = reverse_lazy('dashboard:unassigned')
form_class = LotsForm
def get_context_data(self, **kwargs):
@ -125,7 +133,7 @@ class LotsTagsView(DashboardView, TemplateView):
template_name = "lots.html"
title = _("lots")
breadcrumb = _("lots") + " /"
success_url = reverse_lazy('dashboard:unassigned_devices')
success_url = reverse_lazy('dashboard:unassigned')
def get_context_data(self, **kwargs):
self.pk = kwargs.get('pk')

View file

@ -2,6 +2,7 @@ from django.core.management.base import BaseCommand
from user.models import Institution
from lot.models import LotTag
class Command(BaseCommand):
help = "Create a new Institution"
@ -13,6 +14,11 @@ class Command(BaseCommand):
self.create_lot_tags()
def create_lot_tags(self):
LotTag.objects.create(
inbox=True,
name="Inbox",
owner=self.institution
)
tags = [
"Entrada",
"Salida",

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2025-02-18 08:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='institution',
name='algorithm',
field=models.CharField(choices=[('ereuse24', 'ereuse24'), ('ereuse22', 'ereuse22')], default='ereuse24', max_length=30),
),
]

View file

@ -1,8 +1,10 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
from utils.constants import ALGOS
# Create your models here.
ALGORITHMS = [(x, x) for x in ALGOS.keys()]
class Institution(models.Model):
@ -27,6 +29,7 @@ class Institution(models.Model):
blank=True,
null=True
)
algorithm = models.CharField(max_length=30, choices=ALGORITHMS, default='ereuse24')
class UserManager(BaseUserManager):