avtopost.kultmost.ru/tasks.ipynb
2026-09-01 23:23:50 -04:00

978 lines
41 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "4563b7d7",
"metadata": {},
"outputs": [],
"source": [
"# ignore\n",
"import os\n",
"\n",
"def load_env(file_path):\n",
" \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n",
" with open(file_path) as f:\n",
" for line in f:\n",
" line = line.strip()\n",
" if not line or line.startswith('#'):\n",
" continue\n",
" \n",
" try:\n",
" key, value = line.split('=', 1)\n",
" key = key.strip()\n",
" value = value.strip('\"\\' ')\n",
" # Преобразование строковых значений в булевые\n",
" if value.lower() in ['true', '1', 'yes']:\n",
" value = True\n",
" elif value.lower() in ['false', '0', 'no']:\n",
" value = False\n",
" \n",
" # Проверка, что ключ не пустой\n",
" if key:\n",
" os.environ[key] = str(value) # Храним как строку\n",
" except ValueError:\n",
" print(f\"Ошибка: неверный формат в строке '{line}'\")\n",
"\n",
"try:\n",
" load_env('env')\n",
"except:\n",
" load_env('env_example')"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "b66cc5eb",
"metadata": {},
"outputs": [],
"source": [
"from config import *\n",
"from db import *\n",
"from typing import Dict, Any, List, Optional, Callable, Union\n",
"import email_task as email_task\n",
"import vk as vk\n",
"import tg as tg\n",
"from utils import *\n",
"from models import NotifyItem\n",
"from auth import token_create\n",
"import asyncio\n"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "019d30b5",
"metadata": {},
"outputs": [],
"source": [
"async def notify_in_profile(item: NotifyItem):\n",
" return await notify_collection.update_one(\n",
" {\n",
" \"user_id\": item.user_id,\n",
" \"type\": item.type_item,\n",
" \"title\": item.title,\n",
" \"link\": item.link,\n",
" \"text\": item.text,\n",
" },\n",
" {\n",
" \"$set\": {\n",
" \"user_id\": item.user_id,\n",
" \"type\": item.type_item,\n",
" \"title\": item.title,\n",
" \"text\": item.text,\n",
" \"link\": item.link,\n",
" \"read\": True,\n",
" \"prod\": PROD,\n",
" }\n",
" },\n",
" upsert=True,\n",
" )\n"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "f26f18e9",
"metadata": {},
"outputs": [],
"source": [
"async def email_new_post(new_post: Dict):\n",
" async for user in users_collection.find({'roles':'partner'}):\n",
" login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/paintings/{new_post['_id']}')}'\n",
" if PROD:\n",
" email_task.notify.delay(user['email'], {\n",
" 'text':'На сервисе опубликована новая картина',\n",
" 'list':[[\n",
" {'label':'Название','text':new_post['title'], 'img':f\"{FULL_DOMAIN}/b/posts/{new_post['medias_original'][0]}\"},\n",
" {'label':'Звание художника','text':new_post['grade']},\n",
" {'label':'Город','text':new_post['city']},\n",
" {'label':'Размер','text':new_post['size']},\n",
" {'label':'Материал','text':new_post['material']},\n",
" {'label':'Жанр','text':new_post['genre']},\n",
" {'label':'Цена','text':new_post['price']},\n",
" ]],\n",
" 'primary': {'href':login_link,'text':'Войти и посмотреть'},\n",
" 'title': f\"от художника {new_post['author']}\",\n",
" 'login_link': login_link,\n",
" },\n",
" 'Новая картина', 'main.html')\n"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "cbff5107",
"metadata": {},
"outputs": [],
"source": [
"async def update_social_post(post):\n",
" pass"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "e09b70e2",
"metadata": {},
"outputs": [],
"source": [
"async def publish_social_post(post):\n",
" query = {\n",
" 'active': True,\n",
" \"$or\": [{'service': True}, {'user_id': post['user']['_id']}]\n",
" }\n",
" post_ref = {'_id': post['_id'], 'db': 'posts', 'col': 'posts'}\n",
" medias = post.get(\"medias_original\", [])\n",
"\n",
" platforms = [\n",
" ('tg', tg, lambda s: [\n",
" f\"{FULL_PATH_DOMAIN}/a/{post['user']['_id']}\" if should_sign_avatar(post, s) else None,\n",
" *[f\"{FULL_PATH_DOMAIN}/b/posts/{m}\" for m in medias]\n",
" ]),\n",
" ('vk', vk, lambda s: [\n",
" post['user']['_id'] if should_sign_avatar(post, s) else None,\n",
" *medias\n",
" ])\n",
" ]\n",
"\n",
" for platform_type, client, get_attachments in platforms:\n",
" if post.get(platform_type) != []:\n",
" continue\n",
" socials = [doc async for doc in social_collection.find({**query, 'type': platform_type})]\n",
" socials = {item[\"id\"]: item for item in socials}.values()\n",
" for social in socials:\n",
" text = parse_posts_text(post, platform_type, social.get('service', False))\n",
" sign_avatar = should_sign_avatar(post, social)\n",
" attachments = [a for a in get_attachments(social) if a is not None]\n",
" \n",
" client.notify.delay(post_ref, text, attachments, bson_clear(social), sign_avatar)"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "846846ca",
"metadata": {},
"outputs": [],
"source": [
"async def user_social_competition(competition):\n",
" async for user in users_collection.find({'roles':'admin'}):\n",
" # email_task.notify.delay\n",
" if PROD:\n",
" pass\n",
" else:\n",
" print(competition)"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "829dfa3d",
"metadata": {},
"outputs": [],
"source": [
"class SocialContentHandler:\n",
" \"\"\"Base class for content type specific handlers\"\"\"\n",
" \n",
" def get_post_ref(self, item: Dict[str, Any]) -> Dict[str, Any]:\n",
" \"\"\"Return reference for the content item\"\"\"\n",
" raise NotImplementedError\n",
" \n",
" def get_medias(self, item: Dict[str, Any]) -> List[Optional[str]]:\n",
" \"\"\"Return list of media file_ids\"\"\"\n",
" raise NotImplementedError\n",
" \n",
" def get_text(self, item: Dict[str, Any]) -> str:\n",
" \"\"\"Parse text for the content\"\"\"\n",
" raise NotImplementedError\n",
"\n",
"\n",
"class CompetitionHandler(SocialContentHandler):\n",
" def get_post_ref(self, item: Dict[str, Any]) -> Dict[str, Any]:\n",
" return {'_id': item['_id'], 'db': 'competitions', 'col': 'competitions'}\n",
" \n",
" def get_medias(self, item: Dict[str, Any]) -> List[Optional[str]]:\n",
" return [item.get(\"poster_file_id\")]\n",
" \n",
" def get_text(self, item: Dict[str, Any]) -> str:\n",
" return parse_text_competition(item)\n",
"\n",
"\n",
"class PostHandler(SocialContentHandler):\n",
" def get_post_ref(self, item: Dict[str, Any]) -> Dict[str, Any]:\n",
" return {'_id': item['_id'], 'db': 'posts', 'col': 'posts'}\n",
" \n",
" def get_medias(self, item: Dict[str, Any]) -> List[Optional[str]]:\n",
" # Adjust according to your Post schema\n",
" return item.get(\"media_file_ids\", []) or [item.get(\"main_image\")]\n",
" \n",
" def get_text(self, item: Dict[str, Any]) -> str:\n",
" return item.get(\"text\", \"\")\n",
"\n",
"\n",
"class NewsHandler(SocialContentHandler):\n",
" def get_post_ref(self, item: Dict[str, Any]) -> Dict[str, Any]:\n",
" return {'_id': item['_id'], 'db': 'news', 'col': 'news'}\n",
" \n",
" def get_medias(self, item: Dict[str, Any]) -> List[Optional[str]]:\n",
" return item.get(\"media_ids\", [])\n",
" \n",
" def get_text(self, item: Dict[str, Any]) -> str:\n",
" return item.get(\"title\", \"\") + \"\\n\\n\" + item.get(\"content\", \"\")\n",
"\n",
"\n",
"# Registry for handlers\n",
"CONTENT_HANDLERS: Dict[str, SocialContentHandler] = {\n",
" \"competition\": CompetitionHandler(),\n",
" \"post\": PostHandler(),\n",
" \"news\": NewsHandler(),\n",
" # Add more: \"user\": UserProfileHandler(), etc.\n",
"}\n",
"\n",
"\n",
"class SocialPlatform:\n",
" \"\"\"Platform configuration\"\"\"\n",
" def __init__(\n",
" self,\n",
" type_name: str,\n",
" client: Any,\n",
" get_attachments: Callable[[Dict], List[str]],\n",
" sign_avatar_func: Optional[Callable] = None\n",
" ):\n",
" self.type = type_name\n",
" self.client = client\n",
" self.get_attachments = get_attachments\n",
" self.sign_avatar_func = sign_avatar_func or should_sign_avatar\n",
"\n",
"\n",
"# Platform registry - easy to extend\n",
"PLATFORMS: Dict[str, SocialPlatform] = {\n",
" \"tg\": SocialPlatform(\n",
" type_name=\"tg\",\n",
" client=tg,\n",
" get_attachments=lambda social, medias: [\n",
" f\"{FULL_PATH_DOMAIN}/a/{social.get('user_id')}\" if should_sign_avatar(social) else None,\n",
" *[f\"{FULL_PATH_DOMAIN}/b/competition/{m}\" for m in medias if m] # generalize path\n",
" ]\n",
" ),\n",
" \"vk\": SocialPlatform(\n",
" type_name=\"vk\",\n",
" client=vk,\n",
" get_attachments=lambda social, medias: [\n",
" social.get('user_id') if should_sign_avatar(social) else None,\n",
" *medias\n",
" ]\n",
" ),\n",
" # Add more platforms easily:\n",
" # \"ok\": SocialPlatform(...),\n",
" # \"telegram_channel\": ...\n",
"}\n",
"\n",
"\n",
"async def get_socials_for_content(\n",
" content_type: str,\n",
" item: Dict[str, Any],\n",
" service_social: bool = False,\n",
" user_social: bool = False,\n",
" users_social: bool = False\n",
") -> List[Dict]:\n",
" \"\"\"Get relevant social accounts based on rules\"\"\"\n",
" query: Dict = {'active': True, \"$or\": []}\n",
" \n",
" if service_social:\n",
" query[\"$or\"].append({'service': True})\n",
" if user_social:\n",
" query[\"$or\"].append({'user_id': item.get('user', {}).get('_id')})\n",
" if users_social:\n",
" query[\"$or\"].extend([\n",
" {\"competition\": True},\n",
" {\"competition\": {\"$exists\": False}}\n",
" ])\n",
" \n",
" # Filter by platform types that exist in registry\n",
" platform_types = list(PLATFORMS.keys())\n",
" cursor = social_collection.find({**query, 'type': {'$in': platform_types}})\n",
" socials = [doc async for doc in cursor]\n",
" \n",
" # Deduplicate by id\n",
" return list({item[\"id\"]: item for item in socials}.values())\n",
"\n",
"\n",
"async def send_to_social(\n",
" content_type: str,\n",
" item: Dict[str, Any],\n",
" social: Dict[str, Any],\n",
" platform: SocialPlatform\n",
") -> bool:\n",
" \"\"\"Send one item to one social account with spam protection\"\"\"\n",
" handler = CONTENT_HANDLERS.get(content_type)\n",
" if not handler:\n",
" print(f\"Unknown content type: {content_type}\")\n",
" return False\n",
"\n",
" redis_key = f\"nospamsends:{content_type}:{item['_id']}:{social['_id']}\"\n",
" \n",
" already_sent = await redis_client.set(\n",
" redis_key,\n",
" \"1\",\n",
" ex=8 * 60 * 60, # 8 hours\n",
" nx=True\n",
" )\n",
" \n",
" if not already_sent:\n",
" return False # already sent recently\n",
"\n",
" post_ref = handler.get_post_ref(item)\n",
" medias = handler.get_medias(item)\n",
" text = handler.get_text(item)\n",
" \n",
" sign_avatar = should_sign_avatar(item, social) if hasattr(handler, 'should_sign') else should_sign_avatar(item, social)\n",
" \n",
" attachments = [a for a in platform.get_attachments(social, medias) if a is not None]\n",
" \n",
" try:\n",
" platform.client.notify.delay(\n",
" post_ref,\n",
" text,\n",
" attachments,\n",
" bson_clear(social),\n",
" sign_avatar\n",
" )\n",
" print(f\"Queued {content_type} {item['_id']} to {platform.type} social {social['_id']}\")\n",
" return True\n",
" except Exception as e:\n",
" print(f\"Failed to queue to {platform.type}: {e}\")\n",
" return False\n",
"\n",
"\n",
"async def service_social_publish(\n",
" content_type: str,\n",
" item: Dict[str, Any],\n",
" service_social: bool = False,\n",
" user_social: bool = False,\n",
" users_social: bool = False\n",
"):\n",
" \"\"\"\n",
" Generic social publishing service.\n",
" \n",
" Usage:\n",
" await service_social_publish(\"competition\", competition_dict)\n",
" await service_social_publish(\"post\", post_dict)\n",
" \"\"\"\n",
" if content_type not in CONTENT_HANDLERS:\n",
" raise ValueError(f\"Unsupported content type: {content_type}\")\n",
" \n",
" socials = await get_socials_for_content(\n",
" content_type, item, service_social, user_social, users_social\n",
" )\n",
" \n",
" tasks = []\n",
" for social in socials:\n",
" platform_key = social.get(\"type\")\n",
" platform = PLATFORMS.get(platform_key)\n",
" if not platform:\n",
" continue\n",
" \n",
" tasks.append(send_to_social(content_type, item, social, platform))\n",
" \n",
" if tasks:\n",
" await asyncio.gather(*tasks, return_exceptions=True)\n",
"\n",
"\n",
"# # Convenience wrappers\n",
"# async def service_social_competition(competition: Dict, **kwargs):\n",
"# return await service_social_publish(\"competition\", competition, **kwargs)\n",
"\n",
"\n",
"# async def service_social_post(post: Dict, **kwargs):\n",
"# return await service_social_publish(\"post\", post, **kwargs)\n",
"\n",
"\n",
"# async def service_social_news(news: Dict, **kwargs):\n",
"# return await service_social_publish(\"news\", news, **kwargs)"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b68a608",
"metadata": {},
"outputs": [],
"source": [
"async def service_social_exhibition(exhibition):\n",
" query = { 'active': True, \"$or\": []}\n",
" query[\"$or\"].append({'service': True})\n",
" query[\"$or\"].append({'user_id': exhibition['user']['_id']})\n",
"\n",
" post_ref = {'_id': str(exhibition['_id']), 'db': 'exhibition', 'col': 'exhibition'}\n",
" medias = [exhibition.get(\"poster_orig_id\")]\n",
"\n",
" platforms = [\n",
" ('tg', tg, lambda s: [\n",
" # f\"{FULL_PATH_DOMAIN}/a/{exhibition['user']['_id']}\" if should_sign_avatar(exhibition, s) else None,\n",
" *[f\"{FULL_PATH_DOMAIN}/b/exhibition/{m}\" for m in medias]\n",
" ]),\n",
" ('vk', vk, lambda s: [\n",
" # exhibition['user']['_id'] if should_sign_avatar(exhibition, s) else None,\n",
" *medias\n",
" ])\n",
" ]\n",
"\n",
" for platform_type, client, get_attachments in platforms:\n",
" if exhibition.get(platform_type) != []:\n",
" continue\n",
" socials = [doc async for doc in social_collection.find({**query, 'type': platform_type})]\n",
" socials = {item[\"id\"]: item for item in socials}.values()\n",
" for social in socials:\n",
" text = parse_text_exhibition(exhibition)\n",
" sign_avatar = should_sign_avatar(exhibition, social)\n",
" attachments = [a for a in get_attachments(social) if a is not None]\n",
" redis_key = f\"nospamsends:{platform_type}:{exhibition['_id']}:{social['_id']}\"\n",
" already_sent = await redis_client.set(\n",
" redis_key, \n",
" 1, \n",
" ex=8*60*60,\n",
" nx=True\n",
" )\n",
" print(f\"nospamsends:{platform_type}:{exhibition['_id']}:{social['_id']}\")\n",
" if already_sent:\n",
" print(social)\n",
" client.notify.delay(post_ref, text, attachments, bson_clear(social), False)"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "e19b21ec",
"metadata": {},
"outputs": [],
"source": [
"async def service_social_competition(competition, serviceSocial, userSocial, usersSocial):\n",
" query = { 'active': True, \"$or\": []}\n",
" if serviceSocial:\n",
" query[\"$or\"].append({'service': True})\n",
"\n",
" if userSocial:\n",
" query[\"$or\"].append({'user_id': competition['user']['_id']})\n",
"\n",
" if usersSocial:\n",
" query[\"$or\"].append({\"competition\": True})\n",
" query[\"$or\"].append({\"competition\": {\"$exists\": False}})\n",
" post_ref = {'_id': str(competition['_id']), 'db': 'competitions', 'col': 'competitions'}\n",
" medias = [competition.get(\"poster_file_id\")]\n",
"\n",
" platforms = [\n",
" ('tg', tg, lambda s: [\n",
" # f\"{FULL_PATH_DOMAIN}/a/{competition['user']['_id']}\" if should_sign_avatar(competition, s) else None,\n",
" *[f\"{FULL_PATH_DOMAIN}/b/competition/{m}\" for m in medias]\n",
" ]),\n",
" ('vk', vk, lambda s: [\n",
" # competition['user']['_id'] if should_sign_avatar(competition, s) else None,\n",
" *medias\n",
" ])\n",
" ]\n",
"\n",
" for platform_type, client, get_attachments in platforms:\n",
" \n",
" socials = [doc async for doc in social_collection.find({**query, 'type': platform_type})]\n",
" socials = {item[\"id\"]: item for item in socials}.values()\n",
" for social in socials:\n",
" text = parse_text_competition(competition)\n",
" sign_avatar = should_sign_avatar(competition, social)\n",
" attachments = [a for a in get_attachments(social) if a is not None]\n",
" redis_key = f\"nospamsends:{platform_type}:{competition['_id']}:{social['_id']}\"\n",
" already_sent = await redis_client.set(\n",
" redis_key, \n",
" 1, \n",
" ex=8*60*60,\n",
" nx=True\n",
" )\n",
" print(f\"nospamsends:{platform_type}:{competition['_id']}:{social['_id']}\")\n",
" if already_sent:\n",
" print(social)\n",
" client.notify.delay(post_ref, text, attachments, bson_clear(social), False)"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "031c0d4a",
"metadata": {},
"outputs": [],
"source": [
"async def email_new_user(new_user):\n",
" async for user in users_collection.find({'roles':'admin'}):\n",
" login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/artists/{new_user['_id']}')}'\n",
" role = {'user':'Художник','galerist':'Галерея',\"reporter\":'Репортер'}[new_user['roles'][0]]\n",
" if PROD:\n",
" email_task.notify.delay(user['email'], {\n",
" 'list':[[\n",
" {'label':'ФИО','text': new_user['female_name_lastname'], 'img':f\"{FULL_DOMAIN}/a/{new_user['_id']}\"},\n",
" {'label':'Звание','text': new_user['grade']} if new_user['roles'][0] != 'galerist' else {},\n",
" {'label':'Город проживания','text': new_user['living_place']}\n",
" ]],\n",
" 'text': f'',\n",
" 'primary': {'href':login_link,'text':'Войти и посмотреть'},\n",
" 'title': f\"{role} {new_user['username']}\",\n",
" 'login_link': login_link\n",
" },\n",
" 'Новый пользователь', 'main.html')"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "1d03d7e1",
"metadata": {},
"outputs": [],
"source": [
"async def invite_users_to_competitions(doc):\n",
" new_notify = []\n",
"\n",
" query = {\n",
" \"roles\": {\"$in\": [\"user\"]},\n",
" \"$or\": [\n",
" {\"email_subscribed\": True},\n",
" {\"email_subscribed\": {\"$exists\": False}}\n",
" ]\n",
" }\n",
"\n",
" async for user in users_collection.find(query):\n",
" if not user.get(\"email\"):\n",
" continue\n",
"\n",
" redis_key = f\"nospam:{user['email']}:{doc['_id']}\"\n",
"\n",
" already_sent = await redis_client.set(\n",
" redis_key, \n",
" 1, \n",
" ex=24*60*60,\n",
" nx=True\n",
" )\n",
"\n",
" if already_sent:\n",
" new_notify.append(\n",
" notify_in_profile(NotifyItem(\n",
" user_id=str(user['_id']),\n",
" type_item='competition',\n",
" title='Приглашение на конкурс',\n",
" link=f'/profile/competition/{doc.get(\"id\") or doc.get(\"_id\")}',\n",
" text=f'«{doc['title']}» Организатор {doc['user']['username']}'\n",
" ))\n",
" )\n",
" if PROD:\n",
" email_task.notify.delay(user['email'], {'competition': bson_clear(doc), 'login_link': f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/competition/{doc.get(\"id\") or doc.get(\"_id\")}')}'}, 'Конкурс', 'competition.html')\n",
" await asyncio.gather(*new_notify)"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e22dab5",
"metadata": {},
"outputs": [],
"source": [
"async def accept_competition_req(competition, user_id):\n",
" user = await users_collection.find_one({'_id': ObjectId(user_id)},{'email':1})\n",
" login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=f'/profile/invitation')}'\n",
" if PROD:\n",
" email_task.notify.delay(user['email'], {\n",
" 'text':f'на конкурсе {competition['title']}',\n",
" 'list':[[ ]],\n",
" 'primary': {'href':login_link,'text':'Войти и посмотреть'},\n",
" 'title': f\"Вы прошли отбор\",\n",
" 'login_link': login_link,\n",
" },\n",
" 'Приглашение', 'main.html')\n",
" await notify_in_profile(NotifyItem(user_id=user_id, type_item='invation', title='Приглашение', link='/profile/invitation', text=f'Вы прошли отбор на конкурсе {competition['title']}'))\n"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "b25adab4",
"metadata": {},
"outputs": [],
"source": [
"async def get_sender(post_id):\n",
" delivery_cursor = delivery_collection.find(\n",
" {\n",
" 'post_id': post_id,\"prod\": PROD,\n",
" 'status': { '$nin': [\"Ожидает отправки\", \"Ожидает принятия условий\", \"Третье\"] }\n",
" },\n",
" {'receiver': 1}\n",
" ).sort('_id', 1) # 1 = ascending, -1 = descending (newest first)\n",
"\n",
" delivery = await delivery_cursor.limit(1).to_list(length=1)\n",
" delivery = delivery[0] if delivery else {}\n",
" return delivery.get('receiver')"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "3c8e1473",
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"from bson import ObjectId\n",
"\n",
"async def accept_invitations(invitations_id: str, posts_ids: List[str]):\n",
" invitation = await invitations_collection.find_one_and_update(\n",
" {\"_id\": ObjectId(invitations_id)},\n",
" {\"$set\": {\"accept\": True}},\n",
" return_document=True,\n",
" )\n",
"\n",
" invitation_posts = invitation.get('posts', []) or []\n",
" invitation_post_ids = [str(doc['_id']) for doc in invitation_posts]\n",
"\n",
" selected_set = set(str(x) for x in posts_ids)\n",
" selected_ids = [pid for pid in invitation_post_ids if pid in selected_set]\n",
" other_ids = [pid for pid in invitation_post_ids if pid not in selected_set]\n",
"\n",
" if selected_ids:\n",
" await delivery_collection.update_many(\n",
" {'post_id': {'$in': selected_ids}},\n",
" {'$set': {'status': 'Ожидает отправки'}}\n",
" )\n",
"\n",
" if other_ids:\n",
" await delivery_collection.update_many(\n",
" {'post_id': {'$in': other_ids}},\n",
" {'$set': {'status': 'Приглашение откланено'}} # как вы написали\n",
" )\n",
"\n",
" await notify_in_profile(NotifyItem(\n",
" user_id=invitation.get('exhibition',{}).get(\"user\",{}).get('_id'),\n",
" type_item='c',\n",
" title=f'Выставка \"{invitation.get('exhibition',{}).get(\"title\")}\"',\n",
" link=f'/profile/delivery',\n",
" text=f'Художник принял условия'\n",
" ))"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc239a3e",
"metadata": {},
"outputs": [],
"source": [
"async def exhibition_send_invitations(exhibition_id):\n",
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n",
" exhibition_owner = exhibition.get('user')\n",
" obj_ids = [ObjectId(x) for x in exhibition.get('posts', [])]\n",
" posts = await posts_collection.find({\"_id\": {\"$in\": obj_ids}, 'status': 'В каталоге'}).to_list() if obj_ids else []\n",
" new_posts = {}\n",
" all_posts = {}\n",
" for post in posts:\n",
" post_id = str(post.get('_id'))\n",
" user = display_user(post.get('user'))\n",
" doc = await delivery_collection.update_one(\n",
" {\n",
" \"exhibition_id\": str(exhibition.get('_id')),\"post_id\": post_id, \"status\": \"Ожидает принятия условий\", \"type\": \"exhibition\",\n",
" },{\n",
" \"$set\":{\n",
" \"sender\": user,\n",
" \"user\": user,\n",
" \"receiver\": display_user(exhibition_owner),\n",
" \"exhibition_id\": str(exhibition.get('_id')),\n",
" \"address\": exhibition.get('address'),\n",
" \"post_id\": post_id, \n",
" \"prod\": PROD,\n",
" \"img\": post.get('medias',[0])[0],\n",
" \"title\": post.get('title'),\n",
" \"status\": \"Ожидает принятия условий\",\n",
" \"type\": \"exhibition\",\n",
" \"updated_at\": datetime.utcnow()\n",
" }\n",
" }, upsert=True)\n",
" if doc.upserted_id:\n",
" if (user_id:=user.get('_id')) in new_posts:\n",
" new_posts[user_id].append(post)\n",
" else:\n",
" new_posts.update({user_id: [post]})\n",
" if (user_id:=user.get('_id')) in all_posts:\n",
" all_posts[user_id].append(post)\n",
" else:\n",
" all_posts.update({user_id: [post]})\n",
" print(all_posts)\n",
" if all_posts:\n",
" for user_id, posts in all_posts.items():\n",
" doc = await invitations_collection.update_one(\n",
" {'exhibition_id': str(exhibition.get('_id')),'user_id': user_id,'type': 'Выставка'},\n",
" \n",
" {\"$set\":{'exhibition': bson_clear(exhibition), \n",
" 'exhibition_id': str(exhibition.get('_id')),\n",
" 'accept': False, \n",
" 'user':display_user(exhibition_owner), \n",
" 'user_id': user_id, \n",
" 'posts': posts,\n",
" 'prod': PROD, \n",
" 'type': 'Выставка', \n",
" 'item': exhibition.get('title')}},\n",
" upsert=True\n",
" )\n",
" if new_posts:\n",
" for user_id, posts in new_posts.items():\n",
" await notify_in_profile(NotifyItem(\n",
" user_id=user_id,\n",
" type_item='invitation',\n",
" title='Приглашение',\n",
" link=f'/profile/invitation',\n",
" text=f'Приглашаем на выставку \"{exhibition.get(\"title\")}\"'\n",
" ))\n",
" \n",
" user = posts[0].get('user')\n",
" token = await token_create(\n",
" str(user['_id']), \n",
" url=f'/profile/invitation#main'\n",
" )\n",
" login_link = f'{FULL_DOMAIN}/login/{token}'\n",
" email_data = {\n",
" 'text': 'Приглашаем на выставку со следующими картинами: ',\n",
" 'list': [\n",
" [\n",
" {'label': 'Название', 'text': item.get('title', ''), 'img':f\"{FULL_DOMAIN}/b/posts/{item['medias_original'][0]}\"},\n",
" ]\n",
" for item in posts\n",
" ],\n",
" 'primary': {\n",
" 'href': login_link,\n",
" 'text': 'Войти и посмотреть'\n",
" },\n",
" 'title': exhibition.get('title', 'Выставка'),\n",
" 'login_link': login_link,\n",
" }\n",
" if PROD:\n",
" email_task.notify.delay(\n",
" user['email'],\n",
" email_data,\n",
" f'Приглашение на выставку {exhibition.get('title')}',\n",
" 'main.html'\n",
" )\n",
" \n"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "5db0ae46",
"metadata": {},
"outputs": [],
"source": [
"async def update_competition_req(competition, user, posts):\n",
" competition_id = competition.get('_id')\n",
"\n",
" if not user or not posts or not competition_id:\n",
" return\n",
"\n",
" posts_sorted = sorted(posts, key=lambda p: p.get('status') != 'Проходит')\n",
"\n",
" # Генерация login link\n",
" token = await token_create(\n",
" str(user['_id']), \n",
" url=f'/profile/invitation#main'\n",
" )\n",
" login_link = f'{FULL_DOMAIN}/login/{token}'\n",
"\n",
" if True:\n",
" competition_owner = competition.get('user', {})\n",
"\n",
" # === Создание доставок ===\n",
" \n",
" post_ids_to_mark = [] # для последующего обновления delivery: True\n",
" post_ids_to_remove_delivery = []\n",
" for post in posts:\n",
" post_id = str(post.get('_id'))\n",
" if post.get('status') == 'Проходит':\n",
" sender = await get_sender(post_id) or display_user(user)\n",
" delivery_collection.update_one({\n",
" \"competition_id\": competition_id,\"post_id\": post_id, \"status\": \"Ожидает отправки\",\n",
" \"sender._id\": sender.get('_id'), \"receiver._id\": competition_owner.get('_id')\n",
" },{\n",
" \"$set\":{\n",
" \"sender\": sender,\n",
" \"user\": display_user(user),\n",
" \"receiver\": display_user(competition_owner),\n",
" \"competition_id\": competition_id,\n",
" \"address\": competition.get('address'),\n",
" \"post_id\": post_id,\n",
" \"prod\": PROD,\n",
" \"img\": post.get('medias',[0])[0],\n",
" \"title\": post.get('title'),\n",
" \"status\": \"Ожидает отправки\",\n",
" \"type\": \"competition_reward\",\n",
" \"updated_at\": datetime.utcnow()\n",
" }\n",
" }, upsert=True)\n",
" post_ids_to_mark.append(post_id)\n",
" elif post.get('status') != 'Проходит':\n",
" post_ids_to_remove_delivery.append(post_id)\n",
"\n",
"\n",
" if post_ids_to_remove_delivery:\n",
" await delivery_collection.delete_many({\n",
" \"competition_id\": competition_id,\n",
" \"status\": \"Ожидает отправки\",\n",
" \"post_id\": {\"$in\": post_ids_to_remove_delivery}\n",
" })\n",
"\n",
" await notify_in_profile(NotifyItem(\n",
" user_id=str(user['_id']),\n",
" type_item='invitation',\n",
" title='Приглашение',\n",
" link=f'/profile/invitation',\n",
" text=f'Вы прошли отбор на конкурсе \"{competition.get(\"title\")}\"'\n",
" ))\n",
" all_allowed = all(\n",
" post.get('status') in ('Проходит', 'На рассмотрении')\n",
" for post in posts\n",
" )\n",
" has_approved = any(p.get('status') == 'Проходит' for p in posts)\n",
"\n",
" # Email-уведомление\n",
" if user.get('email') and all_allowed and has_approved:\n",
" await invitations_collection.update_one({\n",
" \"user_id\": str(user['_id']),\n",
" \"item\": competition.get(\"title\"),\n",
" \"title\": \"Вы прошли отбор\",\n",
" \"type_\": competition.get(\"type_\"),\n",
" \"user\": display_user(competition_owner),\n",
" \"prod\": PROD,\n",
" \"type\": \"Конкурс\"\n",
" },{'$set':{\n",
" \"user_id\": str(user['_id']),\n",
" \"item\": competition.get(\"title\"),\n",
" \"title\": \"Вы прошли отбор\",\n",
" \"competition\": competition,\n",
" \"type_\": competition.get(\"type_\"),\n",
" \"user\": display_user(competition_owner),\n",
" \"prod\": PROD,\n",
" \"type\": \"Конкурс\"\n",
" }},upsert=True)\n",
" if PROD and user.get('email') and all_allowed and has_approved:\n",
" email_data = {\n",
" 'text': 'Статус заявки обновлен',\n",
" 'list': [\n",
" [\n",
" {'label': 'Название', 'text': item.get('title', ''), 'img':f\"{FULL_DOMAIN}/b/posts/{item['medias_original'][0]}\"},\n",
" {'label': 'Статус', 'text': item.get('status', '')}\n",
" ]\n",
" for item in posts_sorted\n",
" ],\n",
" 'primary': {\n",
" 'href': login_link,\n",
" 'text': 'Войти и посмотреть'\n",
" },\n",
" 'title': competition.get('title', 'Конкурс'),\n",
" 'login_link': login_link,\n",
" }\n",
"\n",
" email_task.notify.delay(\n",
" user['email'],\n",
" email_data,\n",
" 'Обновление статуса заявки на конкурс',\n",
" 'main.html'\n",
" )"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "8ca4a900",
"metadata": {},
"outputs": [],
"source": [
"async def new_competiton_req(request):\n",
" competition = await competitions_col.find_one({'_id':ObjectId(request['competition_id'])})\n",
" user = competition['user']\n",
" if competition.get('type_') == 'artist':\n",
" link = f'/profile/partner/competition/{request['competition_id']}/{request['user']['_id']}'\n",
" else:\n",
" link = f'/profile/partner/competition/{request['competition_id']}'\n",
" login_link = f'{FULL_DOMAIN}/login/{await token_create(str(user['_id']), url=link)}'\n",
" print(user)\n",
" await notify_in_profile(NotifyItem(\n",
" user_id=user['_id'],\n",
" type_item='competition_req',\n",
" title='Новая заявка на конкурс',\n",
" link=link,\n",
" text=f'«{competition['title']}» От {request['user']['username']}'\n",
" ))\n",
" if PROD:\n",
" email_task.notify.delay(user['email'], {\n",
" 'list':[[{'label':'Название','text':i['title'], 'img':f\"{FULL_DOMAIN}/b/posts/{i['medias'][0]}\"},{'label':'Размер','text':i['size']},{'label':'Описание','text':i['description']}] for i in request['posts']],\n",
" 'text': f'Картины на конкурс «{competition['title']}»:',\n",
" 'primary': {'href':login_link,'text':'Войти и посмотреть'},\n",
" 'title': f\"От {request['user']['username']}\",\n",
" 'login_link': login_link\n",
" },\n",
" 'Новая заявка на конкурс', 'main.html')"
],
"display_name": ".venv"
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}