Не подтверждена Коммит 34167fbd создал по автору AdLeGeR's avatar AdLeGeR Зафиксировано автором GitHub
Просмотр файлов

Merge pull request #173 from RushanCources/issue169_announcements-covers

<Issue169-announcements_covers> to <main>
владельцы f0f9fc34 7c94c0b3
......@@ -7,10 +7,11 @@ def allowed_users(allowed_roles=None):
def decorator(view_func):
def wrapper_func(request, *args, **kwargs):
if str(request.user) == "AnonymousUser":
return HttpResponsePermanentRedirect('/announcements')
group = request.user.role
if group in allowed_roles:
return view_func(request, *args, **kwargs)
return HttpResponsePermanentRedirect('/announcements')
return wrapper_func
return decorator
from django import template
import urllib.parse
register = template.Library()
......@@ -6,3 +7,8 @@ register = template.Library()
@register.filter
def split(value):
return value.split('/')[-1]
@register.filter
def encode(value):
return urllib.parse.unquote(value)
......@@ -9,5 +9,7 @@ urlpatterns = [
path('redactor/editannouncement/<int:id>', views.editannouncement, name='editannouncement'),
path('search/', views.search, name='search'),
path('<int:id>', views.announcement, name='announcement'),
path('delete/<int:id>', views.delete_announcement, name='delete')
path('delete/<int:id>', views.delete_announcement, name='delete'),
path('upload_image/', views.upload_image, name='upload_image'),
path('delete_cover/', views.delete_cover, name='delete_cover'),
]
\ Нет новой строки в конце файла
from django.shortcuts import render
from django.db.models import Q
from django.http import HttpResponse, HttpResponsePermanentRedirect
from django.http import HttpResponsePermanentRedirect
from .models import Announcement
from datetime import date
from .decorators import allowed_users
......@@ -9,25 +9,22 @@ from .forms import AnnouncementForm
from django.core.paginator import Paginator
from django.conf import settings
from pathlib import Path
from django.http import JsonResponse
import os
from django.core.files.storage import FileSystemStorage
import urllib.parse
def index(request):
"""Отвечает за рендер шаблона главной страницы"""
group = None
superuser = False
anns = Announcement.objects.all()
anns = Announcement.objects.all().order_by('-is_pinned')
paginator = Paginator(anns, 20) # Сколько объявлений на странице
page_number = request.GET.get('page')
page_announcements = paginator.get_page(page_number)
if request.user.groups.exists():
group = request.user.groups.all()[0].name
if group in ['Teacher', 'admin']:
superuser = True
data = {'superuser': superuser, 'page_announcements': page_announcements, 'count': anns.count()}
data = {'page_announcements': page_announcements, 'count': anns.count()}
return render(request, 'dec/dec.html', context=data)
......@@ -87,6 +84,12 @@ def editor(request, id):
try:
announcement = Announcement.objects.get(id=id)
if request.user.id == announcement.author_id or request.user.role == 'Администратор':
pass
else:
return HttpResponsePermanentRedirect('/announcements')
initial_data = {
'title': announcement.title,
'body': announcement.body,
......@@ -135,7 +138,7 @@ def editannouncement(request, id):
files_to_delete = request.POST.get('file_id_to_delete')
image_url = request.POST.get('image_url')
if int(files_to_delete) is not -1:
if files_to_delete != '-1':
if ',' in files_to_delete:
files_to_delete = files_to_delete.split(',')
......@@ -178,7 +181,7 @@ def search(request):
query_filter |= Q(title__icontains=word) | Q(body__icontains=word) | Q(
author__first_name__icontains=word) | Q(author__last_name__icontains=word)
anns = Announcement.objects.filter(query_filter)
anns = Announcement.objects.filter(query_filter).order_by('-is_pinned')
paginator = Paginator(anns, 20) # Сколько объявлений на странице
page_number = request.GET.get('page')
......@@ -187,11 +190,13 @@ def search(request):
data = {
'page_announcements': page_announcements,
'search_value': query,
'count': anns.count(),
}
return render(request, 'dec/dec.html', context=data)
def announcement(request, id):
"""Отвечает за рендер шаблона объявления (не готово)"""
try:
announcement = Announcement.objects.get(id=id)
......@@ -212,6 +217,9 @@ def delete_announcement(request, id):
if request.method != 'GET':
return render(request, 'WrongData.html')
if request.user.id != Announcement.objects.get(id=id).author_id and request.user.role != 'Администратор':
return HttpResponsePermanentRedirect('/announcements')
announcement = Announcement.objects.get(id=id)
files = announcement.files.all()
......@@ -223,3 +231,60 @@ def delete_announcement(request, id):
announcement.delete()
return HttpResponsePermanentRedirect('/announcements')
@allowed_users(allowed_roles=['Учитель', 'Администратор'])
def upload_image(request):
"""Загружает обложку на сервер и отправляет response в ajax"""
if request.method == 'POST':
image = request.FILES['image']
valid_extensions = ['.png', '.jpg', '.jpeg', '.pjp', '.jfif', '.svgz', '.jxl', '.ico', '.tiff', '.avif', '.svg', '.tif', '.gif', '.xbm', '.pjpeg', '.bmp', '.webp']
file_extension = os.path.splitext(image.name)[1].lower()
if file_extension in valid_extensions:
filename = os.path.join(settings.MEDIA_ROOT, 'covers', get_unique_filename(image.name, os.path.join(settings.MEDIA_ROOT, 'covers')))
with open(filename, 'wb') as destination:
for chunk in image.chunks():
destination.write(chunk)
image_url = os.path.join(settings.MEDIA_URL, 'covers/', image.name)
return JsonResponse({'success': True, 'image_url': image_url})
else:
return JsonResponse({'success': False, 'error': 'Invalid file format'})
return JsonResponse({'success': False})
@allowed_users(allowed_roles=['Учитель', 'Администратор'])
def delete_cover(request):
"""Удаляет обложку с сервера и отправляет response в ajax"""
if request.method == 'POST':
cover_url = urllib.parse.unquote(request.POST.get('cover_url')) # Раскодируем для того чтобы ничего не сломалось
fs = FileSystemStorage(location=settings.MEDIA_ROOT)
file_path = fs.path(str(settings.BASE_DIR).replace('\\', '/') + cover_url)
try:
fs.delete(file_path)
return JsonResponse({'success': True})
except FileNotFoundError:
return JsonResponse({'success': False, 'message': 'File not found.'})
except Exception as e:
return JsonResponse({'success': False, 'message': str(e)})
return JsonResponse({'success': False})
def get_unique_filename(base_filename, directory):
"""Обеспечивает уникальное имя каждой обложки"""
filename, file_extension = os.path.splitext(base_filename)
i = 1
new_filename = base_filename
while os.path.exists(os.path.join(directory, new_filename)):
new_filename = f"{filename}_{i:02d}{file_extension}"
i += 1
return new_filename
......@@ -86,7 +86,6 @@
}
.create-link:hover > span {
padding-right: 5px;
max-width: 500px;
transition: max-width 0.3s cubic-bezier(1, 0, 1, 0);
}
......@@ -150,6 +149,22 @@
fill: #bdbdbd;
}
.announ-delete-btn {
width: 25px;
height: 25px;
padding: 2px;
background: none;
border: none;
outline: none;
cursor: pointer;
border-radius: 5px;
transition: background 0.3s linear;
}
.announ-delete-btn:hover {
background: rgba(0,0,0,.1);
}
.middle-info {
display: flex;
justify-content: space-between;
......@@ -293,6 +308,71 @@
background: #3e5e96;
}
.delete-div {
position: fixed;
display: none;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 20px 45px;
min-height: 300px;
z-index: 10;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
background: #fff;
border-radius: 5px;
}
.delete-active {
display: flex;
}
.delete-title {
margin: 0;
margin-bottom: 10px;
font-size: 20px;
}
.delete-btns {
margin-top: auto;
width: 100%;
display: flex;
justify-content: space-between;
}
.delete-btn {
padding: 10px 30px;
outline: none;
border: none;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
background: #103A84;
color: #fff;
transition: background 0.3s linear;
}
.delete-btn:hover {
background: #0d306d;
}
.delete-btn:active {
background: #3e5e96;
}
.back-form {
display: none;
top: 0;
left: 0;
position: fixed;
z-index: 2;
width: 100vw;
height: 100vh;
background: #00000072;
}
@media (max-width: 2000px){
/* стили для xs-устройств */
......
......@@ -179,33 +179,22 @@
}
.covers-item {
position: relative;
width: calc(100% / 3);
height: 100px;
}
.covers-content {
width: 100%;
height: 100%;
background: no-repeat center center;
background-size: cover;
cursor: pointer;
transition: transform 0.3s linear, box-shadow 0.3s linear;
}
.covers-item:hover {
transform: scale(1.5);
box-shadow: 0px 0px 13px 0px #000000;
opacity: 0.8;
}
.item1 {
background-image: url('../../img/announcements/covers/1.jpg');
}
.item2 {
background-image: url('../../img/announcements/covers/2.jpg');
}
.item3 {
background-image: url('../../img/announcements/covers/3.jpg');
}
.item4 {
background-image: url('../../img/announcements/covers/4.jpg');
.covers-content:hover {
opacity: 1;
}
.covers-selected {
......@@ -245,6 +234,28 @@
margin-left: 5px;
}
.covers-button-delete {
padding: 0;
position: absolute;
top: 0;
left: 0;
border: none;
outline: none;
background: rgba(255, 255, 255, .4);
width: 30px;
height: 30px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background .3s linear;
}
.covers-button-delete:hover {
background: rgba(255, 255, 255, 1);
}
.preview {
display: flex;
border-radius: 9px;
......@@ -261,7 +272,6 @@
bottom: 0;
right: 0;
padding: 10px 30px;
background: transparent;
outline: none;
border: none;
font-size: 16px;
......@@ -280,58 +290,57 @@
background: #3e5e96;
}
.url-cover {
padding: 20px 30px;
.covers-delete-div {
position: fixed;
display: none;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 20px 45px;
min-height: 300px;
z-index: 10;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
row-gap: 50px;
align-items: stretch;
justify-content: space-between;
width: 80%;
max-width: 700px;
z-index: -10;
opacity: 0;
background: #ffffff;
border-radius: 10px;
transform: translate(-50%,-50%);
background: #fff;
border-radius: 5px;
}
.url-active {
z-index: 10;
opacity: 1;
.covers-active {
display: flex;
}
.url-title {
.delete-title {
margin: 0;
margin-bottom: 5px;
font-size: 22px;
margin-bottom: 10px;
font-size: 20px;
}
.url-span {
.delete-subtitle {
display: block;
margin-bottom: 25px;
color: #ff1b24;
font-size: 14px;
font-weight: 300;
text-align: center;
}
.url-input {
padding: 10px;
border-radius: 5px;
background: #FFFFFF;
width: 100%;
border: 1px solid #00000030;
font-size: 16px;
line-height: 20px;
.cover-delete {
margin-bottom: 25px;
height: 250px;
width: 250px;
background: no-repeat center center;
background-size: cover;
}
.url-btns {
.delete-btns {
margin-top: auto;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.url-btns > button {
.delete-btn {
padding: 10px 30px;
outline: none;
border: none;
......@@ -343,11 +352,11 @@
transition: background 0.3s linear;
}
.url-btns > button:hover {
.delete-btn:hover {
background: #0d306d;
}
.url-btns > button:active {
.delete-btn:active {
background: #3e5e96;
}
......
let page = 2; // Start from page 2 since page 1 is already loaded
function checkLoadMoreButtonVisibility() {
let loadedAnnouncements = $('.announ').length;
if (loadedAnnouncements >= totalAnnouncements) {
$("#load-more-button").hide();
} else {
$("#load-more-button").show();
}
}
checkLoadMoreButtonVisibility(); // Initially check button visibility
$("#load-more-button").click(function () {
let query = encodeURIComponent(searchValue);
$.get('?q=' + query + '&page=' + page, function (data) {
let newAnnouncements = $(data).find('.announ');
if (newAnnouncements.length > 0) {
$("#FSL").append(newAnnouncements);
page++;
checkLoadMoreButtonVisibility(); // Check and update button visibility
} else {
$("#load-more-button").hide();
}
});
});
let descr = $('.announ-descr');
for (let i = 0; i < descr.length; i++) {
if (descr[i].offsetHeight > 250) {
$($('.announ-btn')[i]).addClass('announ-btn-active');
$(descr[i]).addClass('announ-descr-close');
}
}
$('.btn-dec').on('click', function () {
let ul = this.parentNode.childNodes[3];
if ($(ul).hasClass('ul-open')) {
$(ul).removeClass('ul-open');
} else {
$(ul).addClass('ul-open');
}
});
$('.announ-btn').on('click', function () {
descr = this.parentNode.childNodes[5];
if ($(descr).hasClass('announ-descr-close')) {
$(descr).removeClass('announ-descr-close');
$(descr).addClass('announ-descr-open');
$(this).html('Скрыть');
} else if ($(descr).hasClass('announ-descr-open')) {
$(descr).removeClass('announ-descr-open');
$(descr).addClass('announ-descr-close');
$(this).html('Подробнее');
}
});
function del_open(url) {
$('.delete-div').addClass('delete-active');
$('.back-form').css({'display' : 'block'});
$('.delete-btn-link').attr('href', url);
}
function del_exit() {
$('.delete-div').removeClass('delete-active');
$('.back-form').css({'display' : 'none'});
}
function del_active() {
del_exit();
}
\ Нет новой строки в конце файла
......@@ -20,4 +20,18 @@ let id_arr = [];
function file_id_update(id) {
id_arr.push(id);
$('.fitd').val(id_arr);
}
let url = $('#id_image_url').val();
let covers = $('.covers-content');
let preview_img = $('.announ-img').attr('style');
for (let i = 0; i < covers.length; i++) {
let styles = $(covers[i]).attr('style');
styles = styles.replace("background-image: url('", '', 1);
styles = styles.replace("')", '', 1);
if (preview_img.includes(styles)) {
$(covers[i]).click();
}
}
\ Нет новой строки в конце файла
......@@ -94,12 +94,12 @@ function file_remove(is_load) {
});
}
$('.covers-item').on('click', function() {
$('.covers-list').on('click', '.covers-content', function() {
let url = $(this).css('background-image');
let input_url = url.slice(url.indexOf('url('));
$('.covers-selected').css({'display' : 'none'});
$(this.childNodes[1]).css({'display' : 'flex'});
$(this).find('.covers-selected').css({'display' : 'flex'});
$('#id_image_url').val(input_url);
......@@ -144,26 +144,23 @@ function preview_update(el, block) {
}
}
function url_open() {
$('.url-cover').addClass('url-active');
let del_el;
function del_open(el) {
del_el = el;
$('.covers-delete-div').addClass('covers-active');
$('.back-form').css({'display' : 'block'});
let url = $(el.parentNode).find('.covers-content').attr('style').replace('background-image:', '');
$('.cover-delete').css({ 'background-image': url });
}
function url_exit() {
$('.url-cover').removeClass('url-active');
function del_exit() {
$('.covers-delete-div').removeClass('covers-active');
$('.back-form').css({'display' : 'none'});
$('.url-input').val('');
}
function url_save() {
let url = $('.url-input').val().trim();
if(url != '') {
url = 'url(' + url + ')';
$('.announ-img').css({'background-image' : url});
$('#id_image_url').val(url);
url_exit();
} else {
alert('Вставьте ссылку на обложку!');
}
function del_active() {
$(del_el).addClass('delete-active');
$(del_el).click();
del_exit();
}
\ Нет новой строки в конце файла
......@@ -13,14 +13,14 @@
<input value="{{ search_value }}" name="q" class="search-input" type="text" placeholder="Поиск...">
<button class="search-button"></button>
</div>
{% comment %} {% if user.role == administrator or user.role == teacher %} {% endcomment %}
{% if user.role == 'Администратор' or user.role == 'Учитель' %}
<a href="{% url 'redactor' %}" class="create-link">
<span>Создать объявление</span>
<svg width="20" height="20" viewBox="0 0 200 200" style="transform: rotate(45deg)">
<path d="m114 100 49-49a9.9 9.9 0 0 0-14-14l-49 49-49-49a9.9 9.9 0 0 0-14 14l49 49-49 49a9.9 9.9 0 0 0 14 14l49-49 49 49a9.9 9.9 0 0 0 14-14Z" fill="#FFFFFF"></path>
</svg>
</a>
{% comment %} {% endif %} {% endcomment %}
{% endif %}
</form>
</div>
......@@ -30,11 +30,20 @@
<div class="announ-content" id="{{ announcement.id }}">
<div class="top-info">
<div class="top-info-text">
{% if announcement.updated_at == announcement.created_at %}
<span class="date">c <span class="date-start">{{ announcement.created_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% else %}
<span class="change">изм.</span>
<span class="date">c <span class="date-start">{{ announcement.updated_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% if announcement.date_of_expiring %}
{% if announcement.updated_at == announcement.created_at %}
<span class="date">c <span class="date-start">{{ announcement.created_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% else %}
<span class="change">изм.</span>
<span class="date">c <span class="date-start">{{ announcement.updated_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% endif %}
{% else %}
{% if announcement.updated_at == announcement.created_at %}
<span class="date">{{ announcement.created_at }}</span>
{% else %}
<span class="change">изм.</span>
<span class="date">{{ announcement.updated_at }}</span>
{% endif %}
{% endif %}
</div>
<div style="display: flex; column-gap: 15px;">
......@@ -50,16 +59,17 @@
{% endif %}
</button>
{% if announcement.author == user.username or user.role == 'Администратор' %}
<a href="{% url 'delete' announcement.id %}" class="delete-btn" title="Удалить объявление">
<svg viewBox="0 0 200 200" width="22px" height="22px"><path d="m114 100 49-49a9.9 9.9 0 0 0-14-14l-49 49-49-49a9.9 9.9 0 0 0-14 14l49 49-49 49a9.9 9.9 0 0 0 14 14l49-49 49 49a9.9 9.9 0 0 0 14-14Z" fill="#f05454"></path></svg>
</a>
{% if announcement.author_id == user.id or user.role == 'Администратор' %}
<button class="announ-delete-btn" onclick="del_open(`{% url 'delete' announcement.id %}`)">
<svg viewBox="0 0 200 200" width="20px" height="20px"><path d="m114 100 49-49a9.9 9.9 0 0 0-14-14l-49 49-49-49a9.9 9.9 0 0 0-14 14l49 49-49 49a9.9 9.9 0 0 0 14 14l49-49 49 49a9.9 9.9 0 0 0 14-14Z" fill="#f05454"></path></svg>
</button>
{% endif %}
</div>
</div>
<div class="middle-info">
<h2 class="announ-title">{{ announcement.title }}</h2>
{% if announcement.author_id == user.id or user.role == 'Администратор' %}
<a href="{% url 'redactor' %}/{{ announcement.id }}">
<svg height="20px" version="1.1" viewBox="0 0 18 18" width="20px">
<g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1">
......@@ -73,6 +83,7 @@
</g>
</svg>
</a>
{% endif %}
</div>
<p class="announ-descr">{{ announcement.body }}</p>
<button class="announ-btn">Подробнее</button>
......@@ -80,11 +91,11 @@
<div>
<a href="#" class="announ-author">{{ announcement.author }}</a>
</div>
{% load split_filter %}
{% load filters %}
<ul class="announ-files-list">
{% for file in announcement.files.all %}
<li class="announ-files-item">
<a href="{{ file.file.url }}" class="announ-files-link">{{ file.file.url|split }}</a>
<a href="{{ file.file.url }}" class="announ-files-link">{{ file.file.url|split|encode }}</a>
</li>
{% endfor %}
</ul>
......@@ -94,76 +105,22 @@
</div>
{% endfor %}
</div>
<script>
$('.btn-dec').on('click', function () {
let ul = this.parentNode.childNodes[3];
if ($(ul).hasClass('ul-open')) {
$(ul).removeClass('ul-open');
} else {
$(ul).addClass('ul-open');
}
});
</script>
<div class="back-form"></div>
<!-- Кнопка для подгрузки новых объявлений, если их будет больше 20 (Надо сделать красивой) -->
<button id="load-more-button" class="load-btn">Загрузить ещё</button>
<!-- Почти весь скрипт ниже сделан с помощью ChatGPT потому что я не шарю в джаваскрипте, поправьте если плохо -->
<!-- Все что он делает - реализует функционал страничек объявлений, добавляя кнопку на подгрузку объявлений, если их будет слишком много. По идее можно ничего не трогать, все работает -->
<div class="delete-div">
<p class="delete-title">Вы уверены, что хотите удалить объявление?</p>
<div class="delete-btns">
<button class="delete-btn" type="button" onclick="del_exit()">Отмена</button>
<a href="#" class="delete-btn delete-btn-link">Удалить</a>
</div>
</div>
<script>
$(document).ready(function() {
var page = 2; // Start from page 2 since page 1 is already loaded
var totalAnnouncements = {{ count }};
function checkLoadMoreButtonVisibility() {
var loadedAnnouncements = $('.announ').length;
if (loadedAnnouncements >= totalAnnouncements) {
$("#load-more-button").hide();
} else {
$("#load-more-button").show();
}
}
checkLoadMoreButtonVisibility(); // Initially check button visibility
$("#load-more-button").click(function() {
var query = encodeURIComponent('{{ search_value }}');
$.get('?q=' + query + '&page=' + page, function(data) {
var newAnnouncements = $(data).find('.announ');
if (newAnnouncements.length > 0) {
$("#FSL").append(newAnnouncements);
page++;
checkLoadMoreButtonVisibility(); // Check and update button visibility
} else {
$("#load-more-button").hide();
}
});
});
let descr = $('.announ-descr');
for (let i = 0; i < descr.length; i++) {
if (descr[i].offsetHeight > 250) {
$($('.announ-btn')[i]).addClass('announ-btn-active');
$(descr[i]).addClass('announ-descr-close');
}
}
$('.announ-btn').on('click', function() {
descr = this.parentNode.childNodes[5];
if ($(descr).hasClass('announ-descr-close')) {
$(descr).removeClass('announ-descr-close');
$(descr).addClass('announ-descr-open');
$(this).html('Скрыть');
} else if ($(descr).hasClass('announ-descr-open')) {
$(descr).removeClass('announ-descr-open');
$(descr).addClass('announ-descr-close');
$(this).html('Подробнее');
}
});
});
let totalAnnouncements = {{ count }};
let searchValue = '{{ search_value }}';
</script>
<script src="{% static 'js/announcement/dec.js' %}"></script>
{% endblock content %}
......@@ -25,11 +25,11 @@
<input type="file" id="add_file" onchange="new_file()" class="file-input">
{{ form.files }}
{{ form.file_id_to_delete }}
{% load split_filter %}
{% load filters %}
<ul class="file-list">
{% for file in announcement.files.all %}
<li title="{{ file.file.url|split }}" class="file-item">
{{ file.file.url|split }}
{{ file.file.url|split|encode }}
<div class="file-item-div" onclick="file_id_update({{ file.id }})"></div>
</li>
{% endfor %}
......@@ -47,9 +47,9 @@
</div>
</div>
<div class="label label-cover"><span>Обложка:</span>
<button class="covers-btn" onclick="url_open()" type="button">Добавить обложку
<svg width="16" height="16" viewBox="0 0 200 200"
style="transform: rotate(45deg)">
<input type="file" accept="image/*" name="image" id="image-upload-input" style="display: none;">
<button class="covers-btn" type="button" onclick="uploadImage()">Добавить обложку
<svg width="16" height="16" viewBox="0 0 200 200" style="transform: rotate(45deg)">
<path
d="m114 100 49-49a9.9 9.9 0 0 0-14-14l-49 49-49-49a9.9 9.9 0 0 0-14 14l49 49-49 49a9.9 9.9 0 0 0 14 14l49-49 49 49a9.9 9.9 0 0 0 14-14Z"
fill="#5A88FF"></path>
......@@ -58,11 +58,17 @@
{{ form.image_url }}
<ul class="covers-list">
{% for cover in covers %}
<li class="covers-item" style="background-image: url('{{cover}}');">
<div class="covers-selected">
<svg class="covers-selected-svg" style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff"> <g/><g><path d="M18.3,6.3L9.1,16.4l-2.3-3c-0.3-0.4-1-0.5-1.4-0.2c-0.4,0.3-0.5,1-0.2,1.4l3,4C8.4,18.8,8.7,19,9,19c0,0,0,0,0,0 c0.3,0,0.5-0.1,0.7-0.3l10-11c0.4-0.4,0.3-1-0.1-1.4C19.3,5.9,18.6,5.9,18.3,6.3z"/></g> </svg>
<li class="covers-item">
<div class="covers-content" style="background-image: url('{{cover}}')">
<div class="covers-selected">
<svg class="covers-selected-svg" style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff"> <g/><g><path d="M18.3,6.3L9.1,16.4l-2.3-3c-0.3-0.4-1-0.5-1.4-0.2c-0.4,0.3-0.5,1-0.2,1.4l3,4C8.4,18.8,8.7,19,9,19c0,0,0,0,0,0 c0.3,0,0.5-0.1,0.7-0.3l10-11c0.4-0.4,0.3-1-0.1-1.4C19.3,5.9,18.6,5.9,18.3,6.3z"/></g> </svg>
</div>
</div>
<button type="button" class="covers-button-delete" data-url="{{ cover }}" title="Удалить обложку" onclick="del_open(this)">
<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M3.89705 4.05379L3.96967 3.96967C4.23594 3.7034 4.6526 3.6792 4.94621 3.89705L5.03033 3.96967L10 8.939L14.9697 3.96967C15.2359 3.7034 15.6526 3.6792 15.9462 3.89705L16.0303 3.96967C16.2966 4.23594 16.3208 4.6526 16.1029 4.94621L16.0303 5.03033L11.061 10L16.0303 14.9697C16.2966 15.2359 16.3208 15.6526 16.1029 15.9462L16.0303 16.0303C15.7641 16.2966 15.3474 16.3208 15.0538 16.1029L14.9697 16.0303L10 11.061L5.03033 16.0303C4.76406 16.2966 4.3474 16.3208 4.05379 16.1029L3.96967 16.0303C3.7034 15.7641 3.6792 15.3474 3.89705 15.0538L3.96967 14.9697L8.939 10L3.96967 5.03033C3.7034 4.76406 3.6792 4.3474 3.89705 4.05379L3.96967 3.96967L3.89705 4.05379Z" fill="#e24a4a" />
</svg>
</button>
</li>
{% endfor %}
</ul>
......@@ -77,7 +83,21 @@
<div class="announ-content">
<div class="top-info">
<div class="top-info-text">
<span class="date">c <span class="date-start"></span> до <span class="date-end"></span></span>
{% if announcement.date_of_expiring %}
{% if announcement.updated_at == announcement.created_at %}
<span class="date">c <span class="date-start">{{ announcement.created_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% else %}
<span class="change">изм.</span>
<span class="date">c <span class="date-start">{{ announcement.updated_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% endif %}
{% else %}
{% if announcement.updated_at == announcement.created_at %}
<span class="date">{{ announcement.created_at }}</span>
{% else %}
<span class="change">изм.</span>
<span class="date">{{ announcement.updated_at }}</span>
{% endif %}
{% endif %}
</div>
<button class="favourite-btn" title="Закрепить">
<svg class="favourite-svg" width="18" height="18" viewBox="0 0 18 18" fill="inherit">
......@@ -95,29 +115,110 @@
<ul class="announ-files-list">
{% for file in announcement.files.all %}
<li class="announ-files-item">
<a href="{{ file.file.url }}" class="announ-files-link">{{ file.file.url|split }}</a>
<a href="{{ file.file.url }}" class="announ-files-link">{{ file.file.url|split|encode }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="announ-img">
</div>
<div class="announ-img" style="background-image: {{ announcement.image_url }}"></div>
</div>
<div class="url-cover">
<div>
<h3 class="url-title">Вставьте ссылку на вашу обложку</h3>
<span class="url-span">Учтите, что ссылка на вашу обложку может перестать быть активной со временем!</span>
</div>
<input class="url-input" type="text" placeholder="Ваша ссылка...">
<div class="url-btns">
<button class="btn exit" type="button" onclick="url_exit()">Отмена</button>
<button class="btn save" onclick="url_save()">Сохранить</button>
<div class="covers-delete-div">
<p class="delete-title">Вы уверены, что хотите удалить обложку?</p>
<span class="delete-subtitle">Учтите, что обложка пропадет везде, где присутствует!</span>
<div class="cover-delete"></div>
<div class="delete-btns">
<button class="delete-btn" type="button" onclick="del_exit()">Отмена</button>
<button class="delete-btn" type="button" onclick="del_active()">Удалить</button>
</div>
</div>
<div class="back-form"></div>
<script src="{% static 'js/announcement/red.js' %}"></script>
<script src="{% static 'js/announcement/ed.js' %}"></script>
<script>
function uploadImage() {
$('#image-upload-input').click();
}
$('#image-upload-input').on('change', function() {
let formData = new FormData();
formData.append('image', this.files[0]);
// CSRF Токен чтобы ничего не сломалось
let csrfToken = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: '/announcements/upload_image/', // Далее параметры для ajax
method: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRFToken': csrfToken
},
success: function(response) {
if (response.success) {
let imageUrl = response.image_url;
// Создаем новый <li> элемент для новой обложки
let newCoverItem = $(`<li class="covers-item"><div class="covers-content" style="background-image: url('${imageUrl}')"><div class="covers-selected"><svg class="covers-selected-svg" style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff"> <g/><g><path d="M18.3,6.3L9.1,16.4l-2.3-3c-0.3-0.4-1-0.5-1.4-0.2c-0.4,0.3-0.5,1-0.2,1.4l3,4C8.4,18.8,8.7,19,9,19c0,0,0,0,0,0 c0.3,0,0.5-0.1,0.7-0.3l10-11c0.4-0.4,0.3-1-0.1-1.4C19.3,5.9,18.6,5.9,18.3,6.3z"/></g> </svg></div></div><button type="button" class="covers-button-delete" data-url="${imageUrl}" title="Удалить обложку" onclick="del_open(this)"><svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M3.89705 4.05379L3.96967 3.96967C4.23594 3.7034 4.6526 3.6792 4.94621 3.89705L5.03033 3.96967L10 8.939L14.9697 3.96967C15.2359 3.7034 15.6526 3.6792 15.9462 3.89705L16.0303 3.96967C16.2966 4.23594 16.3208 4.6526 16.1029 4.94621L16.0303 5.03033L11.061 10L16.0303 14.9697C16.2966 15.2359 16.3208 15.6526 16.1029 15.9462L16.0303 16.0303C15.7641 16.2966 15.3474 16.3208 15.0538 16.1029L14.9697 16.0303L10 11.061L5.03033 16.0303C4.76406 16.2966 4.3474 16.3208 4.05379 16.1029L3.96967 16.0303C3.7034 15.7641 3.6792 15.3474 3.89705 15.0538L3.96967 14.9697L8.939 10L3.96967 5.03033C3.7034 4.76406 3.6792 4.3474 3.89705 4.05379L3.96967 3.96967L3.89705 4.05379Z" fill="#e24a4a" /></svg></button></li>`);
$('.covers-list').append(newCoverItem);
$('#id_image_url').val(imageUrl);
} else {
console.error('Image upload failed.');
}
}
});
});
</script>
<script>
$('.covers-list').on('click', '.delete-active', function() {
let coverUrl = $(this).data('url');
if (!coverUrl) {
// Если CoverUrl не найден пробуем получить его из атрибута стиля
let backgroundImage = $(this).closest('.covers-item').css('background-image');
if (backgroundImage) {
// Получаем url из функции url() с помощью регулярок
let match = /url\(['"]?(.*?)['"]?\)/.exec(backgroundImage);
if (match && match[1]) {
// Извлекаем относительный путь из абсолютного с помощью регулярок
coverUrl = match[1].replace(/^.*?\/media(\/covers\/.*)$/, '/media$1');
}
}
}
if (!coverUrl) {
// Если CoverUrl все еще не найден то бросаем исключение
console.error('Cover URL not found.');
return;
}
// CSRF Токен чтобы ничего не сломалось
let csrfToken = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: '/announcements/delete_cover/', // Далее параметры ajax
method: 'POST',
data: { cover_url: coverUrl },
headers: {
'X-CSRFToken': csrfToken
},
success: function(response) {
if (response.success) {
// Убираем удаленную обложку из DOM
$(this).closest('.covers-item').remove();
} else {
console.error('Cover deletion failed.');
}
}.bind(this)
});
});
</script>
{% endblock content %}
\ Нет новой строки в конце файла
......@@ -38,23 +38,28 @@
</div>
</div>
<div class="label label-cover"><span>Обложка:</span>
<button class="covers-btn" onclick="url_open()" type="button">Добавить обложку
<svg width="16" height="16" viewBox="0 0 200 200"
style="transform: rotate(45deg)">
<input type="file" accept="image/*" name="image" id="image-upload-input" style="display: none;">
<button class="covers-btn" type="button" onclick="uploadImage()">Добавить обложку
<svg width="16" height="16" viewBox="0 0 200 200" style="transform: rotate(45deg)">
<path
d="m114 100 49-49a9.9 9.9 0 0 0-14-14l-49 49-49-49a9.9 9.9 0 0 0-14 14l49 49-49 49a9.9 9.9 0 0 0 14 14l49-49 49 49a9.9 9.9 0 0 0 14-14Z"
fill="#5A88FF"></path>
</svg>
</button>
<!-- Скрытый инпут для url обложек. Список с url обложек доступен в переменной covers -->
{{ form.image_url }}
<ul class="covers-list">
{% for cover in covers %}
<li class="covers-item" style="background-image: url('{{cover}}');">
<div class="covers-selected">
<svg class="covers-selected-svg" style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff"> <g/><g><path d="M18.3,6.3L9.1,16.4l-2.3-3c-0.3-0.4-1-0.5-1.4-0.2c-0.4,0.3-0.5,1-0.2,1.4l3,4C8.4,18.8,8.7,19,9,19c0,0,0,0,0,0 c0.3,0,0.5-0.1,0.7-0.3l10-11c0.4-0.4,0.3-1-0.1-1.4C19.3,5.9,18.6,5.9,18.3,6.3z"/></g> </svg>
<li class="covers-item">
<div class="covers-content" style="background-image: url('{{cover}}')">
<div class="covers-selected">
<svg class="covers-selected-svg" style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff"> <g/><g><path d="M18.3,6.3L9.1,16.4l-2.3-3c-0.3-0.4-1-0.5-1.4-0.2c-0.4,0.3-0.5,1-0.2,1.4l3,4C8.4,18.8,8.7,19,9,19c0,0,0,0,0,0 c0.3,0,0.5-0.1,0.7-0.3l10-11c0.4-0.4,0.3-1-0.1-1.4C19.3,5.9,18.6,5.9,18.3,6.3z"/></g> </svg>
</div>
</div>
<button type="button" class="covers-button-delete" data-url="{{ cover }}" title="Удалить обложку" onclick="del_open(this)">
<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M3.89705 4.05379L3.96967 3.96967C4.23594 3.7034 4.6526 3.6792 4.94621 3.89705L5.03033 3.96967L10 8.939L14.9697 3.96967C15.2359 3.7034 15.6526 3.6792 15.9462 3.89705L16.0303 3.96967C16.2966 4.23594 16.3208 4.6526 16.1029 4.94621L16.0303 5.03033L11.061 10L16.0303 14.9697C16.2966 15.2359 16.3208 15.6526 16.1029 15.9462L16.0303 16.0303C15.7641 16.2966 15.3474 16.3208 15.0538 16.1029L14.9697 16.0303L10 11.061L5.03033 16.0303C4.76406 16.2966 4.3474 16.3208 4.05379 16.1029L3.96967 16.0303C3.7034 15.7641 3.6792 15.3474 3.89705 15.0538L3.96967 14.9697L8.939 10L3.96967 5.03033C3.7034 4.76406 3.6792 4.3474 3.89705 4.05379L3.96967 3.96967L3.89705 4.05379Z" fill="#e24a4a" />
</svg>
</button>
</li>
{% endfor %}
</ul>
......@@ -69,7 +74,21 @@
<div class="announ-content">
<div class="top-info">
<div class="top-info-text">
<span class="date">c <span class="date-start"></span> до <span class="date-end"></span></span>
{% if announcement.date_of_expiring %}
{% if announcement.updated_at == announcement.created_at %}
<span class="date">c <span class="date-start">{{ announcement.created_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% else %}
<span class="change">изм.</span>
<span class="date">c <span class="date-start">{{ announcement.updated_at }}</span> до <span class="date-end">{{ announcement.date_of_expiring }}</span></span>
{% endif %}
{% else %}
{% if announcement.updated_at == announcement.created_at %}
<span class="date">{{ announcement.created_at }}</span>
{% else %}
<span class="change">изм.</span>
<span class="date">{{ announcement.updated_at }}</span>
{% endif %}
{% endif %}
</div>
<button class="favourite-btn" title="Закрепить">
<svg class="favourite-svg" width="18" height="18" viewBox="0 0 18 18" fill="inherit">
......@@ -91,19 +110,101 @@
</div>
</div>
<div class="url-cover">
<div>
<h3 class="url-title">Вставьте ссылку на вашу обложку</h3>
<span class="url-span">Учтите, что ссылка на вашу обложку может перестать быть активной со временем!</span>
</div>
<input class="url-input" type="text" placeholder="Ваша ссылка...">
<div class="url-btns">
<button class="btn exit" type="button" onclick="url_exit()">Отмена</button>
<button class="btn save" onclick="url_save()">Сохранить</button>
<div class="covers-delete-div">
<p class="delete-title">Вы уверены, что хотите удалить обложку?</p>
<span class="delete-subtitle">Учтите, что обложка пропадет везде, где присутствует!</span>
<div class="cover-delete"></div>
<div class="delete-btns">
<button class="delete-btn" type="button" onclick="del_exit()">Отмена</button>
<button class="delete-btn" type="button" onclick="del_active()">Удалить</button>
</div>
</div>
<div class="back-form"></div>
<script src="{% static 'js/announcement/red.js' %}"></script>
<script>
function uploadImage() {
$('#image-upload-input').click();
}
$('#image-upload-input').on('change', function() {
let formData = new FormData();
formData.append('image', this.files[0]);
// CSRF Токен чтобы ничего не сломалось
let csrfToken = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: '/announcements/upload_image/', // Далее параметры для ajax
method: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'X-CSRFToken': csrfToken
},
success: function(response) {
if (response.success) {
let imageUrl = response.image_url;
// Создаем новый <li> элемент для новой обложки
let newCoverItem = $(`<li class="covers-item"><div class="covers-content" style="background-image: url('${imageUrl}')"><div class="covers-selected"><svg class="covers-selected-svg" style="enable-background:new 0 0 24 24;" version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff"> <g/><g><path d="M18.3,6.3L9.1,16.4l-2.3-3c-0.3-0.4-1-0.5-1.4-0.2c-0.4,0.3-0.5,1-0.2,1.4l3,4C8.4,18.8,8.7,19,9,19c0,0,0,0,0,0 c0.3,0,0.5-0.1,0.7-0.3l10-11c0.4-0.4,0.3-1-0.1-1.4C19.3,5.9,18.6,5.9,18.3,6.3z"/></g> </svg></div></div><button type="button" class="covers-button-delete" data-url="${imageUrl}" title="Удалить обложку" onclick="del_open(this)"><svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M3.89705 4.05379L3.96967 3.96967C4.23594 3.7034 4.6526 3.6792 4.94621 3.89705L5.03033 3.96967L10 8.939L14.9697 3.96967C15.2359 3.7034 15.6526 3.6792 15.9462 3.89705L16.0303 3.96967C16.2966 4.23594 16.3208 4.6526 16.1029 4.94621L16.0303 5.03033L11.061 10L16.0303 14.9697C16.2966 15.2359 16.3208 15.6526 16.1029 15.9462L16.0303 16.0303C15.7641 16.2966 15.3474 16.3208 15.0538 16.1029L14.9697 16.0303L10 11.061L5.03033 16.0303C4.76406 16.2966 4.3474 16.3208 4.05379 16.1029L3.96967 16.0303C3.7034 15.7641 3.6792 15.3474 3.89705 15.0538L3.96967 14.9697L8.939 10L3.96967 5.03033C3.7034 4.76406 3.6792 4.3474 3.89705 4.05379L3.96967 3.96967L3.89705 4.05379Z" fill="#e24a4a" /></svg></button></li>`);
$('.covers-list').append(newCoverItem);
$('#id_image_url').val(imageUrl);
} else {
console.error('Image upload failed.');
}
}
});
});
</script>
<script>
$('.covers-list').on('click', '.delete-active', function() {
let coverUrl = $(this).data('url');
if (!coverUrl) {
// Если CoverUrl не найден пробуем получить его из атрибута стиля
let backgroundImage = $(this).closest('.covers-item').css('background-image');
if (backgroundImage) {
// Получаем url из функции url() с помощью регулярок
let match = /url\(['"]?(.*?)['"]?\)/.exec(backgroundImage);
if (match && match[1]) {
// Извлекаем относительный путь из абсолютного с помощью регулярок
coverUrl = match[1].replace(/^.*?\/media(\/covers\/.*)$/, '/media$1');
}
}
}
if (!coverUrl) {
// Если CoverUrl все еще не найден то бросаем исключение
console.error('Cover URL not found.');
return;
}
// CSRF Токен чтобы ничего не сломалось
let csrfToken = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: '/announcements/delete_cover/', // Далее параметры ajax
method: 'POST',
data: { cover_url: coverUrl },
headers: {
'X-CSRFToken': csrfToken
},
success: function(response) {
if (response.success) {
// Убираем удаленную обложку из DOM
$(this).closest('.covers-item').remove();
} else {
console.error('Cover deletion failed.');
}
}.bind(this)
});
});
</script>
{% endblock content %}
\ Нет новой строки в конце файла
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать