From 83982877c3a6c7cde75644b30200f6c562549411 Mon Sep 17 00:00:00 2001 From: "Edward Tirado Jr." Date: Wed, 2 Jul 2025 16:09:38 -0500 Subject: [PATCH 1/2] .idea updates --- .idea/dictionaries/project.xml | 8 ++++++++ .idea/misc.xml | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .idea/dictionaries/project.xml diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..779eda8 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,8 @@ + + + + mpaa + viewset + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 9a8f74c..f6f7e02 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,9 @@ \ No newline at end of file -- 2.39.5 From 2e7dbe4ddc4ce70ea4ac3251d0100b90e916d914 Mon Sep 17 00:00:00 2001 From: "Edward Tirado Jr." Date: Wed, 2 Jul 2025 16:58:44 -0500 Subject: [PATCH 2/2] reorganized viewsets into their own files --- movie_manager/permissions.py | 6 + movie_manager/views.py | 235 --------------------------- movie_manager/viewsets/__init__.py | 11 ++ movie_manager/viewsets/movie.py | 37 +++++ movie_manager/viewsets/movie_list.py | 120 ++++++++++++++ movie_manager/viewsets/schedule.py | 58 +++++++ movie_manager/viewsets/showing.py | 36 ++++ movienight/urls.py | 24 ++- requirements.txt | 2 +- users/views.py | 35 +--- users/viewsets/__init__.py | 4 + users/viewsets/group.py | 13 ++ users/viewsets/user.py | 26 +++ 13 files changed, 330 insertions(+), 277 deletions(-) create mode 100644 movie_manager/permissions.py create mode 100644 movie_manager/viewsets/__init__.py create mode 100644 movie_manager/viewsets/movie.py create mode 100644 movie_manager/viewsets/movie_list.py create mode 100644 movie_manager/viewsets/schedule.py create mode 100644 movie_manager/viewsets/showing.py create mode 100644 users/viewsets/__init__.py create mode 100644 users/viewsets/group.py create mode 100644 users/viewsets/user.py diff --git a/movie_manager/permissions.py b/movie_manager/permissions.py new file mode 100644 index 0000000..3dae833 --- /dev/null +++ b/movie_manager/permissions.py @@ -0,0 +1,6 @@ +from rest_framework import permissions +from rest_framework.permissions import SAFE_METHODS + +class ReadOnly(permissions.BasePermission): + def has_permission(self, request, view): + return request.method in SAFE_METHODS diff --git a/movie_manager/views.py b/movie_manager/views.py index 2d606ea..e69de29 100644 --- a/movie_manager/views.py +++ b/movie_manager/views.py @@ -1,235 +0,0 @@ -import datetime - -from django.db.models import QuerySet -from django.http import JsonResponse -from django.contrib.auth.models import User -from django.utils.dateparse import parse_datetime -from django.db import models -from rest_framework import permissions, viewsets -from knox.auth import TokenAuthentication -from rest_framework.decorators import action, api_view -from rest_framework.exceptions import NotFound -from rest_framework.permissions import AllowAny, SAFE_METHODS - -from movie_db.db_providers.omdb import OMDb -from movie_manager.models import Movie, MovieList, Schedule, Showing -from movie_manager.serializers import ( - MovieListSerializer, - MovieSerializer, - ScheduleSerializer, - ShowingSerializer, MovieListListSerializer, -) - - -class ReadOnly(permissions.BasePermission): - def has_permission(self, request, view): - return request.method in SAFE_METHODS - - -# Create your views here. -class MovieViewset(viewsets.ModelViewSet): - queryset = Movie.objects.all().order_by("title") - authentication_classes = [TokenAuthentication] - permission_classes = [permissions.IsAuthenticated] - - serializer_class = MovieSerializer - - def update(self, request, pk=None, *args, **kwargs): - omdb = OMDb() - updated_movie = omdb.search(request.data.get("imdb_id"), {"type": "imdb_id"}) - - movie = Movie.objects.get(pk=pk) - - movie.title = updated_movie["title"] - movie.actors = updated_movie["actors"] - movie.year = updated_movie["year"] - movie.critic_scores = updated_movie["critic_scores"] - movie.mpaa_rating = updated_movie["mpaa_rating"] - movie.director = updated_movie["director"] - movie.poster = updated_movie["poster"] - movie.plot = updated_movie["plot"] - movie.genre = updated_movie["genre"] - - movie.save() - - return JsonResponse(MovieSerializer(movie).data) - - -class MovieListViewset(viewsets.ModelViewSet): - queryset = MovieList.objects.all() - authentication_classes = [TokenAuthentication] - permission_classes = [permissions.IsAuthenticated | ReadOnly] - - def get_serializer_class(self): - if self.action == "list": - return MovieListListSerializer - else: - return MovieListSerializer - - def get_queryset(self): - base_qs = MovieList.objects.all() - - if self.action == "list": - if self.request.user.is_authenticated: - return base_qs.filter( - models.Q(public=True) | - models.Q(owner=self.request.user) - ).order_by("name") - - return base_qs.filter(public=True).order_by("name") - else: - return MovieList.objects.prefetch_related( - "movies", - "movies__showing_set" - ).order_by("name") - - def perform_create(self, serializer): - serializer.save(owner=self.request.user) - - def get_permissions(self): - if self.action in ['update', 'partial_update', 'destroy']: - self.permission_classes = [permissions.IsAuthenticated] - return super().get_permissions() - - def create(self, request, *args, **kwargs): - movie_list = MovieList.objects.create( - name=request.data.get("name"), - owner=request.user, - ) - - return JsonResponse(MovieListSerializer(movie_list).data) - - def update(self, request, pk=None, *args, **kwargs): - movie_list = MovieList.objects.get(pk=pk) - movie_list.name = request.data.get("name") - movie_list.owner = User.objects.get(pk=request.data.get("owner")) - - if request.data.get("movies"): - movie_ids = request.data.get("movies") - for movie_id in movie_ids: - try: - movie = Movie.objects.get(pk=movie_id) - movie_list.movies.add(movie) - except Movie.DoesNotExist: - raise NotFound(f"Movie {movie_id} does not exist") - - removed_movies = Movie.objects.exclude(id__in=movie_ids) - for removed_movie in removed_movies: - removed_movie.delete() - - movie_list.save() - - return JsonResponse(MovieListSerializer(movie_list).data) - - @action( - detail=True, methods=["put", "delete"], url_path="movie/(?Ptt[0-9]+)" - ) - def add_movie(self, request, pk=None, imdb_id=None, *args, **kwargs): - if request.method == "DELETE": - return self.remove_movie(request, pk, imdb_id) - - movie_list = MovieList.objects.get(pk=pk) - try: - new_movie = Movie.objects.get(imdb_id=imdb_id) - except Movie.DoesNotExist: - omdb = OMDb() - movie = omdb.search(imdb_id, {"type": "imdb_id"}) - - new_movie = Movie.objects.create( - title=movie["title"], - actors=movie["actors"], - year=movie["year"], - imdb_id=movie["imdb_id"], - poster=movie["poster"], - plot=movie["plot"], - genre=movie["genre"], - critic_scores=movie["critic_scores"], - mpaa_rating=movie["mpaa_rating"], - director=movie["director"], - added_by_id=request.user.id, - ) - - movie_list.movies.add(new_movie) - - return JsonResponse(MovieListSerializer(movie_list).data) - - def remove_movie(self, request, pk=None, imdb_id=None, *args, **kwargs): - movie = Movie.objects.filter(imdb_id=imdb_id).first() - - movie_list = MovieList.objects.get(pk=pk) - movie_list.movies.remove(movie) - - return JsonResponse(MovieListSerializer(movie_list).data) - - -class ScheduleViewset(viewsets.ModelViewSet): - queryset = Schedule.objects.all().order_by("name") - authentication_classes = [TokenAuthentication] - permission_classes = [permissions.IsAuthenticated | ReadOnly] - - serializer_class = ScheduleSerializer - - def retrieve(self, request, pk=None, *args, **kwargs): - # Get the schedule instance - instance = self.get_object() - now = datetime.datetime.now() - # get time from start of day - today = datetime.datetime(now.year, now.month, now.day) - - upcoming_showings = Showing.objects.filter( - showtime__gte=today, schedule=instance - ) - - # Create a serialized response - serializer = self.get_serializer(instance) - data = serializer.data - - # Replace all showings with only future showings - data["showings"] = ShowingSerializer(upcoming_showings, many=True).data - - if request.GET.get("past_showings") == "true": - past_showings = Showing.objects.filter( - showtime__lt=today, schedule=instance - ) - - # Add both to the response - data["past_showings"] = [ - { - "id": showing.id, - "showtime": showing.showtime.isoformat(), - "movie": MovieSerializer(showing.movie).data, - } - for showing in past_showings - ] - else: - data["past_showings"] = [] - - return JsonResponse(data) - - -class ShowingViewset(viewsets.ModelViewSet): - queryset = Showing.objects.all().order_by("showtime") - authentication_classes = [TokenAuthentication] - permission_classes = [permissions.IsAuthenticated | ReadOnly] - - serializer_class = ShowingSerializer - - def create(self, request, *args, **kwargs): - movie_id = request.data.get("movie") - movie = Movie.objects.get(pk=movie_id) - - schedule_id = request.data.get("schedule") - schedule = Schedule.objects.get(pk=schedule_id) - - showtime_str = request.data.get("showtime") - showtime = parse_datetime(showtime_str) - - showing = Showing.objects.create( - movie=movie, - schedule=schedule, - showtime=showtime, - public=request.data.get("public"), - owner=request.user, - ) - - return JsonResponse(ShowingSerializer(showing).data) diff --git a/movie_manager/viewsets/__init__.py b/movie_manager/viewsets/__init__.py new file mode 100644 index 0000000..da282d0 --- /dev/null +++ b/movie_manager/viewsets/__init__.py @@ -0,0 +1,11 @@ +from .movie import MovieViewset +from .movie_list import MovieListViewset +from .schedule import ScheduleViewset +from .showing import ShowingViewset + +__all__ = [ + "MovieViewset", + "MovieListViewset", + "ScheduleViewset", + "ShowingViewset", +] diff --git a/movie_manager/viewsets/movie.py b/movie_manager/viewsets/movie.py new file mode 100644 index 0000000..c7b6977 --- /dev/null +++ b/movie_manager/viewsets/movie.py @@ -0,0 +1,37 @@ +from django.http import JsonResponse +from rest_framework import permissions, viewsets + +from movie_db.db_providers.omdb import OMDb +from movie_manager.models import Movie + +from knox.auth import TokenAuthentication + +from movie_manager.serializers import MovieSerializer + + +class MovieViewset(viewsets.ModelViewSet): + queryset = Movie.objects.all().order_by("title") + authentication_classes = [TokenAuthentication] + permission_classes = [permissions.IsAuthenticated] + + serializer_class = MovieSerializer + + def update(self, request, pk=None, *args, **kwargs): + omdb = OMDb() + updated_movie = omdb.search(request.data.get("imdb_id"), {"type": "imdb_id"}) + + movie = Movie.objects.get(pk=pk) + + movie.title = updated_movie["title"] + movie.actors = updated_movie["actors"] + movie.year = updated_movie["year"] + movie.critic_scores = updated_movie["critic_scores"] + movie.mpaa_rating = updated_movie["mpaa_rating"] + movie.director = updated_movie["director"] + movie.poster = updated_movie["poster"] + movie.plot = updated_movie["plot"] + movie.genre = updated_movie["genre"] + + movie.save() + + return JsonResponse(MovieSerializer(movie).data) diff --git a/movie_manager/viewsets/movie_list.py b/movie_manager/viewsets/movie_list.py new file mode 100644 index 0000000..75730b6 --- /dev/null +++ b/movie_manager/viewsets/movie_list.py @@ -0,0 +1,120 @@ +from django.http import JsonResponse +from django.db import models +from django.contrib.auth.models import User +from rest_framework import permissions, viewsets +from rest_framework.decorators import action +from rest_framework.exceptions import NotFound + +from movie_db.db_providers.omdb import OMDb +from movie_manager.models import MovieList, Movie + +from knox.auth import TokenAuthentication + +from movie_manager.permissions import ReadOnly +from movie_manager.serializers import MovieListSerializer, MovieListListSerializer + + +class MovieListViewset(viewsets.ModelViewSet): + queryset = MovieList.objects.all() + authentication_classes = [TokenAuthentication] + permission_classes = [permissions.IsAuthenticated | ReadOnly] + + def get_serializer_class(self): + if self.action == "list": + return MovieListListSerializer + else: + return MovieListSerializer + + def get_queryset(self): + base_qs = MovieList.objects.all() + + if self.action == "list": + if self.request.user.is_authenticated: + return base_qs.filter( + models.Q(public=True) | models.Q(owner=self.request.user) + ).order_by("name") + + return base_qs.filter(public=True).order_by("name") + else: + return MovieList.objects.prefetch_related( + "movies", "movies__showing_set" + ).order_by("name") + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + def get_permissions(self): + if self.action in ["update", "partial_update", "destroy"]: + self.permission_classes = [permissions.IsAuthenticated] + return super().get_permissions() + + def create(self, request, *args, **kwargs): + movie_list = MovieList.objects.create( + name=request.data.get("name"), + owner=request.user, + ) + + return JsonResponse(MovieListSerializer(movie_list).data) + + def update(self, request, pk=None, *args, **kwargs): + movie_list = MovieList.objects.get(pk=pk) + movie_list.name = request.data.get("name") + movie_list.owner = User.objects.get(pk=request.data.get("owner")) + + if request.data.get("movies"): + movie_ids = request.data.get("movies") + for movie_id in movie_ids: + try: + movie = Movie.objects.get(pk=movie_id) + movie_list.movies.add(movie) + except Movie.DoesNotExist: + raise NotFound(f"Movie {movie_id} does not exist") + + removed_movies = Movie.objects.exclude(id__in=movie_ids) + for removed_movie in removed_movies: + removed_movie.delete() + + movie_list.save() + + return JsonResponse(MovieListSerializer(movie_list).data) + + @action( + detail=True, methods=["put", "delete"], url_path="movie/(?Ptt[0-9]+)" + ) + def add_movie(self, request, pk=None, imdb_id=None, *args, **kwargs): + if request.method == "DELETE": + return self.remove_movie(request, pk, imdb_id) + + movie_list = MovieList.objects.get(pk=pk) + try: + new_movie = Movie.objects.get(imdb_id=imdb_id) + except Movie.DoesNotExist: + omdb = OMDb() + movie = omdb.search(imdb_id, {"type": "imdb_id"}) + + new_movie = Movie.objects.create( + title=movie["title"], + actors=movie["actors"], + year=movie["year"], + imdb_id=movie["imdb_id"], + poster=movie["poster"], + plot=movie["plot"], + genre=movie["genre"], + critic_scores=movie["critic_scores"], + mpaa_rating=movie["mpaa_rating"], + director=movie["director"], + added_by_id=request.user.id, + ) + + movie_list.movies.add(new_movie) + + return JsonResponse(MovieListSerializer(movie_list).data) + + @staticmethod + def remove_movie(request, pk=None, imdb_id=None, *args, **kwargs): + movie = Movie.objects.filter(imdb_id=imdb_id).first() + + movie_list = MovieList.objects.get(pk=pk) + movie_list.movies.remove(movie) + + return JsonResponse(MovieListSerializer(movie_list).data) diff --git a/movie_manager/viewsets/schedule.py b/movie_manager/viewsets/schedule.py new file mode 100644 index 0000000..856605a --- /dev/null +++ b/movie_manager/viewsets/schedule.py @@ -0,0 +1,58 @@ +import datetime + +from django.http import JsonResponse +from knox.auth import TokenAuthentication +from rest_framework import viewsets, permissions + +from movie_manager.models import Schedule, Showing +from movie_manager.permissions import ReadOnly +from movie_manager.serializers import ( + ScheduleSerializer, + ShowingSerializer, + MovieSerializer, +) + + +class ScheduleViewset(viewsets.ModelViewSet): + queryset = Schedule.objects.all().order_by("name") + authentication_classes = [TokenAuthentication] + permission_classes = [permissions.IsAuthenticated | ReadOnly] + + serializer_class = ScheduleSerializer + + def retrieve(self, request, pk=None, *args, **kwargs): + # Get the schedule instance + instance = self.get_object() + now = datetime.datetime.now() + # get time from start of day + today = datetime.datetime(now.year, now.month, now.day) + + upcoming_showings = Showing.objects.filter( + showtime__gte=today, schedule=instance + ) + + # Create a serialized response + serializer = self.get_serializer(instance) + data = serializer.data + + # Replace all showings with only future showings + data["showings"] = ShowingSerializer(upcoming_showings, many=True).data + + if request.GET.get("past_showings") == "true": + past_showings = Showing.objects.filter( + showtime__lt=today, schedule=instance + ) + + # Add both to the response + data["past_showings"] = [ + { + "id": showing.id, + "showtime": showing.showtime.isoformat(), + "movie": MovieSerializer(showing.movie).data, + } + for showing in past_showings + ] + else: + data["past_showings"] = [] + + return JsonResponse(data) diff --git a/movie_manager/viewsets/showing.py b/movie_manager/viewsets/showing.py new file mode 100644 index 0000000..1f3c887 --- /dev/null +++ b/movie_manager/viewsets/showing.py @@ -0,0 +1,36 @@ +from django.http import JsonResponse +from django.utils.dateparse import parse_datetime +from knox.auth import TokenAuthentication +from rest_framework import viewsets, permissions + +from movie_manager.models import Showing, Movie, Schedule +from movie_manager.permissions import ReadOnly +from movie_manager.serializers import ShowingSerializer + + +class ShowingViewset(viewsets.ModelViewSet): + queryset = Showing.objects.all().order_by("showtime") + authentication_classes = [TokenAuthentication] + permission_classes = [permissions.IsAuthenticated | ReadOnly] + + serializer_class = ShowingSerializer + + def create(self, request, *args, **kwargs): + movie_id = request.data.get("movie") + movie = Movie.objects.get(pk=movie_id) + + schedule_id = request.data.get("schedule") + schedule = Schedule.objects.get(pk=schedule_id) + + showtime_str = request.data.get("showtime") + showtime = parse_datetime(showtime_str) + + showing = Showing.objects.create( + movie=movie, + schedule=schedule, + showtime=showtime, + public=request.data.get("public"), + owner=request.user, + ) + + return JsonResponse(ShowingSerializer(showing).data) diff --git a/movienight/urls.py b/movienight/urls.py index 22a5c6a..c07c5b5 100644 --- a/movienight/urls.py +++ b/movienight/urls.py @@ -10,25 +10,33 @@ from django.conf.urls.static import static from django.conf import settings from rest_framework.routers import DefaultRouter +from movie_manager.viewsets import ( + MovieViewset, + MovieListViewset, + ScheduleViewset, + ShowingViewset, +) from users import views as user_views -from movie_manager import views as movie_views from movie_db import views as movie_db_views from rest_framework.authtoken.views import obtain_auth_token +from users.viewsets.user import register +from users.viewsets import UserViewSet, GroupViewSet + router = DefaultRouter() -router.register(r"v1/users", user_views.UserViewSet) -router.register(r"v1/groups", user_views.GroupViewSet) -router.register(r"v1/movies", movie_views.MovieViewset) -router.register(r"v1/lists", movie_views.MovieListViewset) -router.register(r"v1/schedules", movie_views.ScheduleViewset) -router.register(r"v1/showings", movie_views.ShowingViewset) +router.register(r"v1/users", UserViewSet) +router.register(r"v1/groups", GroupViewSet) +router.register(r"v1/movies", MovieViewset) +router.register(r"v1/lists", MovieListViewset) +router.register(r"v1/schedules", ScheduleViewset) +router.register(r"v1/showings", ShowingViewset) urlpatterns = [ path("", include(router.urls)), path("admin/", admin.site.urls), path(r"v1/auth/token/", obtain_auth_token), path(r"v1/auth/login/", user_views.LoginView.as_view(), name="knox_login"), - path(r"v1/auth/register/", user_views.register, name="register"), + path(r"v1/auth/register/", register, name="register"), path(r"v1/movies/search", movie_db_views.omdb_search, name="omdb_search"), path(r"v1/auth/", include("knox.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/requirements.txt b/requirements.txt index 456b8f8..7350802 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django==5.1.4 +django==5.2.2 djangorestframework django-rest-knox markdown diff --git a/users/views.py b/users/views.py index bcc7f13..f231eee 100644 --- a/users/views.py +++ b/users/views.py @@ -1,40 +1,9 @@ from django.contrib.auth import login -from django.contrib.auth.models import Group, User, AnonymousUser -from rest_framework import permissions, viewsets, status -from knox.auth import TokenAuthentication +from rest_framework import permissions from knox.views import LoginView as KnoxLoginView from rest_framework.authtoken.serializers import AuthTokenSerializer -from rest_framework.response import Response -from rest_framework.decorators import api_view -from users.serializers import GroupSerializer, UserSerializer - - -class UserViewSet(viewsets.ModelViewSet): - authentication_classes = [TokenAuthentication] - permission_classes = [permissions.IsAuthenticated] - - queryset = User.objects.all().order_by("-date_joined") - serializer_class = UserSerializer - - -class GroupViewSet(viewsets.ModelViewSet): - authentication_classes = [TokenAuthentication] - permission_classes = [permissions.IsAuthenticated] - - queryset = Group.objects.all().order_by("name") - serializer_class = GroupSerializer - - -@api_view(["POST"]) -def register(request): - user_data = UserSerializer(data=request.data) - - if user_data.is_valid(): - User.objects.create_user(**user_data.validated_data) - return Response(request.data, status=status.HTTP_201_CREATED) - else: - return Response([], status=status.HTTP_400_BAD_REQUEST) +from users.serializers import UserSerializer class LoginView(KnoxLoginView): diff --git a/users/viewsets/__init__.py b/users/viewsets/__init__.py new file mode 100644 index 0000000..a3d0f22 --- /dev/null +++ b/users/viewsets/__init__.py @@ -0,0 +1,4 @@ +from .group import GroupViewSet +from .user import UserViewSet + +__all__ = ["GroupViewSet", "UserViewSet"] diff --git a/users/viewsets/group.py b/users/viewsets/group.py new file mode 100644 index 0000000..7bbc91d --- /dev/null +++ b/users/viewsets/group.py @@ -0,0 +1,13 @@ +from knox.auth import TokenAuthentication +from django.contrib.auth.models import Group +from rest_framework import viewsets, permissions, status + +from users.serializers import GroupSerializer + + +class GroupViewSet(viewsets.ModelViewSet): + authentication_classes = [TokenAuthentication] + permission_classes = [permissions.IsAuthenticated] + + queryset = Group.objects.all().order_by("name") + serializer_class = GroupSerializer diff --git a/users/viewsets/user.py b/users/viewsets/user.py new file mode 100644 index 0000000..c2e2063 --- /dev/null +++ b/users/viewsets/user.py @@ -0,0 +1,26 @@ +from django.contrib.auth.models import User, Group +from knox.auth import TokenAuthentication +from rest_framework import viewsets, permissions, status +from rest_framework.decorators import api_view +from rest_framework.response import Response + +from users.serializers import UserSerializer, GroupSerializer + + +class UserViewSet(viewsets.ModelViewSet): + authentication_classes = [TokenAuthentication] + permission_classes = [permissions.IsAuthenticated] + + queryset = User.objects.all().order_by("-date_joined") + serializer_class = UserSerializer + + +@api_view(["POST"]) +def register(request): + user_data = UserSerializer(data=request.data) + + if user_data.is_valid(): + User.objects.create_user(**user_data.validated_data) + return Response(request.data, status=status.HTTP_201_CREATED) + else: + return Response([], status=status.HTTP_400_BAD_REQUEST) -- 2.39.5