This commit is contained in:
parent
51de649686
commit
a6b3b21894
30
orm/draft.py
30
orm/draft.py
|
@ -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 [])]
|
||||||
|
}
|
|
@ -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,130 +417,59 @@ 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.published_at = now
|
||||||
shout.seo = draft.seo
|
session.add(shout)
|
||||||
shout.updated_at = now
|
session.flush() # Получаем ID нового шаута
|
||||||
|
|
||||||
# Устанавливаем published_at только если была ранее снята с публикации
|
|
||||||
if not was_published:
|
|
||||||
shout.published_at = now
|
|
||||||
|
|
||||||
# Сохраняем shout перед созданием связей
|
|
||||||
session.add(shout)
|
|
||||||
session.flush()
|
|
||||||
|
|
||||||
# Очищаем существующие связи
|
# Очищаем существующие связи
|
||||||
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 []):
|
||||||
session.add(sa)
|
sa = ShoutAuthor(shout=shout.id, author=author.id)
|
||||||
|
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,
|
main=topic.main if hasattr(topic, "main") else False
|
||||||
main=topic.main if hasattr(topic, "main") else False
|
)
|
||||||
)
|
session.add(st)
|
||||||
session.add(st)
|
|
||||||
|
|
||||||
# Загружаем темы для шаута после создания связей
|
session.commit()
|
||||||
shout.topics = [
|
|
||||||
session.query(Topic).filter(Topic.id == topic.id).first()
|
|
||||||
for topic in draft.topics
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# Инициализируем пустой список тем если их нет
|
|
||||||
shout.topics = []
|
|
||||||
|
|
||||||
# Обновляем черновик
|
# Инвалидируем кеш
|
||||||
draft.updated_at = now
|
invalidate_shouts_cache()
|
||||||
session.add(draft)
|
invalidate_shout_related_cache(shout.id)
|
||||||
|
|
||||||
# Инвалидируем кэш только если это новая публикация или была снята с публикации
|
# Уведомляем о публикации
|
||||||
if not was_published:
|
await notify_shout(shout.id)
|
||||||
cache_keys = ["feed", f"author_{author_id}", "random_top", "unrated"]
|
|
||||||
|
|
||||||
# Добавляем ключи для тем
|
# Обновляем поисковый индекс
|
||||||
for topic in shout.topics:
|
search_service.index_shout(shout)
|
||||||
cache_keys.append(f"topic_{topic.id}")
|
|
||||||
cache_keys.append(f"topic_shouts_{topic.id}")
|
|
||||||
await cache_by_id(Topic, topic.id, cache_topic)
|
|
||||||
|
|
||||||
# Инвалидируем кэш
|
logger.info(f"Successfully published shout #{shout.id} from draft #{draft_id}")
|
||||||
await invalidate_shouts_cache(cache_keys)
|
logger.debug(f"Shout data: {shout.dict()}")
|
||||||
await invalidate_shout_related_cache(shout, author_id)
|
|
||||||
|
|
||||||
# Обновляем кэш авторов
|
return {"shout": shout}
|
||||||
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()
|
|
||||||
|
|
||||||
# После коммита преобразуем в словари для ответа
|
|
||||||
try:
|
|
||||||
# Важно: для GraphQL схемы возвращаем как shout, так и draft
|
|
||||||
# (поскольку в CommonResult определены оба поля)
|
|
||||||
shout_dict = shout.dict()
|
|
||||||
draft_dict = draft.dict()
|
|
||||||
|
|
||||||
# Логирование для отладки
|
|
||||||
logger.info(f"Successfully published shout #{shout.id} from draft #{draft.id}")
|
|
||||||
logger.debug(f"Shout data: {shout_dict}")
|
|
||||||
|
|
||||||
# Важно: возвращаем draft для CommonResult.draft и shout для CommonResult.shout
|
|
||||||
return {
|
|
||||||
"shout": shout_dict,
|
|
||||||
"draft": draft_dict,
|
|
||||||
"error": None
|
|
||||||
}
|
|
||||||
except Exception as serialize_error:
|
|
||||||
# Если случилась ошибка при сериализации
|
|
||||||
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)}"}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user