Skip to content

Commit

Permalink
Ajouter les liens libres sur les fiches Zone
Browse files Browse the repository at this point in the history
Ce commit ajoute la gestion des liens libres depuis la création et
l'édition de fiche zone tout en netoyant le code restant de l'ancienne
version.

Limite aussi la liste des liens libres potentiels au fiche visible par
l'utilisateur
  • Loading branch information
Anto59290 committed Nov 14, 2024
1 parent 06e7130 commit 4f5c19e
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 129 deletions.
21 changes: 12 additions & 9 deletions core/fields.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist

from core.content_types import content_type_str_to_obj

class MultiModelChoiceField(forms.ChoiceField):

class MultiModelChoiceField(forms.MultipleChoiceField):
def __init__(self, model_choices, *args, **kwargs):
choices = []
for label, queryset in model_choices:
content_type = ContentType.objects.get_for_model(queryset.model)
model_choices = [(f"{content_type.id}-{obj.id}", f"{label}: {obj}") for obj in queryset]
model_choices = [(f"{content_type.id}-{obj.id}", f"{label} : {obj}") for obj in queryset]
choices.extend(model_choices)
super().__init__(choices=choices, *args, **kwargs)

def clean(self, value):
content_type_id, object_id = value.split("-")
content_type = ContentType.objects.get(id=content_type_id)
model_class = content_type.model_class()
try:
return model_class.objects.get(id=object_id)
except model_class.DoesNotExist:
raise forms.ValidationError("L'objet sélectionné n'existe pas.")
list_of_objects = []
for obj_as_str in value:
try:
list_of_objects.append(content_type_str_to_obj(obj_as_str))
except ObjectDoesNotExist:
raise forms.ValidationError("L'un des objets sélectionnés n'existe pas.")
return list_of_objects


class DSFRCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
Expand Down
14 changes: 14 additions & 0 deletions core/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,17 @@ def can_user_access(self, user):
return True
case _:
return False


class WithFreeLinkIdsMixin:
@property
def free_link_ids(self):
content_type = ContentType.objects.get_for_model(self)
links = LienLibre.objects.for_object(self).select_related("content_type_2", "content_type_1")
link_ids = []
for link in links:
if link.object_id_1 == self.id and link.content_type_1 == content_type:
link_ids.append(f"{link.content_type_2.pk}-{link.object_id_2}")
else:
link_ids.append(f"{link.content_type_1.pk}-{link.object_id_1}")
return link_ids
59 changes: 26 additions & 33 deletions sv/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from core.forms import DSFRForm, WithNextUrlMixin, VisibiliteUpdateBaseForm
from core.forms import DSFRForm, VisibiliteUpdateBaseForm

from django.contrib.contenttypes.models import ContentType
from core.fields import MultiModelChoiceField
from django import forms
from django.forms.models import inlineformset_factory
Expand All @@ -15,37 +14,6 @@
from sv.models import FicheZoneDelimitee, ZoneInfestee, OrganismeNuisible, StatutReglementaire, FicheDetection


class FreeLinkForm(DSFRForm, WithNextUrlMixin, forms.ModelForm):
object_id_1 = forms.IntegerField(widget=forms.HiddenInput())
content_type_1 = forms.ModelChoiceField(widget=forms.HiddenInput(), queryset=ContentType.objects.all())

class Meta:
fields = ["object_id_1", "content_type_1"]
model = LienLibre

def __init__(self, *args, **kwargs):
object_id_1 = kwargs.pop("object_id_1", None)
content_type_1 = kwargs.pop("content_type_1", None)
next = kwargs.pop("next", None)
super().__init__(*args, **kwargs)
self.fields["object_choice"] = MultiModelChoiceField(
label="Sélectioner un objet",
model_choices=[
("Fiche Detection", FicheDetection.objects.select_related("numero")),
("Fiche Zone Delimitee", FicheZoneDelimitee.objects.select_related("numero")),
],
)
self.add_next_field(next)
self.fields["object_id_1"].initial = object_id_1
self.fields["content_type_1"].initial = content_type_1

