Skip to content
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

Develop #29

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Empty file added src/crud/__init__.py
Empty file.
77 changes: 77 additions & 0 deletions src/crud/movies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from database import get_db
from fastapi import Depends, Query, HTTPException
from models import MovieModel
from schemas import MovieCreateSchema, MovieUpdateSchema
from sqlalchemy.orm import Session


def create_movie(db: Session, movie: MovieCreateSchema):
db_movie = MovieModel(**movie.dict())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The create_movie function should include a check for duplicate movies based on name and date. If a duplicate is found, it should return a 409 Conflict error with the message: "A movie with the name '{name}' and release date '{date}' already exists." .

db_movie = (
db.query(MovieModel)
.filter(MovieModel.name == movie.name, MovieModel.date == movie.date)
.first()
)
if db_movie:
raise HTTPException(
status_code=409,
detail=f"A movie with the name '{movie.name}' and release date '{movie.date}' already exists."
)
if db_movie is None:
raise HTTPException(status_code=400)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check if db_movie is None: is unnecessary here because the previous check already handles the case where a duplicate movie exists. You should remove this block to avoid raising a 400 Bad Request error incorrectly.

db.add(db_movie)
db.commit()
db.refresh(db_movie)
return db_movie


def get_movie(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


def get_movies(
page: int = Query(default=1, ge=1, description="The page number(>= 1)"),
per_page: int = Query(default=10, ge=1, le=20, description="Film quantity at the page (>= 1 и <= 20)"),
db: Session = Depends(get_db)
):
total_items = db.query(MovieModel).count()
offset = (page - 1) * per_page
movies = db.query(MovieModel).order_by(MovieModel.id.desc()).offset(offset).limit(per_page).all()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_movies function should handle cases where no movies are found for the specified page by returning a 404 Not Found error with the message: "No movies found." This is a requirement from the task description.

base_url = "/movies/"
prev_page = f"{base_url}?page={page - 1}&per_page={per_page}" if page > 1 else None
next_page = f"{base_url}?page={page + 1}&per_page={per_page}" if offset + per_page < total_items else None
if not movies:
raise HTTPException(status_code=404, detail="No movies found for the specified page.")
return {
"movies": movies,
"prev_page": prev_page,
"next_page": next_page,
"total_pages": (total_items + per_page - 1) // per_page,
"total_items": total_items,
}


def update_movie(db: Session, movie_id: int, movie: MovieUpdateSchema):
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 db_movie is None:
raise HTTPException(status_code=400, detail="Invalid input data.")
Comment on lines +58 to +59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check if db_movie is None: is redundant because the previous check if not db_movie: already handles the case where the movie is not found. You should remove this block to avoid raising a 400 Bad Request error incorrectly.

db_movie.title = movie.title
db_movie.genre = movie.genre
db_movie.price = movie.price
db.commit()
db.refresh(db_movie)
return db_movie


def delete_movie(db: Session, movie_id: int):
db_movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first()
if not db_movie:
return None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning None, you should raise a 404 Not Found error when the movie is not found. This will provide a more consistent API response and better error handling.

db.delete(db_movie)
db.commit()
return db_movie
1 change: 1 addition & 0 deletions src/database/populate.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def seed(self):
print(f"Unexpected error: {e}")
raise


def main():
settings = get_settings()
with get_db_contextmanager() as db_session:
Expand Down
41 changes: 35 additions & 6 deletions src/routes/movies.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, joinedload

from src.crud.movies import create_movie, get_movie, get_movies, update_movie, delete_movie
from database import get_db
from database.models import MovieModel, CountryModel, GenreModel, ActorModel, LanguageModel
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from src.database.models import MovieModel
from src.schemas.movies import MovieReadSchema, MovieCreateSchema, \
MovieUpdateSchema

router = APIRouter()


# Write your code here
@router.post("/movies/", response_model=MovieReadSchema)
def add_movie(movie: MovieCreateSchema, db: Session = Depends(get_db)):
return create_movie(db, movie)


@router.get("/movies/", response_model=list[MovieReadSchema], status_code=200)
def list_movies(db: Session = Depends(get_db)):
db_movie = get_movies(db)
Comment on lines +18 to +19

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list_movies function should accept page and per_page query parameters to handle pagination. If no movies are found for the specified page, it should return a 404 Not Found error with the message: "No movies found." .

return db_movie
Comment on lines +19 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list_movies function should accept page and per_page parameters to pass them to the get_movies function. This will allow for proper pagination of the movie list.



@router.get("/movies/{movie_id}", response_model=MovieReadSchema)
def read_movie(movie_id: int, db: Session = Depends(get_db)):
db_movie = get_movie(db, movie_id)
return db_movie


@router.patch("/movies/{movie_id}", response_model=MovieReadSchema, status_code=200)
def edit_movie(movie_id: int, movie: MovieUpdateSchema, db: Session = Depends(get_db)):
db_movie = update_movie(db, movie_id, movie)
return db_movie


@router.delete("/movies/{movie_id}", response_model=MovieReadSchema, status_code=204)
def remove_movie(movie_id: int, db: Session = Depends(get_db)):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete route should not specify a response_model when the status code is 204 No Content. Consider removing the response_model parameter to align with the semantics of a 204 response.

db_movie = delete_movie(db, movie_id)
if not db_movie:
raise HTTPException(status_code=404, detail="Movie with the given ID was not found.")
return db_movie
98 changes: 97 additions & 1 deletion src/schemas/movies.py
Original file line number Diff line number Diff line change
@@ -1 +1,97 @@
# Write your code here
from datetime import datetime, date, timedelta
from typing import Optional, List

from pydantic import BaseModel, Field, validator


class CountrySchema(BaseModel):
name: str


class GenreSchema(BaseModel):
name: str


class ActorSchema(BaseModel):
name: str


class LanguageSchema(BaseModel):
name: str


class MovieBaseSchema(BaseModel):
name: str
date: datetime.date
score: Optional[float]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use date instead of datetime.date for the date field to be consistent with the release_date field in MovieCreateSchema.

genre: Optional[str]
overview: Optional[str]
crew: Optional[str]
orig_title: Optional[str]
status: Optional[str]
orig_lang: Optional[str]
budget: Optional[int]
revenue: Optional[float]
country: Optional[str]


class MovieCreateSchema(BaseModel):
title: str = Field(max_length=255, description="Название фильма (не более 255 символов)")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider inheriting MovieCreateSchema from MovieBaseSchema to ensure consistency and avoid redundancy in field definitions.

release_date: date = Field(description="Дата выхода фильма")
score: int = Field(ge=0, le=100, description="Оценка (0-100)")
budget: int = Field(ge=0, description="Бюджет (не может быть отрицательным)")
revenue: int = Field(ge=0, description="Доход (не может быть отрицательным)")
country: CountrySchema
genres: List[GenreSchema]
actors: List[ActorSchema]
languages: List[LanguageSchema]

@validator("release_date")
def validate_date(cls, value):
max_date = date.today() + timedelta(days=365)
if value > max_date:
raise ValueError("Дата выхода не может превышать текущую дату более чем на 1 год.")
return value


class MovieUpdateSchema(BaseModel):
score: int = Field(ge=0, le=100, description="Оценка (0-100)")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider inheriting MovieUpdateSchema from MovieBaseSchema to ensure consistency and avoid redundancy in field definitions.

budget: int = Field(ge=0, description="Бюджет (не может быть отрицательным)")
revenue: int = Field(ge=0, description="Доход (не может быть отрицательным)")


class MovieReadSchema(MovieBaseSchema):
id: int = Field(ge=0)
genres: List[GenreSchema]
actors: List[ActorSchema]
languages: List[LanguageSchema]

class Config:
from_attributes = True


class MovieDetailResponseSchema(BaseModel):
id: int
name: str
date: datetime.date
score: Optional[float]
genre: Optional[str]
overview: Optional[str]
crew: Optional[str]
orig_title: Optional[str]
status: Optional[str]
orig_lang: Optional[str]
budget: Optional[int]
revenue: Optional[float]
country: Optional[str]

class Config:
from_attributes: True


class MovieListResponseSchema(BaseModel):
movies: List[MovieDetailResponseSchema]
prev_page: Optional[str]
next_page: Optional[str]
total_pages: int
total_items: int
Loading