Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ ENV/
# Editors stuff
.idea
.vscode

# Python venv
/lib
lib64
pyvenv.cfg
/bin
/share
notes_perso.txt
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ run: ## Run the test server.

install: ## Install the python requirements.
pip install -r requirements.txt

migrate: ## Make migrations
python manage.py migrate

makemigrations: ## Generate database migrations from models state
python manage.py makemigrations
13 changes: 7 additions & 6 deletions padam_django/apps/common/management/commands/create_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@


class Command(BaseCommand):

help = 'Create test data'
help = "Create test data"

def handle(self, *args, **options):
management.call_command('create_users', number=5)
management.call_command('create_drivers', number=5)
management.call_command('create_buses', number=10)
management.call_command('create_places', number=30)
management.call_command("create_users", number=5)
management.call_command("create_drivers", number=5)
management.call_command("create_buses", number=10)
management.call_command("create_places", number=30)
management.call_command("create_bus_shifts", number=10)
management.call_command("create_bus_stops", number=10)
36 changes: 36 additions & 0 deletions padam_django/apps/fleet/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,42 @@ class BusAdmin(admin.ModelAdmin):
pass


class BusStopsInline(admin.TabularInline):
model = models.BusStop
ordering = ("datetime",)


@admin.register(models.BusShift)
class BusShiftAdmin(admin.ModelAdmin):
readonly_fields = (
"total_duration",
"start_datetime",
"end_datetime",
"has_enough_stops",
)
fieldsets = [
(
"Main",
{
"fields": [
"bus",
"driver",
"start_datetime",
"end_datetime",
"total_duration",
]
},
),
("Flags", {"fields": ["has_enough_stops"]}),
]
inlines = [BusStopsInline]


@admin.register(models.Driver)
class DriverAdmin(admin.ModelAdmin):
pass


@admin.register(models.BusStop)
class BusStopAdmin(admin.ModelAdmin):
pass
5 changes: 4 additions & 1 deletion padam_django/apps/fleet/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@


class FleetConfig(AppConfig):
name = 'padam_django.apps.fleet'
name = "padam_django.apps.fleet"

def ready(self):
import padam_django.apps.fleet.signals
14 changes: 14 additions & 0 deletions padam_django/apps/fleet/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class BusOtherShiftsOverlapException(Exception):
pass


class DriverOtherShiftsOverlapException(Exception):
pass


class StopWouldOverlapBusOtherShifts(Exception):
pass


class StopWouldOverlapDriverOtherShifts(Exception):
pass
22 changes: 20 additions & 2 deletions padam_django/apps/fleet/factories.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import factory
from faker import Faker
from django.utils import timezone

from . import models


fake = Faker(['fr'])
fake = Faker(["fr"])


class DriverFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory('padam_django.apps.users.factories.UserFactory')
user = factory.SubFactory("padam_django.apps.users.factories.UserFactory")

class Meta:
model = models.Driver
Expand All @@ -19,3 +20,20 @@ class BusFactory(factory.django.DjangoModelFactory):

class Meta:
model = models.Bus


class BusShiftFactory(factory.django.DjangoModelFactory):
bus = factory.SubFactory("padam_django.apps.fleet.factories.BusFactory")
driver = factory.SubFactory("padam_django.apps.fleet.factories.DriverFactory")

class Meta:
model = models.BusShift


class BusStopFactory(factory.django.DjangoModelFactory):
place = factory.SubFactory("padam_django.apps.geography.factories.PlaceFactory")
datetime = factory.Faker("date_time", tzinfo=timezone.get_current_timezone())
shift = factory.SubFactory("padam_django.apps.fleet.factories.BusShiftFactory")

class Meta:
model = models.BusStop
12 changes: 12 additions & 0 deletions padam_django/apps/fleet/management/commands/create_bus_shifts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from padam_django.apps.common.management.base import CreateDataBaseCommand

from padam_django.apps.fleet.factories import BusShiftFactory


class Command(CreateDataBaseCommand):
help = "Create few bus shifts"

