From 50454504240c401b019a66c0dbdc3380b32ae0bf Mon Sep 17 00:00:00 2001 From: pedro Date: Fri, 8 Nov 2024 15:55:04 +0100 Subject: [PATCH 01/16] improve .env and docker-reset --- .env.example | 7 +++---- docker-reset.sh | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index cb97c80..ed60e95 100644 --- a/.env.example +++ b/.env.example @@ -1,17 +1,16 @@ DOMAIN=localhost +DEMO=true # note that with DEBUG=true, logs are more verbose (include tracebacks) DEBUG=true -DEMO=true +ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1, STATIC_ROOT=/tmp/static/ MEDIA_ROOT=/tmp/media/ -ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1, -DOMAIN=localhost EMAIL_HOST="mail.example.org" EMAIL_HOST_USER="fillme_noreply" EMAIL_HOST_PASSWORD="fillme_passwd" EMAIL_PORT=587 -EMAIL_USE_TLS=True +EMAIL_USE_TLS=true EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend" EMAIL_FILE_PATH="/tmp/app-messages" ENABLE_EMAIL=false diff --git a/docker-reset.sh b/docker-reset.sh index bf14db2..9dc0032 100755 --- a/docker-reset.sh +++ b/docker-reset.sh @@ -14,6 +14,11 @@ main() { if [ "${DETACH:-}" ]; then detach_arg='-d' fi + + if [ ! -f .env ]; then + cp -v .env.example .env + echo "WARNING: .env was not there, .env.example was copied, this only happens once" + fi # remove old database sudo rm -vfr ./db/* docker compose down -v From 2653b0eeee28fa65c726814b9597b572612d1d9e Mon Sep 17 00:00:00 2001 From: sergio_gimenez Date: Tue, 29 Oct 2024 08:52:17 +0100 Subject: [PATCH 02/16] Add improved redame --- README.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 375cd85..a919c4d 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,118 @@ -# INSTALACIÓN: +# Device Hub -La instalación es muy estándar +DeviceHub is an IT Asset Management System focused on reusing devices, created under the [eReuse.org](https://www.ereuse.org) project. +## Overview + +DeviceHub aims to: + +- Provide a common IT Asset Management platform for donors, receivers, and IT professionals. +- Automatically collect, analyze, and share device metadata while ensuring privacy and traceability. +- Integrate with existing IT Asset Management Systems. +- Operate in a decentralized manner. + +DeviceHub primarily works with three types of objects: + +1. **Devices**: Including computers, smartphones, and their components. +2. **Events**: Actions performed on devices (e.g., Repair, Allocate). +3. **Accounts**: Users who perform events on devices. + +## Installation + +### Quickstart + +For a quick start with dummy data, DeviceHub can be run directly with docker. To do so, from the root of the project run: + +```bash +./docker-reset.sh ``` + +Also there is a demo running in http://demo.ereuse.org/. The token for accessing the instance will be always: `token=5018dd65-9abd-4a62-8896-80f34ac66150`, but the instance will be reset every day a t 4 am. + +## Running from baremetal + +### Prerequisites + +- Python 3.10 +- pip +- virtualenv + +Specially when developing, is quite convenient to run DeviceHub from a virtual environment. To start with this deployment, create a virtual environment to isolate our project dependencies: + +```bash python -m venv env -source env/bin/actevate -python install -r requirements.txt +source env/bin/activate +pip install -r requirements.txt ``` -## IMPORTANT EXTERNAL DEPENDENCIES +### System Dependencies -Para arrancarlo es necesario tener el paquete `xapian-bindings` en tu ordenador. No se instala mediante `pip`, así que depende de cada [sistema operativo](https://xapian.org/download). +#### Xapian -Luego solo necesitas: +Now, install the xapian dependencies (xapian library and python bindings) +```bash +sudo apt-get install python3-xapian libxapian-dev ``` -./manage.py migrate -./manage.py runserver + +Allow the virtual environment to use system-installed packages: + +```bash +export PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages" ``` + +#### Environment Variables + +Now, configure the environment variables. For this, we will expand a `.env` file. You can use the following content as an example: + +```source +STATIC_ROOT=/tmp/static/ +MEDIA_ROOT=/tmp/media/ +ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1, +DOMAIN=localhost +DEBUG=True +``` + +Now, expand the enviroment variables: + +```bash +source .env +``` + +### Migrations + +Now, apply migrations + +```bash +python manage.py makemigrations +python manage.py migrate +``` + +Also, we can add some dummy data into the database to play along: + +```bash +python manage.py add_institution Pangea +python manage.py add_user Pangea user@example.org 1234 +python manage.py up_snapshots example/snapshots/ user@example.org +``` + +### Run DeviceHub + +Finally, we can run the DeviceHub service by running: + +```bash +python manage.py runserver +``` + +## Clean up + +To clean up the deployment and start fresh, just delete Django's database: + +```bash +rm db/* +``` + + +## License + +DeviceHub is released under the [GNU Affero General Public License v3.0](LICENSE). From d136ae3aa30b05e34b46e2136b34aa8e3c6a72ef Mon Sep 17 00:00:00 2001 From: pedro Date: Fri, 8 Nov 2024 15:50:36 +0100 Subject: [PATCH 03/16] README: small changes --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a919c4d..6a39255 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,21 @@ DeviceHub primarily works with three types of objects: ## Installation +Assuming a host with debian stable + ### Quickstart -For a quick start with dummy data, DeviceHub can be run directly with docker. To do so, from the root of the project run: +For a quick start with dummy data in localhost, DeviceHub can be run directly with docker. To do so, from the root of the project run: ```bash ./docker-reset.sh ``` -Also there is a demo running in http://demo.ereuse.org/. The token for accessing the instance will be always: `token=5018dd65-9abd-4a62-8896-80f34ac66150`, but the instance will be reset every day a t 4 am. +Note that everytime you perform the `docker-reset.sh` script, all data is lost. + +Also there is a demo running in http://demo.ereuse.org/. The token for accessing the instance will be always: `token=5018dd65-9abd-4a62-8896-80f34ac66150`, but the instance will be reset every day at 4 am. + +For production needs, review and change .env file properly ## Running from baremetal @@ -73,7 +79,7 @@ DOMAIN=localhost DEBUG=True ``` -Now, expand the enviroment variables: +Now, expand the environment variables: ```bash source .env @@ -104,7 +110,7 @@ Finally, we can run the DeviceHub service by running: python manage.py runserver ``` -## Clean up +### Clean up To clean up the deployment and start fresh, just delete Django's database: @@ -112,7 +118,6 @@ To clean up the deployment and start fresh, just delete Django's database: rm db/* ``` - ## License DeviceHub is released under the [GNU Affero General Public License v3.0](LICENSE). From eb81b65e5b4c37c2297ef958df0eec9457e468a2 Mon Sep 17 00:00:00 2001 From: pedro Date: Sun, 10 Nov 2024 03:51:15 +0100 Subject: [PATCH 04/16] logger: swap colors between INFO and WARNING yellow is more a warning color than purple purple is more relaxing, so better for info --- utils/logger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/logger.py b/utils/logger.py index df85e7e..3a320ce 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -13,9 +13,9 @@ class CustomFormatter(logging.Formatter): if record.levelname == "ERROR": color = RED elif record.levelname == "WARNING": - color = PURPLE - elif record.levelname in ["INFO", "DEBUG"]: color = YELLOW + elif record.levelname in ["INFO", "DEBUG"]: + color = PURPLE else: color = RESET From ccb0be1b4c53b3b5a4c5d8e85809e88d7ea5807d Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Sat, 9 Nov 2024 16:30:49 -0300 Subject: [PATCH 05/16] check for values on websnapshot --- device/templates/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device/templates/details.html b/device/templates/details.html index 331c857..137a4f6 100644 --- a/device/templates/details.html +++ b/device/templates/details.html @@ -58,7 +58,7 @@
{{ object.type }}
- {% if object.is_websnapshot %} + {% if object.is_websnapshot and object.last_user_evidence %} {% for k, v in object.last_user_evidence %}
{{ k }}
From 83dd044325f969c1bb22f7f1fd0d9a70256def92 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Sat, 9 Nov 2024 16:46:01 -0300 Subject: [PATCH 06/16] better error handling and logging --- evidence/management/commands/up_snapshots.py | 25 ++++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/evidence/management/commands/up_snapshots.py b/evidence/management/commands/up_snapshots.py index c987d76..5f699a7 100644 --- a/evidence/management/commands/up_snapshots.py +++ b/evidence/management/commands/up_snapshots.py @@ -47,10 +47,23 @@ class Command(BaseCommand): self.open(filepath) def open(self, filepath): - with open(filepath, 'r') as file: - content = json.loads(file.read()) - path_name = save_in_disk(content, self.user.institution.name) - self.snapshots.append((content, path_name)) + try: + with open(filepath, 'r') as file: + content = json.loads(file.read()) + path_name = save_in_disk(content, self.user.institution.name) + + self.snapshots.append((content, path_name)) + + except json.JSONDecodeError as e: + logger.error("JSON decode error in file %s: %s", filepath, e) + raise ValueError(f"Invalid JSON format in file {filepath}") from e + except FileNotFoundError as e: + logger.error("File not found: %s", filepath) + raise FileNotFoundError(f"File not found: {filepath}") from e + #or we cath'em all + except Exception as e: + logger.exception("Unexpected error when opening file %s: %s", filepath, e) + raise Exception(f"Unexpected error when opening file {filepath}") from e def parsing(self): for s, p in self.snapshots: @@ -58,6 +71,8 @@ class Command(BaseCommand): self.devices.append(Build(s, self.user)) move_json(p, self.user.institution.name) except Exception as err: + if settings.DEBUG: + logger.exception("%s", err) snapshot_id = s.get("uuid", "") - txt = "Could not parse snapshot: %s" + txt = "It is not possible to parse snapshot: %s" logger.error(txt, snapshot_id) From cd0a8217c83dbef867d71e03a0fab3763489ccfc Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Sat, 9 Nov 2024 16:52:26 -0300 Subject: [PATCH 07/16] fixed mac not recovered properly --- evidence/parse.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/evidence/parse.py b/evidence/parse.py index 3ec476b..b98f840 100644 --- a/evidence/parse.py +++ b/evidence/parse.py @@ -4,6 +4,7 @@ import logging from dmidecode import DMIParse from json_repair import repair_json +from evidence.parse_details import get_lshw_child from evidence.models import Annotation from evidence.xapian import index @@ -12,6 +13,23 @@ 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)) + + networks = [] + get_lshw_child(hw, networks, 'network') + + if nets_sorted: + mac = nets_sorted[0]['serial'] + logger.debug("The snapshot has the following MAC: %s" , mac) + return mac + def get_network_cards(child, nets): if child['id'] == 'network' and "PCI:" in child.get("businfo"): From 37631244400a12bbe998ac77ca0087a3d94b8e96 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 03:41:08 -0300 Subject: [PATCH 08/16] minor fix in mac parsing --- evidence/parse.py | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/evidence/parse.py b/evidence/parse.py index b98f840..fd68e06 100644 --- a/evidence/parse.py +++ b/evidence/parse.py @@ -22,8 +22,10 @@ def get_mac(lshw): except json.decoder.JSONDecodeError: hw = json.loads(repair_json(lshw)) - networks = [] - get_lshw_child(hw, networks, 'network') + nets = [] + get_lshw_child(hw, nets, 'network') + + nets_sorted = sorted(nets, key=lambda x: x['businfo']) if nets_sorted: mac = nets_sorted[0]['serial'] @@ -31,36 +33,6 @@ def get_mac(lshw): return mac -def get_network_cards(child, nets): - if child['id'] == 'network' and "PCI:" in child.get("businfo"): - nets.append(child) - if child.get('children'): - [get_network_cards(x, nets) for x in child['children']] - - -def get_mac(lshw): - nets = [] - try: - if type(lshw) is dict: - hw = lshw - else: - hw = json.loads(lshw) - except json.decoder.JSONDecodeError: - hw = json.loads(repair_json(lshw)) - - try: - get_network_cards(hw, nets) - except Exception as ss: - logger.warning("%s", ss) - return - - nets_sorted = sorted(nets, key=lambda x: x['businfo']) - # This funcion get the network card integrated in motherboard - # integrate = [x for x in nets if "pci@0000:00:" in x.get('businfo', '')] - - if nets_sorted: - return nets_sorted[0]['serial'] - class Build: def __init__(self, evidence_json, user, check=False): From 80083caf993734f503161ea59470150c89cdba42 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 04:06:22 -0300 Subject: [PATCH 09/16] better error messages --- evidence/forms.py | 20 ++++++++++++++------ evidence/management/commands/up_snapshots.py | 6 +++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/evidence/forms.py b/evidence/forms.py index dab7a60..353bbf4 100644 --- a/evidence/forms.py +++ b/evidence/forms.py @@ -33,13 +33,21 @@ class UploadForm(forms.Form): exist_annotation = Annotation.objects.filter( uuid=file_json['uuid'] ).first() - + if exist_annotation: - raise ValidationError("error: {} exist".format(file_name)) - - except Exception: - raise ValidationError("error in: {}".format(file_name)) - + raise ValidationError( + _("The snapshot already exists"), + code="duplicate_snapshot", + params={"file_name": file_name}, + ) + + #Caught any error and display it as Validation Error so the Form handles it + except Exception as e: + raise ValidationError( + _("Error on '%(file_name)s': %(error)s"), + code="error", + params={"file_name": file_name, "error": getattr(e, 'message', str(e))}, + ) self.evidences.append((file_name, file_json)) return True diff --git a/evidence/management/commands/up_snapshots.py b/evidence/management/commands/up_snapshots.py index 5f699a7..760fab3 100644 --- a/evidence/management/commands/up_snapshots.py +++ b/evidence/management/commands/up_snapshots.py @@ -56,14 +56,14 @@ class Command(BaseCommand): except json.JSONDecodeError as e: logger.error("JSON decode error in file %s: %s", filepath, e) - raise ValueError(f"Invalid JSON format in file {filepath}") from e + raise ValueError(f"Invalid JSON format in file. Check for file integrity.") from e except FileNotFoundError as e: logger.error("File not found: %s", filepath) - raise FileNotFoundError(f"File not found: {filepath}") from e + raise FileNotFoundError(f"File not found") from e #or we cath'em all except Exception as e: logger.exception("Unexpected error when opening file %s: %s", filepath, e) - raise Exception(f"Unexpected error when opening file {filepath}") from e + raise Exception(f"Unexpected error when opening file") from e def parsing(self): for s, p in self.snapshots: From 90f057288dd73919cf6a958921b613a835d476ae Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 04:28:41 -0300 Subject: [PATCH 10/16] upload form now displays error messages once --- evidence/templates/upload.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/evidence/templates/upload.html b/evidence/templates/upload.html index 056cf55..2a76b76 100644 --- a/evidence/templates/upload.html +++ b/evidence/templates/upload.html @@ -13,18 +13,18 @@ {% csrf_token %} {% if form.errors %} {% endif %} -{% bootstrap_form form %} -
- {% translate "Cancel" %} +{% bootstrap_form form alert_error_type="fields" %} + From b4c18535fb5f776b2f6a09c570aa8c50ad4f0d5c Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 14:12:26 -0300 Subject: [PATCH 11/16] grammar fix and todo added --- evidence/forms.py | 3 +-- evidence/views.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/evidence/forms.py b/evidence/forms.py index 353bbf4..0ac251f 100644 --- a/evidence/forms.py +++ b/evidence/forms.py @@ -38,10 +38,9 @@ class UploadForm(forms.Form): raise ValidationError( _("The snapshot already exists"), code="duplicate_snapshot", - params={"file_name": file_name}, ) - #Caught any error and display it as Validation Error so the Form handles it + #Catch any error and display it as Validation Error so the Form handles it except Exception as e: raise ValidationError( _("Error on '%(file_name)s': %(error)s"), diff --git a/evidence/views.py b/evidence/views.py index 201a621..65a033b 100644 --- a/evidence/views.py +++ b/evidence/views.py @@ -51,6 +51,7 @@ class UploadView(DashboardView, FormView): return response def form_invalid(self, form): + #TODO: change file_input field class to "is-invalid" if any errors occur response = super().form_invalid(form) return response From 8dade92f7e4067d9d69728b45bc0c2d85447fc46 Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 14:47:44 -0300 Subject: [PATCH 12/16] changed form validation onto field --- evidence/forms.py | 2 +- evidence/templates/upload.html | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/evidence/forms.py b/evidence/forms.py index 0ac251f..a2c0020 100644 --- a/evidence/forms.py +++ b/evidence/forms.py @@ -15,7 +15,7 @@ from utils.save_snapshots import move_json, save_in_disk class UploadForm(forms.Form): evidence_file = MultipleFileField(label=_("File")) - def clean(self): + def clean_evidence_file(self): self.evidences = [] data = self.cleaned_data.get('evidence_file') if not data: diff --git a/evidence/templates/upload.html b/evidence/templates/upload.html index 2a76b76..6337b3e 100644 --- a/evidence/templates/upload.html +++ b/evidence/templates/upload.html @@ -8,22 +8,20 @@
+ + + {% load django_bootstrap5 %}
{% csrf_token %} -{% if form.errors %} - -{% endif %} -{% bootstrap_form form alert_error_type="fields" %} -
+ +{% bootstrap_form form alert_error_type="none" error_css_class="alert alert-danger alert-icon alert-icon-border" %} + From ac70a4ae25357061af3abd065349937686ae782b Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 14:49:17 -0300 Subject: [PATCH 13/16] catching pandas exception --- evidence/forms.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/evidence/forms.py b/evidence/forms.py index a2c0020..83d9e6b 100644 --- a/evidence/forms.py +++ b/evidence/forms.py @@ -130,7 +130,15 @@ class ImportForm(forms.Form): data = self.cleaned_data["file_import"] self.file_name = data.name - df = pd.read_excel(data) + + try: + df = pd.read_excel(data) + except Exception as e: + raise ValidationError( + _("Error on '%(file_name)s': Invalid File"), + params={"file_name": self.file_name} + ) + df.fillna('', inplace=True) data_pd = df.to_dict(orient='index') From ac48a77a2f20f3edefbb2e3667bb190994f69f6e Mon Sep 17 00:00:00 2001 From: Thomas Rusiecki Date: Mon, 11 Nov 2024 14:57:20 -0300 Subject: [PATCH 14/16] added success messages --- evidence/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/evidence/views.py b/evidence/views.py index 65a033b..dabcd9d 100644 --- a/evidence/views.py +++ b/evidence/views.py @@ -1,5 +1,6 @@ import json +from django.contrib import messages from urllib.parse import urlparse from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ @@ -47,11 +48,11 @@ class UploadView(DashboardView, FormView): def form_valid(self, form): form.save(self.request.user) + messages.success(self.request, _("Evidence uploaded successfully.")) response = super().form_valid(form) return response def form_invalid(self, form): - #TODO: change file_input field class to "is-invalid" if any errors occur response = super().form_invalid(form) return response @@ -71,6 +72,7 @@ class ImportView(DashboardView, FormView): def form_valid(self, form): form.save() + messages.success(self.request, _("Evidence imported successfully.")) response = super().form_valid(form) return response From a748b900ca3d095dc53f59fde44dd5c02eebc225 Mon Sep 17 00:00:00 2001 From: pedro Date: Tue, 12 Nov 2024 14:31:46 +0100 Subject: [PATCH 15/16] recover old up_snapshot functionality - We don't need the if settings.DEBUG anymore, we have a logger configuration that when DEBUG is there, a trace is always generated - restore old error message that is shorter and clearer than the new one --- evidence/management/commands/up_snapshots.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/evidence/management/commands/up_snapshots.py b/evidence/management/commands/up_snapshots.py index 760fab3..b1ca8c7 100644 --- a/evidence/management/commands/up_snapshots.py +++ b/evidence/management/commands/up_snapshots.py @@ -71,8 +71,6 @@ class Command(BaseCommand): self.devices.append(Build(s, self.user)) move_json(p, self.user.institution.name) except Exception as err: - if settings.DEBUG: - logger.exception("%s", err) snapshot_id = s.get("uuid", "") - txt = "It is not possible to parse snapshot: %s" + txt = "Could not parse snapshot: %s" logger.error(txt, snapshot_id) From 65788b36afd3243266c8de4d251adf2621478b04 Mon Sep 17 00:00:00 2001 From: pedro Date: Tue, 12 Nov 2024 14:37:55 +0100 Subject: [PATCH 16/16] up_snapshots: simplify and clarify errors specially, do not halt in case of errors, just print them in logs --- evidence/management/commands/up_snapshots.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/evidence/management/commands/up_snapshots.py b/evidence/management/commands/up_snapshots.py index b1ca8c7..36f5c28 100644 --- a/evidence/management/commands/up_snapshots.py +++ b/evidence/management/commands/up_snapshots.py @@ -54,23 +54,15 @@ class Command(BaseCommand): self.snapshots.append((content, path_name)) - except json.JSONDecodeError as e: - logger.error("JSON decode error in file %s: %s", filepath, e) - raise ValueError(f"Invalid JSON format in file. Check for file integrity.") from e - except FileNotFoundError as e: - logger.error("File not found: %s", filepath) - raise FileNotFoundError(f"File not found") from e - #or we cath'em all except Exception as e: - logger.exception("Unexpected error when opening file %s: %s", filepath, e) - raise Exception(f"Unexpected error when opening file") from e + logger.error("Could not open file %s: %s", filepath, e) def parsing(self): for s, p in self.snapshots: try: self.devices.append(Build(s, self.user)) move_json(p, self.user.institution.name) - except Exception as err: + except Exception as e: snapshot_id = s.get("uuid", "") - txt = "Could not parse snapshot: %s" - logger.error(txt, snapshot_id) + txt = "Could not parse snapshot %s: %s" + logger.error(txt, snapshot_id, e)