WIP: Changed annotation syntax to properties and created mutable user_properties #31
|
@ -4,6 +4,7 @@ DEMO=true
|
||||||
DEBUG=true
|
DEBUG=true
|
||||||
ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1,
|
ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1,
|
||||||
|
|
||||||
|
DEVICE_LOG_PATH=/tmp
|
||||||
STATIC_ROOT=/tmp/static/
|
STATIC_ROOT=/tmp/static/
|
||||||
MEDIA_ROOT=/tmp/media/
|
MEDIA_ROOT=/tmp/media/
|
||||||
EMAIL_HOST="mail.example.org"
|
EMAIL_HOST="mail.example.org"
|
||||||
|
|
|
@ -7,7 +7,7 @@ app_name = 'api'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('v1/snapshot/', views.NewSnapshotView.as_view(), name='new_snapshot'),
|
path('v1/snapshot/', views.NewSnapshotView.as_view(), name='new_snapshot'),
|
||||||
path('v1/annotation/<str:pk>/', views.AddAnnotationView.as_view(), name='new_annotation'),
|
path('v1/property/<str:pk>/', views.AddPropertyView.as_view(), name='new_property'),
|
||||||
path('v1/device/<str:pk>/', views.DetailsDeviceView.as_view(), name='device'),
|
path('v1/device/<str:pk>/', views.DetailsDeviceView.as_view(), name='device'),
|
||||||
path('v1/tokens/', views.TokenView.as_view(), name='tokens'),
|
path('v1/tokens/', views.TokenView.as_view(), name='tokens'),
|
||||||
path('v1/tokens/new', views.TokenNewView.as_view(), name='new_token'),
|
path('v1/tokens/new', views.TokenNewView.as_view(), name='new_token'),
|
||||||
|
|
36
api/views.py
36
api/views.py
|
@ -21,7 +21,7 @@ from django.views.generic.edit import (
|
||||||
from utils.save_snapshots import move_json, save_in_disk
|
from utils.save_snapshots import move_json, save_in_disk
|
||||||
from django.views.generic.edit import View
|
from django.views.generic.edit import View
|
||||||
from dashboard.mixins import DashboardView
|
from dashboard.mixins import DashboardView
|
||||||
from evidence.models import Annotation
|
from evidence.models import SystemProperty, UserProperty
|
||||||
from evidence.parse_details import ParseSnapshot
|
from evidence.parse_details import ParseSnapshot
|
||||||
from evidence.parse import Build
|
from evidence.parse import Build
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
|
@ -90,11 +90,11 @@ class NewSnapshotView(ApiMixing):
|
||||||
logger.error("%s", txt)
|
logger.error("%s", txt)
|
||||||
return JsonResponse({'status': txt}, status=500)
|
return JsonResponse({'status': txt}, status=500)
|
||||||
|
|
||||||
exist_annotation = Annotation.objects.filter(
|
exist_property = SystemProperty.objects.filter(
|
||||||
uuid=data['uuid']
|
uuid=data['uuid']
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if exist_annotation:
|
if exist_property:
|
||||||
txt = "error: the snapshot {} exist".format(data['uuid'])
|
txt = "error: the snapshot {} exist".format(data['uuid'])
|
||||||
logger.warning("%s", txt)
|
logger.warning("%s", txt)
|
||||||
return JsonResponse({'status': txt}, status=500)
|
return JsonResponse({'status': txt}, status=500)
|
||||||
|
@ -111,25 +111,24 @@ class NewSnapshotView(ApiMixing):
|
||||||
text = "fail: It is not possible to parse snapshot"
|
text = "fail: It is not possible to parse snapshot"
|
||||||
return JsonResponse({'status': text}, status=500)
|
return JsonResponse({'status': text}, status=500)
|
||||||
|
|
||||||
annotation = Annotation.objects.filter(
|
property = SystemProperty.objects.filter(
|
||||||
uuid=data['uuid'],
|
uuid=data['uuid'],
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
# TODO this is hardcoded, it should select the user preferred algorithm
|
# TODO this is hardcoded, it should select the user preferred algorithm
|
||||||
key="hidalgo1",
|
key="hidalgo1",
|
||||||
owner=self.tk.owner.institution
|
owner=self.tk.owner.institution
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
|
||||||
if not annotation:
|
if not property:
|
||||||
logger.error("Error: No annotation for uuid: %s", data["uuid"])
|
logger.error("Error: No property for uuid: %s", data["uuid"])
|
||||||
return JsonResponse({'status': 'fail'}, status=500)
|
return JsonResponse({'status': 'fail'}, status=500)
|
||||||
|
|
||||||
url_args = reverse_lazy("device:details", args=(annotation.value,))
|
url_args = reverse_lazy("device:details", args=(property.value,))
|
||||||
url = request.build_absolute_uri(url_args)
|
url = request.build_absolute_uri(url_args)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"dhid": annotation.value[:6].upper(),
|
"dhid": property.value[:6].upper(),
|
||||||
"url": url,
|
"url": url,
|
||||||
# TODO replace with public_url when available
|
# TODO replace with public_url when available
|
||||||
"public_url": url
|
"public_url": url
|
||||||
|
@ -255,22 +254,21 @@ class DetailsDeviceView(ApiMixing):
|
||||||
"components": snapshot.get("components"),
|
"components": snapshot.get("components"),
|
||||||
})
|
})
|
||||||
|
|
||||||
uuids = Annotation.objects.filter(
|
uuids = SystemProperty.objects.filter(
|
||||||
owner=self.tk.owner.institution,
|
owner=self.tk.owner.institution,
|
||||||
value=self.pk
|
value=self.pk
|
||||||
).values("uuid")
|
).values("uuid")
|
||||||
|
|
||||||
annotations = Annotation.objects.filter(
|
properties = UserProperty.objects.filter(
|
||||||
uuid__in=uuids,
|
uuid__in=uuids,
|
||||||
owner=self.tk.owner.institution,
|
owner=self.tk.owner.institution,
|
||||||
type = Annotation.Type.USER
|
|
||||||
).values_list("key", "value")
|
).values_list("key", "value")
|
||||||
|
|
||||||
data.update({"annotations": list(annotations)})
|
data.update({"properties": list(properties)})
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class AddAnnotationView(ApiMixing):
|
class AddPropertyView(ApiMixing):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
response = self.auth()
|
response = self.auth()
|
||||||
|
@ -279,13 +277,12 @@ class AddAnnotationView(ApiMixing):
|
||||||
|
|
||||||
self.pk = kwargs['pk']
|
self.pk = kwargs['pk']
|
||||||
institution = self.tk.owner.institution
|
institution = self.tk.owner.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.property = SystemProperty.objects.filter(
|
||||||
owner=institution,
|
owner=institution,
|
||||||
value=self.pk,
|
value=self.pk,
|
||||||
type=Annotation.Type.SYSTEM
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not self.annotation:
|
if not self.property:
|
||||||
return JsonResponse({}, status=404)
|
return JsonResponse({}, status=404)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -296,10 +293,9 @@ class AddAnnotationView(ApiMixing):
|
||||||
logger.error("Invalid Snapshot of user %s", self.tk.owner)
|
logger.error("Invalid Snapshot of user %s", self.tk.owner)
|
||||||
return JsonResponse({'error': 'Invalid JSON'}, status=500)
|
return JsonResponse({'error': 'Invalid JSON'}, status=500)
|
||||||
|
|
||||||
Annotation.objects.create(
|
UserProperty.objects.create(
|
||||||
uuid=self.annotation.uuid,
|
uuid=self.property.uuid,
|
||||||
owner=self.tk.owner.institution,
|
owner=self.tk.owner.institution,
|
||||||
type = Annotation.Type.USER,
|
|
||||||
key = key,
|
key = key,
|
||||||
value = value
|
value = value
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.core.exceptions import PermissionDenied
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from evidence.models import Annotation
|
from evidence.models import SystemProperty
|
||||||
from lot.models import LotTag
|
from lot.models import LotTag
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class DashboardView(LoginRequiredMixin):
|
||||||
dev_ids = self.request.session.pop("devices", [])
|
dev_ids = self.request.session.pop("devices", [])
|
||||||
|
|
||||||
self._devices = []
|
self._devices = []
|
||||||
for x in Annotation.objects.filter(value__in=dev_ids).filter(
|
for x in SystemProperty.objects.filter(value__in=dev_ids).filter(
|
||||||
owner=self.request.user.institution
|
owner=self.request.user.institution
|
||||||
).distinct():
|
).distinct():
|
||||||
self._devices.append(Device(id=x.value))
|
self._devices.append(Device(id=x.value))
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
{% trans 'Exports' %}
|
{% trans 'Exports' %}
|
||||||
</a>
|
</a>
|
||||||
{% if lot %}
|
{% if lot %}
|
||||||
<a href="{% url 'lot:annotations' object.id %}" type="button" class="btn btn-green-admin">
|
<a href="{% url 'lot:properties' object.id %}" type="button" class="btn btn-green-admin">
|
||||||
<i class="bi bi-tag"></i>
|
<i class="bi bi-tag"></i>
|
||||||
{% trans 'Annotations' %}
|
{% trans 'properties' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.shortcuts import Http404
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dashboard.mixins import InventaryMixin, DetailsMixin
|
from dashboard.mixins import InventaryMixin, DetailsMixin
|
||||||
from evidence.models import Annotation
|
from evidence.models import SystemProperty
|
||||||
from evidence.xapian import search
|
from evidence.xapian import search
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from lot.models import Lot
|
from lot.models import Lot
|
||||||
|
@ -74,7 +74,7 @@ class SearchView(InventaryMixin):
|
||||||
|
|
||||||
for x in matches:
|
for x in matches:
|
||||||
# devices.append(self.get_annotations(x))
|
# devices.append(self.get_annotations(x))
|
||||||
dev = self.get_annotations(x)
|
dev = self.get_properties(x)
|
||||||
if dev.id not in dev_id:
|
if dev.id not in dev_id:
|
||||||
devices.append(dev)
|
devices.append(dev)
|
||||||
dev_id.append(dev.id)
|
dev_id.append(dev.id)
|
||||||
|
@ -83,10 +83,10 @@ class SearchView(InventaryMixin):
|
||||||
# TODO fix of pagination, the count is not correct
|
# TODO fix of pagination, the count is not correct
|
||||||
return devices, count
|
return devices, count
|
||||||
|
|
||||||
def get_annotations(self, xp):
|
def get_properties(self, xp):
|
||||||
snap = xp.document.get_data()
|
snap = xp.document.get_data()
|
||||||
uuid = json.loads(snap).get('uuid')
|
uuid = json.loads(snap).get('uuid')
|
||||||
return Device.get_annotation_from_uuid(uuid, self.request.user.institution)
|
return Device.get_properties_from_uuid(uuid, self.request.user.institution)
|
||||||
|
|
||||||
def search_hids(self, query, offset, limit):
|
def search_hids(self, query, offset, limit):
|
||||||
qry = Q()
|
qry = Q()
|
||||||
|
@ -95,8 +95,7 @@ class SearchView(InventaryMixin):
|
||||||
if i:
|
if i:
|
||||||
qry |= Q(value__startswith=i)
|
qry |= Q(value__startswith=i)
|
||||||
|
|
||||||
chids = Annotation.objects.filter(
|
chids = SystemProperty.objects.filter(
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
owner=self.request.user.institution
|
owner=self.request.user.institution
|
||||||
).filter(
|
).filter(
|
||||||
qry
|
qry
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from utils.device import create_annotation, create_doc, create_index
|
from utils.device import create_property, create_doc, create_index
|
||||||
from utils.save_snapshots import move_json, save_in_disk
|
from utils.save_snapshots import move_json, save_in_disk
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ class BaseDeviceFormSet(forms.BaseFormSet):
|
||||||
|
|
||||||
path_name = save_in_disk(doc, self.user.institution.name, place="placeholder")
|
path_name = save_in_disk(doc, self.user.institution.name, place="placeholder")
|
||||||
create_index(doc, self.user)
|
create_index(doc, self.user)
|
||||||
create_annotation(doc, user, commit=commit)
|
create_property(doc, user, commit=commit)
|
||||||
move_json(path_name, self.user.institution.name, place="placeholder")
|
move_json(path_name, self.user.institution.name, place="placeholder")
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
|
|
||||||
from utils.constants import ALGOS
|
from utils.constants import ALGOS
|
||||||
from evidence.models import Annotation, Evidence
|
from evidence.models import SystemProperty, UserProperty, Evidence
|
||||||
from lot.models import DeviceLot
|
from lot.models import DeviceLot
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class Device:
|
||||||
self.shortid = self.pk[:6].upper()
|
self.shortid = self.pk[:6].upper()
|
||||||
self.algorithm = None
|
self.algorithm = None
|
||||||
self.owner = None
|
self.owner = None
|
||||||
self.annotations = []
|
self.properties = []
|
||||||
self.hids = []
|
self.hids = []
|
||||||
self.uuids = []
|
self.uuids = []
|
||||||
self.evidences = []
|
self.evidences = []
|
||||||
|
@ -38,61 +38,59 @@ class Device:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
|
|
||||||
def initial(self):
|
def initial(self):
|
||||||
self.get_annotations()
|
self.get_properties()
|
||||||
self.get_uuids()
|
self.get_uuids()
|
||||||
self.get_hids()
|
self.get_hids()
|
||||||
self.get_evidences()
|
self.get_evidences()
|
||||||
self.get_lots()
|
self.get_lots()
|
||||||
|
|
||||||
def get_annotations(self):
|
def get_properties(self):
|
||||||
if self.annotations:
|
if self.properties:
|
||||||
return self.annotations
|
return self.properties
|
||||||
|
|
||||||
self.annotations = Annotation.objects.filter(
|
self.properties = SystemProperty.objects.filter(
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
value=self.id
|
value=self.id
|
||||||
).order_by("-created")
|
).order_by("-created")
|
||||||
|
|
||||||
if self.annotations.count():
|
if self.properties.count():
|
||||||
self.algorithm = self.annotations[0].key
|
self.algorithm = self.properties[0].key
|
||||||
self.owner = self.annotations[0].owner
|
self.owner = self.properties[0].owner
|
||||||
|
|
||||||
return self.annotations
|
return self.properties
|
||||||
|
|
||||||
def get_user_annotations(self):
|
def get_user_properties(self):
|
||||||
if not self.uuids:
|
if not self.uuids:
|
||||||
self.get_uuids()
|
self.get_uuids()
|
||||||
|
|
||||||
annotations = Annotation.objects.filter(
|
user_properties = UserProperty.objects.filter(
|
||||||
uuid__in=self.uuids,
|
uuid__in=self.uuids,
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
type=Annotation.Type.USER
|
type=UserProperty.Type.USER,
|
||||||
)
|
)
|
||||||
return annotations
|
return user_properties
|
||||||
|
|
||||||
def get_user_documents(self):
|
def get_user_documents(self):
|
||||||
if not self.uuids:
|
if not self.uuids:
|
||||||
self.get_uuids()
|
self.get_uuids()
|
||||||
|
|
||||||
annotations = Annotation.objects.filter(
|
properties = UserProperty.objects.filter(
|
||||||
uuid__in=self.uuids,
|
uuid__in=self.uuids,
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
type=Annotation.Type.DOCUMENT
|
type=UserProperty.Type.DOCUMENT
|
||||||
)
|
)
|
||||||
return annotations
|
return properties
|
||||||
|
|
||||||
def get_uuids(self):
|
def get_uuids(self):
|
||||||
for a in self.get_annotations():
|
for a in self.get_properties():
|
||||||
if a.uuid not in self.uuids:
|
if a.uuid not in self.uuids:
|
||||||
self.uuids.append(a.uuid)
|
self.uuids.append(a.uuid)
|
||||||
|
|
||||||
def get_hids(self):
|
def get_hids(self):
|
||||||
annotations = self.get_annotations()
|
properties = self.get_properties()
|
||||||
|
|
||||||
algos = list(ALGOS.keys())
|
algos = list(ALGOS.keys())
|
||||||
algos.append('CUSTOM_ID')
|
algos.append('CUSTOM_ID')
|
||||||
self.hids = list(set(annotations.filter(
|
self.hids = list(set(properties.filter(
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
key__in=algos,
|
key__in=algos,
|
||||||
).values_list("value", flat=True)))
|
).values_list("value", flat=True)))
|
||||||
|
|
||||||
|
@ -103,11 +101,12 @@ class Device:
|
||||||
self.evidences = [Evidence(u) for u in self.uuids]
|
self.evidences = [Evidence(u) for u in self.uuids]
|
||||||
|
|
||||||
def get_last_evidence(self):
|
def get_last_evidence(self):
|
||||||
annotations = self.get_annotations()
|
properties = self.get_properties()
|
||||||
if not annotations.count():
|
if not properties.count():
|
||||||
return
|
return
|
||||||
annotation = annotations.first()
|
property = properties.first()
|
||||||
self.last_evidence = Evidence(annotation.uuid)
|
|
||||||
|
self.last_evidence = Evidence(property.uuid)
|
||||||
|
|
||||||
def is_eraseserver(self):
|
def is_eraseserver(self):
|
||||||
if not self.uuids:
|
if not self.uuids:
|
||||||
|
@ -115,13 +114,13 @@ class Device:
|
||||||
if not self.uuids:
|
if not self.uuids:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
annotation = Annotation.objects.filter(
|
property = UserProperty.objects.filter(
|
||||||
uuid__in=self.uuids,
|
uuid__in=self.uuids,
|
||||||
owner=self.owner,
|
owner=self.owner,
|
||||||
type=Annotation.Type.ERASE_SERVER
|
type=UserProperty.Type.ERASE_SERVER
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if annotation:
|
if property:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -136,7 +135,7 @@ class Device:
|
||||||
def get_unassigned(cls, institution, offset=0, limit=None):
|
def get_unassigned(cls, institution, offset=0, limit=None):
|
||||||
|
|
||||||
sql = """
|
sql = """
|
||||||
WITH RankedAnnotations AS (
|
WITH RankedProperties AS (
|
||||||
SELECT
|
SELECT
|
||||||
t1.value,
|
t1.value,
|
||||||
t1.key,
|
t1.key,
|
||||||
|
@ -150,33 +149,31 @@ class Device:
|
||||||
END,
|
END,
|
||||||
t1.created DESC
|
t1.created DESC
|
||||||
) AS row_num
|
) AS row_num
|
||||||
FROM evidence_annotation AS t1
|
FROM evidence_systemproperty AS t1
|
||||||
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
||||||
WHERE t2.device_id IS NULL
|
WHERE t2.device_id IS NULL
|
||||||
AND t1.owner_id = {institution}
|
AND t1.owner_id = {institution}
|
||||||
AND t1.type = {type}
|
|
||||||
)
|
)
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
value
|
value
|
||||||
FROM
|
FROM
|
||||||
RankedAnnotations
|
RankedProperties
|
||||||
WHERE
|
WHERE
|
||||||
row_num = 1
|
row_num = 1
|
||||||
""".format(
|
""".format(
|
||||||
institution=institution.id,
|
institution=institution.id,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
)
|
)
|
||||||
if limit:
|
if limit:
|
||||||
sql += " limit {} offset {}".format(int(limit), int(offset))
|
sql += " limit {} offset {}".format(int(limit), int(offset))
|
||||||
|
|
||||||
sql += ";"
|
sql += ";"
|
||||||
|
|
||||||
annotations = []
|
properties = []
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
annotations = cursor.fetchall()
|
properties = cursor.fetchall()
|
||||||
|
|
||||||
devices = [cls(id=x[0]) for x in annotations]
|
devices = [cls(id=x[0]) for x in properties]
|
||||||
count = cls.get_unassigned_count(institution)
|
count = cls.get_unassigned_count(institution)
|
||||||
return devices, count
|
return devices, count
|
||||||
|
|
||||||
|
@ -184,7 +181,7 @@ class Device:
|
||||||
def get_unassigned_count(cls, institution):
|
def get_unassigned_count(cls, institution):
|
||||||
|
|
||||||
sql = """
|
sql = """
|
||||||
WITH RankedAnnotations AS (
|
WITH RankedProperties AS (
|
||||||
SELECT
|
SELECT
|
||||||
t1.value,
|
t1.value,
|
||||||
t1.key,
|
t1.key,
|
||||||
|
@ -198,30 +195,28 @@ class Device:
|
||||||
END,
|
END,
|
||||||
t1.created DESC
|
t1.created DESC
|
||||||
) AS row_num
|
) AS row_num
|
||||||
FROM evidence_annotation AS t1
|
FROM evidence_systemproperty AS t1
|
||||||
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
||||||
WHERE t2.device_id IS NULL
|
WHERE t2.device_id IS NULL
|
||||||
AND t1.owner_id = {institution}
|
AND t1.owner_id = {institution}
|
||||||
AND t1.type = {type}
|
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(DISTINCT value)
|
COUNT(DISTINCT value)
|
||||||
FROM
|
FROM
|
||||||
RankedAnnotations
|
RankedProperties
|
||||||
WHERE
|
WHERE
|
||||||
row_num = 1
|
row_num = 1
|
||||||
""".format(
|
""".format(
|
||||||
institution=institution.id,
|
institution=institution.id,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
)
|
)
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
return cursor.fetchall()[0][0]
|
return cursor.fetchall()[0][0]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_annotation_from_uuid(cls, uuid, institution):
|
def get_properties_from_uuid(cls, uuid, institution):
|
||||||
sql = """
|
sql = """
|
||||||
WITH RankedAnnotations AS (
|
WITH RankedProperties AS (
|
||||||
SELECT
|
SELECT
|
||||||
t1.value,
|
t1.value,
|
||||||
t1.key,
|
t1.key,
|
||||||
|
@ -235,31 +230,29 @@ class Device:
|
||||||
END,
|
END,
|
||||||
t1.created DESC
|
t1.created DESC
|
||||||
) AS row_num
|
) AS row_num
|
||||||
FROM evidence_annotation AS t1
|
FROM evidence_systemproperty AS t1
|
||||||
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
||||||
WHERE t2.device_id IS NULL
|
WHERE t2.device_id IS NULL
|
||||||
AND t1.owner_id = {institution}
|
AND t1.owner_id = {institution}
|
||||||
AND t1.type = {type}
|
|
||||||
AND t1.uuid = '{uuid}'
|
AND t1.uuid = '{uuid}'
|
||||||
)
|
)
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
value
|
value
|
||||||
FROM
|
FROM
|
||||||
RankedAnnotations
|
RankedProperties
|
||||||
WHERE
|
WHERE
|
||||||
row_num = 1;
|
row_num = 1;
|
||||||
""".format(
|
""".format(
|
||||||
uuid=uuid.replace("-", ""),
|
uuid=uuid.replace("-", ""),
|
||||||
institution=institution.id,
|
institution=institution.id,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
annotations = []
|
properties = []
|
||||||
with connection.cursor() as cursor:
|
with connection.cursor() as cursor:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
annotations = cursor.fetchall()
|
properties = cursor.fetchall()
|
||||||
|
|
||||||
return cls(id=annotations[0][0])
|
return cls(id=properties[0][0])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_websnapshot(self):
|
def is_websnapshot(self):
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<a href="#details" class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">{% trans 'General details' %}</a>
|
<a href="#details" class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">{% trans 'General details' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#annotations" class="nav-link" data-bs-toggle="tab" data-bs-target="#annotations">{% trans 'User annotations' %}</a>
|
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'User properties' %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">{% trans 'Documents' %}</a>
|
<a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">{% trans 'Documents' %}</a>
|
||||||
|
@ -100,15 +100,15 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="annotations">
|
<div class="tab-pane fade" id="user_properties">
|
||||||
<div class="btn-group mt-1 mb-3">
|
<div class="btn-group mt-1 mb-3">
|
||||||
<a href="{% url 'device:add_annotation' object.pk %}" class="btn btn-primary">
|
<a href="{% url 'device:add_user_property' object.pk %}" class="btn btn-primary">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
{% trans 'Add new annotation' %}
|
{% trans 'New user property' %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="card-title">{% trans 'Annotations' %}</h5>
|
<h5 class="card-title">{% trans 'User properties' %}</h5>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -122,23 +122,87 @@
|
||||||
{% trans 'Created on' %}
|
{% trans 'Created on' %}
|
||||||
</th>
|
</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for a in object.get_user_annotations %}
|
{% for a in object.get_user_properties %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ a.key }}</td>
|
<td>{{ a.key }}</td>
|
||||||
<td>{{ a.value }}</td>
|
<td>{{ a.value }}</td>
|
||||||
<td>{{ a.created }}</td>
|
<td>{{ a.created }}</td>
|
||||||
<td></td>
|
<td>
|
||||||
<td></td>
|
<div class="btn-group float-end">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#editModal{{ a.id }}">
|
||||||
|
<i class="bi bi-pencil"></i> {% trans 'Edit' %}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ a.id }}">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- pop up modal for delete confirmation -->
|
||||||
|
{% for a in object.get_user_properties %}
|
||||||
|
<div class="modal fade" id="deleteModal{{ a.id }}" tabindex="-1" aria-labelledby="deleteModalLabel{{ a.id }}" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="deleteModalLabel{{ a.id }}">{% trans "Confirm Deletion" %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p><strong>{% trans "Key:" %}</strong> {{ a.key }}</p>
|
||||||
|
<p><strong>{% trans "Value:" %}</strong> {{ a.value }}</p>
|
||||||
|
<p><strong>{% trans "Created on:" %}</strong> {{ a.created }}</p>
|
||||||
|
</div>
|
||||||
|
<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' a.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-danger">{% trans "Delete" %}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- popup modals for edit button -->
|
||||||
|
{% for a in object.get_user_properties %}
|
||||||
|
<div class="modal fade" id="editModal{{ a.id }}" tabindex="-1" aria-labelledby="editModalLabel{{ a.id }}" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="editModalLabel{{ a.id }}">{% trans "Edit User Property" %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<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" %}</label>
|
||||||
|
<input type="text" class="form-control" id="key" name="key" value="{{ a.key }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="value" class="form-label">{% trans "Value" %}</label>
|
||||||
|
<input type="text" class="form-control" id="value" name="value" value="{{ a.value }}">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-center">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||||
|
<button type="submit" class="btn btn-primary">{% trans "Save changes" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<div class="tab-pane fade" id="documents">
|
<div class="tab-pane fade" id="documents">
|
||||||
<div class="btn-group mt-1 mb-3">
|
<div class="btn-group mt-1 mb-3">
|
||||||
<a href="{% url 'device:add_document' object.pk %}" class="btn btn-primary">
|
<a href="{% url 'device:add_document' object.pk %}" class="btn btn-primary">
|
||||||
|
|
|
@ -7,7 +7,9 @@ urlpatterns = [
|
||||||
path("add/", views.NewDeviceView.as_view(), name="add"),
|
path("add/", views.NewDeviceView.as_view(), name="add"),
|
||||||
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
||||||
path("<str:pk>/", views.DetailsView.as_view(), name="details"),
|
path("<str:pk>/", views.DetailsView.as_view(), name="details"),
|
||||||
path("<str:pk>/annotation/add", views.AddAnnotationView.as_view(), name="add_annotation"),
|
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>/document/add", views.AddDocumentView.as_view(), name="add_document"),
|
||||||
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
|
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
|
||||||
|
|
||||||
|
|
113
device/views.py
113
device/views.py
|
@ -1,23 +1,28 @@
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.shortcuts import get_object_or_404, Http404
|
from django.contrib import messages
|
||||||
|
from django.shortcuts import get_object_or_404, redirect, Http404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic.edit import (
|
from django.views.generic.edit import (
|
||||||
CreateView,
|
CreateView,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
FormView,
|
FormView,
|
||||||
|
DeleteView,
|
||||||
)
|
)
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from dashboard.mixins import DashboardView, Http403
|
from dashboard.mixins import DashboardView, Http403
|
||||||
from evidence.models import Annotation
|
from evidence.models import UserProperty, SystemProperty
|
||||||
from lot.models import LotTag
|
from lot.models import LotTag
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from device.forms import DeviceFormSet
|
from device.forms import DeviceFormSet
|
||||||
|
|
||||||
|
|
||||||
|
device_logger = logging.getLogger('device_log')
|
||||||
|
|
||||||
class NewDeviceView(DashboardView, FormView):
|
class NewDeviceView(DashboardView, FormView):
|
||||||
template_name = "new_device.html"
|
template_name = "new_device.html"
|
||||||
title = _("New Device")
|
title = _("New Device")
|
||||||
|
@ -69,7 +74,7 @@ class EditDeviceView(DashboardView, UpdateView):
|
||||||
title = _("Update Device")
|
title = _("Update Device")
|
||||||
breadcrumb = "Device / Update Device"
|
breadcrumb = "Device / Update Device"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
model = Annotation
|
model = SystemProperty
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
|
@ -87,7 +92,7 @@ class DetailsView(DashboardView, TemplateView):
|
||||||
template_name = "details.html"
|
template_name = "details.html"
|
||||||
title = _("Device")
|
title = _("Device")
|
||||||
breadcrumb = "Device / Details"
|
breadcrumb = "Device / Details"
|
||||||
model = Annotation
|
model = SystemProperty
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.pk = kwargs['pk']
|
self.pk = kwargs['pk']
|
||||||
|
@ -167,65 +172,131 @@ class PublicDeviceWebView(TemplateView):
|
||||||
return JsonResponse(device_data)
|
return JsonResponse(device_data)
|
||||||
|
|
||||||
|
|
||||||
class AddAnnotationView(DashboardView, CreateView):
|
class AddUserPropertyView(DashboardView, CreateView):
|
||||||
template_name = "new_annotation.html"
|
template_name = "new_user_property.html"
|
||||||
title = _("New annotation")
|
title = _("New User Property")
|
||||||
breadcrumb = "Device / New annotation"
|
breadcrumb = "Device / New Property"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
model = Annotation
|
model = UserProperty
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
form.instance.uuid = self.annotation.uuid
|
form.instance.uuid = self.property.uuid
|
||||||
form.instance.type = Annotation.Type.USER
|
form.instance.type = UserProperty.Type.USER
|
||||||
|
|
||||||
|
messages.success(self.request, _("User property successfully added."))
|
||||||
|
|
||||||
|
device_logger.info(
|
||||||
|
f"Created user property (key='{form.instance.key}', value='{form.instance.value}') by user {self.request.user}, for evidence uuid: {self.property.uuid}."
|
||||||
|
)
|
||||||
|
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
institution = self.request.user.institution
|
institution = self.request.user.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.property = SystemProperty.objects.filter(
|
||||||
owner=institution,
|
owner=institution,
|
||||||
value=pk,
|
value=pk,
|
||||||
type=Annotation.Type.SYSTEM
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not self.annotation:
|
if not self.property:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
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):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
user_property = get_object_or_404(UserProperty, pk=pk, owner=self.request.user.institution)
|
||||||
|
|
||||||
|
if not user_property:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['instance'] = user_property
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
old_key= self.object.key
|
||||||
|
old_value = self.object.value
|
||||||
|
new_key = form.cleaned_data['key']
|
||||||
|
new_value = form.cleaned_data['value']
|
||||||
|
|
||||||
|
form.instance.owner = self.request.user.institution
|
||||||
|
form.instance.user = self.request.user
|
||||||
|
form.instance.type = UserProperty.Type.USER
|
||||||
|
response = super().form_valid(form)
|
||||||
|
|
||||||
|
messages.success(self.request, _("User property updated successfully."))
|
||||||
|
device_logger.info(
|
||||||
|
f"Updated property from (key='{old_key}', value='{old_value}') to (key='{new_key}', value='{new_value}') by user {self.request.user}."
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteUserPropertyView(DashboardView, DeleteView):
|
||||||
|
model = UserProperty
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
old_value = self.object.key
|
||||||
|
self.object.delete()
|
||||||
|
device_logger.info(f"Deleted property with key '{old_value}' by user {self.request.user}.")
|
||||||
|
messages.success(self.request, _("User property deleted successfully."))
|
||||||
|
|
||||||
|
# Redirect back to the original URL
|
||||||
|
return redirect(referer)
|
||||||
|
|
||||||
|
|
||||||
class AddDocumentView(DashboardView, CreateView):
|
class AddDocumentView(DashboardView, CreateView):
|
||||||
template_name = "new_annotation.html"
|
template_name = "new_user_property.html"
|
||||||
title = _("New Document")
|
title = _("New Document")
|
||||||
breadcrumb = "Device / New document"
|
breadcrumb = "Device / New document"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
model = Annotation
|
model = UserProperty
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
form.instance.uuid = self.annotation.uuid
|
form.instance.uuid = self.property.uuid
|
||||||
form.instance.type = Annotation.Type.DOCUMENT
|
form.instance.type = UserProperty.Type.DOCUMENT
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
institution = self.request.user.institution
|
institution = self.request.user.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.property = SystemProperty.objects.filter(
|
||||||
owner=institution,
|
owner=institution,
|
||||||
value=pk,
|
value=pk,
|
||||||
type=Annotation.Type.SYSTEM
|
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not self.annotation:
|
if not self.property:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
|
|
|
@ -65,6 +65,8 @@ ENABLE_EMAIL = config("ENABLE_EMAIL", default=True, cast=bool)
|
||||||
|
|
||||||
EVIDENCES_DIR = config("EVIDENCES_DIR", default=os.path.join(BASE_DIR, "db"))
|
EVIDENCES_DIR = config("EVIDENCES_DIR", default=os.path.join(BASE_DIR, "db"))
|
||||||
|
|
||||||
|
DEVICE_LOG_PATH = config("DEVICE_LOG_PATH", default="/tmp")
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
@ -210,6 +212,10 @@ LOGGING = {
|
||||||
'()': CustomFormatter,
|
'()': CustomFormatter,
|
||||||
'format': '%(levelname)s %(asctime)s %(message)s'
|
'format': '%(levelname)s %(asctime)s %(message)s'
|
||||||
},
|
},
|
||||||
|
'verbose': {
|
||||||
|
'format': '{levelname} {asctime} {module} {message}',
|
||||||
|
'style': '{',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"handlers": {
|
"handlers": {
|
||||||
"console": {
|
"console": {
|
||||||
|
@ -217,6 +223,12 @@ LOGGING = {
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"formatter": "colored"
|
"formatter": "colored"
|
||||||
},
|
},
|
||||||
|
'device_log_file': {
|
||||||
|
'level': 'INFO',
|
||||||
|
|||||||
|
'class': 'logging.FileHandler',
|
||||||
|
'filename': DEVICE_LOG_PATH + "/device_changes.log",
|
||||||
|
'formatter': 'verbose',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"handlers": ["console"],
|
"handlers": ["console"],
|
||||||
|
@ -232,7 +244,12 @@ LOGGING = {
|
||||||
"handlers": ["console"],
|
"handlers": ["console"],
|
||||||
"level": "ERROR",
|
"level": "ERROR",
|
||||||
"propagate": False,
|
"propagate": False,
|
||||||
}
|
},
|
||||||
|
'device_log': {
|
||||||
|
'handlers': ['device_log_file'],
|
||||||
|
'level': 'INFO',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ import pandas as pd
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from utils.device import create_annotation, create_doc, create_index
|
from utils.device import create_property, create_doc, create_index
|
||||||
from utils.forms import MultipleFileField
|
from utils.forms import MultipleFileField
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from evidence.parse import Build
|
from evidence.parse import Build
|
||||||
from evidence.models import Annotation
|
from evidence.models import SystemProperty
|
||||||
from utils.save_snapshots import move_json, save_in_disk
|
from utils.save_snapshots import move_json, save_in_disk
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,11 +30,11 @@ class UploadForm(forms.Form):
|
||||||
try:
|
try:
|
||||||
file_json = json.loads(file_data)
|
file_json = json.loads(file_data)
|
||||||
Build(file_json, None, check=True)
|
Build(file_json, None, check=True)
|
||||||
exist_annotation = Annotation.objects.filter(
|
exists_property = SystemProperty.objects.filter(
|
||||||
uuid=file_json['uuid']
|
uuid=file_json['uuid']
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if exist_annotation:
|
if exists_property:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("The snapshot already exists"),
|
_("The snapshot already exists"),
|
||||||
code="duplicate_snapshot",
|
code="duplicate_snapshot",
|
||||||
|
@ -68,9 +68,8 @@ class UserTagForm(forms.Form):
|
||||||
self.pk = None
|
self.pk = None
|
||||||
self.uuid = kwargs.pop('uuid', None)
|
self.uuid = kwargs.pop('uuid', None)
|
||||||
self.user = kwargs.pop('user')
|
self.user = kwargs.pop('user')
|
||||||
instance = Annotation.objects.filter(
|
instance = SystemProperty.objects.filter(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
key='CUSTOM_ID',
|
key='CUSTOM_ID',
|
||||||
owner=self.user.institution
|
owner=self.user.institution
|
||||||
).first()
|
).first()
|
||||||
|
@ -86,9 +85,8 @@ class UserTagForm(forms.Form):
|
||||||
if not data:
|
if not data:
|
||||||
return False
|
return False
|
||||||
self.tag = data
|
self.tag = data
|
||||||
self.instance = Annotation.objects.filter(
|
self.instance = SystemProperty.objects.filter(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
key='CUSTOM_ID',
|
key='CUSTOM_ID',
|
||||||
owner=self.user.institution
|
owner=self.user.institution
|
||||||
).first()
|
).first()
|
||||||
|
@ -106,9 +104,8 @@ class UserTagForm(forms.Form):
|
||||||
self.instance.save()
|
self.instance.save()
|
||||||
return
|
return
|
||||||
|
|
||||||
Annotation.objects.create(
|
SystemProperty.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
key='CUSTOM_ID',
|
key='CUSTOM_ID',
|
||||||
value=self.tag,
|
value=self.tag,
|
||||||
owner=self.user.institution,
|
owner=self.user.institution,
|
||||||
|
@ -164,8 +161,8 @@ class ImportForm(forms.Form):
|
||||||
table = []
|
table = []
|
||||||
for row in self.rows:
|
for row in self.rows:
|
||||||
doc = create_doc(row)
|
doc = create_doc(row)
|
||||||
annotation = create_annotation(doc, self.user)
|
property = create_property(doc, self.user)
|
||||||
table.append((doc, annotation))
|
table.append((doc, property))
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
for doc, cred in table:
|
for doc, cred in table:
|
||||||
|
@ -186,9 +183,9 @@ class EraseServerForm(forms.Form):
|
||||||
self.pk = None
|
self.pk = None
|
||||||
self.uuid = kwargs.pop('uuid', None)
|
self.uuid = kwargs.pop('uuid', None)
|
||||||
self.user = kwargs.pop('user')
|
self.user = kwargs.pop('user')
|
||||||
instance = Annotation.objects.filter(
|
instance = UserProperty.objects.filter(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.ERASE_SERVER,
|
type=UserProperty.Type.ERASE_SERVER,
|
||||||
key='ERASE_SERVER',
|
key='ERASE_SERVER',
|
||||||
owner=self.user.institution
|
owner=self.user.institution
|
||||||
).first()
|
).first()
|
||||||
|
@ -201,9 +198,9 @@ class EraseServerForm(forms.Form):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.erase_server = self.cleaned_data.get('erase_server', False)
|
self.erase_server = self.cleaned_data.get('erase_server', False)
|
||||||
self.instance = Annotation.objects.filter(
|
self.instance = UserProperty.objects.filter(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.ERASE_SERVER,
|
type=UserProperty.Type.ERASE_SERVER,
|
||||||
key='ERASE_SERVER',
|
key='ERASE_SERVER',
|
||||||
owner=self.user.institution
|
owner=self.user.institution
|
||||||
).first()
|
).first()
|
||||||
|
@ -222,9 +219,9 @@ class EraseServerForm(forms.Form):
|
||||||
if self.instance:
|
if self.instance:
|
||||||
return
|
return
|
||||||
|
|
||||||
Annotation.objects.create(
|
UserProperty.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.ERASE_SERVER,
|
type=UserProperty.Type.ERASE_SERVER,
|
||||||
key='ERASE_SERVER',
|
key='ERASE_SERVER',
|
||||||
value=self.erase_server,
|
value=self.erase_server,
|
||||||
owner=self.user.institution,
|
owner=self.user.institution,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from utils.device import create_annotation, create_doc, create_index
|
from utils.device import create_property, create_doc, create_index
|
||||||
from user.models import Institution
|
from user.models import Institution
|
||||||
from evidence.parse import Build
|
from evidence.parse import Build
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ class Command(BaseCommand):
|
||||||
def build_placeholder(self, s, user, f_path):
|
def build_placeholder(self, s, user, f_path):
|
||||||
try:
|
try:
|
||||||
create_index(s, user)
|
create_index(s, user)
|
||||||
create_annotation(s, user, commit=True)
|
create_property(s, user, commit=True)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
txt = "In placeholder %s \n%s"
|
txt = "In placeholder %s \n%s"
|
||||||
logger.warning(txt, f_path, err)
|
logger.warning(txt, f_path, err)
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
# Generated by Django 5.0.6 on 2024-12-10 19:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("evidence", "0002_alter_annotation_type"),
|
||||||
|
("user", "0001_initial"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="SystemProperty",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("key", models.CharField(max_length=256)),
|
||||||
|
("value", models.CharField(max_length=256)),
|
||||||
|
("uuid", models.UUIDField()),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="UserProperty",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("key", models.CharField(max_length=256)),
|
||||||
|
("value", models.CharField(max_length=256)),
|
||||||
|
("uuid", models.UUIDField()),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.SmallIntegerField(
|
||||||
|
choices=[(1, "User"), (2, "Document"), (3, "EraseServer")],
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="Annotation",
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="systemproperty",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("key", "uuid"), name="system_unique_type_key_uuid"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="userproperty",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("key", "uuid", "type"), name="user_unique_type_key_uuid"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -3,32 +3,51 @@ import json
|
||||||
from dmidecode import DMIParse
|
from dmidecode import DMIParse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
from utils.constants import STR_EXTEND_SIZE, CHASSIS_DH
|
from utils.constants import STR_EXTEND_SIZE, CHASSIS_DH
|
||||||
from evidence.xapian import search
|
from evidence.xapian import search
|
||||||
from evidence.parse_details import ParseSnapshot
|
from evidence.parse_details import ParseSnapshot
|
||||||
from user.models import User, Institution
|
from user.models import User, Institution
|
||||||
|
|
||||||
|
|
||||||
class Annotation(models.Model):
|
class Property(models.Model):
|
||||||
cayop
commented
La clase Type no tiene sentido que este en Property. Creo que es mejor ponerla en la clase UserProperty. Lo mismo con la columna type. Solo pondria la columna type en UserProperty. Esto te libera del CheckConstraint de los dos modelos que has creado. La clase Type no tiene sentido que este en Property. Creo que es mejor ponerla en la clase UserProperty. Lo mismo con la columna type. Solo pondria la columna type en UserProperty. Esto te libera del CheckConstraint de los dos modelos que has creado.
El CheckConstraint lo puedes cambiar en SystemProperty haciendo que solo coja los fields key, uuid
|
|||||||
class Type(models.IntegerChoices):
|
|
||||||
SYSTEM = 0, "System"
|
|
||||||
USER = 1, "User"
|
|
||||||
DOCUMENT = 2, "Document"
|
|
||||||
ERASE_SERVER = 3, "EraseServer"
|
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
uuid = models.UUIDField()
|
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User, on_delete=models.SET_NULL, null=True, blank=True)
|
User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
type = models.SmallIntegerField(choices=Type)
|
|
||||||
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
#Only for shared behaviour, it is not a table
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class SystemProperty(Property):
|
||||||
|
uuid = models.UUIDField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
fields=["key", "uuid"], name="system_unique_type_key_uuid")
|
||||||
cayop
commented
En general separo las clases con dos lineas y los metodos de la clase solo con una linea En general separo las clases con dos lineas y los metodos de la clase solo con una linea
Si un fichero no tiene clases y solo tiene funciones, entonces separo esas funciones con dos lineas
rskthomas
commented
Perfecto! Lo tendré en cuenta a partir de ahora y lo cambio en donde vea que haga falta Perfecto! Lo tendré en cuenta a partir de ahora y lo cambio en donde vea que haga falta
|
|||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UserProperty(Property):
|
||||||
|
uuid = models.UUIDField()
|
||||||
|
|
||||||
|
class Type(models.IntegerChoices):
|
||||||
|
USER = 1, "User"
|
||||||
|
DOCUMENT = 2, "Document"
|
||||||
|
ERASE_SERVER = 3, "EraseServer"
|
||||||
|
|
||||||
|
type = models.SmallIntegerField(choices=Type, default=Type.USER)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["key", "uuid", "type"], name="user_unique_type_key_uuid")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,22 +58,22 @@ class Evidence:
|
||||||
self.doc = None
|
self.doc = None
|
||||||
self.created = None
|
self.created = None
|
||||||
self.dmi = None
|
self.dmi = None
|
||||||
self.annotations = []
|
self.properties = []
|
||||||
self.components = []
|
self.components = []
|
||||||
self.default = "n/a"
|
self.default = "n/a"
|
||||||
|
|
||||||
self.get_owner()
|
self.get_owner()
|
||||||
self.get_time()
|
self.get_time()
|
||||||
|
|
||||||
def get_annotations(self):
|
def get_properties(self):
|
||||||
self.annotations = Annotation.objects.filter(
|
self.properties = SystemProperty.objects.filter(
|
||||||
uuid=self.uuid
|
uuid=self.uuid
|
||||||
).order_by("created")
|
).order_by("created")
|
||||||
|
|
||||||
def get_owner(self):
|
def get_owner(self):
|
||||||
if not self.annotations:
|
if not self.properties:
|
||||||
self.get_annotations()
|
self.get_properties()
|
||||||
a = self.annotations.first()
|
a = self.properties.first()
|
||||||
if a:
|
if a:
|
||||||
self.owner = a.owner
|
self.owner = a.owner
|
||||||
|
|
||||||
|
@ -80,7 +99,7 @@ class Evidence:
|
||||||
self.created = self.doc.get("endTime")
|
self.created = self.doc.get("endTime")
|
||||||
|
|
||||||
if not self.created:
|
if not self.created:
|
||||||
self.created = self.annotations.last().created
|
self.created = self.properties.last().created
|
||||||
|
|
||||||
def get_components(self):
|
def get_components(self):
|
||||||
if self.is_legacy():
|
if self.is_legacy():
|
||||||
|
@ -131,9 +150,8 @@ class Evidence:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls, user):
|
def get_all(cls, user):
|
||||||
return Annotation.objects.filter(
|
return SystemProperty.objects.filter(
|
||||||
owner=user.institution,
|
owner=user.institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
key="hidalgo1",
|
key="hidalgo1",
|
||||||
).order_by("-created").values_list("uuid", "created").distinct()
|
).order_by("-created").values_list("uuid", "created").distinct()
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from dmidecode import DMIParse
|
||||||
from json_repair import repair_json
|
from json_repair import repair_json
|
||||||
from evidence.parse_details import get_lshw_child
|
from evidence.parse_details import get_lshw_child
|
||||||
|
|
||||||
from evidence.models import Annotation
|
from evidence.models import SystemProperty
|
||||||
from evidence.xapian import index
|
from evidence.xapian import index
|
||||||
from utils.constants import CHASSIS_DH
|
from utils.constants import CHASSIS_DH
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ class Build:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.index()
|
self.index()
|
||||||
self.create_annotations()
|
self.create_properties()
|
||||||
|
|
||||||
def index(self):
|
def index(self):
|
||||||
snap = json.dumps(self.json)
|
snap = json.dumps(self.json)
|
||||||
|
@ -72,24 +72,22 @@ class Build:
|
||||||
|
|
||||||
return hashlib.sha3_256(hid.encode()).hexdigest()
|
return hashlib.sha3_256(hid.encode()).hexdigest()
|
||||||
|
|
||||||
def create_annotations(self):
|
def create_properties(self):
|
||||||
annotation = Annotation.objects.filter(
|
property = SystemProperty.objects.filter(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
owner=self.user.institution,
|
owner=self.user.institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if annotation:
|
if property:
|
||||||
txt = "Warning: Snapshot %s already registered (annotation exists)"
|
txt = "Warning: Snapshot %s already registered (property exists)"
|
||||||
logger.warning(txt, self.uuid)
|
logger.warning(txt, self.uuid)
|
||||||
return
|
return
|
||||||
|
|
||||||
for k, v in self.algorithms.items():
|
for k, v in self.algorithms.items():
|
||||||
Annotation.objects.create(
|
SystemProperty.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
owner=self.user.institution,
|
owner=self.user.institution,
|
||||||
user=self.user,
|
user=self.user,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
key=k,
|
key=k,
|
||||||
value=v
|
value=v
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for snap in object.annotations %}
|
{% for snap in object.properties %}
|
||||||
<tbody>
|
<tbody>
|
||||||
{% if snap.type == 0 %}
|
{% if snap.type == 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% if form.tag.value %}
|
{% if form.tag.value %}
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
<a class="btn btn-yellow" href="{% url 'evidence:delete_annotation' form.pk %}">{% translate "Delete" %}</a>
|
<a class="btn btn-yellow" href="{% url 'evidence:delete_user_property' form.pk %}">{% translate "Delete" %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,5 +20,4 @@ urlpatterns = [
|
||||||
path("<uuid:pk>", views.EvidenceView.as_view(), name="details"),
|
path("<uuid:pk>", views.EvidenceView.as_view(), name="details"),
|
||||||
path("<uuid:pk>/eraseserver", views.EraseServerView.as_view(), name="erase_server"),
|
path("<uuid:pk>/eraseserver", views.EraseServerView.as_view(), name="erase_server"),
|
||||||
path("<uuid:pk>/download", views.DownloadEvidenceView.as_view(), name="download"),
|
path("<uuid:pk>/download", views.DownloadEvidenceView.as_view(), name="download"),
|
||||||
path('annotation/<int:pk>/del', views.AnnotationDeleteView.as_view(), name='delete_annotation'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -13,7 +13,7 @@ from django.views.generic.edit import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from dashboard.mixins import DashboardView, Http403
|
from dashboard.mixins import DashboardView, Http403
|
||||||
from evidence.models import Evidence, Annotation
|
from evidence.models import SystemProperty, UserProperty, Evidence
|
||||||
from evidence.forms import (
|
from evidence.forms import (
|
||||||
UploadForm,
|
UploadForm,
|
||||||
UserTagForm,
|
UserTagForm,
|
||||||
|
@ -95,7 +95,7 @@ class EvidenceView(DashboardView, FormView):
|
||||||
if self.object.owner != self.request.user.institution:
|
if self.object.owner != self.request.user.institution:
|
||||||
raise Http403
|
raise Http403
|
||||||
|
|
||||||
self.object.get_annotations()
|
self.object.get_properties()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -141,31 +141,6 @@ class DownloadEvidenceView(DashboardView, TemplateView):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class AnnotationDeleteView(DashboardView, DeleteView):
|
|
||||||
model = Annotation
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.pk = kwargs['pk']
|
|
||||||
|
|
||||||
try:
|
|
||||||
referer = self.request.META["HTTP_REFERER"]
|
|
||||||
path_referer = urlparse(referer).path
|
|
||||||
resolver_match = resolve(path_referer)
|
|
||||||
url_name = resolver_match.view_name
|
|
||||||
kwargs_view = resolver_match.kwargs
|
|
||||||
except:
|
|
||||||
# if is not possible resolve the reference path return 404
|
|
||||||
raise Http404
|
|
||||||
|
|
||||||
self.object = get_object_or_404(
|
|
||||||
self.model,
|
|
||||||
pk=self.pk,
|
|
||||||
owner=self.request.user.institution
|
|
||||||
)
|
|
||||||
self.object.delete()
|
|
||||||
|
|
||||||
|
|
||||||
return redirect(url_name, **kwargs_view)
|
|
||||||
|
|
||||||
|
|
||||||
class EraseServerView(DashboardView, FormView):
|
class EraseServerView(DashboardView, FormView):
|
||||||
|
@ -182,7 +157,7 @@ class EraseServerView(DashboardView, FormView):
|
||||||
if self.object.owner != self.request.user.institution:
|
if self.object.owner != self.request.user.institution:
|
||||||
raise Http403
|
raise Http403
|
||||||
|
|
||||||
self.object.get_annotations()
|
self.object.get_properties()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Generated by Django 5.0.6 on 2024-12-10 19:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("lot", "0002_alter_lot_closed"),
|
||||||
|
("user", "0001_initial"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="LotProperty",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("key", models.CharField(max_length=256)),
|
||||||
|
("value", models.CharField(max_length=256)),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.SmallIntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, "System"),
|
||||||
|
(1, "User"),
|
||||||
|
(2, "Document"),
|
||||||
|
(3, "EraseServer"),
|
||||||
|
],
|
||||||
|
default=1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"lot",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="lot.lot"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="LotAnnotation",
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="lotproperty",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("key", "lot", "type"), name="lot_unique_type_key_lot"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,8 +7,8 @@ from utils.constants import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from user.models import User, Institution
|
from user.models import User, Institution
|
||||||
|
from evidence.models import Property
|
||||||
# from device.models import Device
|
# from device.models import Device
|
||||||
# from evidence.models import Annotation
|
|
||||||
|
|
||||||
|
|
||||||
class LotTag(models.Model):
|
class LotTag(models.Model):
|
||||||
|
@ -45,17 +45,20 @@ class Lot(models.Model):
|
||||||
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
||||||
d.delete()
|
d.delete()
|
||||||
|
|
||||||
|
class LotProperty (Property):
|
||||||
|
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
|
||||||
|
|
||||||
class LotAnnotation(models.Model):
|
|
||||||
class Type(models.IntegerChoices):
|
class Type(models.IntegerChoices):
|
||||||
SYSTEM= 0, "System"
|
SYSTEM = 0, "System"
|
||||||
USER = 1, "User"
|
USER = 1, "User"
|
||||||
DOCUMENT = 2, "Document"
|
DOCUMENT = 2, "Document"
|
||||||
|
ERASE_SERVER = 3, "EraseServer"
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
type = models.SmallIntegerField(choices=Type.choices, default=Type.USER)
|
||||||
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
|
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
class Meta:
|
||||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
constraints = [
|
||||||
type = models.SmallIntegerField(choices=Type)
|
models.UniqueConstraint(
|
||||||
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
fields=["key", "lot", "type"], name="lot_unique_type_key_lot"
|
||||||
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
)
|
||||||
|
]
|
||||||
|
|
|
@ -11,15 +11,15 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="tab-pane fade show active" id="details">
|
<div class="tab-pane fade show active" id="details">
|
||||||
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
||||||
<a href="{% url 'lot:add_annotation' lot.pk %}" class="btn btn-primary">
|
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-primary">
|
||||||
|
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add new annotation
|
Add new lot Property
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="card-title mt-2">Annotations</h5>
|
<h5 class="card-title mt-2">Properties</h5>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for a in annotations %}
|
{% for a in properties %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ a.key }}</td>
|
<td>{{ a.key }}</td>
|
||||||
<td>{{ a.value }}</td>
|
<td>{{ a.value }}</td>
|
|
@ -12,6 +12,6 @@ urlpatterns = [
|
||||||
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
|
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
|
||||||
path("<int:pk>/document/", views.LotDocumentsView.as_view(), name="documents"),
|
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>/document/add", views.LotAddDocumentView.as_view(), name="add_document"),
|
||||||
path("<int:pk>/annotation", views.LotAnnotationsView.as_view(), name="annotations"),
|
path("<int:pk>/property", views.LotPropertiesView.as_view(), name="properties"),
|
||||||
path("<int:pk>/annotation/add", views.LotAddAnnotationView.as_view(), name="add_annotation"),
|
path("<int:pk>/property/add", views.LotAddPropertyView.as_view(), name="add_property"),
|
||||||
]
|
]
|
||||||
|
|
43
lot/views.py
43
lot/views.py
|
@ -9,10 +9,9 @@ from django.views.generic.edit import (
|
||||||
FormView,
|
FormView,
|
||||||
)
|
)
|
||||||
from dashboard.mixins import DashboardView
|
from dashboard.mixins import DashboardView
|
||||||
from lot.models import Lot, LotTag, LotAnnotation
|
from lot.models import Lot, LotTag, LotProperty
|
||||||
from lot.forms import LotsForm
|
from lot.forms import LotsForm
|
||||||
|
|
||||||
|
|
||||||
class NewLotView(DashboardView, CreateView):
|
class NewLotView(DashboardView, CreateView):
|
||||||
template_name = "new_lot.html"
|
template_name = "new_lot.html"
|
||||||
title = _("New lot")
|
title = _("New lot")
|
||||||
|
@ -143,18 +142,18 @@ class LotsTagsView(DashboardView, TemplateView):
|
||||||
|
|
||||||
|
|
||||||
class LotAddDocumentView(DashboardView, CreateView):
|
class LotAddDocumentView(DashboardView, CreateView):
|
||||||
template_name = "new_annotation.html"
|
template_name = "new_property.html"
|
||||||
title = _("New Document")
|
title = _("New Document")
|
||||||
breadcrumb = "Device / New document"
|
breadcrumb = "Device / New document"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
model = LotAnnotation
|
model = LotProperty
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
form.instance.lot = self.lot
|
form.instance.lot = self.lot
|
||||||
form.instance.type = LotAnnotation.Type.DOCUMENT
|
form.instance.type = LotProperty.Type.DOCUMENT
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -169,16 +168,16 @@ class LotAddDocumentView(DashboardView, CreateView):
|
||||||
class LotDocumentsView(DashboardView, TemplateView):
|
class LotDocumentsView(DashboardView, TemplateView):
|
||||||
template_name = "documents.html"
|
template_name = "documents.html"
|
||||||
title = _("New Document")
|
title = _("New Document")
|
||||||
breadcrumb = "Device / New document"
|
breadcrumb = "Devicce / New document"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.pk = kwargs.get('pk')
|
self.pk = kwargs.get('pk')
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
|
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
|
||||||
documents = LotAnnotation.objects.filter(
|
documents = LotProperty.objects.filter(
|
||||||
lot=lot,
|
lot=lot,
|
||||||
owner=self.request.user.institution,
|
owner=self.request.user.institution,
|
||||||
type=LotAnnotation.Type.DOCUMENT,
|
type=LotProperty.Type.DOCUMENT,
|
||||||
)
|
)
|
||||||
context.update({
|
context.update({
|
||||||
'lot': lot,
|
'lot': lot,
|
||||||
|
@ -189,48 +188,48 @@ class LotDocumentsView(DashboardView, TemplateView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class LotAnnotationsView(DashboardView, TemplateView):
|
class LotPropertiesView(DashboardView, TemplateView):
|
||||||
template_name = "annotations.html"
|
template_name = "properties.html"
|
||||||
title = _("New Annotation")
|
title = _("New Lot Property")
|
||||||
breadcrumb = "Device / New annotation"
|
breadcrumb = "Lot / New property"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.pk = kwargs.get('pk')
|
self.pk = kwargs.get('pk')
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
|
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
|
||||||
annotations = LotAnnotation.objects.filter(
|
properties = LotProperty.objects.filter(
|
||||||
lot=lot,
|
lot=lot,
|
||||||
owner=self.request.user.institution,
|
owner=self.request.user.institution,
|
||||||
type=LotAnnotation.Type.USER,
|
type=LotProperty.Type.USER,
|
||||||
)
|
)
|
||||||
context.update({
|
context.update({
|
||||||
'lot': lot,
|
'lot': lot,
|
||||||
'annotations': annotations,
|
'properties': properties,
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'breadcrumb': self.breadcrumb
|
'breadcrumb': self.breadcrumb
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class LotAddAnnotationView(DashboardView, CreateView):
|
class LotAddPropertyView(DashboardView, CreateView):
|
||||||
template_name = "new_annotation.html"
|
template_name = "new_property.html"
|
||||||
title = _("New Annotation")
|
title = _("New Lot Property")
|
||||||
breadcrumb = "Device / New annotation"
|
breadcrumb = "Device / New property"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
model = LotAnnotation
|
model = LotProperty
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
form.instance.user = self.request.user
|
form.instance.user = self.request.user
|
||||||
form.instance.lot = self.lot
|
form.instance.lot = self.lot
|
||||||
form.instance.type = LotAnnotation.Type.USER
|
form.instance.type = LotProperty.Type.USER
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution)
|
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution)
|
||||||
self.success_url = reverse_lazy('lot:annotations', args=[pk])
|
self.success_url = reverse_lazy('lot:properties', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from evidence.xapian import index
|
from evidence.xapian import index
|
||||||
from evidence.models import Annotation
|
from evidence.models import SystemProperty
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ def create_doc(data):
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
def create_annotation(doc, user, commit=False):
|
def create_property(doc, user, commit=False):
|
||||||
if not doc or not doc.get('uuid') or not doc.get("CUSTOMER_ID"):
|
if not doc or not doc.get('uuid') or not doc.get("CUSTOMER_ID"):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -76,25 +76,23 @@ def create_annotation(doc, user, commit=False):
|
||||||
'uuid': doc['uuid'],
|
'uuid': doc['uuid'],
|
||||||
'owner': user.institution,
|
'owner': user.institution,
|
||||||
'user': user,
|
'user': user,
|
||||||
'type': Annotation.Type.SYSTEM,
|
|
||||||
'key': 'CUSTOMER_ID',
|
'key': 'CUSTOMER_ID',
|
||||||
'value': doc['CUSTOMER_ID'],
|
'value': doc['CUSTOMER_ID'],
|
||||||
}
|
}
|
||||||
if commit:
|
if commit:
|
||||||
annotation = Annotation.objects.filter(
|
property = SystemProperty.objects.filter(
|
||||||
uuid=doc["uuid"],
|
uuid=doc["uuid"],
|
||||||
owner=user.institution,
|
owner=user.institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if annotation:
|
if property:
|
||||||
txt = "Warning: Snapshot %s already registered (annotation exists)"
|
txt = "Warning: Snapshot %s already registered (system property exists)"
|
||||||
logger.warning(txt, doc["uuid"])
|
logger.warning(txt, doc["uuid"])
|
||||||
return annotation
|
return property
|
||||||
|
|
||||||
return Annotation.objects.create(**data)
|
return SystemProperty.objects.create(**data)
|
||||||
|
|
||||||
return Annotation(**data)
|
return SystemProperty(**data)
|
||||||
|
|
||||||
|
|
||||||
def create_index(doc, user):
|
def create_index(doc, user):
|
||||||
|
|
Loading…
Reference in a new issue
Creo que es mejor poner el directorio donde reside device_changes.log en una variable definida en el .env y por defecto poner algo como "/tmp"