This commit is contained in:
Vladislav 2026-07-20 07:51:01 -04:00
parent 8f312bc038
commit 4a44be2ed6
6 changed files with 283 additions and 91 deletions

View File

@ -102,6 +102,8 @@
"import email_task as email_task\n", "import email_task as email_task\n",
"from pydantic import EmailStr\n", "from pydantic import EmailStr\n",
"import secrets\n", "import secrets\n",
"from pymongo import ReturnDocument\n",
"\n",
"logging.basicConfig(level=logging.INFO)\n", "logging.basicConfig(level=logging.INFO)\n",
"logger = logging.getLogger(__name__)" "logger = logging.getLogger(__name__)"
] ]
@ -531,35 +533,109 @@
"source": [ "source": [
"@app.get(\"/cron/5\")\n", "@app.get(\"/cron/5\")\n",
"async def cron_every_five_min(token: str = Security(get_token)):\n", "async def cron_every_five_min(token: str = Security(get_token)):\n",
" exhibition = await exhibition_collection.find_one_and_update(\n", " now = datetime.now(timezone.utc)\n",
" {\"prod\": PROD, \"status\": 0, \"date_finish\": {\"$lt\": datetime.now(timezone.utc) + timedelta(hours=3)}},\n", " limit_dt = now + timedelta(hours=3)\n",
" {\"$set\": {\"status\": 2}}, return_document=False)\n", "\n",
" if exhibition:\n", " prod_filter = {\"prod\": PROD}\n",
" tasks = []\n", "\n",
" async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}):\n", " # 1) If there's an exhibition that is going to be finished soon -> move deliveries to \"Возврат художнику\"\n",
" new_doc = {**doc, 'delivery_type':'', 'track':'', 'status': 'Возврат художнику', 'receiver': doc.get('sender'), 'sender': doc.get('receiver')}\n", " exhibition_finished = await exhibition_collection.find_one_and_update(\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", " {**prod_filter, \"date_finish\": {\"$lt\": limit_dt}},\n",
" asyncio.gather(*tasks)\n", " {\"$set\": {\"status\": 2}},\n",
" else:\n", " return_document=ReturnDocument.AFTER,\n",
" exhibition = await exhibition_collection.find_one_and_update(\n", " )\n",
" {\"prod\": PROD, \"datetime_start\": {\"$lt\": datetime.now(timezone.utc) + timedelta(hours=3)}},\n", " if exhibition_finished:\n",
" {\"$set\": {\"status\": 1}}, return_document=False)\n", " exhibition_id_str = str(exhibition_finished[\"_id\"])\n",
" if exhibition:\n", "\n",
" if exhibition.get('vk') == [] or exhibition.get('tg') == []:\n", " async for doc in delivery_collection.find(\n",
" await service_social_exhibition(exhibition)\n", " {\"exhibition_id\": exhibition_id_str, \"status\": \"Получено\", **prod_filter},\n",
" tasks = []\n", " {\"_id\": 0},\n",
" async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}):\n", " ):\n",
" post = await posts_collection.find_one({'_id': ObjectId(doc.get('post_id'))})\n", " sender = doc.get(\"sender\")\n",
" new_doc = {**doc, 'exhibition': exhibition, 'status': 'На выставке', 'user': post.get('user'), 'post': post}\n", " post_id = doc.get(\"post_id\")\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", " exhibition_id = doc.get(\"exhibition_id\")\n",
" tasks.append(notify_in_profile(NotifyItem(\n", "\n",
" user_id=str(post.get('user',{}).get('_id')),\n", " new_doc = {\n",
" type_item='exhibtion',\n", " **doc,\n",
" title=f'Выставка {doc.get('exhibition',{}).get('title')}',\n", " \"delivery_type\": \"\",\n",
" link=f'/profile/exhibition',\n", " \"track\": \"\",\n",
" text=f'Ваши картины на выставке'\n", " \"status\": \"Возврат художнику\",\n",
" )))\n", " \"receiver\": sender,\n",
" asyncio.gather(*tasks)\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" " return {}\n"
] ]
}, },
@ -2946,7 +3022,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "44ea690c", "id": "aabb40cb",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -2956,9 +3032,16 @@
" current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n", " current_user: Dict = Security(get_current_user, scopes=[\"create:posts\"])\n",
"):\n", "):\n",
" await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')})\n", " await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')})\n",
" return {'ok': True} \n", " return {'ok': True} \n"
" \n", ]
"\n", },
{
"cell_type": "code",
"execution_count": null,
"id": "d38cafed",
"metadata": {},
"outputs": [],
"source": [
"@app.post(\"/api/post\", response_model=dict)\n", "@app.post(\"/api/post\", response_model=dict)\n",
"async def post_painting(\n", "async def post_painting(\n",
" background_tasks: BackgroundTasks,\n", " background_tasks: BackgroundTasks,\n",
@ -3013,9 +3096,16 @@
" return doc_out\n", " return doc_out\n",
" except Exception as e:\n", " except Exception as e:\n",
" print(e)\n", " print(e)\n",
" raise HTTPException(status_code=500, detail=str(e))\n", " raise HTTPException(status_code=500, detail=str(e))\n"
"\n", ]
"\n", },
{
"cell_type": "code",
"execution_count": null,
"id": "44ea690c",
"metadata": {},
"outputs": [],
"source": [
"@app.put(\"/api/post/{post_id}\", response_model=dict)\n", "@app.put(\"/api/post/{post_id}\", response_model=dict)\n",
"async def put_painting(\n", "async def put_painting(\n",
" background_tasks: BackgroundTasks,\n", " background_tasks: BackgroundTasks,\n",
@ -3067,7 +3157,6 @@
" if res is None:\n", " if res is None:\n",
" raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n", " raise HTTPException(status_code=404, detail=\"Картина не найдена\")\n",
"\n", "\n",
"\n",
" doc_out = {\n", " doc_out = {\n",
" \"_id\":post_id,\n", " \"_id\":post_id,\n",
" \"id\":post_id,\n", " \"id\":post_id,\n",
@ -3075,12 +3164,18 @@
" }\n", " }\n",
" doc_out = bson_clear(doc_out)\n", " doc_out = bson_clear(doc_out)\n",
" \n", " \n",
"\n",
" background_tasks.add_task(update_social_post, doc_out)\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", " background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media)\n",
" return doc_out\n", " return doc_out\n"
"\n", ]
"\n", },
{
"cell_type": "code",
"execution_count": null,
"id": "3f18f6a6",
"metadata": {},
"outputs": [],
"source": [
"@app.post(\"/api/posts/media\", response_model=dict)\n", "@app.post(\"/api/posts/media\", response_model=dict)\n",
"async def post_media(\n", "async def post_media(\n",
" medias: Optional[List[UploadFile]] = File(None),\n", " medias: Optional[List[UploadFile]] = File(None),\n",
@ -3096,7 +3191,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "abe852d2", "id": "9c463130",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
@ -3136,9 +3231,16 @@
" background_tasks.add_task(replace_news_medias, doc_out)\n", " background_tasks.add_task(replace_news_medias, doc_out)\n",
" return doc_out\n", " return doc_out\n",
" except Exception as e:\n", " except Exception as e:\n",
" raise HTTPException(status_code=500, detail=str(e))\n", " raise HTTPException(status_code=500, detail=str(e))\n"
"\n", ]
"\n", },
{
"cell_type": "code",
"execution_count": null,
"id": "de77638d",
"metadata": {},
"outputs": [],
"source": [
"@app.put(\"/api/news/{news_id}\", response_model=dict)\n", "@app.put(\"/api/news/{news_id}\", response_model=dict)\n",
"async def put_news(\n", "async def put_news(\n",
" background_tasks: BackgroundTasks,\n", " background_tasks: BackgroundTasks,\n",
@ -3177,9 +3279,16 @@
" **{k: v for k, v in update_doc.items()}\n", " **{k: v for k, v in update_doc.items()}\n",
" }\n", " }\n",
" doc_out = bson_clear(doc_out)\n", " doc_out = bson_clear(doc_out)\n",
" return doc_out\n", " return doc_out\n"
"\n", ]
"\n", },
{
"cell_type": "code",
"execution_count": null,
"id": "abe852d2",
"metadata": {},
"outputs": [],
"source": [
"@app.post(\"/api/news/media\", response_model=dict)\n", "@app.post(\"/api/news/media\", response_model=dict)\n",
"async def news_media(\n", "async def news_media(\n",
" medias: Optional[List[UploadFile]] = File(None),\n", " medias: Optional[List[UploadFile]] = File(None),\n",

138
main.py
View File

@ -41,6 +41,8 @@ from templates import templates
import email_task as email_task import email_task as email_task
from pydantic import EmailStr from pydantic import EmailStr
import secrets import secrets
from pymongo import ReturnDocument
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -335,35 +337,109 @@ async def cron_every_day(token: str = Security(get_token)):
@app.get("/cron/5") @app.get("/cron/5")
async def cron_every_five_min(token: str = Security(get_token)): async def cron_every_five_min(token: str = Security(get_token)):
exhibition = await exhibition_collection.find_one_and_update( now = datetime.now(timezone.utc)
{"prod": PROD, "status": 0, "date_finish": {"$lt": datetime.now(timezone.utc) + timedelta(hours=3)}}, limit_dt = now + timedelta(hours=3)
{"$set": {"status": 2}}, return_document=False)
if exhibition: prod_filter = {"prod": PROD}
tasks = []
async for doc in delivery_collection.find({'exhibition_id': str(exhibition.get('_id')), 'status': 'Получено'},{'_id':0}): # 1) If there's an exhibition that is going to be finished soon -> move deliveries to "Возврат художнику"
new_doc = {**doc, 'delivery_type':'', 'track':'', 'status': 'Возврат художнику', 'receiver': doc.get('sender'), 'sender': doc.get('receiver')} exhibition_finished = await exhibition_collection.find_one_and_update(
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)) {**prod_filter, "date_finish": {"$lt": limit_dt}},
asyncio.gather(*tasks) {"$set": {"status": 2}},
else: return_document=ReturnDocument.AFTER,
exhibition = await exhibition_collection.find_one_and_update( )
{"prod": PROD, "datetime_start": {"$lt": datetime.now(timezone.utc) + timedelta(hours=3)}}, if exhibition_finished:
{"$set": {"status": 1}}, return_document=False) exhibition_id_str = str(exhibition_finished["_id"])
if exhibition:
if exhibition.get('vk') == [] or exhibition.get('tg') == []: async for doc in delivery_collection.find(
await service_social_exhibition(exhibition) {"exhibition_id": exhibition_id_str, "status": "Получено", **prod_filter},
tasks = [] {"_id": 0},
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'))}) sender = doc.get("sender")
new_doc = {**doc, 'exhibition': exhibition, 'status': 'На выставке', 'user': post.get('user'), 'post': post} post_id = doc.get("post_id")
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)) exhibition_id = doc.get("exhibition_id")
tasks.append(notify_in_profile(NotifyItem(
user_id=str(post.get('user',{}).get('_id')), new_doc = {
type_item='exhibtion', **doc,
title=f'Выставка {doc.get('exhibition',{}).get('title')}', "delivery_type": "",
link=f'/profile/exhibition', "track": "",
text=f'Ваши картины на выставке' "status": "Возврат художнику",
))) "receiver": sender,
asyncio.gather(*tasks) "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 {} return {}
@ -2216,7 +2292,7 @@ async def post_delete(
): ):
await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}) await posts_collection.delete_one({'_id': ObjectId(post_id), 'user._id': current_user.get('_id')})
return {'ok': True} return {'ok': True}
@app.post("/api/post", response_model=dict) @app.post("/api/post", response_model=dict)
async def post_painting( async def post_painting(
@ -2326,7 +2402,6 @@ async def put_painting(
if res is None: if res is None:
raise HTTPException(status_code=404, detail="Картина не найдена") raise HTTPException(status_code=404, detail="Картина не найдена")
doc_out = { doc_out = {
"_id":post_id, "_id":post_id,
"id":post_id, "id":post_id,
@ -2334,7 +2409,6 @@ async def put_painting(
} }
doc_out = bson_clear(doc_out) doc_out = bson_clear(doc_out)
background_tasks.add_task(update_social_post, doc_out) background_tasks.add_task(update_social_post, doc_out)
background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media) background_tasks.add_task(replace_new_medias, bson_clear(doc_out), links_media)
return doc_out return doc_out

View File

@ -43,11 +43,14 @@
</div> </div>
<header class="c_77wsp b_qx462"> <header class="c_77wsp b_qx462">
<h2 class="c_aiv78 b_6jj0l">{{exhibition.title}}</h2> <h2 class="c_aiv78 b_6jj0l">{{exhibition.title}}</h2>
<a class="c_wcdb7 b_ofiw3" href="{% if exhibition.get('tizer_link') %}{{exhibition.tizer_link}}{% else %}/b/exhibition/{{ exhibition.tizer_file_id }}{% endif %}" rel="noopener noreferrer" style="width: 100%;">
<span class="b_j2ln2">Релиз выставки</span>
</a>
<p>Место проведения: {{ exhibition.get('place') }}</p> <p>Место проведения: {{ exhibition.get('place') }}</p>
{% if posts|length > 0 %} {% if posts|length > 0 %}
<p>Картины: {{posts|length}}</p> <p>Картины: {{posts|length}}</p>
{% endif %} {% 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), "Приглашаем на выставку") }}
</header> </header>
</div> </div>
<div style="grid-area: c;display: grid;gap: 10px;"> <div style="grid-area: c;display: grid;gap: 10px;">
@ -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_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_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_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{overflow: hidden;}
picture>img{width: 100%;object-fit: contain;} 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;} .b_ob59y { border-radius: 1em; height: 70px; min-width: 70px; max-width: 170px; vertical-align: top; object-fit: cover;}

View File

@ -60,6 +60,12 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<a class="c_aq9et" href="{% if exhibition.get('tizer_link') %}{{exhibition.tizer_link}}{% else %}/b/exhibition/{{ exhibition.tizer_file_id }}{% endif %}" rel="noopener noreferrer">
<span class="c_som9s">Релиз выставки</span>
</a>
<a class="c_aq9et" href="{{link}}" rel="noopener noreferrer">
<span class="c_som9s">Открыть</span>
</a>
<p>Место проведения: {{ exhibition.get('place') }}</p> <p>Место проведения: {{ exhibition.get('place') }}</p>
<div class="b_otttb"> <div class="b_otttb">
<span class="c_2fr39 b_tfnz2">С {{ ( exhibition.datetime_start.strftime('%d.%m.%Y')) if exhibition.datetime_start else ''}}</span> <span class="c_2fr39 b_tfnz2">С {{ ( exhibition.datetime_start.strftime('%d.%m.%Y')) if exhibition.datetime_start else ''}}</span>
@ -70,7 +76,7 @@
{% endif %} {% endif %}
<div class="fl"> <div class="fl">
<span class="c_2fr39 b_tfnz2" style="z-index: 2;"> <span class="c_2fr39 b_tfnz2" style="z-index: 2;">
{{ 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), "Приглашаем на выставку") }}
</span> </span>
</div> </div>

