1
0
Fork 0
mirror of https://github.com/EDeev/yandex_lyceum.git synced 2026-10-08 04:59:28 +03:00

Compare commits

..

4 commits

Author SHA1 Message Date
8e0dca0207 README на русском и английском, скриншот 2026-10-02 21:18:33 +00:00
e892a11052 CI: проверка синтаксиса (ruff) и импорт обоих Flask-приложений 2026-10-02 21:18:33 +00:00
968588eea1 Ключ API Яндекс.Карт и SECRET_KEY из окружения, зависимости только используемые
requirements.txt сокращён до Flask, Flask-WTF, WTForms, requests и Pillow; Werkzeug
закреплён на 2.3 — с Werkzeug 3 Flask-WTF 1.1 не импортируется.
2026-10-02 21:18:33 +00:00
37d30ee29f Порядок: дубль удалён, папки названы по содержанию
«WEB. HTML 2» повторял «WEB. HTML flask-wtf» (отличались только два маршрута) — оставлена
более поздняя версия. Папки: 01-flask-html, 02-flask-wtf-forms, 03-yandex-maps-search.
Переводы строк приведены к LF.
2026-10-02 21:18:33 +00:00
45 changed files with 726 additions and 997 deletions

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
* text=auto eol=lf
*.png binary
*.jpg binary

22
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install ruff==0.14.0 -r requirements.txt
- name: Ruff (синтаксис и ошибки)
run: ruff check --select E9,F .
- name: Приложения импортируются
run: |
cd 01-flask-html && python -c "import server" && cd ..
cd 02-flask-wtf-forms && python -c "import server"

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 422 KiB

View file

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View file

@ -1,5 +1,5 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired

View file

@ -1,9 +1,11 @@
import os
from flask import Flask, render_template, redirect
from loginform import LoginForm
app = Flask(__name__)
app.config['SECRET_KEY'] = 'yandexlyceum_secret_key'
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'yandexlyceum_secret_key')
@app.route('/<label>')

View file

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

View file

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 267 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,3 +1,4 @@
import os
from io import BytesIO
from PIL import Image
from delta import delta
@ -5,7 +6,7 @@ import requests
search_api_server = "https://search-maps.yandex.ru/v1/"
api_key = "your_yandex_api_key_here"
api_key = os.environ.get("YANDEX_API_KEY", "")
address_ll = "37.588392,55.734036"

45
README.en.md Normal file
View file

