راه
۴ کیو· سختپیشرفتهPYTHON۴ ثانیه · ۲۵۶ مگابایت

QMDB – کوئری‌ها (مدیریت داده‌ها)

این تمرین در اصل برای ارسال چند فایل (در یک zip) نوشته شده است. در این نسخه همه‌چیز را در همین یک فایل بنویس: اول مدل‌ها، بعد Managerها. خط‌های from models import … هم همان‌طور که در صورت سؤال آمده کار می‌کنند. داور یک پایگاه‌داده‌ی SQLite در حافظه می‌سازد، جدول‌ها را از روی Base تو درست می‌کند و متغیر session را در اختیار آزمون‌ها می‌گذارد.

در این تمرین شما باید متدهای مربوط به عملیات پایه و کوئری‌های پرکاربرد روی پایگاه‌داده برای سیستم امتیازدهی فیلم کوئرا را پیاده‌سازی کنید. شما باید این متدها را در کلاس‌های Manager که برای تعامل با مدل‌هایی که در قسمت قبل تعریف کرده‌اید ایجاد شده‌اند، پیاده‌سازی کنید. ⚠️ توجه کنید که باید حتماً کد خود از قسمت قبلی را به این تمرین انتقال دهید.


📄 فایل models.py

این فایل شامل تعریف مدل‌های Base، Movie، User، Genre، Review و MovieGenre است که در تمرین قبلی پیاده‌سازی کرده‌اید.


🎯 کلاس GenreManager

این کلاس در فایل genre_manager.py تعریف شده‌است.

python
class GenreManager:
    def __init__(self, session: Session):
        self.session = session

    def create(self, name: str) -> Genre:
        pass

    def get(self, genre_id: int) -> Genre | None:
        pass

    def get_all(self):
        pass

    def get_genre_by_name(self, name: str) -> Genre | None:
        pass

📄 genre_manager.py

🔹 create(self, name: str) -> Genre

یک ژانر جدید در پایگاه‌داده ایجاد می‌کند. این متد باید یک رکورد جدید در جدول genres ایجاد کند.

ورودی‌ها: name از نوع str که نام ژانر است.

خروجی: شیء Genre ایجادشده.

🔹 get(self, genre_id: int) -> Genre | None

اطلاعات یک ژانر را با استفاده از شناسه (id) آن از پایگاه‌داده دریافت می‌کند. این متد باید شیء ژانر از جنس Genre را برگرداند.

ورودی‌ها: genre_id از نوع int که شناسه‌ی ژانر است.

خروجی: شیء Genre با شناسه‌ی داده‌شده یا None اگر ژانری با این شناسه وجود نداشته باشد.

💡 راهنمایی: برای دریافت یک شیء در صورت وجود یا None در صورت عدم وجود، می‌توانید از متد scalar_one_or_none() استفاده کنید.

🔹 get_all(self)

لیستی از تمام ژانرهای موجود در پایگاه‌داده را برمی‌گرداند.

خروجی: لیستی از اشیاء Genre.

🔹 get_genre_by_name(self, name: str) -> Genre | None

اطلاعات یک ژانر را با استفاده از نام آن دریافت می‌کند.

ورودی‌ها: name از نوع str که نام ژانر است.

خروجی: شیء Genre با نام داده‌شده یا None اگر ژانری با این نام وجود نداشته باشد.


🎯 کلاس MovieManager

این کلاس در فایل movie_manager.py تعریف شده‌است.

python
class MovieManager:
    def __init__(self, session: Session) -> None:
        self.session = session

    def create(self, title: str, release_year: int) -> Movie:
        pass

    def get(self, movie_id: int) -> Movie | None:
        pass

    def get_all(self):
        pass

    def add_genre(self, movie_id: int, genre: Genre) -> Movie:
        pass

    def get_reviews(self, movie_id: int):
        pass

    def get_average_rating(self, movie_id: int):
        pass

📄 movie_manager.py

🔹 create(self, title: str, release_year: int) -> Movie

یک فیلم جدید در پایگاه‌داده ایجاد می‌کند. این متد باید یک رکورد جدید در جدول movies ایجاد کند.

ورودی‌ها: title از نوع str که عنوان فیلم است و release_year از نوع int که سال انتشار فیلم است.