View File

@ -76,7 +76,7 @@
<div class="c_nb6gy floating-group file-group poster-group" id="file-block-1"> <div class="c_nb6gy floating-group file-group poster-group" id="file-block-1">
<div class="file-name-display" id="doc_name_1">Выберите файл</div> <div class="file-name-display" id="doc_name_1">Выберите файл</div>
<input type="file" id="tizer" name="tizer" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" required> <input type="file" id="tizer" name="tizer" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" required>
<label for="tizer" class="c_z3ybu floating-label">Тизер (PDF, DOC)</label> <label for="tizer" class="c_z3ybu floating-label">Релиз (PDF, DOC)</label>
<svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/> <path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/>
@ -89,7 +89,7 @@
<!-- Блок ссылки --> <!-- Блок ссылки -->
<div class="c_aihm6 floating-group" id="link-block-1" style="display: none;"> <div class="c_aihm6 floating-group" id="link-block-1" style="display: none;">
<input type="url" id="tizer_link" name="tizer_link" class="c_xhd5k" placeholder="https://"> <input type="url" id="tizer_link" name="tizer_link" class="c_xhd5k" placeholder="https://">
<label for="tizer_link" class="c_z3ybu floating-label">Тизер (ссылка)</label> <label for="tizer_link" class="c_z3ybu floating-label">Релиз (ссылка)</label>
<div id="link-error-1" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div> <div id="link-error-1" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div>
</div> </div>
@ -109,7 +109,7 @@
<div class="c_nb6gy floating-group file-group poster-group" id="file-block-2"> <div class="c_nb6gy floating-group file-group poster-group" id="file-block-2">
<div class="file-name-display" id="doc_name_2">Выберите файл</div> <div class="file-name-display" id="doc_name_2">Выберите файл</div>
<input type="file" id="rules" name="rules" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" required> <input type="file" id="rules" name="rules" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" required>
<label for="rules" class="c_z3ybu floating-label">Условия конкурса (PDF, DOC)</label> <label for="rules" class="c_z3ybu floating-label">Условия выставки (PDF, DOC)</label>
<svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/> <path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/>
@ -122,7 +122,7 @@
<!-- Блок ссылки --> <!-- Блок ссылки -->
<div class="c_aihm6 floating-group" id="link-block-2" style="display: none;"> <div class="c_aihm6 floating-group" id="link-block-2" style="display: none;">
<input type="url" id="rules_link" name="rules_link" class="c_xhd5k" placeholder="https://"> <input type="url" id="rules_link" name="rules_link" class="c_xhd5k" placeholder="https://">
<label for="rules_link" class="c_z3ybu floating-label">Условия конкурса (ссылка)</label> <label for="rules_link" class="c_z3ybu floating-label">Условия выставки (ссылка)</label>
<div id="link-error-2" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div> <div id="link-error-2" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div>
</div> </div>