@ -0,0 +1,45 @@
# Yandex Lyceum
[Русский](README.md) · **English**
[![CI](https://github.com/EDeev/yandex_lyceum/actions/workflows/ci.yml/badge.svg)](https://github.com/EDeev/yandex_lyceum/actions/workflows/ci.yml)
Solutions to the Yandex Lyceum web development course: Flask apps built around the "Mars colonization
mission" storyline, plus organization search via the Yandex Maps API.
**Status:** coursework (Yandex Lyceum, 2022), completed, archived
![Mission applicant form](docs/screenshots/astronaut.png)
**Stack:** Python · Flask · Jinja2 · Flask-WTF · Bootstrap · Yandex Maps API · Pillow
| Folder | Contents |
|---|---|
| `01-flask-html` | Flask and Jinja2 pages: mission promo page, applicant form, planet choice, selection results, photo upload |
| `02-flask-wtf-forms` | Flask-WTF forms: sign-in, training by profession, list of professions, form auto-answer, cabin assignment |
| `03-yandex-maps-search` | finding the nearest organization via the Yandex organization search API and showing a map |
## Running
```bash
pip install -r requirements.txt
cd 01-flask-html && python server.py # http://127.0.0.1:8080
YANDEX_API_KEY=your_key python 03-yandex-maps-search/main.py
```
`03-yandex-maps-search` needs a Yandex organization search API key in `YANDEX_API_KEY`.
## License
Coursework (Yandex Lyceum, 2021/22). The code is open for study; there is no separate license.
## Author
**Egor Deev** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
---
<div align="center">
<sub>⭐ If you find this project useful, give it a star on GitHub!</sub>
<p><sub>Made with ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
</div>

140
README.md
View file

@ -1,139 +1,45 @@
# Yandex Lyceum Projects 🚀
# Yandex Lyceum
> Коллекция веб-приложений разработанных в рамках образовательной программы Яндекс Лицея
**Русский** · [English](README.en.md)
## 📋 Описание проекта
[![CI](https://github.com/EDeev/yandex_lyceum/actions/workflows/ci.yml/badge.svg)](https://github.com/EDeev/yandex_lyceum/actions/workflows/ci.yml)
Репозиторий содержит комплекс учебных проектов, демонстрирующих эволюцию навыков веб-разработки от базовых HTML-страниц до полнофункциональных Flask-приложений с интеграцией внешних API.
Решения задач курса веб-разработки Яндекс Лицея: Flask-приложения по сюжету «Миссия колонизации Марса»
и поиск организаций через API Яндекс.Карт.
## 🏗️ Архитектура решений
**Статус:** учебный проект (Яндекс Лицей, 2022), завершён, в архиве
### Модули проекта:
![Анкета претендента на участие в миссии](docs/screenshots/astronaut.png)
#### 1. **WEB.HTML** - Базовая веб-разработка
- Статические HTML-страницы с CSS-стилизацией
- Обработка форм и файловых загрузок
- Маршрутизация и шаблонизация
**Стек:** Python · Flask · Jinja2 · Flask-WTF · Bootstrap · API Яндекс.Карт · Pillow
#### 2. **WEB.HTML 2** - Продвинутая веб-архитектура
- Flask-приложение с расширенной функциональностью
- Интеграция Bootstrap для адаптивного дизайна
- Система аутентификации и авторизации
| Папка | Что внутри |
|---|---|
| `01-flask-html` | страницы на Flask и Jinja2: рекламная страница миссии, анкета претендента, выбор планеты, результаты отбора, загрузка фото |
| `02-flask-wtf-forms` | формы на Flask-WTF: вход, тренажёры по профессиям, список профессий, автоответ анкеты, распределение по каютам |
| `03-yandex-maps-search` | поиск ближайшей организации через API поиска по организациям и показ карты |
#### 3. **WEB.HTML flask-wtf** - Профессиональные веб-формы
- Валидация пользовательского ввода
- CSRF-защита
- Обработка сложных форм с множественными полями
## Запуск
#### 4. **Полный поиск** - Геоинформационные сервисы
- Интеграция с Yandex Maps API
- Алгоритмы геопозиционирования
- Динамическое построение карт
## 🛠️ Технологический стек
```
Backend: Flask 2.0+, Python 3.8+
Frontend: HTML5, CSS3, Bootstrap 5.1.3
Forms: Flask-WTF, WTForms
APIs: Yandex Maps Search API
Libraries: Pillow, Requests, Jinja2
```
## 🚀 Быстрый старт
### Установка зависимостей
```bash
pip install -r requirements.txt
cd 01-flask-html && python server.py # http://127.0.0.1:8080
YANDEX_API_KEY=ваш_ключ python 03-yandex-maps-search/main.py
```
### Запуск приложений
```bash
# Базовое HTML-приложение
cd "WEB.HTML"
python server.py
Для `03-yandex-maps-search` нужен ключ API поиска по организациям Яндекса в переменной `YANDEX_API_KEY`.
# Flask-приложение с шаблонами
cd "WEB.HTML 2"
python server.py
## Лицензия
# Приложение с формами
cd "WEB.HTML flask-wtf"
python server.py
```
Учебный проект (Яндекс Лицей, 2021/22). Код открыт для изучения, отдельной лицензии нет.
### Конфигурация API
Для работы геосервисов необходимо настроить API-ключ в `main.py`:
```python
api_key = "your_yandex_api_key_here"
```
## Автор
## 📁 Структура проекта
```
yandex_lyceum/
├── WEB.HTML/ # Базовые веб-технологии
│ ├── server.py # Flask-сервер
│ ├── static/ # Статические ресурсы
│ └── templates/ # HTML-шаблоны
├── WEB.HTML 2/ # Расширенная веб-архитектура
│ ├── server.py
│ ├── loginform.py # Формы авторизации
│ └── templates/
├── WEB.HTML flask-wtf/ # Профессиональные формы
└── Полный поиск/ # Геоинформационные сервисы
├── main.py # Основная логика
└── delta.py # Вспомогательные алгоритмы
```
## 🎯 Функциональные возможности
### Веб-интерфейсы:
- **Космическая тематика:** Интерактивные страницы миссии колонизации Марса
- **Система профессий:** Каталогизация и фильтрация специальностей
- **Анкетирование:** Комплексные формы с валидацией данных
- **Аутентификация:** Многоуровневая система доступа
### Геосервисы:
- **Поиск объектов:** Интеграция с Yandex Search API
- **Картографирование:** Динамическое построение карт
- **Геопозиционирование:** Алгоритмы расчета координат
## 🔧 Конфигурация
### Переменные окружения:
```bash
FLASK_ENV=development
SECRET_KEY=yandexlyceum_secret_key
YANDEX_API_KEY=your_api_key
```
### Порты по умолчанию:
- **Flask-приложения:** `127.0.0.1:8080`
- **Статические файлы:** автоматическая маршрутизация
## 🏆 Образовательные цели
Проект демонстрирует прогрессивное освоение:
- Фундаментальных веб-технологий
- Серверной архитектуры Flask
- Интеграции внешних API
- Профессиональных практик разработки
## 📄 Лицензия
Образовательный проект в рамках программы Яндекс Лицея
## 👨‍💻 Автор
**Деев Егор Викторович** - Backend Developer
- GitHub: [@EDeev](https://github.com/EDeev)
- Email: egor@deev.space
- Telegram: [@Egor_Deev](https://t.me/Egor_Deev)
**Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
---
<div align="center">
<sub>Разработано в рамках образовательной программы Яндекс Лицея для демонстрации навыков полного цикла веб-разработки</sub>
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p>
<sub>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
<p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
</div>

View file

@ -1,53 +0,0 @@
from flask import Flask, render_template, redirect
from loginform import LoginForm
app = Flask(__name__)
app.config['SECRET_KEY'] = 'yandexlyceum_secret_key'
@app.route('/')
@app.route('/index')
def index(label):
param = {'label': 'И на Марсе будут яблони цвести!', 'title': label}
return render_template('index.html', **param)
@app.route('/training/<prof>')
def training(prof):
param = {'title': "Тренировки в полёте", "spec": prof}
return render_template('training.html', **param)
@app.route('/list_prof/<var>')
def list_prof(var):
param = {'title': "Список профессий", "var": var}
return render_template('list_prof.html', **param)
@app.route('/answer')
@app.route('/auto_answer')
def answer():
param = {'title': "Анкета", 'surname': "Фёдоров", 'name': "Марк", 'education': "Среднее", 'profession': " Писатель",
'sex': "Male", 'motivation': "Распространить хип-хоп в космос!", 'ready': "True"}
return render_template('auto_answer.html', **param)
@app.route('/login', methods=['GET', 'POST'])
def login():
param = {'title': "Аварийный доступ"}
form = LoginForm()
if form.validate_on_submit():
return redirect('/success')
return render_template('login.html', **param, form=form)
@app.route('/distribution')
def success():
param = {'title': "По каютам!", "users": ['Ридли Скотт', 'Энди Уир', 'Марк Уотни', "Венката Капур",
'Тедди Сандерс', 'Шон Бин']}
return render_template('distribution.html', **param)
if __name__ == '__main__':
app.run(port=8080, host='127.0.0.1')

View file

@ -1,7 +0,0 @@
h4.color_edit {
color: "black";
}
p.padmarging {
margin-left: 20px;
}

View file

@ -1,15 +0,0 @@
{% extends "base_two.html" %}
{% block content1 %}
<div class="alert alert-light" role="alert">
<h4><font color="#DCDCDC">И на Марсе будут яблони цвести!</font></h4>
</div>
<h4 class="color_edit"><p style="padding-left: 60px">Фамилия: {{surname}}</p></h4>
<h4 class="color_edit"><p style="padding-left: 60px">Имя: {{name}}</p></h4>
<h4 class="color_edit"><p style="padding-left: 60px">Образование: {{education}}</p></h4>
<h4 class="color_edit"><p style="padding-left: 60px">Профессия: {{profession}}</p></h4>
<h4 class="color_edit"><p style="padding-left: 60px">Пол: {{sex}}</p></h4>
<h4 class="color_edit"><p style="padding-left: 60px">Мотивация: {{motivation}}</p></h4>
<h4 class="color_edit"><p style="padding-left: 60px">Готовы остаться на Марсе? {{ready}}</p></h4>
{% endblock %}

View file

@ -1,32 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">
<!-- <link rel="stylesheet" type="text/css" href="static/css/style.css" /> -->
<title>{{title}}</title>
</head>
<body>
<header>
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand mb-0" href="#"><h1>Миссия Колонизация Марса</h1></a>
<div class="navbar-text mb-0 h5">
Mars One
</div>
</div>
</nav>
</header>
<!-- Begin page content -->
<main role="main" class="container-fluid">
{% block content1 %}{% endblock %}
</main>
</body>
</html>

View file

@ -1,45 +0,0 @@
{% extends "base_two.html" %}
{% block content2 %}
<h1><img src="../static/img/mars_ico.png">Аварийный доступ</h1><br>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
<big>{{ form.id_user.label }}</big>><br>
{{ form.id_user(class="form-control") }}<br>
{% for error in form.id_user.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
</p>
<p>
<big>{{ form.pas_user.label }}</big><br>
{{ form.pas_user(class="form-control", type="password") }}<br>
{% for error in form.pas_user.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
</p>
<p>
<big>{{ form.id_cap.label }}</big><br>
{{ form.id_cap(class="form-control") }}<br>
{% for error in form.id_cap.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
</p>
<p>
<big>{{ form.pas_cap.label }}</big><br>
{{ form.pas_cap(class="form-control", type="password") }}<br>
{% for error in form.pas_cap.errors %}
<div class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endfor %}
</p>
<p>{{ form.submit(type="submit", class="btn btn-primary") }}</p>
</form>
{% endblock %}

View file

@ -1,17 +0,0 @@
{% extends "base_two.html" %}
{% block content2 %}
{% set sin = ['врач', "экзобиолог", "климатолог", "астрогеолог", "гляциолог", "метеоролог"] %}
{% set inj = ['инженер-исследователь', "пилот", "строитель", "инженер по терраформированию",
"специалист по радиационной защите", "инженер жизнеобеспечения", "оператор марсохода",
"киберинженер", "штурман", "пилот дронов"] %}
{% if spec.lower() in sin %}
<p class="text-center h2">Научные симуляторы</p>
<p class="text-center"><img src="/static/img/kb1.jpg" class="img-fluid"></p>
{% elif spec.lower() in inj %}
<p class="text-center h2">Инженерные тренажеры</p>
<p class="text-center"><img src="/static/img/kb2.jpg" class="img-fluid"></p>
{% else %}
<p class="text-center h1">В наших списках ваша профессия отсутствует!</p>
{% endif %}
{% endblock %}

View file

@ -1,14 +0,0 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class LoginForm(FlaskForm):
id_user = StringField('Id астронавта', validators=[DataRequired()])
pas_user = PasswordField('Пароль астронавта', validators=[DataRequired()])
id_cap = StringField('Id капитана', validators=[DataRequired()])
pas_cap = PasswordField('Пароль капитана', validators=[DataRequired()])
# remember_me = BooleanField('Запомнить меня') <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
submit = SubmitField('Доступ')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,10 +0,0 @@
{% extends "base.html" %}
{% block content1 %}
<div class="alert alert-light" role="alert">
<h4><font color="#DCDCDC">И на Марсе будут яблони цвести!</font></h4>
</div>
<div class="container">
{% block content2 %}{% endblock %}
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends "base_two.html" %}
{% block content2 %}
<h1 align="center">Размещение по каютам</h1>
{% for user in users %}
<h3>{{ user }} - Каюта № {{ loop.index }} {% if loop.first %} - первый в очереди {% endif %}</h3>
{% endfor %}
{% endblock %}

View file

@ -1,5 +0,0 @@
{% extends "base.html" %}
{% block content1 %}
<h4>{{label}}</h4>
{% endblock %}

View file

@ -1,27 +0,0 @@
{% extends "base_two.html" %}
{% block content2 %}
{% set all_prof = ['врач', "экзобиолог", "климатолог", "астрогеолог", "гляциолог", "метеоролог",
'инженер-исследователь', "пилот", "строитель", "инженер по терраформированию",
"специалист по радиационной защите", "инженер жизнеобеспечения", "оператор марсохода",
"киберинженер", "штурман", "пилот дронов"] %}
{% if var == "ol" %}
<p class="h1">Список профессий</p>
<div class="container">
<ol>
{% for p in all_prof %}
<li class="h5">{{p}}</li>
{% endfor %}
</ol>
</div>
{% elif var == "ul" %}
<p class="h1">Список профессий</p>
<div class="container">
{% for p in all_prof %}
<li class="h5">{{p}}</li>
{% endfor %}
</div>
{% else %}
<p class="h1">Индекс указан неправильно!</p>
{% endif %}
{% endblock %}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

View file

@ -1,23 +1,6 @@
# Core Flask Framework
Flask==2.3.3
Jinja2==3.1.2
# Flask Extensions
Werkzeug==2.3.8
Flask-WTF==1.1.1
WTForms==3.0.1
# HTTP Client Library
requests==2.31.0
# Image Processing
Pillow==10.0.1
# Security
cryptography==41.0.7
# Development Dependencies
python-dotenv==1.0.0
# Optional: Testing Framework
pytest==7.4.3
pytest-flask==1.3.0