├── taxi ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0003_alter_manufacturer_name.py │ ├── 0002_alter_manufacturer_options_and_more.py │ └── 0001_initial.py ├── tests.py ├── apps.py ├── urls.py ├── admin.py ├── models.py └── views.py ├── tests ├── __init__.py └── test_taxi_service.py ├── taxi_service ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── static └── css │ └── styles.css ├── .gitignore ├── requirements.txt ├── .flake8 ├── templates ├── includes │ ├── sidebar.html │ └── pagination.html ├── taxi │ ├── car_detail.html │ ├── manufacturer_list.html │ ├── index.html │ ├── car_list.html │ ├── driver_detail.html │ └── driver_list.html └── base.html ├── manage.py ├── .github └── workflows │ └── test.yml ├── checklist.md ├── README.md └── taxi_service_db_data.json /taxi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /taxi/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /taxi_service/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /taxi/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | *.iml 4 | .env 5 | .DS_Store 6 | venv/ 7 | .pytest_cache/ 8 | **__pycache__/ 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django==4.1 2 | flake8==5.0.4 3 | flake8-quotes==3.3.1 4 | flake8-variables-names==0.0.5 5 | pep8-naming==0.13.2 6 | -------------------------------------------------------------------------------- /taxi/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TaxiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "taxi" 7 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | inline-quotes = " 3 | ignore = E203, E266, W503, N807, N818, F401 4 | max-line-length = 79 5 | max-complexity = 18 6 | select = B,C,E,F,W,T4,B9,Q0,N8,VNE 7 | exclude = 8 | **migrations 9 | venv 10 | tests -------------------------------------------------------------------------------- /templates/includes/sidebar.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /templates/taxi/car_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

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

5 |

Drivers

6 |
7 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/taxi/manufacturer_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Manufacturer list

5 | 6 | {% if manufacturer_list %} 7 | 12 | {% endif %} 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /templates/taxi/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Taxi Service Home

5 |

Welcome to Best Taxi Ever!

6 |

Dynamic content

7 |

The Taxi service has the following record counts:

8 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /taxi_service/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for taxi_service project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taxi_service.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /taxi_service/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for taxi_service project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taxi_service.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /templates/taxi/car_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Car list

5 | {% if car_list %} 6 | 14 | {% else %} 15 |

There are no cars in taxi

16 | {% endif %} 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /templates/taxi/driver_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

5 | {{ driver.first_name }} {{ driver.last_name }}. 6 | License number: {{ driver.license_number }} 7 |

8 | {% for car in driver.cars.all %} 9 |
10 |

Id:{{ car.id }} 11 | Car model: {{ car.model }} 12 |

13 | 14 | {% empty %} 15 |

No cars!

16 | {% endfor %} 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /templates/taxi/driver_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Driver list

5 | {% if driver_list %} 6 | 14 | {% else %} 15 |

There are no drivers in taxi

