diff --git a/delivery_router.py b/delivery_router.py index e9bea1e..8b7b2be 100644 --- a/delivery_router.py +++ b/delivery_router.py @@ -56,11 +56,15 @@ async def send_delivery( continue # Уведомление получателю + if (exhibition:=existing.get('exhibition',{})) and (exhibition_id:=existing.get('exhibition_id')): + link = f'/profile/delivery/exhibition/{exhibition_id}' + else: + link = f'/profile/delivery' notify = await notify_in_profile(NotifyItem( user_id=str(existing["receiver"]["_id"]), type_item="delivery", title="Доставка картин", - link="/profile/delivery", + link=link, text=f"{str(existing['sender']['username'])} — прислал вам картины." )) @@ -106,13 +110,16 @@ async def accept_delivery( if not existing: errors.append({"item": item_id, "error": "Доставка не найдена или не для вас"}) continue - + if (exhibition:=existing.get('exhibition',{})) and (exhibition_id:=existing.get('exhibition_id')): + link = f'/profile/delivery/exhibition/{exhibition_id}' + else: + link = f'/profile/delivery' # Уведомление отправителю notify = await notify_in_profile(NotifyItem( user_id=str(existing["sender"]["_id"]), type_item="delivery", title="Доставка картин", - link="/profile/delivery", + link=link, text=f"Картина «{existing['title']}» получена — {str(existing['receiver']['username'])}." )) diff --git a/main.ipynb b/main.ipynb index f85b285..4568785 100644 --- a/main.ipynb +++ b/main.ipynb @@ -564,12 +564,21 @@ " )\n", " if exhibition_finished:\n", " exhibition_id_str = str(exhibition_finished[\"_id\"])\n", + " cursor = delivery_collection.aggregate([\n", + " {\"$match\": {\n", + " \"exhibition_id\": exhibition_id_str, **prod_filter\n", + " }},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " {\"$group\": {\n", + " \"_id\": \"$post_id\",\n", + " \"doc\": {\"$first\": \"$$ROOT\"}\n", + " }},\n", + " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " ])\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", + " data = [doc async for doc in cursor if doc.get('status') == 'На выставке']\n", + " for doc in data:\n", " post_id = doc.get(\"post_id\")\n", " exhibition_id = doc.get(\"exhibition_id\")\n", "\n", @@ -578,9 +587,11 @@ " \"delivery_type\": \"\",\n", " \"track\": \"\",\n", " \"status\": \"Возврат художнику\",\n", - " \"receiver\": sender,\n", - " \"sender\": doc.get(\"receiver\"),\n", + " \"receiver\": doc.get(\"user\"),\n", + " \"sender\": doc.get(\"exhibition\", {}).get(\"user\"),\n", " }\n", + " new_doc.pop('_id')\n", + " new_doc.pop('exhibition')\n", "\n", " await delivery_collection.update_one(\n", " {**prod_filter, \"status\": \"Возврат художнику\", \"post_id\": post_id, \"exhibition_id\": exhibition_id},\n", @@ -1614,9 +1625,24 @@ "):\n", " exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n", " if exhibition:\n", - " posts = await delivery_collection.find({'status': 'На выставке', \"exhibition_id\": exhibition_id}).to_list()\n", + " match_ = {\n", + " \"exhibition_id\": exhibition_id,\n", + " 'prod': PROD,\n", + " }\n", + "\n", + " cursor = delivery_collection.aggregate([\n", + " {\"$match\": match_},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " {\"$group\": {\n", + " \"_id\": \"$post_id\",\n", + " \"doc\": {\"$first\": \"$$ROOT\"}\n", + " }},\n", + " {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n", + " {\"$sort\": {\"_id\": -1}},\n", + " ])\n", + " posts = [doc async for doc in cursor if doc.get('status') == 'На выставке']\n", " return templates.TemplateResponse(\n", - " request=request, name=\"exhibition/index.html\", context={\"id\": 1, \"user\": user, \"exhibition\": exhibition, \"posts\": posts}\n", + " request=request, name=\"exhibition/index.html\", context={\"id\": 1, \"user\": user, \"exhibition\": exhibition, \"posts\": posts, 'now': datetime.now()}\n", " )\n", " else:\n", " raise HTTPException(status_code=307, headers={\"Location\": '/competitions'}, detail=\"Конкурс не найден\")" @@ -1735,7 +1761,7 @@ " 'prod': PROD\n", " }).sort({'_id':-1}).limit(1000).to_list()\n", " return templates.TemplateResponse(\n", - " request=request, name=\"exhibitions.html\", context={\"id\": 1, \"user\": user, \"exhibitions\": exhibitions}\n", + " request=request, name=\"exhibitions.html\", context={\"id\": 1, \"user\": user, \"exhibitions\": exhibitions, 'now': datetime.now()}\n", " )" ], "display_name": ".venv" @@ -1817,11 +1843,7 @@ " ]})\n", " \n", " if not user:\n", - " # Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно\n", - " # Но для удобства использования мы можем сказать \"не найден\".\n", - " # Чтобы предотвратить перечисление, некоторые приложения всегда возвращают \"OK\", но отправляют письмо только если найдено.\n", - " # Здесь мы придерживаемся вашей оригинальной логики, но помните о риске перечисления.\n", - " raise HTTPException(status_code=404, detail=\"Пользователь с таким email не найден\")\n", + " return {'ok': True}\n", " \n", " # 4. Генерация токена\n", " token = secrets.token_urlsafe(32)\n", @@ -1845,13 +1867,13 @@ " # 7. Асинхронная отправка email\n", " try:\n", " email_task.notify.delay(\n", - " recipient=user['email'],\n", - " context={\n", + " to_email=user['email'],\n", + " data={\n", " 'link': reset_link, \n", " 'email': user['email']\n", " },\n", " subject='Восстановление пароля',\n", - " template_name='restore.html'\n", + " template='restore.html'\n", " )\n", " except Exception as e:\n", " # Логирование ошибки\n", @@ -3229,6 +3251,53 @@ ], "display_name": ".venv" }, + { + "cell_type": "code", + "execution_count": null, + "id": "8feec4b3", + "metadata": {}, + "outputs": [], + "source": [ + "@app.put(\"/api/post/{post_id}/sale\", response_model=dict)\n", + "async def sale_painting(\n", + " post_id: str,\n", + " current_user: Dict = Security(get_current_user)\n", + " ):\n", + " fin = [{'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}]\n", + " if 'partner' in current_user.get('roles', []):\n", + " fin.append({'_id': ObjectId(post_id)})\n", + " doc = await posts_collection.update_one({'$or': fin}, {'$set': {\"status\": \"Продано\"}})\n", + " if doc.modified_count: \n", + " existing = await delivery_collection.find_one(\n", + " {\"post_id\": post_id, \"status\": \"На выставке\"},\n", + " {\"_id\": 0}\n", + " )\n", + " if existing:\n", + " update_data = {\n", + " \"status\": \"Продано\",\n", + " \"post\": {\"status\": \"Продано\"},\n", + " }\n", + " notify = await notify_in_profile(NotifyItem(\n", + " user_id=str(existing[\"user\"][\"_id\"]),\n", + " type_item=\"delivery\",\n", + " title=\"Картина продана\",\n", + " link=\"/profile/delivery\",\n", + " text=f\"{str(existing.get('post',{}).get('title'))}\"\n", + " ))\n", + "\n", + " new_doc = {**existing, **update_data, \"notify\": notify.upserted_id}\n", + "\n", + " result = await delivery_collection.insert_one(new_doc)\n", + " else:\n", + " doc = await posts_collection.update_one({'$or': fin}, {'$set': {\"status\": \"В каталоге\"}})\n", + " await delivery_collection.delete_one(\n", + " {\"post_id\": post_id, \"status\": \"Продано\"}\n", + " )\n", + "\n", + " return {'ok': True}" + ], + "display_name": ".venv" + }, { "cell_type": "code", "execution_count": null, @@ -4420,7 +4489,7 @@ " if field == 'name':\n", " field = 'title'\n", " if op == \"like\":\n", - " return {field: {\"$regex\": value, \"$options\": \"i\"}}\n", + " return {field: {\"$regex\": safe_strip(value), \"$options\": \"i\"}}\n", " return {field: value}\n", " mongo_query = mql_to_mongo(mql)\n", " for post in [doc async for doc in posts_collection.find(mongo_query).sort({\"_id\":-1}).limit(per_page * page + 8)][per_page * page - per_page:per_page * page]:\n", @@ -4800,7 +4869,7 @@ " ],\n", " 'prod': PROD,\n", " }},\n", - " {\"$sort\": {\"_id\": -1}}, # why not work\n", + " {\"$sort\": {\"_id\": -1}},\n", " {\"$group\": {\n", " \"_id\": \"$post_id\",\n", " \"doc\": {\"$first\": \"$$ROOT\"}\n", @@ -4952,6 +5021,11 @@ "\n", " galerists = [{\"_id\": uname, \"count\": counts_by_username.get(uname, '')} for uname in usernames]\n", " posts = [doc async for doc in posts_collection.find().sort('_id', -1).limit(32)]\n", + " d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n", + " for post in posts:\n", + " for doc in d_posts:\n", + " if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n", + " post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n", " return templates.TemplateResponse(\n", " request=request, name=\"profile/partner/exhibition catalog.html\", context={\"now\": datetime.now(), \"user\": user, \"galerists\": galerists, \"genres\": genres, \"living_places\": living_places, \"exhibition\": exhibition, \"posts\": posts}\n", " )\n" @@ -4977,7 +5051,7 @@ " d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n", " for post in posts:\n", " for doc in d_posts:\n", - " if str(post.get('_id')) == doc.get('post_id') and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n", + " if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n", " post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n", " return templates.TemplateResponse(\n", " request=request, name=\"profile/partner/exhibition.html\", context={\"now\": datetime.now(), \"user\": user, \"exhibition\": exhibition, \"posts\": posts}\n", diff --git a/main.py b/main.py index 0c3d59f..e0c93cb 100644 --- a/main.py +++ b/main.py @@ -352,12 +352,21 @@ async def cron_every_five_min(token: str = Security(get_token)): ) if exhibition_finished: exhibition_id_str = str(exhibition_finished["_id"]) + cursor = delivery_collection.aggregate([ + {"$match": { + "exhibition_id": exhibition_id_str, **prod_filter + }}, + {"$sort": {"_id": -1}}, + {"$group": { + "_id": "$post_id", + "doc": {"$first": "$$ROOT"} + }}, + {"$replaceRoot": {"newRoot": "$doc"}}, + {"$sort": {"_id": -1}}, + ]) - async for doc in delivery_collection.find( - {"exhibition_id": exhibition_id_str, "status": "Получено", **prod_filter}, - {"_id": 0}, - ): - sender = doc.get("sender") + data = [doc async for doc in cursor if doc.get('status') == 'На выставке'] + for doc in data: post_id = doc.get("post_id") exhibition_id = doc.get("exhibition_id") @@ -366,9 +375,11 @@ async def cron_every_five_min(token: str = Security(get_token)): "delivery_type": "", "track": "", "status": "Возврат художнику", - "receiver": sender, - "sender": doc.get("receiver"), + "receiver": doc.get("user"), + "sender": doc.get("exhibition", {}).get("user"), } + new_doc.pop('_id') + new_doc.pop('exhibition') await delivery_collection.update_one( {**prod_filter, "status": "Возврат художнику", "post_id": post_id, "exhibition_id": exhibition_id}, @@ -1201,9 +1212,24 @@ async def exhibition_members( ): exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)}) if exhibition: - posts = await delivery_collection.find({'status': 'На выставке', "exhibition_id": exhibition_id}).to_list() + match_ = { + "exhibition_id": exhibition_id, + 'prod': PROD, + } + + cursor = delivery_collection.aggregate([ + {"$match": match_}, + {"$sort": {"_id": -1}}, + {"$group": { + "_id": "$post_id", + "doc": {"$first": "$$ROOT"} + }}, + {"$replaceRoot": {"newRoot": "$doc"}}, + {"$sort": {"_id": -1}}, + ]) + posts = [doc async for doc in cursor if doc.get('status') == 'На выставке'] return templates.TemplateResponse( - request=request, name="exhibition/index.html", context={"id": 1, "user": user, "exhibition": exhibition, "posts": posts} + request=request, name="exhibition/index.html", context={"id": 1, "user": user, "exhibition": exhibition, "posts": posts, 'now': datetime.now()} ) else: raise HTTPException(status_code=307, headers={"Location": '/competitions'}, detail="Конкурс не найден") @@ -1286,7 +1312,7 @@ async def exhibitions_all( 'prod': PROD }).sort({'_id':-1}).limit(1000).to_list() return templates.TemplateResponse( - request=request, name="exhibitions.html", context={"id": 1, "user": user, "exhibitions": exhibitions} + request=request, name="exhibitions.html", context={"id": 1, "user": user, "exhibitions": exhibitions, 'now': datetime.now()} ) @app.get("/competitions", response_class=HTMLResponse) @@ -1350,11 +1376,7 @@ async def restore_api( ]}) if not user: - # Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно - # Но для удобства использования мы можем сказать "не найден". - # Чтобы предотвратить перечисление, некоторые приложения всегда возвращают "OK", но отправляют письмо только если найдено. - # Здесь мы придерживаемся вашей оригинальной логики, но помните о риске перечисления. - raise HTTPException(status_code=404, detail="Пользователь с таким email не найден") + return {'ok': True} # 4. Генерация токена token = secrets.token_urlsafe(32) @@ -1378,13 +1400,13 @@ async def restore_api( # 7. Асинхронная отправка email try: email_task.notify.delay( - recipient=user['email'], - context={ + to_email=user['email'], + data={ 'link': reset_link, 'email': user['email'] }, subject='Восстановление пароля', - template_name='restore.html' + template='restore.html' ) except Exception as e: # Логирование ошибки @@ -2402,6 +2424,44 @@ async def post_painting( raise HTTPException(status_code=500, detail=str(e)) +@app.put("/api/post/{post_id}/sale", response_model=dict) +async def sale_painting( + post_id: str, + current_user: Dict = Security(get_current_user) + ): + fin = [{'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}] + if 'partner' in current_user.get('roles', []): + fin.append({'_id': ObjectId(post_id)}) + doc = await posts_collection.update_one({'$or': fin}, {'$set': {"status": "Продано"}}) + if doc.modified_count: + existing = await delivery_collection.find_one( + {"post_id": post_id, "status": "На выставке"}, + {"_id": 0} + ) + if existing: + update_data = { + "status": "Продано", + "post": {"status": "Продано"}, + } + notify = await notify_in_profile(NotifyItem( + user_id=str(existing["user"]["_id"]), + type_item="delivery", + title="Картина продана", + link="/profile/delivery", + text=f"{str(existing.get('post',{}).get('title'))}" + )) + + new_doc = {**existing, **update_data, "notify": notify.upserted_id} + + result = await delivery_collection.insert_one(new_doc) + else: + doc = await posts_collection.update_one({'$or': fin}, {'$set': {"status": "В каталоге"}}) + await delivery_collection.delete_one( + {"post_id": post_id, "status": "Продано"} + ) + + return {'ok': True} + @app.put("/api/post/{post_id}", response_model=dict) async def put_painting( background_tasks: BackgroundTasks, @@ -3389,7 +3449,7 @@ async def get_posts( if field == 'name': field = 'title' if op == "like": - return {field: {"$regex": value, "$options": "i"}} + return {field: {"$regex": safe_strip(value), "$options": "i"}} return {field: value} mongo_query = mql_to_mongo(mql) for post in [doc async for doc in posts_collection.find(mongo_query).sort({"_id":-1}).limit(per_page * page + 8)][per_page * page - per_page:per_page * page]: @@ -3678,7 +3738,7 @@ async def profile_delivery( ], 'prod': PROD, }}, - {"$sort": {"_id": -1}}, # why not work + {"$sort": {"_id": -1}}, {"$group": { "_id": "$post_id", "doc": {"$first": "$$ROOT"} @@ -3806,6 +3866,11 @@ async def profile_partner_exhibitions_catalog( galerists = [{"_id": uname, "count": counts_by_username.get(uname, '')} for uname in usernames] posts = [doc async for doc in posts_collection.find().sort('_id', -1).limit(32)] + d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list() + for post in posts: + for doc in d_posts: + if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')): + post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')}) return templates.TemplateResponse( request=request, name="profile/partner/exhibition catalog.html", context={"now": datetime.now(), "user": user, "galerists": galerists, "genres": genres, "living_places": living_places, "exhibition": exhibition, "posts": posts} ) @@ -3823,7 +3888,7 @@ async def profile_partner_exhibitions( d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list() for post in posts: for doc in d_posts: - if str(post.get('_id')) == doc.get('post_id') and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')): + if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')): post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')}) return templates.TemplateResponse( request=request, name="profile/partner/exhibition.html", context={"now": datetime.now(), "user": user, "exhibition": exhibition, "posts": posts} diff --git a/migrate.ipynb b/migrate.ipynb deleted file mode 100644 index c5ae839..0000000 --- a/migrate.ipynb +++ /dev/null @@ -1,85 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "90b31bac", - "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')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c3f5835c", - "metadata": {}, - "outputs": [], - "source": [ - "from db import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d80807dd", - "metadata": {}, - "outputs": [], - "source": [ - "await posts_collection.update_many({'status':'publish'},{'$set':{'status':'В каталоге'}})" - ] - } - ], - "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 -} diff --git a/migrate.py b/migrate.py deleted file mode 100644 index 33a1ba1..0000000 --- a/migrate.py +++ /dev/null @@ -1,3 +0,0 @@ -from db import * - -await posts_collection.update_many({'status':'publish'},{'$set':{'status':'В каталоге'}}) \ No newline at end of file diff --git a/static/service-worker.js b/static/service-worker.js index 7c4bb9a..958a026 100644 --- a/static/service-worker.js +++ b/static/service-worker.js @@ -2,7 +2,7 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox workbox.setConfig({ debug: false }); -const CACHE_NAME = 'app-cache-v2.1.4'; +const CACHE_NAME = 'app-cache-v2.1.5'; const MIN_VALID_SIZE = 500; const ASSETS_TO_CACHE = [ diff --git a/static/sw.js b/static/sw.js index 7c4bb9a..958a026 100644 --- a/static/sw.js +++ b/static/sw.js @@ -2,7 +2,7 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox workbox.setConfig({ debug: false }); -const CACHE_NAME = 'app-cache-v2.1.4'; +const CACHE_NAME = 'app-cache-v2.1.5'; const MIN_VALID_SIZE = 500; const ASSETS_TO_CACHE = [ diff --git a/tasks.ipynb b/tasks.ipynb index 8c7980c..a47bca3 100644 --- a/tasks.ipynb +++ b/tasks.ipynb @@ -676,14 +676,17 @@ " if other_ids:\n", " await delivery_collection.update_many(\n", " {'post_id': {'$in': other_ids}},\n", - " {'$set': {'status': 'Приглашение откланено'}} # как вы написали\n", + " {'$set': {'status': 'Приглашение отклонено'}} # как вы написали\n", " )\n", - "\n", + " if (exhibition:=invitation.get('exhibition',{})) and (exhibition_id:=invitation.get('exhibition_id')):\n", + " link = f'/profile/delivery/exhibition/{exhibition_id}'\n", + " else:\n", + " link = f'/profile/delivery'\n", " await notify_in_profile(NotifyItem(\n", - " user_id=invitation.get('exhibition',{}).get(\"user\",{}).get('_id'),\n", + " user_id=exhibition.get(\"user\",{}).get('_id'),\n", " type_item='c',\n", - " title=f'Выставка \"{invitation.get('exhibition',{}).get(\"title\")}\"',\n", - " link=f'/profile/delivery',\n", + " title=f'Выставка \"{exhibition.get(\"title\")}\"',\n", + " link=link,\n", " text=f'Художник принял условия'\n", " ))" ], @@ -700,7 +703,7 @@ " 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}}).to_list() if obj_ids else []\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", diff --git a/tasks.py b/tasks.py index 3fd5f8c..533bdbe 100644 --- a/tasks.py +++ b/tasks.py @@ -514,14 +514,17 @@ async def accept_invitations(invitations_id: str, posts_ids: List[str]): if other_ids: await delivery_collection.update_many( {'post_id': {'$in': other_ids}}, - {'$set': {'status': 'Приглашение откланено'}} # как вы написали + {'$set': {'status': 'Приглашение отклонено'}} # как вы написали ) - + if (exhibition:=invitation.get('exhibition',{})) and (exhibition_id:=invitation.get('exhibition_id')): + link = f'/profile/delivery/exhibition/{exhibition_id}' + else: + link = f'/profile/delivery' await notify_in_profile(NotifyItem( - user_id=invitation.get('exhibition',{}).get("user",{}).get('_id'), + user_id=exhibition.get("user",{}).get('_id'), type_item='c', - title=f'Выставка "{invitation.get('exhibition',{}).get("title")}"', - link=f'/profile/delivery', + title=f'Выставка "{exhibition.get("title")}"', + link=link, text=f'Художник принял условия' )) @@ -529,7 +532,7 @@ async def exhibition_send_invitations(exhibition_id): exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)}) exhibition_owner = exhibition.get('user') obj_ids = [ObjectId(x) for x in exhibition.get('posts', [])] - posts = await posts_collection.find({"_id": {"$in": obj_ids}}).to_list() if obj_ids else [] + posts = await posts_collection.find({"_id": {"$in": obj_ids}, 'status': 'В каталоге'}).to_list() if obj_ids else [] new_posts = {} all_posts = {} for post in posts: diff --git a/templates/articles.html b/templates/articles.html index 20ea670..47f7582 100644 --- a/templates/articles.html +++ b/templates/articles.html @@ -37,7 +37,6 @@ max-width: 100%; overflow-wrap: break-word; word-break: break-word; - hyphens: auto; } .c_qx7jk{margin: 100px 0;} .c_h9bsw{width: 100%;object-fit: scale-down;} diff --git a/templates/base.html b/templates/base.html index 8057ce9..ab85e47 100644 --- a/templates/base.html +++ b/templates/base.html @@ -18,7 +18,6 @@ h1,h2,h3,p { max-width: 100%; overflow-wrap: break-word; word-break: break-word; - hyphens: auto; } html{display: flex;justify-content: center;} input, button {touch-action: manipulation;} diff --git a/templates/components/header.html b/templates/components/header.html index 59ca3b6..49b57af 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -149,7 +149,7 @@ document.addEventListener('DOMContentLoaded', () => { // 2) newest first const aTime = parseDate(a.dateStr)?.getTime() ?? 0; const bTime = parseDate(b.dateStr)?.getTime() ?? 0; - return bTime - aTime; + return aTime - bTime; }); notificationsArray.forEach(item => { diff --git a/templates/exhibition/index.html b/templates/exhibition/index.html index 0b1c5b7..f369b30 100644 --- a/templates/exhibition/index.html +++ b/templates/exhibition/index.html @@ -11,11 +11,8 @@
-
+
-
-
{{ exhibition.title }} @@ -30,14 +27,16 @@ Назад - {% if exhibition.get('status',-1) == 2 %} -
- Выставка закончилась -
- {% else %} -
- Идет выставка +
+ {% if now > exhibition.date_finish %} + Закончилась + {% elif now > exhibition.datetime_start %} + Началась + {% else %} + Формируется + {% endif %}
+ {% if now < exhibition.date_finish %} До {{ (exhibition.date_finish.strftime('%d.%m.%Y')) if exhibition.date_finish else ''}} {% endif %}
@@ -95,6 +94,15 @@

{{post.title}}

+ {% if user and (user._id == post.user._id or 'partner' in user.get('roles',[]))%} + + {% endif %}

{{post.city or ''}}

@@ -109,7 +117,38 @@ {% endfor %}
- + + - - - - + {{ footer.section(user) }} {% endblock %} diff --git a/templates/profile/delivery_history.html b/templates/profile/delivery_history.html index d316f33..a527386 100644 --- a/templates/profile/delivery_history.html +++ b/templates/profile/delivery_history.html @@ -44,7 +44,7 @@ {{doc.status}}
{% endif %} - {% if doc.type == 'exhibition' and doc.get('exhibition') %} + {% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %}

{{doc.exhibition.title}}

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

@@ -54,6 +54,57 @@ {{doc.receiver.username}}
+ {% if doc.get('user') and user._id == doc.user._id%} +
+ Показать контакты + +
+{% endif %} {% else %} {% if doc.address %}

Адрес: {{doc.address}}

@@ -75,6 +126,57 @@ {{doc.sender.username}}
+ {% if user._id != doc.sender._id%} +
+ Показать контакты + +
+{% endif %} {% endif %} {% if doc.receiver %}
@@ -85,6 +187,57 @@ +7 *** *** {{ doc.receiver.phone[-4:] }}
+ {% if user._id != doc.receiver._id%} +
+ Показать контакты + +
+{% endif %} {% endif %} {% endif %}

diff --git a/templates/profile/delivery_old.html b/templates/profile/delivery_old.html index 3c564db..3a25f44 100644 --- a/templates/profile/delivery_old.html +++ b/templates/profile/delivery_old.html @@ -31,8 +31,16 @@ Посмотреть историю

{{doc.title}}

- - {% if doc.type == 'exhibition' and doc.get('exhibition') %} + {% if doc.status == 'На выставке' %} + + {% endif %} + {% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %}

{{doc.get('exhibition').title}}

@@ -41,6 +49,57 @@ {{doc.receiver.username}}
+ {% if doc.get('user') and user._id == doc.user._id%} +
+ Показать контакты + +
+{% endif %} {% else %} {% if doc.address %}

Адрес: {{doc.address}}

@@ -216,15 +275,47 @@
{{ profile_nav.profile_nav(user, 'delivery') }}
+ {% set statuses = ['Ожидает отправки', 'Возврат художнику'] %} + {% set statusAccept = ['Ожидает получения'] %} - {% set statuses = ['Ожидает отправки', 'Возврат художнику'] %} - {% if data | selectattr('status', 'in', statuses) | list | length > 0 %} + {% set first = + data + | selectattr('status', 'in', statuses) + | list + | length > 0 + %} + + {% set second = + ( + data + | selectattr('status', 'in', statusAccept) + | list + | length > 0 + ) + and + ( + data + | selectattr('receiver._id', 'equalto', user._id) + | list + | length > 0 + ) + %} + + {% if first or second %}
+ {% if first %}

Настройки отправки

-

Можно менять перед каждой отправкой картины

+
+ Выбрать все картины + + {% endif %}
{% endif %} @@ -250,12 +355,72 @@ {# Sticky bottom action bar #}
+ + {%endif%} {% if doc.get('type_')=='paint' %} {% set statuses = ['Ожидает отправки', 'Возврат художнику'] %} @@ -216,14 +251,7 @@ {{ render_doc(doc) }} {% endfor %} {# Sticky bottom action bar #} -
- - -
+ {#