draft-topics-fix2
All checks were successful
Deploy on push / deploy (push) Successful in 45s

This commit is contained in:
Untone 2025-05-07 10:37:18 +03:00
parent 51de649686
commit a6b3b21894
2 changed files with 94 additions and 130 deletions

View File

@ -74,3 +74,33 @@ class Draft(Base):
lazy="noload", # Не грузим по умолчанию, только через options lazy="noload", # Не грузим по умолчанию, только через options
viewonly=True # Указываем, что это связь только для чтения viewonly=True # Указываем, что это связь только для чтения
) )
def dict(self):
"""
Сериализует объект Draft в словарь.
Гарантирует, что поля topics и authors всегда будут списками.
"""
return {
"id": self.id,
"created_at": self.created_at,
"created_by": self.created_by,
"community": self.community,
"layout": self.layout,
"slug": self.slug,
"title": self.title,
"subtitle": self.subtitle,
"lead": self.lead,
"body": self.body,
"media": self.media or [],
"cover": self.cover,
"cover_caption": self.cover_caption,
"lang": self.lang,
"seo": self.seo,
"updated_at": self.updated_at,
"deleted_at": self.deleted_at,
"updated_by": self.updated_by,
"deleted_by": self.deleted_by,
# Гарантируем, что topics и authors всегда будут списками
"topics": [topic.dict() for topic in (self.topics or [])],
"authors": [author.dict() for author in (self.authors or [])]
}

View File