16 | {% endif %} 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /taxi/migrations/0003_alter_manufacturer_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.2 on 2022-06-16 07:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('taxi', '0002_alter_manufacturer_options_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='manufacturer', 15 | name='name', 16 | field=models.CharField(max_length=255, unique=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /templates/includes/pagination.html: -------------------------------------------------------------------------------- 1 | {% if is_paginated %} 2 | 17 | {% endif %} 18 | -------------------------------------------------------------------------------- /taxi/migrations/0002_alter_manufacturer_options_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.2 on 2022-04-12 06:44 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('taxi', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='manufacturer', 15 | options={'ordering': ['name']}, 16 | ), 17 | migrations.AlterField( 18 | model_name='driver', 19 | name='license_number', 20 | field=models.CharField(max_length=255, unique=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "taxi_service.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /taxi/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ( 4 | index, 5 | CarListView, 6 | CarDetailView, 7 | DriverListView, 8 | DriverDetailView, 9 | ManufacturerListView, 10 | ) 11 | 12 | urlpatterns = [ 13 | path("", index, name="index"), 14 | path( 15 | "manufacturers/", 16 | ManufacturerListView.as_view(), 17 | name="manufacturer-list", 18 | ), 19 | path("cars/", CarListView.as_view(), name="car-list"), 20 | path("cars//", CarDetailView.as_view(), name="car-detail"), 21 | path("drivers/", DriverListView.as_view(), name="driver-list"), 22 | path( 23 | "drivers//", DriverDetailView.as_view(), name="driver-detail" 24 | ), 25 | ] 26 | 27 | app_name = "taxi" 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 10 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Set Up Python 3.10 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: "3.10" 21 | 22 | - name: Install requirements 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install -r requirements.txt 26 | 27 | - name: Run flake8 28 | run: flake8 29 | 30 | - name: Run tests 31 | timeout-minutes: 5 32 | run: python manage.py test 33 | 34 | -------------------------------------------------------------------------------- /taxi/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from .models import Driver, Car, Manufacturer 4 | 5 | 6 | @admin.register(Driver) 7 | class DriverAdmin(UserAdmin): 8 | list_display = UserAdmin.list_display + ("license_number",) 9 | fieldsets = UserAdmin.fieldsets + ( 10 | (("Additional info", {"fields": ("license_number",)}),) 11 | ) 12 | add_fieldsets = UserAdmin.add_fieldsets + ( 13 | ( 14 | ( 15 | "Additional info", 16 | { 17 | "fields": ( 18 | "first_name", 19 | "last_name", 20 | "license_number", 21 | ) 22 | }, 23 | ), 24 | ) 25 | ) 26 | 27 | 28 | @admin.register(Car) 29 | class CarAdmin(admin.ModelAdmin): 30 | search_fields = ("model",) 31 | list_filter = ("manufacturer",) 32 | 33 | 34 | admin.site.register(Manufacturer) 35 | -------------------------------------------------------------------------------- /taxi/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractUser 3 | 4 | 5 | class Manufacturer(models.Model): 6 | name = models.CharField(max_length=255, unique=True) 7 | country = models.CharField(max_length=255) 8 | 9 | class Meta: 10 | ordering = ["name"] 11 | 12 | def __str__(self): 13 | return f"{self.name} {self.country}" 14 | 15 | 16 | class Driver(AbstractUser): 17 | license_number = models.CharField(max_length=255, unique=True) 18 | 19 | class Meta: 20 | verbose_name = "driver" 21 | verbose_name_plural = "drivers" 22 | 23 | def __str__(self): 24 | return f"{self.username} ({self.first_name} {self.last_name})" 25 | 26 | 27 | class Car(models.Model): 28 | model = models.CharField(max_length=255) 29 | manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) 30 | drivers = models.ManyToManyField(Driver, related_name="cars") 31 | 32 | def __str__(self): 33 | return self.model 34 | -------------------------------------------------------------------------------- /taxi_service/urls.py: -------------------------------------------------------------------------------- 1 | """taxi_service URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path("", views.home, name="home") 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path("", Home.as_view(), name="home") 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path("blog/", include("blog.urls")) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from django.conf import settings 19 | from django.conf.urls.static import static 20 | 21 | 22 | urlpatterns = [ 23 | path("admin/", admin.site.urls), 24 | path("", include("taxi.urls", namespace="taxi")), 25 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 26 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Taxi Service{% endblock %} 6 | 7 | 8 | 12 | 13 | {% load static %} 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | 22 | {% block sidebar %} 23 | {% include "includes/sidebar.html" %} 24 | {% endblock %} 25 | 26 |
27 |
28 | 29 | {% block content %}{% endblock %} 30 | 31 | {% block pagination %} 32 | {% include "includes/pagination.html" %} 33 | {% endblock %} 34 | 35 |
36 |
37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /taxi/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.views import generic 3 | 4 | from .models import Driver, Car, Manufacturer 5 | 6 | 7 | def index(request): 8 | """View function for the home page of the site.""" 9 | 10 | num_drivers = Driver.objects.count() 11 | num_cars = Car.objects.count() 12 | num_manufacturers = Manufacturer.objects.count() 13 | 14 | context = { 15 | "num_drivers": num_drivers, 16 | "num_cars": num_cars, 17 | "num_manufacturers": num_manufacturers, 18 | } 19 | 20 | return render(request, "taxi/index.html", context=context) 21 | 22 | 23 | class ManufacturerListView(generic.ListView): 24 | model = Manufacturer 25 | context_object_name = "manufacturer_list" 26 | template_name = "taxi/manufacturer_list.html" 27 | paginate_by = 5 28 | 29 | 30 | class CarListView(generic.ListView): 31 | model = Car 32 | paginate_by = 5 33 | queryset = Car.objects.select_related("manufacturer") 34 | 35 | 36 | class CarDetailView(generic.DetailView): 37 | model = Car 38 | 39 | 40 | class DriverListView(generic.ListView): 41 | model = Driver 42 | paginate_by = 5 43 | 44 | 45 | class DriverDetailView(generic.DetailView): 46 | model = Driver 47 | queryset = Driver.objects.prefetch_related("cars__manufacturer") 48 | -------------------------------------------------------------------------------- /checklist.md: -------------------------------------------------------------------------------- 1 | # Сheck Your Code Against the Following Points 2 | 3 | ## Don't Push db files 4 | 5 | Make sure you don't push db files (files with `.sqlite`, `.db3`, etc. extension). 6 | 7 | ## Don't forget to attach all screenshots of created/modified pages. 8 | 9 | ## Code Efficiency 10 | 1. Make sure you've added a blank line at the end to all your files including `.css`, `.html` and `.gitignore`. 11 | 2. Use `pluralize`. 12 | 13 | Good example: 14 | 15 | ```html 16 |

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

17 | ``` 18 | 19 | Bad example: 20 | 21 | ```html 22 |

You have visited this page {{ num_visits }} {% if num_visits == 1 %} time {% else %} times {% endif %}.

23 | ``` 24 | 25 | 3. Make sure that `num_visits` works as expected. 26 | When you visit the page for the first time there should be: `You have visited this page 1 time` 27 | 28 | 4. Make sure you use 2 whitespaces indentations in your `.html` files. 29 | 30 | ## Code style 31 | 32 | Use `-` to split words in URL identification parameter `name`, not the `_`. 33 | 34 | Good example: 35 | 36 | ```python 37 | urlpatterns = [ 38 | path("buses/", BusListView.as_view(), name="bus-list"), 39 | ] 40 | ``` 41 | 42 | Bad example: 43 | 44 | ```python 45 | urlpatterns = [ 46 | path("buses/", BusListView.as_view(), name="bus_list"), 47 | ] 48 | ``` 49 | 50 | ## Clean Code 51 | Add comments, prints, and functions to check your solution when you write your code. 52 | Don't forget to delete them when you are ready to commit and push your code. 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taxi service authentication 2 | 3 | Read [the guideline](https://github.com/mate-academy/py-task-guideline/blob/main/README.md) before starting. 4 | - Use the following command to load prepared data from fixture to test and debug your code: 5 | `python manage.py loaddata taxi_service_db_data.json`. 6 | - After loading data from fixture you can use following superuser (or create another one by yourself): 7 | - Login: `admin.user` 8 | - Password: `1qazcde3` 9 | - Make sure that you change the settings for [html-files](https://github.com/mate-academy/py-task-guideline/blob/main/html_settings/README.MD). 10 | 11 | Feel free to add more data using admin panel, if needed. 12 | 13 | In this task, you will implement a visit counter, login/logout functionality, and make your site available only 14 | authenticated users. 15 | 16 | 1. Implement a visit counter on the home screen (use the `num_visits` variable name). It should show how many times a user visited the home page before. 17 | 18 | 2. Create login/logout screens. Provide authentication to your project using built-in Django authentication. 19 | 20 | 3. Display the username on the top of the sidebar and login/logout button depending on if the user is authenticated. 21 | If the driver clicks on username - the corresponding Driver detail page must open. 22 | 23 | 4. Protect all your views from unauthenticated users. 24 | 25 | 5. In Driver's list view add (Me), if this is a current user: 26 | 27 | Example: 28 | ``` 29 | - Admin User (Me) 30 | - Joyce Byers 31 | - Jim Hopper 32 | ``` 33 | 34 | NOTE: Attach screenshots of all created or modified pages to pull request. It's important to attach images not links to them. See example: 35 | 36 | ![image](https://mate-academy-images.s3.eu-central-1.amazonaws.com/python_pr_with_images.png) 37 | 38 | **Note:** we use the `-` hyphen in URLs names in our Django course, whilst we use the `_` underscore in our DRF course. 39 | 40 | ### Note: Check your code using this [checklist](checklist.md) before pushing your solution. 41 | -------------------------------------------------------------------------------- /taxi/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.2 on 2022-03-24 06:38 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('auth', '0012_alter_user_first_name_max_length'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Driver', 22 | fields=[ 23 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('password', models.CharField(max_length=128, verbose_name='password')), 25 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 26 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 27 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 28 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 29 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 30 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 31 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 32 | ('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')), 33 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 34 | ('license_number', models.CharField(max_length=255)), 35 | ('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')), 36 | ('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')), 37 | ], 38 | options={ 39 | 'verbose_name': 'driver', 40 | 'verbose_name_plural': 'drivers', 41 | }, 42 | managers=[ 43 | ('objects', django.contrib.auth.models.UserManager()), 44 | ], 45 | ), 46 | migrations.CreateModel( 47 | name='Manufacturer', 48 | fields=[ 49 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 50 | ('name', models.CharField(max_length=255)), 51 | ('country', models.CharField(max_length=255)), 52 | ], 53 | ), 54 | migrations.CreateModel( 55 | name='Car', 56 | fields=[ 57 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 58 | ('model', models.CharField(max_length=255)), 59 | ('drivers', models.ManyToManyField(related_name='cars', to=settings.AUTH_USER_MODEL)), 60 | ('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='taxi.manufacturer')), 61 | ], 62 | ), 63 | ] 64 | -------------------------------------------------------------------------------- /taxi_service/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for taxi_service project. 3 | 4 | Generated by "django-admin startproject" using Django 4.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / "subdir". 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = ( 24 | "django-insecure-8ovil3xu6=eaoqd#-#&ricv159p0pypoh5_lgm*)-dfcjqe=yc" 25 | ) 26 | 27 | # SECURITY WARNING: don"t run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = [] 31 | 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | "django.contrib.admin", 37 | "django.contrib.auth", 38 | "django.contrib.contenttypes", 39 | "django.contrib.sessions", 40 | "django.contrib.messages", 41 | "django.contrib.staticfiles", 42 | "taxi", 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | "django.middleware.security.SecurityMiddleware", 47 | "django.contrib.sessions.middleware.SessionMiddleware", 48 | "django.middleware.common.CommonMiddleware", 49 | "django.middleware.csrf.CsrfViewMiddleware", 50 | "django.contrib.auth.middleware.AuthenticationMiddleware", 51 | "django.contrib.messages.middleware.MessageMiddleware", 52 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 53 | ] 54 | 55 | ROOT_URLCONF = "taxi_service.urls" 56 | 57 | TEMPLATES = [ 58 | { 59 | "BACKEND": "django.template.backends.django.DjangoTemplates", 60 | "DIRS": [BASE_DIR / "templates"], 61 | "APP_DIRS": True, 62 | "OPTIONS": { 63 | "context_processors": [ 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.contrib.auth.context_processors.auth", 67 | "django.contrib.messages.context_processors.messages", 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = "taxi_service.wsgi.application" 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 78 | 79 | DATABASES = { 80 | "default": { 81 | "ENGINE": "django.db.backends.sqlite3", 82 | "NAME": BASE_DIR / "db.sqlite3", 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | "NAME": "django.contrib.auth.password_validation." 93 | "UserAttributeSimilarityValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation." 97 | "MinimumLengthValidator", 98 | }, 99 | { 100 | "NAME": "django.contrib.auth.password_validation." 101 | "CommonPasswordValidator", 102 | }, 103 | { 104 | "NAME": "django.contrib.auth.password_validation." 105 | "NumericPasswordValidator", 106 | }, 107 | ] 108 | 109 | AUTH_USER_MODEL = "taxi.Driver" 110 | 111 | # Internationalization 112 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 113 | 114 | LANGUAGE_CODE = "en-us" 115 | 116 | TIME_ZONE = "Europe/Kiev" 117 | 118 | USE_I18N = True 119 | 120 | USE_TZ = True 121 | 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 125 | 126 | STATIC_URL = "static/" 127 | 128 | STATICFILES_DIRS = (BASE_DIR / "static",) 129 | 130 | STATIC_ROOT = BASE_DIR / "staticfiles" 131 | 132 | # Default primary key field type 133 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 134 | 135 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 136 | -------------------------------------------------------------------------------- /tests/test_taxi_service.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | from django.urls import reverse 4 | 5 | from taxi.models import Car, Manufacturer, Driver 6 | 7 | TestCase.fixtures = ["taxi_service_db_data.json"] 8 | 9 | 10 | class PublicTests(TestCase): 11 | def test_car_list_login_required(self): 12 | response = self.client.get(reverse("taxi:car-list")) 13 | 14 | self.assertNotEqual(response.status_code, 200) 15 | 16 | def test_manufacturer_list_login_required(self): 17 | response = self.client.get(reverse("taxi:manufacturer-list")) 18 | 19 | self.assertNotEqual(response.status_code, 200) 20 | 21 | def test_driver_login_required(self): 22 | response = self.client.get(reverse("taxi:driver-list")) 23 | 24 | self.assertNotEqual(response.status_code, 200) 25 | 26 | def test_index_login_required(self): 27 | response = self.client.get(reverse("taxi:index")) 28 | 29 | self.assertNotEqual(response.status_code, 200) 30 | 31 | def test_login(self): 32 | response = self.client.get(reverse("login")) 33 | 34 | self.assertEqual(response.status_code, 200) 35 | self.assertTemplateUsed(response, "registration/login.html") 36 | 37 | 38 | class PrivateHomeTests(TestCase): 39 | def setUp(self) -> None: 40 | self.client.force_login(get_user_model().objects.get(id=1)) 41 | 42 | def test_index(self): 43 | response = self.client.get(reverse("taxi:index")) 44 | 45 | self.assertEqual(response.status_code, 200) 46 | self.assertTemplateUsed(response, "taxi/index.html") 47 | 48 | def test_visit_counter(self): 49 | visits = 3 50 | 51 | for visit in range(visits): 52 | response = self.client.get(reverse("taxi:index")) 53 | self.assertEqual(response.context["num_visits"], visit + 1) 54 | 55 | 56 | class PrivateManufacturerTests(TestCase): 57 | def setUp(self) -> None: 58 | self.client.force_login(get_user_model().objects.get(id=1)) 59 | 60 | def test_manufacturer_list(self): 61 | response = self.client.get(reverse("taxi:manufacturer-list")) 62 | manufacturers = Manufacturer.objects.all() 63 | 64 | self.assertEqual(response.status_code, 200) 65 | self.assertEqual( 66 | list(response.context["manufacturer_list"]), 67 | list( 68 | manufacturers[ 69 | 0 : len(list(response.context["manufacturer_list"])) 70 | ] 71 | ), 72 | ) 73 | self.assertTemplateUsed(response, "taxi/manufacturer_list.html") 74 | 75 | 76 | class PrivateCarTests(TestCase): 77 | def setUp(self) -> None: 78 | self.client.force_login(get_user_model().objects.get(id=1)) 79 | 80 | def test_car_list(self): 81 | response = self.client.get(reverse("taxi:car-list")) 82 | cars = Car.objects.all() 83 | 84 | self.assertEqual(response.status_code, 200) 85 | self.assertEqual( 86 | list(response.context["car_list"]), 87 | list(cars[0 : len(list(response.context["car_list"]))]), 88 | ) 89 | self.assertTemplateUsed(response, "taxi/car_list.html") 90 | 91 | def test_car_detail(self): 92 | response = self.client.get(reverse("taxi:car-detail", args=[1])) 93 | 94 | self.assertEqual(response.status_code, 200) 95 | self.assertTemplateUsed(response, "taxi/car_detail.html") 96 | 97 | 98 | class PrivateDriverTests(TestCase): 99 | def setUp(self) -> None: 100 | self.client.force_login(get_user_model().objects.get(id=1)) 101 | 102 | def test_driver_list(self): 103 | response = self.client.get(reverse("taxi:driver-list")) 104 | drivers = Driver.objects.all() 105 | 106 | self.assertEqual(response.status_code, 200) 107 | self.assertEqual( 108 | list(response.context["driver_list"]), 109 | list(drivers[0 : len(list(response.context["driver_list"]))]), 110 | ) 111 | self.assertTemplateUsed(response, "taxi/driver_list.html") 112 | 113 | def test_driver_detail(self): 114 | response = self.client.get(reverse("taxi:driver-detail", args=[1])) 115 | 116 | self.assertEqual(response.status_code, 200) 117 | self.assertTemplateUsed(response, "taxi/driver_detail.html") 118 | 119 | 120 | class LogInTest(TestCase): 121 | def setUp(self): 122 | self.credentials = {"username": "admin.user", "password": "1qazcde3"} 123 | 124 | def test_login(self): 125 | response = self.client.post( 126 | reverse("login"), self.credentials, follow=True 127 | ) 128 | self.assertTrue(response.context["user"].is_active) 129 | print(response.context["user"]) 130 | -------------------------------------------------------------------------------- /taxi_service_db_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "taxi.manufacturer", 4 | "pk": 1, 5 | "fields": { 6 | "name": "Lincoln", 7 | "country": "USA" 8 | } 9 | }, 10 | { 11 | "model": "taxi.manufacturer", 12 | "pk": 2, 13 | "fields": { 14 | "name": "Ford Motor Company", 15 | "country": "USA" 16 | } 17 | }, 18 | { 19 | "model": "taxi.manufacturer", 20 | "pk": 3, 21 | "fields": { 22 | "name": "General Motors", 23 | "country": "USA" 24 | } 25 | }, 26 | { 27 | "model": "taxi.manufacturer", 28 | "pk": 4, 29 | "fields": { 30 | "name": "Toyota", 31 | "country": "Japan" 32 | } 33 | }, 34 | { 35 | "model": "taxi.manufacturer", 36 | "pk": 5, 37 | "fields": { 38 | "name": "Suzuki", 39 | "country": "Japan" 40 | } 41 | }, 42 | { 43 | "model": "taxi.manufacturer", 44 | "pk": 6, 45 | "fields": { 46 | "name": "Mitsubishi", 47 | "country": "Japan" 48 | } 49 | }, 50 | { 51 | "model": "taxi.manufacturer", 52 | "pk": 7, 53 | "fields": { 54 | "name": "Volkswagen", 55 | "country": "Germany" 56 | } 57 | }, 58 | { 59 | "model": "taxi.manufacturer", 60 | "pk": 8, 61 | "fields": { 62 | "name": "Daimler", 63 | "country": "Germany" 64 | } 65 | }, 66 | { 67 | "model": "taxi.manufacturer", 68 | "pk": 9, 69 | "fields": { 70 | "name": "FCA", 71 | "country": "Italy" 72 | } 73 | }, 74 | { 75 | "model": "taxi.manufacturer", 76 | "pk": 10, 77 | "fields": { 78 | "name": "Renault", 79 | "country": "France" 80 | } 81 | }, 82 | { 83 | "model": "taxi.manufacturer", 84 | "pk": 11, 85 | "fields": { 86 | "name": "SAIC", 87 | "country": "China" 88 | } 89 | }, 90 | { 91 | "model": "taxi.manufacturer", 92 | "pk": 12, 93 | "fields": { 94 | "name": "Mazda", 95 | "country": "Japan" 96 | } 97 | }, 98 | { 99 | "model": "taxi.manufacturer", 100 | "pk": 13, 101 | "fields": { 102 | "name": "BAIC", 103 | "country": "China" 104 | } 105 | }, 106 | { 107 | "model": "taxi.manufacturer", 108 | "pk": 14, 109 | "fields": { 110 | "name": "BMW", 111 | "country": "Germany" 112 | } 113 | }, 114 | { 115 | "model": "taxi.driver", 116 | "pk": 1, 117 | "fields": { 118 | "password": "pbkdf2_sha256$320000$6uM28XWOFX6ewyloJzjqmt$L+ZhofPylneoaL2iZS4sZu4ZMUNb4oIevfAdzOMn/eE=", 119 | "last_login": "2022-08-08T14:14:03Z", 120 | "is_superuser": true, 121 | "username": "admin.user", 122 | "first_name": "Admin", 123 | "last_name": "User", 124 | "email": "admin.user@taxi.com", 125 | "is_staff": true, 126 | "is_active": true, 127 | "date_joined": "2022-08-08T13:58:29Z", 128 | "license_number": "ADM56984", 129 | "groups": [], 130 | "user_permissions": [] 131 | } 132 | }, 133 | { 134 | "model": "taxi.driver", 135 | "pk": 2, 136 | "fields": { 137 | "password": "pbkdf2_sha256$320000$73Z1Y57bcoscZ37s9rE9v0$4SEQ0TtXwJKzO5/CRdFSrTifatiPjkZ38gM2H8Kqsdg=", 138 | "last_login": null, 139 | "is_superuser": false, 140 | "username": "joyce.byers", 141 | "first_name": "Joyce", 142 | "last_name": "Byers", 143 | "email": "joyce.byers@taxi.com", 144 | "is_staff": false, 145 | "is_active": true, 146 | "date_joined": "2022-08-08T14:17:04Z", 147 | "license_number": "JOY26458", 148 | "groups": [], 149 | "user_permissions": [] 150 | } 151 | }, 152 | { 153 | "model": "taxi.driver", 154 | "pk": 3, 155 | "fields": { 156 | "password": "pbkdf2_sha256$320000$QtPol3T7x9quBj4BnbnVwW$UwT4kihkRfSvQEWEeuqVKwMgis2ikKVZfJDBcQMvOKY=", 157 | "last_login": null, 158 | "is_superuser": false, 159 | "username": "jim.hopper", 160 | "first_name": "Jim", 161 | "last_name": "Hopper", 162 | "email": "jim.hopper@taxi.com", 163 | "is_staff": false, 164 | "is_active": true, 165 | "date_joined": "2022-08-08T14:19:09Z", 166 | "license_number": "JIM26531", 167 | "groups": [], 168 | "user_permissions": [] 169 | } 170 | }, 171 | { 172 | "model": "taxi.driver", 173 | "pk": 4, 174 | "fields": { 175 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l1$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 176 | "last_login": null, 177 | "is_superuser": false, 178 | "username": "jonathan.byers", 179 | "first_name": "Jonathan", 180 | "last_name": "Byers", 181 | "email": "jonathan.byers@taxi.com", 182 | "is_staff": false, 183 | "is_active": true, 184 | "date_joined": "2022-08-08T14:19:09Z", 185 | "license_number": "JON26231", 186 | "groups": [], 187 | "user_permissions": [] 188 | } 189 | }, 190 | { 191 | "model": "taxi.driver", 192 | "pk": 5, 193 | "fields": { 194 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l1$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 195 | "last_login": null, 196 | "is_superuser": false, 197 | "username": "dustin.henderson", 198 | "first_name": "Dustin", 199 | "last_name": "Henderson", 200 | "email": "dustin.henderson@taxi.com", 201 | "is_staff": false, 202 | "is_active": true, 203 | "date_joined": "2022-08-09T14:19:09Z", 204 | "license_number": "DUS25131", 205 | "groups": [], 206 | "user_permissions": [] 207 | } 208 | }, 209 | { 210 | "model": "taxi.driver", 211 | "pk": 6, 212 | "fields": { 213 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l2$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 214 | "last_login": null, 215 | "is_superuser": false, 216 | "username": "mike.wheeler", 217 | "first_name": "Mike", 218 | "last_name": "Wheeler", 219 | "email": "mike.wheeler@taxi.com", 220 | "is_staff": false, 221 | "is_active": true, 222 | "date_joined": "2022-08-09T14:19:09Z", 223 | "license_number": "MIK25131", 224 | "groups": [], 225 | "user_permissions": [] 226 | } 227 | }, 228 | { 229 | "model": "taxi.driver", 230 | "pk": 7, 231 | "fields": { 232 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l2$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 233 | "last_login": null, 234 | "is_superuser": false, 235 | "username": "lucas.sinclair", 236 | "first_name": "Lucas", 237 | "last_name": "Sinclair", 238 | "email": "lucas.sinclair@taxi.com", 239 | "is_staff": false, 240 | "is_active": true, 241 | "date_joined": "2022-08-09T14:19:09Z", 242 | "license_number": "LUK35131", 243 | "groups": [], 244 | "user_permissions": [] 245 | } 246 | }, 247 | { 248 | "model": "taxi.driver", 249 | "pk": 8, 250 | "fields": { 251 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l2$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 252 | "last_login": null, 253 | "is_superuser": false, 254 | "username": "nancy.wheeler", 255 | "first_name": "Nancy", 256 | "last_name": "Wheeler", 257 | "email": "nancy.wheeler@taxi.com", 258 | "is_staff": false, 259 | "is_active": true, 260 | "date_joined": "2022-08-09T14:19:09Z", 261 | "license_number": "NAN34131", 262 | "groups": [], 263 | "user_permissions": [] 264 | } 265 | }, 266 | { 267 | "model": "taxi.driver", 268 | "pk": 9, 269 | "fields": { 270 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l2$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 271 | "last_login": null, 272 | "is_superuser": false, 273 | "username": "martin.brenner", 274 | "first_name": "Martin", 275 | "last_name": "Brenner", 276 | "email": "martin.brenner@taxi.com", 277 | "is_staff": false, 278 | "is_active": true, 279 | "date_joined": "2022-08-09T14:19:09Z", 280 | "license_number": "MAR34231", 281 | "groups": [], 282 | "user_permissions": [] 283 | } 284 | }, 285 | { 286 | "model": "taxi.driver", 287 | "pk": 10, 288 | "fields": { 289 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l2$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 290 | "last_login": null, 291 | "is_superuser": false, 292 | "username": "billy.hargrove", 293 | "first_name": "Billy", 294 | "last_name": "Hargrove", 295 | "email": "billy.hargrove@taxi.com", 296 | "is_staff": false, 297 | "is_active": true, 298 | "date_joined": "2022-08-09T14:19:09Z", 299 | "license_number": "BIL39231", 300 | "groups": [], 301 | "user_permissions": [] 302 | } 303 | }, 304 | { 305 | "model": "taxi.driver", 306 | "pk": 11, 307 | "fields": { 308 | "password": "pbkdf2_sha256$320000$9yU0PLTDKlL02PedUtk2l2$LKjj8O1xGiPGEkDpjsHk87db1iLgt8dgDqECzcsivio", 309 | "last_login": null, 310 | "is_superuser": false, 311 | "username": "bob.newby", 312 | "first_name": "Bob", 313 | "last_name": "Newby", 314 | "email": "bob.newby@taxi.com", 315 | "is_staff": false, 316 | "is_active": true, 317 | "date_joined": "2022-08-09T14:19:09Z", 318 | "license_number": "BOB09631", 319 | "groups": [], 320 | "user_permissions": [] 321 | } 322 | }, 323 | { 324 | "model": "taxi.car", 325 | "pk": 1, 326 | "fields": { 327 | "model": "Lincoln Continental", 328 | "manufacturer": 1, 329 | "drivers": [ 330 | 3 331 | ] 332 | } 333 | }, 334 | { 335 | "model": "taxi.car", 336 | "pk": 2, 337 | "fields": { 338 | "model": "Toyota Yaris", 339 | "manufacturer": 4, 340 | "drivers": [ 341 | 2, 342 | 3 343 | ] 344 | } 345 | }, 346 | { 347 | "model": "taxi.car", 348 | "pk": 3, 349 | "fields": { 350 | "model": "Suzuki Vitara", 351 | "manufacturer": 5, 352 | "drivers": [ 353 | 1, 354 | 2, 355 | 5 356 | ] 357 | } 358 | }, 359 | { 360 | "model": "taxi.car", 361 | "pk": 4, 362 | "fields": { 363 | "model": "Mitsubishi Eclipse", 364 | "manufacturer": 6, 365 | "drivers": [ 366 | 1 367 | ] 368 | } 369 | }, 370 | { 371 | "model": "taxi.car", 372 | "pk": 5, 373 | "fields": { 374 | "model": "Mitsubishi Lancer", 375 | "manufacturer": 6, 376 | "drivers": [ 377 | 11 378 | ] 379 | } 380 | }, 381 | { 382 | "model": "taxi.car", 383 | "pk": 6, 384 | "fields": { 385 | "model": "Ford Focus", 386 | "manufacturer": 2, 387 | "drivers": [ 388 | 4 389 | ] 390 | } 391 | }, 392 | { 393 | "model": "taxi.car", 394 | "pk": 7, 395 | "fields": { 396 | "model": "Cadillac Escalade", 397 | "manufacturer": 3, 398 | "drivers": [ 399 | 5 400 | ] 401 | } 402 | }, 403 | { 404 | "model": "taxi.car", 405 | "pk": 8, 406 | "fields": { 407 | "model": "Arcfox Alpha-S", 408 | "manufacturer": 13, 409 | "drivers": [ 410 | ] 411 | } 412 | }, 413 | { 414 | "model": "taxi.car", 415 | "pk": 9, 416 | "fields": { 417 | "model": "Beijing EX5", 418 | "manufacturer": 13, 419 | "drivers": [ 420 | 9 421 | ] 422 | } 423 | }, 424 | { 425 | "model": "taxi.car", 426 | "pk": 10, 427 | "fields": { 428 | "model": "MG 5 II", 429 | "manufacturer": 11, 430 | "drivers": [ 431 | 8 432 | ] 433 | } 434 | }, 435 | { 436 | "model": "taxi.car", 437 | "pk": 11, 438 | "fields": { 439 | "model": "Rising Auto ER6", 440 | "manufacturer": 11, 441 | "drivers": [ 442 | 7, 443 | 3 444 | ] 445 | } 446 | }, 447 | { 448 | "model": "taxi.car", 449 | "pk": 12, 450 | "fields": { 451 | "model": "SP250", 452 | "manufacturer": 8, 453 | "drivers": [ 454 | 6 455 | ] 456 | } 457 | }, 458 | { 459 | "model": "taxi.car", 460 | "pk": 13, 461 | "fields": { 462 | "model": "Daimler Sovereign", 463 | "manufacturer": 8, 464 | "drivers": [ 465 | 11, 466 | 7 467 | ] 468 | } 469 | }, 470 | { 471 | "model": "taxi.car", 472 | "pk": 14, 473 | "fields": { 474 | "model": "X5 M", 475 | "manufacturer": 14, 476 | "drivers": [ 477 | 10 478 | ] 479 | } 480 | }, 481 | { 482 | "model": "taxi.car", 483 | "pk": 15, 484 | "fields": { 485 | "model": "Floride", 486 | "manufacturer": 10, 487 | "drivers": [ 488 | 9 489 | ] 490 | } 491 | }, 492 | { 493 | "model": "taxi.car", 494 | "pk": 16, 495 | "fields": { 496 | "model": "MX-30", 497 | "manufacturer": 12, 498 | "drivers": [ 499 | 4 500 | ] 501 | } 502 | } 503 | ] 504 | --------------------------------------------------------------------------------