From 7b9b081f002ed714323e265d97c5eb4d023d44ea Mon Sep 17 00:00:00 2001 From: Anton Hryhorenko Date: Sun, 22 Dec 2024 19:18:17 +0100 Subject: [PATCH] Complete project with serializers and views(API, Generic and ViewSet) --- .gitignore | 2 +- ...emahall_genre_movie_actors_movie_genres.py | 47 +++++++ cinema/models.py | 26 ++++ cinema/serializers.py | 60 +++++++- cinema/urls.py | 22 ++- cinema/views.py | 131 ++++++++++++++---- db.sqlite3 | Bin 0 -> 188416 bytes 7 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py create mode 100644 db.sqlite3 diff --git a/.gitignore b/.gitignore index 9c18d6ac5..1d330c009 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ venv/ .pytest_cache/ **__pycache__/ *.pyc -db.sqlite3 \ No newline at end of file +db.sqlite3git \ No newline at end of file diff --git a/cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py b/cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py new file mode 100644 index 000000000..17ccc84d5 --- /dev/null +++ b/cinema/migrations/0002_actor_cinemahall_genre_movie_actors_movie_genres.py @@ -0,0 +1,47 @@ +# Generated by Django 4.1 on 2024-12-22 17:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cinema', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Actor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('first_name', models.CharField(max_length=50)), + ('last_name', models.CharField(max_length=50)), + ], + ), + migrations.CreateModel( + name='CinemaHall', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('rows', models.PositiveIntegerField()), + ('seats_in_row', models.PositiveIntegerField()), + ], + ), + migrations.CreateModel( + name='Genre', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ], + ), + migrations.AddField( + model_name='movie', + name='actors', + field=models.ManyToManyField(to='cinema.actor'), + ), + migrations.AddField( + model_name='movie', + name='genres', + field=models.ManyToManyField(to='cinema.genre'), + ), + ] diff --git a/cinema/models.py b/cinema/models.py index cc477513f..d8090ef27 100644 --- a/cinema/models.py +++ b/cinema/models.py @@ -1,10 +1,36 @@ from django.db import models +class Actor(models.Model): + first_name = models.CharField(max_length=50) + last_name = models.CharField(max_length=50) + + def __str__(self): + return f"{self.first_name} {self.last_name}" + + +class Genre(models.Model): + name = models.CharField(max_length=100, unique=True) + + def __str__(self): + return self.name + + +class CinemaHall(models.Model): + name = models.CharField(max_length=100) + rows = models.PositiveIntegerField() + seats_in_row = models.PositiveIntegerField() + + def __str__(self): + return self.name + + class Movie(models.Model): title = models.CharField(max_length=255) description = models.TextField() duration = models.IntegerField() + actors = models.ManyToManyField(Actor) + genres = models.ManyToManyField(Genre) def __str__(self): return self.title diff --git a/cinema/serializers.py b/cinema/serializers.py index 050db5771..6a0da01c3 100644 --- a/cinema/serializers.py +++ b/cinema/serializers.py @@ -1,6 +1,5 @@ from rest_framework import serializers - -from cinema.models import Movie +from cinema.models import Movie, Actor, Genre, CinemaHall class MovieSerializer(serializers.Serializer): @@ -22,3 +21,60 @@ def update(self, instance, validated_data): instance.save() return instance + + +class ActorSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + first_name = serializers.CharField(max_length=255) + last_name = serializers.CharField(max_length=255) + + def create(self, validated_data): + return Actor.objects.create(**validated_data) + + def update(self, instance, validated_data): + instance.first_name = validated_data.get( + "first_name", instance.first_name + ) + instance.last_name = validated_data.get( + "last_name", instance.last_name + ) + + instance.save() + + return instance + + +class GenreSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + name = serializers.CharField(max_length=255, required=True) + + def create(self, validated_data): + return Genre.objects.create(**validated_data) + + def update(self, instance, validated_data): + instance.name = validated_data.get("name", instance.name) + + instance.save() + + return instance + + +class CinemaHallSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + name = serializers.CharField(max_length=255, required=True) + rows = serializers.IntegerField(required=True) + seats_in_row = serializers.IntegerField(required=True) + + def create(self, validated_data): + return CinemaHall.objects.create(**validated_data) + + def update(self, instance, validated_data): + instance.name = validated_data.get("name", instance.name) + instance.rows = validated_data.get("rows", instance.rows) + instance.seats_in_row = validated_data.get( + "seats_in_row", instance.seats_in_row + ) + + instance.save() + + return instance diff --git a/cinema/urls.py b/cinema/urls.py index 1ae7d5cb0..f110d7ced 100644 --- a/cinema/urls.py +++ b/cinema/urls.py @@ -1,10 +1,24 @@ -from django.urls import path +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from cinema.views import ( + GenreList, + GenreDetail, + CinemaHallViewSet, + MovieViewSet, + ActorList, + ActorDetail, +) -from cinema.views import movie_list, movie_detail +router = DefaultRouter() +router.register("cinema_halls", CinemaHallViewSet, basename="cinema_hall") +router.register("movies", MovieViewSet, basename="movie") urlpatterns = [ - path("movies/", movie_list, name="movie-list"), - path("movies//", movie_detail, name="movie-detail"), + path("genres/", GenreList.as_view(), name="genre-list"), + path("genres//", GenreDetail.as_view(), name="genre-detail"), + path("actors/", ActorList.as_view(), name="actor-list"), + path("actors//", ActorDetail.as_view(), name="actor-detail"), + path("", include(router.urls)), ] app_name = "cinema" diff --git a/cinema/views.py b/cinema/views.py index 78ba8a79c..f7d6acb88 100644 --- a/cinema/views.py +++ b/cinema/views.py @@ -1,45 +1,124 @@ -from rest_framework.decorators import api_view +from django.http import Http404 +from rest_framework.generics import ( + RetrieveAPIView, + CreateAPIView, + UpdateAPIView, + DestroyAPIView, GenericAPIView, +) +from rest_framework.mixins import ( + ListModelMixin, + CreateModelMixin, + RetrieveModelMixin, + UpdateModelMixin, + DestroyModelMixin +) from rest_framework.response import Response from rest_framework import status +from rest_framework.views import APIView +from rest_framework.viewsets import GenericViewSet, ModelViewSet -from django.shortcuts import get_object_or_404 +from cinema.models import Movie, Genre, Actor, CinemaHall +from cinema.serializers import MovieSerializer, GenreSerializer, ActorSerializer, CinemaHallSerializer -from cinema.models import Movie -from cinema.serializers import MovieSerializer +class GenreList(APIView): + def get(self, request): + genres = Genre.objects.all() + serializer = GenreSerializer(genres, many=True) + return Response(serializer.data) -@api_view(["GET", "POST"]) -def movie_list(request): - if request.method == "GET": - movies = Movie.objects.all() - serializer = MovieSerializer(movies, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - if request.method == "POST": - serializer = MovieSerializer(data=request.data) + def post(self, request): + serializer = GenreSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -@api_view(["GET", "PUT", "DELETE"]) -def movie_detail(request, pk): - movie = get_object_or_404(Movie, pk=pk) +class GenreDetail(APIView): + def get_object(self, pk): + try: + return Genre.objects.get(pk=pk) + except Genre.DoesNotExist: + raise Http404 - if request.method == "GET": - serializer = MovieSerializer(movie) - return Response(serializer.data, status=status.HTTP_200_OK) + def get(self, request, pk): + genre = self.get_object(pk) + serializer = GenreSerializer(genre) + return Response(serializer.data) - if request.method == "PUT": - serializer = MovieSerializer(movie, data=request.data) + def put(self, request, pk): + genre = self.get_object(pk) + serializer = GenreSerializer(genre, data=request.data) if serializer.is_valid(): serializer.save() - return Response(serializer.data, status=status.HTTP_200_OK) - + return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - if request.method == "DELETE": - movie.delete() + def putch(self, request, pk): + genre = self.get_object(pk) + serializer = GenreSerializer(genre, data=request.data, partial=True) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + def delete(self, request, pk): + genre = self.get_object(pk) + genre.delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class ActorDetail( + CreateAPIView, + RetrieveAPIView, + UpdateAPIView, + DestroyAPIView, +): + queryset = Actor.objects.all() + serializer_class = ActorSerializer + + +class ActorList( + GenericAPIView, + ListModelMixin, + CreateModelMixin, + RetrieveModelMixin, + UpdateModelMixin, + DestroyModelMixin +): + queryset = Actor.objects.all() + serializer_class = ActorSerializer + + def get(self, request, *args, **kwargs): + if "pk" in kwargs: + return self.retrieve(request, *args, **kwargs) + return self.list(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self.create(request, *args, **kwargs) + + def put(self, request, *args, **kwargs): + return self.update(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) + + def patch(self, request, *args, **kwargs): + return self.partial_update(request, *args, **kwargs) + + +class CinemaHallViewSet( + GenericViewSet, + ListModelMixin, + CreateModelMixin, + RetrieveModelMixin, + UpdateModelMixin, + DestroyModelMixin +): + queryset = CinemaHall.objects.all() + serializer_class = CinemaHallSerializer + + +class MovieViewSet(ModelViewSet): + queryset = Movie.objects.all() + serializer_class = MovieSerializer diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..e0188541ba5b0c2585a37e785fad1518f88965dc GIT binary patch literal 188416 zcmeI54{#gVec!Rb0w72NkCep2>GX$7NR$XulmLPt$orEsCE@OPqNqEHs!o}DkOi;= zR|2pU07^%@lMZ-tk|s@NlC){snZ_AUJ=2-m6SotOr~Wfdoj>QMb>i{Zt?R~aI@8#b zOkI1@j~0=oc~f6O~uce*b}+JEo&e((D}@9o3Bw{Lf^zr3V3G-b`GRn&%( z@(g(-$@5-C@pwF^>Hm59-}+mk4Y{>LYss;_)8=W<`P*OgQ>d4{!EN5dzR%tmcw^wq zz`qTBCHU8&4+lQyFUh~``wQ>idap^p;`!oEj`n-J`Rt)!^v;yjtn1aH_NuzsSkGOL;>{2p?>}{Tj zxtT(`Fh8vcWj)4+tXw$d_ZaQq(ILJk3eQ4^4iY~(6pX%)#9!S_yy=N_=abWkLVj)D zUH9<5boV&hOL8j$slCnmqD|ktw0&w{ayv4cOB9N!>2y9}*PI?gLp#@>9$ULg?vl*D z^F~#w!wqHjbcBj#vZH7mCIzF~(5j7GV{1d>Rcd-|CNrze`Hd|Quewd zQ7etOa+!y&2uX{}i>r$ZOG~$`gW1bHm9SS+k9V8J{?dqVCkBGisVV8f;V!h|408ZA z_Xl^vXa>Kn;F)d2{z^sI?i1N-?+FE?qodN#oa#i@u6+GFKspee4YgjsZ`7zp-cxIZ zb+tB;oSuuft0h&EvRZHC%0@}A#+0Jk&>DJ0bB62nTz!)&2Ct+sC2tsIXDC0)n*iH? zDq*{~e!D4^HN8g3>}=T*va5wwQT4K&QYLMuMDe-MhPt-aE0RLgLPNhtD!cXb9OrHu zdQ~GtJ=u>Z`vcMJsN@mNVf}8IF4uBYP-IWnH+;*wz)7|@2ey~{g3 zy|g2HRGi(H1iKe#@wQqm867{~mZZIZoZ7$7cFUA-Xo)vpkb_YpB{e@FPBxdMa_YuL zuB_&@a;`#GBIR6WHeXB?Q+aV2;u67c!R+i&xESv4*}MvIiN^EZ*2Fs&LKa2gT!EO{4;x1jWsPUIxmR_|Ua?L4^$$6xXWc-6io@@ch~b?xq^RxM~< zyS|S6bp=lFbMPF+j5lvd!RUnx(zfbkSJ6u~wZS`M78m<~x5IC&C;ULvh>QKZGo9}I z;puEIxvpAO#-&kTNR z@I!-tXK;Anj|VJ42jPba2lrjpZB)6+^a^JF^lWGZt$lbBCUXMXaq-*fc-LV?CyDk?>p=7TdvEoUBB zSIgyGNvqa0>eSuSX}E<4)~y{8Pbr=Bx?^^wzwra7M(`q%U0~M-CYDsfPG@Bq% z3j^eAvE^(wSEJsgPL;(PxZrnC>N#GkT+1mLU-WvQDI&9>h53UT9}XM(TwiIcZg1ekO^^ zYYz~K5!byur3lL03jx1p{P_hZMsaS|G&&92c+ERhyau`AO(o_Mvx|PeCrX-%6}?I> zQhackZf@1KL^<<;SfiR-E32hkVcpPaT)@@Ubb_+CAd@SiY`L1YU0Jf|tTnh>R~N}N zxk&iP#dzDr?hQg>E;BoS!AndjhG-CWZ_s8FNvcm7iO^A_W8TCQHIVYXYf9;P8it<_ zv-f)jBj5D0KWATNpJp2S1@_M(-(<_|Y4#pA9QnTwvuQ`#AOHd&00JNY0w4eaAOHd& z00JQJ!x0!7^o>jA;GQ{(Cr5{Usg5JoAm3ngs5`(M@EaUrwhu8N7#fP$M}$#uB<#D; z@@kJI%7^i&1-p_uM{lX8&7}g*F z0w4eaAOHd&00JNY0w4eaAn+&?7?O`htv@k3l!%Xzw`y2-l! z7!qhrh~V@8C;E?j*q^X}!+wtC*^_KA@{P!EM}93*i!4XZM-GL*8~&s4$HN~CuZB+z zzA^aa!H*6;82qWhi-RWz{$}871D_fA^?}B~>cC|G5Bk5*|Ka`z{Xf(v6*^@bRgBdhS`HmlB9$+``NSC$!7FyKc9o$c0xO5X#6yVPNqXbg?&NYmSP@K_@w=*Q)GH! zK4b~%G`3VZ&10syrLEK466i2LJ4WX56kR_T9S;g{9bBu!@T5Y9WAQ$rSX}J4#frVy zD20#HEjp81+rm<4+MG0PENyKYOP1I_eu8X9&-QUaZ3C`F?3_F zB88x(qHSTRFm0YNZ7daS8%u@QKYmoG2yzu|1FpjK@VM|05Gt%rOVEwQ`12$s&S2$oi7gg8C_K~>z(1$S9<(N5Fj^neGIxh$0P zZeLq6-%;q{4a&J}No|E&8ifTtnZYeAZ7mB+6yJS<9=VWCjLTe1E10X`+b8KU3aZ&Y zk-F$+wv!ioF?uqBYPQcxzinX&Fm3432X132Xxms4#Qt%5w1INz;~LrqT!h$}pvM-Z z$&^>9uzR;{DfXdrL9)D%@mhN907uE$2hY*-2i)3HW(RO-qA)xQ$sQD>MejY{_T)cA-jO-kJ( zQm3P`bV2GJd^%+ww;bA|OtB-f6qmY&lg5NX$Iy`?KILskI11(scY!KCk|RZTit)}kQ^EpHLi7p-~XqV{^Jh>KmY_l00ck)1V8`; zKmY_l00cnb5hlRz|6Be4;3J$(WDNvB00ck)1V8`;KmY_l00ck)1m4vIaR2{ZUHr&A z2!H?xfB*=900@8p2!H?xfWW(i0Kfk~$Ufp>-)DctzRmuOeVu)UeSzKt@LBc|_6hc} zcL@e$69hm21V8`;KmY_l00ck)1V8`;9zKDfEJ>0a7&OSb4p&914Z_{r?dANe}h?{|oy(`z`h%_Dk%i*&RBBKM()` z5C8!X009sH0T2KI5C8!X0D(OTu#oJD9$hi=di7>aFReF*`()42W7pNnrdEDYE!?Ra z)ku(RqS=aG-nv-ctTtGH$GKSBEND0Na#^oO{1j&-OPfaHCAFkQW!gXcqEXj2)}J?O zMWZ^kq}|gSjj&Jlj6|=hjmBlQVpNZKWzUHdOPWzt$>_3vN!_UF#;`<2qde_py|8YS zNde~n?^%#A009sH0T2KI5C8!X009sH0T2LzcRT^~|KIUNhzx)L2!H?xfB*=900@8p z2!H?xfWV#v`1Ai0Y{bKUz`n=+lAZ$i7W*IUztKAY{+Rs-_Pg{JfKRf2&Hfd=3*bZS zpR!+NzsP=;HQ5K)CNr4M3haICCVQD(VOjPpn`bFD#m=%Zc8nb24+KB}1V8`;KmY_l z00ck)1V8`;-tGkaK1u4I5X-Y-c}6V9`7(4`EKiALOf1LvvQH7qQL#KJmM8c!cw8)x ziRBYwd6X{$kBengEFTlg5x(>v5zE73c}OgW`BEMdOD2{Pu?+L2Z%`};#Ij#3LwxD& z6U(4j2E@`Y`$Ce;7e2o5`h4c||JL{a-tMIWeINh=AOHd&00JNY0w4eaAOHd&00IXj z!0-Q~|9?Puhz0^600JNY0w4eaAOHd&00JNY0`Cw4=>NY%3JNko00ck)1V8`;KmY_l z00ck)1VG?T5J3O`O~6A`5C8!X009sH0T2KI5C8!X009tqhY&#j{~c0LkO=}H00JNY z0w4eaAOHd&00JNY0&ju<`u}eN9-@K(2!H?xfB*=900@8p2!H?xfWSM1Ksfl<9wxo+ zVW%Ts3VkK;x$ynLpAQ`I-<1FH!1qGW_GJg2_I+M@T{`ah2c9o_p6SX|bLem|dTmN- zCiQAjdsW?RtmiiCS}j+q8Jin*b6YRwX4T}hR-DUs8yvZ`l3iHMDvQgPv(2FsPG7~_ zYPDqK)M7=i=E_E??W>TUoze2@Y`4L2hp$(ZSa)zw{FriWxz{YFOvFSE^kOXDoH!ng z<|unFi|qBdEEv^>R&C@OTN_%AvRRnP7qmGo-)(qo4{1C3*;!&&j9C1&pFS3hPEAQa zd!eD`%UZXB?){UNqSb|qOIddc1VGU#e%00DKSXXNk(}_g9?UjtwLPIyI zxwW!diYfJqS}s%4%4W5$m#SK^ZK+&(A$#cx6;X%%PduX};^c%pS4qSfnaNo%RF zr1S93;&NnTy`b9d?|aq&BGaiR}5Y!l*`<1MQ~eOUR+&VSX#Pe9n4;)R9dyC z7oIbP_L;D6zgCtTdWEu9R2v%KYLkgKUyKH$#)8zm+jS}mJsVoBqSx!hxSq4Od7VwJ z<@4#a=|WqOd7{`x^(Q)O@6Luhcs6VC&e1plJ2_r|;>~9d1*3Q9#H)8@rmI~MyF3^3 zGpTf|WxD=OvS$>CtM zG%qz(I|pW{oz?E5n42l23-i;OP}XC7$fX#1jJk@T<X$)RBMeI)+sZsJW( z1V5jgP89NM^X|Hb_ochX*&-_B&OoSw!+*jnoMXr2_657f z2q-Gi?Sp4o9HWMb zPVo*;bew~tux3)6(l*vBa+Z3#v>~+G> zb?;-*u`#K6#|gAK_gFAmpx@kEU4`FWhuZZ}olk3OG3mGv?lwKTQ+?<*?D5%MF}fo- zD{}m;ymH^|$Fz0j-z`p5hI*%@+l(3!-@LSa%EsAK_3SgsE<}EQB?`sVbUL4~Taccp zLp!$~J+}Kw#*wKW2Rv)$LDzO;`<209bYVey@RVJlM1ggen%!P>hlE|uR96>P9q&Ca z9>z~vN2xh3!CO6yp5HY)f|ZZG&efN^f#~FZfezn*0t;FK-d*H zQB^D2If@x?-jagR3m2qqRrs_zQx&~bQ+c&CfcyVlV;a~60T2KI5C8!X009sH0T2KI5CDNkkU*I76b|@3fnN{! z*-S(aFAUxp_`QKd|Mx@7p<{h&@RC0v=X{^_9rIq5!k(v{2%5`J1*7$pwCxiEb_Knv zRn%O?xTkBml2)y0b!&&l=fplAs8i?H(&==5Rt(g+L^I1n zZAxA?{6@ikTnpp+xM=#BC&qhCd>Ua09Ce5kH> zQb2Lj9K`D#ii|f$=Yr9jDXF>8TfOOSyiu#?#ERGI*_r9WY=-XNI?n;q=!}(HKFNiK z%3Uma-MG|U(;4T}N|f$Cjb`>z*s4ak{QPW2OJ`<~a2{jsRnHtQvubqDo8tVOvx0ad8s9#a6t@Rn)11E=W;Ix+ zsZ3Udun#D%)xi!wP$SrR2O!xs!K!B5r#jZET{};#GVNM3s_L5BsOM+`9v$9K>`r^( zCEm;=%-@8hla4r5EHGkwz?F#zIuMREy;g7JI-#UHC!LkmT`l6t^MPn~RPu<{-?}+O zx8n@4pR;ewCEJ^Quf1m~xa-71{GK6Q2^dDXeThM#{Aee=q!XWFw!LKNLb1b=Oz5gmFWVxg z(=#0+xsZmsw$>|xLZ}Al_vpvHTOZF&?lzT$>uoJc)U5(^2#crA1)|T7?OML(9bB`3 z#r9}(crqwv0#rnG76m9m>@DU}#=_)4g}iqm#G9vMG+%47W4@Ms+cekJVCAOQW-_zd zj5!Ixwu@M@t?Lw#A3JnMFJ?p8WzBQil`LHgTptTYwmjYcz`*gkUoKWp*iKEtkTnX~~%?~8ILfF{4l=xk$hHZSH`q7QK z-N?K47pxyNXCy1tJ#MQz^K3UaUB5D&b#>Z#k2>CbVU#AQ($(|_MCrQ-$>g z6}bWd5C8!X009sH0T2KI5C8!X00Acf^#7eSunhtr00JNY0w4eaAOHd&00JNY0*@vE z^#32taz(B{00ck)1V8`;KmY_l00ck)1VF$^0R4X_4Qzt|2!H?xfB*=900@8p2!H?x zfWV_kfWQC0pDlRUx7mMXf5<+=KE!^WZLxK>LI?2&0w4eaAOHd&00JNY0w4eaAOHd& zU=m>cvgc^j{FIIOR@#ve9T+nY*x&SFeYTnSstpU;2gJ9T4hLk;GBc_Tt=iz*gR<-y8R4Ip;9qMA`)Du9_r%9W4tZ&JjPKeXm>8C1 zPxORZtmswxC=7Y2ZSnrUX9$G}0w4eaAOHd&00JNY0w4eaAOHd&@F){-%>QSPaz2qa z5C8!X009sH0T2KI5C8!X009tqHxuCZ|7G@35BnDTGW&P5fjn^Lxf6=_Re9)s&k?xvW)B9vSuuyA>m^ zSLvWpeEd*U@_VKvX+8AQq;00JNY0w4eaAOHd&00JNY0w4ea9?yX9m}K?;gZ;jvlG*$R*uUD2sp%9<{Kgj-thy4xvKlB8^|73qc&j9=< z_Ior7;J4XFDFA;U00JNY0w4eaAOHd&00JNY0w4eaKNbRkLD@t7|3F|stV03%BldarISRlZ2!H?xfB*=900@8p2!H?xfB*=9z(XbA z_t7W-J^D|}0kQ1oOJ7JV`@}NHm)?L_`o&U~eKZEZ7ha#Q)&Kt#@BhES{(^m%W&nJZ zeS>|8eTrrQ`~mx&hnhho4gw$m0w4eaAOHd&00JNY0w4ea?|K5fHy;qa|B&eY_le$r zQ1t!-qW3S0-akzP;3EW9@1Jk`d}1U(^#8^8|H1_iGa|nmew{Y(2Ld1f0w4eaAOHd& z00JNY0w8cu0(m*$In|t!ZdCI%O}(S$%i4lcp>MdAHMLf)D|MryHP-cNNzqGC0ibOgHjZ}Y8ok6*K1l)DQ#92^}ec;$ElVVrL5o4lot(+4&7fj6mqN-)vZ=W zdvu&Fl#RM3k|>kzvr2x8r%d@TDS5SUhr2pkSJpQ9TkR=?FsvB_pe`ynK(ESI%XOt-)HaNo+Td;qHH~vvuNj-Ab&>iesUR3d zDxbGXDeVXRm0X*Diet>59XHp;|R6h~E|;!tgF6}>7T ztIDPG+4I-Vd;U=~A_Y9-+md&cuwK?S)LKI;E>xAxYEi2d4023LHx!lPsZ?fVjhu-@ zH;l4gAo`7lR@rFqLKG#huWc!sT3A;Mf?J~u>mtgUUZ$GV617;<>+29v0 zE*kX)vGe>BJpX^t(;D$W00ck)1V8`;KmY_l00ck)1VG@eC15@OAGDtT4_MFtd#&gH z1J?8Zvi1Bwz4%W&?;o(9|My$Z|I2v(|E(<-cmn|t009sH0T2KI5C8!X009sHfgc9} zF|YqkKK~yMe#{dIC?2-Pro!J2A0K@Gz*h(QLx0%!|N3qQKN0w3aDxKz2Ld1f0wD0N zBhWnZ-eB~4Tx!nKTsH01;%%BnX5{J`&A&6MIqlUAnhcmL(!9Eynl8*v&(5YgP0n9h z$u6vBmBr=D*;kZUXP|5Rm~w5o+c2g~#2jhG;?3Eg2u9;^Y5Q!0&!_Bk+qrknQn0#k zaVhKOxRseZ+Ez@tr`8HI|8HU@5m%P4tt!hmmX?&4Ru-==tlUzrWN)2QVl8{hwHl*Y zg0D8(mK5rgNs}KNT0^gBZG-qz&jzB`;*v+utlur`oZM!^5c|1K<+)_%{uI@|K3)Tx zqt67RH&aq`K}0L)Rjs1tD#krs%aychO{<%$oLB78=(PU@YxLEYMF-ynA zloydmn-Zn+8GU*$g{E9mSe%M(ZhQ+qWabma$Di zonK3*)A`xlW>vrIqW6@g_r~(#%QxDU-o?-=EXsY0S+2wP{xU(;tra~M-+t!FU^JDI z9*l`o!X;6cLlc%@>*R2;;N?ms$|tpIC7NRDc2Xz{H&(ALE|agT+2vJI)gl*D@_LD4 z+Obx$&t+G#%a^j(m6$U%E19@*nH#K#b9HffadmNFY3Y`AFnigV;r+N4boS>u-n=p& zjE;{>+qZi7Ifb6xnb?6)HS|VVv)i5NblPrsD7!_iUa09}R&#G#Q{3c}e2r>v>Cg!{ zx8n(@78*vao)atHM9j`i7iKesv@@G#X#I?pTdVnYvFLT$6!QgbTANdQn+P*XzomDVCSrfS z_m>H(LN@a4XVN=15oV$;fKTsS;jKz?pnBgT_akT`+8N$Y6CsTE*+gVgf#{X-oqoJ6 zc4mv5bneBP(P`1PH19gw7GdeM8*@eNY{Y)JX1J3X>tEb_|G)MAzpv68 z|GrCa0sIoZ1@Ig6{=eU0pL*9_@FK$?00JNY0w4eaAOHd&00JNY0wD0VBEawd2gTk0 zfO!9(SG@l(Am0Bci}(L|#ryy0F#xjki}(M@^bA1n`~N&cZ>x+!3