<commit_before>from secrets import token_urlsafe

import ics
import random
import re
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.search import SearchVector, SearchRank
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import Case, Sum, Count, When, CharField, F, Q
from django.db.models.functions import Coalesce
from django.template.defaultfilters import floatformat
from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _
from django_prometheus.models import ExportModelOperationsMixin
from dynamic_filenames import FilePattern
from slugify import slugify
from stdimage.models import StdImageField

from agir.groups.models import Membership
from agir.lib.form_fields import CustomJSONEncoder
from agir.lib.form_fields import DateTimePickerWidget
from agir.lib.model_fields import FacebookEventField
from agir.lib.models import (
    BaseAPIResource,
    AbstractLabel,
    NationBuilderResource,
    ContactMixin,
    LocationMixin,
    ImageMixin,
    DescriptionMixin,
    DescriptionField,
    BaseSubtype,
    TimeStampedModel,
    banner_path,
)
from agir.lib.search import PrefixSearchQuery
from agir.lib.utils import front_url, resize_and_autorotate


class EventQuerySet(models.QuerySet):
    def public(self):
        return self.filter(visibility=Event.VISIBILITY_PUBLIC)

    def listed(self):
        return self.public().filter(do_not_list=False)

    def upcoming(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(end_time__gte=as_of)
        if published_only:
            condition &= models.Q(visibility=Event.VISIBILITY_PUBLIC)

        return self.filter(condition)

    def past(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(end_time__lt=as_of)
        if published_only:
            condition &= models.Q(visibility=Event.VISIBILITY_PUBLIC)
        return self.filter(condition)

    def with_participants(self):
        confirmed_guests = Q(rsvps__identified_guests__status=RSVP.STATUS_CONFIRMED)
        confirmed_rsvps = Q(rsvps__status=RSVP.STATUS_CONFIRMED)

        return self.annotate(
            all_attendee_count=Case(
                When(
                    subscription_form=None,
                    then=Coalesce(Sum("rsvps__guests") + Count("rsvps"), 0),
                ),
                default=Coalesce(Count("rsvps__identified_guests") + Count("rsvps"), 0),
                output_field=CharField(),
            )
        ).annotate(
            confirmed_attendee_count=Case(
                When(payment_parameters=None, then=F("all_attendee_count")),
                When(
                    subscription_form=None,
                    then=Coalesce(
                        Sum("rsvps__guests", filter=confirmed_rsvps)
                        + Count("rsvps", filter=confirmed_rsvps),
                        0,
                    ),
                ),
                default=Coalesce(
                    Count("rsvps__identified_guests", filter=confirmed_guests)
                    + Count("rsvps", filter=confirmed_rsvps),
                    0,
                ),
                output_field=CharField(),
            )
        )

    def search(self, query):
        vector = (
            SearchVector(models.F("name"), config="french_unaccented", weight="A")
            + SearchVector(
                models.F("location_name"), config="french_unaccented", weight="B"
            )
            + SearchVector(
                models.F("location_city"), config="french_unaccented", weight="B"
            )
            + SearchVector(
                models.F("location_zip"), config="french_unaccented", weight="B"
            )
            + SearchVector(
                models.F("description"), config="french_unaccented", weight="C"
            )
            + SearchVector(
                models.F("report_content"), config="french_unaccented", weight="C"
            )
        )
        query = PrefixSearchQuery(query, config="french_unaccented")

        return (
            self.annotate(search=vector)
            .filter(search=query)
            .annotate(rank=SearchRank(vector, query))
            .order_by("-rank")
        )


class RSVPQuerySet(models.QuerySet):
    def upcoming(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(event__end_time__gte=as_of)
        if published_only:
            condition &= models.Q(event__visibility=Event.VISIBILITY_PUBLIC)

        return self.filter(condition)

    def past(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(event__end_time__lt=as_of)
        if published_only:
            condition &= models.Q(event__visibility=Event.VISIBILITY_PUBLIC)

        return self.filter(condition)


class CustomDateTimeField(models.DateTimeField):
    def formfield(self, **kwargs):
        defaults = {"widget": DateTimePickerWidget}
        defaults.update(kwargs)
        return super().formfield(**defaults)


def get_default_subtype():
    return (
        EventSubtype.objects.filter(type=EventSubtype.TYPE_PUBLIC_ACTION)
        .order_by("created")
        .values("id")
        .first()["id"]
    )


report_image_path = FilePattern(
    filename_pattern="{app_label}/{model_name}/{instance.id}/report_banner{ext}"
)


class Event(
    ExportModelOperationsMixin("event"),
    BaseAPIResource,
    NationBuilderResource,
    LocationMixin,
    ImageMixin,
    DescriptionMixin,
    ContactMixin,
):
    """
    Model that represents an event
    """

    objects = EventQuerySet.as_manager()

    name = models.CharField(
        _("nom"), max_length=255, blank=False, help_text=_("Le nom de l'événement")
    )

    VISIBILITY_ADMIN = "A"
    VISIBILITY_ORGANIZER = "O"
    VISIBILITY_PUBLIC = "P"
    VISIBILITY_CHOICES = (
        (VISIBILITY_ADMIN, "Caché"),
        (VISIBILITY_ORGANIZER, "Visible par les organisateurs"),
        (VISIBILITY_PUBLIC, "Public"),
    )

    visibility = models.CharField(
        "Visibilité",
        max_length=1,
        choices=VISIBILITY_CHOICES,
        default=VISIBILITY_PUBLIC,
    )

    subtype = models.ForeignKey(
        "EventSubtype",
        verbose_name="Sous-type",
        related_name="events",
        on_delete=models.PROTECT,
        default=get_default_subtype,
    )

    nb_path = models.CharField(_("NationBuilder path"), max_length=255, blank=True)

    tags = models.ManyToManyField("EventTag", related_name="events", blank=True)

    start_time = CustomDateTimeField(_("date et heure de début"), blank=False)
    end_time = CustomDateTimeField(_("date et heure de fin"), blank=False)
    max_participants = models.IntegerField(
        "Nombre maximum de participants", blank=True, null=True
    )
    allow_guests = models.BooleanField(
        "Autoriser les participant⋅e⋅s à inscrire des invité⋅e⋅s", default=False
    )
    facebook = FacebookEventField("Événement correspondant sur Facebook", blank=True)

    attendees = models.ManyToManyField(
        "people.Person", related_name="events", through="RSVP"
    )

    organizers = models.ManyToManyField(
        "people.Person", related_name="organized_events", through="OrganizerConfig"
    )
    organizers_groups = models.ManyToManyField(
        "groups.SupportGroup",
        related_name="organized_events",
        through="OrganizerConfig",
    )

    report_image = StdImageField(
        verbose_name=_("image de couverture"),
        blank=True,
        variations={"thumbnail": (400, 250), "banner": (1200, 400)},
        upload_to=report_image_path,
        help_text=_(
            "Cette image apparaîtra en tête de votre compte-rendu, et dans les partages que vous ferez du"
            " compte-rendu sur les réseaux sociaux."
        ),
    )

    report_content = DescriptionField(
        verbose_name=_("compte-rendu de l'événement"),
        blank=True,
        allowed_tags="allowed_tags",
        help_text=_(
            "Ajoutez un compte-rendu de votre événement. N'hésitez pas à inclure des photos."
        ),
    )

    report_summary_sent = models.BooleanField(
        "Le mail de compte-rendu a été envoyé", default=False
    )

    subscription_form = models.OneToOneField(
        "people.PersonForm", null=True, blank=True, on_delete=models.PROTECT
    )
    payment_parameters = JSONField(
        verbose_name=_("Paramètres de paiement"), null=True, blank=True
    )

    scanner_event = models.IntegerField(
        "L'ID de l'événement sur le logiciel de tickets", blank=True, null=True
    )
    scanner_category = models.IntegerField(
        "La catégorie que doivent avoir les tickets sur scanner", blank=True, null=True
    )

    enable_jitsi = models.BooleanField("Activer la visio-conférence", default=False)

    participation_template = models.TextField(
        _("Template pour la page de participation"), blank=True, null=True
    )

    do_not_list = models.BooleanField(
        "Ne pas lister l'événement",
        default=False,
        help_text="L'événement n'apparaîtra pas sur la carte, ni sur le calendrier "
        "et ne sera pas cherchable via la recherche interne ou les moteurs de recherche.",
    )

    legal = JSONField(
        _("Informations juridiques"),
        default=dict,
        blank=True,
        encoder=CustomJSONEncoder,
    )

    class Meta:
        verbose_name = _("événement")
        verbose_name_plural = _("événements")
        ordering = ("-start_time", "-end_time")
        permissions = (
            # DEPRECIATED: every_event was set up as a potential solution to Rest Framework django permissions
            # Permission class default behaviour of requiring both global permissions and object permissions before
            # allowing users. Was not used in the end.s
            ("every_event", _("Peut éditer tous les événements")),
            ("view_hidden_event", _("Peut voir les événements non publiés")),
        )
        indexes = (
            models.Index(
                fields=["start_time", "end_time"], name="events_datetime_index"
            ),
            models.Index(fields=["end_time"], name="events_end_time_index"),
            models.Index(fields=["nb_path"], name="events_nb_path_index"),
        )

    def __str__(self):
        return f"{self.name} ({self.get_display_date()})"

    def __repr__(self):
        return f"{self.__class__.__name__}(id={str(self.pk)!r}, name={self.name!r})"

    def to_ics(self):
        event_url = front_url("view_event", args=[self.pk], auto_login=False)
        return ics.Event(
            name=self.name,
            begin=self.start_time,
            end=self.end_time,
            uid=str(self.pk),
            description=self.description + f"<p>{event_url}</p>",
            location=self.short_address,
            url=event_url,
        )

    @property
    def participants(self):
        try:
            return self.all_attendee_count
        except AttributeError:
            if self.subscription_form:
                return (
                    self.rsvps.annotate(
                        identified_guests_count=Count("identified_guests")
                    ).aggregate(participants=Sum(F("identified_guests_count") + 1))[
                        "participants"
                    ]
                    or 0
                )
            return (
                self.rsvps.aggregate(participants=Sum(models.F("guests") + 1))[
                    "participants"
                ]
                or 0
            )

    @property
    def type(self):
        return self.subtype.type

    def get_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)
        end_time = self.end_time.astimezone(tz)

        if start_time.date() == end_time.date():
            date = formats.date_format(start_time, "DATE_FORMAT")
            return _("le {date}, de {start_hour} à {end_hour}").format(
                date=date,
                start_hour=formats.time_format(start_time, "TIME_FORMAT"),
                end_hour=formats.time_format(end_time, "TIME_FORMAT"),
            )

        return _("du {start_date}, {start_time} au {end_date}, {end_time}").format(
            start_date=formats.date_format(start_time, "DATE_FORMAT"),
            start_time=formats.date_format(start_time, "TIME_FORMAT"),
            end_date=formats.date_format(end_time, "DATE_FORMAT"),
            end_time=formats.date_format(end_time, "TIME_FORMAT"),
        )

    def get_simple_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)

        return _("le {date} à {time}").format(
            date=formats.date_format(start_time, "DATE_FORMAT"),
            time=formats.time_format(start_time, "TIME_FORMAT"),
        )

    def is_past(self):
        return timezone.now() > self.end_time

    def is_current(self):
        return self.start_time < timezone.now() < self.end_time

    def clean(self):
        if self.start_time and self.end_time and self.end_time < self.start_time:
            raise ValidationError(
                {
                    "end_time": _(
                        "La date de fin de l'événement doit être postérieure à sa date de début."
                    )
                }
            )

    def get_price_display(self):
        if self.payment_parameters is None:
            return None

        base_price = self.payment_parameters.get("price", 0)
        min_price = base_price
        max_price = base_price

        for mapping in self.payment_parameters.get("mappings", []):
            prices = [m["price"] for m in mapping["mapping"]]
            min_price += min(prices)
            max_price += max(prices)

        if min_price == max_price == 0:
            if "free_pricing" in self.payment_parameters:
                return "Prix libre"
            else:
                return None

        if min_price == max_price:
            display = "{} €".format(floatformat(min_price / 100, 2))
        else:
            display = "de {} à {} €".format(
                floatformat(min_price / 100, 2), floatformat(max_price / 100, 2)
            )

        if "free_pricing" in self.payment_parameters:
            display += " + montant libre"

        return display

    @property
    def is_free(self):
        return self.payment_parameters is None

    def get_price(self, submission_data: dict = None):
        price = self.payment_parameters.get("price", 0)

        if submission_data is None:
            return price

        for mapping in self.payment_parameters.get("mappings", []):
            values = [submission_data.get(field) for field in mapping["fields"]]

            d = {tuple(v for v in m["values"]): m["price"] for m in mapping["mapping"]}

            price += d.get(tuple(values), 0)

        if "free_pricing" in self.payment_parameters:
            field = self.payment_parameters["free_pricing"]
            price += max(0, int(submission_data.get(field, 0) * 100))
<commit_msg>fix(event): crashes if mapping does not work<commit_after>from secrets import token_urlsafe

import ics
import random
import re
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.search import SearchVector, SearchRank
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import Case, Sum, Count, When, CharField, F, Q
from django.db.models.functions import Coalesce
from django.template.defaultfilters import floatformat
from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _
from django_prometheus.models import ExportModelOperationsMixin
from dynamic_filenames import FilePattern
from slugify import slugify
from stdimage.models import StdImageField

from agir.groups.models import Membership
from agir.lib.form_fields import CustomJSONEncoder
from agir.lib.form_fields import DateTimePickerWidget
from agir.lib.model_fields import FacebookEventField
from agir.lib.models import (
    BaseAPIResource,
    AbstractLabel,
    NationBuilderResource,
    ContactMixin,
    LocationMixin,
    ImageMixin,
    DescriptionMixin,
    DescriptionField,
    BaseSubtype,
    TimeStampedModel,
    banner_path,
)
from agir.lib.search import PrefixSearchQuery
from agir.lib.utils import front_url, resize_and_autorotate


class EventQuerySet(models.QuerySet):
    def public(self):
        return self.filter(visibility=Event.VISIBILITY_PUBLIC)

    def listed(self):
        return self.public().filter(do_not_list=False)

    def upcoming(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(end_time__gte=as_of)
        if published_only:
            condition &= models.Q(visibility=Event.VISIBILITY_PUBLIC)

        return self.filter(condition)

    def past(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(end_time__lt=as_of)
        if published_only:
            condition &= models.Q(visibility=Event.VISIBILITY_PUBLIC)
        return self.filter(condition)

    def with_participants(self):
        confirmed_guests = Q(rsvps__identified_guests__status=RSVP.STATUS_CONFIRMED)
        confirmed_rsvps = Q(rsvps__status=RSVP.STATUS_CONFIRMED)

        return self.annotate(
            all_attendee_count=Case(
                When(
                    subscription_form=None,
                    then=Coalesce(Sum("rsvps__guests") + Count("rsvps"), 0),
                ),
                default=Coalesce(Count("rsvps__identified_guests") + Count("rsvps"), 0),
                output_field=CharField(),
            )
        ).annotate(
            confirmed_attendee_count=Case(
                When(payment_parameters=None, then=F("all_attendee_count")),
                When(
                    subscription_form=None,
                    then=Coalesce(
                        Sum("rsvps__guests", filter=confirmed_rsvps)
                        + Count("rsvps", filter=confirmed_rsvps),
                        0,
                    ),
                ),
                default=Coalesce(
                    Count("rsvps__identified_guests", filter=confirmed_guests)
                    + Count("rsvps", filter=confirmed_rsvps),
                    0,
                ),
                output_field=CharField(),
            )
        )

    def search(self, query):
        vector = (
            SearchVector(models.F("name"), config="french_unaccented", weight="A")
            + SearchVector(
                models.F("location_name"), config="french_unaccented", weight="B"
            )
            + SearchVector(
                models.F("location_city"), config="french_unaccented", weight="B"
            )
            + SearchVector(
                models.F("location_zip"), config="french_unaccented", weight="B"
            )
            + SearchVector(
                models.F("description"), config="french_unaccented", weight="C"
            )
            + SearchVector(
                models.F("report_content"), config="french_unaccented", weight="C"
            )
        )
        query = PrefixSearchQuery(query, config="french_unaccented")

        return (
            self.annotate(search=vector)
            .filter(search=query)
            .annotate(rank=SearchRank(vector, query))
            .order_by("-rank")
        )


class RSVPQuerySet(models.QuerySet):
    def upcoming(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(event__end_time__gte=as_of)
        if published_only:
            condition &= models.Q(event__visibility=Event.VISIBILITY_PUBLIC)

        return self.filter(condition)

    def past(self, as_of=None, published_only=True):
        if as_of is None:
            as_of = timezone.now()

        condition = models.Q(event__end_time__lt=as_of)
        if published_only:
            condition &= models.Q(event__visibility=Event.VISIBILITY_PUBLIC)

        return self.filter(condition)


class CustomDateTimeField(models.DateTimeField):
    def formfield(self, **kwargs):
        defaults = {"widget": DateTimePickerWidget}
        defaults.update(kwargs)
        return super().formfield(**defaults)


def get_default_subtype():
    return (
        EventSubtype.objects.filter(type=EventSubtype.TYPE_PUBLIC_ACTION)
        .order_by("created")
        .values("id")
        .first()["id"]
    )


report_image_path = FilePattern(
    filename_pattern="{app_label}/{model_name}/{instance.id}/report_banner{ext}"
)


class Event(
    ExportModelOperationsMixin("event"),
    BaseAPIResource,
    NationBuilderResource,
    LocationMixin,
    ImageMixin,
    DescriptionMixin,
    ContactMixin,
):
    """
    Model that represents an event
    """

    objects = EventQuerySet.as_manager()

    name = models.CharField(
        _("nom"), max_length=255, blank=False, help_text=_("Le nom de l'événement")
    )

    VISIBILITY_ADMIN = "A"
    VISIBILITY_ORGANIZER = "O"
    VISIBILITY_PUBLIC = "P"
    VISIBILITY_CHOICES = (
        (VISIBILITY_ADMIN, "Caché"),
        (VISIBILITY_ORGANIZER, "Visible par les organisateurs"),
        (VISIBILITY_PUBLIC, "Public"),
    )

    visibility = models.CharField(
        "Visibilité",
        max_length=1,
        choices=VISIBILITY_CHOICES,
        default=VISIBILITY_PUBLIC,
    )

    subtype = models.ForeignKey(
        "EventSubtype",
        verbose_name="Sous-type",
        related_name="events",
        on_delete=models.PROTECT,
        default=get_default_subtype,
    )

    nb_path = models.CharField(_("NationBuilder path"), max_length=255, blank=True)

    tags = models.ManyToManyField("EventTag", related_name="events", blank=True)

    start_time = CustomDateTimeField(_("date et heure de début"), blank=False)
    end_time = CustomDateTimeField(_("date et heure de fin"), blank=False)
    max_participants = models.IntegerField(
        "Nombre maximum de participants", blank=True, null=True
    )
    allow_guests = models.BooleanField(
        "Autoriser les participant⋅e⋅s à inscrire des invité⋅e⋅s", default=False
    )
    facebook = FacebookEventField("Événement correspondant sur Facebook", blank=True)

    attendees = models.ManyToManyField(
        "people.Person", related_name="events", through="RSVP"
    )

    organizers = models.ManyToManyField(
        "people.Person", related_name="organized_events", through="OrganizerConfig"
    )
    organizers_groups = models.ManyToManyField(
        "groups.SupportGroup",
        related_name="organized_events",
        through="OrganizerConfig",
    )

    report_image = StdImageField(
        verbose_name=_("image de couverture"),
        blank=True,
        variations={"thumbnail": (400, 250), "banner": (1200, 400)},
        upload_to=report_image_path,
        help_text=_(
            "Cette image apparaîtra en tête de votre compte-rendu, et dans les partages que vous ferez du"
            " compte-rendu sur les réseaux sociaux."
        ),
    )

    report_content = DescriptionField(
        verbose_name=_("compte-rendu de l'événement"),
        blank=True,
        allowed_tags="allowed_tags",
        help_text=_(
            "Ajoutez un compte-rendu de votre événement. N'hésitez pas à inclure des photos."
        ),
    )

    report_summary_sent = models.BooleanField(
        "Le mail de compte-rendu a été envoyé", default=False
    )

    subscription_form = models.OneToOneField(
        "people.PersonForm", null=True, blank=True, on_delete=models.PROTECT
    )
    payment_parameters = JSONField(
        verbose_name=_("Paramètres de paiement"), null=True, blank=True
    )

    scanner_event = models.IntegerField(
        "L'ID de l'événement sur le logiciel de tickets", blank=True, null=True
    )
    scanner_category = models.IntegerField(
        "La catégorie que doivent avoir les tickets sur scanner", blank=True, null=True
    )

    enable_jitsi = models.BooleanField("Activer la visio-conférence", default=False)

    participation_template = models.TextField(
        _("Template pour la page de participation"), blank=True, null=True
    )

    do_not_list = models.BooleanField(
        "Ne pas lister l'événement",
        default=False,
        help_text="L'événement n'apparaîtra pas sur la carte, ni sur le calendrier "
        "et ne sera pas cherchable via la recherche interne ou les moteurs de recherche.",
    )

    legal = JSONField(
        _("Informations juridiques"),
        default=dict,
        blank=True,
        encoder=CustomJSONEncoder,
    )

    class Meta:
        verbose_name = _("événement")
        verbose_name_plural = _("événements")
        ordering = ("-start_time", "-end_time")
        permissions = (
            # DEPRECIATED: every_event was set up as a potential solution to Rest Framework django permissions
            # Permission class default behaviour of requiring both global permissions and object permissions before
            # allowing users. Was not used in the end.s
            ("every_event", _("Peut éditer tous les événements")),
            ("view_hidden_event", _("Peut voir les événements non publiés")),
        )
        indexes = (
            models.Index(
                fields=["start_time", "end_time"], name="events_datetime_index"
            ),
            models.Index(fields=["end_time"], name="events_end_time_index"),
            models.Index(fields=["nb_path"], name="events_nb_path_index"),
        )

    def __str__(self):
        return f"{self.name} ({self.get_display_date()})"

    def __repr__(self):
        return f"{self.__class__.__name__}(id={str(self.pk)!r}, name={self.name!r})"

    def to_ics(self):
        event_url = front_url("view_event", args=[self.pk], auto_login=False)
        return ics.Event(
            name=self.name,
            begin=self.start_time,
            end=self.end_time,
            uid=str(self.pk),
            description=self.description + f"<p>{event_url}</p>",
            location=self.short_address,
            url=event_url,
        )

    @property
    def participants(self):
        try:
            return self.all_attendee_count
        except AttributeError:
            if self.subscription_form:
                return (
                    self.rsvps.annotate(
                        identified_guests_count=Count("identified_guests")
                    ).aggregate(participants=Sum(F("identified_guests_count") + 1))[
                        "participants"
                    ]
                    or 0
                )
            return (
                self.rsvps.aggregate(participants=Sum(models.F("guests") + 1))[
                    "participants"
                ]
                or 0
            )

    @property
    def type(self):
        return self.subtype.type

    def get_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)
        end_time = self.end_time.astimezone(tz)

        if start_time.date() == end_time.date():
            date = formats.date_format(start_time, "DATE_FORMAT")
            return _("le {date}, de {start_hour} à {end_hour}").format(
                date=date,
                start_hour=formats.time_format(start_time, "TIME_FORMAT"),
                end_hour=formats.time_format(end_time, "TIME_FORMAT"),
            )

        return _("du {start_date}, {start_time} au {end_date}, {end_time}").format(
            start_date=formats.date_format(start_time, "DATE_FORMAT"),
            start_time=formats.date_format(start_time, "TIME_FORMAT"),
            end_date=formats.date_format(end_time, "DATE_FORMAT"),
            end_time=formats.date_format(end_time, "TIME_FORMAT"),
        )

    def get_simple_display_date(self):
        tz = timezone.get_current_timezone()
        start_time = self.start_time.astimezone(tz)

        return _("le {date} à {time}").format(
            date=formats.date_format(start_time, "DATE_FORMAT"),
            time=formats.time_format(start_time, "TIME_FORMAT"),
        )

    def is_past(self):
        return timezone.now() > self.end_time

    def is_current(self):
        return self.start_time < timezone.now() < self.end_time

    def clean(self):
        if self.start_time and self.end_time and self.end_time < self.start_time:
            raise ValidationError(
                {
                    "end_time": _(
                        "La date de fin de l'événement doit être postérieure à sa date de début."
                    )
                }
            )

    def get_price_display(self):
        if self.payment_parameters is None:
            return None

        base_price = self.payment_parameters.get("price", 0)
        min_price = base_price
        max_price = base_price

        for mapping in self.payment_parameters.get("mappings", []):
            prices = [m["price"] for m in mapping["mapping"]]
            min_price += min(prices)
            max_price += max(prices)

        if min_price == max_price == 0:
            if "free_pricing" in self.payment_parameters:
                return "Prix libre"
            else:
                return None

        if min_price == max_price:
            display = "{} €".format(floatformat(min_price / 100, 2))
        else:
            display = "de {} à {} €".format(
                floatformat(min_price / 100, 2), floatformat(max_price / 100, 2)
            )

        if "free_pricing" in self.payment_parameters:
            display += " + montant libre"

        return display

    @property
    def is_free(self):
        return self.payment_parameters is None

    def get_price(self, submission_data: dict = None):
        price = self.payment_parameters.get("price", 0)

        if submission_data is None:
            return price

        for mapping in self.payment_parameters.get("mappings", []):
            values = [submission_data.get(field) for field in mapping["fields"]]

            d = {tuple(v for v in m["values"]): m["price"] for m in mapping["mapping"]}

            price += d[tuple(values)]

        if "free_pricing" in self.payment_parameters:
            field = self.payment_parameters["free_pricing"]
            price += max(0, int(submission_data.get(field, 0) * 100))

{'fraction_non_alphanumeric': 0.10062059603077123, 'fraction_numerical': 0.0031353028637921003, 'mean_word_length': 2.3720980926430517, 'pattern_counts': {'":': 8, '<': 13, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5071111', 'n_tokens_mistral': 9356, 'n_tokens_neox': 8927, 'n_words': 1823}