def clean(self):
super().clean()
obj = self.cleaned_data["object_choice"]
self.instance.content_type_2 = ContentType.objects.get_for_model(obj)
self.instance.object_id_2 = obj.id


class FicheDetectionVisibiliteUpdateForm(VisibiliteUpdateBaseForm, forms.ModelForm):
class Meta:
model = FicheDetection
Expand Down Expand Up @@ -129,6 +97,16 @@ def __init__(self, *args, **kwargs):
if self.instance.pk:
self.fields.pop("visibilite")

qs_detection = FicheDetection.objects.all().get_fiches_user_can_view(self.user).select_related("numero")
qs_zone = FicheZoneDelimitee.objects.all().get_fiches_user_can_view(self.user).select_related("numero")
self.fields["free_link"] = MultiModelChoiceField(
label="Sélectionner un objet",
model_choices=[
("Fiche Détection", qs_detection),
("Fiche zone délimitée", qs_zone),
],
)

organisme_nuisible_libelle = self.data.get("organisme_nuisible") or self.initial.get("organisme_nuisible")
self.fields["detections_hors_zone"].queryset = (
FicheDetection.objects.all()
Expand Down Expand Up @@ -167,8 +145,23 @@ def save(self, commit=True):
if commit:
instance.save()
self.save_detections_hors_zone(instance)
self.save_free_links(instance)
return instance

def save_free_links(self, instance):
links_ids_to_keep = []
for obj in self.cleaned_data["free_link"]:
link = LienLibre.objects.for_both_objects(obj, instance)

if link:
links_ids_to_keep.append(link.id)
else:
link = LienLibre.objects.create(related_object_1=instance, related_object_2=obj)
links_ids_to_keep.append(link.id)

links_to_delete = LienLibre.objects.for_object(instance).exclude(id__in=links_ids_to_keep)
links_to_delete.delete()

def save_detections_hors_zone(self, instance):
detections_from_form = set(self.cleaned_data.get("detections_hors_zone", []))
detections_from_db = set(FicheDetection.objects.filter(hors_zone_infestee=instance))
Expand Down
14 changes: 14 additions & 0 deletions sv/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,17 @@ def get_contacts_structures_not_in_fin_suivi(self, fiche_zone_delimitee):
fin_suivi_contacts_ids = fiche_zone_delimitee.fin_suivi.values_list("contact", flat=True)
contacts_not_in_fin_suivi = contacts_structure_fiche.exclude(id__in=fin_suivi_contacts_ids)
return contacts_not_in_fin_suivi

def get_fiches_user_can_view(self, user):
if user.agent.structure.is_mus_or_bsv:
return self.filter(
Q(visibilite__in=[Visibilite.LOCAL, Visibilite.NATIONAL])
| Q(visibilite=Visibilite.BROUILLON, createur=user.agent.structure)
)
return self.filter(
Q(visibilite=Visibilite.NATIONAL)
| Q(
visibilite__in=[Visibilite.BROUILLON, Visibilite.LOCAL],
createur=user.agent.structure,
)
)
19 changes: 4 additions & 15 deletions sv/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.contrib.contenttypes.models import ContentType
from django.db import models, transaction
from django.core.validators import RegexValidator
from django.db.models import TextChoices, Q
Expand All @@ -13,8 +12,9 @@
AllowVisibiliteMixin,
IsActiveMixin,
WithMessageUrlsMixin,
WithFreeLinkIdsMixin,
)
from core.models import Document, Message, Contact, Structure, FinSuiviContact, UnitesMesure, Visibilite, LienLibre
from core.models import Document, Message, Contact, Structure, FinSuiviContact, UnitesMesure, Visibilite
from sv.managers import (
LaboratoireAgreeManager,
LaboratoireConfirmationOfficielleManager,
Expand Down Expand Up @@ -417,6 +417,7 @@ class FicheDetection(
AllowVisibiliteMixin,
WithEtatMixin,
WithMessageUrlsMixin,
WithFreeLinkIdsMixin,
models.Model,
):
class Meta:
Expand Down Expand Up @@ -521,18 +522,6 @@ def get_fiche_zone_delimitee(self) -> "FicheZoneDelimitee":
if self.zone_infestee and self.zone_infestee.fiche_zone_delimitee:
return self.zone_infestee.fiche_zone_delimitee

@property
def free_link_ids(self):
content_type = ContentType.objects.get_for_model(self)
links = LienLibre.objects.for_object(self).select_related("content_type_2", "content_type_1")
link_ids = []
for link in links:
if link.object_id_1 == self.id and link.content_type_1 == content_type:
link_ids.append(f"{link.content_type_2.pk}-{link.object_id_2}")
else:
link_ids.append(f"{link.content_type_1.pk}-{link.object_id_1}")
return link_ids


class ZoneInfestee(models.Model):
class UnitesSurfaceInfesteeTotale(TextChoices):
Expand Down Expand Up @@ -566,7 +555,7 @@ class Meta:
)


