models.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """
  2. SQL Alchemy models declaration.
  3. https://docs.sqlalchemy.org/en/14/orm/declarative_styles.html#example-two-dataclasses-with-declarative-table
  4. Dataclass style for powerful autocompletion support.
  5. https://alembic.sqlalchemy.org/en/latest/tutorial.html
  6. Note, it is used by alembic migrations logic, see `alembic/env.py`
  7. Alembic shortcuts:
  8. # create migration
  9. alembic revision --autogenerate -m "migration_name"
  10. # apply all migrations
  11. alembic upgrade head
  12. """
  13. from typing import Optional
  14. import uuid
  15. from datetime import datetime
  16. from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
  17. from sqlalchemy.dialects.postgresql import UUID
  18. from sqlalchemy.ext.hybrid import hybrid_property
  19. from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
  20. from sqlalchemy.sql import func
  21. class Base(DeclarativeBase):
  22. pass
  23. def uid_column() -> Mapped[str]:
  24. """Returns a postgreSQL UUID column for SQL ORM"""
  25. return mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4()))
  26. class User(Base):
  27. __tablename__ = "user_model"
  28. id: Mapped[str] = uid_column()
  29. email: Mapped[str] = mapped_column(String(254), nullable=False, unique=True, index=True)
  30. hashed_password: Mapped[str] = mapped_column(String(128), nullable=False)
  31. class Project(Base):
  32. __tablename__ = "projects"
  33. id: Mapped[str] = uid_column()
  34. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  35. updated_at: Mapped[datetime] = mapped_column(
  36. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  37. )
  38. name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
  39. is_public: Mapped[bool] = mapped_column(Boolean())
  40. volunteers: Mapped[list["Volunteer"]] = relationship(
  41. back_populates="project", cascade="delete, delete-orphan"
  42. )
  43. slots: Mapped[list["Slot"]] = relationship(
  44. back_populates="project", cascade="delete, delete-orphan"
  45. )
  46. sms: Mapped[list["Sms"]] = relationship(
  47. back_populates="project", cascade="delete, delete-orphan"
  48. )
  49. templates: Mapped[list["SlotTemplate"]] = relationship(
  50. back_populates="project", cascade="delete, delete-orphan"
  51. )
  52. tags: Mapped[list["SlotTag"]] = relationship(
  53. back_populates="project", cascade="delete, delete-orphan"
  54. )
  55. association_table_volunteer_slot = Table(
  56. "association_volunteer_slot",
  57. Base.metadata,
  58. Column(
  59. "volunteer_id",
  60. ForeignKey("volunteers.id", ondelete="CASCADE"),
  61. primary_key=True,
  62. ),
  63. Column("slot_id", ForeignKey("slots.id", ondelete="CASCADE"), primary_key=True),
  64. )
  65. class Volunteer(Base):
  66. __tablename__ = "volunteers"
  67. id: Mapped[str] = uid_column()
  68. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  69. project: Mapped["Project"] = relationship(back_populates="volunteers")
  70. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  71. updated_at: Mapped[datetime] = mapped_column(
  72. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  73. )
  74. name: Mapped[str] = mapped_column(String(128))
  75. surname: Mapped[str] = mapped_column(String(128))
  76. email: Mapped[str] = mapped_column(String(128))
  77. phone_number: Mapped[str] = mapped_column(String(128))
  78. automatic_sms: Mapped[bool] = mapped_column(Boolean(), default=False)
  79. slots: Mapped[list["Slot"]] = relationship(
  80. secondary=association_table_volunteer_slot, back_populates="volunteers"
  81. )
  82. @hybrid_property
  83. def slots_id(self) -> list[str]:
  84. return [s.id for s in self.slots]
  85. class Slot(Base):
  86. __tablename__ = "slots"
  87. id: Mapped[str] = uid_column()
  88. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  89. project: Mapped["Project"] = relationship(back_populates="slots")
  90. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  91. updated_at: Mapped[datetime] = mapped_column(
  92. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  93. )
  94. title: Mapped[str] = mapped_column(String(128), nullable=False)
  95. starting_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  96. ending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  97. required_volunteers: Mapped[int] = mapped_column(Integer, default=0)
  98. volunteers: Mapped[list[Volunteer]] = relationship(
  99. secondary=association_table_volunteer_slot, back_populates="slots"
  100. )
  101. template_id: Mapped[Optional[str]] = mapped_column(ForeignKey("slot_templates.id"))
  102. template: Mapped["SlotTemplate"] = relationship(back_populates="slots")
  103. @hybrid_property
  104. def volunteers_id(self) -> list[str]:
  105. return [v.id for v in self.volunteers]
  106. association_table_template_tags = Table(
  107. "association_description_tag",
  108. Base.metadata,
  109. Column(
  110. "description_id",
  111. ForeignKey("slot_templates.id", ondelete="CASCADE"),
  112. primary_key=True,
  113. ),
  114. Column("tag_id", ForeignKey("slot_tags.id", ondelete="CASCADE"), primary_key=True),
  115. )
  116. class SlotTag(Base):
  117. __tablename__ = "slot_tags"
  118. id: Mapped[str] = uid_column()
  119. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  120. project: Mapped["Project"] = relationship(back_populates="tags")
  121. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  122. updated_at: Mapped[datetime] = mapped_column(
  123. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  124. )
  125. title: Mapped[str] = mapped_column(String(), default="")
  126. templates: Mapped[list["SlotTemplate"]] = relationship(
  127. secondary=association_table_template_tags, back_populates="tags"
  128. )
  129. @hybrid_property
  130. def templates_id(self) -> list[str]:
  131. return [s.id for s in self.templates]
  132. class SlotTemplate(Base):
  133. __tablename__ = "slot_templates"
  134. id: Mapped[str] = uid_column()
  135. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  136. project: Mapped["Project"] = relationship(back_populates="templates")
  137. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  138. updated_at: Mapped[datetime] = mapped_column(
  139. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  140. )
  141. title: Mapped[str] = mapped_column(String(), default="")
  142. description: Mapped[str] = mapped_column(String(), default="")
  143. place: Mapped[str] = mapped_column(String(), default="")
  144. responsible_contact: Mapped[str] = mapped_column(String(), default="")
  145. slots: Mapped[list[Slot]] = relationship(back_populates="template")
  146. tags: Mapped[list[SlotTag]] = relationship(
  147. secondary=association_table_template_tags, back_populates="templates"
  148. )
  149. @hybrid_property
  150. def slots_id(self) -> list[str]:
  151. return [s.id for s in self.slots]
  152. @hybrid_property
  153. def tags_id(self) -> list[str]:
  154. return [s.id for s in self.tags]
  155. class Sms(Base):
  156. __tablename__ = "sms"
  157. id: Mapped[str] = mapped_column(
  158. UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
  159. )
  160. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  161. project: Mapped["Project"] = relationship(back_populates="sms")
  162. volunteer_id: Mapped[str] = mapped_column(ForeignKey("volunteers.id"), nullable=True)
  163. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  164. updated_at: Mapped[datetime] = mapped_column(
  165. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  166. )
  167. content: Mapped[str] = mapped_column(String(), nullable=False)
  168. phone_number: Mapped[str] = mapped_column(String(24))
  169. sending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now)
  170. send_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)