diff --git a/.gitignore b/.gitignore index b26d6116..b9b230d8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ venv/ .pytest_cache/ **__pycache__/ +db.sqlite3 diff --git a/manage.py b/manage.py index e6082a24..3d09a6ce 100755 --- a/manage.py +++ b/manage.py @@ -4,7 +4,7 @@ import sys -def main(): +def main() -> None: """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taxi_service.settings") try: diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..f274edd1 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,5 @@ +{ + "executionEnvironments": { + "root": ["."] + } +} diff --git a/requirements.txt b/requirements.txt index 53898f6d..5bc04d7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,19 @@ -django==4.1 +asgiref==3.7.2 +Django==4.1 +django-debug-toolbar==4.2.0 +django-extensions==3.2.3 +django-stubs==4.2.4 +django-stubs-ext==4.2.2 flake8==5.0.4 flake8-quotes==3.3.1 flake8-variables-names==0.0.5 +mccabe==0.7.0 +mypy==1.5.1 +mypy-extensions==1.0.0 pep8-naming==0.13.2 +pycodestyle==2.9.1 +pyflakes==2.5.0 +sqlparse==0.4.4 +types-pytz==2023.3.1.1 +types-PyYAML==6.0.12.11 +typing_extensions==4.8.0 diff --git a/static/css/styles.css b/static/css/styles.css index 1d8a1b3d..bd439e13 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -1,3 +1,153 @@ +html { + --header-height: 60px; + --footer-height: 100px; +} + body { - margin-top: 20px; + margin: 0; +} + +h1 { + margin: 0 0 10px; +} + +.header { + padding: 0 2%; + display: flex; + justify-content: space-between; + align-items: center; + height: var(--header-height); + background-color: #4772b5; + color: white; +} + +.header ul { + margin: 0; + padding: 0; + list-style: none; +} + +.header a { + color: #c7dcfc; + text-decoration: none; +} + +.header-right { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 10px; +} + +.container { + box-sizing: border-box; + padding: 20px; + min-height: calc(100vh - var(--header-height) - var(--footer-height)); + overflow: hidden; +} + +.sidebar { + float: left; + max-width: 300px; + min-width: 150px; + width: 20%; +} + +.sidebar__link { + display: block; + background-color: #b2cdf7; + color: #000; + padding: 10px 20px; + text-decoration: none; +} + +.sidebar__link:hover { + background-color: #89a3cc; +} + +.content { + padding: 0 20px; + float: right; + width: 70%; +} + +.content a { + text-decoration: none; + color: #2765c4; +} + + +.content ul { + margin: 0; + padding: 0; + list-style: none; +} + +.footer { + display: flex; + justify-content: center; + align-items: center; + height: var(--footer-height); + line-height: var(--footer-height); + background-color: #000; + color: #fff; +} + +.card { + border: 1px solid #000; + border-radius: 5px; + max-width: 300px; + margin-bottom: 8px; + padding: 0 10px; +} + +.bold { + font-weight: bold; +} + +.pagination { + display: inline-flex; + gap: 8px; + align-items: center; + margin-top: 10px; +} + +.pagination a { + padding: 8px 13px; + border: 1px solid #000; + border-radius: 5px; +} + +.login-form { + width: 350px; + border: 1px solid #ddd; + padding: 2rem; + background: #ffffff; +} + +.form-input { + background: #fafafa; + box-sizing: border-box; + border: 1px solid #eeeeee; + padding: 12px; + width: 100%; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-button { + background: #b2cdf7; + border: 1px solid #ddd; + color: #000; + padding: 10px; + width: 100%; + text-transform: uppercase; +} + +.form-button:hover { + background: #89a3cc; } + diff --git a/taxi/admin.py b/taxi/admin.py index 08448baa..e3c07c54 100644 --- a/taxi/admin.py +++ b/taxi/admin.py @@ -5,8 +5,8 @@ @admin.register(Driver) class DriverAdmin(UserAdmin): - list_display = UserAdmin.list_display + ("license_number",) - fieldsets = UserAdmin.fieldsets + ( + list_display = UserAdmin.list_display + ("license_number",) # type: ignore + fieldsets = UserAdmin.fieldsets + ( # type: ignore (("Additional info", {"fields": ("license_number",)}),) ) add_fieldsets = UserAdmin.add_fieldsets + ( diff --git a/taxi/migrations/0001_initial.py b/taxi/migrations/0001_initial.py index ff29293e..25ca1256 100644 --- a/taxi/migrations/0001_initial.py +++ b/taxi/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0.2 on 2022-03-24 06:38 +# Generated by Django 4.0.2 on 2022-06-16 07:34 from django.conf import settings import django.contrib.auth.models @@ -31,7 +31,7 @@ class Migration(migrations.Migration): ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('license_number', models.CharField(max_length=255)), + ('license_number', models.CharField(max_length=255, unique=True)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], @@ -47,7 +47,7 @@ class Migration(migrations.Migration): name='Manufacturer', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), + ('name', models.CharField(max_length=255, unique=True)), ('country', models.CharField(max_length=255)), ], ), diff --git a/taxi/migrations/0002_alter_car_options_alter_driver_options_and_more.py b/taxi/migrations/0002_alter_car_options_alter_driver_options_and_more.py new file mode 100644 index 00000000..f4d49b54 --- /dev/null +++ b/taxi/migrations/0002_alter_car_options_alter_driver_options_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.2 on 2023-09-21 20:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('taxi', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='car', + options={'ordering': ['model']}, + ), + migrations.AlterModelOptions( + name='driver', + options={'ordering': ['first_name'], 'verbose_name': 'driver', 'verbose_name_plural': 'drivers'}, + ), + migrations.AlterModelOptions( + name='manufacturer', + options={'ordering': ['name']}, + ), + ] diff --git a/taxi/migrations/0002_alter_manufacturer_options_and_more.py b/taxi/migrations/0002_alter_manufacturer_options_and_more.py deleted file mode 100644 index a056822c..00000000 --- a/taxi/migrations/0002_alter_manufacturer_options_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0.2 on 2022-04-12 06:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('taxi', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='manufacturer', - options={'ordering': ['name']}, - ), - migrations.AlterField( - model_name='driver', - name='license_number', - field=models.CharField(max_length=255, unique=True), - ), - ] diff --git a/taxi/migrations/0003_alter_manufacturer_name.py b/taxi/migrations/0003_alter_manufacturer_name.py deleted file mode 100644 index 806cc0e6..00000000 --- a/taxi/migrations/0003_alter_manufacturer_name.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0.2 on 2022-06-16 07:46 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('taxi', '0002_alter_manufacturer_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='manufacturer', - name='name', - field=models.CharField(max_length=255, unique=True), - ), - ] diff --git a/taxi/models.py b/taxi/models.py index 9061de82..b1a05ab5 100644 --- a/taxi/models.py +++ b/taxi/models.py @@ -7,27 +7,28 @@ class Manufacturer(models.Model): country = models.CharField(max_length=255) class Meta: - ordering = ["name"] - - def __str__(self): - return f"{self.name} {self.country}" + ordering = [ + "name", + ] class Driver(AbstractUser): license_number = models.CharField(max_length=255, unique=True) class Meta: + ordering = [ + "first_name", + ] verbose_name = "driver" verbose_name_plural = "drivers" - def __str__(self): - return f"{self.username} ({self.first_name} {self.last_name})" - class Car(models.Model): model = models.CharField(max_length=255) manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) drivers = models.ManyToManyField(Driver, related_name="cars") - def __str__(self): - return self.model + class Meta: + ordering = [ + "model", + ] diff --git a/taxi/urls.py b/taxi/urls.py index c663d6e2..d6808868 100644 --- a/taxi/urls.py +++ b/taxi/urls.py @@ -1,12 +1,12 @@ from django.urls import path from .views import ( - index, - CarListView, CarDetailView, - DriverListView, + CarListView, DriverDetailView, + DriverListView, ManufacturerListView, + index, ) urlpatterns = [ @@ -20,7 +20,9 @@ path("cars//", CarDetailView.as_view(), name="car-detail"), path("drivers/", DriverListView.as_view(), name="driver-list"), path( - "drivers//", DriverDetailView.as_view(), name="driver-detail" + "drivers//", + DriverDetailView.as_view(), + name="driver-detail", ), ] diff --git a/taxi/views.py b/taxi/views.py index 82ad312f..e07f879b 100644 --- a/taxi/views.py +++ b/taxi/views.py @@ -1,47 +1,60 @@ +from typing import Any +from django.http import HttpRequest, HttpResponse from django.shortcuts import render -from django.views import generic +from django.views.generic import DetailView, ListView +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.decorators import login_required from .models import Driver, Car, Manufacturer +@login_required def index(request): - """View function for the home page of the site.""" - num_drivers = Driver.objects.count() num_cars = Car.objects.count() num_manufacturers = Manufacturer.objects.count() + num_visits = request.session.get("num_visits", 0) + request.session["num_visits"] = num_visits + 1 + context = { "num_drivers": num_drivers, "num_cars": num_cars, "num_manufacturers": num_manufacturers, + "num_visits": num_visits + 1, } return render(request, "taxi/index.html", context=context) -class ManufacturerListView(generic.ListView): +class ManufacturerListView(LoginRequiredMixin, ListView): model = Manufacturer + template_name = "manufacturer_list.html" context_object_name = "manufacturer_list" - template_name = "taxi/manufacturer_list.html" + queryset = Manufacturer.objects.all() paginate_by = 5 -class CarListView(generic.ListView): +class CarListView(LoginRequiredMixin, ListView): model = Car - paginate_by = 5 queryset = Car.objects.select_related("manufacturer") + paginate_by = 5 -class CarDetailView(generic.DetailView): +class CarDetailView(LoginRequiredMixin, DetailView): model = Car -class DriverListView(generic.ListView): +class DriverListView(LoginRequiredMixin, ListView): model = Driver paginate_by = 5 -class DriverDetailView(generic.DetailView): +class DriverDetailView(LoginRequiredMixin, DetailView): model = Driver - queryset = Driver.objects.prefetch_related("cars__manufacturer") + queryset = Driver.objects.all() + + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + context = super().get_context_data(**kwargs) + context["car_list"] = self.object.cars.select_related("manufacturer") + return context diff --git a/taxi_service/settings.py b/taxi_service/settings.py index b6c0cf19..7902fb0d 100644 --- a/taxi_service/settings.py +++ b/taxi_service/settings.py @@ -1,15 +1,3 @@ -""" -Django settings for taxi_service project. - -Generated by "django-admin startproject" using Django 4.0.2. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.0/ref/settings/ -""" - from pathlib import Path # Build paths inside the project like this: BASE_DIR / "subdir". @@ -20,9 +8,7 @@ # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = ( - "django-insecure-8ovil3xu6=eaoqd#-#&ricv159p0pypoh5_lgm*)-dfcjqe=yc" -) +SECRET_KEY = "django-insecure-8ovil3xu6=eaoqd#-#&ricv159poh5_lgm*)-dfcjqe=yc" # SECURITY WARNING: don"t run with debug turned on in production! DEBUG = True @@ -32,18 +18,24 @@ # Application definition -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "taxi", -] +INSTALLED_APPS = ( + [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "taxi", + ] + + ["debug_toolbar"] + if DEBUG + else [] +) MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", @@ -108,6 +100,8 @@ AUTH_USER_MODEL = "taxi.Driver" +LOGIN_REDIRECT_URL = "/" + # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ @@ -133,3 +127,8 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +INTERNAL_IPS = [ + "127.0.0.1", +] diff --git a/taxi_service/urls.py b/taxi_service/urls.py index 8b94449f..207831d9 100644 --- a/taxi_service/urls.py +++ b/taxi_service/urls.py @@ -1,18 +1,3 @@ -"""taxi_service URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.0/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path("", views.home, name="home") -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path("", Home.as_view(), name="home") -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path("blog/", include("blog.urls")) -""" from django.contrib import admin from django.urls import path, include from django.conf import settings @@ -22,4 +7,6 @@ urlpatterns = [ path("admin/", admin.site.urls), path("", include("taxi.urls", namespace="taxi")), + path("accounts/", include("django.contrib.auth.urls")), + path("__debug__", include("debug_toolbar.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/templates/base.html b/templates/base.html index f4a0b6cf..f9d7ae4c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,40 +1,41 @@ - - - {% block title %}Taxi Service{% endblock %} - - - - - {% load static %} - - - - -
-
-
- + + {% block title %} + Taxi service + {% endblock %} + + + {% load static %} + + + +
+ + +
+
{% block sidebar %} {% include "includes/sidebar.html" %} - {% endblock %} - + {% endblock sidebar %} +
+ {% block content %} + {% endblock content %} + {% block pagination %} + {% include "includes/pagination.html" %} + {% endblock pagination %} +
-
- - {% block content %}{% endblock %} - - {% block pagination %} - {% include "includes/pagination.html" %} - {% endblock %} - + -
-
- - + + diff --git a/templates/includes/pagination.html b/templates/includes/pagination.html index 118c2806..a090dba6 100644 --- a/templates/includes/pagination.html +++ b/templates/includes/pagination.html @@ -1,17 +1,16 @@ {% if is_paginated %} -
    +
+
{% endif %} + diff --git a/templates/includes/sidebar.html b/templates/includes/sidebar.html index b7cd72dc..657ebdb6 100644 --- a/templates/includes/sidebar.html +++ b/templates/includes/sidebar.html @@ -1,6 +1,7 @@ - + + diff --git a/templates/registration/logged_out.html b/templates/registration/logged_out.html new file mode 100644 index 00000000..8cc6495c --- /dev/null +++ b/templates/registration/logged_out.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block content %} +

You have logged out

+

Click here to log in again

+{% endblock content %} + diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 00000000..181e40d0 --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} +

Login

+ {% if errors %} +

{{ errors }}

+ {% endif %} + +{% endblock content %} + diff --git a/templates/taxi/car_detail.html b/templates/taxi/car_detail.html index db6da00a..41f30c63 100644 --- a/templates/taxi/car_detail.html +++ b/templates/taxi/car_detail.html @@ -1,12 +1,16 @@ {% extends "base.html" %} {% block content %} -

Manufacturer: ({{ car.manufacturer.name }}, {{ car.manufacturer.country }})

-

Drivers

-
-
    +

    {{ car.model }}

    +

    Manufacturer: {{ car.manufacturer.name }} ({{ car.manufacturer.country }})

    +

    Drivers:

    {% for driver in car.drivers.all %} -
  • {{ driver.first_name }} {{ driver.last_name }}
  • +
    +

    Name: {{ driver.first_name }} {{ driver.last_name }}

    +

    License number: {{ driver.license_number }}

    +
    + {% empty %} +

    There are no drivers

    {% endfor %} -
-{% endblock %} +{% endblock content %} + diff --git a/templates/taxi/car_list.html b/templates/taxi/car_list.html index e107b579..bc2e538a 100644 --- a/templates/taxi/car_list.html +++ b/templates/taxi/car_list.html @@ -6,12 +6,14 @@

Car list

{% else %} -

There are no cars in taxi

+

There are no cars

{% endif %} -{% endblock %} +{% endblock content %} + diff --git a/templates/taxi/driver_detail.html b/templates/taxi/driver_detail.html index 37b766d0..e946a8cd 100644 --- a/templates/taxi/driver_detail.html +++ b/templates/taxi/driver_detail.html @@ -1,18 +1,17 @@ {% extends "base.html" %} {% block content %} -

- {{ driver.first_name }} {{ driver.last_name }}. - License number: {{ driver.license_number }} -

- {% for car in driver.cars.all %} -
-

Id:{{ car.id }} - Car model: {{ car.model }} -

+

{{ driver.first_name }} {{ driver.last_name }}

+

License number: {{ driver.license_number }}

+ {% if car_list %} +

Cars:

+ {% for car in car_list %} +
+

Model: {{ car.model }}

+
+ {% endfor %} + {% else %} +

Doesn't own any cars

+ {% endif %} +{% endblock content %} - {% empty %} -

No cars!

- {% endfor %} - -{% endblock %} diff --git a/templates/taxi/driver_list.html b/templates/taxi/driver_list.html index c6c3e823..4f381384 100644 --- a/templates/taxi/driver_list.html +++ b/templates/taxi/driver_list.html @@ -4,14 +4,19 @@

Driver list

{% if driver_list %} {% else %} -

There are no drivers in taxi

+

There are no drivers

{% endif %} -{% endblock %} +{% endblock content %} + diff --git a/templates/taxi/index.html b/templates/taxi/index.html index 13c59aa8..7d8ce669 100644 --- a/templates/taxi/index.html +++ b/templates/taxi/index.html @@ -1,13 +1,12 @@ {% extends "base.html" %} {% block content %} -

Taxi Service Home

-

Welcome to Best Taxi Ever!

-

Dynamic content

-

The Taxi service has the following record counts:

+

Taxi service

+

You visited this page: {{ num_visits }} time{{ num_visits|pluralize }}

    -
  • Cars: {{ num_cars }}
  • -
  • Drivers: {{ num_drivers }}
  • -
  • Manufacturers: {{ num_manufacturers }}
  • +
  • Number of cars: {{ num_cars }}
  • +
  • Number of drivers: {{ num_drivers }}
  • +
  • Number of manufacturers: {{ num_manufacturers }}
-{% endblock %} +{% endblock content %} + diff --git a/templates/taxi/manufacturer_list.html b/templates/taxi/manufacturer_list.html index 2d86e7e6..94a19bdb 100644 --- a/templates/taxi/manufacturer_list.html +++ b/templates/taxi/manufacturer_list.html @@ -2,12 +2,14 @@ {% block content %}

Manufacturer list

- {% if manufacturer_list %}
    {% for manufacturer in manufacturer_list %} -
  • {{ manufacturer.name }} {{ manufacturer.country }}
  • +
  • {{ manufacturer.name }}
  • {% endfor %}
+ {% else %} +

There are no manufacturers

{% endif %} -{% endblock %} +{% endblock content %} +