class FicheZoneDelimitee(AllowVisibiliteMixin, WithEtatMixin, WithMessageUrlsMixin, models.Model):
class FicheZoneDelimitee(AllowVisibiliteMixin, WithEtatMixin, WithMessageUrlsMixin, WithFreeLinkIdsMixin, models.Model):
class CaracteristiquesPrincipales(models.TextChoices):
PLEIN_AIR_ZONE_PRODUCTION_CHAMP = (
"plein_air_zone_production_champ",
Expand Down
8 changes: 0 additions & 8 deletions sv/static/sv/fichezone_detail.js

This file was deleted.

22 changes: 22 additions & 0 deletions sv/static/sv/fichezone_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ function initializeAllChoices() {
}
}

function inititializeFreeLinksChoices(){
options = {
searchResultLimit: 500,
classNames: {
containerInner: 'fr-select',
},
removeItemButton: true,
itemSelectText: '',
noResultsText: 'Aucun résultat trouvé',
noChoicesText: 'Aucune fiche à sélectionner',
searchFields: ['label'],
};
const freeLinksChoices = new Choices(document.getElementById("id_free_link"), options);
const freeLinksIds = JSON.parse(document.getElementById('free-links-id').textContent);
if (!!freeLinksIds) {
freeLinksIds.forEach(value => {
freeLinksChoices.setChoiceByValue(value);
});
}
}