خروجی: شیء Movie ایجادشده.

🔹 get(self, movie_id: int) -> Movie | None

اطلاعات یک فیلم را با استفاده از شناسه (id) آن از پایگاه‌داده دریافت می‌کند. این متد باید شیء فیلم از جنس Movie را برگرداند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است.

خروجی: شیء Movie با شناسه‌ی داده شده یا None اگر فیلمی با این شناسه وجود نداشته باشد.

🔹 get_all(self)

لیستی از تمام فیلم‌های موجود در پایگاه‌داده را برمی‌گرداند.

خروجی: لیستی از اشیاء Movie.

🔹 add_genre(self, movie_id: int, genre: Genre) -> Movie

یک ژانر را به فیلم اضافه می‌کند. این متد باید رابطه‌ی بین فیلم و ژانر را در جدول میانی movie_genres برقرار کند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است و genre از نوع Genre که شیء ژانر موردنظر است.

خروجی: شیء Movie که ژانر به آن اضافه شده‌است.

🔹 get_reviews(self, movie_id: int)

لیستی از نقدها و امتیازهای مربوط به یک فیلم را دریافت می‌کند. این متد باید تمام نقدهای ثبت‌شده برای فیلم مشخص شده را برگرداند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است.

خروجی: لیستی از اشیاء Review مربوط به فیلم.

🔹 get_average_rating(self, movie_id: int)

میانگین امتیاز یک فیلم را محاسبه می‌کند. این متد باید میانگین امتیازات داده‌شده برای فیلم را محاسبه و برگرداند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است.

خروجی: میانگین امتیاز فیلم به‌صورت float یا None اگر فیلم هیچ نقدی نداشته باشد.


🎯 کلاس UserManager

این کلاس در فایل user_manager.py تعریف شده‌است.

python
class UserManager:
    def __init__(self, session: Session):
        self.session = session

    def create(self, name: str, email: str) -> User:
        pass

    def get(self, user_id: int) -> User | None:
        pass

    def get_all(self):
        pass

    def get_user_by_email(self, email: str) -> User | None:
        pass

    def get_most_active_users(self, limit=5):
        pass

📄 user_manager.py

🔹 create(self, name: str, email: str) -> User

یک کاربر جدید در پایگاه‌داده ایجاد می‌کند. این متد باید رکورد جدیدی در جدول users اضافه کند.

ورودی‌ها: name از نوع str که نام کاربر است و email از نوع str که ایمیل کاربر است.

خروجی: شیء User ایجادشده.

🔹 get(self, user_id: int) -> User | None

اطلاعات یک کاربر را با استفاده از شناسه (id) آن از پایگاه‌داده دریافت می‌کند. این متد باید شیء کاربر از جنس User را برگرداند.

ورودی‌ها: user_id از نوع int که شناسه‌ی کاربر است.

خروجی: شیء User با شناسه‌ی داده‌شده یا None اگر کاربری با این شناسه وجود نداشته باشد.

🔹 get_all(self)

لیستی از تمام کاربران موجود در پایگاه داده را برمی‌گرداند.

خروجی: لیستی از اشیاء User.

🔹 get_user_by_email(self, email: str) -> User | None

اطلاعات یک کاربر را با استفاده از ایمیل آن دریافت می‌کند.

ورودی‌ها: email از نوع str که ایمیل کاربر است.

خروجی: شیء User با ایمیل داده‌شده یا None اگر کاربری با این ایمیل وجود نداشته باشد.

🔹 get_most_active_users(self, limit=5)

لیستی از فعال‌ترین کاربران (کاربرانی که بیشترین تعداد نقد را دارند) را بر اساس تعداد نقدهای ثبت‌شده برمی‌گرداند.

ورودی‌ها: limit از نوع int (اختیاری) که حداکثر تعداد کاربران موردنظر است (پیش‌فرض ۵).

خروجی: لیستی از تاپل‌ها، که هر تاپل شامل شناسه‌ی کاربر (user_id) از نوع int و تعداد نقدهای آن کاربر (review_count) از نوع int است. لیست بر اساس تعداد نقدها به‌صورت نزولی مرتب شده‌است.


🎯 کلاس ReviewManager

این کلاس در فایل review_manager.py تعریف شده‌است.

