Compare commits
11 Commits
db0cd8395c
...
cad8cb4998
| Author | SHA256 | Date | |
|---|---|---|---|
| cad8cb4998 | |||
| 56301e6c0e | |||
| 31a5d4059c | |||
| 7a92bacd61 | |||
| cbdbf53acd | |||
| 77a52bebc0 | |||
| 45d405b2e7 | |||
| 117073c0b1 | |||
| 4a05b50fee | |||
| c1d05b5984 | |||
| da15dc7fb1 |
50
main.ipynb
50
main.ipynb
@ -3842,6 +3842,8 @@
|
||||
" update_doc[\"status\"] = -1\n",
|
||||
" if payload.date_finish is not None:\n",
|
||||
" update_doc[\"date_finish\"] = payload.date_finish\n",
|
||||
" if datetime.now(timezone.utc) + timedelta(hours=3) > payload.datetime_start and datetime.now(timezone.utc) + timedelta(hours=3) < payload.date_finish:\n",
|
||||
" update_doc[\"status\"] = 0\n",
|
||||
" if payload.address is not None:\n",
|
||||
" await users_collection.update_one(\n",
|
||||
" { \"_id\": ObjectId(current_user[\"_id\"]) },\n",
|
||||
@ -4155,6 +4157,54 @@
|
||||
],
|
||||
"display_name": ".venv"
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "30acea7c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@app.get(\"/b/{bucket_name}/{file_id}/{new_w}/{quality}/{GaussianBlur_radius}\")\n",
|
||||
"async def get_file_with_config(\n",
|
||||
" bucket_name: str,\n",
|
||||
" file_id: str,\n",
|
||||
" new_w: int,\n",
|
||||
" quality: int,\n",
|
||||
" GaussianBlur_radius: int,\n",
|
||||
"):\n",
|
||||
" res = await stream_gridfs_file(file_id, bucket_name)\n",
|
||||
" if res is None:\n",
|
||||
" raise HTTPException(status_code=404, detail=\"File not found\")\n",
|
||||
"\n",
|
||||
" filename, content_type, file_length, iterator = res\n",
|
||||
"\n",
|
||||
" # Собираем все байты файла\n",
|
||||
" data = b\"\".join([chunk async for chunk in iterator()])\n",
|
||||
"\n",
|
||||
" # Обрабатываем\n",
|
||||
" processed = await build_composite(\n",
|
||||
" data,\n",
|
||||
" new_w=new_w,\n",
|
||||
" quality=quality,\n",
|
||||
" GaussianBlur_radius=GaussianBlur_radius,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" headers = {\n",
|
||||
" \"Content-Disposition\": content_disposition_header(filename, inline=True),\n",
|
||||
" \"Cache-Control\": \"public, max-age=3600\",\n",
|
||||
" \"ETag\": f'\"{bucket_name}:{file_id}:{new_w}:{quality}:{GaussianBlur_radius}\"',\n",
|
||||
" \"Vary\": \"Accept-Encoding\",\n",
|
||||
" \"Content-Length\": str(len(processed.getvalue())),\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" return StreamingResponse(\n",
|
||||
" processed,\n",
|
||||
" media_type=\"image/webp\",\n",
|
||||
" headers=headers,\n",
|
||||
" )"
|
||||
],
|
||||
"display_name": ".venv"
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
|
||||
41
main.py
41
main.py
@ -2918,6 +2918,8 @@ async def put_competition(
|
||||
update_doc["status"] = -1
|
||||
if payload.date_finish is not None:
|
||||
update_doc["date_finish"] = payload.date_finish
|
||||
if datetime.now(timezone.utc) + timedelta(hours=3) > payload.datetime_start and datetime.now(timezone.utc) + timedelta(hours=3) < payload.date_finish:
|
||||
update_doc["status"] = 0
|
||||
if payload.address is not None:
|
||||
await users_collection.update_one(
|
||||
{ "_id": ObjectId(current_user["_id"]) },
|
||||
@ -3195,6 +3197,45 @@ async def get_avatar(user_id: str):
|
||||
return StreamingResponse(iterator(), media_type=content_type, headers=headers)
|
||||
|
||||
|
||||
@app.get("/b/{bucket_name}/{file_id}/{new_w}/{quality}/{GaussianBlur_radius}")
|
||||
async def get_file_with_config(
|
||||
bucket_name: str,
|
||||
file_id: str,
|
||||
new_w: int,
|
||||
quality: int,
|
||||
GaussianBlur_radius: int,
|
||||
):
|
||||
res = await stream_gridfs_file(file_id, bucket_name)
|
||||
if res is None:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
filename, content_type, file_length, iterator = res
|
||||
|
||||
# Собираем все байты файла
|
||||
data = b"".join([chunk async for chunk in iterator()])
|
||||
|
||||
# Обрабатываем
|
||||
processed = await build_composite(
|
||||
data,
|
||||
new_w=new_w,
|
||||
quality=quality,
|
||||
GaussianBlur_radius=GaussianBlur_radius,
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Disposition": content_disposition_header(filename, inline=True),
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
"ETag": f'"{bucket_name}:{file_id}:{new_w}:{quality}:{GaussianBlur_radius}"',
|
||||
"Vary": "Accept-Encoding",
|
||||
"Content-Length": str(len(processed.getvalue())),
|
||||
}
|
||||
|
||||
return StreamingResponse(
|
||||
processed,
|
||||
media_type="image/webp",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
async def unsubscribe_user(
|
||||
email: str,
|
||||
token: Optional[str] = None
|
||||
|
||||
@ -214,7 +214,8 @@
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
/* ---------- Utility helpers (removed) ---------- */
|
||||
|
||||
/* Toggle visibility of a block (flex / none) */
|
||||
@ -359,7 +360,8 @@ document.getElementById('address').value = '{{ user.send_address[-1] }}'
|
||||
// @license-end
|
||||
</script>
|
||||
{% endif %}
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
function setTomorrowAtNoon() {
|
||||
const input = document.getElementById('datetime_start');
|
||||
const date_finish = document.getElementById('date_finish');
|
||||
|
||||
@ -107,7 +107,7 @@
|
||||
<div class="b_1tx4m">
|
||||
<picture>
|
||||
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ post.title }}" class="b_1tx4m"
|
||||
src="/b/posts/{{ post.medias[0] }}" data-src="/b/posts/{{ post.medias_original[0] }}">
|
||||
src="/b/posts/{{ post.medias_original[0] }}/500/90/80" data-src="/b/posts/{{ post.medias_original[0] }}">
|
||||
</picture>
|
||||
</div>
|
||||
<figcaption class="c_fzh03 b_bp0ws">
|
||||
|
||||
@ -54,7 +54,8 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
async function logout() {
|
||||
try {
|
||||
const resp = await fetch('/logout', {
|
||||
@ -73,7 +74,8 @@
|
||||
}
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
async function switchProfile() {
|
||||
try {
|
||||
const resp = await fetch('/api/switch-profile', {
|
||||
@ -89,7 +91,8 @@
|
||||
}
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const avatarInput = document.getElementById('avatar');
|
||||
const preview = document.getElementById('preview');
|
||||
|
||||
@ -215,7 +215,8 @@ document.getElementById('place').value = '{{ user.places[-1] }}'
|
||||
// @license-end
|
||||
</script>
|
||||
{% endif %}
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
/* ---------- Utility helpers (removed) ---------- */
|
||||
|
||||
/* Toggle visibility of a block (flex / none) */
|
||||
@ -370,7 +371,8 @@ function bindLinkValidation({
|
||||
// @license-end
|
||||
</script>
|
||||
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
function setTomorrowAtNoon() {
|
||||
const input = document.getElementById('datetime_start');
|
||||
const date_finish = document.getElementById('date_finish');
|
||||
@ -391,7 +393,8 @@ function bindLinkValidation({
|
||||
setTomorrowAtNoon();
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
|
||||
const titleInput = document.getElementById('title');
|
||||
const topicInput = document.getElementById('topic');
|
||||
|
||||
@ -166,7 +166,8 @@
|
||||
</article>
|
||||
</div>
|
||||
</form>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
function toggleBlock(id, show) {
|
||||
document.querySelector(id).style.display = show ? 'flex' : 'none';
|
||||
document.querySelector(id).querySelector('input').required = show ? true : false;
|
||||
@ -295,7 +296,8 @@ bindLinkValidation({
|
||||
});
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
function setTomorrowAtNoon() {
|
||||
const input = document.getElementById('datetime_start');
|
||||
const date_finish = document.getElementById('date_finish');
|
||||
@ -316,7 +318,8 @@ bindLinkValidation({
|
||||
setTomorrowAtNoon();
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
|
||||
const exhibition = JSON.parse({{exhibition|tojson}})
|
||||
const exhibitionId = exhibition._id
|
||||
|
||||
@ -27,7 +27,8 @@
|
||||
{{ articles.section(news) }}
|
||||
{{ footer.section(user) }}
|
||||
{% if pwa %}
|
||||
<script async>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
fetch('/api/me')
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<a href="/paintings/{{post._id}}" class="b_kbsvf">
|
||||
<picture>
|
||||
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ post.title }}" class="b_ya0t5"
|
||||
src="/b/posts/{{ post.medias[0] }}" data-src="/b/posts/{{ post.medias_original[0] }}">
|
||||
src="/b/posts/{{ post.medias_original[0] }}/500/90/80" data-src="/b/posts/{{ post.medias_original[0] }}">
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
@ -84,7 +84,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
|
||||
.b_rhbo8 { color: #5F1798; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
|
||||
.b_tbfd3 { color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; text-align: right; }
|
||||
.b_gagrr { align-items: center; align-self: stretch; display: flex; justify-content: space-between; }
|
||||
.b_kbsvf { align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
|
||||
.b_kbsvf { align-items: center; align-self: stretch; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
|
||||
.b_h57rq { align-items: flex-start; background: #FDFAFF; border-radius: 5px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; }
|
||||
.c_jy9j2{padding: 10px 20px; height: 226px;}
|
||||
/* Mobile */
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
{% block title %}Пользователи{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
{{ header.section(user) }}
|
||||
<section class="c_dg3k4">
|
||||
@ -16,22 +15,7 @@
|
||||
<div class="c_ru432">
|
||||
{{ profile_nav.profile_nav(user, 'users') }}
|
||||
<div class="c_tihtk" id="main">
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
input { padding: 8px; width: 300px; margin-bottom: 10px; border-bottom: 1px solid #5f1798;position: sticky;
|
||||
left: 10px;}
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
|
||||
th { background-color: #f4f4f4; }
|
||||
tr.hidden { display: none; }
|
||||
.highlight { background-color: #ffeb3b; padding: 2px 4px; border-radius: 3px; }
|
||||
</style>
|
||||
|
||||
|
||||
<input type="text" id="searchInput" placeholder="Поиск ..." onkeyup="searchTable()">
|
||||
|
||||
<table id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -134,15 +118,10 @@ function normalizeText(str) {
|
||||
}
|
||||
// @license-end
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ footer.section(user) }}
|
||||
{% endblock %}
|
||||
|
||||
@ -150,6 +129,42 @@ function normalizeText(str) {
|
||||
{{ header.style() }}
|
||||
{{ profile_nav.style() }}
|
||||
{{ footer.style() }}
|
||||
<style>
|
||||
input {
|
||||
padding: 8px;
|
||||
width: 300px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #5f1798;
|
||||
position: sticky;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
tr.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #ffeb3b;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@media (max-width: 699px) {
|
||||
.c_tihtk {
|
||||
|
||||
@ -139,7 +139,8 @@ document.querySelectorAll('.post').forEach(postEl => {
|
||||
// @license-end
|
||||
</script>
|
||||
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
const min_paintings = parseInt('{{competition.min_paintings}}')
|
||||
const max_paintings = parseInt('{{competition.max_paintings}}')
|
||||
const competition_id = '{{competition._id}}'
|
||||
@ -373,7 +374,8 @@ function loadAndClick() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
loadAndClick();
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const avatarInput = document.getElementById('avatar');
|
||||
|
||||
@ -228,7 +228,7 @@ picture > img {
|
||||
}
|
||||
.b_kbsvf {
|
||||
padding: 10px 20px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -150,7 +150,7 @@ picture > img {
|
||||
}
|
||||
.b_kbsvf {
|
||||
padding: 10px 20px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -539,7 +539,8 @@
|
||||
});
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
function toggleTrackField() {
|
||||
const deliveryType = document.getElementById('delivery_type');
|
||||
const trackGroup = document.getElementById('track-group');
|
||||
@ -643,7 +644,7 @@ picture > img {
|
||||
}
|
||||
.b_kbsvf {
|
||||
padding: 10px 20px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
{% block title %}Анкета{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
{{ header.section(user) }}
|
||||
<section class="c_dg3k4">
|
||||
@ -16,15 +15,14 @@
|
||||
<div class="c_ru432">
|
||||
{{ profile_nav.profile_nav(user, 'form') }}
|
||||
<div class="c_tihtk" id="main">
|
||||
|
||||
<div class="c_aihm6 floating-group">
|
||||
<label for="female_name_lastname" class="c_z3ybu floating-label">ФИО<span style="color:red">*</span></label>
|
||||
<input name="female_name_lastname" id="female_name_lastname" class="c_xhd5k" required />
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
|
||||
<label for="birthday" class="c_z3ybu floating-label">Дата рождения<span style="color:red">*</span></label>
|
||||
|
||||
<div class="c_aihm6 floating-group birthday">
|
||||
<select id="day">
|
||||
<option>1</option>
|
||||
@ -295,9 +293,8 @@ payload = {
|
||||
'print_merch': document.getElementById('print_merch').value,
|
||||
'own_workshop': document.getElementById('own_workshop').value,
|
||||
'workshop_tour': document.getElementById('workshop_tour').value,
|
||||
'biography': document.getElementById('biography').value}
|
||||
|
||||
|
||||
'biography': document.getElementById('biography').value
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/user/form`, {
|
||||
@ -328,11 +325,7 @@ window.location.reload();
|
||||
showToast({ message: 'Ошибка соединения с сервером!', status: 'error', duration: 2500 });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
// @license-end
|
||||
</script>
|
||||
{{ footer.section(user) }}
|
||||
|
||||
@ -790,7 +790,7 @@ picture > img {
|
||||
}
|
||||
.b_kbsvf {
|
||||
padding: 10px 20px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -173,7 +173,7 @@ picture > img {
|
||||
}
|
||||
.b_kbsvf {
|
||||
padding: 10px 20px;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -75,7 +75,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
|
||||
.b_rhbo8 { color: #5F1798; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
|
||||
.b_tbfd3 { color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; text-align: right; }
|
||||
.b_gagrr { align-items: center; align-self: stretch; display: flex; justify-content: space-between; }
|
||||
.b_kbsvf { align-items: flex-start; align-self: stretch; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
|
||||
.b_kbsvf { align-items: center; align-self: stretch; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
|
||||
.b_h57rq { align-items: flex-start; background: #FDFAFF; border-radius: 5px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; }
|
||||
.c_jy9j2{padding: 10px 20px; height: 226px;}
|
||||
/* Mobile */
|
||||
|
||||
@ -206,7 +206,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
document.querySelectorAll('.c_6cezu.b_6spaf').forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const parent = toggle.parentElement; // ваш parent
|
||||
|
||||
@ -208,7 +208,8 @@ failBtn.addEventListener('click', () => selectMember('fail'));
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
document.querySelectorAll('.c_6cezu.b_6spaf').forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const parent = toggle.parentElement; // ваш parent
|
||||
|
||||
@ -88,7 +88,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
const galerists = [
|
||||
{% for p in galerists %}
|
||||
{
|
||||
@ -100,7 +101,8 @@
|
||||
document.getElementById('filter-galerists').dataset.list=JSON.stringify(galerists)
|
||||
// @license-end
|
||||
</script>
|
||||
<script defer>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
const living_places = [
|
||||
{% for p in living_places %}
|
||||
{
|
||||
@ -112,7 +114,8 @@ document.getElementById('filter-galerists').dataset.list=JSON.stringify(galerist
|
||||
document.getElementById('filter-city').dataset.list=JSON.stringify(living_places)
|
||||
// @license-end
|
||||
</script>
|
||||
<script async>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const genres_count = {{genres|tojson}};
|
||||
let genres = [
|
||||
|
||||
55
utils.py
55
utils.py
@ -352,81 +352,72 @@ def is_safe_redirect(next_url: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
async def build_composite(data, scale: float = 1.5, new_w: int = 500, quality: int = 90, GaussianBlur_radius: int = 80):
|
||||
src_img = Image.open(BytesIO(data)).convert('RGB')
|
||||
async def build_composite(
|
||||
data: bytes,
|
||||
scale: float = 1.5,
|
||||
new_w: int = 500,
|
||||
quality: int = 90,
|
||||
GaussianBlur_radius: int = 80,
|
||||
) -> BytesIO:
|
||||
src_img = Image.open(BytesIO(data)).convert("RGB")
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 2️⃣ Параметры конечного результата
|
||||
# --------------------------------------------------------------
|
||||
new_h = int(new_w / scale) # высота для соотношения 3:2 (≈333)
|
||||
canvas_sz = (new_w, new_h) # размер «фонового» холста
|
||||
canvas_sz = (new_w, new_h)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 3️⃣ Фоновое изображение – cover + blur(3)
|
||||
# --------------------------------------------------------------
|
||||
# - «cover» = заполняем весь холст, обрезая лишнее,
|
||||
# сохраняем пропорции оригинала.
|
||||
# - Затем делаем размытие радиусом 3.
|
||||
# --------------------------------------------------------------
|
||||
# Сначала масштабируем так, чтобы одна из сторон полностью покрывала холст
|
||||
src_ratio = src_img.width / src_img.height
|
||||
canvas_ratio = new_w / new_h
|
||||
|
||||
if src_ratio > canvas_ratio: # изображение более «широкое»
|
||||
# масштабируем по высоте, ширина будет больше холста → обрезаем по горизонтали
|
||||
# --------------------------------------------------------------
|
||||
# 3️⃣ Фоновое изображение – cover + blur (optional)
|
||||
# --------------------------------------------------------------
|
||||
if src_ratio > canvas_ratio:
|
||||
scale_h = new_h
|
||||
scale_w = int(scale_h * src_ratio)
|
||||
else: # изображение более «высокое» или точное соотношение
|
||||
else:
|
||||
scale_w = new_w
|
||||
scale_h = int(scale_w / src_ratio)
|
||||
|
||||
# масштабируем
|
||||
bg_resized = src_img.resize((scale_w, scale_h), Image.LANCZOS)
|
||||
|
||||
# обрезаем центр, чтобы получить точно new_w × new_h
|
||||
left = (scale_w - new_w) // 2
|
||||
top = (scale_h - new_h) // 2
|
||||
right = left + new_w
|
||||
bottom = top + new_h
|
||||
bg_cropped = bg_resized.crop((left, top, right, bottom))
|
||||
|
||||
# размытие
|
||||
bg_blurred = bg_cropped.filter(ImageFilter.GaussianBlur(radius=GaussianBlur_radius))
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 4️⃣ Переднее изображение – contain + центрировать
|
||||
# --------------------------------------------------------------
|
||||
# - «contain» = вписать изображение в границы canvas,
|
||||
# полностью показывая его, оставляя пустые поля (прозрачные)
|
||||
# --------------------------------------------------------------
|
||||
# Находим масштаб, чтобы изображение полностью поместилось
|
||||
if src_ratio > canvas_ratio:
|
||||
# ограничиваем ширину
|
||||
fore_w = new_w
|
||||
fore_h = int(fore_w / src_ratio)
|
||||
else:
|
||||
# ограничиваем высоту
|
||||
fore_h = new_h
|
||||
fore_w = int(fore_h * src_ratio)
|
||||
|
||||
fore_resized = src_img.resize((fore_w, fore_h), Image.LANCZOS)
|
||||
|
||||
# Создаём прозрачный холст того же размера, что и фон
|
||||
final_img = Image.new('RGB', canvas_sz, (0, 0, 0))
|
||||
# --------------------------------------------------------------
|
||||
# 5️⃣ Сборка
|
||||
# --------------------------------------------------------------
|
||||
final_img = Image.new("RGB", canvas_sz, (255, 255, 255))
|
||||
|
||||
# Сначала помещаем размытый фон
|
||||
if GaussianBlur_radius:
|
||||
bg_blurred = bg_cropped.filter(ImageFilter.GaussianBlur(radius=GaussianBlur_radius))
|
||||
final_img.paste(bg_blurred, (0, 0))
|
||||
# else: оставляем чёрный фон (или можно вставить bg_cropped без blur)
|
||||
|
||||
# Затем — переднее изображение, центрированное
|
||||
offset_x = (new_w - fore_w) // 2
|
||||
offset_y = (new_h - fore_h) // 2
|
||||
final_img.paste(fore_resized, (offset_x, offset_y))
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 5️⃣ Возврат / сохранение
|
||||
# 6️⃣ Возврат
|
||||
# --------------------------------------------------------------
|
||||
out_bytes = BytesIO()
|
||||
# final_img.save(out_bytes, format='JPEG', quality=85)
|
||||
final_img.save(out_bytes, format="WEBP", quality=quality, method=6)
|
||||
out_bytes.seek(0)
|
||||
return out_bytes # JPEG‑байты готового композитного изображения
|
||||
return out_bytes
|
||||
|
||||
Loading…
Reference in New Issue
Block a user