From 4a44be2ed619d90fb02be11e56cb88abaf19976e5475e7a13cf2c301c2459b95 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Mon, 20 Jul 2026 07:51:01 -0400 Subject: [PATCH] fix --- main.ipynb | 205 +++++++++++++++++++++------- main.py | 138 ++++++++++++++----- templates/exhibition/index.html | 7 +- templates/exhibitions.html | 8 +- templates/form/exhibition.html | 8 +- templates/form/exhibition_edit.html | 8 +- 6 files changed, 283 insertions(+), 91 deletions(-) diff --git a/main.ipynb b/main.ipynb index 962826f..50cf559 100644 --- a/main.ipynb +++ b/main.ipynb @@ -102,6 +102,8 @@ "import email_task as email_task\n", "from pydantic import EmailStr\n", "import secrets\n", + "from pymongo import ReturnDocument\n", + "\n", "logging.basicConfig(level=logging.INFO)\n", "logger = logging.getLogger(__name__)" ] @@ -531,35 +533,109 @@ "source": [ "@app.get(\"/cron/5\")\n", "async def cron_every_five_min(token: str = Security(get_token)):\n", - " exhibition = await exhibition_collection.find_one_and_update(\n", - " {\"prod\": PROD, \"status\": 0, \"date_finish\": {\"$lt\": datetime.now(timezone.utc) + timedelta(hours=3)}},\n", - " {\"$set\": {\"status\": 2}}, return_document=False)\n", - " if exhibition:\n", - " tasks = []\n", - " async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}):\n", - " new_doc = {**doc, 'delivery_type':'', 'track':'', 'status': 'Возврат художнику', 'receiver': doc.get('sender'), 'sender': doc.get('receiver')}\n", - " tasks.append(delivery_collection.update_one({\"prod\": PROD, 'status': 'Возврат художнику', 'post_id': doc.get('post_id'),'exhibition_id': doc.get('exhibition_id')},{'$set':new_doc},upsert=True))\n", - " asyncio.gather(*tasks)\n", - " else:\n", - " exhibition = await exhibition_collection.find_one_and_update(\n", - " {\"prod\": PROD, \"datetime_start\": {\"$lt\": datetime.now(timezone.utc) + timedelta(hours=3)}},\n", - " {\"$set\": {\"status\": 1}}, return_document=False)\n", - " if exhibition:\n", - " if exhibition.get('vk') == [] or exhibition.get('tg') == []:\n", - " await service_social_exhibition(exhibition)\n", - " tasks = []\n", - " async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}):\n", - " post = await posts_collection.find_one({'_id': ObjectId(doc.get('post_id'))})\n", - " new_doc = {**doc, 'exhibition': exhibition, 'status': 'На выставке', 'user': post.get('user'), 'post': post}\n", - " tasks.append(delivery_collection.update_one({\"prod\": PROD, 'status': 'На выставке', 'post_id': doc.get('post_id'),'exhibition_id': doc.get('exhibition_id')},{'$set':new_doc},upsert=True))\n", - " tasks.append(notify_in_profile(NotifyItem(\n", - " user_id=str(post.get('user',{}).get('_id')),\n", - " type_item='exhibtion',\n", - " title=f'Выставка {doc.get('exhibition',{}).get('title')}',\n", - " link=f'/profile/exhibition',\n", - " text=f'Ваши картины на выставке'\n", - " )))\n", - " asyncio.gather(*tasks)\n", + " now = datetime.now(timezone.utc)\n", + " limit_dt = now + timedelta(hours=3)\n", + "\n", + " prod_filter = {\"prod\": PROD}\n", + "\n", + " # 1) If there's an exhibition that is going to be finished soon -> move deliveries to \"Возврат художнику\"\n", + " exhibition_finished = await exhibition_collection.find_one_and_update(\n", + " {**prod_filter, \"date_finish\": {\"$lt\": limit_dt}},\n", + " {\"$set\": {\"status\": 2}},\n", + " return_document=ReturnDocument.AFTER,\n", + " )\n", + " if exhibition_finished:\n", + " exhibition_id_str = str(exhibition_finished[\"_id\"])\n", + "\n", + " async for doc in delivery_collection.find(\n", + " {\"exhibition_id\": exhibition_id_str, \"status\": \"Получено\", **prod_filter},\n", + " {\"_id\": 0},\n", + " ):\n", + " sender = doc.get(\"sender\")\n", + " post_id = doc.get(\"post_id\")\n", + " exhibition_id = doc.get(\"exhibition_id\")\n", + "\n", + " new_doc = {\n", + " **doc,\n", + " \"delivery_type\": \"\",\n", + " \"track\": \"\",\n", + " \"status\": \"Возврат художнику\",\n", + " \"receiver\": sender,\n", + " \"sender\": doc.get(\"receiver\"),\n", + " }\n", + "\n", + " await delivery_collection.update_one(\n", + " {**prod_filter, \"status\": \"Возврат художнику\", \"post_id\": post_id, \"exhibition_id\": exhibition_id},\n", + " {\"$set\": new_doc},\n", + " upsert=True,\n", + " )\n", + "\n", + " return {}\n", + " tasks = []\n", + " async for exhibition_to_start in exhibition_collection.find(\n", + " {**prod_filter}\n", + " ):\n", + " exhibition_id_str = str(exhibition_to_start[\"_id\"])\n", + " exhibition_title = exhibition_to_start.get(\"title\")\n", + "\n", + " notified_user_id = None\n", + " notified_post_user_id = None\n", + "\n", + " async for doc in delivery_collection.find(\n", + " {\"exhibition_id\": exhibition_id_str, \"status\": \"Получено\", **prod_filter},\n", + " {\"_id\": 0},\n", + " ):\n", + " post_id = doc.get(\"post_id\")\n", + " exhibition_id = doc.get(\"exhibition_id\")\n", + "\n", + " post = await posts_collection.find_one({\"_id\": ObjectId(post_id)})\n", + "\n", + " # Use user from post; keep them even if multiple deliveries exist\n", + " if post and post.get(\"user\") and not notified_post_user_id:\n", + " notified_post_user_id = str(post[\"user\"].get(\"_id\"))\n", + "\n", + " new_doc = {\n", + " **doc,\n", + " \"exhibition\": exhibition_to_start,\n", + " \"status\": \"На выставке\",\n", + " \"user\": post.get(\"user\") if post else None,\n", + " \"post\": post,\n", + " **prod_filter\n", + " }\n", + " print(new_doc)\n", + "\n", + " tasks.append(delivery_collection.update_one(\n", + " {**prod_filter, \"status\": \"На выставке\", \"post_id\": post_id, \"exhibition_id\": exhibition_id},\n", + " {\"$set\": new_doc},\n", + " upsert=True,\n", + " ))\n", + "\n", + " # NOTE: your original code used `post` from inside the loop (could be last item / even undefined).\n", + " # This version notifies once using the first found post user.\n", + " if notified_post_user_id:\n", + " await notify_in_profile(\n", + " NotifyItem(\n", + " user_id=notified_post_user_id,\n", + " type_item=\"exhibtion\",\n", + " title=f\"Выставка {exhibition_title}\",\n", + " link=\"/profile/exhibition\",\n", + " text=\"Ваши картины на выставке\",\n", + " )\n", + " )\n", + " asyncio.gather(tasks)\n", + " # 2) Otherwise find an exhibition that should start soon -> move deliveries to \"На выставке\"\n", + " exhibition_to_start = await exhibition_collection.find_one_and_update(\n", + " {**prod_filter, \"status\": 0, \"datetime_start\": {\"$lt\": limit_dt}},\n", + " {\"$set\": {\"status\": 1}},\n", + " return_document=ReturnDocument.AFTER,\n", + " )\n", + "\n", + " if not exhibition_to_start:\n", + " return {}\n", + "\n", + " # If vk/tg arrays are empty -> send social invitation\n", + " if exhibition_to_start.get(\"vk\") == [] or exhibition_to_start.get(\"tg\") == []:\n", + " await service_social_exhibition(bson_clear(exhibition_to_start))\n", " return {}\n" ] }, @@ -2946,7 +3022,7 @@ { "cell_type": "code", "execution_count": null, - "id": "44ea690c", + "id": "aabb40cb", "metadata": {}, "outputs": [], "source": [ @@ -2956,9 +3032,16 @@ " current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n", "):\n", " await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')})\n", - " return {'ok': True} \n", - " \n", - "\n", + " return {'ok': True} \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d38cafed", + "metadata": {}, + "outputs": [], + "source": [ "@app.post(\"/api/post\", response_model=dict)\n", "async def post_painting(\n", " background_tasks: BackgroundTasks,\n", @@ -3013,9 +3096,16 @@ " return doc_out\n", " except Exception as e:\n", " print(e)\n", - " raise HTTPException(status_code=500, detail=str(e))\n", - "\n", - "\n", + " raise HTTPException(status_code=500, detail=str(e))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44ea690c", + "metadata": {}, + "outputs": [], + "source": [ "@app.put(\"/api/post/{post_id}\", response_model=dict)\n", "async def put_painting(\n", " background_tasks: BackgroundTasks,\n", @@ -3067,7 +3157,6 @@ " if res is None:\n", " raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n", "\n", - "\n", " doc_out = {\n", " \"_id\":post_id,\n", " \"id\":post_id,\n", @@ -3075,12 +3164,18 @@ " }\n", " doc_out = bson_clear(doc_out)\n", " \n", - "\n", " background_tasks.add_task(update_social_post, doc_out)\n", " background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media)\n", - " return doc_out\n", - "\n", - "\n", + " return doc_out\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f18f6a6", + "metadata": {}, + "outputs": [], + "source": [ "@app.post(\"/api/posts/media\", response_model=dict)\n", "async def post_media(\n", " medias: Optional[List[UploadFile]] = File(None),\n", @@ -3096,7 +3191,7 @@ { "cell_type": "code", "execution_count": null, - "id": "abe852d2", + "id": "9c463130", "metadata": {}, "outputs": [], "source": [ @@ -3136,9 +3231,16 @@ " background_tasks.add_task(replace_news_medias, doc_out)\n", " return doc_out\n", " except Exception as e:\n", - " raise HTTPException(status_code=500, detail=str(e))\n", - "\n", - "\n", + " raise HTTPException(status_code=500, detail=str(e))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de77638d", + "metadata": {}, + "outputs": [], + "source": [ "@app.put(\"/api/news/{news_id}\", response_model=dict)\n", "async def put_news(\n", " background_tasks: BackgroundTasks,\n", @@ -3177,9 +3279,16 @@ " **{k: v for k, v in update_doc.items()}\n", " }\n", " doc_out = bson_clear(doc_out)\n", - " return doc_out\n", - "\n", - "\n", + " return doc_out\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "abe852d2", + "metadata": {}, + "outputs": [], + "source": [ "@app.post(\"/api/news/media\", response_model=dict)\n", "async def news_media(\n", " medias: Optional[List[UploadFile]] = File(None),\n", diff --git a/main.py b/main.py index 4c2f986..0be0c80 100644 --- a/main.py +++ b/main.py @@ -41,6 +41,8 @@ from templates import templates import email_task as email_task from pydantic import EmailStr import secrets +from pymongo import ReturnDocument + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -335,35 +337,109 @@ async def cron_every_day(token: str = Security(get_token)): @app.get("/cron/5") async def cron_every_five_min(token: str = Security(get_token)): - exhibition = await exhibition_collection.find_one_and_update( - {"prod": PROD, "status": 0, "date_finish": {"$lt": datetime.now(timezone.utc) + timedelta(hours=3)}}, - {"$set": {"status": 2}}, return_document=False) - if exhibition: - tasks = [] - async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}): - new_doc = {**doc, 'delivery_type':'', 'track':'', 'status': 'Возврат художнику', 'receiver': doc.get('sender'), 'sender': doc.get('receiver')} - tasks.append(delivery_collection.update_one({"prod": PROD, 'status': 'Возврат художнику', 'post_id': doc.get('post_id'),'exhibition_id': doc.get('exhibition_id')},{'$set':new_doc},upsert=True)) - asyncio.gather(*tasks) - else: - exhibition = await exhibition_collection.find_one_and_update( - {"prod": PROD, "datetime_start": {"$lt": datetime.now(timezone.utc) + timedelta(hours=3)}}, - {"$set": {"status": 1}}, return_document=False) - if exhibition: - if exhibition.get('vk') == [] or exhibition.get('tg') == []: - await service_social_exhibition(exhibition) - tasks = [] - async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}): - post = await posts_collection.find_one({'_id': ObjectId(doc.get('post_id'))}) - new_doc = {**doc, 'exhibition': exhibition, 'status': 'На выставке', 'user': post.get('user'), 'post': post} - tasks.append(delivery_collection.update_one({"prod": PROD, 'status': 'На выставке', 'post_id': doc.get('post_id'),'exhibition_id': doc.get('exhibition_id')},{'$set':new_doc},upsert=True)) - tasks.append(notify_in_profile(NotifyItem( - user_id=str(post.get('user',{}).get('_id')), - type_item='exhibtion', - title=f'Выставка {doc.get('exhibition',{}).get('title')}', - link=f'/profile/exhibition', - text=f'Ваши картины на выставке' - ))) - asyncio.gather(*tasks) + now = datetime.now(timezone.utc) + limit_dt = now + timedelta(hours=3) + + prod_filter = {"prod": PROD} + + # 1) If there's an exhibition that is going to be finished soon -> move deliveries to "Возврат художнику" + exhibition_finished = await exhibition_collection.find_one_and_update( + {**prod_filter, "date_finish": {"$lt": limit_dt}}, + {"$set": {"status": 2}}, + return_document=ReturnDocument.AFTER, + ) + if exhibition_finished: + exhibition_id_str = str(exhibition_finished["_id"]) + + async for doc in delivery_collection.find( + {"exhibition_id": exhibition_id_str, "status": "Получено", **prod_filter}, + {"_id": 0}, + ): + sender = doc.get("sender") + post_id = doc.get("post_id") + exhibition_id = doc.get("exhibition_id") + + new_doc = { + **doc, + "delivery_type": "", + "track": "", + "status": "Возврат художнику", + "receiver": sender, + "sender": doc.get("receiver"), + } + + await delivery_collection.update_one( + {**prod_filter, "status": "Возврат художнику", "post_id": post_id, "exhibition_id": exhibition_id}, + {"$set": new_doc}, + upsert=True, + ) + + return {} + tasks = [] + async for exhibition_to_start in exhibition_collection.find( + {**prod_filter} + ): + exhibition_id_str = str(exhibition_to_start["_id"]) + exhibition_title = exhibition_to_start.get("title") + + notified_user_id = None + notified_post_user_id = None + + async for doc in delivery_collection.find( + {"exhibition_id": exhibition_id_str, "status": "Получено", **prod_filter}, + {"_id": 0}, + ): + post_id = doc.get("post_id") + exhibition_id = doc.get("exhibition_id") + + post = await posts_collection.find_one({"_id": ObjectId(post_id)}) + + # Use user from post; keep them even if multiple deliveries exist + if post and post.get("user") and not notified_post_user_id: + notified_post_user_id = str(post["user"].get("_id")) + + new_doc = { + **doc, + "exhibition": exhibition_to_start, + "status": "На выставке", + "user": post.get("user") if post else None, + "post": post, + **prod_filter + } + print(new_doc) + + tasks.append(delivery_collection.update_one( + {**prod_filter, "status": "На выставке", "post_id": post_id, "exhibition_id": exhibition_id}, + {"$set": new_doc}, + upsert=True, + )) + + # NOTE: your original code used `post` from inside the loop (could be last item / even undefined). + # This version notifies once using the first found post user. + if notified_post_user_id: + await notify_in_profile( + NotifyItem( + user_id=notified_post_user_id, + type_item="exhibtion", + title=f"Выставка {exhibition_title}", + link="/profile/exhibition", + text="Ваши картины на выставке", + ) + ) + asyncio.gather(tasks) + # 2) Otherwise find an exhibition that should start soon -> move deliveries to "На выставке" + exhibition_to_start = await exhibition_collection.find_one_and_update( + {**prod_filter, "status": 0, "datetime_start": {"$lt": limit_dt}}, + {"$set": {"status": 1}}, + return_document=ReturnDocument.AFTER, + ) + + if not exhibition_to_start: + return {} + + # If vk/tg arrays are empty -> send social invitation + if exhibition_to_start.get("vk") == [] or exhibition_to_start.get("tg") == []: + await service_social_exhibition(bson_clear(exhibition_to_start)) return {} @@ -2216,7 +2292,7 @@ async def post_delete( ): await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}) return {'ok': True} - + @app.post("/api/post", response_model=dict) async def post_painting( @@ -2326,7 +2402,6 @@ async def put_painting( if res is None: raise HTTPException(status_code=404, detail="Картина не найдена") - doc_out = { "_id":post_id, "id":post_id, @@ -2334,7 +2409,6 @@ async def put_painting( } doc_out = bson_clear(doc_out) - background_tasks.add_task(update_social_post, doc_out) background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media) return doc_out diff --git a/templates/exhibition/index.html b/templates/exhibition/index.html index 33b7fb9..1ede15d 100644 --- a/templates/exhibition/index.html +++ b/templates/exhibition/index.html @@ -43,11 +43,14 @@

{{exhibition.title}}

+ + Релиз выставки +

Место проведения: {{ exhibition.get('place') }}

{% if posts|length > 0 %}

Картины: {{posts|length}}

{% endif %} - {{ share.share('/exhibition/{}'.format(exhibition._id), exhibition.title, '/b/exhibition/{}'.format(exhibition.poster_file_id), "🎨") }} + {{ share.share('/exhibition/{}'.format(exhibition._id), exhibition.title, '/b/exhibition/{}'.format(exhibition.poster_file_id), "Приглашаем на выставку") }}
@@ -157,7 +160,7 @@ .b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; } .b_5dr47_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .b_xz6em { align-items: center; align-self: stretch; display: flex; gap: 13px; } -.b_sdf{font-size: 12px;font-weight: bold;} +.b_sdf{font-size: 11px;font-weight: bold;} picture{overflow: hidden;} picture>img{width: 100%;object-fit: contain;} .b_ob59y { border-radius: 1em; height: 70px; min-width: 70px; max-width: 170px; vertical-align: top; object-fit: cover;} diff --git a/templates/exhibitions.html b/templates/exhibitions.html index 764f399..eb694af 100644 --- a/templates/exhibitions.html +++ b/templates/exhibitions.html @@ -60,6 +60,12 @@
{% endif %} + + Релиз выставки + + + Открыть +

Место проведения: {{ exhibition.get('place') }}

С {{ ( exhibition.datetime_start.strftime('%d.%m.%Y')) if exhibition.datetime_start else ''}} @@ -70,7 +76,7 @@ {% endif %}
- {{ share.share('/exhibition/{}'.format(exhibition._id), exhibition.title, '/b/exhibition/{}'.format(exhibition.poster_file_id), "🎨") }} + {{ share.share('/exhibition/{}'.format(exhibition._id), exhibition.title, '/b/exhibition/{}'.format(exhibition.poster_file_id), "Приглашаем на выставку") }}
diff --git a/templates/form/exhibition.html b/templates/form/exhibition.html index 4943908..e119584 100644 --- a/templates/form/exhibition.html +++ b/templates/form/exhibition.html @@ -76,7 +76,7 @@
Выберите файл
- + @@ -89,7 +89,7 @@ @@ -109,7 +109,7 @@
Выберите файл
- + @@ -122,7 +122,7 @@ diff --git a/templates/form/exhibition_edit.html b/templates/form/exhibition_edit.html index e14f1e6..edb1745 100644 --- a/templates/form/exhibition_edit.html +++ b/templates/form/exhibition_edit.html @@ -59,7 +59,7 @@
Выберите файл
- + @@ -69,7 +69,7 @@
@@ -85,7 +85,7 @@
Выберите файл
- + @@ -95,7 +95,7 @@