feat(assets): allow editing general note field on asset details

This commit is contained in:
kdeterme 2026-10-09 23:22:09 +02:00
parent 15403b8604
commit e51f698030
5 changed files with 39 additions and 8 deletions

View file

@ -16,32 +16,32 @@ ASSET_EDITABLE_FIELDS_BY_ROLE = {
'admin': [ 'admin': [
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category', 'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
'serial_number', 'installation_date', 'brand', 'warranty_duration', 'serial_number', 'installation_date', 'brand', 'warranty_duration',
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat', 'last_inspection_date', 'funding_program', 'note', 'geom', 'lon', 'lat',
'location_id', 'intersection_id', 'location_id', 'intersection_id',
], ],
'manager': [ 'manager': [
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category', 'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
'serial_number', 'installation_date', 'brand', 'warranty_duration', 'serial_number', 'installation_date', 'brand', 'warranty_duration',
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat', 'last_inspection_date', 'funding_program', 'note', 'geom', 'lon', 'lat',
'location_id', 'intersection_id', 'location_id', 'intersection_id',
], ],
'controller': [ 'controller': [
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category', 'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
'serial_number', 'installation_date', 'brand', 'warranty_duration', 'serial_number', 'installation_date', 'brand', 'warranty_duration',
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat', 'last_inspection_date', 'funding_program', 'note', 'geom', 'lon', 'lat',
'location_id', 'intersection_id', 'location_id', 'intersection_id',
], ],
'external_manager': [ 'external_manager': [
'name_fr', 'name_nl', 'external_reference', 'category', 'brand', 'serial_number', 'funding_program' 'name_fr', 'name_nl', 'external_reference', 'category', 'brand', 'serial_number', 'funding_program', 'note',
], ],
'technician': ['name_fr', 'name_nl', 'external_reference', 'category', 'brand', 'serial_number', 'funding_program'], 'technician': ['name_fr', 'name_nl', 'external_reference', 'category', 'brand', 'serial_number', 'funding_program', 'note'],
'operator': [], 'operator': [],
'observer': [], 'observer': [],
'viewer': [], 'viewer': [],
'editor': [ 'editor': [
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category', 'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
'serial_number', 'installation_date', 'brand', 'warranty_duration', 'serial_number', 'installation_date', 'brand', 'warranty_duration',
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat', 'last_inspection_date', 'funding_program', 'note', 'geom', 'lon', 'lat',
'location_id', 'intersection_id', 'location_id', 'intersection_id',
], ],
} }
@ -588,6 +588,10 @@ def get_allowed_update_fields_for_asset(user, asset):
role_fields = ASSET_EDITABLE_FIELDS_BY_ROLE.get(role.name, []) role_fields = ASSET_EDITABLE_FIELDS_BY_ROLE.get(role.name, [])
allowed_fields.update(role_fields) allowed_fields.update(role_fields)
# Superuser fallback if no explicit roles configured
if getattr(user, 'is_superuser', False) and not allowed_fields:
allowed_fields.update(ASSET_EDITABLE_FIELDS_BY_ROLE.get('admin', []))
# Check validation permission # Check validation permission
if can_validate_asset(user, asset): if can_validate_asset(user, asset):
allowed_fields.add('validation_status') allowed_fields.add('validation_status')

View file

@ -534,6 +534,19 @@
</div> </div>
</td> </td>
</tr> </tr>
<!-- Note générale -->
<tr class="asset-editable-field" data-field="note" data-can-edit="{% if 'note' in allowed_update_fields %}true{% else %}false{% endif %}">
<th>{% translate "Note générale" %}</th>
<td>
<div class="field-display">
<span class="field-value text-muted">{{ asset.note|default:"-"|linebreaksbr }}</span>
</div>
<div class="field-edit d-none">
<textarea class="form-control form-control-sm" rows="3">{{ asset.note|default:'' }}</textarea>
</div>
</td>
</tr>
<!-- Champs spécifiques au type d'asset --> <!-- Champs spécifiques au type d'asset -->
{% get_asset_specific_fields asset as asset_specific_fields %} {% get_asset_specific_fields asset as asset_specific_fields %}
{% if asset_specific_fields %} {% if asset_specific_fields %}

View file

@ -490,7 +490,7 @@
<span class="field-value text-muted">{{ asset.note|default:"-"|linebreaksbr }}</span> <span class="field-value text-muted">{{ asset.note|default:"-"|linebreaksbr }}</span>
</div> </div>
<div class="field-edit d-none"> <div class="field-edit d-none">
<textarea class="form-control form-control-sm" rows="2">{{ asset.note|default:'' }}</textarea> <textarea class="form-control form-control-sm" rows="3">{{ asset.note|default:'' }}</textarea>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -3516,6 +3516,16 @@ class AssetValidationPermissionTest(TestCase):
fields = get_allowed_update_fields_for_asset(self.user, self.asset) fields = get_allowed_update_fields_for_asset(self.user, self.asset)
self.assertIn("validation_status", fields) self.assertIn("validation_status", fields)
def test_note_in_allowed_update_fields(self):
from assets.permissions import get_allowed_update_fields_for_asset
from common.models import Role
editor_role, _ = Role.objects.get_or_create(name="editor")
self.user_config.roles.add(editor_role)
fields = get_allowed_update_fields_for_asset(self.user, self.asset)
self.assertIn("note", fields)
def test_bulk_edit_assets(self): def test_bulk_edit_assets(self):
import json import json
from django.urls import reverse from django.urls import reverse

View file

@ -178,6 +178,8 @@ class MobileAssetDetailTests(TestCase):
self.assertContains(response, 'id="asset-cms-edit-btn"') self.assertContains(response, 'id="asset-cms-edit-btn"')
self.assertContains(response, 'id="asset-cms-save-btn"') self.assertContains(response, 'id="asset-cms-save-btn"')
self.assertContains(response, 'id="asset-cms-cancel-btn"') self.assertContains(response, 'id="asset-cms-cancel-btn"')
self.assertIn('note', response.context['allowed_update_fields'])
self.assertContains(response, 'data-field="note" data-can-edit="true"')
def test_tree_mobile_specific_elements(self): def test_tree_mobile_specific_elements(self):
"""NatureTree specific mobile design: dendrometric widgets, icons and input attributes.""" """NatureTree specific mobile design: dendrometric widgets, icons and input attributes."""
@ -224,7 +226,8 @@ class MobileAssetDetailTests(TestCase):
"height": 18.2, "height": 18.2,
"circumference": 175.0, "circumference": 175.0,
"vitality": "strong", "vitality": "strong",
"tag_number": "TAG-99999" "tag_number": "TAG-99999",
"note": "Arbre sain, élagage prévu à l'automne."
} }
response = self.client.post( response = self.client.post(
@ -243,6 +246,7 @@ class MobileAssetDetailTests(TestCase):
self.assertEqual(self.tree.circumference, 175.0) self.assertEqual(self.tree.circumference, 175.0)
self.assertEqual(self.tree.vitality, "strong") self.assertEqual(self.tree.vitality, "strong")
self.assertEqual(self.tree.tag_number, "TAG-99999") self.assertEqual(self.tree.tag_number, "TAG-99999")
self.assertEqual(self.tree.note, "Arbre sain, élagage prévu à l'automne.")
def test_mobile_index_shows_assets_card_when_authorized(self): def test_mobile_index_shows_assets_card_when_authorized(self):
"""User with asset view rights sees 'Assets' card on mobile home.""" """User with asset view rights sees 'Assets' card on mobile home."""