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.
59 changes: 59 additions & 0 deletions src/crud/movies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from database import get_db
from fastapi import Depends, Query
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.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()
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
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:
return None
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
61 changes: 55 additions & 6 deletions src/routes/movies.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
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)):
existing_movie = (
db.query(MovieModel)
.filter(MovieModel.name == movie.name, MovieModel.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."
)
if existing_movie is None:
raise HTTPException(status_code=400)
return create_movie(db, movie)

Choose a reason for hiding this comment

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

The check for existing_movie is None is redundant because it contradicts the previous check for existing_movie. Remove this block to avoid unnecessary code execution.



@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." .

if not 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.

raise HTTPException(status_code=404, detail="No movies found for the specified page.")
return db_movie


@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)
if not db_movie:
raise HTTPException(status_code=404, detail="Movie with the given ID was not found.")
return db_movie


@router.put("/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)

Choose a reason for hiding this comment

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

The edit_movie function should use the PATCH method instead of PUT to allow partial updates. Ensure that only the fields provided in the request body are updated, leaving all other fields unchanged.

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.")
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
104 changes: 103 additions & 1 deletion src/schemas/movies.py
Original file line number Diff line number Diff line change
@@ -1 +1,103 @@
# 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):
id: int
name: str

Choose a reason for hiding this comment

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

The id field should not be part of MovieBaseSchema as it is not typically included in schemas meant for creation or updates. Consider removing it from MovieBaseSchema and adding it only to schemas where it is necessary, like MovieReadSchema.

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(MovieBaseSchema):
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.

The MovieCreateSchema should not inherit from MovieBaseSchema if it requires different fields. Consider defining MovieCreateSchema independently to ensure it only includes the necessary fields for movie creation.

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(MovieBaseSchema):
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.

The MovieUpdateSchema should not inherit from MovieBaseSchema if it requires different fields. Consider defining MovieUpdateSchema independently to ensure it only includes the necessary fields for movie updates.

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

@validator("id")
def validate_id(cls, value):
if movie_id := value:
return movie_id

Choose a reason for hiding this comment

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

The validate_id method in MovieReadSchema is redundant and does not perform any meaningful validation. Consider removing this method.


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