def test_closing_shop_gives_all_employees_salary(self, shop, admin_user): shop2 = ShopFactory() employee1 = Employment.objects.create(user=admin_user, shop=shop, role=EmployeeRoleChoices.CASHIER, timespan=DateRange( date(2020, 12, 1))) employee2 = Employment.objects.create(user=admin_user, shop=shop, role=EmployeeRoleChoices.CASHIER, timespan=DateRange( date(2020, 12, 1), date(2021, 1, 1))) employee3 = Employment.objects.create(user=admin_user, shop=shop2, role=EmployeeRoleChoices.CASHIER, timespan=DateRange( date(2020, 12, 1))) shop.close() employee1_salary = Salary.objects.get() # only single salary assert employee1_salary.value == 2000 assert employee1_salary.when == timezone.now().date() employee1.refresh_from_db() employee2.refresh_from_db() employee3.refresh_from_db() assert employee1.timespan.upper == timezone.now().date() assert employee2.timespan.upper == date(2021, 1, 1) assert employee3.timespan.upper is None
def get_changeform_initial_data(self, request): """Permet de préremplir le champs `dates' en fonction de la dernière élection""" initial = super().get_changeform_initial_data(request) initial.setdefault("dates", self.default_date_range) if "person" in request.GET: try: initial["person"] = Person.objects.get( pk=request.GET["person"]) except Person.DoesNotExist: pass if "conseil" in request.GET: try: initial["conseil"] = self.get_conseil_queryset(request).get( code=request.GET["conseil"]) except ObjectDoesNotExist: pass if "debut" in request.GET and "fin" in request.GET: try: initial["dates"] = DateRange( datetime.strptime(request.GET["debut"], "%Y-%m-%d").date(), datetime.strptime(request.GET["fin"], "%Y-%m-%d").date(), ) except ValueError: pass return initial
class MandatDeputeEuropeen(MandatAbstrait): DEFAULT_DATE_RANGE = DateRange(date(2019, 6, 1), date(2024, 5, 31)) person = models.ForeignKey( "people.Person", verbose_name="Élu", on_delete=models.CASCADE, related_name="mandats_depute_europeen", related_query_name="mandat_depute_depute_europeen", ) reference = models.ForeignKey( "data_france.DeputeEuropeen", on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Référence dans les données du RNE", help_text= "La fiche correspondant à cet élu dans le répertoire national des élus", related_name="elus", related_query_name="elu", ) def titre_complet(self, conseil_avant=False): return genrer(self.person.gender, "député européen", "députée européenne", "député") class Meta: verbose_name = "Mandat de député⋅e européen⋅ne" verbose_name_plural = "Mandats de député⋅e européen⋅ne" ordering = ("person", )
def test_extending_when_there_is_a_newer_sub(self): now = timezone.now().date() + timedelta(days=10) Subscription.objects.create(range=DateRange(now, None), plan_id=1, admin=self.admin) response = self.client.post(self.url) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn(b'not a newest one', response.content)
class TestDateRangeField(FieldValues): """ Values for `ListField` with CharField as child. """ serializer_class = DateRangeSerializer valid_inputs = [ ({'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)'}, DateRange( **{'lower': datetime.date(2001, 1, 1), 'upper': datetime.date(2001, 2, 2), 'bounds': '[)'})), ({'upper': '2001-02-02', 'bounds': '[)'}, DateRange( **{'upper': datetime.date(2001, 2, 2), 'bounds': '[)'})), ({'lower': '2001-01-01', 'bounds': '[)'}, DateRange( **{'lower': datetime.date(2001, 1, 1), 'bounds': '[)'})), ({'empty': True}, DateRange(**{'empty': True})), ({}, DateRange()), ] invalid_inputs = [ ({'lower': 'a'}, ['Date has wrong format. Use one of these' ' formats instead: ' 'YYYY-MM-DD.']), ('not a dict', ['Expected a dictionary of items but got type "str".']), ] outputs = [ (DateRange( **{'lower': datetime.date(2001, 1, 1), 'upper': datetime.date(2001, 2, 2)}), {'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)'}), (DateRange(**{'empty': True}), {'empty': True}), (DateRange(), {'bounds': '[)', 'lower': None, 'upper': None}), ] field = DateRangeField() def test_no_source_on_child(self): with pytest.raises(AssertionError) as exc_info: DateRangeField(child=serializers.IntegerField(source='other')) assert str(exc_info.value) == ( "The `source` argument is not meaningful when applied to a `child=` field. " "Remove `source=` from the field declaration." )
def test_convert_old_acl_date_earlier_inc(): rulestr = "( date <= 01.01.2015 )" access_rules = convert_old_acl(rulestr) assert len(access_rules) == 1 assert_access_rule_with_flags(access_rules[0], invert=False, blocking=False, dateranges=[ DateRange(datetime.date(1, 1, 1), datetime.date(2015, 1, 1), '(]') ])
def test_convert_old_acl_date_later(): rulestr = "( date > 01.01.2015 )" access_rules = convert_old_acl(rulestr) assert len(access_rules) == 1 assert_access_rule_with_flags(access_rules[0], invert=False, blocking=False, dateranges=[ DateRange(datetime.date(2015, 1, 1), datetime.date(9999, 12, 31), '()') ])
def test_convert_old_acl_group_and_date(): rulestr = "( group x ) AND ( date > 01.01.2015 )" access_rules = convert_old_acl(rulestr) assert len(access_rules) == 1 assert_access_rule_with_flags(access_rules[0], invert=False, blocking=False, group_ids=[99990000], dateranges=[ DateRange(datetime.date(2015, 1, 1), datetime.date(9999, 12, 31), '()') ])
def _get_billing_cycle_number(self, billing_cycle): """Gets the 1-indexed number of the billing cycle relative to the provided billing cycle""" begins_before_initial_date = billing_cycle.date_range.lower < self.initial_billing_cycle.date_range.lower if begins_before_initial_date: raise ProvidedBillingCycleBeginsBeforeInitialBillingCycle( '{} precedes initial cycle {}'.format( billing_cycle, self.initial_billing_cycle)) billing_cycle_number = BillingCycle.objects.filter( date_range__contained_by=DateRange( self.initial_billing_cycle.date_range.lower, billing_cycle.date_range.upper, bounds='[]', ), ).count() return billing_cycle_number
class MandatDepute(MandatAbstrait): DEFAULT_DATE_RANGE = DateRange(date(2017, 7, 1), date(2022, 6, 30)) person = models.ForeignKey( "people.Person", verbose_name="Élu", on_delete=models.CASCADE, related_name="mandats_deputes", related_query_name="mandat_depute", ) conseil = models.ForeignKey( "data_france.CirconscriptionLegislative", verbose_name="Circonscription", blank=True, null=True, on_delete=models.CASCADE, ) reference = models.ForeignKey( "data_france.Depute", on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Référence dans les données AN", help_text="La fiche correspondant à cet élu dans la base de l'AN", related_name="elus", related_query_name="elu", ) def titre_complet(self, conseil_avant=False): titre = genrer(self.person.gender, "député⋅e") if not self.conseil: circo = "circonscription inconnue" else: circo = str(self.conseil) if conseil_avant: return f"{circo}, {titre}" return f"{titre} de la {circo}" class Meta: verbose_name = "Mandat de député⋅e" verbose_name_plural = "Mandats de député⋅e" ordering = ("person", "conseil")
class Migration(migrations.Migration): dependencies = [ ('common', '0035_additionalagreement_language'), ] operations = [ migrations.AddField( model_name='additionalagreement', name='active_date_range', field=django.contrib.postgres.fields.ranges.DateRangeField( default=DateRange(datetime.date(2017, 2, 15), datetime.date(2017, 4, 13))), preserve_default=False, ), migrations.AlterUniqueTogether( name='additionalagreement', unique_together=set([('cinema', 'film', 'active_from', 'dimension', 'language')]), ), ]
def extend_builder_plan(self, request, *args, **kwargs): admin = self.get_object() serializer = self.get_serializer(data=request.data) if serializer.is_valid(): try: subscription = Profile.get_active_subscription(admin.id) except Subscription.DoesNotExist: return Response({'detail': 'No active subscription.'}, status=status.HTTP_400_BAD_REQUEST) if Subscription.objects.filter(admin=admin, range__gt=subscription.range).exists(): return Response({'detail': 'Current subscription is not a newest one.'}, status=status.HTTP_400_BAD_REQUEST) if subscription.plan.name != 'builder': return Response({'detail': 'Current subscription is not on a builder plan.'}, status=status.HTTP_400_BAD_REQUEST) now = timezone.now().date() subscription.range = DateRange(subscription.start, now + timedelta(days=serializer.data['days'])) subscription.save() return Response(SubscriptionSerializer(subscription, context=self.get_serializer_context()).data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Crée une contrainte d'exclusion qui empêche l'existence de deux mandats pour les mêmes # personnes et conseils si leurs plages de dates se chevauchent (l'opérateur && exprime # l'intersection) AJOUTER_EXCLUSION = """ CREATE EXTENSION IF NOT EXISTS btree_gist; ALTER TABLE {table} ADD CONSTRAINT {table}_unique_within_dates EXCLUDE USING gist (person_id WITH =, conseil_id WITH =, dates WITH &&); """ RETIRER_EXCLUSION = """ ALTER TABLE {table} DROP CONSTRAINT {table}_unique_within_dates; """ # faux défaut default_date_range = DateRange(lower=datetime.date(2000, 1, 1), upper=datetime.date(2000, 1, 2)) class Migration(migrations.Migration): dependencies = [ ("elus", "0007_nettoyage_champs"), ] operations = [ migrations.RunSQL(sql=CREER_EXTENSION_BTREE_GIST, reverse_sql=migrations.RunSQL.noop), migrations.AddField( model_name="mandatdepartemental", name="dates", field=django.contrib.postgres.fields.ranges.DateRangeField(
class MandatMunicipal(MandatAbstrait): DEFAULT_DATE_RANGE = DateRange(date(2020, 6, 28), date(2026, 3, 31)) MANDAT_INCONNU = "INC" MANDAT_CONSEILLER_MAJORITE = "MAJ" MANDAT_CONSEILLER_OPPOSITION = "OPP" MANDAT_MAIRE = "MAI" MANDAT_MAIRE_ADJOINT = "ADJ" MANDAT_MAIRE_DA = "MDA" MANDAT_CHOICES = ( (MANDAT_INCONNU, "Situation au conseil inconnue"), (MANDAT_CONSEILLER_MAJORITE, "Conseiller⋅e municipal⋅e majoritaire"), (MANDAT_CONSEILLER_OPPOSITION, "Conseiller⋅e municipal⋅e minoritaire"), (MANDAT_MAIRE, "Maire"), (MANDAT_MAIRE_ADJOINT, "Adjoint⋅e au maire"), (MANDAT_MAIRE_DA, "Maire d'une commune déléguée ou associée"), ) MANDAT_EPCI_PAS_DE_MANDAT = "NON" MANDAT_EPCI_MANDAT_INCONNU = "INC" MANDAT_EPCI_MAJORITE = "MAJ" MANDAT_EPCI_OPPOSITION = "OPP" MANDAT_EPCI_PRESIDENT = "PRE" MANDAT_EPCI_VICE_PRESIDENT = "VPR" MANDAT_EPCI_CHOICES = ( (MANDAT_EPCI_PAS_DE_MANDAT, "Pas de mandat communautaire"), (MANDAT_EPCI_MANDAT_INCONNU, "Délégué⋅e, situation inconnue"), (MANDAT_EPCI_MAJORITE, "Délégué⋅e majoritaire"), (MANDAT_EPCI_OPPOSITION, "Délégué⋅e minoritaire"), (MANDAT_EPCI_PRESIDENT, "Président"), (MANDAT_EPCI_VICE_PRESIDENT, "Vice-Président"), ) reference = models.ForeignKey( "data_france.EluMunicipal", on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Référence dans le RNE", help_text= "La fiche correspondant à cet élu dans le Répertoire National des Élus", related_name="elus", related_query_name="elu", ) person = models.ForeignKey( "people.Person", verbose_name="Élu", on_delete=models.CASCADE, related_name="mandats_municipaux", related_query_name="mandat_municipal", ) conseil = models.ForeignKey( "data_france.Commune", verbose_name="Commune", blank=True, null=True, on_delete=models.CASCADE, ) mandat = models.CharField( "Type de mandat", max_length=3, choices=MANDAT_CHOICES, blank=False, default=MANDAT_INCONNU, ) delegations = ArrayField( verbose_name="Délégations", base_field=models.CharField(max_length=20, choices=DELEGATIONS_CHOICES), null=False, blank=True, default=list, ) communautaire = models.CharField( "Élu EPCI", max_length=3, choices=MANDAT_EPCI_CHOICES, default=MANDAT_EPCI_PAS_DE_MANDAT, ) class Meta: verbose_name_plural = "Mandats municipaux" ordering = ("conseil", "person") def __str__(self): if hasattr(self, "person") and hasattr(self, "conseil"): if self.conseil is None: return f"{self.person}, {genrer(self.person.gender, 'élu⋅e')} {genrer(self.person.gender, 'municipal⋅e')} (commune inconnue)" return f"{self.person}, {genrer(self.person.gender, 'élu⋅e')} à {self.conseil.nom_complet}" return "Nouveau mandat municipal" def titre_complet(self, conseil_avant=False): if self.mandat in [ self.MANDAT_INCONNU, self.MANDAT_CONSEILLER_MAJORITE, self.MANDAT_CONSEILLER_OPPOSITION, ]: if self.conseil and (self.conseil.code == "75056"): # cas spécial de paris : titre = genrer(self.person.gender, "conseiller⋅ère") else: titre = genrer(self.person.gender, "Conseiller⋅ère municipal⋅e") elif self.mandat == self.MANDAT_MAIRE_ADJOINT: titre = f"{genrer(self.person.gender, 'Adjoint⋅e')} au maire" else: titre = self.get_mandat_display() if self.conseil is None: return titre if conseil_avant: return f"{self.conseil.nom_complet}, {titre}" return f"{titre} {self.conseil.nom_avec_charniere}" def get_absolute_url(self): return reverse( viewname="elus:modifier_mandat_municipal", kwargs={"pk": self.id}, ) def get_delete_url(self): return reverse( viewname="elus:supprimer_mandat_municipal", kwargs={"pk": self.id}, )
def active_date_range(self): return DateRange(get_random_date(), get_random_date(future=True))
class MandatRegional(MandatAbstrait): DEFAULT_DATE_RANGE = DateRange(date(2021, 7, 1), date(2027, 3, 31)) MANDAT_INCONNU = "INC" MANDAT_CONSEILLER_MAJORITE = "MAJ" MANDAT_CONSEILLER_OPPOSITION = "OPP" MANDAT_PRESIDENT = "PRE" MANDAT_VICE_PRESIDENT = "VPR" MANDAT_CHOICES = ( (MANDAT_INCONNU, "Situation au conseil inconnue"), (MANDAT_CONSEILLER_MAJORITE, "Conseiller⋅e majoritaire"), (MANDAT_CONSEILLER_OPPOSITION, "Conseiller⋅e minoritaire"), (MANDAT_PRESIDENT, "Président"), (MANDAT_VICE_PRESIDENT, "Vice-Président"), ) person = models.ForeignKey( "people.Person", verbose_name="Élu", on_delete=models.CASCADE, related_name="mandats_regionaux", related_query_name="mandat_regional", ) conseil = models.ForeignKey( "data_france.CollectiviteRegionale", verbose_name="Conseil régional (ou de collectivité unique)", blank=True, null=True, on_delete=models.CASCADE, ) mandat = models.CharField( "Type de mandat", max_length=3, choices=MANDAT_CHOICES, blank=False, default=MANDAT_INCONNU, ) delegations = ArrayField( verbose_name="Délégations", base_field=models.CharField(max_length=20, choices=DELEGATIONS_CHOICES), null=False, blank=True, default=list, ) reference = models.ForeignKey( "data_france.EluRegional", on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Référence dans le RNE", help_text= "La fiche correspondant à cet élu dans le Répertoire National des Élus", related_name="elus", related_query_name="elu", ) class Meta: verbose_name = "Mandat régional" verbose_name_plural = "Mandats régionaux" ordering = ("conseil", "person") def __str__(self): if hasattr(self, "person") and hasattr(self, "conseil"): if self.person.gender == "M": elu = "élu" elif self.person.gender == "F": elu = "élue" else: elu = "élu⋅e" if self.conseil is None: return f"{self.person}, {genrer(self.person.gender, 'élu⋅e')} {genrer(self.person.gender, 'régional⋅e')} (lieu inconnu)" return f"{self.person}, {elu} au {self.conseil.nom}" return "Nouveau mandat régional" def titre_complet(self, conseil_avant=False): ctu = (self.conseil and self.conseil.type == CollectiviteRegionale.TYPE_COLLECTIVITE_UNIQUE) if self.mandat in [ self.MANDAT_INCONNU, self.MANDAT_CONSEILLER_MAJORITE, self.MANDAT_CONSEILLER_OPPOSITION, ]: if ctu: titre = genrer(self.person.gender, "Conseiller⋅ère") else: titre = genrer(self.person.gender, "Conseiller⋅ère régional⋅e") else: qualif = "" if ctu else " du conseil régional" titre = genrer( self.person.gender, f"{self.get_mandat_display()}{qualif}", ) if self.conseil is None: return titre nom_conseil = self.conseil.nom if conseil_avant: return f"{nom_conseil}, {titre}" if nom_conseil.startswith("Assemblée"): return f"{titre} à l'{nom_conseil}" return f"{titre} de {nom_conseil}" def get_absolute_url(self): return reverse( viewname="elus:modifier_mandat_regional", kwargs={"pk": self.id}, ) def get_delete_url(self): return reverse( viewname="elus:supprimer_mandat_regional", kwargs={"pk": self.id}, )
class MandatDepartemental(MandatAbstrait): DEFAULT_DATE_RANGE = DateRange(date(2021, 7, 1), date(2027, 3, 31)) MANDAT_INCONNU = "INC" MANDAT_CONSEILLER_MAJORITE = "MAJ" MANDAT_CONSEILLER_OPPOSITION = "OPP" MANDAT_PRESIDENT = "PRE" MANDAT_VICE_PRESIDENT = "VPR" MANDAT_CHOICES = ( (MANDAT_INCONNU, "Situation au conseil inconnue"), (MANDAT_CONSEILLER_MAJORITE, "Conseiller⋅e majoritaire"), (MANDAT_CONSEILLER_OPPOSITION, "Conseiller⋅e minoritaire"), (MANDAT_PRESIDENT, "Président⋅e"), (MANDAT_VICE_PRESIDENT, "Vice-Président⋅e"), ) person = models.ForeignKey( "people.Person", verbose_name="Élu", on_delete=models.CASCADE, related_name="mandats_departementaux", related_query_name="mandat_departemental", ) conseil = models.ForeignKey( "data_france.CollectiviteDepartementale", verbose_name="Conseil départemental (ou de métropole)", blank=True, null=True, on_delete=models.CASCADE, ) mandat = models.CharField( "Type de mandat", max_length=3, choices=MANDAT_CHOICES, blank=False, default=MANDAT_INCONNU, ) delegations = ArrayField( verbose_name="Délégations", base_field=models.CharField(max_length=20, choices=DELEGATIONS_CHOICES), null=False, blank=True, default=list, ) reference = models.ForeignKey( "data_france.EluDepartemental", on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Référence dans le RNE", help_text= "La fiche correspondant à cet élu dans le Répertoire National des Élus", related_name="elus", related_query_name="elu", ) class Meta: verbose_name = "Mandat départemental" verbose_name_plural = "Mandats départementaux" ordering = ("conseil", "person") def __str__(self): if hasattr(self, "person") and hasattr(self, "conseil"): if self.conseil is None: return f"{self.person}, {genrer(self.person.gender, 'élu⋅e')} {genrer(self.person.gender, 'départemental⋅e')} (lieu inconnu)" return f"{self.person}, {genrer(self.person.gender, 'élu.e')} au {self.conseil.nom}" return "Nouveau mandat départemental" def titre_complet(self, conseil_avant=False): if self.mandat in [ self.MANDAT_INCONNU, self.MANDAT_CONSEILLER_MAJORITE, self.MANDAT_CONSEILLER_OPPOSITION, ]: titre = genrer(self.person.gender, "Conseiller⋅ère") else: titre = genrer(self.person.gender, self.get_mandat_display()) if self.conseil is None: return titre if conseil_avant: return f"{self.conseil.nom}, {titre}" return f"{titre} {self.conseil.nom_avec_charniere}" def get_absolute_url(self): return reverse( viewname="elus:modifier_mandat_departemental", kwargs={"pk": self.id}, ) def get_delete_url(self): return reverse( viewname="elus:supprimer_mandat_departemental", kwargs={"pk": self.id}, )
class MandatConsulaire(MandatAbstrait): DEFAULT_DATE_RANGE = DateRange(date(2021, 6, 1), date(2027, 5, 31)) class Mandat(models.TextChoices): CONSEILLER = "C", "Conseiller⋅ère consulaire" MEMBRE_AFE = "M", "Conseiller⋅ère consulaire et membre de l'AFE" DELEGUE = "D", "Délégué⋅e consulaire" person = models.ForeignKey( "people.Person", verbose_name="Élu", on_delete=models.CASCADE, related_name="mandats_consulaires", related_query_name="mandat_consulaire", ) conseil = models.ForeignKey( "data_france.CirconscriptionConsulaire", verbose_name="Circonscription consulaire", blank=True, null=True, on_delete=models.CASCADE, ) mandat = models.CharField( verbose_name="Mandat", max_length=1, blank=False, choices=Mandat.choices, default=Mandat.CONSEILLER, ) def __str__(self): if hasattr(self, "person") and hasattr(self, "conseil"): if self.conseil is None: return f"{self.person}, {genrer(self.person.gender, self.get_mandat_display())} (circonscription inconnue)" return f"{self.person}, {genrer(self.person.gender, self.get_mandat_display())} ({self.conseil.nom})" return "Nouveau mandat consulaire" def titre_complet(self, conseil_avant=False): titre = genrer(self.person.gender, self.get_mandat_display()) conseil = (self.conseil.nom if self.conseil else "Circonscription consulaire non renseignée") if conseil_avant: return f"{conseil}, {titre}" return f"{titre} ({conseil})" def get_absolute_url(self): return reverse( viewname="elus:modifier_mandat_consulaire", kwargs={"pk": self.id}, ) def get_delete_url(self): return reverse( viewname="elus:supprimer_mandat_consulaire", kwargs={"pk": self.id}, ) class Meta: verbose_name_plural = "mandats consulaires"
class DateRangeSerializer(serializers.Serializer): range = DateRangeField(initial=DateRange(None, None))
class TestDateRangeField(FieldValues): """ Values for `ListField` with CharField as child. """ serializer_class = DateRangeSerializer valid_inputs = [ ({ 'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)' }, DateRange( **{ 'lower': datetime.date(2001, 1, 1), 'upper': datetime.date(2001, 2, 2), 'bounds': '[)' })), ({ 'upper': '2001-02-02', 'bounds': '[)' }, DateRange(**{ 'upper': datetime.date(2001, 2, 2), 'bounds': '[)' })), ({ 'lower': '2001-01-01', 'bounds': '[)' }, DateRange(**{ 'lower': datetime.date(2001, 1, 1), 'bounds': '[)' })), ({ 'empty': True }, DateRange(**{'empty': True})), ({}, DateRange()), ] invalid_inputs = [ ({ 'lower': 'a' }, [ 'Date has wrong format. Use one of these' ' formats instead: ' 'YYYY-MM-DD.' ]), ('not a dict', ['Expected a dictionary of items but got type "str".']), ({ 'lower': '2001-02-02', 'upper': '2001-01-01' }, ['The start of the range must not exceed the end of the range.']), ] outputs = [ (DateRange( **{ 'lower': datetime.date(2001, 1, 1), 'upper': datetime.date(2001, 2, 2) }), { 'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)' }), (DateRange(**{'empty': True}), { 'empty': True }), (DateRange(), { 'bounds': '[)', 'lower': None, 'upper': None }), ({ 'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)' }, { 'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)' }), ({ 'lower': datetime.date(2001, 1, 1), 'upper': datetime.date(2001, 2, 2), 'bounds': '[)' }, { 'lower': '2001-01-01', 'upper': '2001-02-02', 'bounds': '[)' }), ({ 'upper': '2001-02-02', 'bounds': '[)' }, { 'lower': None, 'upper': '2001-02-02', 'bounds': '[)' }), ({ 'lower': '2001-01-01', 'bounds': '[)' }, { 'lower': '2001-01-01', 'upper': None, 'bounds': '[)' }), ({}, {}), ] field = DateRangeField() def test_no_source_on_child(self): with pytest.raises(AssertionError) as exc_info: DateRangeField(child=serializers.IntegerField(source='other')) assert str(exc_info.value) == ( "The `source` argument is not meaningful when applied to a `child=` field. " "Remove `source=` from the field declaration.") def test_initial_value_of_field(self): serializer = DateRangeSerializer() assert serializer.data['range'] == { 'lower': None, 'upper': None, 'bounds': '[)' } def test_allow_empty(self): serializer = DateRangeWithAllowEmptyFalseSerializer(data={"range": {}}) with pytest.raises(serializers.ValidationError) as exc_info: serializer.is_valid(raise_exception=True) assert exc_info.value.detail == [ "This dictionary may not be empty." ] serializer = DateRangeWithAllowEmptyTrueSerializer(data={"range": {}}) assert serializer.is_valid()
def handle(self, **options): SpecialDaysOperation.objects.all().delete() Timetable.objects.all().delete() VehicleJourney.objects.all().delete() JourneyPattern.objects.all().delete() JourneyPatternSection.objects.all().delete() JourneyPatternTimingLink.objects.all().delete() Route.objects.all().delete() Line.objects.all().delete() Operator.objects.all().delete() for tnds_zone in settings.TNDS_ZONES: local_filename, headers = urlretrieve('ftp://%s:%[email protected]/%s.zip' % (settings.TNDS_USERNAME, settings.TNDS_PASSWORD, tnds_zone), filename=os.path.join(settings.TNDS_NEW_DIR, '%s.zip' % tnds_zone)) traveline_zip_file = zipfile.ZipFile(local_filename) for filename in traveline_zip_file.namelist(): logger.info("Processing file %s" % filename) try: with traveline_zip_file.open(filename) as xml_file: content = xmltodict.parse(xml_file) if content['TransXChange']['Services']['Service'].get('Mode') in ["bus", "coach"]: # Operator operator = content['TransXChange']['Operators']['Operator'] bus_operator, created = Operator.objects.get_or_create( id=operator['@id'], defaults={'code': operator['OperatorCode'], 'short_name': operator['OperatorShortName'], 'trading_name': operator['TradingName']}) # Service / Line service = content['TransXChange']['Services']['Service'] bus_line = Line.objects.create( line_id=service['Lines']['Line']['@id'], line_name=service['Lines']['Line']['LineName'], area=tnds_zone, filename=content['TransXChange']['@FileName'], description=service.get('Description', ''), operator=bus_operator, standard_origin=service['StandardService']['Origin'], standard_destination=service['StandardService']['Destination'], start_date=service['OperatingPeriod']['StartDate'], end_date=service['OperatingPeriod']['EndDate'], regular_days_of_week= list(service['OperatingProfile']['RegularDayType']['DaysOfWeek'].keys()) if 'OperatingProfile' in service and 'RegularDayType' in service['OperatingProfile'] and 'DaysOfWeek' in service['OperatingProfile']['RegularDayType'] else ('MondayToFriday',), bank_holiday_operation= list(service['OperatingProfile']['BankHolidayOperation']['DaysOfOperation'].keys()) if 'OperatingProfile' in service and 'BankHolidayOperation' in service['OperatingProfile'] and 'DaysOfOperation' in service['OperatingProfile']['BankHolidayOperation'] else None ) # Routes routes = content['TransXChange']['RouteSections']['RouteSection'] if routes.__class__ is not list: routes = list([routes]) routes_desc = list([content['TransXChange']['Routes']['Route']]) else: routes_desc = content['TransXChange']['Routes']['Route'] route_objects = [] for route_id in range(0, len(routes)): route = routes[route_id] stops = [] if route['RouteLink'].__class__ is list: next = None for stop in route['RouteLink']: # check if To from next item and From from current are the same if next and next != stop['From']['StopPointRef']: logger.error('route links are not sequentials in route %s' % routes_desc[route_id]['@id']) stops.append(stop['From']['StopPointRef']) next = stop['To']['StopPointRef'] stops.append(route['RouteLink'][-1]['To']['StopPointRef']) else: stops.append(route['RouteLink']['From']['StopPointRef']) stops.append(route['RouteLink']['To']['StopPointRef']) route_objects.append( Route(id=tnds_zone+'-'+routes_desc[route_id]['@id'], line=bus_line, stops_list=','.join(stops), description=routes_desc[route_id]['Description'])) if route_objects: Route.objects.bulk_create(route_objects) # Journey Pattern Sections journey_pattern_sections = \ content['TransXChange']['JourneyPatternSections']['JourneyPatternSection'] if journey_pattern_sections.__class__ is not list: journey_pattern_sections = list([journey_pattern_sections]) journey_pattern_section_objects = [] journey_pattern_timing_link_objects = [] for journey_pattern_section in journey_pattern_sections: journey_pattern_section_objects.append( JourneyPatternSection(id=tnds_zone+'-'+journey_pattern_section['@id'])) journey_pattern_timing_links = journey_pattern_section['JourneyPatternTimingLink'] if journey_pattern_timing_links.__class__ is not list: journey_pattern_timing_links = list([journey_pattern_timing_links]) for journey_pattern_timing_link in journey_pattern_timing_links: wait_time = xml_timedelta_to_python(journey_pattern_timing_link['To']['WaitTime']) \ if 'WaitTime' in journey_pattern_timing_link['To'] else None if not Stop.objects.filter(atco_code=journey_pattern_timing_link['From']['StopPointRef']): logger.error("Stop %s cannot be found in the database, creating a temporal entry" % journey_pattern_timing_link['From']['StopPointRef']) Stop.objects.create(atco_code=journey_pattern_timing_link['From']['StopPointRef'], naptan_code="Unknown", common_name="Unknown", indicator="Unknown", locality_name="", longitude=0, latitude=0) if not Stop.objects.filter(atco_code=journey_pattern_timing_link['To']['StopPointRef']): logger.error("Stop %s cannot be found in the database, creating a temporal entry" % journey_pattern_timing_link['To']['StopPointRef']) Stop.objects.create(atco_code=journey_pattern_timing_link['To']['StopPointRef'], naptan_code="Unknown", common_name="Unknown", indicator="Unknown", locality_name="", longitude=0, latitude=0) journey_pattern_timing_link_objects.append(JourneyPatternTimingLink( id=tnds_zone+'-'+journey_pattern_timing_link['@id'], stop_from_id=journey_pattern_timing_link['From']['StopPointRef'], stop_to_id=journey_pattern_timing_link['To']['StopPointRef'], stop_from_timing_status=journey_pattern_timing_link['From']['TimingStatus'], stop_to_timing_status=journey_pattern_timing_link['To']['TimingStatus'], stop_from_sequence_number=journey_pattern_timing_link['From']['@SequenceNumber'], stop_to_sequence_number=journey_pattern_timing_link['To']['@SequenceNumber'], run_time=xml_timedelta_to_python(journey_pattern_timing_link['RunTime']), wait_time=wait_time, journey_pattern_section_id=tnds_zone+'-'+journey_pattern_section['@id'] )) if journey_pattern_section_objects: JourneyPatternSection.objects.bulk_create(journey_pattern_section_objects) if journey_pattern_timing_link_objects: JourneyPatternTimingLink.objects.bulk_create(journey_pattern_timing_link_objects) # Journey Pattern journey_patterns = \ content['TransXChange']['Services']['Service']['StandardService']['JourneyPattern'] if journey_patterns.__class__ is not list: journey_patterns = list([journey_patterns]) journey_pattern_objects = [] for journey_pattern in journey_patterns: journey_pattern_objects.append(JourneyPattern( id=tnds_zone+'-'+journey_pattern['@id'], direction=journey_pattern['Direction'], route_id=tnds_zone+'-'+journey_pattern['RouteRef'], section_id=tnds_zone+'-'+journey_pattern['JourneyPatternSectionRefs']) ) if journey_pattern_objects: JourneyPattern.objects.bulk_create(journey_pattern_objects) # Journey journeys = content['TransXChange']['VehicleJourneys']['VehicleJourney'] journeys = list([journeys]) if journeys.__class__ is not list else journeys order_journey = 1 vehicle_journey_objects = [] special_days_operation = [] for journey in journeys: regular_days = [] nonoperation_bank_holidays = [] operation_bank_holidays = [] if 'OperatingProfile' in journey: element = journey['OperatingProfile'] if 'RegularDayType' in element and 'DaysOfWeek' in element['RegularDayType']: week_days_element = element['RegularDayType']['DaysOfWeek'] for day in list(week_days_element.keys()): if 'To' in day: day_range_bounds = [WEEKDAYS[i] for i in day.split('To')] day_range = range(day_range_bounds[0], day_range_bounds[1] + 1) regular_days += [calendar.day_name[i] for i in day_range] elif day == 'Weekend': regular_days += ["Saturday", "Sunday"] else: regular_days.append(day) # Special Days: if 'SpecialDaysOperation' in element: if 'DaysOfNonOperation' in element['SpecialDaysOperation'] and element['SpecialDaysOperation']['DaysOfNonOperation']: noopdays = element['SpecialDaysOperation']['DaysOfNonOperation']['DateRange'] noopdays = list([noopdays]) if noopdays.__class__ is not list else noopdays nonoperation_days = \ list(map(lambda x: SpecialDaysOperation(vehicle_journey_id=journey['PrivateCode'], days=DateRange(lower=x['StartDate'], upper=x['EndDate'], bounds="[]"), operates=False), noopdays)) special_days_operation += nonoperation_days if 'DaysOfOperation' in element['SpecialDaysOperation'] and element['SpecialDaysOperation']['DaysOfOperation']: opdays = element['SpecialDaysOperation']['DaysOfNonOperation']['DateRange'] opdays = list([opdays]) if opdays.__class__ is not list else opdays operation_days = \ list(map(lambda x: SpecialDaysOperation(vehicle_journey_id=journey['PrivateCode'], days=DateRange(lower=x['StartDate'], upper=x['EndDate'], bounds="[]"), operates=True), opdays)) special_days_operation += operation_days # Bank Holidays if 'BankHolidayOperation' in element: if 'DaysOfNonOperation' in element['BankHolidayOperation'] and element['BankHolidayOperation']['DaysOfNonOperation']: nonoperation_bank_holidays = list( element['BankHolidayOperation']['DaysOfNonOperation'].keys()) if 'DaysOfOperation' in element['BankHolidayOperation'] and element['BankHolidayOperation']['DaysOfOperation']: operation_bank_holidays = list( element['BankHolidayOperation']['DaysOfOperation'].keys()) vehicle_journey_objects.append(VehicleJourney( id=journey['PrivateCode'], journey_pattern_id=tnds_zone+'-'+journey['JourneyPatternRef'], departure_time=journey['DepartureTime'], days_of_week=' '.join(regular_days), nonoperation_bank_holidays=' '.join(nonoperation_bank_holidays), operation_bank_holidays=' '.join(operation_bank_holidays), order=order_journey) ) order_journey += 1 if vehicle_journey_objects: VehicleJourney.objects.bulk_create(vehicle_journey_objects) SpecialDaysOperation.objects.bulk_create(special_days_operation) xml_file.close() except Exception as e: logger.exception("Error while trying to process file %s, exception was %s" % (filename, e)) for vehicle_journey in VehicleJourney.objects.all(): Timetable.objects.bulk_create(vehicle_journey.generate_timetable()) for tnds_zone in settings.TNDS_ZONES: os.rename(os.path.join(settings.TNDS_NEW_DIR, '%s.zip' % tnds_zone), os.path.join(settings.TNDS_DIR, '%s.zip' % tnds_zone))
import reversion from django.contrib.gis.db.models.functions import Distance from django.contrib.postgres.fields import ArrayField, DateRangeField from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from django.db import models, connection from django.urls import reverse from django.utils import timezone from django.utils.functional import cached_property from django.utils.html import format_html from psycopg2._range import DateRange from agir.lib.display import genrer from agir.lib.history import HistoryMixin MUNICIPAL_DEFAULT_DATE_RANGE = DateRange(date(2020, 6, 28), date(2026, 3, 31)) DEPARTEMENTAL_DEFAULT_DATE_RANGE = DateRange(date(2015, 3, 29), date(2021, 3, 31)) REGIONAL_DEFAULT_DATE_RANGE = DateRange(date(2015, 12, 13), date(2021, 3, 31)) DELEGATIONS_CHOICES = ( ("social", "Action sociale"), ("civiles juridiques", "Affaires civiles et juridiques"), ("économie", "Affaires économiques"), ("école", "Affaires scolaires"), ("agriculture", "Agriculture"), ("veterans", "Anciens combattants"), ("cantines", "Cantines"), ("commerce", "Commerce"), ("cimetières", "Cimetières"), ("international", "Coopération internationale"),