Будь ласка, використовуйте цей ідентифікатор, щоб цитувати або посилатися на цей матеріал:
https://er.chdtu.edu.ua/handle/ChSTU/6032| Назва: | Веб-застосунок для аналізу активності користувача в соціальних мережах |
| Автори: | Оксамитна, Любов Павлівна Гречішкін, Олександр Володимирович |
| Ключові слова: | СОЦІАЛЬНА МЕРЕЖА,;ВЕБ-ЗАСТОСУНОК,;БАЗИ ДАНИХ,;ПРОЄКТУВАННЯ,;УПРАВЛІННЯ,;DJANGO,;ФУНКЦІОНАЛЬНІСТЬ,;ОПТИМІЗАЦІЯ. |
| Дата публікації: | 11-чер-2025 |
| Короткий огляд (реферат): | Актуальність теми полягає в потребі підвищення ефективності, зручності, варіативності взаємодії користувачів з системою аналізу даних про активність в соціальних мережах, що дозволить використовуючи передову аналітику даних у поєднанні з інтуїтивно зрозумілими методами візуалізації. Метою кваліфікаційної роботи бакалавра є розробка інноваційного веб-застосунку, який збирає, обробляє та аналізує дані про активність користувачів у різних соціальних мережах. Об’єкт дослідження: процес обліку активності користувачів у соціальних мережах. Предмет дослідження: веб-застосунок для аналізу активності користувача в соціальних мережах. |
| URI (Уніфікований ідентифікатор ресурсу): | https://er.chdtu.edu.ua/handle/ChSTU/6032 |
| Розташовується у зібраннях: | 122 Комп’ютерні науки (Комп’ютерні науки та прикладне програмування) |
Файли цього матеріалу:
| Файл | Опис | Розмір | Формат | |
|---|---|---|---|---|
| Пояснювальна записка_Гречішкін Олександр_КН-2101_2024-2025.pdf Restricted Access | 3.46 MB | Adobe PDF | Переглянути/Відкрити Запит копії |
Усі матеріали в архіві електронних ресурсів захищено авторським правом, усі права збережено.
Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ
Факультет інформаційних технологій і систем
Кафедра комп’ютерних наук та системного аналізу
Пояснювальна записка
до кваліфікаційної роботи
бакалавра
(освітньо-кваліфікаційний рівень)
на тему: «Веб-застосунок для аналізу активності користувача в соціальних
мережах»
Виконав: студент 4 курсу, групи КН-2101
спеціальності 122 − «Комп’ютерні науки»
(шифр і назва спеціальності)
освітня програма «Комп’ютерні науки та
(назва освітньої програми)
прикладне програмування
Гречішкін Олександр Володимирович
Керівник __________ Оксамитна Л.П.
(прізвище та ініціали)
Рецензент __________ Стась С.В.
(прізвище та ініціали)
Черкаси 2025 рік
Бланк завдання на кваліфікаційну роботу бакалавра студенту
Черкаський державний технологічний університет
Факультет Інформаційних технологій і систем
Кафедра Комп’ютерних наук та системного аналізу
Освітньо-кваліфікаційний рівень Бакалавр
Спеціальність 122 – Комп’ютерні науки
Освітня програма Комп’ютерні науки та прикладне програмування
ЗАТВЕРДЖУЮ
Завідувач кафедри КНСА
_______________ Юрій ТРИУС
«____» _____________ 2025 р.
ЗАВДАННЯ
на кваліфікаційну роботу бакалавра студенту
Гречішкіну Олександру Володимировичу
(прізвище, ім’я, по батькові)
1. Тема роботи Веб-застосунок для аналізу активності користувача в соціальних мережах
Керівник роботи Оксамитна Л.П., к.т.н., доцент
(прізвище, ім’я, по батькові, науковий ступінь, вчене звання)
затверджені наказом університету від «25» лютого 2025 р. №53/03-03.
2. Строк подання студентом роботи до 11 червня 2025 року.
3. Вихідні дані до роботи:
Практичні навички роботи з інформаційними ресурсами. Робота з базами даних.
Звіт з виробничої практики. Звіт з переддипломної практики.
4. Зміст пояснювальної записки (перелік питань, що їх належить розробити):
Вступ
4.1. Дослідження предметної області.
4.2. Проєктування та розробка веб-застосунку для аналізу активності користувача в соціальних
мережах.
4.3. Програмна реалізація.
Висновки.
5. Перелік додатків (з точним зазначенням назв додатків):
5.1. Додаток А. Специфікація 482.ЧДТУ. 52108-01.
5.2. Додаток Б. Текст програми.
5.4. Презентація у вигляді 35 слайдів.
6. Консультанти розділів роботи
Прізвище, ініціали та Підпис, дата
Розділ посада
завдання видав завдання прийняв
консультанта
7. Дата видачі завдання 17.12.2024
КАЛЕНДАРНИЙ ПЛАН
Строк
№ з/п Назва етапів кваліфікаційної роботи бакалавра виконання Примітка
етапів роботи
1 Видача завдання на кваліфікаційну роботу
17.12.2024 Виконано
бакалавра.
2 Аналіз літературних джерел, об’єкту та предмету
до 12.01.2025 Виконано
дослідження.
3 Написання теоретичного розділу кваліфікаційної
до 18.03.2025 Виконано
роботи бакалавра.
4 Написання аналітичного розділу кваліфікаційної
до 05.04.2025 Виконано
роботи бакалавра.
5 Написання практичних розділів й висновків до
до 01.05.2025 Виконано
кваліфікаційної роботи бакалавра.
6 Передзахист кваліфікаційної роботи бакалавра
26.05.2025 Виконано
на засіданні кафедри КНСА.
7 Подання роботи завідувачу кафедри КНСА. до 11.06.2025 Виконано
8 Захист кваліфікаційної роботи бакалавра. 11.06.2025 Виконано
Студент _____________________________ Олександр ГРЕЧІШКІН
(підпис)
Керівник роботи ____________________________ Любов ОКСАМИТНА
(підпис)
РЕФЕРАТ
Кваліфікаційна робота бакалавра містить: 80 с., 40 рис., 5 таблиць, 6
використаних джерел, 2 додатки.
Актуальність теми. Актуальність теми полягає в потребі підвищення
ефективності, зручності, варіативності взаємодії користувачів з системою аналізу
даних про активність в соціальних мережах, що дозволить використовуючи передову
аналітику даних у поєднанні з інтуїтивно зрозумілими методами візуалізації.
Мета роботи і задачі дослідження. Метою кваліфікаційної роботи бакалавра є
розробка інноваційного веб-застосунку, який збирає, обробляє та аналізує дані про
активність користувачів у різних соціальних мережах.
Для досягнення поставленої мети в роботі вирішувалися такі завдання:
провести дослідження і аналіз предметної області;
дослідження існуючих аналогів та виявлення їхніх переваг і недоліків;
проєктування та розробка веб-застосунку з інтуїтивно зрозумілим інтерфейсом.
Об’єкт дослідження: процес обліку активності користувачів у соціальних
мережах.
Предмет дослідження: веб-застосунок для аналізу активності користувача в
соціальних мережах.
Методи дослідження. Для досягнення поставленої мети в кваліфікаційній
роботі бакалавра використовувалися наступні методи:
теоретичний метод (аналіз літератури з проблеми дослідження);
емпіричний – полягав у спостереженні веб-застосунків для аналізу активності
користувача в соціальних мережах;
порівняльний – для здійснення порівнянь існуючих аналогів веб-застосунків.
Перелік ключових слів: СОЦІАЛЬНА МЕРЕЖА, ВЕБ-ЗАСТОСУНОК,
РОЗРОБКА, БАЗИ ДАНИХ, ПРОЄКТУВАННЯ, DJANGO, УПРАВЛІННЯ,
ТЕСТУВАННЯ, ФУНКЦІОНАЛЬНІСТЬ, ОПТИМІЗАЦІЯ.
ABSTRACT
Work contains. 80 pages, 40 figures, 5 tables, 6 sources used, 2 attachments.
Actuality of theme. The relevance of the topic lies in the need to increase the
efficiency, convenience, and variability of user interaction with the system for analyzing
data on activity in social networks, which will allow using advanced data analytics in
combination with intuitive visualization methods.
Purpose and tasks of the research. The goal of the bachelor's qualification work is
to develop an innovative web application that collects, processes, and analyzes data on user
activity in various social networks.
To achieve this purpose, the following objectives were addressed:
conduct research and analysis of the subject area;
research of existing analogues and identification of their advantages and
disadvantages;
design and development of a web application with an intuitive interface.
The object of research. The process of recording user activity on social networks.
The subject of research: Web application for analyzing user activity on social
networks.
Research methods: To achieve the purpose of the bachelor’s qualification work, the
following methods were applied:
theoretical method (analysis of literature on the research problem);
empirical – consisted in observing web applications to analyze user activity in social
networks;
comparative – to make comparisons of existing analogues of web applications.
The list of keywords: SOCIAL NETWORK, WEB APPLICATION,
DEVELOPMENT, DATABASES, DESIGN, DJANGO, MANAGEMENT, TESTING,
FUNCTIONALITY, OPTIMIZATION.
7
ЗМІСТ
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І ТЕРМІНІВ 8
ВСТУП 9
1 АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ 11
1.1 Огляд існуючих рішень 11
1.1.1 Аналіз платформи «Semrush Social Media Analytics» 11
1.1.2 Аналіз платформи «Buffer Analyze» 13
1.1.3 Аналіз веб-сайту «Google Analytics» 14
1.1.4 Аналіз платформи «Hootsuite Analytics» 16
1.1.5 Аналіз системи «Sprout Social» 17
1.1.6 Аналіз системи «Brandwatch» 19
1.2 Розробка вимог до веб-застосунку для аналізу активності користувача в
соціальних мережах 21
Висновки до розділу 1 22
2 ПРОЄКТУВАННЯ ВЕБ-ЗАСТОСУНКУ ДЛЯ АНАЛІЗУ АКТИВНОСТІ
КОРИСТУВАЧА В СОЦІАЛЬНИХ МЕРЕЖАХ 23
2.1 Опис технологій для розробки 23
2.2 Проєктування архітектури бази даних 25
2.3 Проєктування сторінок веб-застосунку 29
Висновки до розділу 2 32
3 ПРОГРАМНА РЕАЛІЗАЦІЯ 33
3.1 Реалізація функцій веб-застосунку для аналізу активності користувача в
соціальних мережах 33
3.2 Реалізація зовнішнього UI/UX – дизайну 43
Висновки до розділу 3 48
ВИСНОВКИ 49
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 50
Додаток А. Специфікація 482.ЧДТУ. 52108-01 51
Додаток Б. Текст програми 53
8
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, СКОРОЧЕНЬ І
ТЕРМІНІВ
БД – б аза даних.
CSS – мова стилізації, яка визначає зовнішній вигляд елементів
веб-сторінок.
JavaScript – мова програмування для додавання інтерактивності до веб-
сторінок.
SQLite – легка реляційна база даних, що не вимагає серверної
частини для роботи.
UI – користувацький інтерфейс для взаємодії користувача із
системою.
UX – досвід користувача, який відображає враження від взаємодії
з додатком.
HTTP – протокол передачі даних для обміну інформацією між
клієнтом і сервером.
API – інтерфейс для взаємодії між різними компонентами
програмного забезпечення.
SQL – мова для запитів до баз даних, яка використовується для
управління та маніпуляції даними.
9
ВСТУП
Дослідження активності користувачів у соціальних мережах є надзвичайно
актуальним у багатьох сферах: кібербезпека, маркетинг, соціологія, політичний
аналіз та інші.
У сфері кібербезпеки моніторинг соціальних мереж може допомогти у
виявленні шахрайських дій, дезінформаційних кампаній та загроз цифровій безпеці.
Для маркетологу аналіз активності користувачів допомагає зрозуміти
споживчі уподобання, оптимізувати рекламні стратегії та підвищити рівень
залученості.
Соціологи та психологи вивчають поведінку користувачів, щоб дослідити
вплив соціальних мереж на психічне здоров'я, стосунки та культурні зміни
Для бізнесу соціальні медіа – це золота жила даних, які розкривають споживчі
уподобання, поведінку та моделі залучення. Детальний аналіз дозволяє
маркетологам створювати таргетовані рекламні кампанії, оптимізувати контент для
кращого залучення та вдосконалювати стратегії обслуговування клієнтів.
Використання систем для аналізу активності можуть вирішувати не тільки
бізнес задачі а більш вузько направлені задачі відповідно до специфічних вимог
користувача.
Враховуючи, що питання дослідження активності користувачів соціальних є
не новим та програмні засоби постійно вдосконалюються, залишається певні
обмеження на перелік соціальних мереж та варіантів взаємодії з ними, що в свою
чергу негативно впливає на, зручність, зрозумілість та зовнішній вигляд.
Існуючі програмні засоби не можуть повністю задовольнити вимоги, щодо
простоти, зручності, адаптивності, варіативності та доступності для звичайного
користувача. Тому, виникає необхідність в проєктуванні та створенні веб-
застосунку для аналізу активності користувача в соціальних мережах, який
дозволить використовуючи передову аналітику даних у поєднанні з інтуїтивно
зрозумілими методами візуалізації, виявити закономірності, тенденції та аномалії в
соціальних взаємодіях.
10
Актуальність кваліфікаційної роботи бакалавра.
Актуальність теми полягає в потребі підвищення ефективності, зручності,
варіативності взаємодії користувачів з системою аналізу даних про активність в
соціальних мережах, що дозволить використовуючи передову аналітику даних у
поєднанні з інтуїтивно зрозумілими методами візуалізації.
Метою кваліфікаційної роботи бакалавра є розробка інноваційного веб-
застосунку, який збирає, обробляє та аналізує дані про активність користувачів у
різних соціальних мережах.
Для досягнення поставленої мети в роботі вирішувалися такі завдання:
провести дослідження і аналіз предметної області;
дослідження існуючих аналогів та виявлення їхніх переваг і недоліків;
проєктування та розробка веб-застосунку з інтуїтивно зрозумілим
інтерфейсом.
Об’єкт дослідження: процес обліку активності користувачів у соціальних
мережах.
Предмет дослідження: веб-застосунок для аналізу активності користувача в
соціальних мережах.
Методи дослідження. Для досягнення поставленої мети в кваліфікаційній
роботі бакалавра використовувалися наступні методи:
теоретичний метод (аналіз літератури з проблеми дослідження);
емпіричний – полягав у спостереженні веб-застосунків для аналізу активності
користувача в соціальних мережах;
порівняльний – для здійснення порівнянь існуючих аналогів веб-застосунків.
11
1 АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ
1.1 Огляд існуючих рішень
Активне використання та розвиток соціальних мереж зумовив створення
великої кількості сервісів для аналізу активності користувачів. Найбільш
популярними серед таких систем вважають «Semrush Social Media Analytics»,
«Buffer Analyze», «Google Analytics», «Sprout Social», «Hootsuite Analytics»,
«Brandwatch».
1.1.1 Аналіз платформи «Semrush Social Media Analytics»
«Semrush Social Media Analytics» [1] – це комплексні програмні платформи,
призначені для збору, обробки та перетворення великої кількості даних, створених
користувачами, на дієві ідеї, подано на рисунку 1.1. Їхні основні функціональні
можливості базуються на трьох основних принципах:
Рисунок 1.1 – Головна сторінка «Semrush Social Media Analytics» [1]
Поглинання та агрегація даних: ці інструменти встановлюють зв'язок з
багатьма соціальними мережами (наприклад, Facebook, Instagram, Twitter, LinkedIn,
TikTok, YouTube, Reddit, Pinterest та ін.) за допомогою API-інтеграцій або
контрольованого веб-скрепингу. Кожна платформа має власну унікальну структуру
12
даних, обмеження та частоту оновлення. Конвеєр поглинання повинен враховувати
ці відмінності, реалізуючи потоки даних у реальному часі (використовуючи такі
технології, як Apache Kafka) та пакетну обробку історичних даних.
Обробка даних, аналіз і машинне навчання: за лаштунками складні алгоритми,
починаючи від базових статистичних методів і закінчуючи глибоким навчанням та
обробкою природної мови (NLP), аналізують необроблені дані. Вони включають
аналіз настроїв, виявлення тенденцій, розпізнавання шаблонів залучення, виявлення
спільнот (аналіз графів) і прогнозне моделювання. Ці алгоритми допомагають
перетворити метрики – уподобання, пости, коментарі, перегляди і навіть невидимі
сигнали, такі як витрачений час або рухи курсору, на розуміння поведінки аудиторії
та ефективності кампанії.
Візуалізація даних та звітність: після обробки дані представляються в
інтерактивних дашбордах, які дозволяють користувачам розрізати показники
ефективності за часом.
Заснований ще у 2008 році на сьогоднішній день Semrush займає 10-15% ринку
цифрової маркетингової аналітики, а його модулі для соціальних мереж є
невід'ємною частиною більш широкого набору маркетингових інструментів.
Переваги:
Комплексні дані – поглиблена аналітика виходить за рамки поверхневих
показників, пропонуючи конкурентний аналіз і аналіз тенденцій.
Єдина маркетингова інформація – легко інтегрує соціальні медіа з іншими
даними цифрового маркетингу (SEO/PPC).
Кастомізовані звіти – гнучкі можливості звітування, які можна адаптувати до
конкретних потреб кампанії.
Недоліки:
Вартість – вища ціна може зробити його менш доступним для малого бізнесу.
Складність – широкий функціонал може вимагати певного періоду навчання
для повного використання всіх переваг.
Занадто громістикий для базових потреб – широкий інструментарій може бути
більш ніж необхідним, якщо потрібні лише базові знання про соціальні мережі.
13
1.1.2 Аналіз платформи «Buffer Analyze»
«Buffer Analyze» [2] розроблена з акцентом на зручність використання, «Buffer
Analyze» надає легку, але глибоку аналітику, подано на рисунку 1.2. Її інструменти
візуального звітування дозволяють користувачам швидко зрозуміти тенденції
залучення та показники ефективності, а тісна інтеграція з інструментами планування
«Buffer Analyze» забезпечує безперебійне управління контентом.
Рисунок 1.2 – Головна сторінка платформи «Buffer Analyze» [2]
«Buffer Analyze» підтримує ряд мереж, включаючи Instagram, Facebook,
Twitter, LinkedIn, TikTok і Pinterest – з перспективою додавання інтеграцій по мірі
появи нових платформ.
«Buffer Analyze» є частиною пакету «Buffer», розробленого компанією
«Buffer» Inc., заснованою у 2010 році та за оцінками. «Buffer Analyze» охоплює
близько 5% ринку аналітики соціальних мереж, приваблюючи насамперед малі та
середні підприємства, які цінують простоту поряд з ефективним плануванням.
«Buffer Analyze» вирізняється тим, що пропонує інтуїтивно зрозумілий
користувацький інтерфейс, який мінімізує криву навчання. Його аналітика
зосереджена на ключових показниках залучення, таких як охоплення, покази та
рівень взаємодії. Хоча вона менш комплексна, ніж інструменти корпоративного
14
рівня, її дизайн є оптимальним для користувачів, які потребують негайних, візуально
зрозумілих інсайтів. Обмеження платформи полягають у відносно неглибокій
сегментації даних та обмежених можливостях кастомізації, особливо коли потрібна
глибша багатовимірна аналітика або прогнозування.
Переваги:
Зручний для користувача – мінімалістичний, інтуїтивно зрозумілі дашборди
спрощують інтерпретацію даних.
Доступність – конкурентоспроможні ціни добре підходять для невеликих
організацій та незалежних користувачів.
Інтегрований робочий процес – тісно пов'язаний з екосистемою планування
Buffer для оптимізації роботи.
Недоліки:
Обмежена аналітична глибина – може не вистачати детальної сегментації та
розширених функцій, які пропонують спеціалізовані аналітичні інструменти.
Кастомізація – менше можливостей для детального налаштування звітів у
порівнянні з більш надійними платформами.
Покриття мережі хоча він регулярно оновлюється, він може не підтримувати
стільки нішевих мереж або мереж, що розвиваються, як деякі конкуренти.
1.1.3 Аналіз веб-сайту «Google Analytics»
«Google Analytics» [3] побудований на технології, придбаній у компанії
Urchin, Inc., був запущений компанією Google у 2005 році, подано на рисунку 1.3.
Google Analytics домінує у сфері веб-аналітики, займаючи десь 70-80% ринку
аналітики веб-сайтів. Його роль у відстеженні соціального реферального трафіку є
важливою в рамках ширшої екосистеми.
15
Рисунок 1.3 – Головна сторінка веб-сайту «Google Analytics» [3]
«Google Analytics» чудово відстежує шлях від постів у соціальних мережах до
конверсії на сайті. Він використовує кастомні інформаційні панелі, сегментацію та
кастомні виміри для вилучення інформації з даних про рефералів. Однак, хоча він
забезпечує надійну веб-аналітику, він менш ефективний, коли йдеться про прямі
показники залучення (наприклад, лайки, коментарі та реакції на рівні постів), які
можна знайти в спеціальних інструментах для соціальних мереж. Його модель
вимагає технічних ноу-хау для налаштування ефективного соціального відстеження,
що може обмежити доступність для не технічних користувачів.
Хоча «Google Analytics» не є спеціальним інструментом для соціальних
мереж, він опосередковано відстежує соціальні показники, приписуючи
реферальний трафік з таких платформ, як Facebook, Twitter, LinkedIn, Instagram та
інших, за допомогою параметрів UTM та користувацьких посилань.
Переваги:
Надійний і безкоштовний: пропонує потужну аналітику безкоштовно для
більшості стандартних випадків використання.
Кастомізація – широкі можливості кастомізації за допомогою інформаційних
панелей, фільтрів та спеціальних параметрів.
Інтеграція – легко підключається до інших сервісів Google (Ads, Data Studio)
для отримання більш повної картини.
16
Недоліки:
Не орієнтований на соціальні мережі – потребує додаткового налаштування
для отримання детальних соціальних показників.
Технічне налаштування – потрібен певний рівень технічних знань, щоб
правильно сегментувати та аналізувати дані про соціальні посилання.
Обмежені показники залучення – прямі соціальні взаємодії
(уподобання/коментарі/поширення) не фіксуються з такою ж глибиною, як у
спеціальних інструментах соціальних мереж.
1.1.4 Аналіз платформи «Hootsuite Analytics»
«Hootsuite Analytics» [4] розроблена для багатогранного управління
соціальними мережами, подано на рисунку 1.4.
Рисунок 1.4 – Головна сторінка платформи «Hootsuite Analytics» [4]
Її можливості моніторингу в режимі реального часу та настроювані
інформаційні панелі підкріплені надійним бекендом, який обробляє мільйони
соціальних сигналів. Здатність платформи сегментувати дані за каналами, часовими
поясами та демографічною інформацією робить її потужним інструментом для
динамічних середовищ. Однак, її широкий набір функцій також означає, що
користувачі повинні орієнтуватися в складному інтерфейсі, а пов'язані з цим витрати
можуть бути суттєвими для невеликих агентств.
«Hootsuite Analytics» розроблена компанією Hootsuite Inc., заснованою у 2008
році Райаном Холмсом. На зараз Hootsuite займає 15-20% ринку управління
17
соціальними мережами та аналітики, обслуговуючи великі команди та
корпоративних клієнтів своїми інтегрованими рішеннями.
«Hootsuite Analytics» інтегрується з широким спектром мереж, включаючи
Facebook, Instagram, Twitter, LinkedIn, Pinterest, YouTube, TikTok та нові платформи,
такі як Threads та та Reddit. Така широка підтримка робить його універсальним
інструментом для багатоканальних кампаній.
Переваги:
Універсальність – підтримує широкий спектр соціальних мереж,
задовольняючи потреби різних брендів.
Дані в режимі реального часу – пропонує аналітику майже в режимі реального
часу, щоб допомогти користувачам швидко реагувати на тенденції.
Індивідуальні дашборди – панелі моніторингу з можливістю гнучкого
налаштування дозволяють створювати індивідуальні звіти.
Недоліки:
Вища ціна – преміум-ціни можуть бути бар'єром для невеликих команд або
окремих користувачів.
Складність – глибина та широта функцій може вразити користувачів-
початківців.
Періодичні затримки у деяких сценаріях з великими обсягами даних у
реальному часі можуть виникати незначні затримки.
1.1.5 Аналіз системи «Sprout Social»
«Sprout Social» [5] заснована в 2010 році, за попередніми оцінками займає
близько 8-12% ринку, особливо серед середніх і великих організацій, які шукають
рішення для управління соціальними мережами та аналітики на рівні підприємства,
подано на рисунку 1.5.
18
Рисунок 1.5 – Головна сторінка системи «Sprout Social» [5]
«Sprout Social» використовує потужну візуалізацію даних та автоматизовану
звітність для надання детальної інформації про демографічні показники аудиторії та
тенденції залучення. Його аналітичний механізм інтегрує аналіз настроїв та
прогнозне моделювання, допомагаючи компаніям передбачати тенденції та
проактивно коригувати свої соціальні стратегії. Незважаючи на потужний
функціонал, преміум-ціни та потенційна надмірність для користувачів, які
зосереджуються виключно на основних показниках, створюють бюджетні та
операційні проблеми.
«Sprout Social» надає інструменти аналітики та управління для мереж,
включаючи Facebook, Instagram, Twitter, LinkedIn, Pinterest, TikTok, Threads і
YouTube. Це дозволяє користувачам відстежувати ефективність як на вже існуючих,
так і на нових платформах.
Переваги:
Інтуїтивно зрозумілий інтерфейс – захоплюючі візуальні ефекти та зрозумілі
інформаційні панелі спрощують інтерпретацію даних.
Глибокий аналіз – пропонує всебічне розуміння поведінки аудиторії та
ефективності контенту.
19
Надійна підтримка – якісне обслуговування клієнтів і велика кількість
документації підвищують довіру користувачів.
Недоліки:
Преміум-ціни – розширені функції коштують дорожче, що може бути
доступним не для кожного бюджету.
Дублювання функцій – деякі функції можуть бути надлишковими, якщо
потрібна лише базова аналітика.
Обмежені можливості налаштування – незважаючи на надійність, можливості
кастомізації візуалізації даних можуть бути менш широкими, ніж у деяких нішевих
інструментів.
1.1.6 Аналіз системи «Brandwatch»
«Brandwatch» [6] була заснована у 2005 році і з того часу позиціонує себе як
лідер у сфері соціального прослуховування та аналізу настроїв. У ніші розширеного
моніторингу займає приблизно 5-7% ринку, подано на рисунку 1.6. Зазвичай,
клієнти – це насамперед бренди, які потребують глибшого розуміння суспільних
настроїв та швидкого виявлення тенденцій.
Рисунок 1.6 – Головна сторінка системи «Brandwatch» [6]
«Brandwatch» вирізняється своїми передовими алгоритмами штучного
20
інтелекту, які виконують аналіз настроїв у реальному часі, кластеризацію тем та
виявлення аномалій. Обробляючи неструктуровані дані з багатьох джерел, вона
представляє складну картину сприйняття бренду та ринкових тенденцій. Однак,
крута крива навчання та преміальна цінова модель означають, що вона найкраще
підходить для великих підприємств, які можуть інвестувати в інтенсивне навчання
та поглиблений аналіз. Крім того, широкі можливості збору даних можуть призвести
до перевантаження інформації, якщо її не відфільтрувати та не інтерпретувати
належним чином.
«Brandwatch» може аналізувати дані з Facebook, Twitter, Instagram, LinkedIn,
TikTok, WhatsApp, YouTube, Reddit, а також блоги, форуми та оглядові сайти.
Переваги:
Комплексний збір даних – «Brandwatch» сканує понад 90 мільйонів джерел,
включаючи соціальні мережі, блоги, форуми та новинні сайти різними мовами.
Багатомовність і глобальне охоплення – здатність аналізувати дані 27 мовами
робить «Brandwatch» особливо корисним для компаній, що працюють на
міжнародному рівні.
Розширений аналіз настроїв і трендів – завдяки надійному аналізу настроїв,
категоризації та побудові графіків трендів, платформа може визначити тон і
контекст, що стоять за соціальними розмовами.
Налаштовувані дашборди та потужні інструменти візуалізації – система
пропонує настроювані інформаційні панелі, до яких можна надавати спільний
доступ, що дозволяють підприємствам візуалізувати показники, створювати
комплексні звіти та налаштовувати уявлення відповідно до того, що є найбільш
важливим.
Інтеграція та робочі процеси – «Brandwatch» підтримує численні інтеграції з
іншими бізнес-інструментами. У поєднанні з такими функціями робочого процесу,
як автоматизація, сповіщення та масові дії, це допомагає впорядкувати процес
перетворення необроблених даних на дієві інсайди.
Недоліки:
21
Складна для новачків – через глибоку функціональність і складність створення
розширених запитів, нові користувачі – особливо ті, хто не має технічної підготовки
– можуть спочатку вважати інтерфейс складним. Для повного використання
потенціалу системи може знадобитися додаткове навчання та час.
Висока ціна – «Brandwatch» часто позиціонується як рішення для підприємств.
Його ціна, як правило, відносно висока (стартова ціна часто становить кілька сотень
доларів або фунтів стерлінгів на місяць), що робить його менш доступним для
малого бізнесу або стартапів з обмеженим бюджетом.
Складність у налаштуванні – хоча можливості кастомізації системи потужні,
вони також можуть бути складними. Користувачам можуть знадобитися спеціальні
ресурси для повного налаштування вигляду інформаційної панелі та оптимальних
конфігурацій сповіщень, що може зайняти багато часу.
Потенційна надмірність для невеликих проектів – для команд або кампаній,
які потребують більш простої, менш повної інформації про соціальні мережі, широта
функцій, доступних в «Brandwatch», може перевищувати реальні потреби. У таких
випадках більш простий і спрощений інструмент може бути більш економічно
ефективним.
1.2 Розробка вимог до веб-застосунку для аналізу активності
користувача в соціальних мережах
Послуги з аналізу соціальних мереж є потужним активом в арсеналі сучасного
користувача. Їхня здатність надавати детальну інформацію про поведінку аудиторії,
динаміку подій та трендів може значно покращити стратегічний підхід до будь яких
ситуацій. Однак, дуже важливо зважити переваги та потенційні проблеми.
Багато існуючих інструментів для аналізу соціальних медіа мають високу ціну
та складну логіку та структуру для початківців. Їхні інформаційні панелі можуть
бути захаращені, іноді пропонуючи більше даних, ніж потрібно користувачам, без
визначення пріоритетів, що призводить до інформаційного перевантаження.
Детальний аналіз проблем та переваг спонукає до формування вимог до веб-
22
застосунку для аналізу активності користувача в соціальних мережах, який
створюється:
Інтуїтивно зрозумілий інтерфейс.
Можливість вводу та виведення інформації про активності в соціальних
мережах (пости, відео, підписники, сторіс).
Обробка та аналіз інформації.
Використання сучасних технологій для відображення активності
користувачів.
Використання облікових записів та хешування паролів для забезпечення
безпеки веб-застосунку.
Гнучка та адаптивна система без обмежень кількості соціальних мереж.
Використання сучасних технологій для розробки веб-застосунку.
Висновки до розділу 1
У даному розділі детально проаналізовано провідні системи, платформи та
ресурси для поглибленого аналізу соціальних мереж для визначення основних
переваг та недоліків як комерційних, так і безкоштовних сервісів. Поглиблений
аналіз дозволяє узагальнити, що більшість функцій систем є надлишковими, а
користувачі віддають перевагу більш «простому» та безкоштовному Google
Analytics, а саме близько 70% користувачів.
Також було сформовано основні функціональні вимоги до веб-застосунку для
аналізу активності користувача в соціальних мережах.
23
2 ПРОЄКТУВАННЯ ВЕБ-ЗАСТОСУНКУ ДЛЯ АНАЛІЗУ АКТИВНОСТІ
КОРИСТУВАЧА В СОЦІАЛЬНИХ МЕРЕЖАХ
2.1 Опис технологій для розробки
Для забезпечення максимальної якості розробки веб-застосунку для аналізу
активності користувача в соціальних мережах були застосовані стек технології, що
дозволяють швидко розробляти функціональні рішення з належним інтерфейсом.
Основними мовами програмування, які використовувалися при розробці, були:
Python, JavaScript, HTML, CSS.
Основою для розробки веб-застосунку став легкий та зручний
мікрофреймворк для веб-розробки на Python Flask.
Flask – це мікрофреймворк для розробки веб-застосунків на Python, який
відрізняється своєю простотою та високою гнучкістю. Він дозволяє швидко
створювати як невеликі прототипи, так і повноцінні веб-застосунки, не накладаючи
суворих обмежень на архітектуру проєкту. Завдяки використанню шаблонізатора
створювати динамічні HTML-сторінки стає простим і інтуїтивно зрозумілим
завданням. Вбудований сервер розробки дозволяє працювати в режимі
налагодження, що значно полегшує виправлення помилок під час створення
проєкту.
Використання Flask та розширень для бекенду дозволили оптимізувати
роботу веб-застосунку, зокрема:
Flask-SQLAlchemy забезпечує інтеграцію Flask з СУБД SQLAlchemy –
потужним Object-Relational Mapper (ORM). Дозволяє взаємодіяти з базою даних,
використовуючи об'єкти Python замість SQL-запитів.
Flask-Login: забезпечує управління сесіями користувачів у Flask-застосунках.
Обробляє вхід, вихід, запам'ятовування користувачів та захист сторінок.
Flask-WTF: інтеграція Flask з WTForms, бібліотекою для створення та
валідації веб-форм. Дозволяє легко створювати безпечні форми з перевіркою даних.
24
Для створення, сучасних, зручних сторінок веб-застосунку (фронтенду)
використовувалися:
Jinja2 – потужний та гнучкий шаблонізатор для Python, який дозволяє
створювати динамічні веб-сторінки шляхом комбінування шаблонів із даними. Він
використовує синтаксис, схожий на Python, що робить його зрозумілим і
інтуїтивним у використанні, а також дозволяє працювати з умовними операторами,
циклами, фільтрами та макросами для побудови складних шаблонів. Здатність до
наслідування шаблонів допомагає зменшити дублювання коду, сприяючи розробці
модульних та легко підтримуваних проєктів, а також Jinja2 пропонує ефективну
компіляцію шаблонів до оптимізованого Python-коду, що забезпечує швидкий
рендеринг контенту.
Bootstrap – безкоштовного фреймворку для розробки інтерфейсів, який
спрощує створення адаптивних, мобільних веб-сайтів за допомогою CSS і JavaScript.
Він забезпечує широкі можливості для розробки завдяки своєму сучасному та
гнучкому набору компонентів, системі сіток і утиліт, які дозволяють швидко
створювати привабливі інтерфейси, що коректно відображаються на різних
пристроях.
Chart.js – це легка і гнучка бібліотека для створення графіків у веб-
застосунках, яка використовує елемент <canvas> HTML5 для рендерингу
візуалізацій. Вона дозволяє розробникам швидко інтегрувати різноманітні типи
графіків, такі як лінійні, стовпчикові, кругові, діаграми розсіяння та інші, завдяки
простому і зрозумілому API. Бібліотека забезпечує плавну анімацію переходів,
адаптивність до різних пристроїв і легкість налаштування, що робить її ідеальним
інструментом для візуалізації даних як у простих прототипах, так і у складних веб-
застосунках.
Одним із обов'язкових елементів будь-якого веб-застосунку є база даних (БД),
а саме SQLite. SQLite – це невеликий, швидкий і самодостатній реляційний двигун
баз даних, реалізований як бібліотека на мові C. Цей механізм працює без потреби у
встановленні окремого серверу, адже всі дані зберігаються в одному файлі, що
значно спрощує інтеграцію та розгортання. SQLite підтримує більшість стандартних
25
SQL-операцій та забезпечує транзакційну цілісність з властивостями ACID, що
гарантує надійне зберігання даних. Завдяки своїй портативності, простоті та високій
надійності, SQLite використовується в безлічі додатків, від популярних веб-
браузерів і мобільних додатків до вбудованих систем і прототипів, що робить її
одним із найпопулярніших виборів для роботи з базами даних.
2.2 Проєктування архітектури бази даних
Для реалізації всіх функцій збирання, обробки та виведення інформації
обов’язково потрібно спроєктувати та інтегрувати базу даних, яка дозволить
зберігати інформацію про користувачів (облікові записи) та всі активності (пости,
відео, сторіс, підписників).
Спроєктована база даних складається з достатньої кількості об’єктів, зокрема
наступних таблиць:
Таблиця: «User» зберігає інформацію про користувачів системи, що дозволяє
їм реєструватися, входити та керувати власними даними.
Таблиця 2.1 – Структура таблиці «User»
Назва елемента Тип даних Опис
id Integer Первинний ключ. Унікальний
ідентифікатор користувача.
first_name String(50) Ім'я користувача
last_name String(50) Прізвище користувача
email String(120) Електронна пошта користувача
username String(50) Логін користувача
password String(100) Зберігається хешований пароль
is_admin Boolean Булеве значення, що вказує, чи є
користувач адміністратором
Таблиця «Post» зберігає деталі про публікації (пости) у соціальних мережах,
додані користувачами для аналізу та відстеження.
26
Таблиця 2.2 – Структура таблиці «Post»
Назва елемента Тип даних Опис
id Integer Унікальний ідентифікатор посту.
user_id Integer Зовнішній ключ, який посилається на id
користувача у таблиці user. Вказує, хто
створив пост.
date String(20) Дата публікації посту (зберігається як
рядок у форматі 'YYYY-MM-DD').
platform String(50) Назва платформи (наприклад, "Instagram",
"Facebook").
title String(100) Посилання або короткий опис посту
likes Integer Кількість вподобань.
shares Integer Кількість поширень.
Таблиця «Video» зберігає дані про відео, завантажені користувачами на різні
відео платформи.
Таблиця 2.3 – Структура таблиці «Video»
Назва Тип даних Опис
елемента
id Integer Унікальний ідентифікатор опублікованого
відео
user_id Integer Зовнішній ключ, який посилається на id
користувача у таблиці user. Вказує, хто створив
пост.
date String(20) Дата публікації відео (рядок 'YYYY-MM-DD')
platform String(50) Назва відеоплатформи (наприклад, "YouTube",
"TikTok")
title String(100) Заголовок відео
views Integer Кількість переглядів
likes Integer Кількість вподобань
shares Integer Кількість поширень
Таблиця «Story» зберігає дані про короткі історії (stories), розміщені
користувачами у соціальних мережах.
Таблиця 2.4 – Структура таблиці «Story»
Назва елемента Тип даних Опис
id Integer Унікальний ідентифікатор
опублікованого сторіс
user_id Integer Зовнішній ключ, який посилається на
id користувача у таблиці user. Вказує,
хто створив пост.
date String(20 Дата публікації сторіс (рядок 'YYYY-
27
) MM-DD')
platform String(50 Назва платформи (наприклад,
) "Instagram")
title String(10 Заголовок сторіс. NULLABLE=True
0) (може бути порожнім)
views Integer Кількість переглядів
replies Integer Кількість відповідей
Таблиця «Subscriber» зберігає записи про кількість підписників користувача на
певних соціальних платформах станом на конкретну дату.
Таблиця 2.5 – Структура таблиці «Subscriber»
Назва елемента Тип даних Опис
id Integer Унікальний ідентифікатор
user_id Integer Зовнішній ключ, який посилається на id
користувача у таблиці user. Вказує, хто
створив пост.
date String(20) Дата фіксації кількості підписників
(рядок 'YYYY-MM-DD')
platform String(50) Назва платформи (наприклад,
"Instagram", "YouTube")
count Integer Загальна кількість підписників
Після проєктування таблиць можна сформувати діаграму зв’язків бази даних.
При створенні веб-застосунку за допомогою фреймворка є певна особливість
взаємодії. Flask-застосунок використовує SQLAlchemy як ORM (Object-Relational
Mapper) та SQLite як базу даних. Це популярне поєднання для невеликих та середніх
проєктів, оскільки SQLite є легкою, файловою базою даних, що не потребує
окремого сервера.
Інформація про базу даних таблиці та поля генерується безпосередньо з файлу
models.py. Кожен клас в цьому файлі (наприклад, User, Post, Video, Story, Subscriber)
відповідає таблиці в базі даних, з доданою частиною коду (рис. 2.1).
Для ініціалізації бази даних використовується файл __init__.py. Цей файл
відповідає за створення та налаштування Flask-додатку, включаючи ініціалізацію
бази даних.
28
Рисунок 2.1 – Вигляд класу з доданою частиною коду
Далі SQLAlchemy(): створюється екземпляр SQLAlchemy, але він ще не
прив'язаний до конкретного Flask-додатку.
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db': Це
найважливіший рядок для конфігурації бази даних. Він вказує SQLAlchemy, де
знаходиться файл бази даних. У даному випадку 'sqlite:///site.db' означає, що база
даних буде файлом з ім'ям site.db у кореневому каталозі вашого проекту. Якщо цей
файл не існує, він буде створений автоматично. Після створення формується файл
бази даних із вказаними параметрами у models.py, з’являється можливість
переглянути зв'язки (рис. 2.2).
29
Рисунок 2.2 – Діаграма зв’язків бази даних
2.3 Проєктування сторінок веб-застосунку
Проєктування сторінок є важливим етапом для зовнішнього вигляду та
розуміння функцій, які будуть виконуватися.
Для реалізації проєкту було створено сторінки: «login.html», register.html,
«dashboard.html», «statistics.html», «add.html».
Сторінка «login.html» – сторінка, призначена для входу користувачів в систему.
На сторінці відображаються:
В центрі сторінки – картка із заголовком «Авторизація».
Всередині форми:
− Поле логіну (username).
30
− Поле паролю (password).
− Кнопка “Увійти”.
− Посилання “Створити акаунт”, яке веде на сторінку реєстрації (/register)
− Що виконується:
− Після натискання кнопки “Увійти”:
− Дані передаються на POST /login.
− Якщо логін і пароль правильні – відбувається авторизація (login_user).
− Користувача перекидає на /dashboard.
Сторінка «dashboard.html» – Головна сторінка після входу (Дашборд).
На сторінці відображаються:
Заголовок: «Аналітика».
Перший блок: 4 картки зі статистикою:
− Кількість постів.
− Кількість відео.
− Кількість сторіс.
− Кількість підписників.
Далі є 12 інтерактивних графіків (Chart.js):
За днями:
− динаміка постів, лайків, репостів;
− динаміка відео, переглядів, лайків, репостів;
− динаміка сторіс, переглядів, відповідей;
− динаміка підписників.
За платформами кругові діаграми для:
− постів;
− відео;
− сторіс;
− підписників.
Що виконується:
− Після завантаження сторінки запускається JavaScript:
− Графіки створюються через Chart.js з даних, переданих у chart_data.
31
− Якщо в даних є значення — графік створюється.
− Якщо немає – виводиться повідомлення “Немає даних для діаграми ...”.
Сторінка «statistics.html» – Сторінка статистики.
На сторінці відображаються:
− Заголовок: «Статистика за даними».
− Вгорі – форма з фільтром типу контенту:
− Обирається один з: пости / відео / сторіс / підписники.
Під фільтром таблиця вибраними даними з відповідними колонками:
Що виконується:
При зміні фільтра (типу контенту) – форма автоматично надсилається
(onchange="this.form.submit()").
У відповідь сервер повертає оновлену таблицю з даними.
Сторінка «register.html» – сторінка реєстрації нового користувача.
Що відображається:
Заголовок: «Створення акаунту».
В центрі – форма з полями:
− Ім’я.
− Прізвище.
− Електронна пошта.
− Логін.
− Пароль.
− Кнопка “Створити акаунт”.
Що виконується:
Коли користувач заповнює форму та натискає кнопку:
Дані відправляються на POST /register.
У бекенді:
− створюється об’єкт User;
− пароль хешується через generate_password_hash;
− новий користувач зберігається у базу;
− Після реєстрації – редірект на /login з повідомленням "Користувач створений".
32
Сторінка «add.html» – Сторінка додавання нових записів.
Що відображається:
Заголовок сторінки: "Додати дані - Статистика Соцмереж".
Заголовок: "Додати дані".
Чотири окремі секції/картки
− Картка з синім заголовком. Містить форму для додавання посту: Поля: "Дата",
"Платформа", "Заголовок", "Лайки", "Репости". Кнопка "Додати пост".
− Картка із зеленим заголовком. Містить форму для додавання відео: Поля:
"Дата", "Платформа", "Заголовок", "Перегляди", "Лайки", "Репости". Кнопка
"Додати відео".
− Картка з помаранчевим заголовком. Містить форму для додавання сторіс:
Поля: "Дата", "Платформа", "Заголовок (опціонально)", "Перегляди",
"Відповіді". Кнопка "Додати сторіс".
− Картка з блакитним заголовком. Містить форму для додавання даних про
підписників: Поля: "Дата", "Платформа", "Кількість". Кнопка "Додати
підписників".
− Кнопка “Зберегти”, яка відправляє форму на POST /add.
Висновки до розділу 2
У процесі розробки веб-застосунку для аналізу активності користувача в
соціальних мережах детально розглянуто стек технологій, що дозволяє швидко
розробляти функціональні рішення з належним інтерфейсом.
Розглянутий Flask мікро фреймворк для розробки веб-застосунків на Python та
його розширення: Flask-SQLAlchemy, Flask-Login, Flask-WTF.
Розглянуто взаємодію з Flask з базою даних за допомогою SQLAlchemy.
Спроєктована реляційна база даних для реалізації веб-застосунку.
Розглянуто використання бази даних з фреймворком.
Детально спроєктовані сторінки веб-застосунку для аналізу активності
користувача в соціальних мережах з описом елементів та функцій.
33
3 ПРОГРАМНА РЕАЛІЗАЦІЯ
3.1 Реалізація функцій веб-застосунку для аналізу активності
користувача в соціальних мережах
Початковим етапом програмної реалізації є створення проєкту базової
структури папок середовища.
Далі створюємо віртуальне середовище командою python -m venv venv.
Далі встановлюємо бібліотеки «pip install Flask Flask-SQLAlchemy Flask-Login
Flask-WTF Werkzeug» та «pip install Chart.js».
Створення основного файлу веб-застосунку __init__.py – це буде центральний
файл для створення та конфігурації Flask-застосунку, а також ініціалізації
розширень.
Визначаємо моделі бази даних models.py та створюємо файл.
Прописуємо значення для таблиць бази даних.
Рисунок 3.1 – Код моделі користувача
34
Рисунок 3.2 – Код моделі постів
Рисунок 3.3 – Код моделі відео
Рисунок 3.4 – Код моделі підписок
35
Рисунок 3.5 – Код моделі історій
Обов’язковим є визначення маршрутів та логіки веб-застосунку, описавши їх
в routes.py. Цей файл міститиме всі маршрути (URL-адреси) веб-застосунку та логіку
обробки запитів.
Рисунок 3.6 – Код моделі користувача
Наприклад, для авторизації використовується:
Рисунок 3.7 – Код маршруту авторизації
36
Далі потрібно створити шаблони сторінок сайту:
У першу чергу створюємо файл, який буде шаблоном для інших сторінок
«base.html», саме в ньому підключений bootstrap та розташовані всі базові стилі:
Рисунок 3.8 – Розмітка шаблону для всіх сторінок
Створюємо сторінку «Вхід» (Login) та налаштовуємо авторизацію:
Рисунок 3.9 – Розмітка сторінки «Вхід» (Login)
37
Рисунок 3.10 – Код маршруту сторінки Входу (Login)
Код сторінки «Реєстрація» (Register):
Рисунок 3.11 – Розмітка сторінки «Реєстрації»
Створюємо та налаштовуємо виведення графіків за допомогою Chart.js. "Дашборд"
(Dashboard):
38
Рисунок 3.12 – Розмітка сторінки Дашбрду ч.1
Рисунок 3.13 – Розмітка сторінки Дашбрду ч.2
39
Рисунок 3.14 – Розмітка сторінки Дашбрду ч.3
Рисунок 3.15 – Розмітка сторінки Дашбрду ч.4
40
Рисунок 3.16 – Розмітка сторінки Дашбрду ч.5
Рисунок 3.17 – Розмітка сторінки Дашбрду ч.6
Запускаємо створення діаграми.
41
Рисунок 3.18 – Код клієнтської сторони для створення дашборду
Створюємо сторінку та прописуємо логіку додавання "Додати дані" (Add Data):
Рисунок 3.19 – Розмітка сторінки дашборду
42
Рисунок 3.20 – Код маршруту додавання даних
Логіка кнопки "Вийти" (Logout):
Рисунок 3.21 – Код маршрут "Вийти" (Logout)
43
3.2 Реалізація зовнішнього UI/UX – дизайну
Головна сторінка веб-застосунку для аналізу активності користувача в
соціальних мережах зображена на рисунку 3.22. Для перегляду веб-застосунку
потрібно авторизуватися.
Рисунок 3.22 – Вікно авторизації веб-застосунку
Якщо немає облікового запису, то потрібно перейти на сторінку реєстрації та
ввести дані для реєстрації (рис. 3.23).
Рисунок 3.23 – Вікно реєстрації
Після авторизації відкриється сторінка аналітики з відображенням статистики
у вигляді графів (рис. 3.24–3.26).
44
Рисунок 3.24 – Вікно «Аналітика»
Рисунок 3.25 – Розділ динаміки по днях
45
Рисунок 3.26 – Розділ статистики по платформам
Для відображення статистики в табличній формі потрібно перейти на
«Статистика» та вибрати категорію для перегляду (рис. 3.27–3.28).
Рисунок 3.27 – Сторінка «Статистика» (без вибраного типу контенту)
46
Рисунок 3.28 – Сторінка «Статистика» (без вибраного типу контенту)
Для додавання нових записів в базу даних потрібно перейти у вкладку «Додати
дані», як відображено на рисунку 3.29.
Рисунок 3.29 – Сторінка «Додати дані»
Для додавання даних потрібно заповнити потрібні поля та натиснути на
кнопку, наприклад «Додати сторіс», як відображено на рисунку 3.30.
Після успішного збереження даних, отримаємо повідомлення (рис. 3.31).
47
Рисунок 3.30 – Введення нових даних
Рисунок 3.31 – Оповіщення про успішне збереження даних
Висновки до розділу 3
У даному розділі було детально описано процес реалізації серверної та
клієнтської частини проєкту, налаштування проєкту на базі фреймворка flask.
48
Розглянуто реалізацію функцій та зовнішнього UI/UX – дизайну веб-
застосунку для аналізу активності користувача в соціальних мережах.
Представлено результати статистики розділів динаміки по днях та по
платформам.
49
ВИСНОВКИ
У ході написання кваліфікаційної роботи бакалавра було реалізовано веб-
застосунок для аналізу активності користувача в соціальних мережах.
Було детально проаналізовано провідні системи для поглибленого аналізу
соціальних мереж для визначення основних переваг та недоліків, як комерційних так
і безкоштовних сервісів. Поглиблений аналіз дозволив зробити висновки, що
більшість функцій систем є надлишковими, а користувачі віддають перевагу більш
«простому» та безкоштовному Google Analytics, а саме близько 70% користувачів.
Також було сформовано основні функціональні вимоги до веб-застосунку для
аналізу активності користувача в соціальних мережах.
Розглянутий Flask мікро фреймворк для розробки веб-застосунків на Python та
його розширення: Flask-SQLAlchemy, Flask-Login, Flask-WTF, а також взаємодію з
Flask з базою даних за допомогою SQLAlchemy.
Спроєктована реляційна база даних для реалізації веб-застосунку та
розглянуто використання її з фреймворком.
Детально спроєктовані сторінки веб-застосунку для аналізу активності
користувача в соціальних мережах з описом елементів та функцій.
Мета роботи досягнута. У результаті виконання роботи були досягнуті всі цілі,
визначені на етапі розробки вимог і проєктування та розроблено готовий продукт
для використання.
50
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ
1. Сайт “Semrush Social Media Analytics”. URL: https://www.semrush.com/social-
media/ (дата звернення 15.05.2025).
2. Сайт “Buffer Analyze”. URL: https://buffer.com/analyze (дата звернення
15.05.2025).
3. Сайт “Google Analytics”. URL:
https://marketingplatform.google.com/about/analytics/ (дата звернення
15.05.2025).
4. Сайт “Hootsuite Analytics”. URL: https://hootsuite.com (дата звернення
15.05.2025).
5. Сайт “Sprout Social”. URL: https://sproutsocial.com (дата звернення 15.05.2025).
6. Сайт “Brandwatch”. URL: https://www.brandwatch.com (дата звернення
15.05.2025).
51
ДОДАТОК А
Затверджую
Зав. кафедри КНСА,
______________ Юрій ТРИУС
«____»____________2025 р.
ВЕБ-ЗАСТОСУНОК ДЛЯ АНАЛІЗУ АКТИВНОСТІ КОРИСТУВАЧА В
СОЦІАЛЬНИХ МЕРЕЖАХ
Специфікація
482.ЧДТУ. 52108-01
Листів 2
Розробник ____________________ Олександр ГРЕЧІШКІН
Керівник ____________________ Любов ОКСАМИТНА
Черкаси – 2025
52
482.ЧДТУ. 52108-01
Позначення Найменування Примітка
Документація
482.ЧДТУ. 52108-01 12 01 Текст програми
53
ДОДАТОК Б
ВЕБ-ЗАСТОСУНОК ДЛЯ АНАЛІЗУ АКТИВНОСТІ КОРИСТУВАЧА В
СОЦІАЛЬНИХ МЕРЕЖАХ
Текст програми
482.ЧДТУ. 52108-01 12 01
Листів 32
Розробник _____________ Олександр ГРЕЧІШКІН
Черкаси – 2025
_base.html світліший синій при наведенні/активності */
color: #ffffff;
<!doctype html> }
<html lang="uk"> .sidebar .nav-link i {
<head> margin-right: 10px;
<meta charset="UTF-8"> }
<meta name="viewport" .content {
content="width=device-width, initial-scale=1.0"> padding: 20px;
<title>{% block title %}Ваш Заголовок{% flex-grow: 1;
endblock %} - Статистика Соцмереж</title> }
<link .footer {
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css background-color: #34495e; /* Той самий,
/bootstrap.min.css" rel="stylesheet"> що й наведення на меню */
<link rel="stylesheet" color: #ecf0f1;
href="https://cdnjs.cloudflare.com/ajax/libs/font- padding: 15px 0;
awesome/6.0.0-beta3/css/all.min.css"> text-align: center;
<style> width: 100%;
body { }
display: flex; </style>
flex-direction: column; {% block head_extra %}{% endblock %}
min-height: 100vh; </head>
} <body>
.wrapper { <header class="navbar navbar-expand-lg
flex: 1; navbar-dark bg-dark">
display: flex; <div class="container-fluid">
} <a class="navbar-brand" href="{{
.sidebar { url_for('main.dashboard') }}">
background-color: #2c3e50; /* Темно-синій <i class="fas fa-chart-line me-
*/ 2"></i>Аналітика Соцмереж
color: #ecf0f1; /* Світлий текст */ </a>
padding-top: 20px; <button class="navbar-toggler" type="button"
min-height: 100vh; data-bs-toggle="collapse" data-bs-target="#navbarNav"
box-shadow: 2px 0 5px rgba(0,0,0,0.1); aria-controls="navbarNav" aria-expanded="false" aria-
} label="Toggle navigation">
.sidebar .nav-link { <span class="navbar-toggler-icon"></span>
color: #ecf0f1; </button>
padding: 10px 15px; <div class="collapse navbar-collapse"
transition: background-color 0.3s ease; id="navbarNav">
} <ul class="navbar-nav ms-auto">
.sidebar .nav-link:hover, .sidebar .nav- {% if current_user.is_authenticated %}
link.active { <li class="nav-item">
background-color: #34495e; /* Трохи <span class="nav-link text-
55
light">Привіт, {{ current_user.first_name }}!</span> url_for('main.statistics') }}">
</li> <i class="fas fa-table"></i> Статистика
<li class="nav-item"> </a>
<a class="nav-link" href="{{ </li>
url_for('main.logout') }}"><i class="fas fa-sign-out-alt me- <li class="nav-item">
1"></i>Вийти</a> <a class="nav-link {% if request.endpoint
</li> == 'main.add_data' %}active{% endif %}" href="{{
{% else %} url_for('main.add_data') }}">
<li class="nav-item"> <i class="fas fa-plus-circle"></i>
<a class="nav-link" href="{{ Додати дані
url_for('main.login') }}"><i class="fas fa-sign-in-alt me- </a>
1"></i>Вхід</a> </li>
</li> <li class="nav-item">
<li class="nav-item"> <a class="nav-link" href="{{
<a class="nav-link" href="{{ url_for('main.logout') }}">
url_for('main.register') }}"><i class="fas fa-user-plus me- <i class="fas fa-sign-out-alt"></i>
1"></i>Реєстрація</a> Вийти
</li> </a>
{% endif %} </li>
</ul> </ul>
</div> </div>
</div> </nav>
</header> {% endif %}
<div class="wrapper"> <main class="col-md-9 ms-sm-auto col-lg-10
{% if current_user.is_authenticated %} px-md-4 content">
<nav id="sidebarMenu" class="col-md-3 col- {% with messages =
lg-2 d-md-block sidebar collapse"> get_flashed_messages(with_categories=true) %}
<div class="position-sticky pt-3"> {% if messages %}
<h5 class="mt-3 text-light text- <div class="container mt-3">
center">Меню</h5> {% for category, message in messages %}
<ul class="nav flex-column"> <div class="alert alert-{{ category }}"
<li class="nav-item"> role="alert">
<a class="nav-link {% if request.endpoint {{ message }}
== 'main.dashboard' %}active{% endif %}" href="{{ </div>
url_for('main.dashboard') }}"> {% endfor %}
<i class="fas fa-chart-bar"></i> Графіки </div>
</a> {% endif %}
</li> {% endwith %}
<li class="nav-item">
<a class="nav-link {% if request.endpoint {% block content %}{% endblock %}
== 'main.statistics' %}active{% endif %}" href="{{ </main>
56
</div> <div class="card shadow-sm">
<div class="card-header bg-primary text-
<footer class="footer"> white">Додати пост</div>
<div class="container"> <div class="card-body">
<span>© {{ now().year }} Аналітика <form method="POST">
Соцмереж. Усі права захищені.</span> {{ post_form.hidden_tag() }}
</div> <div class="mb-3">
</footer> {{
post_form.date.label(class="form-label") }}
<script {{ post_form.date(class="form-
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/b control") }}
ootstrap.bundle.min.js"></script> {% if post_form.date.errors %}
{% block scripts_extra %}{% endblock %} <div class="text-danger">
</body> {% for error in
</html> post_form.date.errors %}{{ error }}{% endfor %}
</div>
add.html {% endif %}
</div>
<div class="mb-3">
{{
post_form.platform.label(class="form-label") }}
{{
post_form.platform(class="form-control") }}
{% extends "_base.html" %} {% if post_form.platform.errors
%}
{% block title %}Додати дані - Статистика <div class="text-danger">
Соцмереж{% endblock %} {# Це єдине місце, де має {% for error in
бути block title #} post_form.platform.errors %}{{ error }}{% endfor %}
</div>
{% block content %} {% endif %}
<h1 class="mt-4">Додати дані</h1> </div>
<div class="mb-3">
{# ... тут знаходиться весь інший вміст вашої {{
сторінки для форм ... #} post_form.title.label(class="form-label") }}
{# Усі ваші форми для додавання Post, Video, {{ post_form.title(class="form-
Story, Subscriber мають бути тут #} control") }}
{# Переконайтеся, що ви видалили всі блоки {% if post_form.title.errors %}
відображення статистики, як ми обговорювали раніше. <div class="text-danger">
#} {% for error in
post_form.title.errors %}{{ error }}{% endfor %}
<div class="row"> </div>
<div class="col-md-6 mb-4"> {% endif %}
57
</div> {{
<div class="mb-3"> video_form.date.label(class="form-label") }}
{{ {{ video_form.date(class="form-
post_form.likes.label(class="form-label") }} control") }}
{{ post_form.likes(class="form- {% if video_form.date.errors %}
control") }} <div class="text-danger">
{% if post_form.likes.errors %} {% for error in
<div class="text-danger"> video_form.date.errors %}{{ error }}{% endfor %}
{% for error in </div>
post_form.likes.errors %}{{ error }}{% endfor %} {% endif %}
</div> </div>
{% endif %} <div class="mb-3">
</div> {{
<div class="mb-3"> video_form.platform.label(class="form-label") }}
{{ {{
post_form.shares.label(class="form-label") }} video_form.platform(class="form-control") }}
{{ post_form.shares(class="form- {% if video_form.platform.errors
control") }} %}
{% if post_form.shares.errors %} <div class="text-danger">
<div class="text-danger"> {% for error in
{% for error in video_form.platform.errors %}{{ error }}{% endfor %}
post_form.shares.errors %}{{ error }}{% endfor %} </div>
</div> {% endif %}
{% endif %} </div>
</div> <div class="mb-3">
<button type="submit" {{
name="submit_button" value="Додати пост" class="btn video_form.title.label(class="form-label") }}
btn-primary"><i class="fas fa-plus-circle me- {{ video_form.title(class="form-
2"></i>Додати пост</button> control") }}
</form> {% if video_form.title.errors %}
</div> <div class="text-danger">
</div> {% for error in
</div> video_form.title.errors %}{{ error }}{% endfor %}
</div>
<div class="col-md-6 mb-4"> {% endif %}
<div class="card shadow-sm"> </div>
<div class="card-header bg-success text- <div class="mb-3">
white">Додати відео</div> {{
<div class="card-body"> video_form.views.label(class="form-label") }}
<form method="POST"> {{ video_form.views(class="form-
{{ video_form.hidden_tag() }} control") }}
<div class="mb-3"> {% if video_form.views.errors %}
58
<div class="text-danger"> <div class="card shadow-sm">
{% for error in <div class="card-header bg-warning text-
video_form.views.errors %}{{ error }}{% endfor %} white">Додати сторіс</div>
</div> <div class="card-body">
{% endif %} <form method="POST">
</div> {{ story_form.hidden_tag() }}
<div class="mb-3"> <div class="mb-3">
{{ {{
video_form.likes.label(class="form-label") }} story_form.date.label(class="form-label") }}
{{ video_form.likes(class="form- {{ story_form.date(class="form-
control") }} control") }}
{% if video_form.likes.errors %} {% if story_form.date.errors %}
<div class="text-danger"> <div class="text-danger">
{% for error in {% for error in
video_form.likes.errors %}{{ error }}{% endfor %} story_form.date.errors %}{{ error }}{% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="mb-3"> <div class="mb-3">
{{ {{
video_form.shares.label(class="form-label") }} story_form.platform.label(class="form-label") }}
{{ video_form.shares(class="form- {{
control") }} story_form.platform(class="form-control") }}
{% if video_form.shares.errors %} {% if story_form.platform.errors
<div class="text-danger"> %}
{% for error in <div class="text-danger">
video_form.shares.errors %}{{ error }}{% endfor %} {# CORRECTED LINE:
</div> Removed the extra {% for error in
{% endif %} story_form.platform.errors %}{{ error }} #}
</div> {% for error in
<button type="submit" story_form.platform.errors %}{{ error }}{% endfor %}
name="submit_button" value="Додати відео" class="btn </div>
btn-success"><i class="fas fa-plus-circle me- {% endif %}
2"></i>Додати відео</button> </div>
</form> <div class="mb-3">
</div> {{
</div> story_form.title.label(class="form-label") }}
</div> {{ story_form.title(class="form-
</div> control") }}
{% if story_form.title.errors %}
<div class="row mt-4"> <div class="text-danger">
<div class="col-md-6 mb-4"> {% for error in
59
story_form.title.errors %}{{ error }}{% endfor %} <form method="POST">
</div> {{ sub_form.hidden_tag() }}
{% endif %} <div class="mb-3">
</div> {{
<div class="mb-3"> sub_form.date.label(class="form-label") }}
{{ {{ sub_form.date(class="form-
story_form.views.label(class="form-label") }} control") }}
{{ story_form.views(class="form- {% if sub_form.date.errors %}
control") }} <div class="text-danger">
{% if story_form.views.errors %} {% for error in
<div class="text-danger"> sub_form.date.errors %}{{ error }}{% endfor %}
{% for error in </div>
story_form.views.errors %}{{ error }}{% endfor %} {% endif %}
</div> </div>
{% endif %} <div class="mb-3">
</div> {{
<div class="mb-3"> sub_form.platform.label(class="form-label") }}
{{ {{
story_form.replies.label(class="form-label") }} sub_form.platform(class="form-control") }}
{{ story_form.replies(class="form- {% if sub_form.platform.errors %}
control") }} <div class="text-danger">
{% if story_form.replies.errors %} {% for error in
<div class="text-danger"> sub_form.platform.errors %}{{ error }}{% endfor %}
{% for error in </div>
story_form.replies.errors %}{{ error }}{% endfor %} {% endif %}
</div> </div>
{% endif %} <div class="mb-3">
</div> {{
<button type="submit" sub_form.count.label(class="form-label") }}
name="submit_button" value="Додати сторіс" class="btn {{ sub_form.count(class="form-
btn-warning text-white"><i class="fas fa-plus-circle me- control") }}
2"></i>Додати сторіс</button> {% if sub_form.count.errors %}
</form> <div class="text-danger">
</div> {% for error in
</div> sub_form.count.errors %}{{ error }}{% endfor %}
</div> </div>
{% endif %}
<div class="col-md-6 mb-4"> </div>
<div class="card shadow-sm"> <button type="submit"
<div class="card-header bg-info text- name="submit_button" value="Додати підписників"
white">Додати підписників</div> class="btn btn-info text-white"><i class="fas fa-plus-circle
<div class="card-body"> me-2"></i>Додати підписників</button>
60
</form> <div class="col-md-3 mb-4">
</div> <div class="card shadow-sm h-100">
</div> <div class="card-header bg-success text-
</div> white">
</div> <i class="fas fa-video me-2"></i>
Всього відео
{% endblock %} </div>
<div class="card-body d-flex align-items-
dashboard.html center justify-content-center">
{% extends "_base.html" %} <h2 class="card-title text-center
display-4">{{ general_stats.total_videos }}</h2>
{% block title %}Дашборд{% endblock %} </div>
</div>
{% block head_extra %} </div>
<script <div class="col-md-3 mb-4">
src="https://cdn.jsdelivr.net/npm/chart.js"></script> <div class="card shadow-sm h-100">
<script <div class="card-header bg-warning text-
src="https://cdn.jsdelivr.net/npm/[email protected]/moment white">
.min.js"></script> <i class="fas fa-history me-2"></i>
<script src="https://cdn.jsdelivr.net/npm/chartjs- Всього сторіс
[email protected]/dist/chartjs-adapter- </div>
moment.min.js"></script> <div class="card-body d-flex align-items-
{% endblock %} center justify-content-center">
<h2 class="card-title text-center
{% block content %} display-4">{{ general_stats.total_stories }}</h2>
<h3>Аналітика</h3> </div>
</div>
<div class="row"> </div>
<div class="col-md-3 mb-4"> <div class="col-md-3 mb-4">
<div class="card shadow-sm h-100"> <div class="card shadow-sm h-100">
<div class="card-header bg-primary text- <div class="card-header bg-info text-
white"> white">
<i class="fas fa-images me-2"></i> <i class="fas fa-users me-2"></i>
Всього постів Всього підписників (останнє)
</div> </div>
<div class="card-body d-flex align-items- <div class="card-body d-flex align-items-
center justify-content-center"> center justify-content-center">
<h2 class="card-title text-center <h2 class="card-title text-center
display-4">{{ general_stats.total_posts }}</h2> display-4">{{ general_stats.latest_subscribers }}</h2>
</div> </div>
</div> </div>
</div> </div>
61
</div> </div>
</div>
<hr> </div>
<div class="col-md-6 mb-4">
<h4>Динаміка за днями</h4> <div class="card shadow-sm h-100">
<div class="row"> <div class="card-header">Динаміка
<div class="col-md-6 mb-4"> переглядів відео</div>
<div class="card shadow-sm h-100"> <div class="card-body">
<div class="card-header">Динаміка <canvas
постів</div> id="videoViewsChart"></canvas> {# Новий Canvas #}
<div class="card-body"> </div>
<canvas id="postsChart"></canvas> </div>
</div> </div>
</div> <div class="col-md-6 mb-4">
</div> <div class="card shadow-sm h-100">
<div class="col-md-6 mb-4"> <div class="card-header">Динаміка
<div class="card shadow-sm h-100"> лайків відео</div>
<div class="card-header">Динаміка <div class="card-body">
лайків постів</div> <canvas
<div class="card-body"> id="videoLikesChart"></canvas> {# Новий Canvas #}
<canvas </div>
id="postLikesChart"></canvas> {# Новий Canvas #} </div>
</div> </div>
</div> <div class="col-md-6 mb-4">
</div> <div class="card shadow-sm h-100">
<div class="col-md-6 mb-4"> <div class="card-header">Динаміка
<div class="card shadow-sm h-100"> репостів відео</div>
<div class="card-header">Динаміка <div class="card-body">
репостів постів</div> <canvas
<div class="card-body"> id="videoSharesChart"></canvas> {# Новий Canvas #}
<canvas </div>
id="postSharesChart"></canvas> {# Новий Canvas #} </div>
</div> </div>
</div>
</div> <div class="col-md-6 mb-4">
<div class="card shadow-sm h-100">
<div class="col-md-6 mb-4"> <div class="card-header">Динаміка
<div class="card shadow-sm h-100"> сторіс</div>
<div class="card-header">Динаміка <div class="card-body">
відео</div> <canvas id="storiesChart"></canvas>
<div class="card-body"> </div>
<canvas id="videosChart"></canvas> </div>
62
</div> <canvas
<div class="col-md-6 mb-4"> id="postsByPlatformChart"></canvas>
<div class="card shadow-sm h-100"> </div>
<div class="card-header">Динаміка </div>
переглядів сторіс</div> </div>
<div class="card-body"> <div class="col-md-6 mb-4">
<canvas <div class="card shadow-sm h-100">
id="storyViewsChart"></canvas> {# Новий Canvas #} <div class="card-header">Відео за
</div> платформами</div>
</div> <div class="card-body">
</div> <canvas
<div class="col-md-6 mb-4"> id="videosByPlatformChart"></canvas>
<div class="card shadow-sm h-100"> </div>
<div class="card-header">Динаміка </div>
відповідей на сторіс</div> </div>
<div class="card-body"> <div class="col-md-6 mb-4">
<canvas <div class="card shadow-sm h-100">
id="storyRepliesChart"></canvas> {# Новий Canvas #} <div class="card-header">Сторіс за
</div> платформами</div>
</div> <div class="card-body">
</div> <canvas
id="storiesByPlatformChart"></canvas>
<div class="col-md-6 mb-4"> </div>
<div class="card shadow-sm h-100"> </div>
<div class="card-header">Динаміка </div>
підписників</div> <div class="col-md-6 mb-4">
<div class="card-body"> <div class="card shadow-sm h-100">
<canvas id="subsChart"></canvas> <div class="card-header">Підписники за
</div> платформами</div>
</div> <div class="card-body">
</div> <canvas
</div> id="subscribersByPlatformChart"></canvas>
</div>
<hr> </div>
</div>
<h4>Статистика за платформами</h4> </div>
<div class="row">
<div class="col-md-6 mb-4"> {% endblock %}
<div class="card shadow-sm h-100">
<div class="card-header">Пости за {% block scripts_extra %}
платформами</div> <script>
<div class="card-body"> const chartData = {{ chart_data | tojson }};
63
title: {
function createChart(elementId, chartConfig, display: true,
titleText) { // Змінено параметр data на chartConfig text: titleText
const canvasElement = },
document.getElementById(elementId); legend: {
if (!canvasElement) { position: 'top',
console.error(`Canvas element with id }
'${elementId}' not found.`); },
return; scales: {
} x: {
const ctx = canvasElement.getContext('2d'); type: 'time',
if (!ctx) { time: {
console.error(`Failed to get 2D context for unit: 'day',
canvas '${elementId}'.`); tooltipFormat: 'YYYY-MM-DD',
return; displayFormats: {
} day: 'MMM DD'
}
// Перевірка, чи chartConfig та },
chartConfig.labels існують і чи labels не порожні title: {
if (chartConfig && chartConfig.labels && display: true,
chartConfig.labels.length > 0 && chartConfig.datasets) { text: 'Дата'
// Перевірка, чи є дані в datasets }
let hasData = false; },
chartConfig.datasets.forEach(dataset => { y: {
if (dataset.data && beginAtZero: true,
dataset.data.some(value => value > 0)) { // Перевіряємо, title: {
чи є хоча б одне значення більше 0 display: true,
hasData = true; text: 'Кількість'
} }
}); }
}
if (hasData) { }
new Chart(ctx, { // Використовуємо ctx });
замість elementId } else {
type: 'line',
data: chartConfig, // Використовуємо canvasElement.parentElement.innerHTML = `<p
chartConfig напряму class="text-muted text-center py-5">Немає даних для
options: { діаграми "${titleText.toLowerCase()}".</p>`;
responsive: true, }
maintainAspectRatio: false, // Додано } else {
для кращого контролю розміру // Якщо chartConfig.labels порожній або
plugins: { відсутній, показуємо повідомлення
64
canvasElement.parentElement.innerHTML = }
`<p class="text-muted text-center py-5">Немає даних для }
діаграми "${titleText.toLowerCase()}".</p>`; }
} });
} } else {
function createPieChart(elementId, canvasElement.parentElement.innerHTML = `<p
chartConfig, titleText) { // Змінено параметр data на class="text-muted text-center py-5">Немає даних для
chartConfig діаграми "${titleText.toLowerCase()}".</p>`;
const canvasElement = }
document.getElementById(elementId); }
if (!canvasElement) {
console.error(`Canvas element with id
'${elementId}' not found.`); document.addEventListener('DOMContentLoaded',
return; function () { // Запускаємо створення діаграм після
} завантаження DOM
const ctx = canvasElement.getContext('2d'); // Динаміка
if (!ctx) { createChart('postsChart', chartData.posts,
console.error(`Failed to get 2D context for 'Динаміка постів');
canvas '${elementId}'.`); createChart('postLikesChart',
return; chartData.post_likes, 'Динаміка лайків постів'); //
} Новий графік
createChart('postSharesChart',
if (chartConfig && chartConfig.labels && chartData.post_shares, 'Динаміка репостів постів'); //
chartConfig.labels.length > 0 && chartConfig.datasets && Новий графік
chartConfig.datasets[0] &&
chartConfig.datasets[0].data.some(value => value > 0)) { createChart('videosChart', chartData.videos,
new Chart(ctx, { // Використовуємо ctx 'Динаміка відео');
type: 'pie', createChart('videoViewsChart',
data: chartConfig, // Використовуємо chartData.video_views, 'Динаміка переглядів відео'); //
chartConfig напряму Новий графік
options: { createChart('videoLikesChart',
responsive: true, chartData.video_likes, 'Динаміка лайків відео'); //
maintainAspectRatio: false, // Новий графік
Додано createChart('videoSharesChart',
plugins: { chartData.video_shares, 'Динаміка репостів відео'); //
title: { Новий графік
display: true,
text: titleText createChart('storiesChart', chartData.stories,
}, 'Динаміка сторіс');
legend: { createChart('storyViewsChart',
position: 'top', chartData.story_views, 'Динаміка переглядів сторіс'); //
65
Новий графік <div class="input-group">
createChart('storyRepliesChart', <span class="input-group-text"><i
chartData.story_replies, 'Динаміка відповідей на сторіс'); class="fas fa-user"></i></span>
// Новий графік {{ form.username(class="form-control" +
(" is-invalid" if form.username.errors else ""),
createChart('subsChart', id="username") }}
chartData.subscribers, 'Динаміка підписників'); </div>
{% if form.username.errors %}
// За платформами <div class="invalid-feedback d-block">
createPieChart('postsByPlatformChart', {% for error in form.username.errors
chartData.posts_by_platform, 'Пости за платформами'); %}{{ error }}{% endfor %}
createPieChart('videosByPlatformChart', </div>
chartData.videos_by_platform, 'Відео за платформами'); {% endif %}
createPieChart('storiesByPlatformChart', </div>
chartData.stories_by_platform, 'Сторіс за платформами');
<div class="mb-3">
createPieChart('subscribersByPlatformChart', {{ form.password.label(class="form-label")
chartData.subscribers_by_platform, 'Підписники за }}
платформами'); <div class="input-group">
}); <span class="input-group-text"><i
</script> class="fas fa-lock"></i></span>
{% endblock %} {{ form.password(class="form-control" +
(" is-invalid" if form.password.errors else ""),
id="password") }}
login. {% extends "_base.html" %} </div>
{% if form.password.errors %}
{% block title %}Вхід{% endblock %} <div class="invalid-feedback d-block">
{% for error in form.password.errors
{% block content %} %}{{ error }}{% endfor %}
<div class="row justify-content-center mt-5"> </div>
<div class="col-md-6 col-lg-4"> {% endif %}
<div class="card shadow-lg p-4"> </div>
<h2 class="card-title text-center mb-
4">Авторизація</h2> <div class="d-grid gap-2">
<form method="POST" action="{{ {{ form.submit(class="btn btn-primary btn-
url_for('main.login') }}"> lg") }} {# Використання form.submit #}
{{ form.hidden_tag() }} {# Додано CSRF <a href="{{ url_for('main.register') }}"
токен #} class="btn btn-link mt-2">Немає акаунту? <i class="fas
fa-user-plus ms-1"></i>Створити акаунт</a>
<div class="mb-3"> </div>
{{ form.username.label(class="form-label") </form>
}} </div>
66
</div> <span class="input-group-text"><i
</div> class="fas fa-signature"></i></span>
{% endblock %}html {{ form.last_name(class="form-control" +
(" is-invalid" if form.last_name.errors else "")) }}
register.html </div>
{% if form.last_name.errors %}
{% extends "_base.html" %} <div class="invalid-feedback d-block">
{% for error in form.last_name.errors
{% block title %}Реєстрація{% endblock %} %}{{ error }}{% endfor %}
</div>
{% block content %} {% endif %}
<div class="row justify-content-center mt-5"> </div>
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg p-4"> <div class="mb-3">
<h2 class="card-title text-center mb- {{ form.email.label(class="form-label") }}
4">Створення акаунту</h2> <div class="input-group">
<form method="POST" action="{{ <span class="input-group-text"><i
url_for('main.register') }}"> class="fas fa-envelope"></i></span>
{{ form.hidden_tag() }} {# Додано CSRF {{ form.email(class="form-control" + ("
токен #} is-invalid" if form.email.errors else "")) }}
</div>
<div class="mb-3"> {% if form.email.errors %}
{{ form.first_name.label(class="form- <div class="invalid-feedback d-block">
label") }} {% for error in form.email.errors %}{{
<div class="input-group"> error }}{% endfor %}
<span class="input-group-text"><i </div>
class="fas fa-signature"></i></span> {% endif %}
{{ form.first_name(class="form-control" </div>
+ (" is-invalid" if form.first_name.errors else "")) }}
</div> <div class="mb-3">
{% if form.first_name.errors %} {{ form.username.label(class="form-label")
<div class="invalid-feedback d-block"> }}
{% for error in form.first_name.errors <div class="input-group">
%}{{ error }}{% endfor %} <span class="input-group-text"><i
</div> class="fas fa-user"></i></span>
{% endif %} {{ form.username(class="form-control" +
</div> (" is-invalid" if form.username.errors else "")) }}
</div>
<div class="mb-3"> {% if form.username.errors %}
{{ form.last_name.label(class="form- <div class="invalid-feedback d-block">
label") }} {% for error in form.username.errors
<div class="input-group"> %}{{ error }}{% endfor %}
67
</div> {{ form.submit(class="btn btn-success btn-
{% endif %} lg") }} {# Використання form.submit #}
</div> <a href="{{ url_for('main.login') }}"
class="btn btn-link mt-2">Вже є акаунт? <i class="fas fa-
<div class="mb-3"> sign-in-alt ms-1"></i>Увійти</a>
{{ form.password.label(class="form-label") </div>
}} </form>
<div class="input-group"> </div>
<span class="input-group-text"><i </div>
class="fas fa-lock"></i></span> </div>
{{ form.password(class="form-control" + {% endblock %}
(" is-invalid" if form.password.errors else "")) }}
</div> statistics.html
{% if form.password.errors %} {% extends "_base.html" %}
<div class="invalid-feedback d-block">
{% for error in form.password.errors {% block title %}Статистика{% endblock %}
%}{{ error }}{% endfor %}
</div> {% block content %}
{% endif %} <h3>Статистика за даними</h3>
</div>
<form method="GET" class="row mb-4 align-
<div class="mb-3"> {# Додано поле для items-end g-3">
підтвердження пароля #} <div class="col-md-4"> {# Змінено ширину на
{{ більшу #}
form.confirm_password.label(class="form-label") }} <label for="content_type" class="form-
<div class="input-group"> label">Тип контенту</label>
<span class="input-group-text"><i <select name="content_type"
class="fas fa-lock"></i></span> id="content_type" class="form-select"
{{ form.confirm_password(class="form- onchange="this.form.submit()">
control" + (" is-invalid" if form.confirm_password.errors <option value="">Оберіть тип</option>
else "")) }} <option value="posts" {% if content_type ==
</div> 'posts' %}selected{% endif %}>Пости</option>
{% if form.confirm_password.errors %} <option value="video" {% if content_type ==
<div class="invalid-feedback d-block"> 'video' %}selected{% endif %}>Відео</option>
{% for error in <option value="stories" {% if content_type
form.confirm_password.errors %}{{ error }}{% endfor == 'stories' %}selected{% endif %}>Сторіс</option>
%} <option value="subscribers" {% if
</div> content_type == 'subscribers' %}selected{% endif
{% endif %} %}>Підписники</option>
</div> </select>
</div>
<div class="d-grid gap-2"> {# Поля "Платформа", "Початкова дата" та
68
"Кінцева дата" ВИДАЛЕНО #} <th>Заголовок</th>
</form> <th>Перегляди</th>
<th>Лайки</th>
<div class="row"> <th>Репости</th>
<div class="col-12"> </tr>
{% if content_type %} </thead>
{% if data %} <tbody>
{% if content_type == 'posts' %} {% for video in data %}
<div class="table-responsive"> <tr>
<table class="table table-striped table- <td>{{ video.date }}</td>
hover"> <td>{{ video.platform
<thead class="table-dark"> }}</td>
<tr> <td>{{ video.title }}</td>
<th>Дата</th> <td>{{ video.views }}</td>
<th>Платформа</th> <td>{{ video.likes }}</td>
<th>Заголовок</th> <td>{{ video.shares }}</td>
<th>Лайки</th> </tr>
<th>Репости</th> {% endfor %}
</tr> </tbody>
</thead> </table>
<tbody> </div>
{% for post in data %} {% elif content_type == 'stories' %}
<tr> <div class="table-responsive">
<td>{{ post.date }}</td> <table class="table table-striped table-
<td>{{ post.platform hover">
}}</td> <thead class="table-dark">
<td>{{ post.title }}</td> <tr>
<td>{{ post.likes }}</td> <th>Дата</th>
<td>{{ post.shares }}</td> <th>Платформа</th>
</tr> <th>Заголовок</th>
{% endfor %} <th>Перегляди</th>
</tbody> <th>Відповіді</th>
</table> </tr>
</div> </thead>
{% elif content_type == 'video' %} <tbody>
<div class="table-responsive"> {% for story in data %}
<table class="table table-striped table- <tr>
hover"> <td>{{ story.date }}</td>
<thead class="table-dark"> <td>{{ story.platform
<tr> }}</td>
<th>Дата</th> <td>{{ story.title |
<th>Платформа</th> default('N/A') }}</td>
69
<td>{{ story.views }}</td> {% endblock %}
<td>{{ story.replies }}</td>
</tr> __init__.py
{% endfor %} from flask import Flask
</tbody> from flask_sqlalchemy import SQLAlchemy
</table> from flask_login import LoginManager
</div> from datetime import datetime # <--- Додайте
{% elif content_type == 'subscribers' %} цей імпорт
<div class="table-responsive">
<table class="table table-striped table- db = SQLAlchemy()
hover"> login_manager = LoginManager()
<thead class="table-dark">
<tr>
<th>Дата</th> def create_app():
<th>Платформа</th> app = Flask(__name__)
<th>Кількість</th> app.config['SECRET_KEY'] = 'secret'
</tr>
</thead> app.config['SQLALCHEMY_DATABASE_URI'] =
<tbody> 'sqlite:///site.db'
{% for sub in data %}
<tr> app.config['SQLALCHEMY_TRACK_MODIFICATION
<td>{{ sub.date }}</td> S'] = False
<td>{{ sub.platform }}</td>
<td>{{ sub.count }}</td> db.init_app(app)
</tr> login_manager.init_app(app)
{% endfor %} login_manager.login_view = 'main.login'
</tbody>
</table> from .routes import main
</div> app.register_blueprint(main)
{% endif %}
{% else %} with app.app_context():
<p class="text-muted text-center py- db.create_all()
5">Немає даних для відображення за обраним типом
контенту.</p> # Додайте цей контекст-процесор для
{% endif %} доступу до "now()" в шаблонах
{% else %} @app.context_processor
<p class="text-muted text-center py- def inject_now():
5">Оберіть тип контенту для перегляду return {'now': datetime.utcnow} # Можете
статистики.</p> використовувати datetime.now для локального часу
{% endif %}
</div> return app
</div>
70
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, # Форма для входу
DateField, SubmitField, SelectField, PasswordField class LoginForm(FlaskForm):
from wtforms.validators import DataRequired, username = StringField('Логін',
Optional, Length, Email, EqualTo, ValidationError validators=[DataRequired()])
from .models import User # Імпортуємо User password = PasswordField('Пароль',
для перевірки унікальності validators=[DataRequired()])
submit = SubmitField('Увійти')
# Форма для реєстрації
class RegistrationForm(FlaskForm): # Форма для постів
first_name = StringField('Ім\'я', class PostForm(FlaskForm):
validators=[DataRequired(), Length(min=2, max=50)]) date = DateField('Дата',
last_name = StringField('Прізвище', validators=[DataRequired()])
validators=[DataRequired(), Length(min=2, max=50)]) platform = StringField('Платформа',
email = StringField('Email', validators=[DataRequired()])
validators=[DataRequired(), Email()]) title = StringField('Заголовок',
username = StringField('Логін', validators=[DataRequired()])
validators=[DataRequired(), Length(min=2, max=20)]) likes = IntegerField('Лайки',
password = PasswordField('Пароль', validators=[DataRequired()])
validators=[DataRequired(), Length(min=6)]) shares = IntegerField('Репости',
confirm_password = validators=[DataRequired()])
PasswordField('Підтвердіть Пароль', submit = SubmitField('Додати пост')
validators=[DataRequired(), EqualTo('password', # Форма для відео
message='Паролі повинні співпадати')]) class VideoForm(FlaskForm):
submit = SubmitField('Зареєструватися') date = DateField('Дата',
validators=[DataRequired()])
def validate_username(self, username): platform = StringField('Платформа',
user = validators=[DataRequired()])
User.query.filter_by(username=username.data).first() title = StringField('Заголовок',
if user: validators=[DataRequired()])
raise ValidationError('Цей логін вже views = IntegerField('Перегляди',
зайнятий. Будь ласка, оберіть інший.') validators=[DataRequired()])
likes = IntegerField('Лайки',
def validate_email(self, email): validators=[DataRequired()])
user = shares = IntegerField('Репости',
User.query.filter_by(email=email.data).first() validators=[DataRequired()])
if user: submit = SubmitField('Додати відео')
raise ValidationError('Цей email вже
зареєстрований. Будь ласка, використайте інший.') # Форма для сторіс
71
class StoryForm(FlaskForm): validators=[DataRequired(), Length(min=2, max=20)])
date = DateField('Дата', password = PasswordField('Пароль',
validators=[DataRequired()]) validators=[DataRequired(), Length(min=6)])
platform = StringField('Платформа', confirm_password =
validators=[DataRequired()]) PasswordField('Підтвердіть Пароль',
title = StringField('Заголовок (опціонально)',
validators=[Optional()]) validators=[DataRequired(), EqualTo('password',
views = IntegerField('Перегляди', message='Паролі повинні співпадати')])
validators=[DataRequired()]) submit = SubmitField('Зареєструватися')
replies = IntegerField('Відповіді',
validators=[DataRequired()]) def validate_username(self, username):
submit = SubmitField('Додати сторіс') user =
User.query.filter_by(username=username.data).first()
# Форма для підписників if user:
class SubscriberForm(FlaskForm): raise ValidationError('Цей логін вже
date = DateField('Дата', зайнятий. Будь ласка, оберіть інший.')
validators=[DataRequired()])
platform = StringField('Платформа', def validate_email(self, email):
validators=[DataRequired()]) user =
count = IntegerField('Кількість підписників', User.query.filter_by(email=email.data).first()
validators=[DataRequired()]) if user:
submit = SubmitField('Додати підписників') raise ValidationError('Цей email вже
зареєстрований. Будь ласка, використайте інший.')
models.py
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, # Форма для входу
DateField, SubmitField, SelectField, PasswordField class LoginForm(FlaskForm):
from wtforms.validators import DataRequired, username = StringField('Логін',
Optional, Length, Email, EqualTo, ValidationError validators=[DataRequired()])
from .models import User # Імпортуємо User password = PasswordField('Пароль',
для перевірки унікальності validators=[DataRequired()])
submit = SubmitField('Увійти')
# Форма для реєстрації
class RegistrationForm(FlaskForm): # Форма для постів
first_name = StringField('Ім\'я', class PostForm(FlaskForm):
validators=[DataRequired(), Length(min=2, max=50)]) date = DateField('Дата',
last_name = StringField('Прізвище', validators=[DataRequired()])
validators=[DataRequired(), Length(min=2, max=50)]) platform = StringField('Платформа',
email = StringField('Email', validators=[DataRequired()])
validators=[DataRequired(), Email()]) title = StringField('Заголовок',
username = StringField('Логін', validators=[DataRequired()])
72
likes = IntegerField('Лайки', count = IntegerField('Кількість підписників',
validators=[DataRequired()]) validators=[DataRequired()])
shares = IntegerField('Репости', submit = SubmitField('Додати підписників')
validators=[DataRequired()])
submit = SubmitField('Додати пост') models.py
from . import db
# Форма для відео from flask_login import UserMixin
class VideoForm(FlaskForm): from . import login_manager
date = DateField('Дата',
validators=[DataRequired()]) @login_manager.user_loader
platform = StringField('Платформа', def load_user(user_id):
validators=[DataRequired()]) return User.query.get(int(user_id))
title = StringField('Заголовок',
validators=[DataRequired()]) class User(db.Model, UserMixin):
views = IntegerField('Перегляди', id = db.Column(db.Integer, primary_key=True)
validators=[DataRequired()]) first_name = db.Column(db.String(50))
likes = IntegerField('Лайки', last_name = db.Column(db.String(50))
validators=[DataRequired()]) email = db.Column(db.String(120),
shares = IntegerField('Репости', unique=True)
validators=[DataRequired()]) username = db.Column(db.String(50),
submit = SubmitField('Додати відео') unique=True)
password = db.Column(db.String(100))
# Форма для сторіс is_admin = db.Column(db.Boolean,
class StoryForm(FlaskForm): default=False) # Залишаємо is_admin
date = DateField('Дата',
validators=[DataRequired()]) def __repr__(self):
platform = StringField('Платформа', return f"User('{self.username}',
validators=[DataRequired()]) '{self.email}', 'Admin: {self.is_admin}')"
title = StringField('Заголовок (опціонально)',
validators=[Optional()]) class Post(db.Model):
views = IntegerField('Перегляди', id = db.Column(db.Integer, primary_key=True)
validators=[DataRequired()]) user_id = db.Column(db.Integer,
replies = IntegerField('Відповіді', db.ForeignKey('user.id'))
validators=[DataRequired()]) date = db.Column(db.String(20)) # Зберігаємо
submit = SubmitField('Додати сторіс') дату як рядок 'YYYY-MM-DD'
platform = db.Column(db.String(50))
# Форма для підписників title = db.Column(db.String(100))
class SubscriberForm(FlaskForm): likes = db.Column(db.Integer)
date = DateField('Дата', shares = db.Column(db.Integer)
validators=[DataRequired()]) user = db.relationship('User', backref='posts')
platform = StringField('Платформа',
validators=[DataRequired()]) def __repr__(self):
73
return f"Post('{self.title}', '{self.platform}', дату як рядок 'YYYY-MM-DD'
'{self.date}')" platform = db.Column(db.String(50))
count = db.Column(db.Integer)
class Video(db.Model): user = db.relationship('User',
id = db.Column(db.Integer, primary_key=True) backref='subscribers')
user_id = db.Column(db.Integer,
db.ForeignKey('user.id')) def __repr__(self):
date = db.Column(db.String(20)) # Зберігаємо return f"Subscriber('{self.platform}',
дату як рядок 'YYYY-MM-DD' '{self.date}', '{self.count}')"
platform = db.Column(db.String(50))
title = db.Column(db.String(100)) routes.py
views = db.Column(db.Integer) from flask import Blueprint, render_template,
likes = db.Column(db.Integer) request, redirect, url_for, flash, jsonify
shares = db.Column(db.Integer) from flask_login import login_user,
user = db.relationship('User', backref='videos') login_required, logout_user, \
current_user
def __repr__(self): from werkzeug.security import
return f"Video('{self.title}', '{self.platform}', generate_password_hash, check_password_hash
'{self.date}')" from datetime import datetime, date
from collections import defaultdict
class Story(db.Model):
id = db.Column(db.Integer, primary_key=True) from .models import db, User, Post, Video, Story,
user_id = db.Column(db.Integer, Subscriber
db.ForeignKey('user.id')) from .forms import RegistrationForm,
date = db.Column(db.String(20)) # Зберігаємо LoginForm, PostForm, VideoForm, StoryForm,
дату як рядок 'YYYY-MM-DD' SubscriberForm
platform = db.Column(db.String(50))
title = db.Column(db.String(100), main = Blueprint('main', __name__)
nullable=True) # Заголовок може бути відсутній
views = db.Column(db.Integer)
replies = db.Column(db.Integer) @main.route('/')
user = db.relationship('User', backref='stories') def home():
if current_user.is_authenticated:
def __repr__(self): return redirect(url_for('main.dashboard'))
return f"Story('{self.title or 'N/A'}', return redirect(url_for('main.login'))
'{self.platform}', '{self.date}')"
class Subscriber(db.Model): @main.route('/login', methods=['GET', 'POST'])
id = db.Column(db.Integer, primary_key=True) def login():
user_id = db.Column(db.Integer, if current_user.is_authenticated:
db.ForeignKey('user.id')) return redirect(url_for('main.dashboard'))
date = db.Column(db.String(20)) # Зберігаємо
74
form = LoginForm()
if form.validate_on_submit():
user = @main.route('/dashboard')
User.query.filter_by(username=form.username.data).first() def dashboard():
if user and def get_all_data(model):
check_password_hash(user.password, return model.query.all()
form.password.data):
login_user(user) posts = get_all_data(Post)
return redirect(url_for('main.dashboard')) videos = get_all_data(Video)
else: stories = get_all_data(Story)
flash('Невірний логін або пароль.', subscribers = get_all_data(Subscriber)
'danger')
return render_template('login.html', # Агрегація даних для графіків
form=form) posts_by_date = defaultdict(int)
videos_by_date = defaultdict(int)
stories_by_date = defaultdict(int)
@main.route('/register', methods=['GET', subscribers_by_date = defaultdict(int)
'POST'])
def register(): posts_by_platform = defaultdict(int)
if current_user.is_authenticated: videos_by_platform = defaultdict(int)
return redirect(url_for('main.dashboard')) stories_by_platform = defaultdict(int)
subscribers_by_platform = defaultdict(int)
form = RegistrationForm()
if form.validate_on_submit(): # Нові агрегатори для детальної динаміки
hashed_password = post_likes_by_date = defaultdict(int)
generate_password_hash(form.password.data) post_shares_by_date = defaultdict(int)
user = User( video_views_by_date = defaultdict(int)
first_name=form.first_name.data, video_likes_by_date = defaultdict(int)
last_name=form.last_name.data, video_shares_by_date = defaultdict(int)
email=form.email.data, story_views_by_date = defaultdict(int)
username=form.username.data, story_replies_by_date = defaultdict(int)
password=hashed_password,
is_admin=(form.username.data == for post in posts:
'admin') posts_by_date[post.date] += 1
) posts_by_platform[post.platform] += 1
db.session.add(user) post_likes_by_date[post.date] += post.likes
db.session.commit() post_shares_by_date[post.date] +=
flash('Ваш акаунт створено! Тепер ви post.shares
можете увійти.', 'success')
return redirect(url_for('main.login')) for video in videos:
return render_template('register.html', videos_by_date[video.date] += 1
form=form) videos_by_platform[video.platform] += 1
75
video_views_by_date[video.date] += Skipping.")
video.views
video_likes_by_date[video.date] += all_dates_dt.sort()
video.likes all_dates_sorted =
video_shares_by_date[video.date] += [d.strftime(query_date_format) for d in all_dates_dt]
video.shares
chart_data = {
for story in stories: 'posts': {
stories_by_date[story.date] += 1 'labels': all_dates_sorted,
stories_by_platform[story.platform] += 1 'datasets': [{'label': 'Кількість постів',
story_views_by_date[story.date] += 'backgroundColor': 'rgba(75, 192, 192, 0.5)',
story.views 'borderColor': 'rgba(75, 192, 192,
story_replies_by_date[story.date] += 1)', 'borderWidth': 1,
story.replies 'data': [posts_by_date.get(d, 0) for
d in all_dates_sorted]}]
for sub in subscribers: },
subscribers_by_date[sub.date] += sub.count 'videos': {
subscribers_by_platform[sub.platform] += 'labels': all_dates_sorted,
sub.count 'datasets': [{'label': 'Кількість відео',
'backgroundColor': 'rgba(153, 102, 255, 0.5)',
all_date_keys = set(posts_by_date.keys()) | \ 'borderColor': 'rgba(153, 102,
set(videos_by_date.keys()) | \ 255, 1)', 'borderWidth': 1,
set(stories_by_date.keys()) | \ 'data': [videos_by_date.get(d, 0)
set(subscribers_by_date.keys()) | \ for d in all_dates_sorted]}]
set(post_likes_by_date.keys()) | \ },
set(post_shares_by_date.keys()) | \ 'stories': {
set(video_views_by_date.keys()) | \ 'labels': all_dates_sorted,
set(video_likes_by_date.keys()) | \ 'datasets': [{'label': 'Кількість сторіс',
set(video_shares_by_date.keys()) | \ 'backgroundColor': 'rgba(255, 159, 64, 0.5)',
set(story_views_by_date.keys()) | \ 'borderColor': 'rgba(255, 159, 64,
set(story_replies_by_date.keys()) 1)', 'borderWidth': 1,
'data': [stories_by_date.get(d, 0)
query_date_format = '%Y-%m-%d' for d in all_dates_sorted]}]
all_dates_dt = [] },
for d_str in all_date_keys: 'subscribers': {
try: 'labels': all_dates_sorted,
'datasets': [{'label': 'Кількість
all_dates_dt.append(datetime.strptime(d_str, підписників', 'backgroundColor': 'rgba(54, 162, 235, 0.5)',
query_date_format)) 'borderColor': 'rgba(54, 162, 235,
except ValueError: 1)', 'borderWidth': 1,
print(f"Warning: Could not parse date 'data': [subscribers_by_date.get(d,
string '{d_str}' with format '{query_date_format}'. 0) for d in all_dates_sorted]}]
76
}, 'backgroundColor': 'rgba(52, 152, 219, 0.5)',
# Нові дані для графіків динаміки 'borderColor': 'rgba(52, 152, 219,
'post_likes': { 1)', 'borderWidth': 1,
'labels': all_dates_sorted, 'data':
'datasets': [{'label': 'Лайки постів', [video_shares_by_date.get(d, 0) for d in
'backgroundColor': 'rgba(255, 99, 132, 0.5)', all_dates_sorted]}]
'borderColor': 'rgba(255, 99, 132, },
1)', 'borderWidth': 1, 'story_views': {
'data': [post_likes_by_date.get(d, 'labels': all_dates_sorted,
0) for d in all_dates_sorted]}] 'datasets': [{'label': 'Перегляди сторіс',
}, 'backgroundColor': 'rgba(142, 68, 173, 0.5)',
'post_shares': { 'borderColor': 'rgba(142, 68, 173,
'labels': all_dates_sorted, 1)', 'borderWidth': 1,
'datasets': [{'label': 'Репости постів', 'data':
'backgroundColor': 'rgba(46, 204, 113, 0.5)', [story_views_by_date.get(d, 0) for d in all_dates_sorted]}]
'borderColor': 'rgba(46, 204, 113, },
1)', 'borderWidth': 1, 'story_replies': {
'data': 'labels': all_dates_sorted,
[post_shares_by_date.get(d, 0) for d in all_dates_sorted]}] 'datasets': [{'label': 'Відповіді на сторіс',
}, 'backgroundColor': 'rgba(127, 140, 141, 0.5)',
'video_views': { 'borderColor': 'rgba(127, 140,
'labels': all_dates_sorted, 141, 1)', 'borderWidth': 1,
'datasets': [{'label': 'Перегляди відео', 'data':
'backgroundColor': 'rgba(241, 196, 15, 0.5)', [story_replies_by_date.get(d, 0) for d in
'borderColor': 'rgba(241, 196, 15, all_dates_sorted]}]
1)', 'borderWidth': 1, },
'data': # Дані для кругових діаграм за
[video_views_by_date.get(d, 0) for d in платформами (залишаються без змін)
all_dates_sorted]}] 'posts_by_platform': {
}, 'labels': list(posts_by_platform.keys()),
'video_likes': { 'datasets': [{'data':
'labels': all_dates_sorted, list(posts_by_platform.values()),
'datasets': [{'label': 'Лайки відео', 'backgroundColor': ['#FF6384',
'backgroundColor': 'rgba(230, 126, 34, 0.5)', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40',
'borderColor': 'rgba(230, 126, 34, '#C9CBCF']}]
1)', 'borderWidth': 1, },
'data': 'videos_by_platform': {
[video_likes_by_date.get(d, 0) for d in all_dates_sorted]}] 'labels': list(videos_by_platform.keys()),
}, 'datasets': [{'data':
'video_shares': { list(videos_by_platform.values()),
'labels': all_dates_sorted, 'backgroundColor': ['#FF6384',
'datasets': [{'label': 'Репости відео', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40',
77
'#C9CBCF']}] latest_subscribers =
}, sum(latest_subs_by_platform.values())
'stories_by_platform': {
'labels': list(stories_by_platform.keys()), general_stats = {
'datasets': [{'data': 'total_posts': total_posts_count,
list(stories_by_platform.values()), 'total_videos': total_videos_count,
'backgroundColor': ['#FF6384', 'total_stories': total_stories_count,
'#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', 'latest_subscribers': latest_subscribers
'#C9CBCF']}] }
},
'subscribers_by_platform': { return render_template('dashboard.html',
'labels': chart_data=chart_data,
list(subscribers_by_platform.keys()), general_stats=general_stats)
'datasets': [{'data':
list(subscribers_by_platform.values()),
'backgroundColor': ['#FF6384', @main.route('/statistics')
'#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', def statistics():
'#C9CBCF']}] data = []
} content_type = request.args.get('content_type')
} query = None
if content_type == 'posts':
total_posts_count = query = Post.query
sum(posts_by_date.values()) # Перейменовано для elif content_type == 'video':
ясності query = Video.query
total_videos_count = elif content_type == 'stories':
sum(videos_by_date.values()) query = Story.query
total_stories_count = elif content_type == 'subscribers':
sum(stories_by_date.values()) query = Subscriber.query
latest_subscribers = 0 if query:
if subscribers: data = query.all()
subscribers_sorted_all_time = else:
sorted(subscribers, data = []
key=lambda s:
(datetime.strptime(s.date, query_date_format), s.id), return render_template('statistics.html',
reverse=True) content_type=content_type,
latest_subs_by_platform = {} data=data,
for sub in subscribers_sorted_all_time: platform=None,
if sub.platform not in start_date=None,
latest_subs_by_platform: end_date=None)
latest_subs_by_platform[sub.platform]
= sub.count
78
@main.route('/add', methods=['GET', 'POST']) date_str =
@login_required story_form.date.data.strftime('%Y-%m-%d')
def add_data(): new_entry =
post_form = PostForm() Story(user_id=current_user.id, date=date_str,
video_form = VideoForm() platform=story_form.platform.data,
story_form = StoryForm() title=story_form.title.data if
sub_form = SubscriberForm() story_form.title.data else None,
views=story_form.views.data,
if request.method == 'POST': replies=story_form.replies.data)
submit_button_value = flash_message = 'Сторіс успішно
request.form.get('submit_button') додано!'
elif submit_button_value == 'Додати
active_form = None підписників' and sub_form.validate_on_submit():
new_entry = None active_form = sub_form
date_str =
if submit_button_value == 'Додати пост' sub_form.date.data.strftime('%Y-%m-%d')
and post_form.validate_on_submit(): new_entry =
active_form = post_form Subscriber(user_id=current_user.id, date=date_str,
date_str = platform=sub_form.platform.data,
post_form.date.data.strftime('%Y-%m-%d') count=sub_form.count.data)
new_entry = Post(user_id=current_user.id, flash_message = 'Дані про підписників
date=date_str, platform=post_form.platform.data, успішно додано!'
title=post_form.title.data,
likes=post_form.likes.data, shares=post_form.shares.data) if new_entry:
flash_message = 'Пост успішно додано!' db.session.add(new_entry)
elif submit_button_value == 'Додати відео' db.session.commit()
and video_form.validate_on_submit(): flash(flash_message, 'success')
active_form = video_form return redirect(url_for('main.add_data'))
date_str = else: # Обробка помилок валідації, якщо
video_form.date.data.strftime('%Y-%m-%d') new_entry не створено
new_entry = form_to_check_errors = None
Video(user_id=current_user.id, date=date_str, if submit_button_value == 'Додати пост':
platform=video_form.platform.data, form_to_check_errors = post_form
title=video_form.title.data, elif submit_button_value == 'Додати
views=video_form.views.data, відео':
likes=video_form.likes.data, form_to_check_errors = video_form
elif submit_button_value == 'Додати
shares=video_form.shares.data) сторіс':
flash_message = 'Відео успішно додано!' form_to_check_errors = story_form
elif submit_button_value == 'Додати сторіс' elif submit_button_value == 'Додати
and story_form.validate_on_submit(): підписників':
active_form = story_form form_to_check_errors = sub_form
79
if form_to_check_errors and not
form_to_check_errors.validate_on_submit() and
form_to_check_errors.is_submitted():
for field, errors in
form_to_check_errors.errors.items():
label_text =
form_to_check_errors[field].label.text if
hasattr(form_to_check_errors[field],
"label") else field.capitalize()
for error in errors:
flash(f'{label_text}: {error}',
'danger')
elif not
form_to_check_errors.is_submitted(): # Якщо форма
взагалі не була відправлена (теоретично неможливо
при POST)
flash('Сталася помилка при відправці
форми.', 'danger')
return render_template('add.html',
post_form=post_form, video_form=video_form,
story_form=story_form,
sub_form=sub_form)
@main.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('main.login'))
run.py
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)