diff --git a/lemarche/api/siaes/serializers.py b/lemarche/api/siaes/serializers.py index 5bad83dc6..3c7bd4951 100644 --- a/lemarche/api/siaes/serializers.py +++ b/lemarche/api/siaes/serializers.py @@ -2,7 +2,7 @@ from lemarche.api.networks.serializers import NetworkSimpleSerializer from lemarche.api.sectors.serializers import SectorSimpleSerializer -from lemarche.siaes.models import Siae, SiaeClientReference, SiaeLabel, SiaeOffer +from lemarche.siaes.models import Siae, SiaeClientReference, SiaeLabelOld, SiaeOffer class SiaeOfferSimpleSerializer(serializers.ModelSerializer): @@ -24,9 +24,9 @@ class Meta: ] -class SiaeLabelSimpleSerializer(serializers.ModelSerializer): +class SiaeLabelOldSimpleSerializer(serializers.ModelSerializer): class Meta: - model = SiaeLabel + model = SiaeLabelOld fields = [ "name", ] @@ -37,7 +37,7 @@ class SiaeDetailSerializer(serializers.ModelSerializer): networks = NetworkSimpleSerializer(many=True) offers = SiaeOfferSimpleSerializer(many=True) client_references = SiaeClientReferenceSimpleSerializer(many=True) - labels = SiaeLabelSimpleSerializer(many=True) + labels_old = SiaeLabelOldSimpleSerializer(many=True) class Meta: model = Siae @@ -67,7 +67,7 @@ class Meta: "networks", "offers", "client_references", - "labels", + "labels_old", # "images", "created_at", "updated_at", diff --git a/lemarche/api/siaes/tests.py b/lemarche/api/siaes/tests.py index 820b9e97f..75c99a6f7 100644 --- a/lemarche/api/siaes/tests.py +++ b/lemarche/api/siaes/tests.py @@ -202,7 +202,7 @@ def test_should_return_detailed_siae_object_to_authenticated_users(self): self.assertTrue("networks" in response.data) self.assertTrue("offers" in response.data) self.assertTrue("client_references" in response.data) - self.assertTrue("labels" in response.data) + self.assertTrue("labels_old" in response.data) class SiaeRetrieveBySlugApiTest(TestCase): diff --git a/lemarche/cocorico/management/commands/migrate_data_to_django.py b/lemarche/cocorico/management/commands/migrate_data_to_django.py index 7a8891615..bda2aaa3a 100644 --- a/lemarche/cocorico/management/commands/migrate_data_to_django.py +++ b/lemarche/cocorico/management/commands/migrate_data_to_django.py @@ -11,7 +11,7 @@ from lemarche.networks.models import Network from lemarche.sectors.models import Sector, SectorGroup from lemarche.siaes import constants as siae_constants -from lemarche.siaes.models import Siae, SiaeClientReference, SiaeImage, SiaeLabel, SiaeOffer +from lemarche.siaes.models import Siae, SiaeClientReference, SiaeImage, SiaeLabelOld, SiaeOffer from lemarche.users.models import User from lemarche.utils.data import rename_dict_key, reset_app_sql_sequences @@ -485,12 +485,12 @@ def migrate_siae_offer(self, cur): def migrate_siae_label(self, cur): """ - Migrate SiaeLabel data + Migrate SiaeLabelOld data """ print("-" * 80) - print("Migrating SiaeLabel...") + print("Migrating SiaeLabelOld...") - SiaeLabel.objects.all().delete() + SiaeLabelOld.objects.all().delete() cur.execute("SELECT * FROM directory_label") resp = cur.fetchall() @@ -507,9 +507,9 @@ def migrate_siae_label(self, cur): [elem.pop(key) for key in ["id"]] # create object - SiaeLabel.objects.create(**elem) + SiaeLabelOld.objects.create(**elem) - print(f"Created {SiaeLabel.objects.count()} labels !") + print(f"Created {SiaeLabelOld.objects.count()} labels !") def migrate_siae_client_reference_logo(self, cur): """ diff --git a/lemarche/fixtures/django/09_siaelabels.json b/lemarche/fixtures/django/09_siaelabels_old.json similarity index 70% rename from lemarche/fixtures/django/09_siaelabels.json rename to lemarche/fixtures/django/09_siaelabels_old.json index 113b14706..39b891406 100644 --- a/lemarche/fixtures/django/09_siaelabels.json +++ b/lemarche/fixtures/django/09_siaelabels_old.json @@ -1,99 +1,99 @@ [ { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 1, "fields": { - "name": "Gestion de projet", + "name": "ISO 9001", "siae": 2653, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 2, "fields": { - "name": "Recyclage économie circulaire", + "name": "ISO 9001", "siae": 88, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 3, "fields": { - "name": "Agriculture urbaine", + "name": "ISO 9001", "siae": 90, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 4, "fields": { - "name": "Restauration", + "name": "ISO 9001", "siae": 3850, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 5, "fields": { - "name": "Nettoyage de locaux", + "name": "BIO", "siae": 3850, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 6, "fields": { - "name": "Entretien des espaces verts", + "name": "HACCP", "siae": 3853, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 7, "fields": { - "name": "Numérisation", + "name": "ISO 14001", "siae": 3853, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 8, "fields": { - "name": "Sous traitance industrielle", + "name": "ESUS", "siae": 3853, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 9, "fields": { - "name": "Manutention", + "name": "ISO 14001", "siae": 3854, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaeoffer", + "model": "siaes.siaelabelold", "pk": 10, "fields": { - "name": "Services logistiques", + "name": "ISO 14001", "siae": 3852, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" diff --git a/lemarche/fixtures/django/09_siaeoffers.json b/lemarche/fixtures/django/09_siaeoffers.json index 8aa974c1a..113b14706 100644 --- a/lemarche/fixtures/django/09_siaeoffers.json +++ b/lemarche/fixtures/django/09_siaeoffers.json @@ -1,99 +1,99 @@ [ { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 1, "fields": { - "name": "ISO 9001", + "name": "Gestion de projet", "siae": 2653, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 2, "fields": { - "name": "ISO 9001", + "name": "Recyclage économie circulaire", "siae": 88, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 3, "fields": { - "name": "ISO 9001", + "name": "Agriculture urbaine", "siae": 90, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 4, "fields": { - "name": "ISO 9001", + "name": "Restauration", "siae": 3850, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 5, "fields": { - "name": "BIO", + "name": "Nettoyage de locaux", "siae": 3850, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 6, "fields": { - "name": "HACCP", + "name": "Entretien des espaces verts", "siae": 3853, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 7, "fields": { - "name": "ISO 14001", + "name": "Numérisation", "siae": 3853, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 8, "fields": { - "name": "ESUS", + "name": "Sous traitance industrielle", "siae": 3853, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 9, "fields": { - "name": "ISO 14001", + "name": "Manutention", "siae": 3854, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" } }, { - "model": "siaes.siaelabel", + "model": "siaes.siaeoffer", "pk": 10, "fields": { - "name": "ISO 14001", + "name": "Services logistiques", "siae": 3852, "created_at": "2021-10-28T12:28:42.408Z", "updated_at": "2021-10-28T12:28:42.416Z" diff --git a/lemarche/fixtures/tests.py b/lemarche/fixtures/tests.py index 52fec5e1b..e6b2055d6 100644 --- a/lemarche/fixtures/tests.py +++ b/lemarche/fixtures/tests.py @@ -2,7 +2,7 @@ from lemarche.networks.models import Network from lemarche.sectors.models import Sector, SectorGroup -from lemarche.siaes.models import Siae, SiaeClientReference, SiaeGroup, SiaeLabel, SiaeOffer, SiaeUser +from lemarche.siaes.models import Siae, SiaeClientReference, SiaeGroup, SiaeLabelOld, SiaeOffer, SiaeUser from lemarche.tenders.models import Tender, TenderQuestion, TenderSiae from lemarche.users.models import User @@ -20,7 +20,7 @@ class FixturesTest(TestCase): "lemarche/fixtures/django/06_siaegroup_sectors.json", "lemarche/fixtures/django/08_siae_networks.json", "lemarche/fixtures/django/09_siaeclientreferences.json", - "lemarche/fixtures/django/09_siaelabels.json", + "lemarche/fixtures/django/09_siaelabels_old.json", "lemarche/fixtures/django/09_siaeoffers.json", "lemarche/fixtures/django/10_tenders.json", "lemarche/fixtures/django/11_tender_questions.json", @@ -42,7 +42,7 @@ def test_flat_fixtures_load_successfully(self): self.assertTrue(len(User.objects.all()) > 0) self.assertTrue(len(SiaeUser.objects.all()) > 0) self.assertTrue(len(SiaeClientReference.objects.all()) > 0) - self.assertTrue(len(SiaeLabel.objects.all()) > 0) + self.assertTrue(len(SiaeLabelOld.objects.all()) > 0) self.assertTrue(len(SiaeOffer.objects.all()) > 0) self.assertTrue(len(Tender.objects.all()) > 0) self.assertTrue(len(TenderQuestion.objects.all()) > 0) diff --git a/lemarche/labels/admin.py b/lemarche/labels/admin.py index 9e35c5cd0..fbf9f090b 100644 --- a/lemarche/labels/admin.py +++ b/lemarche/labels/admin.py @@ -1,5 +1,7 @@ from django.contrib import admin -from django.utils.html import mark_safe +from django.db.models import Count +from django.urls import reverse +from django.utils.html import format_html, mark_safe from lemarche.labels.models import Label from lemarche.utils.admin.admin_site import admin_site @@ -7,11 +9,28 @@ @admin.register(Label, site=admin_site) class LabelAdmin(admin.ModelAdmin): - list_display = ["id", "name", "created_at"] + list_display = ["id", "name", "nb_siaes", "created_at"] search_fields = ["id", "name", "description"] search_help_text = "Cherche sur les champs : ID, Nom, Description" - readonly_fields = ["logo_url", "logo_url_display", "created_at", "updated_at"] + readonly_fields = ["nb_siaes", "logo_url", "logo_url_display", "created_at", "updated_at"] + + fieldsets = ( + ( + None, + { + "fields": ("name", "slug", "description", "website"), + }, + ), + ("Structures", {"fields": ("nb_siaes",)}), + ("Logo", {"fields": ("logo_url", "logo_url_display")}), + ("Dates", {"fields": ("created_at", "updated_at")}), + ) + + def get_queryset(self, request): + qs = super().get_queryset(request) + qs = qs.annotate(siae_count=Count("siaes", distinct=True)) + return qs def get_readonly_fields(self, request, obj=None): # slug cannot be changed after creation @@ -25,6 +44,13 @@ def get_prepopulated_fields(self, request, obj=None): return {"slug": ("name",)} return {} + def nb_siaes(self, label): + url = reverse("admin:siaes_siae_changelist") + f"?labels__id__exact={label.id}" + return format_html(f'{label.siae_count}') + + nb_siaes.short_description = "Nombre de structures" + nb_siaes.admin_order_field = "siae_count" + def logo_url_display(self, instance): if instance.image_url: return mark_safe( diff --git a/lemarche/labels/management/commands/api_agence_bio.py b/lemarche/labels/management/commands/api_agence_bio.py new file mode 100644 index 000000000..87065a96a --- /dev/null +++ b/lemarche/labels/management/commands/api_agence_bio.py @@ -0,0 +1,65 @@ +import time + +import requests + +from lemarche.labels.models import Label +from lemarche.siaes.models import Siae, SiaeLabel +from lemarche.utils.apis import api_slack +from lemarche.utils.commands import BaseCommand + + +API_AGENCE_BIO_ENDPOINT = "https://opendata.agencebio.org/api/gouv/operateurs/" + + +class Command(BaseCommand): + """ + https://api.gouv.fr/les-api/api-professionnels-bio + - limite de l'API : "50 appels / seconde / IP" + + Usage: + python manage.py api_agence_bio --dry-run + python manage.py api_agence_bio + """ + + def add_arguments(self, parser): + parser.add_argument("--dry-run", dest="dry_run", action="store_true", help="Dry run (no changes to the DB)") + + def handle(self, dry_run=False, **options): + self.stdout_info("-" * 80) + self.stdout_info("API Agence Bio") + + label_rge = Label.objects.get(name="Agence Bio") + siaes = Siae.objects.all() + self.stdout_info(f"SIAE count: {siaes.count()}") + + progress = 0 + results = {"success": 0, "error": 0} + + for siae in siaes: + # fetch data + url = f"{API_AGENCE_BIO_ENDPOINT}?siret={siae.siret}" + r = requests.get(url) + r.raise_for_status() + data = r.json() + + # add label to siae + if len(data["items"]): + if not dry_run: + # siae.labels.add(label_rge) + SiaeLabel.objects.create(siae=siae, label=label_rge) + results["success"] += 1 + + progress += 1 + if (progress % 50) == 0: + time.sleep(2) + if (progress % 500) == 0: + print(f"{progress}...") + + msg_success = [ + "----- Recap: API Agence Bio -----", + f"Done! Processed {siaes.count()} siae", + f"success count: {results['success']}/{siaes.count()}", + ] + self.stdout_messages_success(msg_success) + if not dry_run: + api_slack.send_message_to_channel("\n".join(msg_success)) diff --git a/lemarche/siaes/admin.py b/lemarche/siaes/admin.py index fcf766ebe..d0bcd9e94 100644 --- a/lemarche/siaes/admin.py +++ b/lemarche/siaes/admin.py @@ -6,12 +6,14 @@ from django.utils.html import format_html, mark_safe from fieldsets_with_inlines import FieldsetsInlineMixin +from lemarche.labels.models import Label from lemarche.siaes.models import ( Siae, SiaeClientReference, SiaeGroup, SiaeImage, SiaeLabel, + SiaeLabelOld, SiaeOffer, SiaeUser, SiaeUserRequest, @@ -58,6 +60,20 @@ def queryset(self, request, queryset): return queryset +class SiaeLabelInline(admin.TabularInline): + model = SiaeLabel + fields = ["label", "label_with_link", "created_at", "updated_at"] + autocomplete_fields = ["label"] + readonly_fields = ["label_with_link", "created_at", "updated_at"] + extra = 0 + + def label_with_link(self, siae_label): + url = reverse("admin:labels_label_change", args=[siae_label.label_id]) + return format_html(f'{siae_label.label}') + + label_with_link.short_description = Label._meta.verbose_name + + class SiaeUserInline(admin.TabularInline): model = SiaeUser fields = ["user", "user_with_link", "created_at", "updated_at"] @@ -197,6 +213,7 @@ class SiaeAdmin(FieldsetsInlineMixin, gis_admin.OSMGeoAdmin): ) }, ), + SiaeLabelInline, ( "Périmètre d'intervention", { @@ -407,7 +424,7 @@ def offer_count_with_link(self, siae): offer_count_with_link.admin_order_field = "offer_count" def label_count_with_link(self, siae): - url = reverse("admin:siaes_siaelabel_changelist") + f"?siae__id__exact={siae.id}" + url = reverse("admin:siaes_siaelabelold_changelist") + f"?siae__id__exact={siae.id}" return format_html(f'{siae.label_count}') label_count_with_link.short_description = "Nbr de labels" @@ -562,8 +579,8 @@ def siae_with_link(self, siae_offer): siae_with_link.admin_order_field = "siae" -@admin.register(SiaeLabel, site=admin_site) -class SiaeLabelAdmin(admin.ModelAdmin): +@admin.register(SiaeLabelOld, site=admin_site) +class SiaeLabelOldAdmin(admin.ModelAdmin): list_display = ["id", "name", "siae_with_link", "created_at"] search_fields = ["id", "name", "siae__id", "siae__name"] search_help_text = "Cherche sur les champs : ID, Nom, Structure (ID, Nom)" diff --git a/lemarche/siaes/factories.py b/lemarche/siaes/factories.py index eda9394a5..8c8209426 100644 --- a/lemarche/siaes/factories.py +++ b/lemarche/siaes/factories.py @@ -4,7 +4,7 @@ from factory.django import DjangoModelFactory from lemarche.siaes import constants as siae_constants -from lemarche.siaes.models import Siae, SiaeClientReference, SiaeGroup, SiaeLabel, SiaeOffer +from lemarche.siaes.models import Siae, SiaeClientReference, SiaeGroup, SiaeLabelOld, SiaeOffer class SiaeGroupFactory(DjangoModelFactory): @@ -63,8 +63,8 @@ class Meta: name = factory.Faker("name", locale="fr_FR") -class SiaeLabelFactory(DjangoModelFactory): +class SiaeLabelOldFactory(DjangoModelFactory): class Meta: - model = SiaeLabel + model = SiaeLabelOld name = factory.Faker("name", locale="fr_FR") diff --git a/lemarche/siaes/management/commands/update_counts.py b/lemarche/siaes/management/commands/update_counts.py index 4ce7b0a44..0253b0fde 100644 --- a/lemarche/siaes/management/commands/update_counts.py +++ b/lemarche/siaes/management/commands/update_counts.py @@ -37,7 +37,7 @@ def handle(self, *args, **options): network_count = siae.networks.count() offer_count = siae.offers.count() client_reference_count = siae.client_references.count() - label_count = siae.labels.count() + label_count = siae.labels_old.count() image_count = siae.images.count() # Step 3: update count fields diff --git a/lemarche/siaes/migrations/0058_siaelabel_old.py b/lemarche/siaes/migrations/0058_siaelabel_old.py new file mode 100644 index 000000000..fd7c9dad8 --- /dev/null +++ b/lemarche/siaes/migrations/0058_siaelabel_old.py @@ -0,0 +1,43 @@ +# Generated by Django 4.1.7 on 2023-05-31 15:10 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("siaes", "0057_siae_logs"), + ] + + operations = [ + migrations.CreateModel( + name="SiaeLabelOld", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255, verbose_name="Nom")), + ( + "created_at", + models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date de création"), + ), + ("updated_at", models.DateTimeField(auto_now=True, verbose_name="Date de modification")), + ( + "siae", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="labels_old", + to="siaes.siae", + verbose_name="Structure", + ), + ), + ], + options={ + "verbose_name": "Label & certification (old)", + "verbose_name_plural": "Labels & certifications (old)", + }, + ), + migrations.DeleteModel( + name="SiaeLabel", + ), + ] diff --git a/lemarche/siaes/migrations/0059_siae_label_through_model.py b/lemarche/siaes/migrations/0059_siae_label_through_model.py new file mode 100644 index 000000000..dc0eb006a --- /dev/null +++ b/lemarche/siaes/migrations/0059_siae_label_through_model.py @@ -0,0 +1,47 @@ +# Generated by Django 4.1.7 on 2023-05-31 16:34 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("labels", "0001_initial"), + ("siaes", "0058_siaelabel_old"), + ] + + operations = [ + migrations.CreateModel( + name="SiaeLabel", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("logs", models.JSONField(default=list, editable=False, verbose_name="Logs historiques")), + ( + "created_at", + models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date de création"), + ), + ("updated_at", models.DateTimeField(auto_now=True, verbose_name="Date de modification")), + ( + "label", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="labels.label", + verbose_name="Label & certification", + ), + ), + ( + "siae", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="siaes.siae", verbose_name="Structure" + ), + ), + ], + options={ + "verbose_name": "Label & certification", + "verbose_name_plural": "Labels & certifications", + "ordering": ["-created_at"], + }, + ), + ] diff --git a/lemarche/siaes/migrations/0060_siae_labels_m2m.py b/lemarche/siaes/migrations/0060_siae_labels_m2m.py new file mode 100644 index 000000000..92e2e39e9 --- /dev/null +++ b/lemarche/siaes/migrations/0060_siae_labels_m2m.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.7 on 2023-05-31 16:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("labels", "0001_initial"), + ("siaes", "0059_siae_label_through_model"), + ] + + operations = [ + migrations.AddField( + model_name="siae", + name="labels", + field=models.ManyToManyField( + blank=True, + related_name="siaes", + through="siaes.SiaeLabel", + to="labels.label", + verbose_name="Labels & certifications", + ), + ), + ] diff --git a/lemarche/siaes/models.py b/lemarche/siaes/models.py index 2793d9ac5..08ae10039 100644 --- a/lemarche/siaes/models.py +++ b/lemarche/siaes/models.py @@ -171,7 +171,7 @@ def prefetch_many_to_many(self): return self.prefetch_related("sectors", "networks") def prefetch_many_to_one(self): - return self.prefetch_related("offers", "client_references", "labels", "images") + return self.prefetch_related("offers", "client_references", "labels_old", "images") def search_query_set(self): return self.is_live().exclude(kind="OPCS").prefetch_many_to_many() @@ -214,8 +214,8 @@ def has_offer(self): return self.filter(offers__isnull=False).distinct() def has_label(self): - """Only return siaes who have at least 1 SiaeLabel.""" - return self.filter(labels__isnull=False).distinct() + """Only return siaes who have at least 1 SiaeLabelOld.""" + return self.filter(labels_old__isnull=False).distinct() def has_client_reference(self): """Only return siaes who have at least 1 SiaeClientReference.""" @@ -629,7 +629,14 @@ class Siae(models.Model): ) networks = models.ManyToManyField("networks.Network", verbose_name="Réseaux", related_name="siaes", blank=True) groups = models.ManyToManyField("siaes.SiaeGroup", verbose_name="Groupements", related_name="siaes", blank=True) - # ForeignKeys: offers, client_references, labels, images + labels = models.ManyToManyField( + "labels.Label", + through="siaes.SiaeLabel", + verbose_name="Labels & certifications", + related_name="siaes", + blank=True, + ) + # ForeignKeys: offers, client_references, labels_old, images # C2 (ETP) c2_etp_count = models.FloatField("Nombre d'ETP (C2)", blank=True, null=True) @@ -758,7 +765,7 @@ def set_related_counts(self): if self.id: self.offer_count = self.offers.count() self.client_reference_count = self.client_references.count() - self.label_count = self.labels.count() + self.label_count = self.labels_old.count() self.image_count = self.images.count() # user_count, sector_count, network_count? see M2M signals @@ -1130,9 +1137,10 @@ class Meta: class SiaeLabel(models.Model): - name = models.CharField(verbose_name="Nom", max_length=255) + siae = models.ForeignKey("siaes.Siae", verbose_name="Structure", on_delete=models.CASCADE) + label = models.ForeignKey("labels.Label", verbose_name="Label & certification", on_delete=models.CASCADE) - siae = models.ForeignKey("siaes.Siae", verbose_name="Structure", related_name="labels", on_delete=models.CASCADE) + logs = models.JSONField(verbose_name="Logs historiques", editable=False, default=list) created_at = models.DateTimeField(verbose_name="Date de création", default=timezone.now) updated_at = models.DateTimeField(verbose_name="Date de modification", auto_now=True) @@ -1140,6 +1148,22 @@ class SiaeLabel(models.Model): class Meta: verbose_name = "Label & certification" verbose_name_plural = "Labels & certifications" + ordering = ["-created_at"] + + +class SiaeLabelOld(models.Model): + name = models.CharField(verbose_name="Nom", max_length=255) + + siae = models.ForeignKey( + "siaes.Siae", verbose_name="Structure", related_name="labels_old", on_delete=models.CASCADE + ) + + created_at = models.DateTimeField(verbose_name="Date de création", default=timezone.now) + updated_at = models.DateTimeField(verbose_name="Date de modification", auto_now=True) + + class Meta: + verbose_name = "Label & certification (old)" + verbose_name_plural = "Labels & certifications (old)" # ordering = ["id"] def __str__(self): diff --git a/lemarche/siaes/tests.py b/lemarche/siaes/tests.py index fad67fe2d..a3de76b2e 100644 --- a/lemarche/siaes/tests.py +++ b/lemarche/siaes/tests.py @@ -1,14 +1,53 @@ from django.test import TestCase +from lemarche.labels.factories import LabelFactory from lemarche.perimeters.factories import PerimeterFactory from lemarche.perimeters.models import Perimeter from lemarche.sectors.factories import SectorFactory from lemarche.siaes import constants as siae_constants -from lemarche.siaes.factories import SiaeFactory, SiaeGroupFactory, SiaeLabelFactory, SiaeOfferFactory -from lemarche.siaes.models import Siae, SiaeGroup, SiaeUser +from lemarche.siaes.factories import SiaeFactory, SiaeGroupFactory, SiaeLabelOldFactory, SiaeOfferFactory +from lemarche.siaes.models import Siae, SiaeGroup, SiaeLabel, SiaeUser from lemarche.users.factories import UserFactory +class SiaeGroupModelTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.siae_group = SiaeGroupFactory(name="Mon groupement") + + def test_slug_field(self): + self.assertEqual(self.siae_group.slug, "mon-groupement") + + def test_str(self): + self.assertEqual(str(self.siae_group), "Mon groupement") + + +class SiaeGroupModelSaveTest(TestCase): + def test_update_last_updated_fields(self): + siae_group = SiaeGroupFactory() + self.assertEqual(siae_group.employees_insertion_count, None) + self.assertEqual(siae_group.employees_insertion_count_last_updated, None) + # new value: last_updated field will be set + siae_group = SiaeGroup.objects.get(id=siae_group.id) # we need to fetch it again to pass through the __init__ + siae_group.employees_insertion_count = 10 + siae_group.save() + self.assertEqual(siae_group.employees_insertion_count, 10) + self.assertNotEqual(siae_group.employees_insertion_count_last_updated, None) + employees_insertion_count_last_updated = siae_group.employees_insertion_count_last_updated + # same value: last_updated field will not be updated + siae_group = SiaeGroup.objects.get(id=siae_group.id) + siae_group.employees_insertion_count = 10 + siae_group.save() + self.assertEqual(siae_group.employees_insertion_count, 10) + self.assertEqual(siae_group.employees_insertion_count_last_updated, employees_insertion_count_last_updated) + # updated value: last_updated field will be updated + siae_group = SiaeGroup.objects.get(id=siae_group.id) + siae_group.employees_insertion_count = 15 + siae_group.save() + self.assertEqual(siae_group.employees_insertion_count, 15) + self.assertNotEqual(siae_group.employees_insertion_count_last_updated, employees_insertion_count_last_updated) + + class SiaeModelTest(TestCase): def setUp(self): pass @@ -78,7 +117,7 @@ def test_is_missing_content_property(self): sector = SectorFactory() siae_full.sectors.add(sector) SiaeOfferFactory(siae=siae_full) - SiaeLabelFactory(siae=siae_full) + SiaeLabelOldFactory(siae=siae_full) siae_full.save() # to update stats self.assertFalse(siae_full.is_missing_content) self.assertTrue(score_completion_before < siae_full.completion_percent) @@ -92,7 +131,7 @@ def test_is_missing_content_property(self): ) siae_full_2.sectors.add(sector) SiaeOfferFactory(siae=siae_full_2) - SiaeLabelFactory(siae=siae_full_2) + SiaeLabelOldFactory(siae=siae_full_2) siae_full_2.save() # to update stats self.assertFalse(siae_full_2.is_missing_content) @@ -360,39 +399,18 @@ def test_geo_range_in_perimeter_list(self): ) -class SiaeGroupModelTest(TestCase): +class SiaeLabelModelTest(TestCase): @classmethod def setUpTestData(cls): - cls.siae_group = SiaeGroupFactory(name="Mon groupement") - - def test_slug_field(self): - self.assertEqual(self.siae_group.slug, "mon-groupement") - - def test_str(self): - self.assertEqual(str(self.siae_group), "Mon groupement") + cls.label_1 = LabelFactory() + cls.label_2 = LabelFactory() + def test_siae_labels(self): + siae = SiaeFactory() + siae.labels.add(self.label_1) + self.assertEqual(siae.labels.count(), 1) -class SiaeGroupModelSaveTest(TestCase): - def test_update_last_updated_fields(self): - siae_group = SiaeGroupFactory() - self.assertEqual(siae_group.employees_insertion_count, None) - self.assertEqual(siae_group.employees_insertion_count_last_updated, None) - # new value: last_updated field will be set - siae_group = SiaeGroup.objects.get(id=siae_group.id) # we need to fetch it again to pass through the __init__ - siae_group.employees_insertion_count = 10 - siae_group.save() - self.assertEqual(siae_group.employees_insertion_count, 10) - self.assertNotEqual(siae_group.employees_insertion_count_last_updated, None) - employees_insertion_count_last_updated = siae_group.employees_insertion_count_last_updated - # same value: last_updated field will not be updated - siae_group = SiaeGroup.objects.get(id=siae_group.id) - siae_group.employees_insertion_count = 10 - siae_group.save() - self.assertEqual(siae_group.employees_insertion_count, 10) - self.assertEqual(siae_group.employees_insertion_count_last_updated, employees_insertion_count_last_updated) - # updated value: last_updated field will be updated - siae_group = SiaeGroup.objects.get(id=siae_group.id) - siae_group.employees_insertion_count = 15 - siae_group.save() - self.assertEqual(siae_group.employees_insertion_count, 15) - self.assertNotEqual(siae_group.employees_insertion_count_last_updated, employees_insertion_count_last_updated) + def test_siae_labels_through(self): + siae = SiaeFactory() + SiaeLabel.objects.create(siae=siae, label=self.label_2) + self.assertEqual(siae.labels.count(), 1) diff --git a/lemarche/templates/siaes/detail.html b/lemarche/templates/siaes/detail.html index 47d14f303..fda8eea55 100644 --- a/lemarche/templates/siaes/detail.html +++ b/lemarche/templates/siaes/detail.html @@ -324,7 +324,7 @@