@ -107,8 +107,9 @@ async def load_drafts(_, info):
drafts_data = [] drafts_data = []
for draft in drafts: for draft in drafts:
draft_dict = draft.dict() draft_dict = draft.dict()
draft_dict["topics"] = [topic.dict() for topic in draft.topics] # Всегда возвращаем массив для topics, даже если он пустой
draft_dict["authors"] = [author.dict() for author in draft.authors] draft_dict["topics"] = [topic.dict() for topic in (draft.topics or [])]
draft_dict["authors"] = [author.dict() for author in (draft.authors or [])]
# Добавляем информацию о публикации, если она есть # Добавляем информацию о публикации, если она есть
if draft.publication: if draft.publication:
@ -378,32 +379,36 @@ def validate_html_content(html_content: str) -> tuple[bool, str]:
@mutation.field("publish_draft") @mutation.field("publish_draft")
@login_required @login_required
async def publish_draft(_, info, draft_id: int): async def publish_draft(_, info, draft_id: int):
"""Публикует черновик в виде публикации (shout). """
Публикует черновик, создавая новый Shout или обновляя существующий.
Загружает связанные объекты (topics, authors) заранее, чтобы избежать ошибок
с отсоединенными объектами при сериализации.
Args: Args:
draft_id: ID черновика для публикации draft_id (int): ID черновика для публикации
Returns: Returns:
dict: Содержит одно из полей: dict: Результат публикации с shout или сообщением об ошибке
- error: Сообщение об ошибке, если публикация не удалась
- shout: Опубликованный объект Shout
- draft: Черновик (передается в ответе для совместимости с GraphQL схемой)
""" """
user_id = info.context.get("user_id") user_id = info.context.get("user_id")
author_dict = info.context.get("author", {}) author_dict = info.context.get("author", {})
author_id = author_dict.get("id") author_id = author_dict.get("id")
if not user_id or not author_id:
return {"error": "User ID and author ID are required"}
now = int(time.time()) if not user_id or not author_id:
return {"error": "Author ID is required"}
try: try:
with local_session() as session: with local_session() as session:
# Сначала находим черновик # Загружаем черновик со всеми связями
draft = session.query(Draft).filter(Draft.id == draft_id).first() draft = (
session.query(Draft)
.options(
joinedload(Draft.topics),
joinedload(Draft.authors),
joinedload(Draft.publication)
)
.filter(Draft.id == draft_id)
.first()
)
if not draft: if not draft:
return {"error": "Draft not found"} return {"error": "Draft not found"}
@ -412,49 +417,35 @@ async def publish_draft(_, info, draft_id: int):
if not is_valid: if not is_valid:
return {"error": f"Cannot publish draft: {error}"} return {"error": f"Cannot publish draft: {error}"}
# Ищем существующий shout для этого черновика # Проверяем, есть ли уже публикация для этого черновика
shout = session.query(Shout).filter(Shout.draft == draft_id).first() if draft.publication:
was_published = shout.published_at if shout else None shout = draft.publication
if not shout:
# Создаем новый shout если не существует
shout = create_shout_from_draft(session, draft, author_id)
shout.published_at = now
else:
# Обновляем существующую публикацию # Обновляем существующую публикацию
shout.draft = draft.id for field in ["body", "title", "subtitle", "lead", "cover", "cover_caption", "media", "lang", "seo"]:
shout.created_by = author_id if hasattr(draft, field):
shout.title = draft.title setattr(shout, field, getattr(draft, field))
shout.subtitle = draft.subtitle shout.updated_at = int(time.time())
shout.body = draft.body shout.updated_by = author_id
shout.cover = draft.cover else:
shout.cover_caption = draft.cover_caption # Создаем новую публикацию
shout.lead = draft.lead shout = create_shout_from_draft(session, draft, author_id)
shout.layout = draft.layout now = int(time.time())
shout.media = draft.media shout.created_at = now
shout.lang = draft.lang
shout.seo = draft.seo
shout.updated_at = now
# Устанавливаем published_at только если была ранее снята с публикации
if not was_published:
shout.published_at = now shout.published_at = now
# Сохраняем shout перед созданием связей
session.add(shout) session.add(shout)
session.flush() session.flush() # Получаем ID нового шаута
# Очищаем существующие связи # Очищаем существующие связи
session.query(ShoutAuthor).filter(ShoutAuthor.shout == shout.id).delete() session.query(ShoutAuthor).filter(ShoutAuthor.shout == shout.id).delete()
session.query(ShoutTopic).filter(ShoutTopic.shout == shout.id).delete() session.query(ShoutTopic).filter(ShoutTopic.shout == shout.id).delete()
# Добавляем автора # Добавляем авторов
sa = ShoutAuthor(shout=shout.id, author=author_id) for author in (draft.authors or []):
sa = ShoutAuthor(shout=shout.id, author=author.id)
session.add(sa) session.add(sa)
# Добавляем темы если есть # Добавляем темы
if draft.topics: for topic in (draft.topics or []):
for topic in draft.topics:
st = ShoutTopic( st = ShoutTopic(
topic=topic.id, topic=topic.id,
shout=shout.id, shout=shout.id,
@ -462,80 +453,23 @@ async def publish_draft(_, info, draft_id: int):
) )
session.add(st) session.add(st)
# Загружаем темы для шаута после создания связей
shout.topics = [
session.query(Topic).filter(Topic.id == topic.id).first()
for topic in draft.topics
]
else:
# Инициализируем пустой список тем если их нет
shout.topics = []
# Обновляем черновик
draft.updated_at = now
session.add(draft)
# Инвалидируем кэш только если это новая публикация или была снята с публикации
if not was_published:
cache_keys = ["feed", f"author_{author_id}", "random_top", "unrated"]
# Добавляем ключи для тем
for topic in shout.topics:
cache_keys.append(f"topic_{topic.id}")
cache_keys.append(f"topic_shouts_{topic.id}")
await cache_by_id(Topic, topic.id, cache_topic)
# Инвалидируем кэш
await invalidate_shouts_cache(cache_keys)
await invalidate_shout_related_cache(shout, author_id)
# Обновляем кэш авторов
for author in shout.authors:
await cache_by_id(Author, author.id, cache_author)
# Отправляем уведомление о публикации
await notify_shout(shout.dict(), "published")
# Обновляем поисковый индекс
search_service.index(shout)
else:
# Для уже опубликованных материалов просто отправляем уведомление об обновлении
await notify_shout(shout.dict(), "update")
try:
# Фиксируем изменения
session.commit() session.commit()
# После коммита преобразуем в словари для ответа # Инвалидируем кеш
try: invalidate_shouts_cache()
# Важно: для GraphQL схемы возвращаем как shout, так и draft invalidate_shout_related_cache(shout.id)
# (поскольку в CommonResult определены оба поля)
shout_dict = shout.dict()
draft_dict = draft.dict()
# Логирование для отладки # Уведомляем о публикации
logger.info(f"Successfully published shout #{shout.id} from draft #{draft.id}") await notify_shout(shout.id)
logger.debug(f"Shout data: {shout_dict}")
# Важно: возвращаем draft для CommonResult.draft и shout для CommonResult.shout # Обновляем поисковый индекс
return { search_service.index_shout(shout)
"shout": shout_dict,
"draft": draft_dict, logger.info(f"Successfully published shout #{shout.id} from draft #{draft_id}")
"error": None logger.debug(f"Shout data: {shout.dict()}")
}
except Exception as serialize_error: return {"shout": shout}
# Если случилась ошибка при сериализации
logger.error(f"Error serializing result: {serialize_error}", exc_info=True)
return {"error": f"Published successfully but failed to return result: {str(serialize_error)}"}
except Exception as commit_error:
# Ошибка при коммите
session.rollback()
logger.error(f"Commit error: {commit_error}", exc_info=True)
return {"error": f"Failed to save changes: {str(commit_error)}"}
except Exception as e: except Exception as e:
# Общая ошибка обработки logger.error(f"Failed to publish draft {draft_id}: {e}", exc_info=True)
logger.error(f"Failed to publish shout: {e}", exc_info=True) return {"error": f"Failed to publish draft: {str(e)}"}
if "session" in locals():
session.rollback()
return {"error": f"Failed to publish shout: {str(e)}"}