Compare commits
107 commits
main
...
feature/st
|
@ -1,3 +0,0 @@
|
|||
db
|
||||
.git
|
||||
already_configured
|
|
@ -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
|
56
.env.example
56
.env.example
|
@ -1,18 +1,8 @@
|
|||
####
|
||||
# DEV OPTIONS
|
||||
####
|
||||
|
||||
DEV_DOCKER_ALWAYS_BUILD=false
|
||||
|
||||
####
|
||||
# DEVICEHUB
|
||||
####
|
||||
|
||||
DEVICEHUB_DOMAIN=localhost
|
||||
DEVICEHUB_PORT=8001
|
||||
DOMAIN=localhost
|
||||
DEMO=true
|
||||
# note that with DEBUG=true, logs are more verbose (include tracebacks)
|
||||
DEBUG=true
|
||||
ALLOWED_HOSTS=${DOMAIN},${DOMAIN}:8000,127.0.0.1,127.0.0.1:8000
|
||||
DPP=false
|
||||
|
||||
STATIC_ROOT=/tmp/static/
|
||||
|
@ -26,48 +16,6 @@ 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}
|
||||
# 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
5
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 %}
|
|
@ -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'),
|
||||
]
|
||||
|
|
132
admin/views.py
132
admin/views.py
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 }}">
|
||||
«
|
||||
</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 }}">
|
||||
»
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
139
device/models.py
139
device/models.py
|
@ -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()
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
251
device/templates/physical_properties.html
Normal file
251
device/templates/physical_properties.html
Normal 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 %}
|
49
device/templates/tabs/documents.html
Normal file
49
device/templates/tabs/documents.html
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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" %}
|
||||
|
|
|
@ -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"),
|
||||
|
||||
]
|
||||
|
|
207
device/views.py
207
device/views.py
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
|
16
did/views.py
16
did/views.py
|
@ -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()
|
||||
|
|
|
@ -1,59 +1,17 @@
|
|||
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=${DOMAIN:-localhost}
|
||||
- ALLOWED_HOSTS=${ALLOWED_HOSTS:-$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}
|
||||
- 8000:8000
|
||||
|
||||
# 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
|
||||
|
|
|
@ -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:-}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
0
documents/__init__.py
Normal file
3
documents/admin.py
Normal file
3
documents/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
documents/apps.py
Normal file
6
documents/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DocumentsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "documents"
|
0
documents/migrations/__init__.py
Normal file
0
documents/migrations/__init__.py
Normal file
3
documents/models.py
Normal file
3
documents/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
documents/tests.py
Normal file
3
documents/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
documents/views.py
Normal file
3
documents/views.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -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,
|
||||
|
|
|
@ -58,9 +58,7 @@ class UploadForm(forms.Form):
|
|||
|
||||
for ev in self.evidences:
|
||||
path_name = save_in_disk(ev[1], user.institution.name)
|
||||
build = Build
|
||||
file_json = ev[1]
|
||||
build(file_json, user)
|
||||
Build(ev[1], user)
|
||||
move_json(path_name, user.institution.name)
|
||||
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from dmidecode import DMIParse
|
||||
from json_repair import repair_json
|
||||
from evidence.mixin_parse import BuildMix
|
||||
from evidence.legacy_parse_details import get_lshw_child, ParseSnapshot
|
||||
from utils.constants import CHASSIS_DH
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
def get_mac(lshw):
|
||||
try:
|
||||
if type(lshw) is dict:
|
||||
hw = lshw
|
||||
else:
|
||||
hw = json.loads(lshw)
|
||||
except json.decoder.JSONDecodeError:
|
||||
hw = json.loads(repair_json(lshw))
|
||||
|
||||
nets = []
|
||||
get_lshw_child(hw, nets, 'network')
|
||||
|
||||
nets_sorted = sorted(nets, key=lambda x: x['businfo'])
|
||||
|
||||
if nets_sorted:
|
||||
mac = nets_sorted[0]['serial']
|
||||
logger.debug("The snapshot has the following MAC: %s" , mac)
|
||||
return mac
|
||||
|
||||
|
||||
class Build(BuildMix):
|
||||
# This parse is for get info from snapshots created with
|
||||
# workbench-script but builded for send to devicehub-teal
|
||||
|
||||
def get_details(self):
|
||||
dmidecode_raw = self.json["data"]["dmidecode"]
|
||||
self.dmi = DMIParse(dmidecode_raw)
|
||||
|
||||
self.manufacturer = self.dmi.manufacturer().strip()
|
||||
self.model = self.dmi.model().strip()
|
||||
self.chassis = self.get_chassis_dh()
|
||||
self.serial_number = self.dmi.serial_number()
|
||||
self.sku = self.get_sku()
|
||||
self.type = self.chassis
|
||||
self.version = self.get_version()
|
||||
|
||||
def get_chassis_dh(self):
|
||||
chassis = self.get_chassis()
|
||||
lower_type = chassis.lower()
|
||||
for k, v in CHASSIS_DH.items():
|
||||
if lower_type in v:
|
||||
return k
|
||||
return self.default
|
||||
|
||||
def get_sku(self):
|
||||
return self.dmi.get("System")[0].get("SKU Number", "n/a").strip()
|
||||
|
||||
def get_chassis(self):
|
||||
return self.dmi.get("Chassis")[0].get("Type", '_virtual') #
|
||||
|
||||
def get_version(self):
|
||||
return self.dmi.get("System")[0].get("Verson", '_virtual')
|
||||
|
||||
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)
|
|
@ -1,503 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from datetime import datetime
|
||||
from dmidecode import DMIParse
|
||||
from json_repair import repair_json
|
||||
|
||||
from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
def get_lshw_child(child, nets, component):
|
||||
try:
|
||||
if child.get('id') == component:
|
||||
nets.append(child)
|
||||
if child.get('children'):
|
||||
[get_lshw_child(x, nets, component) for x in child['children']]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
class ParseSnapshot:
|
||||
def __init__(self, snapshot, default="n/a"):
|
||||
self.default = default
|
||||
self.dmidecode_raw = snapshot["data"].get("dmidecode", "{}")
|
||||
self.smart_raw = snapshot["data"].get("disks", [])
|
||||
self.hwinfo_raw = snapshot["data"].get("hwinfo", "")
|
||||
self.lshw_raw = snapshot["data"].get("lshw", {}) or {}
|
||||
self.lscpi_raw = snapshot["data"].get("lspci", "")
|
||||
self.device = {"actions": []}
|
||||
self.components = []
|
||||
self.monitors = []
|
||||
|
||||
self.dmi = DMIParse(self.dmidecode_raw)
|
||||
self.smart = self.loads(self.smart_raw)
|
||||
self.lshw = self.loads(self.lshw_raw)
|
||||
self.hwinfo = self.parse_hwinfo()
|
||||
|
||||
self.set_computer()
|
||||
self.get_hwinfo_monitors()
|
||||
self.set_components()
|
||||
self.snapshot_json = {
|
||||
"type": "Snapshot",
|
||||
"device": self.device,
|
||||
"software": snapshot["software"],
|
||||
"components": self.components,
|
||||
"uuid": snapshot['uuid'],
|
||||
"version": snapshot['version'],
|
||||
"endTime": snapshot["timestamp"],
|
||||
"elapsed": 1,
|
||||
}
|
||||
|
||||
def set_computer(self):
|
||||
self.device['manufacturer'] = self.dmi.manufacturer().strip()
|
||||
self.device['model'] = self.dmi.model().strip()
|
||||
self.device['serialNumber'] = self.dmi.serial_number()
|
||||
self.device['type'] = self.get_type()
|
||||
self.device['sku'] = self.get_sku()
|
||||
self.device['version'] = self.get_version()
|
||||
self.device['system_uuid'] = self.get_uuid()
|
||||
self.device['family'] = self.get_family()
|
||||
self.device['chassis'] = self.get_chassis_dh()
|
||||
|
||||
def set_components(self):
|
||||
self.get_cpu()
|
||||
self.get_ram()
|
||||
self.get_mother_board()
|
||||
self.get_graphic()
|
||||
self.get_data_storage()
|
||||
self.get_display()
|
||||
self.get_sound_card()
|
||||
self.get_networks()
|
||||
|
||||
def get_cpu(self):
|
||||
for cpu in self.dmi.get('Processor'):
|
||||
serial = cpu.get('Serial Number')
|
||||
if serial == 'Not Specified' or not serial:
|
||||
serial = cpu.get('ID').replace(' ', '')
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "Processor",
|
||||
"speed": self.get_cpu_speed(cpu),
|
||||
"cores": int(cpu.get('Core Count', 1)),
|
||||
"model": cpu.get('Version'),
|
||||
"threads": int(cpu.get('Thread Count', 1)),
|
||||
"manufacturer": cpu.get('Manufacturer'),
|
||||
"serialNumber": serial,
|
||||
"brand": cpu.get('Family'),
|
||||
"address": self.get_cpu_address(cpu),
|
||||
"bogomips": self.get_bogomips(),
|
||||
}
|
||||
)
|
||||
|
||||
def get_ram(self):
|
||||
for ram in self.dmi.get("Memory Device"):
|
||||
if ram.get('size') == 'No Module Installed':
|
||||
continue
|
||||
if not ram.get("Speed"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "RamModule",
|
||||
"size": self.get_ram_size(ram),
|
||||
"speed": self.get_ram_speed(ram),
|
||||
"manufacturer": ram.get("Manufacturer", self.default),
|
||||
"serialNumber": ram.get("Serial Number", self.default),
|
||||
"interface": ram.get("Type", "DDR"),
|
||||
"format": ram.get("Form Factor", "DIMM"),
|
||||
"model": ram.get("Part Number", self.default),
|
||||
}
|
||||
)
|
||||
|
||||
def get_mother_board(self):
|
||||
for moder_board in self.dmi.get("Baseboard"):
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "Motherboard",
|
||||
"version": moder_board.get("Version"),
|
||||
"serialNumber": moder_board.get("Serial Number", "").strip(),
|
||||
"manufacturer": moder_board.get("Manufacturer", "").strip(),
|
||||
"biosDate": self.get_bios_date(),
|
||||
"ramMaxSize": self.get_max_ram_size(),
|
||||
"ramSlots": len(self.dmi.get("Memory Device")),
|
||||
"slots": self.get_ram_slots(),
|
||||
"model": moder_board.get("Product Name", "").strip(),
|
||||
"firewire": self.get_firmware_num(),
|
||||
"pcmcia": self.get_pcmcia_num(),
|
||||
"serial": self.get_serial_num(),
|
||||
"usb": self.get_usb_num(),
|
||||
}
|
||||
)
|
||||
|
||||
def get_graphic(self):
|
||||
displays = []
|
||||
get_lshw_child(self.lshw, displays, 'display')
|
||||
|
||||
for c in displays:
|
||||
if not c['configuration'].get('driver', None):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "GraphicCard",
|
||||
"memory": "",
|
||||
"manufacturer": c.get("vendor", self.default),
|
||||
"model": c.get("product", self.default),
|
||||
"serialNumber": c.get("serial", self.default),
|
||||
}
|
||||
)
|
||||
|
||||
def get_data_storage(self):
|
||||
for sm in self.smart:
|
||||
if sm.get('smartctl', {}).get('exit_status') == 1:
|
||||
continue
|
||||
model = sm.get('model_name')
|
||||
manufacturer = None
|
||||
hours = sm.get("power_on_time", {}).get("hours", 0)
|
||||
if model and len(model.split(" ")) > 1:
|
||||
mm = model.split(" ")
|
||||
model = mm[-1]
|
||||
manufacturer = " ".join(mm[:-1])
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"actions": self.sanitize(sm),
|
||||
"type": self.get_data_storage_type(sm),
|
||||
"model": model,
|
||||
"manufacturer": manufacturer,
|
||||
"serialNumber": sm.get('serial_number'),
|
||||
"size": self.get_data_storage_size(sm),
|
||||
"variant": sm.get("firmware_version"),
|
||||
"interface": self.get_data_storage_interface(sm),
|
||||
"hours": hours,
|
||||
}
|
||||
)
|
||||
|
||||
def sanitize(self, action):
|
||||
return []
|
||||
|
||||
def get_bogomips(self):
|
||||
if not self.hwinfo:
|
||||
return self.default
|
||||
|
||||
bogomips = 0
|
||||
for row in self.hwinfo:
|
||||
for cel in row:
|
||||
if 'BogoMips' in cel:
|
||||
try:
|
||||
bogomips += float(cel.split(":")[-1])
|
||||
except Exception:
|
||||
pass
|
||||
return bogomips
|
||||
|
||||
def get_networks(self):
|
||||
networks = []
|
||||
get_lshw_child(self.lshw, networks, 'network')
|
||||
|
||||
for c in networks:
|
||||
capacity = c.get('capacity')
|
||||
wireless = bool(c.get('configuration', {}).get('wireless', False))
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "NetworkAdapter",
|
||||
"model": c.get('product'),
|
||||
"manufacturer": c.get('vendor'),
|
||||
"serialNumber": c.get('serial'),
|
||||
"speed": capacity,
|
||||
"variant": c.get('version', 1),
|
||||
"wireless": wireless or False,
|
||||
"integrated": "PCI:0000:00" in c.get("businfo", ""),
|
||||
}
|
||||
)
|
||||
|
||||
def get_sound_card(self):
|
||||
multimedias = []
|
||||
get_lshw_child(self.lshw, multimedias, 'multimedia')
|
||||
|
||||
for c in multimedias:
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "SoundCard",
|
||||
"model": c.get('product'),
|
||||
"manufacturer": c.get('vendor'),
|
||||
"serialNumber": c.get('serial'),
|
||||
}
|
||||
)
|
||||
|
||||
def get_display(self): # noqa: C901
|
||||
TECHS = 'CRT', 'TFT', 'LED', 'PDP', 'LCD', 'OLED', 'AMOLED'
|
||||
|
||||
for c in self.monitors:
|
||||
resolution_width, resolution_height = (None,) * 2
|
||||
refresh, serial, model, manufacturer, size = (None,) * 5
|
||||
year, week, production_date = (None,) * 3
|
||||
|
||||
for x in c:
|
||||
if "Vendor: " in x:
|
||||
manufacturer = x.split('Vendor: ')[-1].strip()
|
||||
if "Model: " in x:
|
||||
model = x.split('Model: ')[-1].strip()
|
||||
if "Serial ID: " in x:
|
||||
serial = x.split('Serial ID: ')[-1].strip()
|
||||
if " Resolution: " in x:
|
||||
rs = x.split(' Resolution: ')[-1].strip()
|
||||
if 'x' in rs:
|
||||
resolution_width, resolution_height = [
|
||||
int(r) for r in rs.split('x')
|
||||
]
|
||||
if "Frequencies: " in x:
|
||||
try:
|
||||
refresh = int(float(x.split(',')[-1].strip()[:-3]))
|
||||
except Exception:
|
||||
pass
|
||||
if 'Year of Manufacture' in x:
|
||||
year = x.split(': ')[1]
|
||||
|
||||
if 'Week of Manufacture' in x:
|
||||
week = x.split(': ')[1]
|
||||
|
||||
if "Size: " in x:
|
||||
size = self.get_size_monitor(x)
|
||||
technology = next((t for t in TECHS if t in c[0]), None)
|
||||
|
||||
if year and week:
|
||||
d = '{} {} 0'.format(year, week)
|
||||
production_date = datetime.strptime(d, '%Y %W %w').isoformat()
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"actions": [],
|
||||
"type": "Display",
|
||||
"model": model,
|
||||
"manufacturer": manufacturer,
|
||||
"serialNumber": serial,
|
||||
'size': size,
|
||||
'resolutionWidth': resolution_width,
|
||||
'resolutionHeight': resolution_height,
|
||||
"productionDate": production_date,
|
||||
'technology': technology,
|
||||
'refreshRate': refresh,
|
||||
}
|
||||
)
|
||||
|
||||
def get_hwinfo_monitors(self):
|
||||
for c in self.hwinfo:
|
||||
monitor = None
|
||||
external = None
|
||||
for x in c:
|
||||
if 'Hardware Class: monitor' in x:
|
||||
monitor = c
|
||||
if 'Driver Info' in x:
|
||||
external = c
|
||||
|
||||
if monitor and not external:
|
||||
self.monitors.append(c)
|
||||
|
||||
def get_size_monitor(self, x):
|
||||
i = 1 / 25.4
|
||||
t = x.split('Size: ')[-1].strip()
|
||||
tt = t.split('mm')
|
||||
if not tt:
|
||||
return 0
|
||||
sizes = tt[0].strip()
|
||||
if 'x' not in sizes:
|
||||
return 0
|
||||
w, h = [int(x) for x in sizes.split('x')]
|
||||
return "{:.2f}".format(np.sqrt(w**2 + h**2) * i)
|
||||
|
||||
def get_cpu_address(self, cpu):
|
||||
default = 64
|
||||
|
||||
try:
|
||||
for ch in self.lshw.get('children', []):
|
||||
for c in ch.get('children', []):
|
||||
if c['class'] == 'processor':
|
||||
return c.get('width', default)
|
||||
except:
|
||||
return default
|
||||
return default
|
||||
|
||||
def get_usb_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "USB" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_serial_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "SERIAL" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_firmware_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "FIRMWARE" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_pcmcia_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "PCMCIA" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_bios_date(self):
|
||||
return self.dmi.get("BIOS")[0].get("Release Date", self.default)
|
||||
|
||||
def get_firmware(self):
|
||||
return self.dmi.get("BIOS")[0].get("Firmware Revision", '1')
|
||||
|
||||
def get_max_ram_size(self):
|
||||
size = 0
|
||||
for slot in self.dmi.get("Physical Memory Array"):
|
||||
capacity = slot.get("Maximum Capacity", '0').split(" ")[0]
|
||||
size += int(capacity)
|
||||
|
||||
return size
|
||||
|
||||
def get_ram_slots(self):
|
||||
slots = 0
|
||||
for x in self.dmi.get("Physical Memory Array"):
|
||||
slots += int(x.get("Number Of Devices", 0))
|
||||
return slots
|
||||
|
||||
def get_ram_size(self, ram):
|
||||
memory = ram.get("Size", "0")
|
||||
return memory
|
||||
|
||||
def get_ram_speed(self, ram):
|
||||
size = ram.get("Speed", "0")
|
||||
return size
|
||||
|
||||
def get_cpu_speed(self, cpu):
|
||||
speed = cpu.get('Max Speed', "0")
|
||||
return speed
|
||||
|
||||
def get_sku(self):
|
||||
return self.dmi.get("System")[0].get("SKU Number", self.default).strip()
|
||||
|
||||
def get_version(self):
|
||||
return self.dmi.get("System")[0].get("Version", self.default).strip()
|
||||
|
||||
def get_uuid(self):
|
||||
return self.dmi.get("System")[0].get("UUID", '').strip()
|
||||
|
||||
def get_family(self):
|
||||
return self.dmi.get("System")[0].get("Family", '')
|
||||
|
||||
def get_chassis(self):
|
||||
return self.dmi.get("Chassis")[0].get("Type", '_virtual')
|
||||
|
||||
def get_type(self):
|
||||
chassis_type = self.get_chassis()
|
||||
return self.translation_to_devicehub(chassis_type)
|
||||
|
||||
def translation_to_devicehub(self, original_type):
|
||||
lower_type = original_type.lower()
|
||||
CHASSIS_TYPE = {
|
||||
'Desktop': [
|
||||
'desktop',
|
||||
'low-profile',
|
||||
'tower',
|
||||
'docking',
|
||||
'all-in-one',
|
||||
'pizzabox',
|
||||
'mini-tower',
|
||||
'space-saving',
|
||||
'lunchbox',
|
||||
'mini',
|
||||
'stick',
|
||||
],
|
||||
'Laptop': [
|
||||
'portable',
|
||||
'laptop',
|
||||
'convertible',
|
||||
'tablet',
|
||||
'detachable',
|
||||
'notebook',
|
||||
'handheld',
|
||||
'sub-notebook',
|
||||
],
|
||||
'Server': ['server'],
|
||||
'Computer': ['_virtual'],
|
||||
}
|
||||
for k, v in CHASSIS_TYPE.items():
|
||||
if lower_type in v:
|
||||
return k
|
||||
return self.default
|
||||
|
||||
def get_chassis_dh(self):
|
||||
chassis = self.get_chassis()
|
||||
lower_type = chassis.lower()
|
||||
for k, v in CHASSIS_DH.items():
|
||||
if lower_type in v:
|
||||
return k
|
||||
return self.default
|
||||
|
||||
def get_data_storage_type(self, x):
|
||||
# TODO @cayop add more SSDS types
|
||||
SSDS = ["nvme"]
|
||||
SSD = 'SolidStateDrive'
|
||||
HDD = 'HardDrive'
|
||||
type_dev = x.get('device', {}).get('type')
|
||||
trim = x.get('trim', {}).get("supported") in [True, "true"]
|
||||
return SSD if type_dev in SSDS or trim else HDD
|
||||
|
||||
def get_data_storage_interface(self, x):
|
||||
interface = x.get('device', {}).get('protocol', 'ATA')
|
||||
if interface.upper() in DATASTORAGEINTERFACE:
|
||||
return interface.upper()
|
||||
|
||||
txt = "Sid: {}, interface {} is not in DataStorageInterface Enum".format(
|
||||
self.sid, interface
|
||||
)
|
||||
self.errors("{}".format(txt))
|
||||
|
||||
def get_data_storage_size(self, x):
|
||||
return x.get('user_capacity', {}).get('bytes')
|
||||
|
||||
def parse_hwinfo(self):
|
||||
hw_blocks = self.hwinfo_raw.split("\n\n")
|
||||
return [x.split("\n") for x in hw_blocks]
|
||||
|
||||
def loads(self, x):
|
||||
if isinstance(x, str):
|
||||
try:
|
||||
try:
|
||||
hw = json.loads(x)
|
||||
except json.decoder.JSONDecodeError:
|
||||
hw = json.loads(repair_json(x))
|
||||
return hw
|
||||
except Exception as ss:
|
||||
logger.warning("%s", ss)
|
||||
return {}
|
||||
return x
|
||||
|
||||
def errors(self, txt=None):
|
||||
if not txt:
|
||||
return self._errors
|
||||
|
||||
logger.error(txt)
|
||||
self._errors.append("%s", txt)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -1,64 +0,0 @@
|
|||
import logging
|
||||
from django.conf import settings
|
||||
|
||||
from utils.constants import ALGOS
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
class BuildMix:
|
||||
def __init__(self, evidence_json):
|
||||
self.json = evidence_json
|
||||
self.uuid = self.json.get('uuid')
|
||||
self.manufacturer = ""
|
||||
self.model = ""
|
||||
self.serial_number = ""
|
||||
self.chassis = ""
|
||||
self.sku = ""
|
||||
self.mac = ""
|
||||
self.type = ""
|
||||
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()
|
||||
|
||||
def get_hid(self, algo):
|
||||
algorithm = ALGOS.get(algo, [])
|
||||
hid = ""
|
||||
for f in algorithm:
|
||||
if hasattr(self, f):
|
||||
hid += getattr(self, f) or ''
|
||||
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)
|
||||
|
||||
def get_doc(self):
|
||||
self._get_components()
|
||||
|
||||
components = sorted(self.components, key=lambda x: x.get("type"))
|
||||
device = self.algorithms.get('ereuse22')
|
||||
|
||||
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", [])
|
||||
hid = ""
|
||||
for f in algorithm:
|
||||
hid += d.get(f, '')
|
||||
return hid
|
|
@ -8,8 +8,7 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from utils.constants import STR_EXTEND_SIZE, CHASSIS_DH
|
||||
from evidence.xapian import search
|
||||
from evidence.parse_details import ParseSnapshot
|
||||
from evidence.normal_parse_details import get_inxi, get_inxi_key
|
||||
from evidence.parse_details import ParseSnapshot, get_inxi, get_inxi_key
|
||||
from user.models import User, Institution
|
||||
|
||||
|
||||
|
@ -37,20 +36,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):
|
||||
|
@ -111,7 +105,7 @@ class Evidence:
|
|||
self.inxi = ev["output"]
|
||||
else:
|
||||
dmidecode_raw = self.doc["data"]["dmidecode"]
|
||||
inxi_raw = self.doc.get("data", {}).get("inxi")
|
||||
inxi_raw = self.doc["data"]["inxi"]
|
||||
self.dmi = DMIParse(dmidecode_raw)
|
||||
try:
|
||||
self.inxi = json.loads(inxi_raw)
|
||||
|
@ -135,11 +129,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():
|
||||
|
@ -155,7 +147,7 @@ class Evidence:
|
|||
return list(self.doc.get('kv').values())[0]
|
||||
|
||||
if self.is_legacy():
|
||||
return self.doc.get('device', {}).get('manufacturer', '')
|
||||
return self.doc['device']['manufacturer']
|
||||
|
||||
if self.inxi:
|
||||
return self.device_manufacturer
|
||||
|
@ -170,7 +162,7 @@ class Evidence:
|
|||
return list(self.doc.get('kv').values())[1]
|
||||
|
||||
if self.is_legacy():
|
||||
return self.doc.get('device', {}).get('model', '')
|
||||
return self.doc['device']['model']
|
||||
|
||||
if self.inxi:
|
||||
return self.device_model
|
||||
|
@ -179,7 +171,7 @@ class Evidence:
|
|||
|
||||
def get_chassis(self):
|
||||
if self.is_legacy():
|
||||
return self.doc.get('device', {}).get('model', '')
|
||||
return self.doc['device']['model']
|
||||
|
||||
if self.inxi:
|
||||
return self.device_chassis
|
||||
|
@ -194,7 +186,7 @@ class Evidence:
|
|||
|
||||
def get_serial_number(self):
|
||||
if self.is_legacy():
|
||||
return self.doc.get('device', {}).get('serialNumber', '')
|
||||
return self.doc['device']['serialNumber']
|
||||
|
||||
if self.inxi:
|
||||
return self.device_serial_number
|
||||
|
@ -211,11 +203,12 @@ 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):
|
||||
self.components = ParseSnapshot(self.doc).components
|
||||
snapshot = ParseSnapshot(self.doc).snapshot_json
|
||||
self.components = snapshot['components']
|
||||
|
||||
def is_legacy(self):
|
||||
if self.doc.get("credentialSubject"):
|
||||
|
@ -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(":", "/")
|
||||
)
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from evidence.mixin_parse import BuildMix
|
||||
from evidence.normal_parse_details import get_inxi_key, get_inxi, ParseSnapshot
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
def get_mac(inxi):
|
||||
nets = get_inxi_key(inxi, "Network")
|
||||
n_nets = len(nets) - 1
|
||||
|
||||
for i in range(0, n_nets):
|
||||
if i + 1 > n_nets:
|
||||
break
|
||||
|
||||
n = nets[i]
|
||||
iface = nets[i + 1]
|
||||
if get_inxi(n, "port"):
|
||||
return get_inxi(iface, 'mac')
|
||||
|
||||
|
||||
class Build(BuildMix):
|
||||
|
||||
def get_details(self):
|
||||
self.from_credential()
|
||||
try:
|
||||
self.inxi = self.json["data"]["inxi"]
|
||||
if isinstance(self.inxi, str):
|
||||
self.inxi = json.loads(self.inxi)
|
||||
except Exception:
|
||||
logger.error("No inxi in snapshot %s", self.uuid)
|
||||
return ""
|
||||
|
||||
machine = get_inxi_key(self.inxi, 'Machine')
|
||||
for m in machine:
|
||||
system = get_inxi(m, "System")
|
||||
if system:
|
||||
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")
|
||||
else:
|
||||
self.sku = get_inxi(m, "part-nu")
|
||||
|
||||
self.mac = get_mac(self.inxi) or ""
|
||||
if not self.mac:
|
||||
txt = "Could not retrieve MAC address in snapshot %s"
|
||||
logger.warning(txt, self.uuid)
|
||||
|
||||
def from_credential(self):
|
||||
if not self.json.get("credentialSubject"):
|
||||
return
|
||||
|
||||
self.uuid = self.json.get("credentialSubject", {}).get("uuid")
|
||||
self.json.update(self.json["credentialSubject"])
|
||||
if self.json.get("evidence"):
|
||||
self.json["data"] = {}
|
||||
for ev in self.json["evidence"]:
|
||||
k = ev.get("operation")
|
||||
if not k:
|
||||
continue
|
||||
self.json["data"][k] = ev.get("output")
|
||||
|
||||
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)
|
|
@ -1,410 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from dmidecode import DMIParse
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
def get_inxi_key(inxi, component):
|
||||
for n in inxi:
|
||||
for k, v in n.items():
|
||||
if component in k:
|
||||
return v
|
||||
|
||||
|
||||
def get_inxi(n, name):
|
||||
for k, v in n.items():
|
||||
if f"#{name}" in k:
|
||||
return v
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
class ParseSnapshot:
|
||||
def __init__(self, snapshot, default="n/a"):
|
||||
self.default = default
|
||||
self.dmidecode_raw = snapshot.get("data", {}).get("dmidecode", "{}")
|
||||
self.smart_raw = snapshot.get("data", {}).get("smartctl", [])
|
||||
self.inxi_raw = snapshot.get("data", {}).get("inxi", "") or ""
|
||||
for ev in snapshot.get("evidence", []):
|
||||
if "dmidecode" == ev.get("operation"):
|
||||
self.dmidecode_raw = ev["output"]
|
||||
if "inxi" == ev.get("operation"):
|
||||
self.inxi_raw = ev["output"]
|
||||
if "smartctl" == ev.get("operation"):
|
||||
self.smart_raw = ev["output"]
|
||||
data = snapshot
|
||||
if snapshot.get("credentialSubject"):
|
||||
data = snapshot["credentialSubject"]
|
||||
|
||||
self.device = {"actions": []}
|
||||
self.components = []
|
||||
|
||||
self.dmi = DMIParse(self.dmidecode_raw)
|
||||
self.smart = self.loads(self.smart_raw)
|
||||
self.inxi = self.loads(self.inxi_raw)
|
||||
|
||||
self.set_computer()
|
||||
self.set_components()
|
||||
self.snapshot_json = {
|
||||
"type": "Snapshot",
|
||||
"device": self.device,
|
||||
"software": data["software"],
|
||||
"components": self.components,
|
||||
"uuid": data['uuid'],
|
||||
"endTime": data["timestamp"],
|
||||
"elapsed": 1,
|
||||
}
|
||||
|
||||
def set_computer(self):
|
||||
machine = get_inxi_key(self.inxi, 'Machine') or []
|
||||
for m in machine:
|
||||
system = get_inxi(m, "System")
|
||||
if system:
|
||||
self.device['manufacturer'] = system
|
||||
self.device['model'] = get_inxi(m, "product")
|
||||
self.device['serialNumber'] = get_inxi(m, "serial")
|
||||
self.device['type'] = get_inxi(m, "Type")
|
||||
self.device['chassis'] = self.device['type']
|
||||
self.device['version'] = get_inxi(m, "v")
|
||||
else:
|
||||
self.device['system_uuid'] = get_inxi(m, "uuid")
|
||||
self.device['sku'] = get_inxi(m, "part-nu")
|
||||
|
||||
def set_components(self):
|
||||
self.get_mother_board()
|
||||
self.get_cpu()
|
||||
self.get_ram()
|
||||
self.get_graphic()
|
||||
self.get_display()
|
||||
self.get_networks()
|
||||
self.get_sound_card()
|
||||
self.get_data_storage()
|
||||
self.get_battery()
|
||||
|
||||
def get_mother_board(self):
|
||||
machine = get_inxi_key(self.inxi, 'Machine') or []
|
||||
mb = {"type": "Motherboard",}
|
||||
for m in machine:
|
||||
bios_date = get_inxi(m, "date")
|
||||
if not bios_date:
|
||||
continue
|
||||
mb["manufacturer"] = get_inxi(m, "Mobo")
|
||||
mb["model"] = get_inxi(m, "model")
|
||||
mb["serialNumber"] = get_inxi(m, "serial")
|
||||
mb["version"] = get_inxi(m, "v")
|
||||
mb["biosDate"] = bios_date
|
||||
mb["biosVersion"] = self.get_bios_version()
|
||||
mb["firewire"]: self.get_firmware_num()
|
||||
mb["pcmcia"]: self.get_pcmcia_num()
|
||||
mb["serial"]: self.get_serial_num()
|
||||
mb["usb"]: self.get_usb_num()
|
||||
|
||||
self.get_ram_slots(mb)
|
||||
|
||||
self.components.append(mb)
|
||||
|
||||
def get_ram_slots(self, mb):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
for m in memory:
|
||||
slots = get_inxi(m, "slots")
|
||||
if not slots:
|
||||
continue
|
||||
mb["slots"] = slots
|
||||
mb["ramSlots"] = get_inxi(m, "modules")
|
||||
mb["ramMaxSize"] = get_inxi(m, "capacity")
|
||||
|
||||
|
||||
def get_cpu(self):
|
||||
cpu = get_inxi_key(self.inxi, 'CPU') or []
|
||||
cp = {"type": "Processor"}
|
||||
vulnerabilities = []
|
||||
for c in cpu:
|
||||
base = get_inxi(c, "model")
|
||||
if base:
|
||||
cp["model"] = get_inxi(c, "model")
|
||||
cp["arch"] = get_inxi(c, "arch")
|
||||
cp["bits"] = get_inxi(c, "bits")
|
||||
cp["gen"] = get_inxi(c, "gen")
|
||||
cp["family"] = get_inxi(c, "family")
|
||||
cp["date"] = get_inxi(c, "built")
|
||||
continue
|
||||
des = get_inxi(c, "L1")
|
||||
if des:
|
||||
cp["L1"] = des
|
||||
cp["L2"] = get_inxi(c, "L2")
|
||||
cp["L3"] = get_inxi(c, "L3")
|
||||
cp["cpus"] = get_inxi(c, "cpus")
|
||||
cp["cores"] = get_inxi(c, "cores")
|
||||
cp["threads"] = get_inxi(c, "threads")
|
||||
continue
|
||||
bogo = get_inxi(c, "bogomips")
|
||||
if bogo:
|
||||
cp["bogomips"] = bogo
|
||||
cp["base/boost"] = get_inxi(c, "base/boost")
|
||||
cp["min/max"] = get_inxi(c, "min/max")
|
||||
cp["ext-clock"] = get_inxi(c, "ext-clock")
|
||||
cp["volts"] = get_inxi(c, "volts")
|
||||
continue
|
||||
ctype = get_inxi(c, "Type")
|
||||
if ctype:
|
||||
v = {"Type": ctype}
|
||||
status = get_inxi(c, "status")
|
||||
if status:
|
||||
v["status"] = status
|
||||
mitigation = get_inxi(c, "mitigation")
|
||||
if mitigation:
|
||||
v["mitigation"] = mitigation
|
||||
vulnerabilities.append(v)
|
||||
|
||||
self.components.append(cp)
|
||||
|
||||
|
||||
def get_ram(self):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
mem = {"type": "RamModule"}
|
||||
|
||||
for m in memory:
|
||||
base = get_inxi(m, "System RAM")
|
||||
if base:
|
||||
mem["size"] = get_inxi(m, "total")
|
||||
slot = get_inxi(m, "manufacturer")
|
||||
if slot:
|
||||
mem["manufacturer"] = slot
|
||||
mem["model"] = get_inxi(m, "part-no")
|
||||
mem["serialNumber"] = get_inxi(m, "serial")
|
||||
mem["speed"] = get_inxi(m, "speed")
|
||||
mem["bits"] = get_inxi(m, "data")
|
||||
mem["interface"] = get_inxi(m, "type")
|
||||
module = get_inxi(m, "modules")
|
||||
if module:
|
||||
mem["modules"] = module
|
||||
|
||||
self.components.append(mem)
|
||||
|
||||
def get_graphic(self):
|
||||
graphics = get_inxi_key(self.inxi, 'Graphics') or []
|
||||
|
||||
for c in graphics:
|
||||
if not get_inxi(c, "Device") or not get_inxi(c, "vendor"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "GraphicCard",
|
||||
"memory": self.get_memory_video(c),
|
||||
"manufacturer": get_inxi(c, "vendor"),
|
||||
"model": get_inxi(c, "Device"),
|
||||
"arch": get_inxi(c, "arch"),
|
||||
"serialNumber": get_inxi(c, "serial"),
|
||||
"integrated": True if get_inxi(c, "port") else False
|
||||
}
|
||||
)
|
||||
|
||||
def get_battery(self):
|
||||
bats = get_inxi_key(self.inxi, 'Battery') or []
|
||||
for b in bats:
|
||||
self.components.append(
|
||||
{
|
||||
"type": "Battery",
|
||||
"model": get_inxi(b, "model"),
|
||||
"serialNumber": get_inxi(b, "serial"),
|
||||
"condition": get_inxi(b, "condition"),
|
||||
"cycles": get_inxi(b, "cycles"),
|
||||
"volts": get_inxi(b, "volts")
|
||||
}
|
||||
)
|
||||
|
||||
def get_memory_video(self, c):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
|
||||
for m in memory:
|
||||
igpu = get_inxi(m, "igpu")
|
||||
agpu = get_inxi(m, "agpu")
|
||||
ngpu = get_inxi(m, "ngpu")
|
||||
gpu = get_inxi(m, "gpu")
|
||||
if igpu or agpu or gpu or ngpu:
|
||||
return igpu or agpu or gpu or ngpu
|
||||
|
||||
return self.default
|
||||
|
||||
def get_data_storage(self):
|
||||
hdds= get_inxi_key(self.inxi, 'Drives') or []
|
||||
for d in hdds:
|
||||
usb = get_inxi(d, "type")
|
||||
if usb == "USB":
|
||||
continue
|
||||
|
||||
serial = get_inxi(d, "serial")
|
||||
if serial:
|
||||
hd = {
|
||||
"type": "Storage",
|
||||
"manufacturer": get_inxi(d, "vendor"),
|
||||
"model": get_inxi(d, "model"),
|
||||
"serialNumber": get_inxi(d, "serial"),
|
||||
"size": get_inxi(d, "size"),
|
||||
"speed": get_inxi(d, "speed"),
|
||||
"interface": get_inxi(d, "tech"),
|
||||
"firmware": get_inxi(d, "fw-rev")
|
||||
}
|
||||
rpm = get_inxi(d, "rpm")
|
||||
if rpm:
|
||||
hd["rpm"] = rpm
|
||||
|
||||
family = get_inxi(d, "family")
|
||||
if family:
|
||||
hd["family"] = family
|
||||
|
||||
sata = get_inxi(d, "sata")
|
||||
if sata:
|
||||
hd["sata"] = sata
|
||||
|
||||
continue
|
||||
|
||||
|
||||
cycles = get_inxi(d, "cycles")
|
||||
if cycles:
|
||||
hd['cycles'] = cycles
|
||||
hd["health"] = get_inxi(d, "health")
|
||||
hd["time of used"] = get_inxi(d, "on")
|
||||
hd["read used"] = get_inxi(d, "read-units")
|
||||
hd["written used"] = get_inxi(d, "written-units")
|
||||
|
||||
self.components.append(hd)
|
||||
continue
|
||||
|
||||
hd = {}
|
||||
|
||||
def sanitize(self, action):
|
||||
return []
|
||||
|
||||
def get_networks(self):
|
||||
nets = get_inxi_key(self.inxi, "Network") or []
|
||||
for i in range(0, len(nets)-1):
|
||||
n = nets[i]
|
||||
model = get_inxi(n, "Device")
|
||||
if not model:
|
||||
continue
|
||||
|
||||
interface = ''
|
||||
for k in n.keys():
|
||||
if "port" in k:
|
||||
interface = "Integrated"
|
||||
if "pcie" in k:
|
||||
interface = "PciExpress"
|
||||
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({
|
||||
"type": "NetworkAdapter",
|
||||
"model": model,
|
||||
"manufacturer": manufacturer,
|
||||
"serialNumber": mac,
|
||||
"speed": speed,
|
||||
"interface": interface,
|
||||
})
|
||||
|
||||
|
||||
def get_sound_card(self):
|
||||
audio = get_inxi_key(self.inxi, "Audio") or []
|
||||
|
||||
for c in audio:
|
||||
model = get_inxi(c, "Device")
|
||||
if not model:
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "SoundCard",
|
||||
"model": model,
|
||||
"manufacturer": get_inxi(c, 'vendor'),
|
||||
"serialNumber": get_inxi(c, 'serial'),
|
||||
}
|
||||
)
|
||||
|
||||
def get_display(self):
|
||||
graphics = get_inxi_key(self.inxi, "Graphics") or []
|
||||
for c in graphics:
|
||||
if not get_inxi(c, "Monitor"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "Display",
|
||||
"model": get_inxi(c, "model"),
|
||||
"manufacturer": get_inxi(c, "vendor"),
|
||||
"serialNumber": get_inxi(c, "serial"),
|
||||
'size': get_inxi(c, "size"),
|
||||
'diagonal': get_inxi(c, "diag"),
|
||||
'resolution': get_inxi(c, "res"),
|
||||
"date": get_inxi(c, "built"),
|
||||
'ratio': get_inxi(c, "ratio"),
|
||||
}
|
||||
)
|
||||
|
||||
def get_usb_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "USB" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_serial_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "SERIAL" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_firmware_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "FIRMWARE" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_pcmcia_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "PCMCIA" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_bios_version(self):
|
||||
return self.dmi.get("BIOS")[0].get("BIOS Revision", '1')
|
||||
|
||||
def loads(self, x):
|
||||
if isinstance(x, str):
|
||||
try:
|
||||
return json.loads(x)
|
||||
except Exception as ss:
|
||||
logger.warning("%s", ss)
|
||||
return {}
|
||||
return x
|
||||
|
||||
def errors(self, txt=None):
|
||||
if not txt:
|
||||
return self._errors
|
||||
|
||||
logger.error(txt)
|
||||
self._errors.append("%s", txt)
|
|
@ -1,28 +0,0 @@
|
|||
import logging
|
||||
|
||||
from evidence.mixin_parse import BuildMix
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
class Build(BuildMix):
|
||||
# This parse is for get info from snapshots created with old workbench
|
||||
# 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", '')
|
||||
|
||||
def _get_components(self):
|
||||
self.components = self.json.get("components", [])
|
||||
|
||||
self.device.pop("actions", None)
|
||||
for c in self.components:
|
||||
c.pop("actions", None)
|
|
@ -1,13 +0,0 @@
|
|||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
class ParseSnapshot:
|
||||
def __init__(self, snapshot, default="n/a"):
|
||||
self.default = default
|
||||
self.snapshot_json = snapshot
|
||||
|
||||
self.device = snapshot.get("device")
|
||||
self.components = snapshot.get("components")
|
|
@ -2,14 +2,12 @@ import json
|
|||
import hashlib
|
||||
import logging
|
||||
|
||||
from evidence import legacy_parse
|
||||
from evidence import old_parse
|
||||
from evidence import normal_parse
|
||||
from dmidecode import DMIParse
|
||||
from evidence.parse_details import ParseSnapshot
|
||||
|
||||
from evidence.models import SystemProperty
|
||||
from evidence.xapian import index
|
||||
from evidence.normal_parse_details import get_inxi_key, get_inxi
|
||||
from evidence.parse_details import get_inxi_key, get_inxi
|
||||
from django.conf import settings
|
||||
|
||||
if settings.DPP:
|
||||
|
@ -26,40 +24,34 @@ def get_mac(inxi):
|
|||
if get_inxi(n, "port"):
|
||||
return get_inxi(iface, 'mac')
|
||||
|
||||
|
||||
class Build:
|
||||
def __init__(self, evidence_json, user, check=False):
|
||||
"""
|
||||
This Build do the save in xapian as document, in Annotations and do
|
||||
register in dlt if is configured for that.
|
||||
|
||||
We have 4 cases for parser diferents snapshots than come from workbench.
|
||||
1) worbench 11 is old_parse.
|
||||
2) legacy is the worbench-script when create a snapshot for devicehub-teal
|
||||
3) some snapshots come as a credential. In this case is parsed as normal_parse
|
||||
4) normal snapshot from worbench-script is the most basic and is parsed as normal_parse
|
||||
"""
|
||||
self.evidence = evidence_json.copy()
|
||||
self.uuid = self.evidence.get('uuid')
|
||||
self.user = user
|
||||
self.json = evidence_json.copy()
|
||||
|
||||
if evidence_json.get("credentialSubject"):
|
||||
self.build = normal_parse.Build(evidence_json)
|
||||
self.uuid = evidence_json.get("credentialSubject", {}).get("uuid")
|
||||
elif evidence_json.get("software") != "workbench-script":
|
||||
self.build = old_parse.Build(evidence_json)
|
||||
elif evidence_json.get("data",{}).get("lshw"):
|
||||
self.build = legacy_parse.Build(evidence_json)
|
||||
else:
|
||||
self.build = normal_parse.Build(evidence_json)
|
||||
self.json.update(evidence_json["credentialSubject"])
|
||||
if evidence_json.get("evidence"):
|
||||
self.json["data"] = {}
|
||||
for ev in evidence_json["evidence"]:
|
||||
k = ev.get("operation")
|
||||
if not k:
|
||||
continue
|
||||
self.json["data"][k] = ev.get("output")
|
||||
|
||||
self.uuid = self.json['uuid']
|
||||
self.user = user
|
||||
self.hid = None
|
||||
self.chid = None
|
||||
self.phid = self.get_signature(self.json)
|
||||
self.generate_chids()
|
||||
|
||||
if check:
|
||||
return
|
||||
|
||||
if not self.build.uuid:
|
||||
return
|
||||
|
||||
self.index()
|
||||
self.create_annotations()
|
||||
self.create_properties()
|
||||
if settings.DPP:
|
||||
self.register_device_dlt()
|
||||
|
||||
|
@ -67,32 +59,123 @@ class Build:
|
|||
snap = json.dumps(self.evidence)
|
||||
index(self.user.institution, self.uuid, snap)
|
||||
|
||||
def create_annotations(self):
|
||||
prop = SystemProperty.objects.filter(
|
||||
def generate_chids(self):
|
||||
self.algorithms = {
|
||||
'hidalgo1': self.get_hid_14(),
|
||||
'legacy_dpp': self.get_chid_dpp(),
|
||||
}
|
||||
|
||||
def get_hid_14(self):
|
||||
if self.json.get("software") == "workbench-script":
|
||||
hid = self.get_hid(self.json)
|
||||
else:
|
||||
device = self.json['device']
|
||||
manufacturer = device.get("manufacturer", '')
|
||||
model = device.get("model", '')
|
||||
chassis = device.get("chassis", '')
|
||||
serial_number = device.get("serialNumber", '')
|
||||
sku = device.get("sku", '')
|
||||
hid = f"{manufacturer}{model}{chassis}{serial_number}{sku}"
|
||||
|
||||
self.chid = hashlib.sha3_256(hid.encode()).hexdigest()
|
||||
return self.chid
|
||||
|
||||
def get_chid_dpp(self):
|
||||
if self.json.get("software") == "workbench-script":
|
||||
device = ParseSnapshot(self.json).device
|
||||
else:
|
||||
device = self.json['device']
|
||||
|
||||
hid = self.get_id_hw_dpp(device)
|
||||
self.chid = hashlib.sha3_256(hid.encode("utf-8")).hexdigest()
|
||||
return self.chid
|
||||
|
||||
def get_id_hw_dpp(self, d):
|
||||
manufacturer = d.get("manufacturer", '')
|
||||
model = d.get("model", '')
|
||||
chassis = d.get("chassis", '')
|
||||
serial_number = d.get("serialNumber", '')
|
||||
sku = d.get("sku", '')
|
||||
typ = d.get("type", '')
|
||||
version = d.get("version", '')
|
||||
|
||||
return f"{manufacturer}{model}{chassis}{serial_number}{sku}{typ}{version}"
|
||||
|
||||
def get_phid(self):
|
||||
if self.json.get("software") == "workbench-script":
|
||||
data = ParseSnapshot(self.json)
|
||||
self.device = data.device
|
||||
self.components = data.components
|
||||
else:
|
||||
self.device = self.json.get("device")
|
||||
self.components = self.json.get("components", [])
|
||||
|
||||
self.device.pop("actions", None)
|
||||
for c in self.components:
|
||||
c.pop("actions", None)
|
||||
|
||||
device = self.get_id_hw_dpp(self.device)
|
||||
components = sorted(self.components, key=lambda x: x.get("type"))
|
||||
doc = [("computer", device)]
|
||||
|
||||
for c in components:
|
||||
doc.append((c.get("type"), self.get_id_hw_dpp(c)))
|
||||
|
||||
return doc
|
||||
|
||||
def create_properties(self):
|
||||
property = SystemProperty.objects.filter(
|
||||
uuid=self.uuid,
|
||||
owner=self.user.institution,
|
||||
)
|
||||
|
||||
if prop:
|
||||
txt = "Warning: Snapshot %s already registered (annotation exists)"
|
||||
if property:
|
||||
txt = "Warning: Snapshot %s already registered (property exists)"
|
||||
logger.warning(txt, self.uuid)
|
||||
return
|
||||
|
||||
for k, v in self.build.algorithms.items():
|
||||
for k, v in self.algorithms.items():
|
||||
SystemProperty.objects.create(
|
||||
uuid=self.uuid,
|
||||
owner=self.user.institution,
|
||||
user=self.user,
|
||||
key=k,
|
||||
value=self.sign(v)
|
||||
value=v
|
||||
)
|
||||
|
||||
def sign(self, doc):
|
||||
return hashlib.sha3_256(doc.encode()).hexdigest()
|
||||
def get_hid(self, snapshot):
|
||||
try:
|
||||
self.inxi = self.json["data"]["inxi"]
|
||||
if isinstance(self.inxi, str):
|
||||
self.inxi = json.loads(self.inxi)
|
||||
except Exception:
|
||||
logger.error("No inxi in snapshot %s", self.uuid)
|
||||
return ""
|
||||
|
||||
machine = get_inxi_key(self.inxi, 'Machine')
|
||||
for m in machine:
|
||||
system = get_inxi(m, "System")
|
||||
if system:
|
||||
manufacturer = system
|
||||
model = get_inxi(m, "product")
|
||||
serial_number = get_inxi(m, "serial")
|
||||
chassis = get_inxi(m, "Type")
|
||||
else:
|
||||
sku = get_inxi(m, "part-nu")
|
||||
|
||||
mac = get_mac(self.inxi) or ""
|
||||
if not mac:
|
||||
txt = "Could not retrieve MAC address in snapshot %s"
|
||||
logger.warning(txt, snapshot['uuid'])
|
||||
return f"{manufacturer}{model}{chassis}{serial_number}{sku}"
|
||||
|
||||
return f"{manufacturer}{model}{chassis}{serial_number}{sku}{mac}"
|
||||
|
||||
def get_signature(self, doc):
|
||||
return hashlib.sha3_256(json.dumps(doc).encode()).hexdigest()
|
||||
|
||||
def register_device_dlt(self):
|
||||
legacy_dpp = self.build.algorithms.get('ereuse22')
|
||||
chid = self.sign(legacy_dpp)
|
||||
phid = self.sign(json.dumps(self.build.get_doc()))
|
||||
chid = self.algorithms.get('legacy_dpp')
|
||||
phid = self.get_signature(self.get_phid())
|
||||
register_device_dlt(chid, phid, self.uuid, self.user)
|
||||
register_passport_dlt(chid, phid, self.uuid, self.user)
|
||||
|
|
|
@ -1,38 +1,407 @@
|
|||
import re
|
||||
import json
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from evidence import (
|
||||
legacy_parse_details,
|
||||
normal_parse_details,
|
||||
old_parse_details
|
||||
)
|
||||
from datetime import datetime
|
||||
from dmidecode import DMIParse
|
||||
|
||||
from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
def get_inxi_key(inxi, component):
|
||||
for n in inxi:
|
||||
for k, v in n.items():
|
||||
if component in k:
|
||||
return v
|
||||
|
||||
|
||||
def get_inxi(n, name):
|
||||
for k, v in n.items():
|
||||
if f"#{name}" in k:
|
||||
return v
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
class ParseSnapshot:
|
||||
def __init__(self, snapshot, default="n/a"):
|
||||
if snapshot.get("credentialSubject"):
|
||||
self.build = normal_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
elif snapshot.get("software") != "workbench-script":
|
||||
self.build = old_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
elif snapshot.get("data",{}).get("lshw"):
|
||||
self.build = legacy_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
else:
|
||||
self.build = normal_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
self.default = default
|
||||
self.dmidecode_raw = snapshot.get("data", {}).get("dmidecode", "{}")
|
||||
self.smart_raw = snapshot.get("data", {}).get("smartctl", [])
|
||||
self.inxi_raw = snapshot.get("data", {}).get("inxi", "") or ""
|
||||
for ev in snapshot.get("evidence", []):
|
||||
if "dmidecode" == ev.get("operation"):
|
||||
self.dmidecode_raw = ev["output"]
|
||||
if "inxi" == ev.get("operation"):
|
||||
self.inxi_raw = ev["output"]
|
||||
if "smartctl" == ev.get("operation"):
|
||||
self.smart_raw = ev["output"]
|
||||
data = snapshot
|
||||
if snapshot.get("credentialSubject"):
|
||||
data = snapshot["credentialSubject"]
|
||||
|
||||
self.default = default
|
||||
self.device = self.build.snapshot_json.get("device")
|
||||
self.components = self.build.snapshot_json.get("components")
|
||||
self.device = {"actions": []}
|
||||
self.components = []
|
||||
|
||||
self.dmi = DMIParse(self.dmidecode_raw)
|
||||
self.smart = self.loads(self.smart_raw)
|
||||
self.inxi = self.loads(self.inxi_raw)
|
||||
|
||||
self.set_computer()
|
||||
self.set_components()
|
||||
self.snapshot_json = {
|
||||
"type": "Snapshot",
|
||||
"device": self.device,
|
||||
"software": data["software"],
|
||||
"components": self.components,
|
||||
"uuid": data['uuid'],
|
||||
"endTime": data["timestamp"],
|
||||
"elapsed": 1,
|
||||
}
|
||||
|
||||
def set_computer(self):
|
||||
machine = get_inxi_key(self.inxi, 'Machine') or []
|
||||
for m in machine:
|
||||
system = get_inxi(m, "System")
|
||||
if system:
|
||||
self.device['manufacturer'] = system
|
||||
self.device['model'] = get_inxi(m, "product")
|
||||
self.device['serialNumber'] = get_inxi(m, "serial")
|
||||
self.device['type'] = get_inxi(m, "Type")
|
||||
self.device['chassis'] = self.device['type']
|
||||
self.device['version'] = get_inxi(m, "v")
|
||||
else:
|
||||
self.device['system_uuid'] = get_inxi(m, "uuid")
|
||||
self.device['sku'] = get_inxi(m, "part-nu")
|
||||
|
||||
def set_components(self):
|
||||
self.get_mother_board()
|
||||
self.get_cpu()
|
||||
self.get_ram()
|
||||
self.get_graphic()
|
||||
self.get_display()
|
||||
self.get_networks()
|
||||
self.get_sound_card()
|
||||
self.get_data_storage()
|
||||
self.get_battery()
|
||||
|
||||
def get_mother_board(self):
|
||||
machine = get_inxi_key(self.inxi, 'Machine') or []
|
||||
mb = {"type": "Motherboard",}
|
||||
for m in machine:
|
||||
bios_date = get_inxi(m, "date")
|
||||
if not bios_date:
|
||||
continue
|
||||
mb["manufacturer"] = get_inxi(m, "Mobo")
|
||||
mb["model"] = get_inxi(m, "model")
|
||||
mb["serialNumber"] = get_inxi(m, "serial")
|
||||
mb["version"] = get_inxi(m, "v")
|
||||
mb["biosDate"] = bios_date
|
||||
mb["biosVersion"] = self.get_bios_version()
|
||||
mb["firewire"]: self.get_firmware_num()
|
||||
mb["pcmcia"]: self.get_pcmcia_num()
|
||||
mb["serial"]: self.get_serial_num()
|
||||
mb["usb"]: self.get_usb_num()
|
||||
|
||||
self.get_ram_slots(mb)
|
||||
|
||||
self.components.append(mb)
|
||||
|
||||
def get_ram_slots(self, mb):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
for m in memory:
|
||||
slots = get_inxi(m, "slots")
|
||||
if not slots:
|
||||
continue
|
||||
mb["slots"] = slots
|
||||
mb["ramSlots"] = get_inxi(m, "modules")
|
||||
mb["ramMaxSize"] = get_inxi(m, "capacity")
|
||||
|
||||
|
||||
def get_cpu(self):
|
||||
cpu = get_inxi_key(self.inxi, 'CPU') or []
|
||||
cp = {"type": "Processor"}
|
||||
vulnerabilities = []
|
||||
for c in cpu:
|
||||
base = get_inxi(c, "model")
|
||||
if base:
|
||||
cp["model"] = get_inxi(c, "model")
|
||||
cp["arch"] = get_inxi(c, "arch")
|
||||
cp["bits"] = get_inxi(c, "bits")
|
||||
cp["gen"] = get_inxi(c, "gen")
|
||||
cp["family"] = get_inxi(c, "family")
|
||||
cp["date"] = get_inxi(c, "built")
|
||||
continue
|
||||
des = get_inxi(c, "L1")
|
||||
if des:
|
||||
cp["L1"] = des
|
||||
cp["L2"] = get_inxi(c, "L2")
|
||||
cp["L3"] = get_inxi(c, "L3")
|
||||
cp["cpus"] = get_inxi(c, "cpus")
|
||||
cp["cores"] = get_inxi(c, "cores")
|
||||
cp["threads"] = get_inxi(c, "threads")
|
||||
continue
|
||||
bogo = get_inxi(c, "bogomips")
|
||||
if bogo:
|
||||
cp["bogomips"] = bogo
|
||||
cp["base/boost"] = get_inxi(c, "base/boost")
|
||||
cp["min/max"] = get_inxi(c, "min/max")
|
||||
cp["ext-clock"] = get_inxi(c, "ext-clock")
|
||||
cp["volts"] = get_inxi(c, "volts")
|
||||
continue
|
||||
ctype = get_inxi(c, "Type")
|
||||
if ctype:
|
||||
v = {"Type": ctype}
|
||||
status = get_inxi(c, "status")
|
||||
if status:
|
||||
v["status"] = status
|
||||
mitigation = get_inxi(c, "mitigation")
|
||||
if mitigation:
|
||||
v["mitigation"] = mitigation
|
||||
vulnerabilities.append(v)
|
||||
|
||||
self.components.append(cp)
|
||||
|
||||
|
||||
def get_ram(self):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
mem = {"type": "RamModule"}
|
||||
|
||||
for m in memory:
|
||||
base = get_inxi(m, "System RAM")
|
||||
if base:
|
||||
mem["size"] = get_inxi(m, "total")
|
||||
slot = get_inxi(m, "manufacturer")
|
||||
if slot:
|
||||
mem["manufacturer"] = slot
|
||||
mem["model"] = get_inxi(m, "part-no")
|
||||
mem["serialNumber"] = get_inxi(m, "serial")
|
||||
mem["speed"] = get_inxi(m, "speed")
|
||||
mem["bits"] = get_inxi(m, "data")
|
||||
mem["interface"] = get_inxi(m, "type")
|
||||
module = get_inxi(m, "modules")
|
||||
if module:
|
||||
mem["modules"] = module
|
||||
|
||||
self.components.append(mem)
|
||||
|
||||
def get_graphic(self):
|
||||
graphics = get_inxi_key(self.inxi, 'Graphics') or []
|
||||
|
||||
for c in graphics:
|
||||
if not get_inxi(c, "Device") or not get_inxi(c, "vendor"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "GraphicCard",
|
||||
"memory": self.get_memory_video(c),
|
||||
"manufacturer": get_inxi(c, "vendor"),
|
||||
"model": get_inxi(c, "Device"),
|
||||
"arch": get_inxi(c, "arch"),
|
||||
"serialNumber": get_inxi(c, "serial"),
|
||||
"integrated": True if get_inxi(c, "port") else False
|
||||
}
|
||||
)
|
||||
|
||||
def get_battery(self):
|
||||
bats = get_inxi_key(self.inxi, 'Battery') or []
|
||||
for b in bats:
|
||||
self.components.append(
|
||||
{
|
||||
"type": "Battery",
|
||||
"model": get_inxi(b, "model"),
|
||||
"serialNumber": get_inxi(b, "serial"),
|
||||
"condition": get_inxi(b, "condition"),
|
||||
"cycles": get_inxi(b, "cycles"),
|
||||
"volts": get_inxi(b, "volts")
|
||||
}
|
||||
)
|
||||
|
||||
def get_memory_video(self, c):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
|
||||
for m in memory:
|
||||
igpu = get_inxi(m, "igpu")
|
||||
agpu = get_inxi(m, "agpu")
|
||||
ngpu = get_inxi(m, "ngpu")
|
||||
gpu = get_inxi(m, "gpu")
|
||||
if igpu or agpu or gpu or ngpu:
|
||||
return igpu or agpu or gpu or ngpu
|
||||
|
||||
return self.default
|
||||
|
||||
def get_data_storage(self):
|
||||
hdds= get_inxi_key(self.inxi, 'Drives') or []
|
||||
for d in hdds:
|
||||
usb = get_inxi(d, "type")
|
||||
if usb == "USB":
|
||||
continue
|
||||
|
||||
serial = get_inxi(d, "serial")
|
||||
if serial:
|
||||
hd = {
|
||||
"type": "Storage",
|
||||
"manufacturer": get_inxi(d, "vendor"),
|
||||
"model": get_inxi(d, "model"),
|
||||
"serialNumber": get_inxi(d, "serial"),
|
||||
"size": get_inxi(d, "size"),
|
||||
"speed": get_inxi(d, "speed"),
|
||||
"interface": get_inxi(d, "tech"),
|
||||
"firmware": get_inxi(d, "fw-rev")
|
||||
}
|
||||
rpm = get_inxi(d, "rpm")
|
||||
if rpm:
|
||||
hd["rpm"] = rpm
|
||||
|
||||
family = get_inxi(d, "family")
|
||||
if family:
|
||||
hd["family"] = family
|
||||
|
||||
sata = get_inxi(d, "sata")
|
||||
if sata:
|
||||
hd["sata"] = sata
|
||||
|
||||
continue
|
||||
|
||||
|
||||
cycles = get_inxi(d, "cycles")
|
||||
if cycles:
|
||||
hd['cycles'] = cycles
|
||||
hd["health"] = get_inxi(d, "health")
|
||||
hd["time of used"] = get_inxi(d, "on")
|
||||
hd["read used"] = get_inxi(d, "read-units")
|
||||
hd["written used"] = get_inxi(d, "written-units")
|
||||
|
||||
self.components.append(hd)
|
||||
continue
|
||||
|
||||
hd = {}
|
||||
|
||||
def sanitize(self, action):
|
||||
return []
|
||||
|
||||
def get_networks(self):
|
||||
nets = get_inxi_key(self.inxi, "Network") or []
|
||||
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
|
||||
|
||||
interface = ''
|
||||
for k in n.keys():
|
||||
if "port" in k:
|
||||
interface = "Integrated"
|
||||
if "pcie" in k:
|
||||
interface = "PciExpress"
|
||||
if get_inxi(n, "type") == "USB":
|
||||
interface = "USB"
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "NetworkAdapter",
|
||||
"model": model,
|
||||
"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 []
|
||||
|
||||
for c in audio:
|
||||
model = get_inxi(c, "Device")
|
||||
if not model:
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "SoundCard",
|
||||
"model": model,
|
||||
"manufacturer": get_inxi(c, 'vendor'),
|
||||
"serialNumber": get_inxi(c, 'serial'),
|
||||
}
|
||||
)
|
||||
|
||||
def get_display(self):
|
||||
graphics = get_inxi_key(self.inxi, "Graphics") or []
|
||||
for c in graphics:
|
||||
if not get_inxi(c, "Monitor"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "Display",
|
||||
"model": get_inxi(c, "model"),
|
||||
"manufacturer": get_inxi(c, "vendor"),
|
||||
"serialNumber": get_inxi(c, "serial"),
|
||||
'size': get_inxi(c, "size"),
|
||||
'diagonal': get_inxi(c, "diag"),
|
||||
'resolution': get_inxi(c, "res"),
|
||||
"date": get_inxi(c, "built"),
|
||||
'ratio': get_inxi(c, "ratio"),
|
||||
}
|
||||
)
|
||||
|
||||
def get_usb_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "USB" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_serial_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "SERIAL" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_firmware_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "FIRMWARE" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_pcmcia_num(self):
|
||||
return len(
|
||||
[
|
||||
u
|
||||
for u in self.dmi.get("Port Connector")
|
||||
if "PCMCIA" in u.get("Port Type", "").upper()
|
||||
]
|
||||
)
|
||||
|
||||
def get_bios_version(self):
|
||||
return self.dmi.get("BIOS")[0].get("BIOS Revision", '1')
|
||||
|
||||
def loads(self, x):
|
||||
if isinstance(x, str):
|
||||
try:
|
||||
return json.loads(x)
|
||||
except Exception as ss:
|
||||
logger.warning("%s", ss)
|
||||
return {}
|
||||
return x
|
||||
|
||||
def errors(self, txt=None):
|
||||
if not txt:
|
||||
return self._errors
|
||||
|
||||
logger.error(txt)
|
||||
self._errors.append("%s", txt)
|
||||
|
|
43
evidence/serializers.py
Normal file
43
evidence/serializers.py
Normal 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)
|
|
@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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")
|
||||
]
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
48
lot/templates/documents.html
Normal file
48
lot/templates/documents.html
Normal 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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"),
|
||||
|
|
146
lot/views.py
146
lot/views.py
|
@ -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
0
tag/__init__.py
Normal file
3
tag/admin.py
Normal file
3
tag/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
tag/apps.py
Normal file
6
tag/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TagConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "tag"
|
0
tag/migrations/__init__.py
Normal file
0
tag/migrations/__init__.py
Normal file
3
tag/models.py
Normal file
3
tag/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
tag/tests.py
Normal file
3
tag/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
tag/views.py
Normal file
3
tag/views.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
8
tests/end-to-end/.gitignore
vendored
8
tests/end-to-end/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
tests-examples
|
||||
example.spec.ts
|
97
tests/end-to-end/package-lock.json
generated
97
tests/end-to-end/package-lock.json
generated
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
// },
|
||||
});
|
|
@ -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; -*-
|
|
@ -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
|
||||
//});
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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):
|
||||
|
|
|
@ -9,28 +9,27 @@ STR_EXTEND_SIZE = 256
|
|||
|
||||
|
||||
# Algorithms for build hids
|
||||
EREUSE24 = [
|
||||
HID_ALGO1 = [
|
||||
"manufacturer",
|
||||
"model",
|
||||
"chassis",
|
||||
"serial_number",
|
||||
"mac"
|
||||
"serialNumber",
|
||||
"sku"
|
||||
]
|
||||
|
||||
# EREUSE22 is used for build the chid of DPP
|
||||
EREUSE22 = [
|
||||
LEGACY_DPP = [
|
||||
"manufacturer",
|
||||
"model",
|
||||
"chassis",
|
||||
"serial_number",
|
||||
"serialNumber",
|
||||
"sku",
|
||||
"type",
|
||||
"version"
|
||||
]
|
||||
|
||||
ALGOS = {
|
||||
"ereuse24": EREUSE24,
|
||||
"ereuse22": EREUSE22
|
||||
"hidalgo1": HID_ALGO1,
|
||||
"legacy_dpp": LEGACY_DPP
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue