QMDB – مدلها
این تمرین در اصل برای ارسال چند فایل (در یک zip) نوشته شده است. در این نسخه همهچیز را در همین یک فایل بنویس: اول مدلها، بعد Managerها. خطهای
from models import …هم همانطور که در صورت سؤال آمده کار میکنند. داور یک پایگاهدادهی SQLite در حافظه میسازد، جدولها را از رویBaseتو درست میکند و متغیرsessionرا در اختیار آزمونها میگذارد.
در تمرینهای این فصل، قرار است یک سیستم مدیریت پایگاهداده برای سامانهی رتبهبندی فیلم کوئرا طراحی کنید. در بخش اول این پروژه، شما باید مدلهای این سیستم را طراحی کنید. برای هر مدل، توضیحات فیلدها و ویژگیهای لازم آورده شدهاست:
🎬 مدل Movie (فیلمها)
این مدل اطلاعات مربوط به فیلمها را ذخیره میکند.
| فیلد | نوع | ویژگیها |
|---|---|---|
id | int | کلید اصلی، autoincrement |
title | str | غیرتهی |
release_year | int | اختیاری |
genres | List[Genre] | رابطهی چندبهچند با Genre از طریق مدل میانی MovieGenre |
reviews | List[Review] | رابطهی یکبهچند با Review |
👤 مدل User (کاربران)
این مدل اطلاعات کاربران سیستم را ذخیره میکند.
| فیلد | نوع | ویژگیها |
|---|---|---|
id | int | کلید اصلی، autoincrement |
name | str | غیرتهی |
email | str | یکتا، غیرتهی |
is_verified | bool | مقدار پیشفرض: False |
created_at | datetime | مقدار پیشفرض: زمان حال (datetime.now) |
reviews | List[Review] | رابطهی یکبهچند با Review |
برای دسترسی به زمان حال میتوانید از کتابخانهی
datetimeاستفاده کنید.نکته: برای استفاده از این تابع بهعنوان ورودی آرگومان
default، نباید آن را فراخوانی کنید؛ یعنی ورودی باید بهصورتdefault=datetime.nowباشد.
from datetime import datetime
datetime.now()
# datetime.datetime(2024, 12, 25, 13, 34, 33, 510059)
🏷️ مدل Genre (ژانرها)
این مدل اطلاعات ژانرهای فیلم را ذخیره میکند.
| فیلد | نوع | ویژگیها |
|---|---|---|
id | int | کلید اصلی، autoincrement |
name | str | یکتا، غیرتهی |
movies | List[Movie] | رابطهی چندبهچند با Movie از طریق مدل میانی MovieGenre |
⭐ مدل Review (نظرات)
این مدل اطلاعات نظرات کاربران برای فیلمها را ذخیره میکند.
| فیلد | نوع | ویژگیها |
|---|---|---|
id | int | کلید اصلی، autoincrement |
movie_id | int | کلید خارجی به Movie |
user_id | int | کلید خارجی به User |
rating | int | غیرتهی |
comment | str | اختیاری |
created_at | datetime | مقدار پیشفرض: زمان حال (datetime.now) |
updated_at | datetime | مقدار پیشفرض: زمان حال (datetime.now) مقدار در زمان بهروزرسانی: زمان حال (datetime.now) |
مقدار در زمان بهروزرسانی را میتوانید با استفاده از آرگومان ورودی
onupdateتعیین کنید. وجود این آرگومان باعث میشود در هنگام بهروزرسانی هر شیء ازReview، مقدار این فیلد نیز بهصورت خودکار بهروزرسانی شود و بهعبارتی، زمان بهروزرسانی شیء بهصورت خودکار ثبت شود.
🔗 مدل MovieGenre (رابط بین ژانر و فیلم)
این مدل یک جدول میانی است که رابطهی چندبهچند بین فیلمها و ژانرها را مدیریت میکند.
| فیلد | نوع | ویژگیها |
|---|---|---|
movie_id | int | کلید خارجی به Movie |
genre_id | int | کلید خارجی به Genre |
✅ پاسخ نهایی: کد کامل مدلها
from typing import List
from datetime import datetime
import sqlalchemy as db
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
# ==================== مدل میانی ====================
class MovieGenre(Base):
__tablename__ = 'movie_genres'
movie_id: Mapped[int] = mapped_column(db.ForeignKey('movies.id'), primary_key=True)
genre_id: Mapped[int] = mapped_column(db.ForeignKey('genres.id'), primary_key=True)
# ==================== مدل Movie ====================
class Movie(Base):
__tablename__ = 'movies'
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str]
release_year: Mapped[int | None]
# رابطهی چندبهچند با Genre از طریق MovieGenre
genres: Mapped[List["Genre"]] = relationship(
secondary="movie_genres",
back_populates="movies"
)
# رابطهی یکبهچند با Review
reviews: Mapped[List["Review"]] = relationship(
back_populates="movie",
cascade="all, delete-orphan"
)
# ==================== مدل User ====================
class User(Base):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str]
email: Mapped[str] = mapped_column(unique=True)
is_verified: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
# رابطهی یکبهچند با Review
reviews: Mapped[List["Review"]] = relationship(
back_populates="user",
cascade="all, delete-orphan"
)
# ==================== مدل Genre ====================
class Genre(Base):
__tablename__ = 'genres'
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(unique=True)
# رابطهی چندبهچند با Movie از طریق MovieGenre
movies: Mapped[List["Movie"]] = relationship(
secondary="movie_genres",
back_populates="genres"
)
# ==================== مدل Review ====================
class Review(Base):
__tablename__ = 'reviews'
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
rating: Mapped[int]
comment: Mapped[str | None]
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
default=datetime.now,
onupdate=datetime.now
)
# کلیدهای خارجی
movie_id: Mapped[int] = mapped_column(db.ForeignKey('movies.id'))
user_id: Mapped[int] = mapped_column(db.ForeignKey('users.id'))
# روابط
movie: Mapped["Movie"] = relationship(back_populates="reviews")
user: Mapped["User"] = relationship(back_populates="reviews")
📄 models.py
چرا ارتباط بین مدلهای Movie و Genre بهدرستی برقرار نمیشود؟
همانطور که در درسنامهی قبل گفته شد، برای برقراری ارتباط بین این دو مدل باید از روش سادهتر مدل میانی استفاده کنید. در این روش، مدل میانی تنها شامل دو فیلد
movie_idوgenre_idخواهد بود. مدلهایMovieوGenreنیز بهصورت زیر تعریف میشوند:
class Movie(Base):
# ... سایر فیلدها
genres: Mapped[List["Genre"]] = relationship(secondary="movie_genres", back_populates="movies")
class Genre(Base):
# ... سایر فیلدها
movies: Mapped[List["Movie"]] = relationship(secondary="movie_genres", back_populates="genres")
- برای رابطهی چندبهچند بین
MovieوGenreاز مدل میانیMovieGenreاستفاده شده است- رابطهی یکبهچند بین
MovieوReviewباForeignKeyدرReviewبرقرار شده است- رابطهی یکبهچند بین
UserوReviewباForeignKeyدرReviewبرقرار شده است- برای فیلد
created_atازdefault=datetime.nowاستفاده شده است (بدون پرانتز)- برای فیلد
updated_atازdefault=datetime.nowوonupdate=datetime.nowاستفاده شده است- فیلدهای اختیاری با
|Noneمشخص شدهاند (مانندrelease_yearوcomment)
🚀 پس از طراحی مدلها، در تمرین بعدی با نحوهی ایجاد جلسه (Session) و انجام عملیات CRUD روی این مدلها آشنا خواهید شد.
نمونهها
sorted(Base.metadata.tables)
['genres', 'movie_genres', 'movies', 'reviews', 'users']
users = Base.metadata.tables["users"] sorted((column.name, column.nullable) for column in users.columns) users.c.email.unique
[('created_at', False), ('email', False), ('id', False), ('is_verified', False), ('name', False)]
True[sorted((column.name, column.nullable) for column in Base.metadata.tables[table].columns)
for table in ("movies", "genres", "reviews")][[('id', False), ('release_year', True), ('title', False)], [('id', False), ('name', False)], [('comment', True), ('created_at', False), ('id', False), ('movie_id', False), ('rating', False), ('updated_at', False), ('user_id', False)]]sorted(fk.target_fullname for fk in Base.metadata.tables["reviews"].foreign_keys) sorted(fk.target_fullname for fk in Base.metadata.tables["movie_genres"].foreign_keys)
['movies.id', 'users.id'] ['genres.id', 'movies.id']
movie = Movie(title="Inception", release_year=2010) genre = Genre(name="Sci-Fi") movie.genres.append(genre) ali = User(name="Ali", email="ali@example.com") session.add_all([movie, ali]) session.commit() session.add(Review(movie_id=movie.id, user_id=ali.id, rating=5, comment="great")) session.commit() [genre.movies[0].title, len(movie.reviews), ali.reviews[0].rating, ali.is_verified, type(ali.created_at).__name__]
['Inception', 1, 5, False, 'datetime']
import time sara = User(name="Sara", email="sara@example.com") up = Movie(title="Up") session.add_all([sara, up]) session.commit() review = Review(movie_id=up.id, user_id=sara.id, rating=3) session.add(review) session.commit() before = review.updated_at time.sleep(0.02) review.rating = 4 session.commit() [review.updated_at > before, review.created_at <= before, review.comment is None, up.release_year is None]
[True, True, True, True]
from sqlalchemy.exc import IntegrityError
session.add(User(name="A", email="same@example.com"))
session.commit()
session.add(User(name="B", email="same@example.com"))
try:
session.commit()
except IntegrityError:
print("email must be unique")
session.rollback()
session.add(Movie(title=None))
try:
session.commit()
except IntegrityError:
print("title is required")
session.rollback()email must be unique title is required
برای نوشتن پاسخ و دریافت امتیاز، ابتدا وارد حساب شوید.
ورود