function addZoneInfesteeForm() {
const totalFormsInput = document.getElementById('id_zoneinfestee_set-TOTAL_FORMS');
let totalForms = parseInt(totalFormsInput.value);
Expand All @@ -37,6 +58,7 @@ document.addEventListener('DOMContentLoaded', function() {

initializeChoices('id_detections_hors_zone');
initializeAllChoices();
inititializeFreeLinksChoices();
const addZoneButton = document.getElementById('add-zone-infestee');
addZoneButton.addEventListener('click', function(event) {
event.preventDefault();
Expand Down
3 changes: 0 additions & 3 deletions sv/templates/sv/fichezonedelimitee_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
<link rel="stylesheet" href="{% static 'sv/fichezonedelimitee_detail.css' %}">
{% endblock %}

{% block scripts %}
<script type="text/javascript" src="{% static 'sv/fichezone_detail.js' %}"></script>
{% endblock %}

{% block content %}
<div class="fr-container--fluid fr-m-4w ">
Expand Down
10 changes: 10 additions & 0 deletions sv/templates/sv/fichezonedelimitee_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,17 @@ <h2>Zones infestées</h2>
</div>
</template>
</div>

</div>

<div id="liens-libre" class="white-container fr-mt-4w">
{{ form.instance.free_link_ids|json_script:"free-links-id" }}
<h2>Liens libres</h2>
<div>
{{ form.free_link }}
</div>
</div>

</div>
</form>
{% endblock %}
24 changes: 23 additions & 1 deletion sv/tests/test_fichedetection_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
)

from sv.constants import REGIONS, DEPARTEMENTS
from core.models import Contact, LienLibre, Visibilite
from core.models import Contact, LienLibre, Visibilite, Structure

from sv.constants import STATUTS_EVENEMENT, STATUTS_REGLEMENTAIRES, CONTEXTES

Expand Down Expand Up @@ -427,3 +427,25 @@ def test_fiche_detection_with_free_link(

assert lien_libre.related_object_1 == fiche_detection
assert lien_libre.related_object_2 == fiche_zone


@pytest.mark.django_db
def test_fiche_detection_with_free_link_cant_see_draft(
live_server,
page: Page,
form_elements: FicheDetectionFormDomElements,
mocked_authentification_user,
fiche_zone_bakery,
choice_js_fill,
):
fiche_zone = fiche_zone_bakery()
fiche_zone.visibilite = Visibilite.BROUILLON
fiche_zone.createur = baker.make(Structure)
fiche_zone.save()

page.goto(f"{live_server.url}{reverse('fiche-detection-creation')}")
fiche_input = "Fiche zone délimitée : " + str(fiche_zone.numero)
page.query_selector("#liens-libre .choices").click()
page.wait_for_selector("input:focus", state="visible", timeout=2_000)
page.locator("*:focus").fill(str(fiche_zone.numero))
expect(page.get_by_role("option", name=fiche_input, exact=True)).not_to_be_visible()
30 changes: 29 additions & 1 deletion sv/tests/test_fichezonedelimitee_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.utils import timezone
from django.urls import reverse

from core.models import Visibilite
from core.models import Visibilite, LienLibre
from sv.models import FicheZoneDelimitee, ZoneInfestee, FicheDetection, Etat
from sv.tests.test_utils import FicheZoneDelimiteeFormPage
from sv.forms import RattachementChoices
Expand Down Expand Up @@ -324,3 +324,31 @@ def test_cant_link_fiche_detection_to_fiche_zone_delimitee_if_fiche_detection_is
expect(page.get_by_text("Aucune fiche détection à sélectionner").nth(1)).to_be_visible()
page.get_by_text("Rattacher des détections").nth(2).click()
expect(page.get_by_text("Aucune fiche détection à sélectionner").nth(2)).to_be_visible()


@pytest.mark.django_db
def test_can_create_fiche_zone_with_free_links(
live_server, page: Page, choice_js_fill, fiche_detection: FicheDetection, fiche_zone_bakery
):
other_fiche = fiche_zone_bakery()
other_fiche.visibilite = Visibilite.NATIONAL
other_fiche.save()
fiche_detection.organisme_nuisible = baker.make("OrganismeNuisible")
fiche_detection.statut_reglementaire = baker.make("StatutReglementaire")
fiche_detection.save()
form_page = FicheZoneDelimiteeFormPage(page, choice_js_fill)

form_page.goto_create_form_page(live_server, fiche_detection.pk, RattachementChoices.HORS_ZONE_INFESTEE)
fiche_input = "Fiche zone délimitée : " + str(other_fiche.numero)
choice_js_fill(page, "#liens-libre .choices", str(other_fiche.numero), fiche_input)
form_page.submit_form()

form_page.check_message_succes()
assert FicheZoneDelimitee.objects.count() == 2
fiche_from_db = FicheZoneDelimitee.objects.last()

assert LienLibre.objects.count() == 1
lien_libre = LienLibre.objects.get()

assert lien_libre.related_object_1 == fiche_from_db
assert lien_libre.related_object_2 == other_fiche
Loading

0 comments on commit 4f5c19e

Please sign in to comment.