Compare commits

..

108 commits

Author SHA1 Message Date
Cayo Puigdefabregas ca6d98368d fix annotations in parse 2025-01-23 13:05:09 +01:00
Cayo Puigdefabregas d8b1a632e6 fix base template 2025-01-23 12:36:29 +01:00
Cayo Puigdefabregas 684b20966f split long line 2025-01-23 12:36:29 +01:00
Cayo Puigdefabregas 840795b759 add version 2025-01-23 12:36:29 +01:00
Cayo Puigdefabregas 65d1ad2017 replace waring for yellow in details 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 37786cadae deleting obsolete if statement 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 365f92601e renamed property variable to prop 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1b90eea6de added missing components tab 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1ea4c0d2e5 added logging for evidence tag changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 79f0d50f7a text size adjustment and displaying none states 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 9d90377e03 more contrast on save note button 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 7aedd5671e states and notes view refactoring 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5656e6553f userproperties views refactoring 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ea0d9e7ddc better bootstrap tables 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki e704d99723 view changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki b1fcdea493 better representiation of delete/edit notes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 477f5a06d0 edit update cannot be blank 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 912b27c1d5 adding remove button for notes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3efe74c638 notes now can be updated 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 0ed78d997c deleted undo state view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 4c93a27109 added a sidebar notes display 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 975f2ab126 changes to state defiinitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 41780cc592 better success message and removed devicelog var 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 22ebe3da1e normalized deviceLog messages 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki d8ac5cb203 command for adding default states 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 92419b89d0 erased old logger 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2605844dd4 minor cosmetic changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki d35cdb1fd7 state definition list changes and disabled logging 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3e04fba3ab default value for no state 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 755ca388eb adding orm migrations 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 966d7d591b log list now shows log table 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki bb7ed1ed5c added logging for states, user properties and notes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 070004f9ea simpler state change and action input 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 63b8815d1b current state table erased and spacing fix 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ecf0d5953f deleted unique constraint on userproperty 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 29acd1faab notes and log models added 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki d805d03075 device tag bugfix 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 661e3510e2 lotproperties delete and update added 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki a2b702cc82 added action migration 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki cf50e9c738 now check for state on delete modal 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki dbc37aa530 cosmetic changes to statesdefinitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 623d285691 change state delete to undo last state 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ed60a8df0d help icon added and new icon on add button 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2e06a7b699 minor changes to states view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2e90a50fd6 statedefinitions update and delete popup changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki f009f10afa changes to state definitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 70061bcc78 statedefinitions edit popup added and url changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 4f77ce1e7e state button rework and warning if same state 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki e646cdfa56 current state helper function added 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 268666909f Device-details html modularized into several files 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fdcef2216e statedefinitions list updated 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 97af732df6 updated logging for states 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 316637e3ab statedefinitions delete now uses correct id 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 69f51ccad0 logging for statedefinitions 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 508cd034d1 better delete modal 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 9baae6293f erase_server type now userproperty 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 23b253cb89 deleted obsolete field type for sysproperties 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 9000826d68 updated views for new model structure 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki f872a7ec52 more property model refactoring 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki a83792a838 property models refactor 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5c4d2bd6a9 added logging for user property creation 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 0d9dc90362 made log var a env variable 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 846166bd9f added loggin for state actions 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 80d282f054 added state delete 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5c2d59aa1a changed tab to log and added logging 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fc5532b55b changed state visual and ordering 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fe171b8d9d minor fix for correct tab handling 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5b33bf000f added current state to device details 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 6bd822f732 added view and url for new action 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2f718dfb92 state institution now nullable 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1634437ae7 condition checking and renaming 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 603d31161a stylish new popup for state change 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 6b5056957f added Sortable js dependency for tables 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2ee1396eae added form for state definition order update 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 76ada577c0 minor cosmetic changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki bc0e7e8f41 changed delete button to icon 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 50c1662a31 send button hidden until changes are made 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2fd2ed3e36 sortable list now updates order of definitions 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 6301edf4bb added Sortable js for state definitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 97d1900e44 added model level constraints 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3bb1145a10 modals for state definition deletion 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 127f6fb42f delete view added and some refactorig 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 8b7782a61c added add state definition view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5c64c86a57 changed constrain on model 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1fc3c07816 fixed models confusion 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 7d99ccc4f9 added model constraints 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ad01b4fefc added admin state definition panel 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 47b721ed6e initial orm state models 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3a8b686177 centered popup modals 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 945d97afd6 disabled lotPoperty field 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki c3aa13a423 added logging for device operations 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 30e657fd79 renaming lotAnnotations to Lotproperty 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki cdcf39296e renaming of annotation to property 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fbee2849c9 change edit view to modal popup 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki f4284894fe added userproperty update view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 142edac285 added user_property 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5be16a8e05 model constraints changed and moved url to /device 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 56c6bb69ac fixed search and moved delete user property class 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 03ddd9b8d9 renaming to new Property tables 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1dba811943 details view changed to now use properties 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 23f732a04e fixed user_properties list not working 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki dbff63eed6 renaming to property 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ade8898fca changed imports 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 279d20b9d0 fixed self inflicted recursion 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1612567bf6 renaming annotation to variable 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki b1ed04e956 variables and function semantic renaming 2025-01-23 12:36:24 +01:00
Thomas Nahuel Rusiecki af5418d2a0 split into system and user properties 2025-01-23 12:33:17 +01:00
Thomas Nahuel Rusiecki 1b6af509e1 annotations renaming on views 2025-01-23 12:33:17 +01:00
91 changed files with 1056 additions and 1785 deletions

View file

@ -1,3 +0,0 @@
db
.git
already_configured

View file

@ -1,17 +0,0 @@
# repository editor configuration normalization
# @see http://editorconfig.org/
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.sh]
indent_size = 8

View file

@ -1,15 +1,5 @@
####
# DEV OPTIONS
####
DEV_DOCKER_ALWAYS_BUILD=false
####
# DEVICEHUB
####
DEVICEHUB_DOMAIN=localhost
DEVICEHUB_PORT=8001
DH_DOMAIN=localhost
DH_PORT=8000
DEMO=true
# note that with DEBUG=true, logs are more verbose (include tracebacks)
DEBUG=true
@ -26,48 +16,7 @@ EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
EMAIL_FILE_PATH="/tmp/app-messages"
ENABLE_EMAIL=false
PREDEFINED_TOKEN='5018dd65-9abd-4a62-8896-80f34ac66150'
DEVICEHUB_ALLOWED_HOSTS=${DEVICEHUB_DOMAIN},${DEVICEHUB_DOMAIN}:${DEVICEHUB_PORT},127.0.0.1,127.0.0.1:${DEVICEHUB_PORT}
DH_ALLOWED_HOSTS=${DH_DOMAIN},${DH_DOMAIN}:${DH_PORT},127.0.0.1,127.0.0.1:${DH_PORT}
# TODO review these vars
#SNAPSHOTS_DIR=/path/to/TODO
#EVIDENCES_DIR=/path/to/TODO
#DEMO_IDHUB_DOMAIN='idhub.example.org'
####
# IDHUB
####
IDHUB_ENABLED=false
IDHUB_DOMAIN=localhost
IDHUB_PORT=9001
IDHUB_ALLOWED_HOSTS=${IDHUB_DOMAIN},${IDHUB_DOMAIN}:${IDHUB_PORT},127.0.0.1,127.0.0.1:${IDHUB_PORT}
IDHUB_TIME_ZONE='Europe/Madrid'
#IDHUB_SECRET_KEY='uncomment-it-and-fill-this'
# enable dev flags when DEVELOPMENT deployment
# adapt to your domain in a production/reverse proxy env
IDHUB_CSRF_TRUSTED_ORIGINS='https://idhub.example.org'
# fill this section with your email credentials
IDHUB_DEFAULT_FROM_EMAIL="user@example.org"
IDHUB_EMAIL_HOST="smtp.example.org"
IDHUB_EMAIL_HOST_USER="smtp_user"
IDHUB_EMAIL_HOST_PASSWORD="smtp_passwd"
IDHUB_EMAIL_PORT=25
IDHUB_EMAIL_USE_TLS=True
IDHUB_EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
# replace with production data
# this is used when IDHUB_DEPLOYMENT is not equal to DEVELOPMENT
IDHUB_ADMIN_USER='admin'
IDHUB_ADMIN_PASSWD='admin'
IDHUB_ADMIN_EMAIL='admin@example.org'
# this option needs to be set to 'n' to be able to make work idhub in docker
# by default it is set to 'y' to facilitate idhub dev when outside docker
IDHUB_SYNC_ORG_DEV='n'
# TODO that is only for testing
IDHUB_ENABLE_EMAIL=false
IDHUB_ENABLE_2FACTOR_AUTH=false
IDHUB_ENABLE_DOMAIN_CHECKER=false
IDHUB_PREDEFINED_TOKEN='27f944ce-3d58-4f48-b068-e4aa95f97c95'

5
.gitignore vendored
View file

@ -1,7 +1,4 @@
db.sqlite3
env/
__pycache__/
.env
# the following could be autogenerated by devicehub
db.sqlite3
example/snapshots/snapshot_workbench-script_verifiable-credential.json

View file

@ -14,7 +14,7 @@ class State(models.Model):
def clean(self):
if not StateDefinition.objects.filter(institution=self.institution, state=self.state).exists():
raise ValidationError(f"The state '{self.state}' is not valid for the institution '{self.institution.name}'.")
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
@ -22,7 +22,6 @@ class State(models.Model):
def __str__(self):
return f"{self.institution.name} - {self.state} - {self.snapshot_uuid}"
class StateDefinition(models.Model):
institution = models.ForeignKey(Institution, on_delete=models.CASCADE)
@ -42,7 +41,7 @@ class StateDefinition(models.Model):
self.order = (max_order or 0) + 1
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
institution = self.institution
order = self.order

View file

@ -2,7 +2,6 @@ from django.views import View
from django.shortcuts import redirect, get_object_or_404
from django.contrib import messages
from action.forms import ChangeStateForm, AddNoteForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import DeleteView, CreateView, UpdateView, FormView
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
@ -10,7 +9,7 @@ from action.models import State, StateDefinition, Note, DeviceLog
from device.models import Device
class ChangeStateView(LoginRequiredMixin, FormView):
class ChangeStateView(FormView):
form_class = ChangeStateForm
def form_valid(self, form):
@ -43,7 +42,7 @@ class ChangeStateView(LoginRequiredMixin, FormView):
return self.request.META.get('HTTP_REFERER') or reverse_lazy('device:details')
class AddNoteView(LoginRequiredMixin, FormView):
class AddNoteView(FormView):
form_class = AddNoteForm
def form_valid(self, form):
@ -74,7 +73,7 @@ class AddNoteView(LoginRequiredMixin, FormView):
return self.request.META.get('HTTP_REFERER') or reverse_lazy('device:details')
class UpdateNoteView(LoginRequiredMixin, UpdateView):
class UpdateNoteView(UpdateView):
model = Note
fields = ['description']
pk_url_kwarg = 'pk'
@ -94,19 +93,19 @@ class UpdateNoteView(LoginRequiredMixin, UpdateView):
)
messages.success(self.request, "Note has been updated.")
return super().form_valid(form)
def form_invalid(self, form):
new_description = form.cleaned_data.get('description', '').strip()
if not new_description:
messages.error(self.request, _("Note cannot be empty."))
super().form_invalid(form)
return redirect(self.get_success_url())
def get_success_url(self):
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details'))
class DeleteNoteView(LoginRequiredMixin, View):
class DeleteNoteView(View):
model = Note
def post(self, request, *args, **kwargs):

