Merge branch 'refs/heads/main' into prod
This commit is contained in:
commit
536837991c
@ -1,3 +1,5 @@
|
||||
SECRET_KEY=amazon
|
||||
DJANGO_DEBUG=T
|
||||
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
|
||||
SECRET_KEY=wevras-vberagv-rebavertf-wefgre
|
||||
DJANGO_DEBUG=1
|
||||
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
|
||||
DJANGO_DB_HOST=188.68.221.227
|
||||
DJANGO_DB_PASSWORD=127001
|
||||
0
CineSync/core/__init__.py
Normal file
0
CineSync/core/__init__.py
Normal file
21
CineSync/core/functions.py
Normal file
21
CineSync/core/functions.py
Normal file
@ -0,0 +1,21 @@
|
||||
from timetable.models import FilmSession
|
||||
|
||||
|
||||
def get_film_to_sessions():
|
||||
film_sessions = FilmSession.objects.nearest_timetable()
|
||||
sessions_by_date_and_film = {}
|
||||
|
||||
for session in film_sessions:
|
||||
session_date = session.start_datetime.date()
|
||||
session_film = session.film
|
||||
sessions_by_date_and_film.setdefault(session_date, {}).setdefault(
|
||||
session_film, []
|
||||
).append(session)
|
||||
|
||||
for session_date in sessions_by_date_and_film:
|
||||
for session_film in sessions_by_date_and_film[session_date]:
|
||||
sessions_by_date_and_film[session_date][session_film].sort(
|
||||
key=lambda x: x.start_datetime
|
||||
)
|
||||
|
||||
return sessions_by_date_and_film
|
||||
0
CineSync/core/migrations/__init__.py
Normal file
0
CineSync/core/migrations/__init__.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Generated by Django 4.2 on 2024-04-26 04:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('films', '0002_alter_film_genres_squashed_0007_alter_film_countries'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='actor',
|
||||
options={'verbose_name': 'актер', 'verbose_name_plural': 'Актеры'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='country',
|
||||
options={
|
||||
'verbose_name': 'страна',
|
||||
'verbose_name_plural': 'Страны',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='director',
|
||||
options={
|
||||
'verbose_name': 'режиссер',
|
||||
'verbose_name_plural': 'Режиссеры',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='film',
|
||||
options={'verbose_name': 'фильм', 'verbose_name_plural': 'Фильмы'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='genre',
|
||||
options={'verbose_name': 'жанр', 'verbose_name_plural': 'Жанры'},
|
||||
),
|
||||
]
|
||||
@ -80,7 +80,7 @@ class Genre(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'films_genres'
|
||||
verbose_name = 'Жанр'
|
||||
verbose_name = 'жанр'
|
||||
verbose_name_plural = 'Жанры'
|
||||
|
||||
|
||||
@ -102,7 +102,7 @@ class Director(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'films_directors'
|
||||
verbose_name = 'Режиссер'
|
||||
verbose_name = 'режиссер'
|
||||
verbose_name_plural = 'Режиссеры'
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ class Actor(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'films_actors'
|
||||
verbose_name = 'Актер'
|
||||
verbose_name = 'актер'
|
||||
verbose_name_plural = 'Актеры'
|
||||
|
||||
|
||||
@ -140,7 +140,7 @@ class Country(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'films_countries'
|
||||
verbose_name = 'Страна'
|
||||
verbose_name = 'страна'
|
||||
verbose_name_plural = 'Страны'
|
||||
|
||||
|
||||
@ -245,5 +245,5 @@ class Film(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'films_films'
|
||||
verbose_name = 'Фильм'
|
||||
verbose_name = 'фильм'
|
||||
verbose_name_plural = 'Фильмы'
|
||||
|
||||
@ -2,7 +2,7 @@ from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from films.models import Film
|
||||
from timetable.models import FilmSession
|
||||
from core.functions import get_film_to_sessions
|
||||
|
||||
|
||||
def films_list(request: HttpResponse) -> HttpResponse:
|
||||
@ -19,28 +19,7 @@ def film_details(request: HttpResponse, film_id: int) -> HttpResponse:
|
||||
Film.objects.released(),
|
||||
id=film_id,
|
||||
)
|
||||
film_sessions = FilmSession.objects.nearest_timetable().filter(
|
||||
film_id=film_id,
|
||||
)
|
||||
sessions_by_date_and_film = {}
|
||||
|
||||
for session in film_sessions:
|
||||
session_date = session.start_datetime.date()
|
||||
if session_date not in sessions_by_date_and_film:
|
||||
sessions_by_date_and_film[session_date] = {}
|
||||
|
||||
film_sessions_for_date = sessions_by_date_and_film[session_date]
|
||||
|
||||
if session.film not in film_sessions_for_date:
|
||||
film_sessions_for_date[session.film] = []
|
||||
|
||||
film_sessions_for_date[session.film].append(session)
|
||||
|
||||
for session_date, session_films in sessions_by_date_and_film.items():
|
||||
for session_film in session_films:
|
||||
sessions_by_date_and_film[session_date][session_film].sort(
|
||||
key=lambda sorted_session: sorted_session.start_datetime,
|
||||
)
|
||||
sessions_by_date_and_film = get_film_to_sessions()
|
||||
|
||||
context = {
|
||||
'film_sessions': sessions_by_date_and_film,
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
CineSync/fixtures/films.json
Normal file
1
CineSync/fixtures/films.json
Normal file
File diff suppressed because one or more lines are too long
1
CineSync/fixtures/timetable.json
Normal file
1
CineSync/fixtures/timetable.json
Normal file
File diff suppressed because one or more lines are too long
@ -5,31 +5,12 @@ from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
from films.models import Film
|
||||
from timetable.models import FilmSession
|
||||
from core.functions import get_film_to_sessions
|
||||
|
||||
|
||||
def homepage(request):
|
||||
films = Film.objects.on_main()
|
||||
film_sessions = FilmSession.objects.nearest_timetable()
|
||||
sessions_by_date_and_film = {}
|
||||
|
||||
for session in film_sessions:
|
||||
session_date = session.start_datetime.date()
|
||||
if session_date not in sessions_by_date_and_film:
|
||||
sessions_by_date_and_film[session_date] = {}
|
||||
|
||||
film_sessions_for_date = sessions_by_date_and_film[session_date]
|
||||
if session.film not in film_sessions_for_date:
|
||||
film_sessions_for_date[session.film] = []
|
||||
|
||||
film_sessions_for_date[session.film].append(session)
|
||||
|
||||
for session_date, session_films in sessions_by_date_and_film.items():
|
||||
for session_film in session_films:
|
||||
sessions_by_date_and_film[session_date][session_film].sort(
|
||||
key=lambda sorted_session: sorted_session.start_datetime,
|
||||
)
|
||||
|
||||
sessions_by_date_and_film = get_film_to_sessions()
|
||||
template = render(
|
||||
request,
|
||||
'home/homepage.html',
|
||||
|
||||
@ -24,5 +24,5 @@
|
||||
.film_card_column::-webkit-scrollbar-thumb {
|
||||
background-color: #0d1d3a;
|
||||
border-radius: 5vw;
|
||||
border: 1px solid #ffffff;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
@ -95,4 +95,11 @@ body::-webkit-scrollbar-thumb {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.not_found_label {
|
||||
width: 100%;
|
||||
color: #eaeaea;
|
||||
text-align: center;
|
||||
font-size: 2vw;
|
||||
margin-top: 10vw;
|
||||
}
|
||||
@ -20,4 +20,9 @@
|
||||
}
|
||||
.btn {
|
||||
margin: 5px !important;
|
||||
}
|
||||
.user_image {
|
||||
width: 20vw;
|
||||
border-radius: 2vw;
|
||||
margin-bottom: 1.5vw;
|
||||
}
|
||||
@ -8,10 +8,11 @@
|
||||
<link href="{% static 'css/films/films_list.css' %}" rel="stylesheet">
|
||||
<div class="films_block">
|
||||
<h1 class="header_title carousel_title">Смотрите в прокате</h1>
|
||||
{% if films %}
|
||||
<div class="films_list row row-cols-3">
|
||||
{% for film in films %}
|
||||
<a class="film_preview_card col" href="{% url 'films:film_details' film_id=film.id %}"
|
||||
style="{% if film.image %}background-image: url('{{ film.image.url }}'){% else %}background-color: #eaeaea{% endif %};">
|
||||
style="{% if film.image %}background-image: url('{{ film.image.url }}'){% else %}background-color: #eaeaea{% endif %};">
|
||||
<div class="film_preview_card_text_block">
|
||||
<h3>{{ film.name }}</h3>
|
||||
<p>{{ film.description|truncatewords_html:10 }}</p>
|
||||
@ -19,5 +20,8 @@
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="not_found_label">Информации о фильмах в прокате не найдена :(</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -8,6 +8,7 @@
|
||||
<link href="{% static 'css/home/homepage.css' %}" rel="stylesheet">
|
||||
<h1 class="header_title carousel_title">Сейчас в прокате</h1>
|
||||
<div id="carouselWhite" class="carousel carousel slide carousel_films" data-bs-ride="carousel">
|
||||
{% if films_preview %}
|
||||
<div class="carousel-indicators">
|
||||
{% for film in films_preview %}
|
||||
<button type="button" data-bs-target="#carouselWhite" data-bs-slide-to="{{ forloop.counter0 }}"
|
||||
@ -35,10 +36,14 @@
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<p class="not_found_label">Информации о фильмах в прокате не найдена :(</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="timetable_block">
|
||||
<h1 class="header_title">Расписание</h1>
|
||||
<div class="container">
|
||||
{% if films_sessions %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
{% for date in films_sessions %}
|
||||
{% include "includes/date_timetable_button.html" %}
|
||||
@ -56,6 +61,9 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="not_found_label">Информации о расписании не найдена :(</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -9,10 +9,14 @@
|
||||
<link href="{% static 'css/tickets/my_orders.css' %}" rel="stylesheet">
|
||||
<div class="container">
|
||||
<h1 class="header_title">Мои заказы</h1>
|
||||
{% if my_orders %}
|
||||
<div class="my_orders_block">
|
||||
{% for order in my_orders %}
|
||||
{% include "includes/my_order_card.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="not_found_label">Кажется, вы еще не оформили ни одного заказа 🤔</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -9,6 +9,7 @@
|
||||
<div class="timetable_block">
|
||||
<h1 class="header_title">Расписание</h1>
|
||||
<div class="container">
|
||||
{% if films_sessions %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
{% for date in films_sessions %}
|
||||
{% include "includes/date_timetable_button.html" %}
|
||||
@ -26,6 +27,9 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="not_found_label">Информации о расписании не найдена :(</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -38,6 +38,9 @@
|
||||
<div id="help" class="form-text">{{ field.help_text }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if user.profile.image %}
|
||||
<img class="user_image" src="{{ user.profile.image.url }}">
|
||||
{% endif %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<p> {{ message }} </p>
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2 on 2024-04-26 04:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tickets', '0002_initial_squashed_0007_alter_order_datetime_order'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='order',
|
||||
options={'verbose_name': 'заказ', 'verbose_name_plural': 'Заказы'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ticket',
|
||||
options={'verbose_name': 'билет', 'verbose_name_plural': 'Билеты'},
|
||||
),
|
||||
]
|
||||
@ -42,7 +42,7 @@ class Order(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'tickets_orders'
|
||||
verbose_name = 'Заказ'
|
||||
verbose_name = 'заказ'
|
||||
verbose_name_plural = 'Заказы'
|
||||
|
||||
|
||||
@ -72,5 +72,5 @@ class Ticket(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'tickets_tickets'
|
||||
verbose_name = 'Билет'
|
||||
verbose_name = 'билет'
|
||||
verbose_name_plural = 'Билеты'
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
# Generated by Django 4.2 on 2024-04-26 04:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
'timetable',
|
||||
'0002_rename_rows_row_filmsession_squashed_0006_remove_auditorium_row_count',
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='auditorium',
|
||||
options={'verbose_name': 'зал', 'verbose_name_plural': 'Залы'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='filmsession',
|
||||
options={'verbose_name': 'сеанс', 'verbose_name_plural': 'Сеансы'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='row',
|
||||
options={'verbose_name': 'место', 'verbose_name_plural': 'Места'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='filmsession',
|
||||
name='end_datetime',
|
||||
field=models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Дата и время окончания сеанса',
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -50,7 +50,7 @@ class Auditorium(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'timetable_auditoriums'
|
||||
verbose_name = 'Зал'
|
||||
verbose_name = 'зал'
|
||||
verbose_name_plural = 'Залы'
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ class Row(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'timetable_rows'
|
||||
verbose_name = 'Место'
|
||||
verbose_name = 'место'
|
||||
verbose_name_plural = 'Места'
|
||||
|
||||
|
||||
@ -134,5 +134,5 @@ class FilmSession(Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'timetable_film_sessions'
|
||||
verbose_name = 'Сеанс'
|
||||
verbose_name = 'сеанс'
|
||||
verbose_name_plural = 'Сеансы'
|
||||
|
||||
@ -15,23 +15,20 @@ from timetable.models import FilmSession
|
||||
|
||||
def timetable_view(request):
|
||||
film_sessions = FilmSession.objects.all_timetable()
|
||||
|
||||
sessions_by_date_and_film = {}
|
||||
|
||||
for session in film_sessions:
|
||||
session_date = session.start_datetime.date()
|
||||
if session_date not in sessions_by_date_and_film:
|
||||
sessions_by_date_and_film[session_date] = {}
|
||||
session_film = session.film
|
||||
sessions_by_date_and_film.setdefault(session_date, {}).setdefault(
|
||||
session_film, []
|
||||
).append(session)
|
||||
|
||||
film_sessions_for_date = sessions_by_date_and_film[session_date]
|
||||
if session.film not in film_sessions_for_date:
|
||||
film_sessions_for_date[session.film] = []
|
||||
|
||||
film_sessions_for_date[session.film].append(session)
|
||||
|
||||
for session_date, session_films in sessions_by_date_and_film.items():
|
||||
for session_film in session_films:
|
||||
for session_date in sessions_by_date_and_film:
|
||||
for session_film in sessions_by_date_and_film[session_date]:
|
||||
sessions_by_date_and_film[session_date][session_film].sort(
|
||||
key=lambda sorted_session: sorted_session.start_datetime,
|
||||
key=lambda x: x.start_datetime
|
||||
)
|
||||
|
||||
template = render(
|
||||
|
||||
@ -53,6 +53,17 @@ class SignUpForm(UserCreationForm):
|
||||
class Meta(UserCreationForm.Meta):
|
||||
fields = ('username', 'email')
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data['username']
|
||||
if len(username) > 150:
|
||||
raise forms.ValidationError('Максимальная длина 150 символов.')
|
||||
if not all(char.isalnum() or char in '@/./+/-/_' for char in username):
|
||||
raise forms.ValidationError(
|
||||
'Можно использовать только буквы, цифры и символы @/./+/-/_.'
|
||||
)
|
||||
|
||||
return username
|
||||
|
||||
|
||||
class ProfileForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
@ -99,3 +110,14 @@ class UserForm(forms.ModelForm):
|
||||
model.first_name.field.name,
|
||||
model.last_name.field.name,
|
||||
]
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data['username']
|
||||
if len(username) > 150:
|
||||
raise forms.ValidationError('Максимальная длина 150 символов.')
|
||||
if not all(char.isalnum() or char in '@/./+/-/_' for char in username):
|
||||
raise forms.ValidationError(
|
||||
'Можно использовать только буквы, цифры и символы @/./+/-/_.'
|
||||
)
|
||||
|
||||
return username
|
||||
|
||||
20
CineSync/users/migrations/0004_alter_profile_options.py
Normal file
20
CineSync/users/migrations/0004_alter_profile_options.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.2 on 2024-04-26 04:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0003_remove_profile_genres_profile_genres'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='profile',
|
||||
options={
|
||||
'verbose_name': 'данные пользователя',
|
||||
'verbose_name_plural': 'Данные пользователей',
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -74,6 +74,6 @@ class Profile(Model):
|
||||
list_display = ['image_tmb']
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Данные пользователя'
|
||||
verbose_name = 'данные пользователя'
|
||||
verbose_name_plural = 'Данные пользователей'
|
||||
db_table = 'users_profiles'
|
||||
|
||||
BIN
ER.png
BIN
ER.png
Binary file not shown.
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 82 KiB |
21
README.md
21
README.md
@ -10,6 +10,16 @@
|
||||
авторизации, редактирования профиля.
|
||||
Сайт расположен по адресу: https://cinesync.numerum.site/
|
||||
|
||||
## Перед запуском
|
||||
|
||||
Необходимо также задать переменные окружения, для этого нужно создать файл `.env`, и указать там необходимые переменны,
|
||||
ознакомится с ними можно в [примере файла](.example_env).
|
||||
|
||||
## Обратите внимание
|
||||
|
||||
Проект предусматривает возможность запуска проекта, как на sqlite, так и на postgres. В зависимости от этого нужно
|
||||
устанавливать разные зависимости (ниже описаны какие) и задавать разные переменные окружения.
|
||||
|
||||
## Инструкция к локальному запуску
|
||||
|
||||
1) Скачать проект или склонировать репозиторий:
|
||||
@ -44,16 +54,21 @@
|
||||
pip3 install -r requirements/test.txt
|
||||
```
|
||||
|
||||
- Для продакшена:
|
||||
- Для продакшена на sqlite:
|
||||
|
||||
```bash
|
||||
pip3 install -r requirements/prod.txt
|
||||
pip3 install -r requirements/base.txt
|
||||
```
|
||||
|
||||
- Для продакшена на postgres:
|
||||
|
||||
```bash
|
||||
pip3 install -r requirements/prod.txt
|
||||
```
|
||||
|
||||
5) Применить миграции:
|
||||
|
||||
```bash
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
```
|
||||
|
||||
|
||||
6
requirements/base.txt
Normal file
6
requirements/base.txt
Normal file
@ -0,0 +1,6 @@
|
||||
Django==4.2
|
||||
django-ckeditor==6.7.0
|
||||
django-cleanup==8.1.0
|
||||
pillow==10.2.0
|
||||
python-dotenv~=1.0.1
|
||||
sorl-thumbnail==12.10.0
|
||||
@ -1,3 +1,3 @@
|
||||
-r prod.txt
|
||||
-r base.txt
|
||||
black==24.1.1
|
||||
django-debug-toolbar==4.3.0
|
||||
@ -1,7 +1,2 @@
|
||||
Django==4.2
|
||||
django-ckeditor==6.7.0
|
||||
django-cleanup==8.1.0
|
||||
pillow==10.2.0
|
||||
python-dotenv~=1.0.1
|
||||
sorl-thumbnail==12.10.0
|
||||
-r base.txt
|
||||
psycopg2-binary==2.9.1
|
||||
@ -1,4 +1,4 @@
|
||||
-r prod.txt
|
||||
-r base.txt
|
||||
flake8==7.0.0
|
||||
flake8-bugbear==24.2.6
|
||||
flake8-clean-block==0.1.2
|
||||
|
||||
BIN
Документы/Текст к презентации.docx
Normal file
BIN
Документы/Текст к презентации.docx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user