From e3d5c62bbde07ae43e53263205a62808d04bd6b3 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:15:18 +0200 Subject: [PATCH 1/9] Add entity relationship diagram --- padam_test-diagram.pdf | Bin 0 -> 12216 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 padam_test-diagram.pdf diff --git a/padam_test-diagram.pdf b/padam_test-diagram.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b15eb6b6826f2c3f8d454f0758a68ec9a8dc28b0 GIT binary patch literal 12216 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 literal 0 HcmV?d00001 From 7357bdecbe02742765fc17915beb5761a094e5c3 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:15:52 +0200 Subject: [PATCH 2/9] Add pre-commit --- .pre-commit-config.yaml | 14 ++++++++++++++ requirements.txt | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..f73a2eaa --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +repos: + - repo: local + hooks: + - id: black + name: apply black + language: system + types: [python] + entry: pipenv run black + - id: ruff + name: ruff + language: system + types: [python] + entry: pipenv run ruff check + args: [--fix] diff --git a/requirements.txt b/requirements.txt index 89c9f0c8..862d173f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,5 @@ ipython==7.25.0 factory-boy==3.2.0 Faker==8.10.1 + +pre-commit==2.21.0 From 851fbb56676920b7a7af4bf98d216a41b22d9c85 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:28:44 +0200 Subject: [PATCH 3/9] Update diagram --- padam_test-diagram.pdf | Bin 12216 -> 12555 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/padam_test-diagram.pdf b/padam_test-diagram.pdf index b15eb6b6826f2c3f8d454f0758a68ec9a8dc28b0..9af63615c995551dd9aa6baafdc95dbc861aa197 100644 GIT binary patch delta 8658 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 Z6Ot+m&kX!C4-GaDzz4!+VUbmr`+vCEe|G=? delta 8444 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 ab9FO=x_LpZtg*Pc1-J#Un3!Z#W&aNj*ztA% From f2a1b1c68c3c7d3a497498c5a082a938e3eb3129 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:29:33 +0200 Subject: [PATCH 4/9] Add command in makefile --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 4062f4c4..9d0af543 100644 --- a/Makefile +++ b/Makefile @@ -3,3 +3,6 @@ run: ## Run the test server. install: ## Install the python requirements. pip install -r requirements.txt + +migrate: ## Run the test server. + python manage.py migrate From 8b15c620552bef1127bcce6b517bfdf732ddd7e0 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:38:08 +0200 Subject: [PATCH 5/9] Add shift apps --- padam_django/apps/shift/__init__.py | 0 padam_django/apps/shift/admin.py | 38 +++++++ padam_django/apps/shift/apps.py | 9 ++ padam_django/apps/shift/factories.py | 36 +++++++ padam_django/apps/shift/forms.py | 32 ++++++ .../apps/shift/migrations/0001_initial.py | 100 ++++++++++++++++++ .../apps/shift/migrations/__init__.py | 0 padam_django/apps/shift/models.py | 41 +++++++ padam_django/apps/shift/receivers.py | 23 ++++ padam_django/apps/shift/tests.py | 96 +++++++++++++++++ padam_django/apps/shift/views.py | 1 + padam_django/settings.py | 83 ++++++++------- pyproject.toml | 32 ++++++ requirements.txt | 2 + 14 files changed, 452 insertions(+), 41 deletions(-) create mode 100644 padam_django/apps/shift/__init__.py create mode 100644 padam_django/apps/shift/admin.py create mode 100644 padam_django/apps/shift/apps.py create mode 100644 padam_django/apps/shift/factories.py create mode 100644 padam_django/apps/shift/forms.py create mode 100644 padam_django/apps/shift/migrations/0001_initial.py create mode 100644 padam_django/apps/shift/migrations/__init__.py create mode 100644 padam_django/apps/shift/models.py create mode 100644 padam_django/apps/shift/receivers.py create mode 100644 padam_django/apps/shift/tests.py create mode 100644 padam_django/apps/shift/views.py create mode 100644 pyproject.toml diff --git a/padam_django/apps/shift/__init__.py b/padam_django/apps/shift/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/padam_django/apps/shift/admin.py b/padam_django/apps/shift/admin.py new file mode 100644 index 00000000..fcfc2b8a --- /dev/null +++ b/padam_django/apps/shift/admin.py @@ -0,0 +1,38 @@ +from django.contrib import admin + +from padam_django.apps.shift.forms import ScheduleStopForm + +from .models import BusShift, BusStop, ScheduleStop + + +@admin.register(BusShift) +class BusShiftAdmin(admin.ModelAdmin): + # Make field not last stop, first_stop not editable + readonly_fields = ("first_stop", "last_stop") + list_display = ( + "bus", + "driver", + "get_stop_count", + "get_validation_stop_information", + ) + + @admin.display(description="Stop count") + def get_stop_count(self, obj): + return ScheduleStop.objects.filter(bus_shift=obj.id).count() + + @admin.display(description="Stop count validation") + def get_validation_stop_information(self, obj): + stop_count = ScheduleStop.objects.filter(bus_shift=obj.id).count() + if stop_count < 2: + return "Missing bus stops" + return "Valid bus stops count" + + +@admin.register(BusStop) +class BusStopAdmin(admin.ModelAdmin): + pass + + +@admin.register(ScheduleStop) +class ScheduleStopAdmin(admin.ModelAdmin): + form = ScheduleStopForm diff --git a/padam_django/apps/shift/apps.py b/padam_django/apps/shift/apps.py new file mode 100644 index 00000000..243a7989 --- /dev/null +++ b/padam_django/apps/shift/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class ShiftConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "padam_django.apps.shift" + + def ready(self): + pass diff --git a/padam_django/apps/shift/factories.py b/padam_django/apps/shift/factories.py new file mode 100644 index 00000000..5901ab4a --- /dev/null +++ b/padam_django/apps/shift/factories.py @@ -0,0 +1,36 @@ +from random import randint + +import factory +from django.utils import timezone + +from padam_django.apps.fleet.factories import BusFactory, DriverFactory +from padam_django.apps.geography.factories import PlaceFactory + +from . import models + + +class BusStopFactory(factory.django.DjangoModelFactory): + name = factory.Sequence(lambda n: "Bus stop %01d" % n) + place = factory.SubFactory(PlaceFactory) + + class Meta: + model = models.BusStop + + +class BusShiftFactory(factory.django.DjangoModelFactory): + bus = factory.SubFactory(BusFactory) + driver = factory.SubFactory(DriverFactory) + first_stop = timezone.now() + last_stop = timezone.now() + + class Meta: + model = models.BusShift + + +class ScheduleStopFactory(factory.django.DjangoModelFactory): + bus_shift = factory.SubFactory(BusShiftFactory) + bus_stop = factory.SubFactory(BusStopFactory) + arrival = timezone.now() + timezone.timedelta(hours=randint(7, 22)) + + class Meta: + model = models.ScheduleStop diff --git a/padam_django/apps/shift/forms.py b/padam_django/apps/shift/forms.py new file mode 100644 index 00000000..96ca0319 --- /dev/null +++ b/padam_django/apps/shift/forms.py @@ -0,0 +1,32 @@ +from django import forms +from django.core.exceptions import ValidationError + +from .models import BusShift, ScheduleStop + + +class ScheduleStopForm(forms.ModelForm): + class Meta: + model = ScheduleStop + fields = "__all__" + + def clean(self): + cleaned_data = super().clean() + bus_shift = cleaned_data["bus_shift"] + arrival = cleaned_data["arrival"] + + # Check bus + bus_shifts = BusShift.objects.filter( + bus=bus_shift.bus, first_stop__lte=arrival, last_stop__gte=arrival + ).count() + + if bus_shifts > 0: + raise ValidationError("Bus is not available") + + driver_shifts = BusShift.objects.filter( + driver=bus_shift.driver, first_stop__lte=arrival, last_stop__gte=arrival + ).count() + + if driver_shifts > 0: + raise ValidationError("Driver is not available") + + return cleaned_data diff --git a/padam_django/apps/shift/migrations/0001_initial.py b/padam_django/apps/shift/migrations/0001_initial.py new file mode 100644 index 00000000..533f84b4 --- /dev/null +++ b/padam_django/apps/shift/migrations/0001_initial.py @@ -0,0 +1,100 @@ +# Generated by Django 3.2.5 on 2024-06-14 07:29 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("geography", "0001_initial"), + ("fleet", "0002_auto_20211109_1456"), + ] + + operations = [ + migrations.CreateModel( + name="BusShift", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("first_stop", models.DateTimeField(blank=True, null=True)), + ("last_stop", models.DateTimeField(blank=True, null=True)), + ( + "bus", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="fleet.bus" + ), + ), + ( + "driver", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="fleet.driver" + ), + ), + ], + ), + migrations.CreateModel( + name="BusStop", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "name", + models.CharField( + max_length=20, verbose_name="Name of the bus stop" + ), + ), + ( + "place", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="geography.place", + ), + ), + ], + ), + migrations.CreateModel( + name="ScheduleStop", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("arrival", models.DateTimeField()), + ( + "bus_shift", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="shift.busshift" + ), + ), + ( + "bus_stop", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="shift.busstop" + ), + ), + ], + ), + ] diff --git a/padam_django/apps/shift/migrations/__init__.py b/padam_django/apps/shift/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/padam_django/apps/shift/models.py b/padam_django/apps/shift/models.py new file mode 100644 index 00000000..ab761d8d --- /dev/null +++ b/padam_django/apps/shift/models.py @@ -0,0 +1,41 @@ +import uuid + +from django.db import models + + +class BusStop(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField("Name of the bus stop", max_length=20) + place = models.ForeignKey("geography.place", on_delete=models.PROTECT) + + def __str__(self): + return f"Name: {self.name} (id: {self.pk})" + + +class BusShift(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + bus = models.ForeignKey("fleet.bus", on_delete=models.PROTECT) + driver = models.ForeignKey("fleet.driver", on_delete=models.PROTECT) + first_stop = models.DateTimeField( + auto_now=False, auto_now_add=False, null=True, blank=True + ) # Use this fields as "cache" + last_stop = models.DateTimeField( + auto_now=False, auto_now_add=False, null=True, blank=True + ) # Use this fields as "cache" + + @property + def shift_duration(self): + return self.last_stop - self.first_stop + + def __str__(self): + return f"Bus: {self.bus.licence_plate} driver: {self.driver.user.username} (id: {self.pk})" + + +class ScheduleStop(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + bus_shift = models.ForeignKey(BusShift, on_delete=models.PROTECT) + bus_stop = models.ForeignKey(BusStop, on_delete=models.PROTECT) + arrival = models.DateTimeField(auto_now=False, auto_now_add=False) + + def __str__(self): + return f"Shift: {self.bus_shift} stop: {self.bus_stop.name} (id: {self.pk})" diff --git a/padam_django/apps/shift/receivers.py b/padam_django/apps/shift/receivers.py new file mode 100644 index 00000000..cdb214cb --- /dev/null +++ b/padam_django/apps/shift/receivers.py @@ -0,0 +1,23 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from .models import BusShift, ScheduleStop + + +@receiver(post_save, sender=ScheduleStop) +def update_bus_shift_departure_or_arrival(sender, instance, **kwargs): + bus_shift = BusShift.objects.get(id=instance.bus_shift.id) + + if bus_shift.first_stop is None and bus_shift.last_stop is None: + bus_shift.first_stop = instance.arrival + bus_shift.last_stop = instance.arrival + bus_shift.save() + + if instance.arrival < bus_shift.first_stop: + bus_shift.first_stop = instance.arrival + bus_shift.save() + if instance.arrival > bus_shift.last_stop: + bus_shift.last_stop = instance.arrival + bus_shift.save() + + return bus_shift diff --git a/padam_django/apps/shift/tests.py b/padam_django/apps/shift/tests.py new file mode 100644 index 00000000..e004348c --- /dev/null +++ b/padam_django/apps/shift/tests.py @@ -0,0 +1,96 @@ +from django.test import TestCase +from django.utils import timezone + +from padam_django.apps.fleet.factories import BusFactory, DriverFactory +from padam_django.apps.shift.factories import ( + BusShiftFactory, + BusStopFactory, + ScheduleStopFactory, +) +from padam_django.apps.shift.forms import ScheduleStopForm + + +class BusShiftTests(TestCase): + def setUp(self): + self.bus = BusFactory() + self.driver = DriverFactory() + + def test_create_shift_and_schedule_success(self): + """ + Test if creation of shift and schedule works. + """ + initial_datetime = timezone.now() + bus_stop = BusStopFactory() + bus_shift = BusShiftFactory(bus=self.bus, driver=self.driver) + ScheduleStopFactory( + bus_shift=bus_shift, bus_stop=bus_stop, arrival=initial_datetime + ) + + # try to create a new ScheduleStop instance + form = ScheduleStopForm( + data={ + "bus_shift": bus_shift, + "bus_stop": bus_stop, + "arrival": initial_datetime + timezone.timedelta(hours=2), + } + ) + + self.assertTrue(form.is_valid()) + + def test_create_shift_and_schedule_error_bus_not_available(self): + """ + Test if bus shifts overlap. + """ + initial_datetime = timezone.now() + bus_stop = BusStopFactory() + bus_shift = BusShiftFactory(bus=self.bus, driver=self.driver) + ScheduleStopFactory( + bus_shift=bus_shift, bus_stop=bus_stop, arrival=initial_datetime + ) + ScheduleStopFactory( + bus_shift=bus_shift, + bus_stop=bus_stop, + arrival=initial_datetime + timezone.timedelta(hours=3), + ) + + # try to create a new ScheduleStop instance + form = ScheduleStopForm( + data={ + "bus_shift": bus_shift, + "bus_stop": bus_stop, + "arrival": initial_datetime + timezone.timedelta(hours=2), + } + ) + + self.assertFalse(form.is_valid()) + self.assertIn("Bus is not available", form.errors["__all__"][0]) + + def test_create_shift_and_schedule_error_driver_not_available(self): + """ + Test if driver shifts overlap. + """ + initial_datetime = timezone.now() + new_bus = BusFactory() + bus_stop = BusStopFactory() + bus_shift = BusShiftFactory(bus=self.bus, driver=self.driver) + new_bus_shift = BusShiftFactory(bus=new_bus, driver=self.driver) + ScheduleStopFactory( + bus_shift=new_bus_shift, bus_stop=bus_stop, arrival=initial_datetime + ) + ScheduleStopFactory( + bus_shift=new_bus_shift, + bus_stop=bus_stop, + arrival=initial_datetime + timezone.timedelta(hours=3), + ) + + # try to create a new ScheduleStop instance + form = ScheduleStopForm( + data={ + "bus_shift": bus_shift, + "bus_stop": bus_stop, + "arrival": initial_datetime + timezone.timedelta(hours=2), + } + ) + + self.assertFalse(form.is_valid()) + self.assertIn("Driver is not available", form.errors["__all__"][0]) diff --git a/padam_django/apps/shift/views.py b/padam_django/apps/shift/views.py new file mode 100644 index 00000000..60f00ef0 --- /dev/null +++ b/padam_django/apps/shift/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/padam_django/settings.py b/padam_django/settings.py index 129e922c..2ff64064 100644 --- a/padam_django/settings.py +++ b/padam_django/settings.py @@ -20,7 +20,7 @@ # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-&r2)+_fdqxe2dtc@1vizr6tsh6!1cesaptlfgj@ug*%3=fnq=i' +SECRET_KEY = "django-insecure-&r2)+_fdqxe2dtc@1vizr6tsh6!1cesaptlfgj@ug*%3=fnq=i" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -32,59 +32,60 @@ INSTALLED_APPS = [ # Django apps - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", # Third party apps - 'django_extensions', + "django_extensions", # Internal apps - 'padam_django.apps.common', - 'padam_django.apps.fleet', - 'padam_django.apps.geography', - 'padam_django.apps.users', + "padam_django.apps.common", + "padam_django.apps.fleet", + "padam_django.apps.geography", + "padam_django.apps.shift", + "padam_django.apps.users", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'padam_django.urls' +ROOT_URLCONF = "padam_django.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'padam_django.wsgi.application' +WSGI_APPLICATION = "padam_django.wsgi.application" # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -94,16 +95,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -116,9 +117,9 @@ # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -130,9 +131,9 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..41e79cae --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[tool.ruff] + +# Maximum line length to 120 characters. +line-length = 120 +# Support Python 3.7+. +target-version = "py37" +src = ["."] + +# Rules to apply +select = [ + "E", # pycodestyle + "W", # pycodestyle + "F", # pyflakes + "UP", # pyupgrade + "I", # isort +] + +# List what was automatically fixed +show-fixes=true + +# Exclude a variety of commonly ignored directories +exclude = [ + "__pypackages__", + "makefile", + "migrations/*", + "requirements/*", + ".env*", + ".git*", + ".ruff_cache", + "*.md", + "*.pyc", +] diff --git a/requirements.txt b/requirements.txt index 862d173f..453a80a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,6 @@ ipython==7.25.0 factory-boy==3.2.0 Faker==8.10.1 +black pre-commit==2.21.0 +ruff From 68b1e243e56d361a9fd26e3146afd180cf81f670 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:44:32 +0200 Subject: [PATCH 6/9] register the receiver --- padam_django/apps/shift/apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/padam_django/apps/shift/apps.py b/padam_django/apps/shift/apps.py index 243a7989..d3e8f503 100644 --- a/padam_django/apps/shift/apps.py +++ b/padam_django/apps/shift/apps.py @@ -6,4 +6,4 @@ class ShiftConfig(AppConfig): name = "padam_django.apps.shift" def ready(self): - pass + import padam_django.apps.shift.receivers # noqa From c089ad5a63ff20ff604d7030aa107a60af82e6b6 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 09:48:24 +0200 Subject: [PATCH 7/9] Remove return from receiver --- padam_django/apps/shift/receivers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/padam_django/apps/shift/receivers.py b/padam_django/apps/shift/receivers.py index cdb214cb..05180ac4 100644 --- a/padam_django/apps/shift/receivers.py +++ b/padam_django/apps/shift/receivers.py @@ -19,5 +19,3 @@ def update_bus_shift_departure_or_arrival(sender, instance, **kwargs): if instance.arrival > bus_shift.last_stop: bus_shift.last_stop = instance.arrival bus_shift.save() - - return bus_shift From 1405497716d561b6b6904b10062fba1b4b652d86 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 10:13:58 +0200 Subject: [PATCH 8/9] Update django admin display --- padam_django/apps/shift/admin.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/padam_django/apps/shift/admin.py b/padam_django/apps/shift/admin.py index fcfc2b8a..1b83afb4 100644 --- a/padam_django/apps/shift/admin.py +++ b/padam_django/apps/shift/admin.py @@ -14,6 +14,9 @@ class BusShiftAdmin(admin.ModelAdmin): "driver", "get_stop_count", "get_validation_stop_information", + "get_shift_start_information", + "get_shift_end_information", + "get_shift_duration_information", ) @admin.display(description="Stop count") @@ -27,12 +30,29 @@ def get_validation_stop_information(self, obj): return "Missing bus stops" return "Valid bus stops count" + @admin.display(description="Shift start") + def get_shift_start_information(self, obj): + return obj.first_stop + + @admin.display(description="Shift end") + def get_shift_end_information(self, obj): + return obj.last_stop + + @admin.display(description="Total duration (hours)") + def get_shift_duration_information(self, obj): + return obj.shift_duration + @admin.register(BusStop) class BusStopAdmin(admin.ModelAdmin): - pass + list_display = ("id", "name", "place") @admin.register(ScheduleStop) class ScheduleStopAdmin(admin.ModelAdmin): form = ScheduleStopForm + list_display = ( + "bus_shift", + "bus_stop", + "arrival", + ) From fb383a255e42037f938a24a6b8cc744e1b2a3699 Mon Sep 17 00:00:00 2001 From: Abderraouf Zerroug Date: Fri, 14 Jun 2024 10:19:19 +0200 Subject: [PATCH 9/9] rename receiver function --- padam_django/apps/shift/receivers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/padam_django/apps/shift/receivers.py b/padam_django/apps/shift/receivers.py index 05180ac4..9e6fdeec 100644 --- a/padam_django/apps/shift/receivers.py +++ b/padam_django/apps/shift/receivers.py @@ -5,7 +5,7 @@ @receiver(post_save, sender=ScheduleStop) -def update_bus_shift_departure_or_arrival(sender, instance, **kwargs): +def update_bus_shift_start_or_end(sender, instance, **kwargs): bus_shift = BusShift.objects.get(id=instance.bus_shift.id) if bus_shift.first_stop is None and bus_shift.last_stop is None: