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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

on:
push:
pull_request:

jobs:
tests:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.9"
cache: "pip"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install -e ".[dev]"

- name: Run tests
run: python manage.py test --noinput
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ __pycache__/
# virtualenv
venv/
ENV/
.venv/
# pipenv: https://github.com/kennethreitz/pipenv
/Pipfile

Expand Down
8 changes: 0 additions & 8 deletions .idea/.gitignore

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/inspectionProfiles/profiles_settings.xml

This file was deleted.

4 changes: 0 additions & 4 deletions .idea/misc.xml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

23 changes: 0 additions & 23 deletions .idea/padam-django-tech-test.iml

This file was deleted.

1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ run: ## Run the test server.

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

migrate: ## Apply database migrations.
python manage.py migrate
11 changes: 0 additions & 11 deletions Pipfile

This file was deleted.

17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ design a simple interface for managing bus routes, using Django admin.

To carry out the test, remember to fork this repository. Ideally, open a PR at the end.

## Submission Documentation

To review the solution:

1. See [`docs/implementation.md`](docs/implementation.md) for the implemented features, technical choices, local development tools, and possible improvements.
2. See [`docs/design.md`](docs/design.md) for the UML diagram, derived properties, and business rules.


## Evaluation criteria

- Code documentation and clarity
Expand All @@ -18,13 +26,12 @@ To carry out the test, remember to fork this repository. Ideally, open a PR at t
| Python | 3.9 |
| Django | 4.2.16 |

- This project was created using Python 3.7. You are free to use another version, but this is the one we recommend.
recommended.
- This project was created using Python 3.7. You are free to use another version, but this is the one we recommend (Django 4.2.16 requires Python ≥ 3.8).
- The database is freely selectable. The project is configured to use `sqlite` by default.

### Start the project

*From your Python 3.7 virtualenv*:
*From your Python 3.9 virtualenv*:

```
make install
Expand Down Expand Up @@ -121,12 +128,12 @@ Pour réaliser le test, pensez à fork ce repository. Idéalement, ouvrir une PR
| Django | 4.2.16 |

- 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.
nous vous conseillons (Django 4.2.16 nécessite Python ≥ 3.8).
- 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*:
*Depuis votre virtualenv Python 3.9*:

```
make install
Expand Down
69 changes: 69 additions & 0 deletions docs/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## UML class diagram

```mermaid
classDiagram
class User {
+id: Integer
+username: String
+email: String
/is_driver: Boolean
}

class Driver {
+id: Integer
+user: User
}

class Bus {
+id: Integer
+licence_plate: String
}

class BusShift {
+id: Integer
+driver: Driver
+bus: Bus
/departure_at: DateTime
/arrival_at: DateTime
/duration: Duration
}

class BusStop {
+id: Integer
+bus_shift: BusShift
+place: Place
+scheduled_at: DateTime
+order: Integer
}

class Place {
+id: Integer
+name: String
+longitude: Decimal
+latitude: Decimal
}

Driver "0..1" --> "1" User : user
BusShift "0..*" --> "1" Driver : driver
BusShift "0..*" --> "1" Bus : bus
BusShift "1" *-- "2..*" BusStop : stops
BusStop "0..*" --> "1" Place : place
```

### Derived properties

These properties are calculated and are not stored in the database:

- `User.is_driver` indicates whether a `Driver` profile exists for the user.
- `BusShift.departure_at` is calculated from the first scheduled stop.
- `BusShift.arrival_at` is calculated from the last scheduled stop.
- `BusShift.duration` is calculated from the departure and arrival datetimes.

### Business rules

- A bus shift must have exactly one driver and one bus.
- A bus shift must have at least two stops.
- Each stop must belong to exactly one bus shift.
- Stop order must be unique within a bus shift.
- Stop times must follow the stop order.
- The last stop time must be later than the first stop time.
88 changes: 88 additions & 0 deletions docs/implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Implementation Summary

## Implemented Features

- Added the `BusShift` and `BusStop` models.
- Required each shift to contain at least two stops.
- Enforced unique stop ordering within each shift.
- Prevented overlapping shifts for the same bus or driver.
- Calculated departure time, arrival time, and duration from the related stops.
- Added search, autocomplete fields, filters, and ordering to the Django Admin.
- Added custom `QuerySet` and manager methods for reusable business queries.
- Identified N+1 queries with Django Debug Toolbar and fixed them with optimized querysets.
- Added automated tests for the main business rules.
- Added Ruff for linting and formatting.
- Added a GitHub Actions CI workflow to run the test suite.

## Technical Choices

Departure time, arrival time, and duration are calculated from the related stops instead of being stored directly on `BusShift`.

This avoids duplicated data and keeps the database consistent and normalized.

If performance measurements later show that these calculations are too expensive, selective denormalization could be considered.

## Local Development Tools

Development dependencies are defined in `pyproject.toml`.

Install the project and its development tools from the project root:

```bash
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```

The development dependencies include:

```toml
[project.optional-dependencies]
dev = [
"django-debug-toolbar==6.1.0",
"ruff",
]
```

### Ruff

Run linting checks:

```bash
python -m ruff check .
```

Check formatting:

```bash
python -m ruff format --check .
```

Format the code:

```bash
python -m ruff format .
```

### Django Debug Toolbar

Django Debug Toolbar is used locally to inspect SQL queries and detect repeated queries or N+1 problems.

It is a diagnostic tool only. The actual fixes use Django ORM features such as:

It must not be enabled in production.

## Possible Improvements

The following improvements were left outside the scope of this exercise:

- Introduce a service layer to separate business workflows from models
- PostgreSQL integration and concurrency.
- Separate settings for development, testing, and production, with sensitive values stored in a .env file.
- Centralize tool configuration in pyproject.toml.
- More validation for external entry points such as APIs or imports.
- More test coverage with `pytest` and `pytest-django`.
- Static type checking with Mypy
- Additional CI checks for migrations, formatting, and linting.
- Upgrade to the latest supported Django version and supported version to benefit from new features, security updates, and bug fixes.
- A REST API for external clients.
- Add internationalization and localization support.
8 changes: 6 additions & 2 deletions padam_django/apps/fleet/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@

@admin.register(models.Bus)
class BusAdmin(admin.ModelAdmin):
pass
search_fields = ("licence_plate",)


@admin.register(models.Driver)
class DriverAdmin(admin.ModelAdmin):
pass
search_fields = (
"user__username",
"user__email",
)
list_select_related = ("user",)
2 changes: 1 addition & 1 deletion padam_django/apps/geography/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@

@admin.register(models.Place)
class PlaceAdmin(admin.ModelAdmin):
pass
search_fields = ("name",)
Empty file.
Loading