View file

@ -1,173 +0,0 @@
{% 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,8 +15,4 @@ 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,7 +18,6 @@ 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):
@ -26,7 +25,7 @@ class AdminView(DashboardView):
response = super().get(*args, **kwargs)
if not self.request.user.is_admin:
raise Http403
return response
class PanelView(AdminView, TemplateView):
@ -112,100 +111,7 @@ class EditUserView(AdminView, UpdateView):
kwargs = super().get_form_kwargs()
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")
@ -218,8 +124,7 @@ class InstitutionView(AdminView, UpdateView):
"logo",
"location",
"responsable_person",
"supervisor_person",
"algorithm"
"supervisor_person"
)
def get_form_kwargs(self):
@ -263,9 +168,8 @@ class AddStateDefinitionView(AdminView, StateDefinitionContextMixin, CreateView)
messages.error(self.request, _("State is already defined."))
return self.form_invalid(form)
def form_invalid(self, form):
super().form_invalid(form)
return redirect(self.success_url)
def form_invalid(self, form):
return super().form_invalid(form)
class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessMessageMixin, DeleteView):
@ -275,11 +179,14 @@ class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessM
def get_success_message(self, cleaned_data):
return f'State definition: {self.object.state}, has been deleted'
def form_valid(self, form):
if not self.object.institution == self.request.user.institution:
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:
raise Http404
return super().form_valid(form)
return super().delete(request, *args, **kwargs)
class UpdateStateOrderView(AdminView, TemplateView):
@ -311,21 +218,14 @@ class UpdateStateDefinitionView(AdminView, UpdateView):
model = StateDefinition
template_name = 'states_panel.html'
fields = ['state']
success_url = reverse_lazy('admin:states_panel')
pk_url_kwarg = 'pk'
def get_queryset(self):
return StateDefinition.objects.filter(institution=self.request.user.institution)
def form_valid(self, form):
try:
response = super().form_valid(form)
messages.success(self.request, _("State definition updated successfully."))
return response
except IntegrityError:
messages.error(self.request, _("State is already defined."))
return self.form_invalid(form)
def get_success_url(self):
messages.success(self.request, _("State definition updated successfully."))
return reverse_lazy('admin:states_panel')
def form_invalid(self, form):
super().form_invalid(form)
return redirect(self.get_success_url())
def form_valid(self, form):
return super().form_valid(form)

View file

@ -90,7 +90,7 @@ class NewSnapshotView(ApiMixing):
ev_uuid = data["credentialSubject"].get("uuid")
if not ev_uuid:
txt = "error: the snapshot does not have an uuid"
txt = "error: the snapshot not have uuid"
logger.error("%s", txt)
return JsonResponse({'status': txt}, status=500)
@ -118,7 +118,7 @@ class NewSnapshotView(ApiMixing):
prop = SystemProperty.objects.filter(
uuid=ev_uuid,
# TODO this is hardcoded, it should select the user preferred algorithm
key="ereuse24",
key="hidalgo1",
owner=self.tk.owner.institution
).first()
@ -127,12 +127,12 @@ class NewSnapshotView(ApiMixing):
logger.error("Error: No property for uuid: %s", ev_uuid)
return JsonResponse({'status': 'fail'}, status=500)
url_args = reverse_lazy("device:details", args=(prop.value,))
url_args = reverse_lazy("device:details", args=(property.value,))
url = request.build_absolute_uri(url_args)
response = {
"status": "success",
"dhid": prop.value[:6].upper(),
"dhid": property.value[:6].upper(),
"url": url,
# TODO replace with public_url when available
"public_url": url

View file

@ -8,7 +8,6 @@ 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):
@ -33,9 +32,6 @@ 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,
)
context.update({
"commit_id": settings.COMMIT,
'title': self.title,
@ -45,7 +41,7 @@ class DashboardView(LoginRequiredMixin):
'section': self.section,
'path': resolve(self.request.path).url_name,
'user': self.request.user,
'lot_tags': lot_tags
'lot_tags': LotTag.objects.filter(owner=self.request.user.institution)
})
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 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()">
<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()">
<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 users tag_panel 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 institution users edit_user new_user delete_user 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,43 +100,33 @@
<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 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()">
<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()">
<i class="bi bi-laptop icon_sidebar"></i>
{% trans 'Device' %}
{% trans 'Devices' %}
</a>
<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' %}
<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' %}
</a>
</li>
</ul>
</li>
<li class="nav-item">
<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()">
<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()">
<i class="bi bi-database icon_sidebar"></i>
{% trans 'Lots' %}
</a>
<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">
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path == 'tag' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
{% for tag in lot_tags %}
<li class="nav-items">
{% if tag.inbox %}
<a class="nav-link{% if path == 'unassigned' %} active2{% endif %}" href="{% url 'dashboard:unassigned' %}">
{% else %}
<a class="nav-link{% if path == 'tags' %} active2{% endif %}" href="{% url 'lot:tags' tag.id %}">
{% endif %}
<a class="nav-link{% if path == 'tag' %} active2{% endif %}" href="{% url 'lot:tag' tag.id %}">
{{ tag.name }}
</a>
</li>
@ -144,29 +134,37 @@
</ul>
</li>
<li class="nav-item">
<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()">
<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()">
<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 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 'List of evidences' %}
</a>
</li>
<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 with JSON file' %}
{% trans 'Upload one' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'list' %} active2{% endif %}" href="{% url 'evidence:list' %}">
{% trans 'Old evidences' %}
</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 with Spreadsheet' %}
{% trans 'Upload Spreadsheet' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'add' %} active2{% endif %}" href="{% url 'device:add' %}">
{% trans 'Upload with Web Form' %}
{% trans 'Create one' %}
</a>
</li>
</ul>
@ -194,10 +192,10 @@
{% endif %}
</h1>
<form method="get" action="{% url 'dashboard:search' %}">
<form method="post" action="{% url 'dashboard:search' %}">
{% csrf_token %}
<div class="input-group rounded">
<input type="search" name="search" class="form-control rounded" {% if search %}value="{{ search }}" {% endif %}placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" />
<input type="search" name="search" class="form-control rounded" placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" />
<span class="input-group-text border-0" id="search-addon">
<i class="fas fa-search"></i>
</span>

View file

@ -4,12 +4,12 @@
<ul class="pagination">
{% if page_number > 1 %}
<li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
<a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}">
&laquo;
</a>
</li>
<li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}">
{% trans 'Previous' %}
</a>
</li>
@ -24,7 +24,7 @@
{% if p == page_number or p == "..." %}
href="#">
{% else %}
href="?page={{ p }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
href="?page={{ p }}&limit={{ limit }}">
{% endif %}
{{ p }}
</a>
@ -34,12 +34,12 @@
{% if page_number < total_pages %}
<li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}">
{% trans 'Next' %}
</a>
</li>
<li class="previous">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}{% if search %}&search={{ search }}{% endif %}">
<a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}">
&raquo;
</a>
</li>

View file

@ -9,6 +9,12 @@
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-center">
{% if lot %}
<a href="{% url 'lot:documents' object.id %}" type="button" class="btn btn-green-admin">
<i class="bi bi-folder2"></i>
{% trans 'Documents' %}
</a>
{% endif %}
<a href="{# url 'dashboard:exports' object.id #}" type="button" class="btn btn-green-admin">
<i class="bi bi-reply"></i>
{% trans 'Exports' %}
@ -25,9 +31,6 @@
<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>
@ -46,9 +49,6 @@
<th scope="col" data-sortable="">
model
</th>
<th scope="col" data-sortable="">
updated
</th>
</tr>
</thead>
{% for dev in devices %}
@ -75,18 +75,16 @@
{{ 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">
<div class="col">
{% render_pagination page total_pages limit search %}
{% render_pagination page total_pages limit %}
</div>
</div>
{% endblock %}

View file

@ -3,7 +3,7 @@ from django import template
register = template.Library()
@register.inclusion_tag('pagination.html')
def render_pagination(page_number, total_pages, limit=10, search=None):
def render_pagination(page_number, total_pages, limit=10):
"""
Template tag for render pagination
@ -16,6 +16,5 @@ def render_pagination(page_number, total_pages, limit=10, search=None):
return {
'page_number': page_number,
'total_pages': total_pages,
'limit': limit,
"search": search,
'limit': limit
}

View file

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

View file

@ -22,16 +22,6 @@ 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"
@ -52,18 +42,8 @@ class LotDashboardView(InventaryMixin, DetailsMixin):
"device_id", flat=True
).distinct()
props = SystemProperty.objects.filter(
owner=self.request.user.institution,
value__in=chids
).order_by("-created")
chids_ordered = []
for x in props:
if x.value not in chids_ordered:
chids_ordered.append(x.value)
chids_page = chids_ordered[offset:offset+limit]
return [Device(id=x) for x in chids_page], len(chids_ordered)
chids_page = chids[offset:offset+limit]
return [Device(id=x) for x in chids_page], chids.count()
class SearchView(InventaryMixin):
@ -72,20 +52,9 @@ class SearchView(InventaryMixin):
title = _("Search Devices")
breadcrumb = "Devices / Search Devices"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
search_params = self.request.GET.urlencode(),
search = self.request.GET.get("search")
if search:
context.update({
'search_params': search_params,
'search': search
})
return context
def get_devices(self, user, offset, limit):
query = dict(self.request.GET).get("search")
post = dict(self.request.POST)
query = post.get("search")
if not query:
return [], 0
@ -96,12 +65,6 @@ class SearchView(InventaryMixin):
offset,
limit
)
count = search(
self.request.user.institution,
query[0],
0,
9999
).size()
if not matches or not matches.size():
return self.search_hids(query, offset, limit)
@ -116,6 +79,7 @@ class SearchView(InventaryMixin):
devices.append(dev)
dev_id.append(dev.id)
count = matches.size()
# TODO fix of pagination, the count is not correct
return devices, count

View file

@ -71,6 +71,17 @@ class Device:
)
return user_properties
def get_user_documents(self):
if not self.uuids:
self.get_uuids()
user_properties = UserProperty.objects.filter(
uuid__in=self.uuids,
owner=self.owner,
type=UserProperty.Type.DOCUMENT
)
return user_properties
def get_uuids(self):
for a in self.get_properties():
if a.uuid not in self.uuids:
@ -136,91 +147,6 @@ 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,
t1.created,
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
ORDER BY created DESC
""".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,
t1.created,
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
ORDER BY created DESC
""".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):
@ -229,21 +155,20 @@ class Device:
SELECT
t1.value,
t1.key,
t1.created,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = '{algorithm}' THEN 2
WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3
END,
t1.created DESC
) AS row_num
) 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}
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT DISTINCT
value
@ -251,10 +176,8 @@ class Device:
RankedProperties
WHERE
row_num = 1
ORDER BY created DESC
""".format(
institution=institution.id,
algorithm=institution.algorithm
)
if limit:
sql += " limit {} offset {}".format(int(limit), int(offset))
@ -278,13 +201,13 @@ class Device:
SELECT
t1.value,
t1.key,
t1.created,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = '{algorithm}' THEN 2
WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3
END,
t1.created DESC
) AS row_num
@ -292,7 +215,6 @@ 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)
@ -300,10 +222,8 @@ class Device:
RankedProperties
WHERE
row_num = 1
ORDER BY created DESC
""".format(
institution=institution.id,
algorithm=institution.algorithm
)
with connection.cursor() as cursor:
cursor.execute(sql)
@ -316,32 +236,31 @@ class Device:
SELECT
t1.value,
t1.key,
t1.created,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = '{algorithm}' THEN 2
WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3
END,
t1.created DESC
) AS row_num
FROM evidence_systemproperty AS t1
WHERE t1.owner_id = {institution}
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.uuid = '{uuid}'
AND t1.key IN ('CUSTOM_ID', '{algorithm}')
)
SELECT DISTINCT
value
FROM
RankedProperties
WHERE
row_num = 1
ORDER BY created DESC
row_num = 1;
""".format(
uuid=uuid.replace("-", ""),
institution=institution.id,
algorithm=institution.algorithm,
)
properties = []
@ -366,12 +285,6 @@ 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.get_time_created()
@property
def serial_number(self):
self.get_last_evidence()
@ -392,15 +305,11 @@ class Device:
@property
def version(self):
self.get_last_evidence()
if not self.last_evidence:
self.get_last_evidence()
return self.last_evidence.get_version()
@property
def components(self):
self.get_last_evidence()
return self.last_evidence.get_components()
@property
def did_document(self):
self.get_last_evidence()
return self.last_evidence.get_did_document()

View file

@ -2,6 +2,108 @@
{% load i18n %}
{% block content %}
<div class="position-fixed" style="bottom: 2rem; right: 2rem; z-index: 9999; display: flex; gap: 0.5rem;">
<button class="btn btn-yellow d-flex align-items-center shadow" type="button"
data-bs-toggle="offcanvas" data-bs-target="#notesOffcanvas" aria-controls="notesOffcanvas"
data-bs-toggle="tooltip" data-bs-placement="left" title="{% trans 'View recent notes' %}">
<i class="bi bi-journal-text me-1"></i>
{% trans "Journal" %}
</button>
</div>
<!-- side panel for latest notes -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="notesOffcanvas" aria-labelledby="notesOffcanvasLabel">
<div class="offcanvas-header">
<h5 id="notesOffcanvasLabel">{% trans "Latest Notes" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" style="margin-bottom: 5rem;">
{% for note in device_notes|slice:":4" %}
<div class="card mb-3 shadow-sm">
<div class="card-body">
<div>
<small class="text-muted">
{{ note.date|timesince }} {% trans "ago" %}
</small>
{% if user == note.user or user.is_admin %}
<span class="badge bg-yellow text-dark ms-2">{% trans "Editable" %}</span>
</div>
<blockquote
class="blockquote mt-2 p-2 bg-light fst-italic"
contenteditable="true"
style="font-size: 1.2em!important"
data-note-id="{{ note.id }}"
title="{% trans 'Click to edit this note' %}"
oninput="toggleSaveLink(this)">
{% else %}
</div>
<blockquote style="font-size: 1.2em!important" class="blockquote mt-2 p-2 fst-italic">
{% endif %}
<p data-note-id="{{ note.id }}">
{{ note.description }}
</p>
<footer class="blockquote-footer text-end mt-2" contenteditable="false">
<small>{{ note.user.get_full_name|default:note.user.username }}</small>
</footer>
</blockquote>
{% if user == note.user or user.is_admin %}
<div class="d-flex justify-content-end align-items-center">
<!-- update note button -->
<form
id="updateNoteForm{{ note.id }}"
method="post"
action="{% url 'action:update_note' note.id %}"
class="d-inline"
>
{% csrf_token %}
<input type="hidden" name="description" id="descriptionInput{{ note.id }}" value="">
<a
type="submit"
id="saveLink{{ note.id }}"
class="text-muted disabled me-4 border border-light rounded"
style="pointer-events: none;"
title="{% trans 'Save changes' %}"
onclick="submitUpdatedNote('{{ note.id }}'); return false;"
>
<i class="fas fa-save px-1"></i>
</a>
</form>
<!-- delete note button -->
<button type="button" class="btn btn-link btn-outline-danger btn-sm text-danger" id="deleteIcon{{ note.id }}" title="{% trans 'Delete note' %}" data-bs-toggle="collapse" data-bs-target="#confirmDelete{{ note.id }}">
<i class="bi bi-trash"></i>
</button>
</div>
<form class="d-inline" method="post" action="{% url 'action:delete_note' note.id %}">
{% csrf_token %}
<div class="collapse mt-2" id="confirmDelete{{ note.id }}">
<div class="card card-body border border-danger text-center">
<p class="mb-2">{% trans 'Are you sure you want to delete this note?' %}</p>
<a
href="#"
class="btn btn-sm btn-outline-danger"
onclick="submitDeleteForm({{ note.id }}); return false;"
>
{% trans 'Confirm delete' %}
</a>
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% empty %}
<p>{% trans "No notes available." %}</p>
{% endfor %}
</div>
</div>
<!-- Top bar buttons -->
<div class="row">
<div class="col">
@ -64,7 +166,13 @@
<a href="#details" class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">{% trans 'General details' %}</a>
</li>
<li class="nav-item">
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'Properties' %}</a>
<a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
</li>
<li class="nav-item">
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'User properties' %}</a>
</li>
<li class="nav-item">
<a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">{% trans 'Documents' %}</a>
</li>
<li class="nav-item">
<a href="#lots" class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">{% trans 'Lots' %}</a>
@ -83,9 +191,6 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'device:device_web' object.id %}" target="_blank">Web</a>
</li>
<li class="nav-item">
<a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
</li>
</ul>
</div>
</div>
@ -97,14 +202,14 @@
{% include 'tabs/user_properties.html' %}
{% include 'tabs/documents.html' %}
{% include 'tabs/lots.html' %}
{% include 'tabs/components.html' %}
{% include 'tabs/evidences.html' %}
{% include 'tabs/dpps.html' %}
<!-- Add a note popup -->
<div class="modal fade" id="addNoteModal" tabindex="-1" aria-labelledby="addNoteModalLabel" aria-hidden="true">
<div class="modal-dialog">
@ -151,5 +256,27 @@
}
}
})
//Enable save button on note if changes are made to it
function toggleSaveLink(blockquoteElem) {
const saveLink = document.getElementById("saveLink" + blockquoteElem.dataset.noteId);
saveLink.classList.remove("disabled", "text-muted", "border-light");
saveLink.classList.add("text-success", "border-success");
saveLink.style.pointerEvents = "auto";
}
//updates note-update-form with new value from blockquote
function submitUpdatedNote(noteId) {
const noteParagraph = document.querySelector('p[data-note-id="' + noteId + '"]');
const newText = noteParagraph.innerText.trim();
const descriptionField = document.getElementById('descriptionInput' + noteId);
descriptionField.value = newText;
document.getElementById('updateNoteForm' + noteId).submit();
}
//simpler are u sure? confirmation message
function submitDeleteForm(noteId) {
document.getElementById('confirmDelete' + noteId).closest('form').submit();
}
</script>
{% endblock %}

View file

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

View file

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

View file

