From 069ed3d758eaa190781d386e6f261e0b305ef157 Mon Sep 17 00:00:00 2001
From: Thomas NOEL <tnoel@entrouvert.com>
Date: Fri, 15 Oct 2021 12:18:06 +0000
Subject: [PATCH] sector: add a sector_name column in CSV, for readability
 (#57021)

---
 .../apps/sector/migrations/0001_initial.py    |  8 ++-
 .../0002_sectorization_street_name.py         | 18 +++++
 passerelle/apps/sector/models.py              | 19 ++++-
 tests/test_address.py                         | 10 +--
 tests/test_import_export.py                   |  4 +-
 tests/test_sector.py                          | 69 +++++++++++--------
 6 files changed, 88 insertions(+), 40 deletions(-)
 create mode 100644 passerelle/apps/sector/migrations/0002_sectorization_street_name.py

diff --git a/passerelle/apps/sector/migrations/0001_initial.py b/passerelle/apps/sector/migrations/0001_initial.py
index 63ee4065..f2750fff 100644
--- a/passerelle/apps/sector/migrations/0001_initial.py
+++ b/passerelle/apps/sector/migrations/0001_initial.py
@@ -51,7 +51,7 @@ class Migration(migrations.Migration):
                     'titles_in_first_line',
                     models.BooleanField(
                         default=True,
-                        help_text='If not, column titles are: street_id,parity,min_housenumber,max_housenumber,sector_id,sector_name, …',
+                        help_text='If not, column titles are: street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name, …',
                         verbose_name='First line defines column titles',
                     ),
                 ),
@@ -87,11 +87,13 @@ class Migration(migrations.Migration):
                 ),
                 (
                     'min_housenumber',
-                    models.PositiveIntegerField(default=0, verbose_name='Minimal house number'),
+                    models.PositiveIntegerField(default=0, verbose_name='Minimum house number (included)'),
                 ),
                 (
                     'max_housenumber',
-                    models.PositiveIntegerField(default=999999, verbose_name='Maximal house number'),
+                    models.PositiveIntegerField(
+                        default=999999, verbose_name='Maximum house number (included)'
+                    ),
                 ),
                 (
                     'sector',
diff --git a/passerelle/apps/sector/migrations/0002_sectorization_street_name.py b/passerelle/apps/sector/migrations/0002_sectorization_street_name.py
new file mode 100644
index 00000000..2f19baa9
--- /dev/null
+++ b/passerelle/apps/sector/migrations/0002_sectorization_street_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.19 on 2021-10-15 11:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sector', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='sectorization',
+            name='street_name',
+            field=models.CharField(blank=True, max_length=150, verbose_name='Street Name'),
+        ),
+    ]
diff --git a/passerelle/apps/sector/models.py b/passerelle/apps/sector/models.py
index f12feb59..cb18e763 100644
--- a/passerelle/apps/sector/models.py
+++ b/passerelle/apps/sector/models.py
@@ -43,7 +43,15 @@ PARITY_CHOICES = (
     (PARITY_EVEN, _('even')),
 )
 
-CSV_TITLES = ['street_id', 'parity', 'min_housenumber', 'max_housenumber', 'sector_id', 'sector_name']
+CSV_TITLES = [
+    'street_id',
+    'street_name',
+    'parity',
+    'min_housenumber',
+    'max_housenumber',
+    'sector_id',
+    'sector_name',
+]
 
 MAX_HOUSENUMBER = 999_999
 
@@ -194,6 +202,7 @@ class SectorResource(BaseResource):
                     parity=parity,
                     min_housenumber=min_housenumber,
                     max_housenumber=max_housenumber,
