Compare commits
84 commits
main
...
feature/f3
Author | SHA1 | Date | |
---|---|---|---|
Sergio Giménez Antón | bd4f6b7d56 | ||
Sergio Giménez Antón | f9c9c9dd7c | ||
Sergio Giménez Antón | 60ccbec369 | ||
Sergio Giménez Antón | 3fb0961815 | ||
Sergio Giménez Antón | 447946a576 | ||
Cayo Puigdefabregas | 5d190d07a3 | ||
Cayo Puigdefabregas | d1abb206e8 | ||
85bae67189 | |||
d429485651 | |||
07c25f4a92 | |||
Cayo Puigdefabregas | 14277c17cb | ||
Cayo Puigdefabregas | f7051c3130 | ||
Cayo Puigdefabregas | 09be1a2f74 | ||
Cayo Puigdefabregas | a3dd5d9639 | ||
Cayo Puigdefabregas | 3f5460b81f | ||
Cayo Puigdefabregas | bf7975bc24 | ||
8e128557c0 | |||
Cayo Puigdefabregas | 25e7e85548 | ||
Cayo Puigdefabregas | ba126491be | ||
Cayo Puigdefabregas | 81e7ba267d | ||
Cayo Puigdefabregas | 1e08f0fc0c | ||
Cayo Puigdefabregas | ebabb6b228 | ||
Cayo Puigdefabregas | 4954199610 | ||
Cayo Puigdefabregas | e84b72c70b | ||
Cayo Puigdefabregas | 99435fff85 | ||
Cayo Puigdefabregas | 6c0e77891f | ||
Cayo Puigdefabregas | a2d859494b | ||
Cayo Puigdefabregas | ea6d990e56 | ||
Cayo Puigdefabregas | 612737d46c | ||
Cayo Puigdefabregas | 30be57ee25 | ||
Cayo Puigdefabregas | 88bdabb64f | ||
Cayo Puigdefabregas | 96268c8caf | ||
7ed05f0932 | |||
Cayo Puigdefabregas | b652d7d452 | ||
Cayo Puigdefabregas | 04ecb4f2f1 | ||
Cayo Puigdefabregas | 1613eaaa44 | ||
Cayo Puigdefabregas | 06264558df | ||
Cayo Puigdefabregas | 80b4c3b4ca | ||
Cayo Puigdefabregas | e2078c7bde | ||
cfae9d4ec9 | |||
Cayo Puigdefabregas | 578fa73fe5 | ||
f1d57ff618 | |||
Cayo Puigdefabregas | 3cf8ceb5d3 | ||
Cayo Puigdefabregas | b56dc0dfda | ||
Cayo Puigdefabregas | 1c58bff515 | ||
Cayo Puigdefabregas | e6c1ede93c | ||
371845971c | |||
b4efcfb171 | |||
ac0d36ea6f | |||
6a3a2b3a2b | |||
850678fbe4 | |||
f43aaf6ac6 | |||
355ed08561 | |||
d0e46aa0b0 | |||
771b216a31 | |||
263eacda99 | |||
8fcd20f609 | |||
15fb5d3739 | |||
d7ff3c2798 | |||
0e0ad400c2 | |||
367d3a7f87 | |||
c90ed58ea0 | |||
45629db102 | |||
1e29f9562d | |||
d0cac9d1d9 | |||
8b4d1f51f6 | |||
Cayo Puigdefabregas | 34ea4bedfc | ||
Cayo Puigdefabregas | fe429e7db6 | ||
Cayo Puigdefabregas | caf2606fd9 | ||
Cayo Puigdefabregas | 73d478f517 | ||
Cayo Puigdefabregas | 0f03171076 | ||
bfdcb33538 | |||
Cayo Puigdefabregas | 271ac83d71 | ||
Cayo Puigdefabregas | f7b2687ca2 | ||
Cayo Puigdefabregas | 1dad22c3d3 | ||
Cayo Puigdefabregas | 7de6d69a6c | ||
Sergio Giménez Antón | fa5b9eec67 | ||
Cayo Puigdefabregas | 7fd42db3e4 | ||
Cayo Puigdefabregas | bed40d3ee0 | ||
Cayo Puigdefabregas | 9553ed6a4c | ||
Sergio Giménez Antón | f3c9297ffd | ||
cb6c7f6fda | |||
Cayo Puigdefabregas | a0276f439e | ||
a4d361ff9b |
|
@ -29,14 +29,17 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#evidences" class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">{% trans 'Evidences' %}</a>
|
<a href="#evidences" class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">{% trans 'Evidences' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% if dpps %}
|
{% if dpps %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="#dpps" class="nav-link" data-bs-toggle="tab" data-bs-target="#dpps">{% trans 'Dpps' %}</a>
|
<a href="#dpps" class="nav-link" data-bs-toggle="tab" data-bs-target="#dpps">{% trans 'Dpps' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'device:device_web' object.id %}" target="_blank">Web</a>
|
<a class="nav-link" href="{% url 'device:device_web' object.id %}" target="_blank">Web</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#environmental_impact" class="nav-link" data-bs-toggle="tab" data-bs-target="#environmental_impact">{% trans 'Environmental impact' %}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -242,23 +245,104 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if dpps %}
|
<div class="tab-pane fade" id="environmental_impact">
|
||||||
<div class="tab-pane fade" id="dpps">
|
<div class="container-fluid py-3">
|
||||||
<h5 class="card-title">{% trans 'List of dpps' %}</h5>
|
<div class="d-flex justify-content-end mb-3">
|
||||||
<div class="list-group col">
|
<a class="btn btn-success">
|
||||||
{% for d in dpps %}
|
<i class="bi bi-file-earmark-pdf"></i>
|
||||||
<div class="list-group-item">
|
{% trans 'Export to PDF' %}
|
||||||
<div class="d-flex w-100 justify-content-between">
|
</a>
|
||||||
<small class="text-muted">{{ d.timestamp }}</small>
|
</div>
|
||||||
<span>{{ d.type }}</span>
|
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100 border-success">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-arrow-down-circle text-success" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-success">Carbon Reduction</h5>
|
||||||
|
<h2 class="mb-2">{{ impact.carbon_saved }}</h2>
|
||||||
|
<p class="card-text text-muted">kg CO₂e saved</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">
|
|
||||||
<a href="{% url 'did:device_web' d.signature %}">{{ d.signature }}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100 border-danger">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-cloud-fill text-danger" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-danger">Carbon Consumed</h5>
|
||||||
|
<h2 class="mb-2">{{ impact.co2_emissions }}</h2>
|
||||||
|
<p class="card-text text-muted">kg CO₂e consumed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100 border-success">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<div class="mb-3">
|
||||||
|
<i class="bi bi-recycle text-success" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-success">Additional Impact Metric</h5>
|
||||||
|
<h2 class="mb-2">85%</h2>
|
||||||
|
<p class="card-text text-muted">whatever</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Impact Details</h5>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row" class="bg-light" style="width: 30%;">Manufacturing Impact Avoided</th>
|
||||||
|
<td>
|
||||||
|
<span class="text-success">{{ impact.carbon_saved }}</span> kg CO₂e
|
||||||
|
<br />
|
||||||
|
<small class="text-muted">Based on average laptop manufacturing emissions</small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<h6>Calculation Method</h6>
|
||||||
|
<small class="text-muted">Based on industry standards X Y and Z</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if dpps %}
|
||||||
|
<div class="tab-pane fade" id="dpps">
|
||||||
|
<h5 class="card-title">{% trans 'List of dpps' %}</h5>
|
||||||
|
<div class="list-group col">
|
||||||
|
{% for d in dpps %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<small class="text-muted">{{ d.timestamp }}</small>
|
||||||
|
<span>{{ d.type }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
<a href="{% url 'did:device_web' d.signature %}">{{ d.signature }}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -9,6 +9,5 @@ urlpatterns = [
|
||||||
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>/annotation/add", views.AddAnnotationView.as_view(), name="add_annotation"),
|
||||||
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")
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,6 +14,7 @@ from evidence.models import Annotation
|
||||||
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
|
||||||
|
from environmental_impact.algorithms.algorithm_factory import FactoryEnvironmentImpactAlgorithm
|
||||||
if settings.DPP:
|
if settings.DPP:
|
||||||
from dpp.models import Proof
|
from dpp.models import Proof
|
||||||
from dpp.api_dlt import PROOF_TYPE
|
from dpp.api_dlt import PROOF_TYPE
|
||||||
|
@ -110,10 +111,16 @@ class DetailsView(DashboardView, TemplateView):
|
||||||
uuid__in=self.object.uuids,
|
uuid__in=self.object.uuids,
|
||||||
type=PROOF_TYPE["IssueDPP"]
|
type=PROOF_TYPE["IssueDPP"]
|
||||||
)
|
)
|
||||||
|
enviromental_impact_algorithm = FactoryEnvironmentImpactAlgorithm.run_environmental_impact_calculation(
|
||||||
|
"dummy_calc"
|
||||||
|
)
|
||||||
|
enviromental_impact = enviromental_impact_algorithm.get_device_environmental_impact(
|
||||||
|
self.object)
|
||||||
context.update({
|
context.update({
|
||||||
'object': self.object,
|
'object': self.object,
|
||||||
'snapshot': self.object.get_last_evidence(),
|
'snapshot': self.object.get_last_evidence(),
|
||||||
'lot_tags': lot_tags,
|
'lot_tags': lot_tags,
|
||||||
|
'impact': enviromental_impact,
|
||||||
'dpps': dpps,
|
'dpps': dpps,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -89,6 +89,7 @@ INSTALLED_APPS = [
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"admin",
|
"admin",
|
||||||
"api",
|
"api",
|
||||||
|
"environmental_impact"
|
||||||
]
|
]
|
||||||
|
|
||||||
DPP = config("DPP", default=False, cast=bool)
|
DPP = config("DPP", default=False, cast=bool)
|
||||||
|
|
0
environmental_impact/__init__.py
Normal file
0
environmental_impact/__init__.py
Normal file
3
environmental_impact/admin.py
Normal file
3
environmental_impact/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
0
environmental_impact/algorithms/__init__.py
Normal file
0
environmental_impact/algorithms/__init__.py
Normal file
30
environmental_impact/algorithms/algorithm_factory.py
Normal file
30
environmental_impact/algorithms/algorithm_factory.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from .dummy_calculator import DummyEnvironmentalImpactAlgorithm
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .algorithm_interface import EnvironmentImpactAlgorithm
|
||||||
|
|
||||||
|
|
||||||
|
class AlgorithmNames():
|
||||||
|
"""
|
||||||
|
Enum class for the different types of algorithms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DUMMY_CALC = "dummy_calc"
|
||||||
|
|
||||||
|
algorithm_names = {
|
||||||
|
DUMMY_CALC: DummyEnvironmentalImpactAlgorithm()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryEnvironmentImpactAlgorithm():
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_environmental_impact_calculation(algorithm_name: str) -> EnvironmentImpactAlgorithm:
|
||||||
|
try:
|
||||||
|
return AlgorithmNames.algorithm_names[algorithm_name]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError("Invalid algorithm name. Valid options are: " +
|
||||||
|
", ".join(AlgorithmNames.algorithm_names.keys()))
|
11
environmental_impact/algorithms/algorithm_interface.py
Normal file
11
environmental_impact/algorithms/algorithm_interface.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from functools import lru_cache
|
||||||
|
from device.models import Device
|
||||||
|
from environmental_impact.models import EnvironmentalImpact
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentImpactAlgorithm(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_device_environmental_impact(self, device: Device) -> EnvironmentalImpact:
|
||||||
|
pass
|
33
environmental_impact/algorithms/dummy_calculator.py
Normal file
33
environmental_impact/algorithms/dummy_calculator.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from device.models import Device
|
||||||
|
from .algorithm_interface import EnvironmentImpactAlgorithm
|
||||||
|
from environmental_impact.models import EnvironmentalImpact
|
||||||
|
|
||||||
|
|
||||||
|
class DummyEnvironmentalImpactAlgorithm(EnvironmentImpactAlgorithm):
|
||||||
|
|
||||||
|
def get_device_environmental_impact(self, device: Device) -> EnvironmentalImpact:
|
||||||
|
# TODO Make a constants file / class
|
||||||
|
avg_watts = 40 # Arbitrary laptop average consumption
|
||||||
|
co2_per_kwh = 0.475
|
||||||
|
power_on_hours = self.get_power_on_hours_from(device)
|
||||||
|
energy_kwh = (power_on_hours * avg_watts) / 1000
|
||||||
|
co2_emissions = energy_kwh * co2_per_kwh
|
||||||
|
return EnvironmentalImpact(co2_emissions=co2_emissions)
|
||||||
|
|
||||||
|
def get_power_on_hours_from(self, device: Device) -> int:
|
||||||
|
# TODO how do I check if the device is a legacy workbench? Is there a better way?
|
||||||
|
is_legacy_workbench = False if device.last_evidence.inxi else True
|
||||||
|
if not is_legacy_workbench:
|
||||||
|
storage_components = device.components[9]
|
||||||
|
str_time = storage_components.get('time of used', -1)
|
||||||
|
else:
|
||||||
|
str_time = ""
|
||||||
|
uptime_in_hours = self.convert_str_time_to_hours(str_time, is_legacy_workbench)
|
||||||
|
return uptime_in_hours
|
||||||
|
|
||||||
|
def convert_str_time_to_hours(self, time_str: str, is_legacy_workbench: bool) -> int:
|
||||||
|
if is_legacy_workbench:
|
||||||
|
return -1 # TODO Power on hours not available in legacy workbench
|
||||||
|
else:
|
||||||
|
multipliers = {'y': 365 * 24, 'd': 24, 'h': 1}
|
||||||
|
return sum(int(part[:-1]) * multipliers[part[-1]] for part in time_str.split())
|
6
environmental_impact/apps.py
Normal file
6
environmental_impact/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentalImpactConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "environmental_impact"
|
0
environmental_impact/migrations/__init__.py
Normal file
0
environmental_impact/migrations/__init__.py
Normal file
8
environmental_impact/models.py
Normal file
8
environmental_impact/models.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EnvironmentalImpact:
|
||||||
|
carbon_saved: float = 0.0
|
||||||
|
co2_emissions: float = 0.0
|
0
environmental_impact/tests/__init__.py
Normal file
0
environmental_impact/tests/__init__.py
Normal file
44
environmental_impact/tests/test_dummy_calculator.py
Normal file
44
environmental_impact/tests/test_dummy_calculator.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
from unittest.mock import patch
|
||||||
|
import uuid
|
||||||
|
from django.test import TestCase
|
||||||
|
from device.models import Device
|
||||||
|
from environmental_impact.models import EnvironmentalImpact
|
||||||
|
from environmental_impact.algorithms.dummy_calculator import DummyEnvironmentalImpactAlgorithm
|
||||||
|
from evidence.models import Evidence
|
||||||
|
|
||||||
|
|
||||||
|
class DummyEnvironmentalImpactAlgorithmTests(TestCase):
|
||||||
|
|
||||||
|
@patch('evidence.models.Evidence.get_doc', return_value={'credentialSubject': {}})
|
||||||
|
@patch('evidence.models.Evidence.get_time', return_value=None)
|
||||||
|
def setUp(self, mock_get_time, mock_get_doc):
|
||||||
|
self.device = Device(id='1')
|
||||||
|
evidence = self.device.last_evidence = Evidence(uuid=uuid.uuid4())
|
||||||
|
evidence.inxi = True
|
||||||
|
evidence.doc = {'credentialSubject': {}}
|
||||||
|
self.algorithm = DummyEnvironmentalImpactAlgorithm()
|
||||||
|
|
||||||
|
def test_get_power_on_hours_from_legacy_device(self):
|
||||||
|
# TODO is there a way to check that?
|
||||||
|
pass
|
||||||
|
|
||||||
|
@patch('evidence.models.Evidence.get_components', return_value=[0, 0, 0, 0, 0, 0, 0, 0, 0, {'time of used': '1y 2d 3h'}])
|
||||||
|
def test_get_power_on_hours_from_inxi_device(self, mock_get_components):
|
||||||
|
hours = self.algorithm.get_power_on_hours_from(self.device)
|
||||||
|
self.assertEqual(
|
||||||
|
hours, 8811, "Inxi-parsed devices should correctly compute power-on hours")
|
||||||
|
|
||||||
|
@patch('evidence.models.Evidence.get_components', return_value=[0, 0, 0, 0, 0, 0, 0, 0, 0, {'time of used': '1y 2d 3h'}])
|
||||||
|
def test_convert_str_time_to_hours(self, mock_get_components):
|
||||||
|
result = self.algorithm.convert_str_time_to_hours('1y 2d 3h', False)
|
||||||
|
self.assertEqual(
|
||||||
|
result, 8811, "String to hours conversion should match expected output")
|
||||||
|
|
||||||
|
@patch('evidence.models.Evidence.get_components', return_value=[0, 0, 0, 0, 0, 0, 0, 0, 0, {'time of used': '1y 2d 3h'}])
|
||||||
|
def test_environmental_impact_calculation(self, mock_get_components):
|
||||||
|
impact = self.algorithm.get_device_environmental_impact(self.device)
|
||||||
|
self.assertIsInstance(impact, EnvironmentalImpact,
|
||||||
|
"Output should be an EnvironmentalImpact instance")
|
||||||
|
expected_co2 = 8811 * 40 * 0.475 / 1000
|
||||||
|
self.assertAlmostEqual(impact.co2_emissions, expected_co2,
|
||||||
|
2, "CO2 emissions calculation should be accurate")
|
|
@ -0,0 +1,17 @@
|
||||||
|
from environmental_impact.algorithms.algorithm_factory import FactoryEnvironmentImpactAlgorithm
|
||||||
|
from django.test import TestCase
|
||||||
|
from environmental_impact.algorithms.dummy_calculator import DummyEnvironmentalImpactAlgorithm
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryEnvironmentImpactAlgorithmTests(TestCase):
|
||||||
|
|
||||||
|
def test_valid_algorithm_name(self):
|
||||||
|
algorithm = FactoryEnvironmentImpactAlgorithm.run_environmental_impact_calculation(
|
||||||
|
'dummy_calc')
|
||||||
|
self.assertIsInstance(algorithm, DummyEnvironmentalImpactAlgorithm,
|
||||||
|
"Factory should return a DummyEnvironmentalImpactAlgorithm instance")
|
||||||
|
|
||||||
|
def test_invalid_algorithm_name(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
FactoryEnvironmentImpactAlgorithm.run_environmental_impact_calculation(
|
||||||
|
'invalid_calc')
|
3
environmental_impact/views.py
Normal file
3
environmental_impact/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Reference in a new issue