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
2 changes: 2 additions & 0 deletions padam_django/apps/common/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def period_overlap(p1_start, p1_end, p2_start, p2_end):
return (min(p1_end, p2_end) - max(p1_start, p2_start)).days + 1 > 0
4 changes: 4 additions & 0 deletions padam_django/apps/fleet/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ class BusAdmin(admin.ModelAdmin):
@admin.register(models.Driver)
class DriverAdmin(admin.ModelAdmin):
pass

@admin.register(models.BusShift)
class BusShiftAdmin(admin.ModelAdmin):
pass
21 changes: 21 additions & 0 deletions padam_django/apps/fleet/factories.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import factory
from datetime import date, datetime, timezone
from faker import Faker

from . import models
Expand All @@ -7,6 +8,15 @@
fake = Faker(['fr'])


def random_time_zone_for_today():
today = date.today()
start_of_day = datetime(today.year, today.month, today.day)
end_of_day = datetime(today.year, today.month, today.day, 23, 59)

return fake.date_time_between(
start_date=start_of_day, end_date=end_of_day).replace(
tzinfo=timezone.utc).astimezone(tz=None)

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

Expand All @@ -19,3 +29,14 @@ class BusFactory(factory.django.DjangoModelFactory):

class Meta:
model = models.Bus

class BusShiftFactory(factory.django.DjangoModelFactory):
bus = factory.SubFactory(BusFactory)
driver = factory.SubFactory(DriverFactory)
line = factory.SubFactory('padam_django.apps.geography.factories.BusLineWith5StopFactory')

departure_time = factory.LazyFunction(random_time_zone_for_today)
arrival_time = factory.LazyFunction(random_time_zone_for_today)

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

from padam_django.apps.fleet.factories import BusShiftFactory


class Command(CreateDataBaseCommand):

help = 'Create few shifts'

def handle(self, *args, **options):
super().handle(*args, **options)
self.stdout.write(f'Creating {self.number} shifts ...')
BusShiftFactory.create_batch(size=self.number)
29 changes: 29 additions & 0 deletions padam_django/apps/fleet/migrations/0003_busshift.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 3.2.5 on 2024-06-11 14:44

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

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

operations = [
migrations.CreateModel(
name='BusShift',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('departure_time', models.DateTimeField()),
('arrival_time', models.DateTimeField()),
('bus', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='fleet.bus')),
('driver', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='fleet.driver')),
('line', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='geography.busline')),
],
options={
'verbose_name_plural': 'Bus shifts',
},
),
]
40 changes: 40 additions & 0 deletions padam_django/apps/fleet/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from django.db import models
from padam_django.apps.common.utils import period_overlap
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class Driver(models.Model):
Expand All @@ -16,3 +19,40 @@ class Meta:

def __str__(self):
return f"Bus: {self.licence_plate} (id: {self.pk})"


class BusShift(models.Model):
bus = models.ForeignKey(Bus, null=True, on_delete=models.SET_NULL)
driver = models.ForeignKey(Driver, null=True, on_delete=models.SET_NULL)
line = models.ForeignKey('geography.BusLine', null=True, on_delete=models.SET_NULL)
departure_time = models.DateTimeField()
arrival_time = models.DateTimeField()

def check_availability_for_entity(self, entity):
"""Takes an entity (example: {'driver': driver_object}).
Raises a ValidationError if the entity is not available for this shift"""
start = self.departure_time
end = self.arrival_time
other_shifts_taken_by_entity = BusShift.objects.filter(**entity).exclude(pk=self.pk).all()
for shift in other_shifts_taken_by_entity:
if period_overlap(shift.departure_time, shift.arrival_time, start, end):
entity_type = list(entity.keys())[0]
raise ValidationError(
_(f"The {entity_type} already is on another shift (line: {shift.line}, departure: {shift.departure_time}, arrival: {shift.arrival_time})"),
code="taken_" + entity_type)

def clean(self):
start = self.departure_time
end = self.arrival_time
if start and end and start > end:
raise ValidationError(
_(f"The departure time {start} must be inferior to the arrival time {end}.)"),
code="departure_time_inferior_to_arrival_time")
self.check_availability_for_entity({'driver': self.driver})
self.check_availability_for_entity({'bus': self.bus})

class Meta:
verbose_name_plural = "Bus shifts"

def __str__(self):
return f"Bus: {self.bus.licence_plate} - Driver: {self.driver.user} (id: {self.pk})"
13 changes: 13 additions & 0 deletions padam_django/apps/geography/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,16 @@
@admin.register(models.Place)
class PlaceAdmin(admin.ModelAdmin):
pass

@admin.register(models.BusStop)
class BusStopAdmin(admin.ModelAdmin):
pass

class BusLineStopInline(admin.TabularInline):
model = models.BusLineStop
extra = 0