+                    street_name=row['street_name'],
                 )
 
     @endpoint(
@@ -267,6 +276,7 @@ class SectorResource(BaseResource):
                 writer.writerow(
                     [
                         sectorization.street_id,
+                        sectorization.street_name,
                         parity.get(sectorization.parity, mix),
                         sectorization.min_housenumber if (limits or sectorization.min_housenumber) else '',
                         sectorization.max_housenumber
@@ -351,6 +361,7 @@ class Sector(models.Model):
 class Sectorization(models.Model):
     sector = models.ForeignKey(Sector, on_delete=models.CASCADE, verbose_name=_('Sector'))
     street_id = models.CharField(max_length=64, verbose_name=_('Street Identifier'))
+    street_name = models.CharField(max_length=150, verbose_name=_('Street Name'), blank=True)
     parity = models.SmallIntegerField(
         choices=PARITY_CHOICES, default=PARITY_ALL, verbose_name=_('Parity of numbers')
     )
@@ -365,8 +376,12 @@ class Sectorization(models.Model):
         ordering = ['street_id', 'min_housenumber', 'parity']
 
     def __str__(self):
+        if self.street_name:
+            street = '%s (%s)' % (self.street_id, self.street_name)
+        else:
+            street = self.street_id
         return '%s, parity:%s, min:%s, max:%s → %s' % (
-            self.street_id,
+            street,
             dict(PARITY_CHOICES).get(self.parity),
             self.min_housenumber,
             self.max_housenumber,
diff --git a/tests/test_address.py b/tests/test_address.py
index 4f5de827..1c483ef4 100644
--- a/tests/test_address.py
+++ b/tests/test_address.py
@@ -206,11 +206,11 @@ BAN = {
     "version": "draft",
 }
 
-CSV = """street_id,parity,min_housenumber,max_housenumber,sector_id,sector_name
-75114_1912,P,,,gs-moulin,Groupe Scolaire Moulin
-75114_1912,I,0,167,gs-zola,Groupe Scolaire Zola
-75114_1912,I,168,999999,ecole-hugo,École Hugo
-75114_1913,,,,ecole-pascal,École Pascal
+CSV = """street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name
+75114_1912,rue du Château,P,,,gs-moulin,Groupe Scolaire Moulin
+75114_1912,rue du Château,I,0,167,gs-zola,Groupe Scolaire Zola
+75114_1912,rue du Château,I,168,999999,ecole-hugo,École Hugo
+75114_1913,rue 1913,,,,ecole-pascal,École Pascal
 """
 
 
diff --git a/tests/test_import_export.py b/tests/test_import_export.py
index 17fcd505..dde47cfe 100644
--- a/tests/test_import_export.py
+++ b/tests/test_import_export.py
@@ -47,8 +47,8 @@ data = """121;69981;DELANOUE;Eliot;H
 
 data_bom = force_text(data, 'utf-8').encode('utf-8-sig')
 
-SECTOR_CSV = """street_id,parity,min_housenumber,max_housenumber,sector_id,sector_name
-38 75114_1912,P,,, gs-moulin, Groupe Scolaire Moulin"""
+SECTOR_CSV = """street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name
+75114_1912,rue du Château,P,,, gs-moulin, Groupe Scolaire Moulin"""
 
 pytestmark = pytest.mark.django_db
 
diff --git a/tests/test_sector.py b/tests/test_sector.py
index 4cc2cabf..4e6583e5 100644
--- a/tests/test_sector.py
+++ b/tests/test_sector.py
@@ -34,43 +34,43 @@ from test_manager import login
 from passerelle.apps.sector.models import Sectorization, SectorResource
 from passerelle.base.models import AccessRight
 
-CSV = """street_id,parity,min_housenumber,max_housenumber,sector_id,sector_name
-75114_1912,P,,, gs-moulin, Groupe Scolaire Moulin
-75114_1912,I,0,999999,gs-zola,Groupe Scolaire Zola
-75114_1913,N,0,999999,ecole-hugo,École Hugo
-75114_1914,,,10,ecole-hugo, École Hugo
-
-75114_1914,,11,, ecole-hugo2, École Hugo 2
-75114_1915,,,,ecole-hugo2 , École Hugo 2
+CSV = """street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name
+75114_1912,rue du Château,P,,, gs-moulin, Groupe Scolaire Moulin
+75114_1912,rue du Château,I,0,999999,gs-zola,Groupe Scolaire Zola
+75114_1913,rue des Moulins ,N,0,999999,ecole-hugo,École Hugo
+75114_1914,rue du Vent,,,10,ecole-hugo, École Hugo
+
+75114_1914,rue du Vent,,11,, ecole-hugo2, École Hugo 2
+75114_1915,  ,,,,ecole-hugo2 , École Hugo 2
 """
 
 CSV_BOM = force_str(force_text(CSV, 'utf-8').encode('utf-8-sig'))
 
-CSV_NO_FIRST_LINE = """75114_1912,P,,, gs-moulin, Groupe Scolaire Moulin
-75114_1912,I,0,999999,gs-zola,Groupe Scolaire Zola
-75114_1913,N,0,999999,ecole-hugo,École Hugo
-75114_1914,,,10,ecole-hugo, École Hugo
+CSV_NO_FIRST_LINE = """75114_1912,rue du Château,P,,, gs-moulin, Groupe Scolaire Moulin
+75114_1912,rue du Château,I,0,999999,gs-zola,Groupe Scolaire Zola
+75114_1913,,N,0,999999,ecole-hugo,École Hugo
+75114_1914,rue du Vent,,,10,ecole-hugo, École Hugo
 
-75114_1914,,11,, ecole-hugo, École Hugo"""
+75114_1914,rue du Vent,,11,, ecole-hugo, École Hugo"""
 
-CSV_REORDERED = """sector_id,sector_name,street_id,parity,min_housenumber,max_housenumber,foo,bar
-gs-moulin, Groupe Scolaire Moulin, 75114_1912,P,,,aaa,bbb
-gs-zola,Groupe Scolaire Zola,75114_1912,I,0,999999,xxx,yyy
-ecole-hugo,École Hugo,75114_1913,N,0,999999,000,1
-,,75114_1999,N,0,999999,,
+CSV_REORDERED = """sector_id,sector_name,street_id,parity,min_housenumber,max_housenumber,foo,street_name,bar
+gs-moulin, Groupe Scolaire Moulin, 75114_1912,P,,,aaa,rue du Château,bbb
+gs-zola,Groupe Scolaire Zola,75114_1912,I,0,999999,xxx,rue du Château,yyy
+ecole-hugo,École Hugo,75114_1913,N,0,999999,000,rue des Moulins,1
+,,75114_1999,N,0,999999,,avenue 999,
 """
 
-CSV_MISSING_COLUMN = """street_id,min_housenumber,max_housenumber,sector_id,sector_name
-75114_1912,,,foo,
-,0,999999,gs-zola,Groupe Scolaire Zola"""
+CSV_MISSING_COLUMN = """street_id,street_name,min_housenumber,max_housenumber,sector_id,sector_name
+75114_1912,,,,foo,
+,,0,999999,gs-zola,Groupe Scolaire Zola"""
 
-CSV_MISSING_SECTOR = """street_id,parity,min_housenumber,max_housenumber,sector_id,sector_name
-75114_1912,P,,, ,
-75114_1912,I,0,999999,gs-zola,Groupe Scolaire Zola"""
+CSV_MISSING_SECTOR = """street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name
+75114_1912,rue du Château,P,,, ,
+75114_1912,rue du Château,I,0,999999,gs-zola,Groupe Scolaire Zola"""
 
-CSV_MISSING_STREET = """street_id,parity,min_housenumber,max_housenumber,sector_id,sector_name
-75114_1912,P,,,foo,
-,I,0,999999,gs-zola,Groupe Scolaire Zola"""
+CSV_MISSING_STREET = """street_id,street_name,parity,min_housenumber,max_housenumber,sector_id,sector_name
+75114_1912,,P,,,foo,
+,,I,0,999999,gs-zola,Groupe Scolaire Zola"""
 
 pytestmark = pytest.mark.django_db
 
@@ -89,6 +89,19 @@ def sector(db):
 def test_sector_creation(sector):
     assert '%s' % sector == 'title [test]'
     assert sector.sector_set.count() == 4
+    rue1913 = Sectorization.objects.get(street_id='75114_1913')
+    assert rue1913.street_name == 'rue des Moulins'
+    assert (
+        '%s' % rue1913
+        == '75114_1913 (rue des Moulins), parity:all, min:0, max:999999 → title [test] > École Hugo [ecole-hugo]'
+    )
+    rue1915 = Sectorization.objects.get(street_id='75114_1915')
+    assert rue1915.street_name == ''
+    assert (
+        '%s' % rue1915
+        == '75114_1915, parity:all, min:0, max:999999 → title [test] > École Hugo 2 [ecole-hugo2]'
+    )
+
     hugo = sector.sector_set.get(slug='ecole-hugo')
     assert Sectorization.objects.filter(sector=hugo).count() == 2
     hugo2 = sector.sector_set.get(slug='gs-zola')
@@ -242,7 +255,7 @@ def test_sector_endpoint_export(app, sector):
     resp = app.get(url)
     assert resp.headers['content-type'] == 'text/csv'
     assert resp.text.startswith(
-        '"street_id","parity","min_housenumber","max_housenumber","sector_id","sector_name"'
+        '"street_id","street_name","parity","min_housenumber","max_housenumber","sector_id","sector_name"'
     )
     assert len(resp.text.splitlines()) == 7
     sector.titles_in_first_line = False
-- 
2.33.0

