From 024585b296397730dccd98480ea9e4b414abe16b Mon Sep 17 00:00:00 2001 From: Matvii Safonov Date: Sun, 10 Nov 2024 14:28:48 +0200 Subject: [PATCH 1/4] Solution --- db/models.py | 68 ++++++++++++++++++++++++++++++++++++++- services/movie.py | 19 +++++++---- services/movie_session.py | 21 ++++++++---- services/order.py | 43 +++++++++++++++++++++++++ services/user.py | 59 +++++++++++++++++++++++++++++++++ settings.py | 4 +++ 6 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 services/order.py create mode 100644 services/user.py diff --git a/db/models.py b/db/models.py index 2a3359a63..e6f1a67e7 100644 --- a/db/models.py +++ b/db/models.py @@ -1,4 +1,8 @@ +from django.contrib.auth.models import AbstractUser +from django.core.exceptions import ValidationError from django.db import models +from django.db.models import UniqueConstraint +import settings class Genre(models.Model): @@ -17,7 +21,7 @@ def __str__(self) -> str: class Movie(models.Model): - title = models.CharField(max_length=255) + title = models.CharField(max_length=255, db_index=True) description = models.TextField() actors = models.ManyToManyField(to=Actor, related_name="movies") genres = models.ManyToManyField(to=Genre, related_name="movies") @@ -50,3 +54,65 @@ class MovieSession(models.Model): def __str__(self) -> str: return f"{self.movie.title} {str(self.show_time)}" + + +class Order(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey( + to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="orders", + ) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return f"{self.created_at}" + + +class Ticket(models.Model): + movie_session = models.ForeignKey( + to=MovieSession, on_delete=models.CASCADE, related_name="tickets" + ) + order = models.ForeignKey( + to=Order, on_delete=models.CASCADE, related_name="tickets" + ) + row = models.IntegerField() + seat = models.IntegerField() + + class Meta: + constraints = [ + UniqueConstraint( + fields=["row", "seat", "movie_session"], + name="ticket_constraint", + ) + ] + + def clean(self) -> None: + if self.seat > self.movie_session.cinema_hall.seats_in_row: + raise ValidationError( + {"seat": [ + f"seat number must be in available range: " + f"(1, seats_in_row): " + f"(1, {self.movie_session.cinema_hall.seats_in_row})" + ]} + ) + if self.row > self.movie_session.cinema_hall.rows: + raise ValidationError( + {"row": [ + f"row number must be in available range: (1, rows): " + f"(1, {self.movie_session.cinema_hall.rows})" + ]} + ) + + def save(self, *args, **kwargs) -> None: + self.full_clean() + return super().save(*args, **kwargs) + + def __str__(self) -> str: + return f"{self.movie_session} (row: {self.row}, seat: {self.seat})" + + +class User(AbstractUser): + pass diff --git a/services/movie.py b/services/movie.py index 4d0f63237..bad36efc6 100644 --- a/services/movie.py +++ b/services/movie.py @@ -1,11 +1,12 @@ from django.db.models import QuerySet - +from django.db import transaction from db.models import Movie def get_movies( - genres_ids: list[int] = None, - actors_ids: list[int] = None, + genres_ids: list[int] = None, + actors_ids: list[int] = None, + title: str = None, ) -> QuerySet: queryset = Movie.objects.all() @@ -15,6 +16,9 @@ def get_movies( if actors_ids: queryset = queryset.filter(actors__id__in=actors_ids) + if title: + queryset = queryset.filter(title__contains=title) + return queryset @@ -22,11 +26,12 @@ def get_movie_by_id(movie_id: int) -> Movie: return Movie.objects.get(id=movie_id) +@transaction.atomic def create_movie( - movie_title: str, - movie_description: str, - genres_ids: list = None, - actors_ids: list = None, + movie_title: str, + movie_description: str, + genres_ids: list = None, + actors_ids: list = None, ) -> Movie: movie = Movie.objects.create( title=movie_title, diff --git a/services/movie_session.py b/services/movie_session.py index f326a082e..2575ad44f 100644 --- a/services/movie_session.py +++ b/services/movie_session.py @@ -1,10 +1,9 @@ from django.db.models import QuerySet - -from db.models import MovieSession +from db.models import MovieSession, Ticket def create_movie_session( - movie_show_time: str, movie_id: int, cinema_hall_id: int + movie_show_time: str, movie_id: int, cinema_hall_id: int ) -> MovieSession: return MovieSession.objects.create( show_time=movie_show_time, @@ -25,10 +24,10 @@ def get_movie_session_by_id(movie_session_id: int) -> MovieSession: def update_movie_session( - session_id: int, - show_time: str = None, - movie_id: int = None, - cinema_hall_id: int = None, + session_id: int, + show_time: str = None, + movie_id: int = None, + cinema_hall_id: int = None, ) -> None: movie_session = MovieSession.objects.get(id=session_id) if show_time: @@ -42,3 +41,11 @@ def update_movie_session( def delete_movie_session_by_id(session_id: int) -> None: MovieSession.objects.get(id=session_id).delete() + + +def get_taken_seats(movie_session_id: int) -> list[dict]: + return [ + ticket for ticket in Ticket.objects.filter( + movie_session=movie_session_id + ).values("row", "seat") + ] diff --git a/services/order.py b/services/order.py new file mode 100644 index 000000000..e68d36417 --- /dev/null +++ b/services/order.py @@ -0,0 +1,43 @@ +from datetime import datetime + +from django.contrib.auth import get_user_model + +from django.db import transaction + +from django.db.models import QuerySet + +from db.models import Order, Ticket + + +@transaction.atomic +def create_order( + tickets: list[dict], + username: str, + date: datetime = None, +) -> Order: + user = get_user_model().objects.get(username=username) + order = Order.objects.create( + user=user + ) + + if date: + order.created_at = date + order.save() + + for ticket in tickets: + Ticket.objects.create( + row=ticket.get("row"), + seat=ticket.get("seat"), + movie_session_id=ticket.get("movie_session"), + order=order + ) + + return order + + +def get_orders(username: str = None) -> QuerySet[Order]: + if username: + user = get_user_model().objects.get(username=username) + return Order.objects.filter(user=user) + + return Order.objects.all() diff --git a/services/user.py b/services/user.py new file mode 100644 index 000000000..41f855e8e --- /dev/null +++ b/services/user.py @@ -0,0 +1,59 @@ +from django.contrib.auth import get_user_model +from django.core.exceptions import ObjectDoesNotExist +from typing import Optional + +User = get_user_model() + + +def create_user( + username: str, + password: str, + email: Optional[str] = None, + first_name: Optional[str] = None, + last_name: Optional[str] = None +) -> User: + user = User.objects.create_user( + username=username, + password=password + ) + if email: + user.email = email + if first_name: + user.first_name = first_name + if last_name: + user.last_name = last_name + user.save() + return user + + +def get_user(user_id: int) -> Optional[User]: + try: + return User.objects.get(id=user_id) + except User.DoesNotExist: + return None + + +def update_user( + user_id: int, + username: Optional[str] = None, + password: Optional[str] = None, + email: Optional[str] = None, + first_name: Optional[str] = None, + last_name: Optional[str] = None +) -> Optional[User]: + try: + user = User.objects.get(id=user_id) + if username: + user.username = username + if password: + user.set_password(password) + if email: + user.email = email + if first_name: + user.first_name = first_name + if last_name: + user.last_name = last_name + user.save() + return user + except ObjectDoesNotExist: + return None diff --git a/settings.py b/settings.py index f25910b30..6dfeee6b1 100644 --- a/settings.py +++ b/settings.py @@ -25,4 +25,8 @@ INSTALLED_APPS = [ "db", + "django.contrib.auth", + "django.contrib.contenttypes", ] + +AUTH_USER_MODEL = "db.User" From 0b027eb6357419d5f66402231e6a4f016712599e Mon Sep 17 00:00:00 2001 From: Matvii Safonov Date: Sun, 10 Nov 2024 14:29:27 +0200 Subject: [PATCH 2/4] Solution --- ...order_alter_movie_title_ticket_and_more.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 db/migrations/0003_user_order_alter_movie_title_ticket_and_more.py diff --git a/db/migrations/0003_user_order_alter_movie_title_ticket_and_more.py b/db/migrations/0003_user_order_alter_movie_title_ticket_and_more.py new file mode 100644 index 000000000..1d6678c1a --- /dev/null +++ b/db/migrations/0003_user_order_alter_movie_title_ticket_and_more.py @@ -0,0 +1,75 @@ +# Generated by Django 4.0.2 on 2024-11-10 14:10 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('db', '0002_alter_movie_actors_alter_movie_genres_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('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')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('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')), + ('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')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + migrations.AlterField( + model_name='movie', + name='title', + field=models.CharField(db_index=True, max_length=255), + ), + migrations.CreateModel( + name='Ticket', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('row', models.IntegerField()), + ('seat', models.IntegerField()), + ('movie_session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to='db.moviesession')), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to='db.order')), + ], + ), + migrations.AddConstraint( + model_name='ticket', + constraint=models.UniqueConstraint(fields=('row', 'seat', 'movie_session'), name='ticket_constraint'), + ), + ] From cb529aad206e8a1c0d4caf42c2fa571d497fda46 Mon Sep 17 00:00:00 2001 From: Matvii Safonov Date: Sun, 10 Nov 2024 14:31:27 +0200 Subject: [PATCH 3/4] Solution --- ...ovie_actors_alter_movie_genres_and_more.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 db/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py diff --git a/db/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py b/db/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py new file mode 100644 index 000000000..068452ffe --- /dev/null +++ b/db/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.0.2 on 2024-11-10 13:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='movie', + name='actors', + field=models.ManyToManyField(related_name='movies', to='db.Actor'), + ), + migrations.AlterField( + model_name='movie', + name='genres', + field=models.ManyToManyField(related_name='movies', to='db.Genre'), + ), + migrations.AlterField( + model_name='moviesession', + name='cinema_hall', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='movie_sessions', to='db.cinemahall'), + ), + migrations.AlterField( + model_name='moviesession', + name='movie', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='movie_sessions', to='db.movie'), + ), + ] From 618df39d62b9fad1e9ed5dbb5f000f5632a015c7 Mon Sep 17 00:00:00 2001 From: Matvii Safonov Date: Mon, 11 Nov 2024 16:51:10 +0200 Subject: [PATCH 4/4] Solution --- services/order.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/order.py b/services/order.py index e68d36417..0262e762e 100644 --- a/services/order.py +++ b/services/order.py @@ -1,11 +1,7 @@ from datetime import datetime - from django.contrib.auth import get_user_model - from django.db import transaction - from django.db.models import QuerySet - from db.models import Order, Ticket