def handle(self, *args, **options):
super().handle(*args, **options)
self.stdout.write(f"Creating {self.number} bus shifts ...")
BusShiftFactory.create_batch(size=self.number)
12 changes: 12 additions & 0 deletions padam_django/apps/fleet/management/commands/create_bus_stops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from padam_django.apps.common.management.base import CreateDataBaseCommand

from padam_django.apps.fleet.factories import BusStopFactory


class Command(CreateDataBaseCommand):
help = "Create few bus stops"

def handle(self, *args, **options):
super().handle(*args, **options)
self.stdout.write(f"Creating {self.number} bus stops ...")
BusStopFactory.create_batch(size=self.number)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.2.5 on 2023-06-11 15:42

import datetime
from django.db import migrations, models
import django.db.models.deletion
from django.utils.timezone import utc


class Migration(migrations.Migration):

replaces = [('fleet', '0003_busstop'), ('fleet', '0004_rename_stop_time_busstop_datetime'), ('fleet', '0005_busshift'), ('fleet', '0006_auto_20230610_1457'), ('fleet', '0007_alter_busstop_shift'), ('fleet', '0008_auto_20230610_1509'), ('fleet', '0009_alter_busstop_shift'), ('fleet', '0010_alter_busstop_datetime'), ('fleet', '0011_busshift_total_duration'), ('fleet', '0012_alter_busshift_total_duration'), ('fleet', '0013_alter_busstop_shift'), ('fleet', '0014_busshift_has_enough_stops'), ('fleet', '0015_alter_busstop_place'), ('fleet', '0016_auto_20230611_1507'), ('fleet', '0017_alter_busshift_total_duration'), ('fleet', '0018_alter_busshift_has_enough_stops')]

dependencies = [
('fleet', '0002_auto_20211109_1456'),
('geography', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='BusShift',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_datetime', models.DateTimeField(default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=utc), verbose_name='Shift start datetime')),
('end_datetime', models.DateTimeField(default=datetime.datetime(1, 1, 1, 0, 0, tzinfo=utc), verbose_name='Shift end datetime')),
('bus', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shifts', to='fleet.bus')),
('driver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shifts', to='fleet.driver')),
('total_duration', models.DurationField(default=datetime.timedelta(0), verbose_name='Total shift duration')),
('has_enough_stops', models.BooleanField(default=False, verbose_name='Has enough stops to be valid')),
],
),
migrations.CreateModel(
name='BusStop',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('datetime', models.DateTimeField(verbose_name='Bus stop datetime')),
('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stops', to='geography.place')),
('shift', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stops', to='fleet.busshift')),
],
),
]
4 changes: 4 additions & 0 deletions padam_django/apps/fleet/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .bus_model import Bus
from .bus_shift_model import BusShift
from .bus_stop_model import BusStop
from .driver_model import Driver
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
from django.db import models


class Driver(models.Model):
user = models.OneToOneField('users.User', on_delete=models.CASCADE, related_name='driver')

def __str__(self):
return f"Driver: {self.user.username} (id: {self.pk})"


class Bus(models.Model):
licence_plate = models.CharField("Name of the bus", max_length=10)

Expand Down
115 changes: 115 additions & 0 deletions padam_django/apps/fleet/models/bus_shift_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from datetime import datetime, MINYEAR, timedelta

from django.db import models
from django.db.models.query import QuerySet
from django.utils import timezone

from padam_django.apps.fleet.models.bus_stop_model import BusStop
from padam_django.apps.fleet.exceptions import (
DriverOtherShiftsOverlapException,
BusOtherShiftsOverlapException,
)

DEFAULT_DATETIME_FOR_MISSING_STOPS = datetime(
year=MINYEAR, month=1, day=1, tzinfo=timezone.get_current_timezone()
)


class BusShift(models.Model):
bus = models.ForeignKey(
"fleet.Bus", on_delete=models.CASCADE, related_name="shifts"
)
driver = models.ForeignKey(
"fleet.Driver", on_delete=models.CASCADE, related_name="shifts"
)
start_datetime = models.DateTimeField(
verbose_name="Shift start datetime", default=DEFAULT_DATETIME_FOR_MISSING_STOPS
)
end_datetime = models.DateTimeField(
verbose_name="Shift end datetime", default=DEFAULT_DATETIME_FOR_MISSING_STOPS
)
total_duration = models.DurationField(
verbose_name="Total shift duration", default=timedelta(days=0)
)
has_enough_stops = models.BooleanField(
verbose_name="Has enough stops to be valid", default=False
)