@admin.register(models.BusLine)
class BusLineAdmin(admin.ModelAdmin):
inlines = (BusLineStopInline,)
pass
38 changes: 38 additions & 0 deletions padam_django/apps/geography/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,41 @@ class PlaceFactory(factory.django.DjangoModelFactory):

class Meta:
model = models.Place

class BusStopFactory(factory.django.DjangoModelFactory):
name = factory.LazyFunction(fake.street_name)
place = factory.SubFactory(PlaceFactory)

class Meta:
model = models.BusStop

class BusLineFactory(factory.django.DjangoModelFactory):
number = factory.LazyFunction(lambda: fake.pyint(min_value=0, max_value=300))

class Meta:
model = models.BusLine

class BusLineStopFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.BusLineStop

stop = factory.SubFactory(BusStopFactory)
line = factory.SubFactory(BusLineFactory)
sequence = factory.LazyFunction(lambda: fake.pyint(min_value=0, max_value=100))

class BusLineWith5StopFactory(BusLineFactory):
line_stop1 = factory.RelatedFactory(
BusLineStopFactory,
factory_related_name='line')
line_stop2 = factory.RelatedFactory(
BusLineStopFactory,
factory_related_name='line')
line_stop3 = factory.RelatedFactory(
BusLineStopFactory,
factory_related_name='line')
line_stop4 = factory.RelatedFactory(
BusLineStopFactory,
factory_related_name='line')
line_stop5 = factory.RelatedFactory(
BusLineStopFactory,
factory_related_name='line')
13 changes: 13 additions & 0 deletions padam_django/apps/geography/management/commands/create_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from padam_django.apps.common.management.base import CreateDataBaseCommand

from padam_django.apps.geography.factories import BusLineWith5StopFactory


class Command(CreateDataBaseCommand):

help = 'Create few lines'

def handle(self, *args, **options):
super().handle(*args, **options)
self.stdout.write(f'Creating {self.number} lines ...')
BusLineWith5StopFactory.create_batch(size=self.number)
13 changes: 13 additions & 0 deletions padam_django/apps/geography/management/commands/create_stops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from padam_django.apps.common.management.base import CreateDataBaseCommand

from padam_django.apps.geography.factories import BusStopFactory


class Command(CreateDataBaseCommand):

help = 'Create few stops'

def handle(self, *args, **options):
super().handle(*args, **options)
self.stdout.write(f'Creating {self.number} stops ...')
BusStopFactory.create_batch(size=self.number)
61 changes: 61 additions & 0 deletions padam_django/apps/geography/migrations/0002_auto_20240611_1444.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generated by Django 3.2.5 on 2024-06-11 14:44

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('geography', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='BusLine',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.IntegerField(default='0')),
],
options={
'ordering': ['number'],
},
),
migrations.AlterModelOptions(
name='place',
options={'ordering': ['name']},
),
migrations.AlterUniqueTogether(
name='place',
unique_together=set(),
),
migrations.CreateModel(
name='BusStop',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50, verbose_name='Name of the place')),
('place', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stop', to='geography.place')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='BusLineStop',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sequence', models.IntegerField(default='0')),
('line', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='geography.busline')),
('stop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='geography.busstop')),
],
options={
'ordering': ['sequence'],
'unique_together': {('line', 'sequence')},
},
),
migrations.AddField(
model_name='busline',
name='stops',
field=models.ManyToManyField(through='geography.BusLineStop', to='geography.BusStop'),
),
]
50 changes: 50 additions & 0 deletions padam_django/apps/geography/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,53 @@ class Meta:

def __str__(self):
return f"Place: {self.name} (id: {self.pk})"

class Meta:
ordering = ['name']


class BusStop(models.Model):
name = models.CharField("Name of the place", max_length=50)
place = models.OneToOneField(Place, on_delete=models.CASCADE, related_name='stop', null=True)

def __str__(self):
return f"Stop: {self.name} (id: {self.pk})"

class Meta:
ordering = ['name']


class BusLine(models.Model):
number = models.IntegerField(default='0')
stops = models.ManyToManyField(BusStop, through="BusLineStop")

def stops_list(self):
"""Returns the ordered list of the stops given by the line"""
return [x.stop for x in BusLineStop.objects.filter(line=self).order_by('sequence')]

def __str__(self):
return f"Line: {self.number} - {self.first_stop} - {self.last_stop} (id: {self.pk})"

@property
def first_stop(self):
return self.stops_list()[0].name if self.stops_list() else ''

@property
def last_stop(self):
return self.stops_list()[-1].name if self.stops_list() else ''

class Meta:
ordering = ['number']


class BusLineStop(models.Model):
stop = models.ForeignKey(BusStop, on_delete=models.CASCADE)
line = models.ForeignKey(BusLine, on_delete=models.CASCADE)
sequence = models.IntegerField(default='0') # stops of a given line are ordered by this sequence

class Meta:
ordering = ['sequence',]
unique_together = (("line", "sequence"), )

def __str__(self):
return f"{self.line.number} - {self.stop.name} (id: {self.pk})"