Réseaux

Labels & certifications

diff --git a/lemarche/www/dashboard/forms.py b/lemarche/www/dashboard/forms.py index 9b98520a0..f90c4642e 100644 --- a/lemarche/www/dashboard/forms.py +++ b/lemarche/www/dashboard/forms.py @@ -10,7 +10,7 @@ SiaeClientReference, SiaeGroup, SiaeImage, - SiaeLabel, + SiaeLabelOld, SiaeOffer, SiaeUserRequest, ) @@ -130,7 +130,7 @@ class Meta: "year_constitution", "employees_insertion_count", "employees_permanent_count", - # "labels", # SiaeLabelFormSet + # "labels", # SiaeLabelOldFormSet ] def __init__(self, *args, **kwargs): @@ -144,13 +144,13 @@ def __init__(self, *args, **kwargs): # self.fields["logo_url"].label = "Importez votre logo" -class SiaeLabelForm(forms.ModelForm): +class SiaeLabelOldForm(forms.ModelForm): class Meta: - model = SiaeLabel + model = SiaeLabelOld fields = ["name"] -SiaeLabelFormSet = inlineformset_factory(Siae, SiaeLabel, form=SiaeLabelForm, extra=1, can_delete=True) +SiaeLabelOldFormSet = inlineformset_factory(Siae, SiaeLabelOld, form=SiaeLabelOldForm, extra=1, can_delete=True) class SiaeEditOfferForm(forms.ModelForm): diff --git a/lemarche/www/dashboard/views.py b/lemarche/www/dashboard/views.py index 1d3518717..12925f329 100644 --- a/lemarche/www/dashboard/views.py +++ b/lemarche/www/dashboard/views.py @@ -32,7 +32,7 @@ SiaeEditOfferForm, SiaeEditSearchForm, SiaeImageFormSet, - SiaeLabelFormSet, + SiaeLabelOldFormSet, SiaeOfferFormSet, SiaeSearchAdoptConfirmForm, SiaeSearchBySiretForm, @@ -377,9 +377,9 @@ def get_context_data(self, **kwargs): context["s3_form_values_siae_logo"] = s3_upload.form_values context["s3_upload_config_siae_logo"] = s3_upload.config if self.request.POST: - context["label_formset"] = SiaeLabelFormSet(self.request.POST, instance=self.object) + context["label_formset"] = SiaeLabelOldFormSet(self.request.POST, instance=self.object) else: - context["label_formset"] = SiaeLabelFormSet(instance=self.object) + context["label_formset"] = SiaeLabelOldFormSet(instance=self.object) context["last_3_siae_content_filled_full"] = ( Siae.objects.with_content_filled_stats() .filter(content_filled_full=True) @@ -392,7 +392,7 @@ def post(self, request, *args, **kwargs): self.object = self.get_object() form_class = self.get_form_class() form = self.get_form(form_class) - label_formset = SiaeLabelFormSet(self.request.POST, instance=self.object) + label_formset = SiaeLabelOldFormSet(self.request.POST, instance=self.object) if form.is_valid() and label_formset.is_valid(): return self.form_valid(form, label_formset) else: diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py index 8676f36bf..c348a6e3d 100644 --- a/lemarche/www/tenders/views.py +++ b/lemarche/www/tenders/views.py @@ -76,7 +76,6 @@ class TenderCreateMultiStepView(SessionWizardView): ] def get_template_names(self): - print("get_template_names") return [self.TEMPLATES[self.steps.current]] def get(self, request, *args, **kwargs):