View File

@ -59,7 +59,7 @@
<div class="c_nb6gy floating-group file-group poster-group" id="file-block-1"> <div class="c_nb6gy floating-group file-group poster-group" id="file-block-1">
<div class="file-name-display" id="doc_name_1">Выберите файл</div> <div class="file-name-display" id="doc_name_1">Выберите файл</div>
<input type="file" id="tizer" name="tizer" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" > <input type="file" id="tizer" name="tizer" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" >
<label for="tizer" class="c_z3ybu floating-label">Тизер (PDF, DOC)</label> <label for="tizer" class="c_z3ybu floating-label">Релиз (PDF, DOC)</label>
<svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/> <path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/> <path d="M14 2V8H20" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/>
@ -69,7 +69,7 @@
</div> </div>
<div class="c_aihm6 floating-group" id="link-block-1" style="display: none;"> <div class="c_aihm6 floating-group" id="link-block-1" style="display: none;">
<input type="url" id="tizer_link" name="tizer_link" class="c_xhd5k" placeholder="https://"> <input type="url" id="tizer_link" name="tizer_link" class="c_xhd5k" placeholder="https://">
<label for="tizer_link" class="c_z3ybu floating-label">Тизер (ссылка)</label> <label for="tizer_link" class="c_z3ybu floating-label">Релиз (ссылка)</label>
<div id="link-error-1" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div> <div id="link-error-1" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div>
</div> </div>
<div class="toggle-group"> <div class="toggle-group">
@ -85,7 +85,7 @@
<div class="c_nb6gy floating-group file-group poster-group" id="file-block-2"> <div class="c_nb6gy floating-group file-group poster-group" id="file-block-2">
<div class="file-name-display" id="doc_name_2">Выберите файл</div> <div class="file-name-display" id="doc_name_2">Выберите файл</div>
<input type="file" id="rules" name="rules" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" > <input type="file" id="rules" name="rules" class="c_4ykpn hidden-file" accept=".pdf,.doc,.docx" >
<label for="rules" class="c_z3ybu floating-label">Условия конкурса (PDF, DOC)</label> <label for="rules" class="c_z3ybu floating-label">Условия выставки (PDF, DOC)</label>
<svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="file-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/> <path d="M14 2H6C4.89543 2 4 2.89543 4 4V20C4 21.1046 4.89543 22 6 22H18C19.1046 22 20 21.1046 20 20V8L14 2Z" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/> <path d="M14 2V8H20" stroke="#5F1798" stroke-width="2" stroke-linejoin="round"/>
@ -95,7 +95,7 @@
</div> </div>
<div class="c_aihm6 floating-group" id="link-block-2" style="display: none;"> <div class="c_aihm6 floating-group" id="link-block-2" style="display: none;">
<input type="url" id="rules_link" name="rules_link" class="c_xhd5k" placeholder="https://"> <input type="url" id="rules_link" name="rules_link" class="c_xhd5k" placeholder="https://">
<label for="rules_link" class="c_z3ybu floating-label">Условия конкурса (ссылка)</label> <label for="rules_link" class="c_z3ybu floating-label">Условия выставки (ссылка)</label>
<div id="link-error-2" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div> <div id="link-error-2" class="error-message" style="display:none; color:#e74c3c; font-size:0.85em; margin-top:4px;"></div>
</div> </div>
<div class="c_aihm6 floating-group"> <div class="c_aihm6 floating-group">