def save(self, *args, **kwargs):
ordered_stops = self.get_ascending_linked_stops()
self.update_stops_related_fields(ordered_stops)

# Must be done after update_stops_related_fields
self.update_total_duration()

return super().save(*args, **kwargs)

def bus_has_overlapping_shifts(self) -> bool:
chosen_bus_shifts: QuerySet[BusShift] = self.bus.shifts
return self.shifts_overlap_with_self(chosen_bus_shifts)

def driver_has_overlapping_shifts(self) -> bool:
driver_shifts: QuerySet[BusShift] = self.driver.shifts
return self.shifts_overlap_with_self(driver_shifts)

def shifts_overlap_with_self(self, shifts: QuerySet) -> bool:
return self.shifts_start_overlap_with_self(
shifts
) or self.shifts_end_overlap_with_self(shifts)

def shifts_start_overlap_with_self(self, shifts: QuerySet) -> bool:
return (
shifts.filter(
start_datetime__gt=self.start_datetime,
start_datetime__lt=self.end_datetime,
)
.exclude(pk=self.pk)
.exists()
)

def shifts_end_overlap_with_self(self, shifts: QuerySet) -> bool:
return (
shifts.filter(
end_datetime__gt=self.start_datetime,
end_datetime__lt=self.end_datetime,
)
.exclude(pk=self.pk)
.exists()
)

def update_total_duration(self) -> None:
self.total_duration = self.end_datetime - self.start_datetime
return

def get_ascending_linked_stops(self) -> QuerySet[BusStop]:
stops: QuerySet[BusStop] = self.stops.all()
return stops.order_by("datetime")

def update_stops_related_fields(self, ordered_stops: QuerySet[BusStop]) -> None:
self.update_has_enough_stops(ordered_stops)
self.update_start_datetime(ordered_stops)
self.update_end_datetime(ordered_stops)
return

def update_has_enough_stops(self, stops: QuerySet[BusStop]) -> bool:
self.has_enough_stops = len(stops) >= 2
return

def update_start_datetime(self, ordered_stops: QuerySet[BusStop]) -> None:
first_stop: BusStop = ordered_stops.first()
if first_stop is not None:
self.start_datetime = first_stop.datetime
else:
self.start_datetime = DEFAULT_DATETIME_FOR_MISSING_STOPS
return

def update_end_datetime(self, ordered_stops: QuerySet[BusStop]) -> None:
last_stop: BusStop = ordered_stops.last()
if last_stop is not None:
self.end_datetime = last_stop.datetime
else:
self.end_datetime = DEFAULT_DATETIME_FOR_MISSING_STOPS
return

def __str__(self):
return f"BusShift: {self.bus} by {self.driver} from {self.start_datetime} to {self.end_datetime} (id: {self.pk})"
14 changes: 14 additions & 0 deletions padam_django/apps/fleet/models/bus_stop_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.db import models


class BusStop(models.Model):
place = models.ForeignKey(
"geography.Place", on_delete=models.CASCADE, related_name="stops"
)
datetime = models.DateTimeField(verbose_name="Bus stop datetime")
shift = models.ForeignKey(
"fleet.BusShift", on_delete=models.CASCADE, related_name="stops"
)

def __str__(self):
return f"BusStop: {self.place} the {self.datetime.date()} at {self.datetime.time()} (id: {self.pk})"
10 changes: 10 additions & 0 deletions padam_django/apps/fleet/models/driver_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.db import models


class Driver(models.Model):
user = models.OneToOneField(
"users.User", on_delete=models.CASCADE, related_name="driver"
)

def __str__(self):
return f"Driver: {self.user.username} (id: {self.pk})"
2 changes: 2 additions & 0 deletions padam_django/apps/fleet/signals/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .bus_stop_signals import *
from .bus_shift_signals import *
Loading