-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implement task #14
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,221 @@ | ||
from fastapi import APIRouter, Depends, HTTPException, Query | ||
from sqlalchemy.exc import IntegrityError | ||
from sqlalchemy.orm import Session, joinedload | ||
import datetime | ||
from typing import Annotated | ||
|
||
from fastapi import ( | ||
APIRouter, | ||
Depends, | ||
HTTPException, | ||
Query, | ||
status | ||
) | ||
from sqlalchemy.orm import Session | ||
|
||
from database import get_db | ||
from database.models import MovieModel, CountryModel, GenreModel, ActorModel, LanguageModel | ||
from database.models import ( | ||
MovieModel, | ||
CountryModel, | ||
GenreModel, | ||
ActorModel, | ||
LanguageModel | ||
) | ||
from schemas.movies import ( | ||
MovieDetailResponseSchema, | ||
MovieListResponseSchema, | ||
MovieUpdateResponseSchema, | ||
MovieCreateResponseSchema, | ||
MovieListReadResponseSchema | ||
) | ||
|
||
|
||
router = APIRouter() | ||
|
||
|
||
# Write your code here | ||
@router.get("/movies/", response_model=MovieListResponseSchema) | ||
def get_movies( | ||
page: Annotated[int, Query(ge=1, description="The page number to fetch.")] = 1, | ||
per_page: Annotated[int, Query(ge=1, le=20, description="Number of movies per page.")] = 10, | ||
db: Session = Depends(get_db) | ||
): | ||
total_items = db.query(MovieModel).count() | ||
total_pages = (total_items + per_page - 1) // per_page | ||
|
||
if page > total_pages or total_items == 0: | ||
raise HTTPException(status_code=404, detail="No movies found.") | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider providing more specific feedback in the HTTPException message, such as indicating whether the issue is with the page number or if there are no movies at all. |
||
offset = (page - 1) * per_page | ||
movies_query = db.query(MovieModel).order_by(MovieModel.id.desc()).offset(offset).limit(per_page).all() | ||
movies = [MovieListReadResponseSchema.model_validate(movie) for movie in movies_query] | ||
|
||
prev_page = f"/theater/movies/?page={page - 1}&per_page={per_page}" if page > 1 else None | ||
next_page = f"/theater/movies/?page={page + 1}&per_page={per_page}" if page < total_pages else None | ||
|
||
return MovieListResponseSchema( | ||
movies=movies, | ||
prev_page=prev_page, | ||
next_page=next_page, | ||
total_pages=total_pages, | ||
total_items=total_items, | ||
) | ||
|
||
|
||
@router.get("/movies/{movie_id}", response_model=MovieDetailResponseSchema) | ||
def get_movie_by_id(movie_id: int, db: Session = Depends(get_db)): | ||
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first() | ||
if not movie: | ||
raise HTTPException(status_code=404, detail="Movie with the given ID was not found.") | ||
return movie | ||
|
||
|
||
@router.post("/movies/", response_model=MovieDetailResponseSchema, status_code=status.HTTP_201_CREATED) | ||
def create_movie(movie: MovieCreateResponseSchema, db: Session = Depends(get_db)): | ||
if len(movie.name) > 255: | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is quite generic. Consider specifying which field is invalid to provide better feedback to the client. |
||
|
||
if movie.date > datetime.datetime.now().date() + datetime.timedelta(days=365): | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'date' field is invalid to provide better feedback to the client. |
||
|
||
if not (0 <= movie.score <= 100): | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'score' field is invalid to provide better feedback to the client. |
||
|
||
if movie.budget < 0 or movie.revenue < 0: | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'budget' or 'revenue' field is invalid to provide better feedback to the client. |
||
|
||
existing_movie = db.query(MovieModel).filter_by(name=movie.name, date=movie.date).first() | ||
if existing_movie: | ||
raise HTTPException( | ||
status_code=409, | ||
detail=f"A movie with the name '{movie.name}' and release date '{movie.date}' already exists." | ||
) | ||
|
||
country = db.query(CountryModel).filter_by(code=movie.country).first() | ||
if not country: | ||
country = CountryModel(code=movie.country) | ||
db.add(country) | ||
db.commit() | ||
db.refresh(country) | ||
|
||
genres = [] | ||
for genre in movie.genres: | ||
genre_obj = db.query(GenreModel).filter_by(name=genre).first() | ||
if not genre_obj: | ||
genre_obj = GenreModel(name=genre) | ||
db.add(genre_obj) | ||
db.commit() | ||
db.refresh(genre_obj) | ||
genres.append(genre_obj) | ||
|
||
actors = [] | ||
for actor in movie.actors: | ||
actor_obj = db.query(ActorModel).filter_by(name=actor).first() | ||
if not actor_obj: | ||
actor_obj = ActorModel(name=actor) | ||
db.add(actor_obj) | ||
db.commit() | ||
db.refresh(actor_obj) | ||
actors.append(actor_obj) | ||
|
||
languages = [] | ||
for language in movie.languages: | ||
language_obj = db.query(LanguageModel).filter_by(name=language).first() | ||
if not language_obj: | ||
language_obj = LanguageModel(name=language) | ||
db.add(language_obj) | ||
db.commit() | ||
db.refresh(language_obj) | ||
languages.append(language_obj) | ||
|
||
db_movie = MovieModel( | ||
name=movie.name, | ||
date=movie.date, | ||
score=movie.score, | ||
overview=movie.overview, | ||
status=movie.status, | ||
budget=movie.budget, | ||
revenue=movie.revenue, | ||
country=country, | ||
genres=genres, | ||
actors=actors, | ||
languages=languages, | ||
) | ||
|
||
db.add(db_movie) | ||
db.commit() | ||
db.refresh(db_movie) | ||
return db_movie | ||
|
||
|
||
@router.delete("/movies/{movie_id}", status_code=status.HTTP_204_NO_CONTENT) | ||
def remove_movie(movie_id: int, db: Session = Depends(get_db)): | ||
db_movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first() | ||
if not db_movie: | ||
raise HTTPException(status_code=404, detail="Movie with the given ID was not found.") | ||
db.delete(db_movie) | ||
db.commit() | ||
|
||
|
||
@router.patch("/movies/{movie_id}") | ||
def edit_movie(movie_id: int, movie: MovieUpdateResponseSchema, db: Session = Depends(get_db)): | ||
db_movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first() | ||
if not db_movie: | ||
raise HTTPException(status_code=404, detail="Movie with the given ID was not found.") | ||
|
||
if movie.name: | ||
if len(movie.name) > 255: | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'name' field is invalid to provide better feedback to the client. |
||
db_movie.name = movie.name | ||
|
||
if movie.date: | ||
if movie.date > datetime.datetime.now().date() + datetime.timedelta(days=365): | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'date' field is invalid to provide better feedback to the client. |
||
db_movie.date = movie.date | ||
|
||
if movie.score: | ||
if not (0 <= movie.score <= 100): | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'score' field is invalid to provide better feedback to the client. |
||
db_movie.score = movie.score | ||
|
||
if movie.overview: | ||
db_movie.overview = movie.overview | ||
if movie.status: | ||
db_movie.status = movie.status | ||
|
||
if movie.budget: | ||
if movie.budget < 0: | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'budget' field is invalid to provide better feedback to the client. |
||
db_movie.budget = movie.budget | ||
|
||
if movie.revenue: | ||
if movie.revenue < 0: | ||
raise HTTPException( | ||
status_code=400, | ||
detail="Invalid input data." | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message 'Invalid input data.' is generic. Consider specifying that the 'revenue' field is invalid to provide better feedback to the client. |
||
db_movie.revenue = movie.revenue | ||
|
||
db.commit() | ||
db.refresh(db_movie) | ||
return {"detail": "Movie updated successfully.", "movie": db_movie} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,97 @@ | ||
# Write your code here | ||
import datetime | ||
|
||
from pydantic import BaseModel | ||
|
||
from database.models import MovieStatusEnum | ||
|
||
|
||
class CountryResponseSchema(BaseModel): | ||
id: int | ||
code: str | ||
name: str | None | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider providing a default value for the 'name' field in the |
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class GenreResponseSchema(BaseModel): | ||
id: int | ||
name: str | ||
|
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class ActorResponseSchema(BaseModel): | ||
id: int | ||
name: str | ||
|
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class LanguageResponseSchema(BaseModel): | ||
id: int | ||
name: str | ||
|
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class MovieListReadResponseSchema(BaseModel): | ||
id: int | ||
name: str | ||
date: datetime.date | ||
score: float | ||
overview: str | ||
|
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class MovieListResponseSchema(BaseModel): | ||
movies: list[MovieListReadResponseSchema] | ||
prev_page: str | None | ||
next_page: str | None | ||
total_pages: int | ||
total_items: int | ||
|
||
|
||
class MovieDetailResponseSchema(BaseModel): | ||
id: int | ||
name: str | ||
date: datetime.date | ||
score: float | ||
overview: str | ||
status: MovieStatusEnum | ||
budget: float | ||
revenue: float | ||
country: CountryResponseSchema | ||
genres: list[GenreResponseSchema] | ||
actors: list[ActorResponseSchema] | ||
languages: list[LanguageResponseSchema] | ||
|
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class MovieCreateResponseSchema(BaseModel): | ||
name: str | ||
date: datetime.date | ||
score: float | ||
overview: str | ||
status: MovieStatusEnum | ||
budget: float | ||
revenue: float | ||
country: str | ||
genres: list[str] | ||
actors: list[str] | ||
languages: list[str] | ||
|
||
model_config = {"from_attributes": True} | ||
|
||
|
||
class MovieUpdateResponseSchema(BaseModel): | ||
name: str | None = None | ||
date: datetime.date | None = None | ||
score: float | None = None | ||
overview: str | None = None | ||
status: MovieStatusEnum | None = None | ||
budget: float | None = None | ||
revenue: float | None = None | ||
|
||
Comment on lines
+89
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that the optional fields in |
||
model_config = {"from_attributes": True} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the SQLAlchemyError, consider logging unexpected errors to a file or a logging system for better traceability.