Skip to content

Commit

Permalink
Tender.external_url: change to UrlField, add validator, add test
Browse files Browse the repository at this point in the history
  • Loading branch information
raphodn committed Apr 17, 2024
1 parent fa94851 commit 55e11fd
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 4.2.9 on 2024-04-17 08:44

from django.db import migrations, models

import lemarche.utils.validators


class Migration(migrations.Migration):
dependencies = [
("tenders", "0084_alter_tender_survey_transactioned_answer"),
]

operations = [
migrations.AlterField(
model_name="tender",
name="external_link",
field=models.CharField(
blank=True,
help_text="Ajoutez ici l'URL de votre appel d'offres",
validators=[lemarche.utils.validators.OptionalSchemeURLValidator()],
verbose_name="Lien vers l'appel d'offres",
),
),
migrations.AlterField(
model_name="tendersiae",
name="source",
field=models.CharField(
choices=[("EMAIL", "E-mail"), ("DASHBOARD", "Dashboard"), ("LINK", "Lien"), ("AI", "IA")],
default="EMAIL",
max_length=20,
verbose_name="Source de la mise en relation",
),
),
]
29 changes: 19 additions & 10 deletions lemarche/tenders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
)
from lemarche.utils.fields import ChoiceArrayField
from lemarche.utils.urls import get_object_admin_url
from lemarche.utils.validators import OptionalSchemeURLValidator


def get_perimeter_filter(siae):
Expand Down Expand Up @@ -377,8 +378,11 @@ class Tender(models.Model):
verbose_name="Comment répondre à cette demande ?",
blank=True,
)
external_link = models.URLField(
verbose_name="Lien vers l'appel d'offres", help_text="Ajoutez ici l'URL de votre appel d'offres", blank=True
external_link = models.CharField(
verbose_name="Lien vers l'appel d'offres",
help_text="Ajoutez ici l'URL de votre appel d'offres",
validators=[OptionalSchemeURLValidator()],
blank=True,
)
deadline_date = models.DateField(
verbose_name="Date de clôture des réponses",
Expand Down Expand Up @@ -1081,24 +1085,29 @@ class TenderSiae(models.Model):
)

source = models.CharField(
verbose_name="Source de la mise en relation",
max_length=20,
choices=tender_constants.TENDER_SIAE_SOURCE_CHOICES,
default=tender_constants.TENDER_SIAE_SOURCE_EMAIL,
)
found_with_ai = models.BooleanField("Trouvé par l'IA", default=False)
is_deleted_by_siae = models.BooleanField("Supprimé par l'utilisateur ?", default=False, db_index=True)
found_with_ai = models.BooleanField(verbose_name="Trouvé par l'IA", default=False)
is_deleted_by_siae = models.BooleanField(verbose_name="Supprimé par l'utilisateur ?", default=False, db_index=True)

# stats: relation
email_send_date = models.DateTimeField("Date d'envoi de l'e-mail", blank=True, null=True)
email_link_click_date = models.DateTimeField("Date de clic sur le lien dans l'e-mail", blank=True, null=True)
detail_display_date = models.DateTimeField("Date de visualisation du besoin", blank=True, null=True)
email_send_date = models.DateTimeField(verbose_name="Date d'envoi de l'e-mail", blank=True, null=True)
email_link_click_date = models.DateTimeField(
verbose_name="Date de clic sur le lien dans l'e-mail", blank=True, null=True
)
detail_display_date = models.DateTimeField(verbose_name="Date de visualisation du besoin", blank=True, null=True)
detail_contact_click_date = models.DateTimeField(
"Date de clic sur les coordonnées du besoin", blank=True, null=True
verbose_name="Date de clic sur les coordonnées du besoin", blank=True, null=True
)
detail_cocontracting_click_date = models.DateTimeField(
"Date de clic sur Répondre en co-traitance", blank=True, null=True
verbose_name="Date de clic sur Répondre en co-traitance", blank=True, null=True
)
detail_not_interested_click_date = models.DateTimeField(
verbose_name="Date de clic sur Pas intéressé", blank=True, null=True
)
detail_not_interested_click_date = models.DateTimeField("Date de clic sur Pas intéressé", blank=True, null=True)
detail_not_interested_feedback = models.TextField(verbose_name="Clic sur Pas intéréssé : explication", blank=True)

# survey
Expand Down
17 changes: 16 additions & 1 deletion lemarche/utils/tests_validators.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
from django.core.exceptions import ValidationError
from django.test import TestCase

from lemarche.utils.validators import validate_naf, validate_post_code, validate_siren, validate_siret
from lemarche.utils.validators import (
OptionalSchemeURLValidator,
validate_naf,
validate_post_code,
validate_siren,
validate_siret,
)


class ValidatorsTest(TestCase):
def test_optional_scheme_url_validator(self):
validator = OptionalSchemeURLValidator()
URL_OK = ["example.com", "www.example.com", "http://www.example.com", "https://www.example.com"]
for item in URL_OK:
validator(item)
URL_NOT_OK = ["test"]
for item in URL_NOT_OK:
self.assertRaises(ValidationError, validator, item)

def test_post_code_validator(self):
validator = validate_post_code
POST_CODE_OK = ["00000", "12345", "38000"]
Expand Down
10 changes: 10 additions & 0 deletions lemarche/utils/validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# https://github.com/betagouv/itou/blob/master/itou/utils/validators.py

from django.core.exceptions import ValidationError
from django.core.validators import URLValidator


class OptionalSchemeURLValidator(URLValidator):
# https://stackoverflow.com/a/49983649/4293684
def __call__(self, value):
if "://" not in value:
# Validate as if it were https://
value = "https://" + value
super(OptionalSchemeURLValidator, self).__call__(value)


def validate_post_code(post_code):
Expand Down
8 changes: 8 additions & 0 deletions lemarche/www/tenders/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ def test_tender_wizard_form_not_created(self):
with self.assertRaises(AssertionError):
self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))

def test_tender_wizard_form_external_link_validation(self):
self.client.force_login(self.user_buyer)
tenders_step_data = self._generate_fake_data_form(_step_1={"general-kind": tender_constants.KIND_TENDER})
# set an external_link with a wrong format
tenders_step_data[1]["detail-external_link"] = "test"
with self.assertRaises(AssertionError):
self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))

def test_tender_wizard_form_external_link_required_for_tender(self):
self.client.force_login(self.user_buyer)
tenders_step_data = self._generate_fake_data_form(_step_1={"general-kind": tender_constants.KIND_TENDER})
Expand Down

0 comments on commit 55e11fd

Please sign in to comment.