python
class ReviewManager:
    def __init__(self, session: Session):
        self.session = session

    def create(self, movie_id: int, user_id: int, rating: int, comment: str = None) -> Review:
        pass

    def get(self, review_id: int) -> Review | None:
        pass

    def get_all(self):
        pass

    def get_reviews_by_user(self, user_id: int):
        pass

    def get_latest_reviews_for_movie_by_time(self, movie_id: int, limit: int = 5):
        pass

    def get_highest_rated_reviews(self, movie_id: int, limit: int = 5):
        pass

    def get_average_rating_by_user(self):
        pass

📄 review_manager.py

🔹 create(self, movie_id: int, user_id: int, rating: int, comment: str = None) -> Review

یک امتیاز و نقد برای یک فیلم توسط یک کاربر اضافه می‌کند. این متد باید یک رکورد جدید در جدول reviews ایجاد کند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است، user_id از نوع int که شناسه‌ی کاربر است، rating از نوع int که امتیاز (از ۱ تا ۵) است و comment از نوع str (اختیاری) که متن نقد است.

خروجی: شیء Review ایجادشده.

🔹 get(self, review_id: int) -> Review | None

اطلاعات یک نقد را با استفاده از شناسه (id) آن از پایگاه‌داده دریافت می‌کند. این متد باید شیء نقد از جنس Review را برگرداند.

ورودی‌ها: review_id از نوع int که شناسه‌ی نقد است.

خروجی: شیء Review با شناسه‌ی داده‌شده یا None اگر نقدی با این شناسه وجود نداشته باشد.

🔹 get_all(self)

لیستی از تمام نقدهای موجود در پایگاه‌داده را برمی‌گرداند.

خروجی: لیستی از اشیاء Review.

🔹 get_reviews_by_user(self, user_id: int)

لیستی از نقدهای ثبت‌شده توسط یک کاربر خاص را دریافت می‌کند.

ورودی‌ها: user_id از نوع int که شناسه‌ی کاربر است.

خروجی: لیستی از اشیاء Review ثبت‌شده توسط کاربر.

🔹 get_latest_reviews_for_movie_by_time(self, movie_id: int, limit: int = 5)

لیستی از جدیدترین نقدهای یک فیلم را بر اساس زمان ایجاد دریافت می‌کند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است و limit از نوع int (اختیاری) که حداکثر تعداد نقدهای موردنظر است (پیش‌فرض ۵).

خروجی: لیستی از اشیاء Review مربوط به فیلم، مرتب‌شده بر اساس زمان ایجاد به‌صورت نزولی (جدیدترین ابتدا).

🔹 get_highest_rated_reviews(self, movie_id: int, limit: int = 5)

لیستی از نقدهای با بالاترین امتیاز برای یک فیلم را دریافت می‌کند.

ورودی‌ها: movie_id از نوع int که شناسه‌ی فیلم است و limit از نوع int (اختیاری) که حداکثر تعداد نقدهای موردنظر است (پیش‌فرض ۵).

خروجی: لیستی از اشیاء Review مربوط به فیلم، مرتب‌شده بر اساس امتیاز به‌صورت نزولی (بالاترین امتیاز ابتدا).

🔹 get_average_rating_by_user(self)

میانگین امتیازاتی که هر کاربر به فیلم‌ها داده است را محاسبه می‌کند.

خروجی: لیستی از تاپل‌ها، که هر تاپل شامل شناسه‌ی کاربر (user_id) از نوع int و میانگین امتیازات داده‌شده توسط آن کاربر (average_rating) از نوع float است.


✅ پاسخ نهایی: پیاده‌سازی کامل Managerها

📄 genre_manager.py

python
from sqlalchemy import select
from sqlalchemy.orm import Session
from models import Genre

class GenreManager:
    def __init__(self, session: Session):
        self.session = session

    def create(self, name: str) -> Genre:
        genre = Genre(name=name)
        self.session.add(genre)
        self.session.commit()
        return genre

    def get(self, genre_id: int) -> Genre | None:
        return self.session.get(Genre, genre_id)

    def get_all(self):
        stmt = select(Genre)
        return self.session.execute(stmt).scalars().all()

    def get_genre_by_name(self, name: str) -> Genre | None:
        stmt = select(Genre).where(Genre.name == name)
        return self.session.execute(stmt).scalars().first()