@ -0,0 +1,251 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ object.pk }}</h3>
</div>
</div>
<div class="row">
<div class="col">
<div class="nav nav-tabs nav-tabs-bordered">
<li class="nav-items">
<a class="nav-link" href="{% url 'device:details' device.pk %}">General details</a>
</li>
<li class="nav-items">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#physicalproperties">Physical properties</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#status">Status</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#traceabiliy">Traceability log</button>
</li>
<li class="nav-items">
<a class="nav-link" href="">Web</a>
</li>
</div>
</div>
</div>
<div class="tab-content pt-2">
<div class="tab-pane fade profile-overview" id="details">
<h5 class="card-title">Details</h5>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">
(<a href="/inventory/device/edit/4W8D3/">Edit Device</a>)
</div>
<div class="col-lg-9 col-md-8">
{%if object.hid %}Snapshot{% else %}Placeholder{% endif %}
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Phid</div>
<div class="col-lg-9 col-md-8">{{ object.id }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Id device internal</div>
<div class="col-lg-9 col-md-8"></div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Type</div>
<div class="col-lg-9 col-md-8">{{ object.type }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Manufacturer</div>
<div class="col-lg-9 col-md-8">{{ object.manufacturer|default:"" }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Model</div>
<div class="col-lg-9 col-md-8">{{ object.model|default:"" }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Part Number</div>
<div class="col-lg-9 col-md-8">{{ object.part_number|default:"" }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Serial Number</div>
<div class="col-lg-9 col-md-8">{{ object.serial_number|default:"" }}</div>
</div>
</div>
<div class="tab-pane fade show active" id="physicalproperties">
<h5 class="card-title">Physical Properties</h5>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">
(<a href="{% url 'device:physical_edit' object.pk %}">Edit Physical Properties</a>)
</div>
</div>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">
{% load django_bootstrap5 %}
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'device:details' device.pk %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane fade profile-overview" id="lots">
<h5 class="card-title">Incoming Lots</h5>
<div class="row">
</div>
<h5 class="card-title">Outgoing Lots</h5>
<div class="row">
</div>
<h5 class="card-title">Temporary Lots</h5>
<div class="row">
</div>
</div>
<div class="tab-pane fade profile-overview" id="documents">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="/inventory/device/4W8D3/document/add/" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</div>
<h5 class="card-title">Documents</h5>
<table class="table">
<thead>
<tr>
<th scope="col">File</th>
<th scope="col">Type</th>
<th scope="col">Description</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="tab-pane fade profile-overview" id="status">
<h5 class="card-title">Status Details</h5>
<div class="row">
<div class="col-lg-3 col-md-4 label">Physical State</div>
<div class="col-lg-9 col-md-8">
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Lifecycle State</div>
<div class="col-lg-9 col-md-8">
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Allocated State</div>
<div class="col-lg-9 col-md-8">
</div>
</div>
</div>
<div class="tab-pane fade profile-overview" id="traceability">
<h5 class="card-title">Traceability log Details</h5>
<div class="list-group col-6">
<div class="list-group-item d-flex justify-content-between align-items-center">
Snapshot ✓
<small class="text-muted">14:07 23-06-2024</small>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
EraseCrypto ✓
<small class="text-muted">14:07 23-06-2024</small>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
EraseCrypto ✓
<small class="text-muted">14:07 23-06-2024</small>
</div>
</div>
</div>
<div class="tab-pane fade profile-overview" id="components">
<h5 class="card-title">Components Snapshot</h5>
<div class="list-group col-6">
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Motherboard</h5>
<small class="text-muted">14:07 23-06-2024</small>
</div>
<p class="mb-1">
hp<br />
890e<br />
</p>
<small class="text-muted">
</small>
</div>
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">NetworkAdapter</h5>
<small class="text-muted">14:07 23-06-2024</small>
</div>
<p class="mb-1">
realtek semiconductor co., ltd.<br />
rtl8852ae 802.11ax pcie wireless network adapter<br />
</p>
<small class="text-muted">
</small>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,49 @@
{% load i18n %}
<div class="tab-pane fade" id="documents">
<div class="btn-group mt-1 mb-3">
<a href="{% url 'device:add_document' object.pk %}" class="btn btn-primary">
<i class="bi bi-plus">
</i>
{% trans 'Add new document' %}
</a>
</div>
<h5 class="card-title">{% trans 'Documents' %}
</h5>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">
{% trans 'Key' %}
</th>
<th scope="col">
{% trans 'Value' %}
</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD HH:mm">
{% trans 'Created on' %}
</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
{% for a in object.get_user_documents %}
<tr>
<td>{{ a.key }}
</td>
<td>{{ a.value }}
</td>
<td>{{ a.created }}
</td>
<td>
</td>
<td>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View file

@ -1,18 +0,0 @@
{% load i18n %}
<div class="tab-pane fade" id="dpps">
<h5 class="card-title">{% trans 'List of dpps' %}</h5>
<div class="list-group col">
{% for d in dpps %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<small class="text-muted">{{ d.2.timestamp }}</small>
<span>{{ d.2.type }}</span>
</div>
<p class="mb-1">
<a href="{% url 'did:device_web' d.0 %}">{{ d.1 }}...</a>
</p>
</div>
{% endfor %}
</div>
</div>

View file

@ -1,34 +1,19 @@
{% load i18n %}
<div class="tab-pane fade" id="evidences">
<div class="tab-pane fade" id="evidences">
<h5 class="card-title">{% trans 'List of evidences' %}</h5>
<div class="list-group col">
<table class="table">
<thead>
<tr>
<th scope="col">uuid</th>
<th scope="col">Did Document</th>
<th scope="col">{% trans "Date" %}</th>
</tr>
</thead>
<tbody>
{% for snap in object.evidences %}
<tr>
<td>
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
</td>
<td>
{% if snap.did_document %}
<a href="{{ snap.did_document }}" target="_blank">DID</a>
{% endif %}
</td>
<td>
<small class="text-muted">{{ snap.created }}</small>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="list-group col-6">
{% for snap in object.evidences %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<small class="text-muted">{{ snap.created }}</small>
</div>
<p class="mb-1">
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
</p>
</div>
{% endfor %}
</div>
</div>
</div>

View file

@ -82,7 +82,7 @@
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}
</button>
<form method="post" action="{% url 'device:delete_user_property' object.id a.id %}">
<form method="post" action="{% url 'device:delete_user_property' a.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-danger">{% trans "Delete" %}
</button>
@ -105,7 +105,7 @@
</button>
</div>
<div class="modal-body">
<form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' object.id a.id %}">
<form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' a.id %}">
{% csrf_token %}
<div class="mb-3">
<label for="key" class="form-label">{% trans "Key" %}

View file

@ -7,11 +7,10 @@ urlpatterns = [
path("add/", views.NewDeviceView.as_view(), name="add"),
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
path("<str:pk>/", views.DetailsView.as_view(), name="details"),
path("<str:pk>/user_property/add",
views.AddUserPropertyView.as_view(), name="add_user_property"),
path("<str:device_id>/user_property/<int:pk>/delete",
views.DeleteUserPropertyView.as_view(), name="delete_user_property"),
path("<str:device_id>/user_property/<int:pk>/update",
views.UpdateUserPropertyView.as_view(), name="update_user_property"),
path("<str:pk>/user_property/add", views.AddUserPropertyView.as_view(), name="add_user_property"),
path("user_property/<int:pk>/delete", views.DeleteUserPropertyView.as_view(), name="delete_user_property"),
path("user_property/<int:pk>/update", views.UpdateUserPropertyView.as_view(), name="update_user_property"),
path("<str:pk>/document/add", views.AddDocumentView.as_view(), name="add_document"),
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
]

View file

@ -1,9 +1,7 @@
import json
import logging
from django.http import JsonResponse
from django.conf import settings
from django.db import IntegrityError
from django.urls import reverse_lazy
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, Http404
@ -26,21 +24,11 @@ if settings.DPP:
from dpp.api_dlt import PROOF_TYPE
class DeviceLogMixin(DashboardView):
def log_registry(self, _uuid, msg):
DeviceLog.objects.create(
snapshot_uuid=_uuid,
event=msg,
user=self.request.user,
institution=self.request.user.institution
)
class NewDeviceView(DashboardView, FormView):
template_name = "new_device.html"
title = _("New Device")
breadcrumb = "Device / New Device"
success_url = reverse_lazy('dashboard:unassigned')
success_url = reverse_lazy('dashboard:unassigned_devices')
form_class = DeviceFormSet
def form_valid(self, form):
@ -53,6 +41,35 @@ class NewDeviceView(DashboardView, FormView):
return response
# 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')
# form_class = LotsForm
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# lots = Lot.objects.filter(owner=self.request.user)
# lot_tags = LotTag.objects.filter(owner=self.request.user)
# context.update({
# 'lots': lots,
# 'lot_tags':lot_tags,
# })
# return context
# def get_form(self):
# form = super().get_form()
# form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
# return form
# def form_valid(self, form):
# form.devices = self.get_session_devices()
# form.save()
# response = super().form_valid(form)
# return response
class EditDeviceView(DashboardView, UpdateView):
template_name = "new_device.html"
title = _("Update Device")
@ -94,32 +111,24 @@ class DetailsView(DashboardView, TemplateView):
lot_tags = LotTag.objects.filter(owner=self.request.user.institution)
dpps = []
if settings.DPP:
_dpps = Proof.objects.filter(
dpps = Proof.objects.filter(
uuid__in=self.object.uuids,
type=PROOF_TYPE["IssueDPP"]
)
for x in _dpps:
dpp = "{}:{}".format(self.pk, x.signature)
dpps.append((dpp, x.signature[:10], x))
last_evidence = self.object.get_last_evidence()
uuids = self.object.uuids
last_evidence= self.object.get_last_evidence(),
uuid=self.object.last_uuid()
state_definitions = StateDefinition.objects.filter(
institution=self.request.user.institution
).order_by('order')
device_states = State.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
device_logs = DeviceLog.objects.filter(
snapshot_uuid__in=uuids).order_by('-date')
device_notes = Note.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
context.update({
'object': self.object,
'snapshot': last_evidence,
'lot_tags': lot_tags,
'dpps': dpps,
"state_definitions": state_definitions,
"device_states": device_states,
"device_logs": device_logs,
"device_notes": device_notes,
"device_states": State.objects.filter(snapshot_uuid=uuid).order_by('-date'),
"device_logs": DeviceLog.objects.filter(snapshot_uuid=uuid).order_by('-date'),
"device_notes": Note.objects.filter(snapshot_uuid=uuid).order_by('-date'),
})
return context
@ -180,7 +189,7 @@ class PublicDeviceWebView(TemplateView):
return JsonResponse(device_data)
class AddUserPropertyView(DeviceLogMixin, CreateView):
class AddUserPropertyView(DashboardView, CreateView):
template_name = "new_user_property.html"
title = _("New User Property")
breadcrumb = "Device / New Property"
@ -193,107 +202,125 @@ class AddUserPropertyView(DeviceLogMixin, CreateView):
form.instance.uuid = self.property.uuid
form.instance.type = UserProperty.Type.USER
try:
response = super().form_valid(form)
messages.success(self.request, _("Property successfully added."))
log_message = _("<Created> UserProperty: {}: {}".format(
form.instance.key,
form.instance.value
))
message = _("<Created> UserProperty: {}: {}".format(form.instance.key, form.instance.value))
DeviceLog.objects.create(
snapshot_uuid=form.instance.uuid,
event=message,
user=self.request.user,
institution=self.request.user.institution
)
self.log_registry(form.instance.uuid, log_message)
return response
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
messages.success(self.request, _("User property successfully added."))
response = super().form_valid(form)
return response
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
institution = self.request.user.institution
self.property = SystemProperty.objects.filter(
owner=institution, value=pk).first()
if not self.property:
raise Http404
self.property = get_object_or_404(SystemProperty, owner=institution, value=pk)
return super().get_form_kwargs()
def get_success_url(self):
pk = self.kwargs.get('pk')
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['pk'] = self.kwargs.get('pk')
return context
return reverse_lazy('device:details', args=[self.kwargs.get('pk')])
class UpdateUserPropertyView(DeviceLogMixin, UpdateView):
class UpdateUserPropertyView(DashboardView, UpdateView):
template_name = "new_user_property.html"
title = _("Update User Property")
breadcrumb = "Device / Update Property"
model = UserProperty
fields = ("key", "value")
def get_form_kwargs(self):
def get_queryset(self):
pk = self.kwargs.get('pk')
institution = self.request.user.institution
self.object = get_object_or_404(UserProperty, owner=institution, pk=pk)
self.old_key = self.object.key
self.old_value = self.object.value
return super().get_form_kwargs()
return UserProperty.objects.filter(pk=pk, owner=institution)
def form_valid(self, form):
old_instance = self.get_object()
old_key = old_instance.key
old_value = old_instance.value
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.type = UserProperty.Type.USER
new_key = form.cleaned_data['key']
new_value = form.cleaned_data['value']
try:
super().form_valid(form)
messages.success(self.request, _("Property updated successfully."))
log_message = _("<Updated> UserProperty: {}: {} to {}: {}".format(
self.old_key,
self.old_value,
new_key,
new_value
))
self.log_registry(form.instance.uuid, log_message)
# return response
return redirect(self.get_success_url())
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
message = _("<Updated> UserProperty: {}: {} to {}: {}".format(old_key, old_value, new_key, new_value))
DeviceLog.objects.create(
snapshot_uuid=form.instance.uuid,
event=message,
user=self.request.user,
institution=self.request.user.institution
)
def form_invalid(self, form):
super().form_invalid(form)
return redirect(self.get_success_url())
messages.success(self.request, _("User property updated successfully."))
return super().form_valid(form)
def get_success_url(self):
pk = self.kwargs.get('device_id')
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
class DeleteUserPropertyView(DeviceLogMixin, DeleteView):
class DeleteUserPropertyView(DashboardView, DeleteView):
model = UserProperty
def get_queryset(self):
return UserProperty.objects.filter(owner=self.request.user.institution)
#using post() method because delete() method from DeleteView has some issues
# with messages framework
#using post() method because delete() method from DeleteView has some issues with messages framework
def post(self, request, *args, **kwargs):
pk = self.kwargs.get('pk')
institution = self.request.user.institution
self.object = get_object_or_404(UserProperty, owner=institution, pk=pk)
self.object = self.get_object()
self.object.delete()
msg = _("<Deleted> User Property: {}:{}".format(
self.object.key,
self.object.value
))
self.log_registry(self.object.uuid, msg)
message = _("<Deleted> User Property: {}:{}".format(self.object.key, self.object.value ))
DeviceLog.objects.create(
snapshot_uuid=self.object.uuid,
event=message,
user=self.request.user,
institution=self.request.user.institution
)
messages.info(self.request, _("User property deleted successfully."))
return self.handle_success()
def handle_success(self):
return redirect(self.get_success_url())
def get_success_url(self):
pk = self.kwargs.get('device_id')
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
class AddDocumentView(DashboardView, CreateView):
template_name = "new_user_property.html"
title = _("New Document")
breadcrumb = "Device / New document"
success_url = reverse_lazy('dashboard:unassigned_devices')
model = UserProperty
fields = ("key", "value")
def form_valid(self, form):
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.uuid = self.property.uuid
form.instance.type = UserProperty.Type.DOCUMENT
response = super().form_valid(form)
return response
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
institution = self.request.user.institution
self.property = SystemProperty.objects.filter(
owner=institution,
value=pk,
).first()
if not self.property:
raise Http404
self.success_url = reverse_lazy('device:details', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs

View file

@ -78,13 +78,16 @@ INSTALLED_APPS = [
'django_extensions',
'django_bootstrap5',
'django_tables2',
"rest_framework",
"login",
"user",
"device",
"evidence",
"lot",
"dashboard",
"action",
"tag",
"lot",
"documents",
"dashboard",
"admin",
"api",
]

View file

@ -106,10 +106,10 @@ class PublicDeviceWebView(TemplateView):
'device': {},
}
dev = Build(self.object.last_evidence.doc, None, check=True)
doc = dev.build.get_doc()
doc = dev.get_phid()
data['document'] = json.dumps(doc)
data['device'] = dev.build.device
data['components'] = dev.build.components
data['device'] = dev.device
data['components'] = dev.components
self.object.get_evidences()
last_dpp = Proof.objects.filter(
@ -118,7 +118,7 @@ class PublicDeviceWebView(TemplateView):
key = self.pk
if last_dpp:
key += ":"+last_dpp.signature
key = last_dpp.signature
url = "https://{}/did/{}".format(
self.request.get_host(),
@ -135,17 +135,17 @@ class PublicDeviceWebView(TemplateView):
for d in self.object.evidences:
d.get_doc()
dev = Build(d.doc, None, check=True)
doc = dev.build.get_doc()
doc = dev.get_phid()
ev = json.dumps(doc)
phid = dev.sign(ev)
phid = dev.get_signature(doc)
dpp = "{}:{}".format(self.pk, phid)
rr = {
'dpp': dpp,
'document': ev,
'algorithm': ALGORITHM,
'manufacturer DPP': '',
'device': dev.build.device,
'components': dev.build.components
'device': dev.device,
'components': dev.components
}
tmpl = dpp_tmpl.copy()

View file

@ -1,59 +1,18 @@
services:
devicehub-django:
init: true
image: farga.pangea.org/ereuse/devicehub-django:latest
build:
context: .
dockerfile: docker/devicehub-django.Dockerfile
environment:
- DEBUG=${DEBUG:-false}
- DOMAIN=${DEVICEHUB_DOMAIN:-localhost}
- PORT=${DEVICEHUB_PORT:-8000}
- ALLOWED_HOSTS=${DEVICEHUB_ALLOWED_HOSTS:-$DEVICEHUB_DOMAIN}
- DOMAIN=${DH_DOMAIN:-localhost}
- PORT=${DH_PORT:-8000}
- ALLOWED_HOSTS=${DH_ALLOWED_HOSTS:-$DH_DOMAIN}
- DEMO=${DEMO:-false}
- DEMO_IDHUB_DOMAIN=${DEMO_IDHUB_DOMAIN:-}
- DEMO_IDHUB_PREDEFINED_TOKEN=${IDHUB_PREDEFINED_TOKEN:-}
- PREDEFINED_TOKEN=${PREDEFINED_TOKEN:-}
- DPP=${DPP:-false}
# TODO manage volumes dev vs prod
volumes:
- .:/opt/devicehub-django
ports:
- ${DEVICEHUB_PORT:-8000}:${DEVICEHUB_PORT:-8000}
- ${DH_PORT}:${DH_PORT}
# TODO add database service for idhub, meanwhile sqlite
idhub:
# https://docs.docker.com/compose/how-tos/profiles/
profiles: [idhub]
init: true
image: farga.pangea.org/ereuse/idhub:latest
environment:
- DOMAIN=${IDHUB_DOMAIN:-localhost}
- ALLOWED_HOSTS=${IDHUB_ALLOWED_HOSTS:-$IDHUB_DOMAIN}
- DEBUG=true
- DEMO=${DEMO:-false}
- INITIAL_ADMIN_EMAIL=${IDHUB_ADMIN_EMAIL}
- INITIAL_ADMIN_PASSWORD=${IDHUB_ADMIN_PASSWD}
- CREATE_TEST_USERS=true
- ENABLE_EMAIL=${IDHUB_ENABLE_EMAIL:-true}
- ENABLE_2FACTOR_AUTH=${IDHUB_ENABLE_2FACTOR_AUTH:-true}
- ENABLE_DOMAIN_CHECKER=${IDHUB_ENABLE_DOMAIN_CHECKER:-true}
- PREDEFINED_TOKEN=${IDHUB_PREDEFINED_TOKEN:-}
- SECRET_KEY=${IDHUB_SECRET_KEY:-publicsecretisnotsecureVtmKBfxpVV47PpBCF2Nzz2H6qnbd}
- STATIC_ROOT=${IDHUB_STATIC_ROOT:-/static/}
- MEDIA_ROOT=${IDHUB_MEDIA_ROOT:-/media/}
- PORT=${IDHUB_PORT:-9001}
- DEFAULT_FROM_EMAIL=${IDHUB_DEFAULT_FROM_EMAIL}
- EMAIL_HOST=${IDHUB_EMAIL_HOST}
- EMAIL_HOST_USER=${IDHUB_EMAIL_HOST_USER}
- EMAIL_HOST_PASSWORD=${IDHUB_EMAIL_HOST_PASSWORD}
- EMAIL_PORT=${IDHUB_EMAIL_PORT}
- EMAIL_USE_TLS=${IDHUB_EMAIL_USE_TLS}
- EMAIL_BACKEND=${IDHUB_EMAIL_BACKEND}
- SUPPORTED_CREDENTIALS=['Snapshot']
- SYNC_ORG_DEV=${IDHUB_SYNC_ORG_DEV}
ports:
- 9001:9001
# TODO add database service for idhub, meanwhile sqlite

View file

@ -19,24 +19,12 @@ main() {
cp -v .env.example .env
echo "WARNING: .env was not there, .env.example was copied, this only happens once"
fi
# load vars
. ./.env
if [ "${IDHUB_ENABLED:-}" = 'true' ]; then
export COMPOSE_PROFILES='idhub'
fi
# remove old database
rm -vfr ./db/*
# deactivate configured flag
rm -vfr ./already_configured
docker compose down -v
if [ "${DEV_DOCKER_ALWAYS_BUILD:-}" = 'true' ]; then
docker compose pull --ignore-buildable
docker compose build
else
docker compose pull
fi
docker compose build
docker compose up ${detach_arg:-}
}

View file

@ -6,7 +6,6 @@ RUN apt update && \
python3-xapian \
git \
sqlite3 \
curl \
jq \
time \
vim \
@ -38,7 +37,6 @@ RUN pip install -i https://test.pypi.org/simple/ ereuseapitest==0.0.14
# Set PYTHONPATH to include the directory with the xapian module
ENV PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages"
COPY . .
COPY docker/devicehub-django.entrypoint.sh /
RUN chown -R app:app /opt/devicehub-django

View file

@ -42,6 +42,19 @@ gen_env_vars() {
export API_RESOLVER='http://id_index_api:3012'
# TODO hardcoded
export ID_FEDERATED='DH1'
# propagate to .env
dpp_env_vars="$(cat <<END
API_DLT=${API_DLT}
API_DLT_TOKEN=${API_DLT_TOKEN}
API_RESOLVER=${API_RESOLVER}
ID_FEDERATED=${ID_FEDERATED}
END
)"
# generate config using env vars from docker
# TODO rethink if this is needed because now this is django, not flask
cat > .env <<END
${dpp_env_vars:-}
END
fi
}
@ -105,54 +118,8 @@ END
./manage.py dlt_register_user "${DATASET_FILE}"
}
# wait until idhub api is prepared to received requests
wait_idhub() {
echo "Start waiting idhub API"
while true; do
result="$(curl -s "${url}" \
| jq -r .error \
|| echo "Reported errors, idhub API is still not ready")"
if [ "${result}" = "Invalid request method" ]; then
break
sleep 2
else
echo "Waiting idhub API"
sleep 3
fi
done
}
demo__send_to_sign_credential() {
filepath="${1}"
# hashlib.sha3_256 of PREDEFINED_TOKEN for idhub
DEMO_IDHUB_PREDEFINED_TOKEN="${DEMO_IDHUB_PREDEFINED_TOKEN:-}"
auth_header="Authorization: Bearer ${DEMO_IDHUB_PREDEFINED_TOKEN}"
json_header='Content-Type: application/json'
curl -s -X POST \
-H "${json_header}" \
-H "${auth_header}" \
-d @"${filepath}" \
"${url}" \
| jq -r .data
}
run_demo() {
if [ "${DEMO_IDHUB_DOMAIN:-}" ]; then
DEMO_IDHUB_DOMAIN="${DEMO_IDHUB_DOMAIN:-}"
# this demo only works with FQDN domain (with no ports)
url="https://${DEMO_IDHUB_DOMAIN}/webhook/sign/"
wait_idhub
demo__send_to_sign_credential \
'example/demo-snapshots-vc/snapshot_pre-verifiable-credential.json' \
> 'example/snapshots/snapshot_workbench-script_verifiable-credential.json'
fi
./manage.py create_default_states "${INIT_ORG}"
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
}
config_phase() {
# TODO review this flag file
# TODO review this flag file
init_flagfile="${program_dir}/already_configured"
if [ ! -f "${init_flagfile}" ]; then
@ -165,7 +132,7 @@ config_phase() {
# 12, 13, 14
config_dpp_part1
# cleanup other snapshots and copy dlt/dpp snapshots
# cleanup other spnapshots and copy dlt/dpp snapshots
# TODO make this better
rm example/snapshots/*
cp example/dpp-snapshots/*.json example/snapshots/
@ -173,7 +140,7 @@ config_phase() {
# # 15. Add inventory snapshots for user "${INIT_USER}".
if [ "${DEMO:-}" = 'true' ]; then
run_demo
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
fi
# remain next command as the last operation for this if conditional
@ -188,11 +155,9 @@ check_app_is_there() {
}
deploy() {
if [ -d /opt/devicehub-django/.git ]; then
# TODO this is weird, find better workaround
git config --global --add safe.directory "${program_dir}"
export COMMIT=$(git log --format="%H %ad" --date=iso -n 1)
fi
# TODO this is weird, find better workaround
git config --global --add safe.directory "${program_dir}"
export COMMIT=$(git log --format="%H %ad" --date=iso -n 1)
if [ "${DEBUG:-}" = 'true' ]; then
./manage.py print_settings
@ -208,9 +173,6 @@ deploy() {
# move the migrate thing in docker entrypoint
# inspired by https://medium.com/analytics-vidhya/django-with-docker-and-docker-compose-python-part-2-8415976470cc
echo "INFO detected NEW deployment"
if [ ! -d "${program_dir}/db/" ]; then
mkdir -p "${program_dir}/db/"
fi
./manage.py migrate
config_phase
fi

0
documents/__init__.py Normal file
View file

3
documents/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
documents/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class DocumentsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "documents"

View file

3
documents/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
documents/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
documents/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -12,7 +12,7 @@ from dpp.models import Proof
class ProofView(View):
def get(self, request, *args, **kwargs):
timestamp = kwargs.get("proof_id")
proof = Proof.objects.filter(timestamp=timestamp).first()
@ -22,9 +22,9 @@ class ProofView(View):
ev = Evidence(proof.uuid)
if not ev.doc:
return JsonResponse({}, status=404)
dev = Build(ev.doc, None, check=True)
doc = dev.build.get_doc()
doc = dev.get_phid()
data = {
"algorithm": ALGORITHM,

View file

@ -30,7 +30,8 @@ class UploadForm(forms.Form):
try:
file_json = json.loads(file_data)
snap = Build(file_json, None, check=True)
build = Build
snap = build(file_json, None, check=True)
exists_property = SystemProperty.objects.filter(
uuid=snap.uuid
).first()
@ -71,6 +72,7 @@ class UserTagForm(forms.Form):
self.pk = None
self.uuid = kwargs.pop('uuid', None)
self.user = kwargs.pop('user')
instance = SystemProperty.objects.filter(
uuid=self.uuid,
key='CUSTOM_ID',
@ -88,6 +90,7 @@ class UserTagForm(forms.Form):
if not data:
return False
self.tag = data
self.instance = SystemProperty.objects.filter(
uuid=self.uuid,
key='CUSTOM_ID',
@ -103,15 +106,18 @@ class UserTagForm(forms.Form):
if self.instance:
old_value = self.instance.value
if not self.tag:
message =_("<Deleted> Evidence Tag. Old Value: '{}'").format(old_value)
message = _("<Deleted> Evidence Tag. Old Value: '{}'").format(
old_value
)
self.instance.delete()
else:
self.instance.value = self.tag
self.instance.save()
if old_value != self.tag:
message=_("<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'").format(old_value, self.tag)
msg = "<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'"
message=_(msg).format(old_value, self.tag)
else:
message =_("<Created> Evidence Tag. Value: '{}'").format(self.tag)
message = _("<Created> Evidence Tag. Value: '{}'").format(self.tag)
SystemProperty.objects.create(
uuid=self.uuid,
key='CUSTOM_ID',
@ -119,7 +125,7 @@ class UserTagForm(forms.Form):
owner=self.user.institution,
user=self.user
)
DeviceLog.objects.create(
snapshot_uuid=self.uuid,
event= message,
@ -128,6 +134,7 @@ class UserTagForm(forms.Form):
)
class ImportForm(forms.Form):
file_import = forms.FileField(label=_("File to import"))
@ -176,8 +183,8 @@ class ImportForm(forms.Form):
table = []
for row in self.rows:
doc = create_doc(row)
property = create_property(doc, self.user)
table.append((doc, property))
prop = create_property(doc, self.user)
table.append((doc, prop))
if commit:
for doc, cred in table:

View file

@ -44,7 +44,7 @@ class Build(BuildMix):
self.chassis = self.get_chassis_dh()
self.serial_number = self.dmi.serial_number()
self.sku = self.get_sku()
self.type = self.chassis
self.typ = self.chassis
self.version = self.get_version()
def get_chassis_dh(self):
@ -66,9 +66,4 @@ class Build(BuildMix):
def _get_components(self):
data = ParseSnapshot(self.json)
self.device = data.device
self.components = data.components
self.device.pop("actions", None)
for c in self.components:
c.pop("actions", None)

View file

@ -4,6 +4,7 @@ import logging
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.conf import settings
from utils.save_snapshots import move_json, save_in_disk
from evidence.parse import Build

View file

@ -1,19 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-29 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evidence", "0004_remove_userproperty_user_unique_type_key_uuid"),
]
operations = [
migrations.AlterField(
model_name="userproperty",
name="type",
field=models.SmallIntegerField(
choices=[(1, "User"), (2, "EraseServer")], default=1
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-30 17:52
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evidence', '0005_alter_userproperty_type'),
('user', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddConstraint(
model_name='userproperty',
constraint=models.UniqueConstraint(fields=('key', 'uuid'), name='userproperty_unique_type_key_uuid'),
),
]

View file

@ -17,13 +17,8 @@ class BuildMix:
self.chassis = ""
self.sku = ""
self.mac = ""
self.type = ""
self.tpy = ""
self.version = ""
self.default = ""
self.algorithms = {}
if not self.uuid:
logger.error("snapshot without UUID. Software {}".format(self.json.get("software")))
return
self.get_details()
self.generate_chids()
@ -32,32 +27,31 @@ class BuildMix:
hid = ""
for f in algorithm:
if hasattr(self, f):
hid += getattr(self, f) or ''
hid += getattr(self, f)
return hid
def generate_chids(self):
self.algorithms = {}
for k in ALGOS.keys():
if not settings.DPP and k == 'ereuse22':
continue
self.algorithms[k] = self.get_hid(k)
self.algorithms = {
'hidalgo1': self.get_hid('hidalgo1'),
}
if settings.DPP:
self.algorithms["legacy_dpp"] = self.get_hid("legacy_dpp")
def get_doc(self):
self._get_components()
for c in self.components:
c.pop("actions", None)
components = sorted(self.components, key=lambda x: x.get("type"))
device = self.algorithms.get('ereuse22')
device = self.algorithms.get('legacy_dpp')
doc = [("computer", device)]
for c in components:
doc.append((c.get("type"), self.get_id_hw_dpp(c)))
return doc
def get_id_hw_dpp(self, d):
algorithm = ALGOS.get("ereuse22", [])
algorithm = ALGOS.get("legacy_dpp", [])
hid = ""
for f in algorithm:
hid += d.get(f, '')

View file

@ -37,20 +37,15 @@ class SystemProperty(Property):
class UserProperty(Property):
uuid = models.UUIDField()
class Type(models.IntegerChoices):
USER = 1, "User"
ERASE_SERVER = 2, "EraseServer"
DOCUMENT = 2, "Document"
ERASE_SERVER = 3, "EraseServer"
uuid = models.UUIDField()
type = models.SmallIntegerField(choices=Type, default=Type.USER)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["key", "uuid"], name="userproperty_unique_type_key_uuid")
]
class Evidence:
def __init__(self, uuid):
@ -135,11 +130,9 @@ class Evidence:
if not self.doc:
self.get_doc()
self.created = self.doc.get("endTime")
if not self.created:
self.created = self.get_time_created()
def get_time_created(self):
return self.properties.last().created.isoformat()
if not self.created:
self.created = self.properties.last().created
def get_components(self):
if self.is_legacy():
@ -211,7 +204,7 @@ class Evidence:
def get_all(cls, user):
return SystemProperty.objects.filter(
owner=user.institution,
key="ereuse24",
key="hidalgo1",
).order_by("-created").values_list("uuid", "created").distinct()
def set_components(self):
@ -225,14 +218,3 @@ class Evidence:
def is_web_snapshot(self):
return self.doc.get("type") == "WebSnapshot"
def did_document(self):
if not self.doc.get("credentialSubject"):
return ''
did = self.doc.get('issuer')
if not "did:web" in did:
return ''
return "https://{}/did.json".format(
did.split("did:web:")[1].replace(":", "/")
)

View file

@ -10,14 +10,9 @@ logger = logging.getLogger('django')
def get_mac(inxi):
nets = get_inxi_key(inxi, "Network")
n_nets = len(nets) - 1
networks = [(nets[i], nets[i + 1]) for i in range(0, len(nets) - 1, 2)]
for i in range(0, n_nets):
if i + 1 > n_nets:
break
n = nets[i]
iface = nets[i + 1]
for n, iface in networks:
if get_inxi(n, "port"):
return get_inxi(iface, 'mac')
@ -41,9 +36,7 @@ class Build(BuildMix):
self.manufacturer = system
self.model = get_inxi(m, "product")
self.serial_number = get_inxi(m, "serial")
self.type = get_inxi(m, "Type")
self.chassis = self.type
self.version = get_inxi(m, "v")
self.chassis = get_inxi(m, "Type")
else:
self.sku = get_inxi(m, "part-nu")
@ -68,9 +61,4 @@ class Build(BuildMix):
def _get_components(self):
data = ParseSnapshot(self.json)
self.device = data.device
self.components = data.components
self.device.pop("actions", None)
for c in self.components:
c.pop("actions", None)

View file

@ -282,8 +282,9 @@ class ParseSnapshot:
def get_networks(self):
nets = get_inxi_key(self.inxi, "Network") or []
for i in range(0, len(nets)-1):
n = nets[i]
networks = [(nets[i], nets[i + 1]) for i in range(0, len(nets) - 1, 2)]
for n, iface in networks:
model = get_inxi(n, "Device")
if not model:
continue
@ -297,25 +298,16 @@ class ParseSnapshot:
if get_inxi(n, "type") == "USB":
interface = "USB"
manufacturer = get_inxi(n, "manufacturer")
speed = get_inxi(n, "speed")
mac = ""
if len(nets) > i+1:
iface = nets[i+1]
mac = get_inxi(iface, 'mac')
if not speed:
speed = get_inxi(iface, "speed")
self.components.append({
self.components.append(
{
"type": "NetworkAdapter",
"model": model,
"manufacturer": manufacturer,
"serialNumber": mac,
"speed": speed,
"manufacturer": get_inxi(n, 'vendor'),
"serialNumber": get_inxi(iface, 'mac'),
"speed": get_inxi(n, "speed"),
"interface": interface,
})
}
)
def get_sound_card(self):
audio = get_inxi_key(self.inxi, "Audio") or []

View file

@ -11,18 +11,12 @@ class Build(BuildMix):
# normaly is worbench 11
def get_details(self):
self.device = self.json.get('device', {})
self.manufacturer = self.device.get("manufacturer", '')
self.model = self.device.get("model", '')
self.chassis = self.device.get("chassis", '')
self.serial_number = self.device.get("serialNumber", '')
self.sku = self.device.get("sku", '')
self.type = self.device.get("type", '')
self.version = self.device.get("version", '')
device = self.json.get('device', {})
self.manufacturer = device.get("manufacturer", '')
self.model = device.get("model", '')
self.chassis = device.get("chassis", '')
self.serial_number = device.get("serialNumber", '')
self.sku = device.get("sku", '')
def _get_components(self):
self.components = self.json.get("components", [])
self.device.pop("actions", None)
for c in self.components:
c.pop("actions", None)

View file

@ -55,9 +55,6 @@ class Build:
if check:
return
if not self.build.uuid:
return
self.index()
self.create_annotations()
if settings.DPP:
@ -74,7 +71,7 @@ class Build:
)
if prop:
txt = "Warning: Snapshot %s already registered (annotation exists)"
txt = "Warning: Snapshot %s already registered (property exists)"
logger.warning(txt, self.uuid)
return
@ -91,7 +88,7 @@ class Build:
return hashlib.sha3_256(doc.encode()).hexdigest()
def register_device_dlt(self):
legacy_dpp = self.build.algorithms.get('ereuse22')
legacy_dpp = self.build.algorithms.get('legacy_dpp')
chid = self.sign(legacy_dpp)
phid = self.sign(json.dumps(self.build.get_doc()))
register_device_dlt(chid, phid, self.uuid, self.user)

43
evidence/serializers.py Normal file
View file

@ -0,0 +1,43 @@
from rest_framework import serializers
from evidence.models import EvidenceJson
import json
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from evidence.parse import Parse
class EvidenceSerializer(serializers.ModelSerializer):
class Meta:
model = EvidenceJson
fields = ['id', 'title', 'content']
@csrf_exempt
def webhook_verify(request):
if request.method == 'POST':
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return JsonResponse({'error': 'Invalid authorization'}, status=401)
token = auth_header.split(' ')[1]
tk = Token.objects.filter(token=token).first()
if not tk:
return JsonResponse({'error': 'Invalid authorization'}, status=401)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
try:
device = Parse(data)
except Exception:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
if not device:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
return JsonResponse({"result": "Ok"}, status=200)
return JsonResponse({'error': 'Invalid request method'}, status=400)

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' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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'),
'success_url': reverse_lazy('dashboard:unassigned_devices'),
'commit_id': settings.COMMIT,
}
def get(self, request, *args, **kwargs):
self.extra_context['success_url'] = request.GET.get(
'next',
reverse_lazy('dashboard:unassigned')
reverse_lazy('dashboard:unassigned_devices')
)
if not self.request.user.is_anonymous:
return redirect(reverse_lazy('dashboard:unassigned'))
return redirect(reverse_lazy('dashboard:unassigned_devices'))
return super().get(request, *args, **kwargs)
def form_valid(self, form):
@ -72,3 +72,4 @@ class PasswordResetView(auth_views.PasswordResetView):
except Exception as err:
logger.error(err)
return HttpResponseRedirect(self.success_url)

View file

@ -1,19 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-29 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("lot", "0004_remove_lotproperty_lot_unique_type_key_lot_and_more"),
]
operations = [
migrations.AlterField(
model_name="lotproperty",
name="type",
field=models.SmallIntegerField(
choices=[(0, "System"), (1, "User")], default=1
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-31 10:33
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('lot', '0005_alter_lotproperty_type'),
('user', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddConstraint(
model_name='lotproperty',
constraint=models.UniqueConstraint(fields=('key', 'lot'), name='property_unique_type_key_lot'),
),
]

View file

@ -1,18 +0,0 @@
# 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

@ -1,18 +0,0 @@
# Generated by Django 5.0.6 on 2025-02-25 12:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('lot', '0007_lottag_inbox'),
]
operations = [
migrations.RenameField(
model_name='lot',
old_name='closed',
new_name='archived',
),
]

View file

@ -6,8 +6,8 @@ from utils.constants import (
STR_EXTEND_SIZE,
)
from user.models import User, Institution
from evidence.models import Property
from user.models import User, Institution
from evidence.models import Property
# from device.models import Device
@ -15,7 +15,6 @@ 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
@ -32,7 +31,7 @@ class Lot(models.Model):
name = models.CharField(max_length=STR_SIZE, blank=True, null=True)
code = models.CharField(max_length=STR_SIZE, blank=True, null=True)
description = models.CharField(max_length=STR_SIZE, blank=True, null=True)
archived = models.BooleanField(default=False)
closed = models.BooleanField(default=False)
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
@ -41,22 +40,17 @@ class Lot(models.Model):
if DeviceLot.objects.filter(lot=self, device_id=v).exists():
return
DeviceLot.objects.create(lot=self, device_id=v)
def remove(self, v):
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):
SYSTEM = 0, "System"
USER = 1, "User"
DOCUMENT = 2, "Document"
type = models.SmallIntegerField(choices=Type.choices, default=Type.USER)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["key", "lot"], name="property_unique_type_key_lot")
]

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' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
</div>

View file

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>Lot {{ lot.name }}</h3>
</div>
</div>
<div class="row">
<div class="tab-pane fade show active" id="details">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="{% url 'lot:add_document' lot.pk %}" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</div>
<h5 class="card-title mt-2">Documents</h5>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for a in documents %}
<tr>
<td>{{ a.key }}</td>
<td>{{ a.value }}</td>
<td>{{ a.created }}</td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View file

@ -11,26 +11,20 @@
<form method="post">
{% csrf_token %}
<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>
{% 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 %}
<button class="btn btn-green-admin" type="submit">Save</button>
</form>

View file

@ -7,13 +7,13 @@
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-center">
{% if show_archived %}
<a href="?show_archived=false" class="btn btn-green-admin">
{% trans 'Show active lots' %}
{% if show_closed %}
<a href="?show_closed=false" class="btn btn-green-admin">
{% trans 'Hide closed lots' %}
</a>
{% else %}
<a href="?show_archived=true" class="btn btn-green-admin">
{% trans 'Show archived lots' %}
<a href="?show_closed=true" class="btn btn-green-admin">
{% trans 'Show closed lots' %}
</a>
{% endif %}

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' %}">{% translate "Cancel" %}</a>
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>

View file

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

View file

@ -10,8 +10,8 @@
<div class="row">
<div class="tab-pane fade show active" id="details">
<div class="d-flex justify-content-end mt-1 mb-3">
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-green-admin d-flex align-items-center">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new lot Property
@ -20,35 +20,34 @@
</div>
<h5 class="card-title mt-2">Properties</h5>
<table class="table table-hover table-bordered table-responsive align-middle">
<thead class="table-light">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">{% trans 'Key' %}</th>
<th scope="col">{% trans 'Value' %}</th>
<th scope="col" data-type="date" class="text-end" data-format="YYYY-MM-DD HH:mm">{% trans 'Created on' %}</th>
<th scope="col" width="5%" class="text-end"></th>
</tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for a in properties %}
<tr>
<td>{{ a.key }}</td>
<td>{{ a.value }}</td>
<td class="text-end">{{ a.created }}</td>
<td>
<div class="btn-group ">
<button type="button" class="btn btn-sm btn-outline-info d-flex align-items-center" data-bs-toggle="modal" data-bs-target="#editPropertyModal{{ a.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="#deletePropertyModal{{ a.id }}">
<i class="bi bi-trash me-1"></i>
{% trans 'Delete' %}
</button>
</div>
</td>
<td>{{ a.created }}</td>
<td class="text-center">
<a href="#" class="text-info" data-bs-toggle="modal" data-bs-target="#editPropertyModal{{ a.id }}">
<i class="bi bi-pencil"></i>
</a>
</td>
<td class="text-center">
<a href="#" class="text-danger" data-bs-toggle="modal" data-bs-target="#deletePropertyModal{{ a.id }}">
<i class="bi bi-trash"></i>
</a>
</td>
</tr>
<div class="modal fade" id="editPropertyModal{{ a.id }}" tabindex="-1" aria-labelledby="editPropertyModalLabel{{ a.id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -76,7 +75,7 @@
</div>
</div>
</div>
<div class="modal fade" id="deletePropertyModal{{ a.id }}" tabindex="-1" aria-labelledby="deletePropertyModalLabel{{ a.id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -98,7 +97,7 @@
</div>
</div>
{% endfor %}
</tbody>
</table>
</div>

View file

@ -9,7 +9,9 @@ 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("group/<int:pk>/", views.LotsTagsView.as_view(), name="tags"),
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
path("<int:pk>/document/", views.LotDocumentsView.as_view(), name="documents"),
path("<int:pk>/document/add", views.LotAddDocumentView.as_view(), name="add_document"),
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

@ -1,4 +1,3 @@
from django.db import IntegrityError
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, redirect, Http404
from django.contrib import messages
@ -18,24 +17,16 @@ class NewLotView(DashboardView, CreateView):
template_name = "new_lot.html"
title = _("New lot")
breadcrumb = "lot / New lot"
success_url = reverse_lazy('dashboard:unassigned')
success_url = reverse_lazy('dashboard:unassigned_devices')
model = Lot
fields = (
"type",
"name",
"code",
"description",
"archived",
"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
@ -47,14 +38,14 @@ class DeleteLotView(DashboardView, DeleteView):
template_name = "delete_lot.html"
title = _("Delete lot")
breadcrumb = "lot / Delete lot"
success_url = reverse_lazy('dashboard:unassigned')
success_url = reverse_lazy('dashboard:unassigned_devices')
model = Lot
fields = (
"type",
"name",
"code",
"description",
"archived",
"closed",
)
def form_valid(self, form):
@ -66,14 +57,14 @@ class EditLotView(DashboardView, UpdateView):
template_name = "new_lot.html"
title = _("Edit lot")
breadcrumb = "Lot / Edit lot"
success_url = reverse_lazy('dashboard:unassigned')
success_url = reverse_lazy('dashboard:unassigned_devices')
model = Lot
fields = (
"type",
"name",
"code",
"description",
"archived",
"closed",
)
def get_form_kwargs(self):
@ -87,20 +78,12 @@ class EditLotView(DashboardView, UpdateView):
kwargs = super().get_form_kwargs()
return kwargs
def get_form(self):
form = super().get_form()
form.fields["type"].queryset = LotTag.objects.filter(
owner=self.request.user.institution,
inbox=False
)
return form
class AddToLotView(DashboardView, FormView):
template_name = "list_lots.html"
title = _("Add to lots")
breadcrumb = "lot / add to lots"
success_url = reverse_lazy('dashboard:unassigned')
success_url = reverse_lazy('dashboard:unassigned_devices')
form_class = LotsForm
def get_context_data(self, **kwargs):
@ -115,8 +98,7 @@ class AddToLotView(DashboardView, FormView):
def get_form(self):
form = super().get_form()
form.fields["lots"].queryset = Lot.objects.filter(
owner=self.request.user.institution)
form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user.institution)
return form
def form_valid(self, form):
@ -141,7 +123,7 @@ class LotsTagsView(DashboardView, TemplateView):
template_name = "lots.html"
title = _("lots")
breadcrumb = _("lots") + " /"
success_url = reverse_lazy('dashboard:unassigned')
success_url = reverse_lazy('dashboard:unassigned_devices')
def get_context_data(self, **kwargs):
self.pk = kwargs.get('pk')
@ -149,15 +131,60 @@ class LotsTagsView(DashboardView, TemplateView):
tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk)
self.title += " {}".format(tag.name)
self.breadcrumb += " {}".format(tag.name)
show_archived = self.request.GET.get('show_archived', 'false') == 'true'
lots = Lot.objects.filter(owner=self.request.user.institution).filter(
type=tag, archived=show_archived
)
show_closed = self.request.GET.get('show_closed', 'false') == 'true'
lots = Lot.objects.filter(owner=self.request.user.institution).filter(type=tag, closed=show_closed)
context.update({
'lots': lots,
'title': self.title,
'breadcrumb': self.breadcrumb,
'show_archived': show_archived
'show_closed': show_closed
})
return context
class LotAddDocumentView(DashboardView, CreateView):
template_name = "new_property.html"
title = _("New Document")
breadcrumb = "Device / New document"
success_url = reverse_lazy('dashboard:unassigned_devices')
model = LotProperty
fields = ("key", "value")
def form_valid(self, form):
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.lot = self.lot
form.instance.type = LotProperty.Type.DOCUMENT
response = super().form_valid(form)
return response
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution)
self.success_url = reverse_lazy('lot:documents', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs
class LotDocumentsView(DashboardView, TemplateView):
template_name = "documents.html"
title = _("New Document")
breadcrumb = "Devicce / New document"
def get_context_data(self, **kwargs):
self.pk = kwargs.get('pk')
context = super().get_context_data(**kwargs)
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
documents = LotProperty.objects.filter(
lot=lot,
owner=self.request.user.institution,
type=LotProperty.Type.DOCUMENT,
)
context.update({
'lot': lot,
'documents': documents,
'title': self.title,
'breadcrumb': self.breadcrumb
})
return context
@ -198,13 +225,8 @@ class AddLotPropertyView(DashboardView, CreateView):
form.instance.user = self.request.user
form.instance.lot = self.lot
form.instance.type = LotProperty.Type.USER
try:
response = super().form_valid(form)
messages.success(self.request, _("Property successfully added."))
return response
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
response = super().form_valid(form)
return response
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
@ -213,11 +235,6 @@ class AddLotPropertyView(DashboardView, CreateView):
kwargs = super().get_form_kwargs()
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['lot_id'] = self.lot.id
return context
class UpdateLotPropertyView(DashboardView, UpdateView):
template_name = "properties.html"
@ -228,33 +245,31 @@ class UpdateLotPropertyView(DashboardView, UpdateView):
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
lot_property = get_object_or_404(
LotProperty,
pk=pk,
owner=self.request.user.institution
)
lot_property = get_object_or_404(LotProperty, pk=pk, owner=self.request.user.institution)
if not lot_property:
raise Http404
lot_pk = lot_property.lot.pk
self.success_url = reverse_lazy('lot:properties', args=[lot_pk])
kwargs = super().get_form_kwargs()
kwargs['instance'] = lot_property
return kwargs
def form_valid(self, form):
try:
response = super().form_valid(form)
messages.success(self.request, _("Property updated successfully."))
return response
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
old_key= self.object.key
old_value = self.object.value
new_key = form.cleaned_data['key']
new_value = form.cleaned_data['value']
def form_invalid(self, form):
super().form_invalid(form)
return redirect(self.get_success_url())
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.type = LotProperty.Type.USER
response = super().form_valid(form)
messages.success(self.request, _("Lot property updated successfully."))
return response
def get_success_url(self):
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
class DeleteLotPropertyView(DashboardView, DeleteView):
@ -262,15 +277,18 @@ class DeleteLotPropertyView(DashboardView, DeleteView):
def post(self, request, *args, **kwargs):
self.pk = kwargs['pk']
referer = request.META.get('HTTP_REFERER')
if not referer:
raise Http404("No referer header found")
self.object = get_object_or_404(
self.model,
pk=self.pk,
owner=self.request.user.institution
)
lot_pk = self.object.lot.pk
old_value = self.object.key
self.object.delete()
messages.success(self.request, _("Lot property deleted successfully."))
self.success_url = reverse_lazy('lot:properties', args=[lot_pk])
# Redirect back to the original URL
return redirect(self.success_url)
return redirect(referer)

0
tag/__init__.py Normal file
View file

3
tag/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
tag/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class TagConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tag"

View file

3
tag/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
tag/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
tag/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -1,8 +0,0 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
tests-examples
example.spec.ts

View file

@ -1,97 +0,0 @@
{
"name": "end-to-end",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "end-to-end",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.49.1",
"@types/node": "^22.10.7"
}
},
"node_modules/@playwright/test": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.49.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@types/node": {
"version": "22.10.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.49.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
}
}
}

View file

@ -1,15 +0,0 @@
{
"name": "end-to-end",
"version": "1.0.0",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"description": "",
"devDependencies": {
"@playwright/test": "^1.49.1",
"@types/node": "^22.10.7"
}
}

View file

@ -1,77 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});

View file

@ -1,21 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-or-later
set -e
set -u
# DEBUG
set -x
main() {
cd "$(dirname "${0}")"
browser="${browser:-firefox}"
project="${project:-firefox}"
headed="${headed:---headed}"
npx playwright test --project "${project}" "${headed}"
}
main "${@}"
# written in emacs
# -*- mode: shell-script; -*-

View file

@ -1,237 +0,0 @@
import { test, expect } from '@playwright/test';
// TODO after the tests, put again demo.ereuse.org as default
const TEST_SITE = process.env.TEST_SITE || 'https://lab1.ereuse.org'
const TEST_USER = process.env.TEST_USER || 'user@example.org'
const TEST_PASSWD = process.env.TEST_PASSWD || '1234'
async function login(page, date, time) {
await page.goto(TEST_SITE);
await page.getByPlaceholder('Email address').click();
await page.getByPlaceholder('Email address').fill(TEST_USER);
await page.getByPlaceholder('Password').fill(TEST_PASSWD);
await page.getByPlaceholder('Password').press('Enter');
}
// when introducing a new test, use only temporarily to just enable that test
//
//test.only('NEW example', async ({ page }) => {
// await login(page);
// test.setTimeout(0)
// await page.pause();
//});
test('Evidence: create and destroy tag (custom id)', async ({ page }) => {
await login(page);
await page.goto(`${TEST_SITE}/evidence/`);
await page.locator('table a').first().click();
await page.getByRole('link', { name: 'Tag' }).click();
// create tag
await page.getByPlaceholder('Tag').click();
await page.getByPlaceholder('Tag').fill('test');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Tag mytag has been added.');
// delete tag
await page.getByRole('link', { name: 'Tag' }).click();
await page.getByRole('link', { name: 'Delete' }).click();
await expect(page.getByRole('alert')).toContainText('Tag mytag has been deleted.');
});
test('Property: create key-value, edit key, edit value, delete property property', async ({ page }) => {
const last_log = '#log tr:nth-child(1) td:nth-child(2)'
await login(page);
// assuming after login, we are in devices page, and there, there is a table with devices
await page.locator('table a').first().click();
// new property; key: init1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('link', { name: ' New user property' }).click();
await page.getByPlaceholder('Key').click();
await page.getByPlaceholder('Key').fill('init1');
await page.getByPlaceholder('Key').press('Tab');
await page.getByPlaceholder('Value').fill('1');
await page.getByRole('button', { name: 'Save' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init1 has been added.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Created> UserProperty: init1: 1');
// edit property; key: init2, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Edit' }).first().click();
await page.getByLabel('Key').click();
await page.getByLabel('Key').fill('init2');
await page.getByRole('button', { name: 'Save changes' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init2 has been updated.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Updated> UserProperty: init1: 1 to init2: 1');
// edit property; key: init2, value: 2
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Edit' }).first().click();
await page.getByLabel('Value').fill('2');
await page.getByRole('button', { name: 'Save changes' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init2 has been updated.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Updated> UserProperty: init2: 1 to init2: 2');
// delete property; key: init2, value: 2
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Delete' }).click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init2 has been updated.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Deleted> User Property: init2:2');
});
test('Property: duplication tests', async ({ page }) => {
await login(page);
// assuming after login, we are in devices page, and there, there is a table with devices
await page.locator('table a').first().click();
// new property; key: uniq1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('link', { name: ' New user property' }).click();
await page.getByPlaceholder('Key').click();
await page.getByPlaceholder('Key').fill('uniq1');
await page.getByPlaceholder('Key').press('Tab');
await page.getByPlaceholder('Value').fill('1');
await page.getByRole('button', { name: 'Save' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property uniq1 has been added.');
// new property (duplicate); key: uniq1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('link', { name: ' New user property' }).click();
await page.getByPlaceholder('Key').click();
await page.getByPlaceholder('Key').fill('uniq1');
await page.getByPlaceholder('Key').press('Tab');
await page.getByPlaceholder('Value').fill('1');
await page.getByRole('button', { name: 'Save' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property uniq1 already exists.');
// delete property; key: uniq1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Delete' }).first().click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property uniq1 deleted has been.');
});
test.only('States: duplication tests', async ({ page }) => {
await login(page);
await page.getByRole('link', { name: ' Admin' }).click();
await page.getByRole('link', { name: 'States' }).click();
// create state: TEST_STATE
await page.getByRole('button', { name: 'Add' }).click();
await page.getByRole('textbox', { name: 'State' }).click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE');
await page.getByRole('button', { name: 'Add state definition' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE has been added.');
// create state (duplicate): TEST_STATE
await page.getByRole('button', { name: 'Add' }).click();
await page.getByRole('textbox', { name: 'State' }).click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE');
await page.getByRole('button', { name: 'Add state definition' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE is already defined.');
// edit state: TEST_STATE -> TEST_STATE_EDIT
await page.getByRole('row', { name: 'TEST_STATE  Edit  Delete' }).getByRole('button').first().click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE_EDIT');
await page.getByRole('button', { name: 'Save Changes' }).click();
// create state: TEST_STATE
await page.getByRole('button', { name: 'Add' }).click();
await page.getByRole('textbox', { name: 'State' }).click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE');
await page.getByRole('button', { name: 'Add state definition' }).click();
// you edit state, and target name already exists
// TODO uncomment. "Cannot create key that already exists (UNIQUE constraint)"
// edit state (duplicated during edit): TEST_STATE_EDIT -> TEST_STATE
//await page.getByRole('row', { name: 'TEST_STATE  Edit  Delete' }).getByRole('button').first().click();
//await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE_EDIT');
//await page.getByRole('button', { name: 'Save Changes' }).click();
// delete state: TEST_STATE_EDIT
await page.getByRole('row', { name: 'TEST_STATE_EDIT  Edit  Delete' }).getByRole('button').nth(1).click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE has been deleted.');
// delete state: TEST_STATE
await page.getByRole('row', { name: 'TEST_STATE  Edit  Delete' }).getByRole('button').nth(1).click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE has been deleted.');
});
test('Lot: duplication tests', async ({ page }) => {
await login(page);
// add lot
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: ' Add new lot' }).click();
await page.getByLabel('Type').selectOption('1');
await page.getByPlaceholder('Name').click();
await page.getByPlaceholder('Name').fill('testlot');
await page.getByPlaceholder('Name').press('Tab');
await page.getByPlaceholder('Code').fill('testlot');
await page.getByPlaceholder('Code').press('Tab');
await page.getByPlaceholder('Description').fill('testlot');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Lot testlot has been added.');
// add (duplicate) lot
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: ' Add new lot' }).click();
await page.getByLabel('Type').selectOption('1');
await page.getByPlaceholder('Name').click();
await page.getByPlaceholder('Name').fill('testlot');
await page.getByPlaceholder('Name').press('Tab');
await page.getByPlaceholder('Code').fill('testlot');
await page.getByPlaceholder('Code').press('Tab');
await page.getByPlaceholder('Description').fill('testlot');
await page.getByPlaceholder('Description').press('Enter');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Lot testlot is already defined.');
// delete lot
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: '' }).nth(3).click();
await page.getByRole('link', { name: 'Cancel' }).click();
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: '' }).first().click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByRole('alert')).toContainText('Lot testlot has been deleted.');
});
// TODO falta probar la parte de notas
// falta vista https://lab1.ereuse.org/dashboard/ con columna de state actual; si no hay None pero con un estilo diferente (cursiva y gris?)
//test('Bug 4: Missing logs for actions', async ({ page }) => {
// await login(page);
// await page.goto(`${TEST_SITE}/device/7b769bd6e9191d5ff163fa4a206b9220dad10c47b45d210d3d4d31d586f6a4b6/#log`);
// // Add your assertions and steps to test if logs are missing
//});
//
//test('Bug 6: Log note is not visible', async ({ page }) => {
// await login(page);
// // Add the specific URL or steps for testing log note visibility
//});

View file

@ -1,7 +1,6 @@
from django.core.management.base import BaseCommand
from user.models import Institution
from lot.models import LotTag, Lot
from lot.models import LotTag
class Command(BaseCommand):
help = "Create a new Institution"
@ -12,14 +11,8 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs):
self.institution = Institution.objects.create(name=kwargs['name'])
self.create_lot_tags()
self.create_lots()
def create_lot_tags(self):
LotTag.objects.create(
inbox=True,
name="Inbox",
owner=self.institution
)
tags = [
"Entrada",
"Salida",
@ -30,59 +23,3 @@ class Command(BaseCommand):
name=tag,
owner=self.institution
)
def create_lots(self):
for g in LotTag.objects.all():
if g.name == "Entrada":
Lot.objects.create(
name="donante-orgA",
owner=self.institution,
archived=True,
type=g
)
Lot.objects.create(
name="donante-orgB",
owner=self.institution,
type=g
)
Lot.objects.create(
name="donante-orgC",
owner=self.institution,
type=g
)
if g.name == "Salida":
Lot.objects.create(
name="beneficiario-org1",
owner=self.institution,
type=g
)
Lot.objects.create(
name="beneficiario-org2",
owner=self.institution,
archived=True,
type=g
)
Lot.objects.create(
name="beneficiario-org3",
owner=self.institution,
type=g
)
if g.name == "Temporal":
Lot.objects.create(
name="palet1",
owner=self.institution,
type=g
)
Lot.objects.create(
name="palet2",
owner=self.institution,
type=g
)
Lot.objects.create(
name="palet3",
owner=self.institution,
archived=True,
type=g
)

View file

@ -1,18 +0,0 @@
# 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,10 +1,8 @@
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
ALGORITHMS = [(x, x) for x in ALGOS.keys()]
# Create your models here.
class Institution(models.Model):
@ -29,7 +27,6 @@ class Institution(models.Model):
blank=True,
null=True
)
algorithm = models.CharField(max_length=30, choices=ALGORITHMS, default='ereuse24')
class UserManager(BaseUserManager):

View file

@ -9,16 +9,15 @@ STR_EXTEND_SIZE = 256
# Algorithms for build hids
EREUSE24 = [
HID_ALGO1 = [
"manufacturer",
"model",
"chassis",
"serial_number",
"mac"
"sku"
]
# EREUSE22 is used for build the chid of DPP
EREUSE22 = [
LEGACY_DPP = [
"manufacturer",
"model",
"chassis",
@ -29,8 +28,8 @@ EREUSE22 = [
]
ALGOS = {
"ereuse24": EREUSE24,
"ereuse22": EREUSE22
"hidalgo1": HID_ALGO1,
"legacy_dpp": LEGACY_DPP
}