From 7355f763de9293060c44bd6d85ac68b1341dd5d1 Mon Sep 17 00:00:00 2001 From: juanny Date: Thu, 31 Oct 2024 01:22:34 +0100 Subject: [PATCH 1/2] new version of the project --- README.md | 90 ++----------------- padam_django/apps/shifts/__init__.py | 0 padam_django/apps/shifts/admin.py | 19 ++++ padam_django/apps/shifts/apps.py | 7 ++ .../apps/shifts/migrations/0001_initial.py | 40 +++++++++ .../apps/shifts/migrations/__init__.py | 0 padam_django/apps/shifts/modelform.py | 59 ++++++++++++ padam_django/apps/shifts/models.py | 68 ++++++++++++++ padam_django/settings.py | 1 + 9 files changed, 202 insertions(+), 82 deletions(-) create mode 100644 padam_django/apps/shifts/__init__.py create mode 100644 padam_django/apps/shifts/admin.py create mode 100644 padam_django/apps/shifts/apps.py create mode 100644 padam_django/apps/shifts/migrations/0001_initial.py create mode 100644 padam_django/apps/shifts/migrations/__init__.py create mode 100644 padam_django/apps/shifts/modelform.py create mode 100644 padam_django/apps/shifts/models.py diff --git a/README.md b/README.md index 8e1a8c29..7281dfcd 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,19 @@ -# Test technique Django / Backend -L'objectif de l'exercice ci-dessous est de modéliser une base de données à partir de spécifications métiers et de -concevoir une interface simple de gestion de trajets de bus, en utilisant l'admin de Django. +### Tester l'interface de gestion des trajets de bus -Pour réaliser le test, pensez à fork ce repository. Idéalement, ouvrir une PR à la fin. - -## Critères d'évaluation - -- Documentation et clarté du code -- Modélisation de la base de donnée -- Maîtrise du framework Django - -## Stack Technique - -| Nom | Version | -| ------ | ------- | -| Python | 3.7 | -| Django | 3.2.5 | - - - Le projet à été réalisé en utilisant Python 3.7. Vous êtes libre d'utiliser une autre version mais c'est celle que - nous vous conseillons. - - La base de donnée est au choix. Le projet est configuré pour utiliser `sqlite` par défaut. - -### Démarrer le projet - -*Depuis votre virtualenv Python 3.7*: +Une fois les migrations faites, il suffit de créer un super utilisateur pour pouvoir se connecter à l'admin Django: ``` -make install -make migrate -make run +python manage.py createsuperuser ``` -Des scripts sont à votre disposition pour vous permettre de rapidement créer de la donnée et de prendre le projet en -main: - - - `create_data` - - `create_buses` - - `create_drivers` - - `create_places` - - `create_users` - -Par exemple: +Puis, il faut créer les données de base : ``` -python manage.py create_drivers -n 5 +python manage.py create_data ``` -## Sujet - -### Description - -Un trajet en bus (`BusShift`) est composé des éléments suivants: - -- Un bus: (`Bus`). -- Un chauffeur: (`Driver`). -- Entre 2 et une infinité d'arrêts (`BusStop`). -- L'heure de départ est déterminée par l'heure de passage au premier arrêt. -- L'heure d'arrivée est déterminée par l'heure de passage au dernier arrêt. -- Il est possible de déduire le temps total nécessaire pour effectuer le trajet depuis l'heure de départ et l'heure d'arrivée. - -La structure de projet qui vous est proposée comprends déjà les models suivants: - - `Bus` - - `Driver` - - `Place` - - `User` (étends le model [AbstractUser de Django](https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#substituting-a-custom-user-model)) - -### Objectifs - -#### Implémenter les modèles `BusShift` and `BusStop` à la base de code existante - -L'implémentation de ces deux modèles est libre et laissée à votre appréciation. Les contraintes métiers suivantes -doivent être respectées: - - - Un même bus ne peut être assigné, en même temps, à plusieurs trajets dont les heures de début et fin se - chevaucheraient. - - Il en va de même pour les chauffeurs. - -#### Fournir une interface de gestion des trajets de bus - -Il doit être possible, pour un utilisateur, de créer ou de modifier des trajets de bus (`BusShift`) en utilisant l'admin -de django. - -**Note**: Il existe plusieurs solutions pour concevoir cette fonctionnalité. Certaines seront peut être plus couteuse -en temps que d'autres ... - -### Conseils +- Et enfin, il faut lancer le serveur. +- Si vous voulez, vous pouvez créer des bus stop directement dans l'interface de création des bus shifts. - - Ne passez pas plus de 4 heures sur un sujet (le but est d'évaluer vos compétences, pas de réduire votre temps libre à néant ;-)) - - Privilégier la qualité et les bonnes pratiques. - - Vous pouvez réduire le périmètre du projet si vous manquez de temps. Une ébauche de réponse est déjà une bonne chose. - - Soyez prêt à présenter le sujet, à justifier vos choix et à parler de comment vous auriez fait les parties que vous avez laisser de côté. +**Note** : J'ai laissé de côté les tests unitaires. \ No newline at end of file diff --git a/padam_django/apps/shifts/__init__.py b/padam_django/apps/shifts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/padam_django/apps/shifts/admin.py b/padam_django/apps/shifts/admin.py new file mode 100644 index 00000000..39e3ad27 --- /dev/null +++ b/padam_django/apps/shifts/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +from .models import BusShift, BusStop +from .modelform import BusShiftForm + + +class BusStopAdmin(admin.ModelAdmin): + """ Class to manage the BusStop model through Django admin """ + list_display = ('place', 'planned_time') + + + +class BusShiftAdmin(admin.ModelAdmin): + """ Class to manage the BusShift model through Django admin """ + form = BusShiftForm + list_display = ('bus', 'driver', 'start_time', 'end_time', 'total_duration_seconds') + + +admin.site.register(BusShift, BusShiftAdmin) +admin.site.register(BusStop, BusStopAdmin) \ No newline at end of file diff --git a/padam_django/apps/shifts/apps.py b/padam_django/apps/shifts/apps.py new file mode 100644 index 00000000..39d5727e --- /dev/null +++ b/padam_django/apps/shifts/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ShiftsConfig(AppConfig): + """ Configuration class for the shifts app """ + default_auto_field = 'django.db.models.BigAutoField' + name = 'padam_django.apps.shifts' diff --git a/padam_django/apps/shifts/migrations/0001_initial.py b/padam_django/apps/shifts/migrations/0001_initial.py new file mode 100644 index 00000000..98c2c6f1 --- /dev/null +++ b/padam_django/apps/shifts/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2.5 on 2024-10-31 00:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('geography', '0001_initial'), + ('fleet', '0002_auto_20211109_1456'), + ] + + operations = [ + migrations.CreateModel( + name='BusStop', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('planned_time', models.DateTimeField()), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bus_stops', to='geography.place')), + ], + options={ + 'ordering': ['planned_time'], + 'unique_together': {('place', 'planned_time')}, + }, + ), + migrations.CreateModel( + name='BusShift', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_time', models.DateTimeField(blank=True, null=True)), + ('end_time', models.DateTimeField(blank=True, null=True)), + ('bus', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shifts', to='fleet.bus')), + ('bus_stops', models.ManyToManyField(related_name='bus_shifts', to='shifts.BusStop')), + ('driver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bus_shifts', to='fleet.driver')), + ], + ), + ] diff --git a/padam_django/apps/shifts/migrations/__init__.py b/padam_django/apps/shifts/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/padam_django/apps/shifts/modelform.py b/padam_django/apps/shifts/modelform.py new file mode 100644 index 00000000..895b2d79 --- /dev/null +++ b/padam_django/apps/shifts/modelform.py @@ -0,0 +1,59 @@ +from django import forms +from padam_django.apps.shifts.models import BusShift + +class BusShiftForm(forms.ModelForm): + """ Class to manage the form for the Shift model through Django admin """ + class Meta: + model = BusShift + fields = ['bus', 'driver', 'bus_stops'] + + + def clean(self): + """ Validation of the criterias for the shifts """ + cleaned_data = super().clean() + bus_stops = cleaned_data.get('bus_stops') + + if bus_stops and bus_stops.count() < 2: + raise forms.ValidationError("A shift must have at least 2 bus stops.") + + if bus_stops: + start_time = bus_stops.order_by('planned_time').first().planned_time + end_time = bus_stops.order_by('planned_time').last().planned_time + else: + return cleaned_data + + cleaned_data['start_time'] = start_time + cleaned_data['end_time'] = end_time + + if start_time and end_time: + overlapping_bus = BusShift.objects.filter( + bus=self.cleaned_data.get('bus'), + start_time__lt=cleaned_data['end_time'], + end_time__gt=cleaned_data['start_time'] + ).exclude(id=self.instance.pk) + + if overlapping_bus.exists(): + raise forms.ValidationError("This shift overlaps with another shift for the same bus.") + + overlapping_driver = BusShift.objects.filter( + driver=self.cleaned_data.get('driver'), + start_time__lt=cleaned_data['end_time'], + end_time__gt=cleaned_data['start_time'] + ).exclude(id=self.instance.pk) + + if overlapping_driver.exists(): + raise forms.ValidationError("This shift overlaps with another shift for the same driver.") + + return cleaned_data + + def save(self, commit=True): + """ Save the bus shift instance """ + bus_shift = super().save(commit=False) + bus_shift.start_time = self.cleaned_data['start_time'] + bus_shift.end_time = self.cleaned_data['end_time'] + + if commit: + bus_shift.save() + bus_shift.bus_stops.set(self.cleaned_data['bus_stops']) + + return bus_shift diff --git a/padam_django/apps/shifts/models.py b/padam_django/apps/shifts/models.py new file mode 100644 index 00000000..9ac8197e --- /dev/null +++ b/padam_django/apps/shifts/models.py @@ -0,0 +1,68 @@ +from django.db import models + + + +class BusStop(models.Model): + """ Model representing a bus stop. """ + place = models.ForeignKey('geography.Place', on_delete=models.CASCADE, related_name='bus_stops') + planned_time = models.DateTimeField(auto_now=False, auto_now_add=False) + + class Meta: + ordering = ['planned_time'] + # Two Bus stops can't have the same planned time and place + unique_together = ['place', 'planned_time'] + + def __str__(self): + """ String for representing the BusStop instance. """ + planned_time = self.planned_time.strftime("%B %d, %Y, %I:%M %p") + return f"{self.place.name.upper()} at {planned_time}" + + +class BusShift(models.Model): + """ Model representing a bus shift. """ + bus = models.ForeignKey('fleet.Bus', on_delete=models.CASCADE,null=False, blank=False, related_name='shifts') + driver = models.ForeignKey('fleet.Driver', on_delete=models.CASCADE, null=False,blank=False,related_name='bus_shifts') + bus_stops = models.ManyToManyField(BusStop, blank=False, related_name='bus_shifts') + start_time = models.DateTimeField(auto_now=False, auto_now_add=False, null=True, blank=True) + end_time = models.DateTimeField(auto_now=False, auto_now_add=False, null=True, blank=True) + + def __str__(self): + """ String representation of the BusShift instance. """ + return f"Bus Shift: {self.bus} with driver {self.driver} from {self.start_time} to {self.end_time}" + + def calculate_start_time(self): + """Returns the start time of the shift, which is the time of the first bus stop.""" + if not self.pk: + return + + ordered_stops = self.bus_stops.order_by('planned_time') + if ordered_stops.exists(): + self.start_time = ordered_stops.first().planned_time + + def calculate_end_time(self): + """" Returns the end time of the shift which is the time of the last bus stop. """ + if not self.pk: + return + ordered_stops = self.bus_stops.order_by('planned_time') + if ordered_stops.exists(): + self.end_time = ordered_stops.last().planned_time + + + @property + def total_duration_seconds(self): + """Calculates the total duration (in seconds) based on start and end times of the shift.""" + if self.start_time and self.end_time: + time_difference = self.end_time - self.start_time + return time_difference.total_seconds() + return + + def save(self, *args, **kwargs): + """ Save the BusShift instance """ + super().save(*args, **kwargs) + if self.bus_stops.exists(): + self.calculate_start_time() + self.calculate_end_time() + super().save(update_fields=['start_time', 'end_time']) # Sauvegarder uniquement les champs modifiés + + + diff --git a/padam_django/settings.py b/padam_django/settings.py index 129e922c..22b9b57d 100644 --- a/padam_django/settings.py +++ b/padam_django/settings.py @@ -45,6 +45,7 @@ 'padam_django.apps.fleet', 'padam_django.apps.geography', 'padam_django.apps.users', + 'padam_django.apps.shifts', ] MIDDLEWARE = [ From 101281645fcc4b0d0e028934bdbbddeb80a1d5ca Mon Sep 17 00:00:00 2001 From: juanny Date: Thu, 31 Oct 2024 02:52:31 +0100 Subject: [PATCH 2/2] change one description --- padam_django/apps/shifts/modelform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/padam_django/apps/shifts/modelform.py b/padam_django/apps/shifts/modelform.py index 895b2d79..9edb2673 100644 --- a/padam_django/apps/shifts/modelform.py +++ b/padam_django/apps/shifts/modelform.py @@ -9,7 +9,7 @@ class Meta: def clean(self): - """ Validation of the criterias for the shifts """ + """ Method to validate the form data based on all criteria """ cleaned_data = super().clean() bus_stops = cleaned_data.get('bus_stops')