📄 movie_manager.py

python
from sqlalchemy import select, func
from sqlalchemy.orm import Session
from models import Movie, Genre, Review

class MovieManager:
    def __init__(self, session: Session) -> None:
        self.session = session

    def create(self, title: str, release_year: int) -> Movie:
        movie = Movie(title=title, release_year=release_year)
        self.session.add(movie)
        self.session.commit()
        return movie

    def get(self, movie_id: int) -> Movie | None:
        return self.session.get(Movie, movie_id)

    def get_all(self):
        stmt = select(Movie)
        return self.session.execute(stmt).scalars().all()

    def add_genre(self, movie_id: int, genre: Genre) -> Movie:
        movie = self.get(movie_id)
        if movie is None:
            raise ValueError(f"فیلم با شناسه {movie_id} وجود ندارد")
        movie.genres.append(genre)
        self.session.commit()
        return movie

    def get_reviews(self, movie_id: int):
        movie = self.get(movie_id)
        if movie is None:
            raise ValueError(f"فیلم با شناسه {movie_id} وجود ندارد")
        return movie.reviews

    def get_average_rating(self, movie_id: int):
        stmt = select(func.avg(Review.rating)).where(Review.movie_id == movie_id)
        return self.session.execute(stmt).scalar_one_or_none()

📄 user_manager.py

python
from sqlalchemy import select, func
from sqlalchemy.orm import Session
from models import User, Review

class UserManager:
    def __init__(self, session: Session):
        self.session = session

    def create(self, name: str, email: str) -> User:
        user = User(name=name, email=email)
        self.session.add(user)
        self.session.commit()
        return user

    def get(self, user_id: int) -> User | None:
        return self.session.get(User, user_id)

    def get_all(self):
        stmt = select(User)
        return self.session.execute(stmt).scalars().all()

    def get_user_by_email(self, email: str) -> User | None:
        stmt = select(User).where(User.email == email)
        return self.session.execute(stmt).scalars().first()

    def get_most_active_users(self, limit=5):
        stmt = select(
            Review.user_id,
            func.count(Review.id).label('review_count')
        ).group_by(Review.user_id).order_by(
            func.count(Review.id).desc()
        ).limit(limit)
        return self.session.execute(stmt).all()

📄 review_manager.py

python
from sqlalchemy import select, func
from sqlalchemy.orm import Session
from models import Review

class ReviewManager:
    def __init__(self, session: Session):
        self.session = session

    def create(self, movie_id: int, user_id: int, rating: int, comment: str = None) -> Review:
        review = Review(
            movie_id=movie_id,
            user_id=user_id,
            rating=rating,
            comment=comment
        )
        self.session.add(review)
        self.session.commit()
        return review

    def get(self, review_id: int) -> Review | None:
        return self.session.get(Review, review_id)

    def get_all(self):
        stmt = select(Review)
        return self.session.execute(stmt).scalars().all()

    def get_reviews_by_user(self, user_id: int):
        stmt = select(Review).where(Review.user_id == user_id)
        return self.session.execute(stmt).scalars().all()

    def get_latest_reviews_for_movie_by_time(self, movie_id: int, limit: int = 5):
        stmt = select(Review).where(
            Review.movie_id == movie_id
        ).order_by(Review.created_at.desc()).limit(limit)
        return self.session.execute(stmt).scalars().all()

    def get_highest_rated_reviews(self, movie_id: int, limit: int = 5):
        stmt = select(Review).where(
            Review.movie_id == movie_id
        ).order_by(Review.rating.desc()).limit(limit)
        return self.session.execute(stmt).scalars().all()

    def get_average_rating_by_user(self):
        stmt = select(
            Review.user_id,
            func.avg(Review.rating).label('average_rating')
        ).group_by(Review.user_id)
        return self.session.execute(stmt).all()

  • از session.get() برای دریافت یک شیء با شناسه استفاده کنید
  • از session.execute(select(Model)).scalars().all() برای دریافت تمام رکوردها استفاده کنید
  • از session.execute(select(Model).where(...)).scalars().first() برای دریافت اولین نتیجه استفاده کنید
  • از session.execute(select(Model)).scalar_one_or_none() برای دریافت یک مقدار اسکالر یا None استفاده کنید
  • برای روابط چندبه‌چند از append() روی لیست رابطه استفاده کنید
  • برای کوئری‌های گروه‌بندی از group_by() و توابع تجمیعی مانند func.count() و func.avg() استفاده کنید
  • برای مرتب‌سازی از order_by() با desc() یا asc() استفاده کنید

