From 8fe6de3c8a38b17ff8f9fb7a11e4873a80ba3094 Mon Sep 17 00:00:00 2001 From: Lytillis Date: Tue, 7 Nov 2023 17:57:08 +0200 Subject: [PATCH 1/5] Tickets and Orders solution --- db/models.py | 73 +++++++++++++++++++++++++++++++++++++++ manage.py | 1 + services/movie.py | 9 +++-- services/movie_session.py | 12 +++++++ settings.py | 4 +++ 5 files changed, 97 insertions(+), 2 deletions(-) diff --git a/db/models.py b/db/models.py index 2a3359a63..1b098f0df 100644 --- a/db/models.py +++ b/db/models.py @@ -1,4 +1,6 @@ +from django.core.exceptions import ValidationError from django.db import models +from django.contrib.auth.models import AbstractUser class Genre(models.Model): @@ -25,6 +27,11 @@ class Movie(models.Model): def __str__(self) -> str: return self.title + class Meta: + indexes = [ + models.Index(fields=["title"]) + ] + class CinemaHall(models.Model): name = models.CharField(max_length=255) @@ -50,3 +57,69 @@ 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("db.User", on_delete=models.DO_NOTHING) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return self.created_at.strftime("%Y-%m-%d %H:%M:%S") + + +class Ticket(models.Model): + movie_session = models.ForeignKey(MovieSession, on_delete=models.CASCADE) + order = models.ForeignKey(Order, on_delete=models.DO_NOTHING) + row = models.IntegerField() + seat = models.IntegerField() + + def __str__(self) -> str: + return ( + f"{self.movie_session.movie.title} " + f"{self.movie_session.show_time.strftime('%Y-%m-%d %H:%M:%S')} " + f"(row: {self.row}, seat: {self.seat})" + ) + + def clean(self, *args, **kwargs) -> None: + super().clean(*args, **kwargs) + duplicates = Ticket.objects.filter( + movie_session=self.movie_session, + order=self.order, + row=self.row, + seat=self.seat, + ) + if duplicates.exists(): + raise ValidationError("Unique constraint failed") + if self.row > self.movie_session.cinema_hall.rows: + raise ValidationError({"row": [ + f"row number must be in available range:" + f" (1, rows): (1, {self.movie_session.cinema_hall.rows})" + ]}) + if self.seat > self.movie_session.cinema_hall.seats_in_row: + raise ValidationError({"seat": [ + f"seat number must be in available range: (1, seats_in_row):" + f" (1, {self.movie_session.cinema_hall.seats_in_row})" + ]}) + + def save(self, *args, **kwargs) -> None: + self.clean() + super(Ticket, self).save(*args, **kwargs) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=[ + "movie_session", + "row", + "seat" + ], + name="TicketUniqueConstraint" + ) + ] + + +class User(AbstractUser): + ... diff --git a/manage.py b/manage.py index e163372ae..b2660062b 100644 --- a/manage.py +++ b/manage.py @@ -4,6 +4,7 @@ if __name__ == "__main__": + print(__package__) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") from django.core.management import execute_from_command_line diff --git a/services/movie.py b/services/movie.py index 4d0f63237..5182e6eee 100644 --- a/services/movie.py +++ b/services/movie.py @@ -1,4 +1,4 @@ -from django.db.models import QuerySet +from django.db import models, transaction from db.models import Movie @@ -6,7 +6,8 @@ def get_movies( genres_ids: list[int] = None, actors_ids: list[int] = None, -) -> QuerySet: + title: str = None +) -> models.QuerySet: queryset = Movie.objects.all() if genres_ids: @@ -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,6 +26,7 @@ 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, diff --git a/services/movie_session.py b/services/movie_session.py index f326a082e..fffa53cfb 100644 --- a/services/movie_session.py +++ b/services/movie_session.py @@ -42,3 +42,15 @@ 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]: + movie_session = get_movie_session_by_id(movie_session_id) + + return [ + { + "row": ticket.row, + "seat": ticket.seat + } + for ticket in movie_session.ticket_set.all() + ] diff --git a/settings.py b/settings.py index f25910b30..f9f0f92fb 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 549e0d04f6bfbec3473d7136c7fb1a76ca0e2451 Mon Sep 17 00:00:00 2001 From: Lytillis Date: Tue, 7 Nov 2023 17:58:30 +0200 Subject: [PATCH 2/5] Tickets and Orders solution --- ...rder_ticket_alter_movie_actors_and_more.py | 114 ++++++++++++++++++ ..._ticket_ticketuniqueconstraint_and_more.py | 21 ++++ ...04_remove_ticket_ticketuniqueconstraint.py | 17 +++ .../0005_ticket_ticketuniqueconstraint.py | 17 +++ services/order.py | 37 ++++++ services/user.py | 54 +++++++++ 6 files changed, 260 insertions(+) create mode 100644 db/migrations/0002_user_order_ticket_alter_movie_actors_and_more.py create mode 100644 db/migrations/0003_remove_ticket_ticketuniqueconstraint_and_more.py create mode 100644 db/migrations/0004_remove_ticket_ticketuniqueconstraint.py create mode 100644 db/migrations/0005_ticket_ticketuniqueconstraint.py create mode 100644 services/order.py create mode 100644 services/user.py diff --git a/db/migrations/0002_user_order_ticket_alter_movie_actors_and_more.py b/db/migrations/0002_user_order_ticket_alter_movie_actors_and_more.py new file mode 100644 index 000000000..c15a8bf7f --- /dev/null +++ b/db/migrations/0002_user_order_ticket_alter_movie_actors_and_more.py @@ -0,0 +1,114 @@ +# Generated by Django 4.0.2 on 2023-11-07 15:09 + +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', '0001_initial'), + ] + + 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')), + ], + 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)), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + 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()), + ], + ), + 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'), + ), + migrations.AddIndex( + model_name='movie', + index=models.Index(fields=['title'], name='db_movie_title_5d0841_idx'), + ), + migrations.AddField( + model_name='ticket', + name='movie_session', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='db.moviesession'), + ), + migrations.AddField( + model_name='ticket', + name='order', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='db.order'), + ), + migrations.AddField( + model_name='order', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='user', + name='groups', + field=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'), + ), + migrations.AddField( + model_name='user', + name='user_permissions', + field=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'), + ), + migrations.AddConstraint( + model_name='ticket', + constraint=models.UniqueConstraint(fields=('row', 'seat', 'movie_session'), name='TicketUniqueConstraint'), + ), + ] diff --git a/db/migrations/0003_remove_ticket_ticketuniqueconstraint_and_more.py b/db/migrations/0003_remove_ticket_ticketuniqueconstraint_and_more.py new file mode 100644 index 000000000..cbb5495af --- /dev/null +++ b/db/migrations/0003_remove_ticket_ticketuniqueconstraint_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.2 on 2023-11-07 16:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0002_user_order_ticket_alter_movie_actors_and_more'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='ticket', + name='TicketUniqueConstraint', + ), + migrations.AddConstraint( + model_name='ticket', + constraint=models.UniqueConstraint(fields=('movie_session', 'row', 'seat'), name='TicketUniqueConstraint'), + ), + ] diff --git a/db/migrations/0004_remove_ticket_ticketuniqueconstraint.py b/db/migrations/0004_remove_ticket_ticketuniqueconstraint.py new file mode 100644 index 000000000..1e2483b66 --- /dev/null +++ b/db/migrations/0004_remove_ticket_ticketuniqueconstraint.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.2 on 2023-11-07 16:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0003_remove_ticket_ticketuniqueconstraint_and_more'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='ticket', + name='TicketUniqueConstraint', + ), + ] diff --git a/db/migrations/0005_ticket_ticketuniqueconstraint.py b/db/migrations/0005_ticket_ticketuniqueconstraint.py new file mode 100644 index 000000000..293dfbb68 --- /dev/null +++ b/db/migrations/0005_ticket_ticketuniqueconstraint.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.2 on 2023-11-07 16:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0004_remove_ticket_ticketuniqueconstraint'), + ] + + operations = [ + migrations.AddConstraint( + model_name='ticket', + constraint=models.UniqueConstraint(fields=('movie_session', 'row', 'seat'), name='TicketUniqueConstraint'), + ), + ] diff --git a/services/order.py b/services/order.py new file mode 100644 index 000000000..6141f10e8 --- /dev/null +++ b/services/order.py @@ -0,0 +1,37 @@ +import datetime +from django.db import transaction, models + +from db.models import Ticket, Order, User +from .movie_session import get_movie_session_by_id + + +@transaction.atomic +def create_order( + tickets: list[dict], username: str, date: datetime = None +) -> Order: + user = User.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( + movie_session=get_movie_session_by_id(ticket["movie_session"]), + order=order, + row=ticket["row"], + seat=ticket["seat"], + ) + + return Order + + +@transaction.atomic +def get_orders(username: str = None) -> models.QuerySet: + if username: + return Order.objects.filter(user__username=username) + + return Order.objects.all() diff --git a/services/user.py b/services/user.py new file mode 100644 index 000000000..2df7ec556 --- /dev/null +++ b/services/user.py @@ -0,0 +1,54 @@ +from db.models import User + + +def create_user( + username: str, + password: str, + email: str = None, + first_name: str = None, + last_name: 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) -> User: + return User.objects.get(pk=user_id) + + +def update_user( + user_id: int, + username: str = None, + password: str = None, + email: str = None, + first_name: str = None, + last_name: str = None +) -> User: + user = get_user(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 From 20186a60758efbe0c3e3329340c599cbed0faf86 Mon Sep 17 00:00:00 2001 From: Lytillis Date: Tue, 7 Nov 2023 18:03:50 +0200 Subject: [PATCH 3/5] Fixed clean() and save() methods in Ticket model --- db/models.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/db/models.py b/db/models.py index 1b098f0df..ed033d596 100644 --- a/db/models.py +++ b/db/models.py @@ -84,15 +84,6 @@ def __str__(self) -> str: ) def clean(self, *args, **kwargs) -> None: - super().clean(*args, **kwargs) - duplicates = Ticket.objects.filter( - movie_session=self.movie_session, - order=self.order, - row=self.row, - seat=self.seat, - ) - if duplicates.exists(): - raise ValidationError("Unique constraint failed") if self.row > self.movie_session.cinema_hall.rows: raise ValidationError({"row": [ f"row number must be in available range:" @@ -105,7 +96,7 @@ def clean(self, *args, **kwargs) -> None: ]}) def save(self, *args, **kwargs) -> None: - self.clean() + self.full_clean() super(Ticket, self).save(*args, **kwargs) class Meta: From 0e840d89bebdc7bafffb465ce52726c7d274e7c5 Mon Sep 17 00:00:00 2001 From: Lytillis Date: Tue, 7 Nov 2023 19:51:06 +0200 Subject: [PATCH 4/5] Changed on_delete for models --- db/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/models.py b/db/models.py index ed033d596..b826dbfcb 100644 --- a/db/models.py +++ b/db/models.py @@ -72,7 +72,7 @@ def __str__(self) -> str: class Ticket(models.Model): movie_session = models.ForeignKey(MovieSession, on_delete=models.CASCADE) - order = models.ForeignKey(Order, on_delete=models.DO_NOTHING) + order = models.ForeignKey(Order, on_delete=models.CASCADE) row = models.IntegerField() seat = models.IntegerField() From a24de31faa24dab1ae994919f829f73f990bef29 Mon Sep 17 00:00:00 2001 From: Lytillis Date: Wed, 15 Nov 2023 19:21:24 +0200 Subject: [PATCH 5/5] Changed code following the checklist, removed redundant code --- db/models.py | 32 ++++++++++++++++---------------- manage.py | 1 - services/order.py | 5 +++-- services/user.py | 5 +++-- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/db/models.py b/db/models.py index b826dbfcb..f16f6486a 100644 --- a/db/models.py +++ b/db/models.py @@ -24,14 +24,14 @@ class Movie(models.Model): actors = models.ManyToManyField(to=Actor, related_name="movies") genres = models.ManyToManyField(to=Genre, related_name="movies") - def __str__(self) -> str: - return self.title - class Meta: indexes = [ models.Index(fields=["title"]) ] + def __str__(self) -> str: + return self.title + class CinemaHall(models.Model): name = models.CharField(max_length=255) @@ -76,6 +76,18 @@ class Ticket(models.Model): row = models.IntegerField() seat = models.IntegerField() + class Meta: + constraints = [ + models.UniqueConstraint( + fields=[ + "movie_session", + "row", + "seat" + ], + name="TicketUniqueConstraint" + ) + ] + def __str__(self) -> str: return ( f"{self.movie_session.movie.title} " @@ -99,18 +111,6 @@ def save(self, *args, **kwargs) -> None: self.full_clean() super(Ticket, self).save(*args, **kwargs) - class Meta: - constraints = [ - models.UniqueConstraint( - fields=[ - "movie_session", - "row", - "seat" - ], - name="TicketUniqueConstraint" - ) - ] - class User(AbstractUser): - ... + pass diff --git a/manage.py b/manage.py index b2660062b..e163372ae 100644 --- a/manage.py +++ b/manage.py @@ -4,7 +4,6 @@ if __name__ == "__main__": - print(__package__) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") from django.core.management import execute_from_command_line diff --git a/services/order.py b/services/order.py index 6141f10e8..17d9cf57b 100644 --- a/services/order.py +++ b/services/order.py @@ -1,7 +1,8 @@ import datetime from django.db import transaction, models +from django.contrib.auth import get_user_model -from db.models import Ticket, Order, User +from db.models import Ticket, Order from .movie_session import get_movie_session_by_id @@ -9,7 +10,7 @@ def create_order( tickets: list[dict], username: str, date: datetime = None ) -> Order: - user = User.objects.get(username=username) + user = get_user_model().objects.get(username=username) order = Order.objects.create( user=user, ) diff --git a/services/user.py b/services/user.py index 2df7ec556..9f7db97a5 100644 --- a/services/user.py +++ b/services/user.py @@ -1,4 +1,5 @@ from db.models import User +from django.contrib.auth import get_user_model def create_user( @@ -8,7 +9,7 @@ def create_user( first_name: str = None, last_name: str = None, ) -> User: - user = User.objects.create_user( + user = get_user_model().objects.create_user( username=username, password=password ) @@ -25,7 +26,7 @@ def create_user( def get_user(user_id: int) -> User: - return User.objects.get(pk=user_id) + return get_user_model().objects.get(pk=user_id) def update_user(