feat: add name_manually_edited field to User model and update related functionality
All checks were successful
CI / lint-and-test (push) Successful in 15s

- Introduced a new boolean column `name_manually_edited` in the `users` table to control whether user names are overwritten during synchronization with Telegram.
- Updated the `get_or_create_user` function to respect the `name_manually_edited` flag, ensuring names are only updated when the flag is false.
- Implemented a new function `update_user_display_name` to allow manual updates of user names while setting the `name_manually_edited` flag to true.
- Enhanced unit tests to cover the new functionality and ensure correct behavior of name handling based on the `name_manually_edited` flag.
This commit is contained in:
2026-02-20 09:30:58 +03:00
parent dc116270b7
commit b61e1ca8a5
5 changed files with 169 additions and 6 deletions

View File

@@ -21,6 +21,7 @@ from duty_teller.db.repository import (
get_duties,
insert_duty,
set_user_phone,
update_user_display_name,
)
__all__ = [
@@ -42,6 +43,7 @@ __all__ = [
"get_duties",
"insert_duty",
"set_user_phone",
"update_user_display_name",
"init_db",
]

View File

@@ -1,6 +1,6 @@
"""SQLAlchemy ORM models for users and duties."""
from sqlalchemy import ForeignKey, Integer, BigInteger, Text
from sqlalchemy import Boolean, ForeignKey, Integer, BigInteger, Text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
@@ -22,6 +22,9 @@ class User(Base):
first_name: Mapped[str | None] = mapped_column(Text, nullable=True)
last_name: Mapped[str | None] = mapped_column(Text, nullable=True)
phone: Mapped[str | None] = mapped_column(Text, nullable=True)
name_manually_edited: Mapped[bool] = mapped_column(
Boolean, nullable=False, server_default="0", default=False
)
duties: Mapped[list["Duty"]] = relationship("Duty", back_populates="user")

View File

@@ -23,12 +23,18 @@ def get_or_create_user(
first_name: str | None = None,
last_name: str | None = None,
) -> User:
"""
Get or create user by telegram_user_id. On create, name comes from Telegram.
On update: username is always synced; full_name/first_name/last_name are only
updated if name_manually_edited is False (otherwise keep existing display name).
"""
user = get_user_by_telegram_id(session, telegram_user_id)
if user:
user.full_name = full_name
user.username = username
user.first_name = first_name
user.last_name = last_name
if not user.name_manually_edited:
user.full_name = full_name
user.first_name = first_name
user.last_name = last_name
session.commit()
session.refresh(user)
return user
@@ -38,6 +44,7 @@ def get_or_create_user(
username=username,
first_name=first_name,
last_name=last_name,
name_manually_edited=False,
)
session.add(user)
session.commit()
@@ -46,7 +53,10 @@ def get_or_create_user(
def get_or_create_user_by_full_name(session: Session, full_name: str) -> User:
"""Find user by exact full_name or create one with telegram_user_id=None (for duty-schedule import)."""
"""
Find user by exact full_name or create one with telegram_user_id=None (for duty-schedule import).
New users get name_manually_edited=True since the name comes from import, not Telegram.
"""
user = session.query(User).filter(User.full_name == full_name).first()
if user:
return user
@@ -56,6 +66,7 @@ def get_or_create_user_by_full_name(session: Session, full_name: str) -> User:
username=None,
first_name=None,
last_name=None,
name_manually_edited=True,
)
session.add(user)
session.commit()
@@ -63,6 +74,30 @@ def get_or_create_user_by_full_name(session: Session, full_name: str) -> User:
return user
def update_user_display_name(
session: Session,
telegram_user_id: int,
full_name: str,
first_name: str | None = None,
last_name: str | None = None,
) -> User | None:
"""
Update display name for user by telegram_user_id and set name_manually_edited=True.
Use from API or admin when name is changed manually; subsequent get_or_create_user
will not overwrite these fields. Returns User or None if not found.
"""
user = session.query(User).filter(User.telegram_user_id == telegram_user_id).first()
if not user:
return None
user.full_name = full_name
user.first_name = first_name
user.last_name = last_name
user.name_manually_edited = True
session.commit()
session.refresh(user)
return user
def delete_duties_in_range(
session: Session,
user_id: int,