🚀 پس از پیاده‌سازی Managerها، می‌توانید از آن‌ها برای انجام عملیات CRUD روی پایگاه‌داده استفاده کنید.

نمونه‌ها

ورودی نمونه ۱
genres = GenreManager(session)
drama = genres.create("Drama")
comedy = genres.create("Comedy")
[drama.id, drama.name]
sorted(genre.name for genre in genres.get_all())
genres.get(drama.id).name
genres.get_genre_by_name("Comedy").name
[genres.get(999) is None, genres.get_genre_by_name("Horror") is None]
خروجی نمونه ۱
[1, 'Drama']
['Comedy', 'Drama']
'Drama'
'Comedy'
[True, True]
ورودی نمونه ۲
movies = MovieManager(session)
genres = GenreManager(session)
inception = movies.create("Inception", 2010)
up = movies.create("Up", 2009)
[inception.title, inception.release_year]
sorted(movie.title for movie in movies.get_all())
movies.get(999) is None
movies.add_genre(inception.id, genres.create("Sci-Fi")).title
[genre.name for genre in movies.get(inception.id).genres]
خروجی نمونه ۲
['Inception', 2010]
['Inception', 'Up']
True
'Inception'
['Sci-Fi']
ورودی نمونه ۳
movies = MovieManager(session)
users = UserManager(session)
reviews = ReviewManager(session)
inception = movies.create("Inception", 2010)
tenet = movies.create("Tenet", 2020)
ali = users.create("Ali", "ali@example.com")
sara = users.create("Sara", "sara@example.com")
first = reviews.create(inception.id, ali.id, 5, "great")
second = reviews.create(inception.id, sara.id, 4)
[first.rating, first.comment, second.comment]
movies.get_average_rating(inception.id)
movies.get_average_rating(tenet.id) is None
sorted(review.rating for review in movies.get_reviews(inception.id))
خروجی نمونه ۳
[5, 'great', None]
4.5
True
[4, 5]
ورودی نمونه ۴
users = UserManager(session)
ali = users.create("Ali", "ali@example.com")
[ali.name, ali.email, ali.is_verified]
users.get(ali.id).email
users.get_user_by_email("ali@example.com").name
[users.get(999) is None, users.get_user_by_email("nobody@example.com") is None]
len(users.get_all())
خروجی نمونه ۴
['Ali', 'ali@example.com', False]
'ali@example.com'
'Ali'
[True, True]
1
ورودی نمونه ۵
import time
movies = MovieManager(session)
users = UserManager(session)
reviews = ReviewManager(session)
a = movies.create("A", 2000)
b = movies.create("B", 2001)
u1 = users.create("U1", "u1@example.com")
u2 = users.create("U2", "u2@example.com")
u3 = users.create("U3", "u3@example.com")
for movie, user, rating in [(a, u1, 2), (a, u2, 5), (b, u1, 4), (a, u1, 3), (b, u3, 1), (b, u1, 5), (b, u2, 3)]:
    reviews.create(movie.id, user.id, rating)
    time.sleep(0.01)
[tuple(row) for row in users.get_most_active_users(2)]
sorted(review.rating for review in reviews.get_reviews_by_user(u1.id))
[review.rating for review in reviews.get_latest_reviews_for_movie_by_time(a.id, 2)]
[review.rating for review in reviews.get_highest_rated_reviews(b.id, 2)]
sorted((row[0], round(row[1], 2)) for row in reviews.get_average_rating_by_user())
[len(reviews.get_all()), reviews.get(999) is None]
خروجی نمونه ۵
[(1, 4), (2, 2)]
[2, 3, 4, 5]
[3, 5]
[5, 4]
[(1, 3.5), (2, 4.0), (3, 1.0)]
[7, True]

برای نوشتن پاسخ و دریافت امتیاز، ابتدا وارد حساب شوید.

ورود