├── .idea ├── .gitignore ├── .name ├── EXAM.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml └── modules.xml ├── instance └── portal.db ├── main.py ├── static ├── css │ └── style.css └── images │ ├── calendar3.svg │ ├── cult.jpg │ ├── daily_news.png │ ├── dorz.png │ ├── foot.jpg │ ├── gro.jpg │ ├── gromada.jpg │ ├── kmr.jpg │ ├── portal-icon.png │ ├── syd.webp │ ├── taryf.jpg │ ├── ukr.jpg │ └── zal.jpg └── templates ├── article.html ├── base.html ├── index.html ├── login.html ├── register.html └── saved_articles.html /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | main.py -------------------------------------------------------------------------------- /.idea/EXAM.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /instance/portal.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/instance/portal.db -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request, redirect, url_for, flash, jsonify 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user 4 | from flask_bcrypt import Bcrypt #розширення для хешування паролів користувачів. 5 | from datetime import datetime 6 | from sqlalchemy.exc import IntegrityError #для обробки помилок цілісності бази даних. 7 | 8 | from sqlalchemy import ForeignKey 9 | from sqlalchemy.orm import relationship 10 | 11 | 12 | 13 | app = Flask(__name__) 14 | app.config['SECRET_KEY'] = 'AF7DAEC41119A548D225221CDAAD3' 15 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///portal.db' 16 | db = SQLAlchemy(app) 17 | bcrypt = Bcrypt(app) 18 | login_manager = LoginManager(app) 19 | 20 | 21 | #Створення об'єктів моделей 22 | 23 | class Article(db.Model): 24 | __tablename__ = 'articles' 25 | id = db.Column(db.Integer, primary_key=True) 26 | title = db.Column(db.String(300), nullable=False) 27 | author = db.Column(db.String(100), nullable=False) 28 | date_published = db.Column(db.DateTime, nullable=False) 29 | tags = db.Column(db.String(100), nullable=False) 30 | content = db.Column(db.Text, nullable=False) 31 | photo = db.Column(db.String(255)) 32 | 33 | class Users(db.Model, UserMixin): 34 | __tablename__ = 'users' 35 | id = db.Column(db.Integer, primary_key=True) 36 | username = db.Column(db.String(100), unique=True, nullable=False) 37 | email = db.Column(db.String(120), unique=True, nullable=False) 38 | password = db.Column(db.String(60), nullable=False) 39 | saved_articles = db.relationship('SavedArticle', backref='user', lazy=True) 40 | comments = db.relationship('Comment', backref='author', lazy=True) 41 | 42 | class SavedArticle(db.Model): 43 | id = db.Column(db.Integer, primary_key=True) 44 | user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) 45 | article_id = db.Column(db.Integer, db.ForeignKey('articles.id'), nullable=False) 46 | 47 | article = db.relationship('Article', foreign_keys=[article_id]) 48 | 49 | 50 | class Comment(db.Model): 51 | id = db.Column(db.Integer, primary_key=True) 52 | user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) 53 | article_id = db.Column(db.Integer, nullable=False) 54 | text = db.Column(db.Text, nullable=False) 55 | 56 | #Створення таблиць баз даних 57 | with app.app_context(): 58 | db.create_all() 59 | 60 | #Завантаження користувачів 61 | @login_manager.user_loader 62 | def load_user(user_id): 63 | return Users.query.get(int(user_id)) 64 | 65 | # Головна сторінка 66 | @app.route('/', methods=['GET', 'POST']) 67 | def index(): 68 | if request.method == 'POST': 69 | keyword = request.form['keyword'] 70 | 71 | query = Article.query 72 | 73 | #Пошук статей за словом 74 | if keyword: 75 | query = query.filter(Article.title.contains(keyword) | Article.content.contains(keyword)) 76 | 77 | query = query.order_by(Article.date_published.desc()) 78 | 79 | articles = query.all() 80 | 81 | if current_user.is_authenticated: 82 | articles = articles 83 | else: 84 | articles = query.limit(3).all() 85 | else: 86 | if current_user.is_authenticated: 87 | articles = Article.query.order_by(Article.date_published.desc()).all() 88 | else: 89 | articles = Article.query.order_by(Article.date_published.desc()).limit(3).all() 90 | return render_template('index.html', articles=articles) 91 | 92 | 93 | #Провірка чи користувач уже зберігав цей пост раніше 94 | @app.route('/check_article_saved/') 95 | @login_required 96 | def check_article_saved(article_id): 97 | existing_saved_article = SavedArticle.query.filter_by(user_id=current_user.id, article_id=article_id).first() 98 | if existing_saved_article: 99 | return jsonify({'saved': True}) 100 | else: 101 | return jsonify({'saved': False}) 102 | 103 | 104 | #Регістрація 105 | @app.route('/register', methods=['GET', 'POST']) 106 | def register(): 107 | if request.method == 'POST': 108 | username = request.form['username'] 109 | email = request.form['email'] 110 | password = request.form['password'] 111 | 112 | hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') 113 | 114 | user = Users(username=username, email=email, password=hashed_password) 115 | try: 116 | db.session.add(user) 117 | db.session.commit() 118 | flash('Ваш обліковий запис був створений. Ви можете увійти!', 'success') 119 | return redirect(url_for('login')) 120 | except IntegrityError as e: 121 | db.session.rollback() #поверне базу даних до стану, яким вона була до початку цієї транзакції. 122 | flash('Ця електронна пошта вже використовується. Будь ласка, виберіть іншу.', 'error') 123 | 124 | return render_template('register.html') 125 | 126 | #Вхід 127 | @app.route('/login', methods=['GET', 'POST']) 128 | def login(): 129 | if request.method == 'POST': 130 | username = request.form['username'] 131 | password = request.form['password'] 132 | user = Users.query.filter_by(username=username).first() 133 | if user and bcrypt.check_password_hash(user.password, password): 134 | login_user(user) 135 | return redirect(url_for('index')) 136 | else: 137 | flash('Невірний логін або пароль.', 'error') 138 | return render_template('login.html') 139 | 140 | #Вихід 141 | @app.route('/logout') 142 | @login_required 143 | def logout(): 144 | logout_user() 145 | return redirect(url_for('index')) 146 | 147 | #Збереження статті в базу даних певним користувачем 148 | @app.route('/save_article_ajax/', methods=['POST']) 149 | @login_required 150 | def save_article_ajax(article_id): 151 | # Перевірка чи користувач вже зберіг цю статтю 152 | existing_saved_article = SavedArticle.query.filter_by(user_id=current_user.id, article_id=article_id).first() 153 | if existing_saved_article: 154 | return jsonify({'message': 'Ця стаття вже збережена.'}) 155 | else: 156 | #Якщо стаття не збережена, додаємо запис до таблиці SavedArticle 157 | saved_article = SavedArticle(user_id=current_user.id, article_id=article_id) 158 | db.session.add(saved_article) 159 | db.session.commit() 160 | return jsonify({'message': 'Матеріал збережено для подальшого читання.'}) 161 | 162 | #Сторінка з збереженими статтями певного користувача 163 | @app.route('/saved_articles') 164 | @login_required 165 | def saved_articles(): 166 | saved_articles = SavedArticle.query.filter_by(user_id=current_user.id).all() 167 | return render_template('saved_articles.html', saved_articles=saved_articles) 168 | 169 | #Сторінка певної статті 170 | @app.route('/article/') 171 | def article(article_id): 172 | article = Article.query.get_or_404(article_id) 173 | 174 | comments = Comment.query.filter_by(article_id=article_id).all() 175 | 176 | return render_template('article.html', article=article, comments=comments) 177 | 178 | 179 | #Залишання коментарів під матеріалами 180 | @app.route('/comment/', methods=['POST']) 181 | @login_required 182 | def comment(article_id): 183 | text = request.form['comment_text'] 184 | comment = Comment(user_id=current_user.id, article_id=article_id, text=text) 185 | db.session.add(comment) 186 | db.session.commit() 187 | 188 | return redirect(url_for('article', article_id=article_id)) 189 | 190 | 191 | # app_ctx = app.app_context() 192 | # app_ctx.push() 193 | # db.create_all() 194 | # article1 = Article(title='Калуський суддя знайшов у своєму кабінеті пристрій для прослуховування', author='Вікна Калуш', date_published=datetime(2023, 10, 4), tags='Суд Прослуховування', content='За вказаними фактами суддя Олег Мигович звернувся до Калуського районного відділу поліції Головного управління Національної поліції в Івано-Франківській області та повідомив про вчинення кримінального правопорушення, передбаченого статтею 359 Кримінального кодексу України — незаконні придбання, збут або використання спеціальних технічних засобів отримання інформації, повідомляють "Вікна" з посиланням на Вищу раду правосуддя. Матеріали за його заявою зареєстровані та направлені до Управління Служби безпеки України в Івано-Франківській області для прийняття рішення. Наразі перевірку повідомлених обставин здійснюють правоохоронні органи. На переконання судді, незаконне збирання інформації шляхом встановлення в його службовому кабінеті спеціальних технічних засобів негласного отримання інформації є втручанням у здійснення правосуддя та проявом неповаги до суду. Випадки, що мають ознаки кримінальних правопорушень, повинні бути належним чином досліджені правоохоронними органами, а винні особи — притягнуті до передбаченої законом відповідальності. З огляду на вказане, ВРП вирішила звернутися до Офісу Генерального прокурора щодо надання інформації про розкриття та розслідування злочину у кримінальному провадженні за фактами, повідомленими суддею Калуського міськрайонного суду Івано-Франківської області Олегом Миговичем.', photo='/static/images/syd.webp') 195 | # article2 = Article(title='В Калуській громаді планують встановити нові дорожні знаки', author='Вікна Калуш', date_published=datetime(2023, 10, 4), tags='Дорожні знаки', content='29 вересня в електронній системі державних закупівель Прозорро калуське комунальне підприємство "Міськсвітло" оголосило закупівлю дорожніх знаків з бюджетом понад 550 тис. гривень. Період подання пропозицій на участь у тендері завершується 7 жовтня, аукціон запланований на 9 жовтня, повідомляють "Вікна". Комунальне підприємство має намір купити 128 різних дорожніх знаків, а також 304 кріплення для знаків, бандажну стрічку та скоби до неї. У вимогах до предмету закупівлі зазначено, що дорожні знаки, які встановлюються на дорогах місцевого значення, мають відповідати вимогам держстандарту, з терміном експлуатації — 7 років. Вони мають бути виготовлені з оцинкованого металу, основа — зі сталі товщиною 0,7-1 мм, з лицевого боку дорожні знаки мають мати світловідбиваючу плівку, а тильна сторона повинна бути пофарбована полімерним покриттям сірого кольору, що не відбиває світло фар зустрічного транспорту. Усі товари повинні бути новими, належним чином упаковані та доставлені до 20 листопада поточного року. Це не перша закупівля дорожніх знаків КП "Міськсвітло" у 2023 році. Так, у травні комунальне підприємство купило 176 різних дорожніх знаків, договір було укладено на близько 382,6 тис. гривень. Таким чином загальний обсяг закупівлі дорожніх знаків у поточному році — 304 штуки. Для порівняння, як випливає зі звіту про роботу КП "Міськсвітло" у 2022 році було встановлено 48 дорожніх знаків та проведено поточний ремонт 17-ти. Як повідомив у коментарі "Вікнам" директор комунального підприємства Іван Кохан, такої кількості нових дорожніх знаків, яку планують купити і встановити у цьому році, у попередні роки не було. За його словами, рішення щодо місць встановлення нових знаків приймає координаційна рада з питань безпеки дорожнього року. Окрім того, зараз купують великі дорожні знаки — анонси, які будуть встановлені на в’їздах на територію Калуської громади, вони є дорожчими, що впливає на бюджет закупівлі. Загалом на території громади вже встановлено більше тисячі дорожніх знаків.', photo='/static/images/dorz.png') 196 | # article3 = Article(title='ФК "Гарда" розгромив брошнівські "Карпати"', author='Вікна Калуш', date_published=datetime(2023, 10, 3), tags='Футбол', content='На 6-ій хвилині матчу калушани відкрили рахунок, голом відзначився нападник Денега. Через 10 хвилин, на 16-ій хвилині матчу Яневич подвоїв перевагу "Гарди", розповів у соцмережі Ярослав Куцій, інформують "Вікна". Проте калушанам було цього недостатньо. На 39-ій хвилині матчу "Гарда" знову відзначилася у воротах номінальних господарів. Цього разу мʼяч забив нападник Дяків. Наприкінці першого тайму Цюпер реалізував пенальті, що дозволило "Карпатам" відквитати один мʼяч. За рахунком 1:3 на користь гостей команди пішли на перерву. Другий тайм розпочався активними діями калушан і на 57-ій хвилині Дяків оформив дубль у ворота номінальних господарів. Наприкінці матчу, на 88-ій хвилині Мельник відправив мʼяч у сітку "Карпат", встановивши остаточний рахунок у матчі — 1:5.', photo='/static/images/foot.jpg') 197 | # article4 = Article(title='У Калуші оприлюднили нові тарифи на опалення', author='Вікна Калуш', date_published=datetime(2023, 9, 26), tags='Тарифи Опалення', content='25 вересня на сайті Калуської міської ради комунальне підприємство «Калуська енергетична Компанія» оприлюднило повідомлення для споживачів — інформацію про намір змінити тарифи на теплову енергію, її виробництво, транспортування та постачання, послуги з постачання теплової енергії на опалювальний сезон 2023/2024 років. Нагадаємо, комунальне підприємство забезпечує тепловою енергією власних споживачів — це мешканці шести багатоквартирних житлових будинків та бюджеті установи, які опалюються від котелень «Калуської енергетичної компанії» (загальний тариф включає тарифи на виробництво, транспортування та постачання теплової енергії). А окрім того надає послугу з транспортування теплової енергії від Калуської ТЕЦ ТОВ «Костанза», який є складовою тарифу на опалення для споживачів теплоелектроцентралі, інформують "Вікна". Нові тарифи не вплинуть на населення. У повідомленні зазначено, що Законом України №2479 — 1Х від 29.07.2022року в редакції від 27.07.2023 року №3220–1Х «Про особливості регулювання відносин на ринку природного газу та у сфері теплопостачання під час дії воєнного стану та подальшого відновлення їх функціонування» протягом дії воєнного стану в Україні та шести місяців після місяця, в якому воєнний стан буде припинено або скасовано, забороняється підвищення тарифів на: послуги з розподілу природного газу для всіх категорій споживачів; теплову енергію (її виробництво, транспортування та постачання) для населення, послуги з постачання теплової енергії для населення. Чинні тарифи на теплову енергію, її виробництво, транспортування та постачання, послугу з постачання теплової енергії КП «Калуська енергетична Компанія» були встановлені рішенням виконавчого комітету Калуської міської ради від 25.10.2022 року №242. ', photo='/static/images/taryf.jpg') 198 | # article5 = Article(title='Рятувальники розшукали 75-річну калушанку, яка заблукала на "Заліссі"', author='Вікна Калуш', date_published=datetime(2023, 9, 25), tags='Розшук Залісся', content='25 вересня о 15:40 до Служби порятунку надійшло повідомлення від жительки міста Калуш про те, що вона під час збирання грибів в лісовому масиві поблизу урочище "Залісся" втратила орієнтування та заблукала, тому потребує допомоги рятувальників. Для надання допомоги було направлено рятувальників пошуково-рятувального відділення міста Івано-Франківськ, які знайшли заблукалу громадянку, 1947 року народження. Рятувальники супроводили жінку до працівників Національної поліції у задовільному стані, інформують "Вікна" з посиланням на Головне управління ДСНС України в Івано-Франківській області. Рятувальники вкотре нагадують громадянам про те, що необхідно відповідально ставитися до походів в ліс. Перед тим, як вирушити до лісу, потрібно вивчити карту місцевості, або краще вирушити з тим, хто добре знає місцевість. Також важливо подбати про повний заряд мобільного телефону.', photo='/static/images/zal.jpg') 199 | # article6 = Article(title='«Укрпошта» ввела в обіг марку із зображенням гурту Kalush Orchestra', author='Zaxid', date_published=datetime(2023, 6, 10), tags='Укр пошта Калуш', content='У Калуші погасили нову поштову марку від «Укрпошти» із зображенням Олега Псюка, лідера гурту Kalush Orchestra. Номінал марки 15 гривень, тираж складає 780 тисяч одиниць. До марки також випустили конверт «Перший день», немаркований художній конверт та художню листівку. Марку можна купити у відділеннях «Укрпошти» у всіх регіонах країни. Про урочисте погашення нової марки від «Укрпошти» у понеділок, 10 квітня, повідомив міський голова Калуша Андрій Найда. За його словами, підписання перших марок здійснив керівник «Укрпошти» Ігор Смілянський, та учасники гурту – Олег Псюк і Килим-мен. Дизайн марки належить Святославу Романчаку, художниця конвертів – Олеся Вакуленко, дизайн листівки – Юрій Сосницький. Марку можна купити у відділеннях «Укрпошти» по всій країні.', photo='/static/images/ukr.jpg') 200 | # article7 = Article(title='«Домашнє насильство НЕ приватна справа»', author='Калуська Міська Рада', date_published=datetime(2023, 10, 2), tags='Насильство', content='«Щороку через домашнє насильство в Україні помирає 600 жінок... Ми навіть не маємо повного розуміння і офіційної статистики. Адже не відомо, скільки жінок помирає від внутрішніх хвороб чи травм, що не були ідентифіковані як наслідки регулярного домашнього насильства". Таку проблематику підняла на тренінгу "Домашнє насильство НЕ приватна справа" кар\'єрна радниця проєкту "ВОНА хаб", кандидатка економічних наук Ірина Серняк. В ході тренінгового заняття працівники структурних підрозділів Калуської міської ради обговорити актуальну та гостру тему сьогодення. Експертка, зокрема, розповіла, за якими ознаками серед працівників та працівниць того чи іншого колективу розпізнати постраждалих від домашнього насильства; як домашнє насильство впливає на робочий процес та економічні показники діяльності підприємства; пояснила, чому не можна звільняти працівниць (-ків), постраждалих від домашнього та гендерно зумовленого насильства. Захід організований управлінням економічного розвитку міста Калуської міської ради та міським центром соціальних служб за фінансової підтримки уряду Канади Embassy of Canada to Ukraine. Нагадуємо, що у нашій громаді, за підтримки UNFPA, Уряду CANADA та БФ "Батерфлай" працює мобільна бригада соціально-психологічної допомоги постраждалим від домашнього насильства. Контактний номер телефону: 067 600 31 46 або за адресою: м. Калуш, вул. Героїв України, 9а.', photo='/static/images/kmr.jpg') 201 | # article8 = Article(title='У Калуші відбулися урочистості з нагоди Дня працівників освіти.', author='Калуська Міська Рада', date_published=datetime(2023, 9, 29), tags='Громада Освіта', content='Вони не просто навчають, але і доводять – освіта жива, орієнтована на здобувача, крокує у ногу із часом. У Калуській громаді функціонує 21 заклад середньої освіти, де навчається 8856 здобувачів освіти. Також працюють 14 закладів дошкільної освіти, де виховують майже 2 тисячі діток. Є три заклади позашкілля, Калуська дитяча музична школа, а також 4 заклади професійної освіти. Сьогодні представники цих закладів, працівники освітньої галузі громади запрошені до Концертної зали на урочистості з нагоди Дня працівників освіти. Цього року педагоги приймали вітання від міського голови Андрія Найди, заступниці міського голови Надії Гуш та начальниці відділу освіти Ірини Люклян. Були і традиційні нагороди й відзнаки.Відзнаки отримали цьогорічні лауреати премії ім. К. Малицької – престижної освітянської нагороди у громаді. Ними стали: вихователька ЗДО «Росинка» Оксана Воробець, учитель предмету «Захист України» Калуського ліцею №10 Богдан Когут і директорка ліцею ім. Д. Бахматюка Оксана Табачук. Срібний значок «Калуш», почесну грамоту міського голови і грошову премію отримали вчителька основ християнської етики ліцею №7 Інна Бусел та заступниця директора ліцею №2 Тамара Тисяк. 36 працівників освіти отримали Почесні грамоти міського голови. Грамотами нагороджено 67 освітян, керівництва районної державної адміністрації – 13. Ще 8 працівників освітньої галузі громади отримали грамоти Департаменту освіти і науки Івано-Франківської ОДА. Подякою Міністерства освіти і науки України нагороджена вчителька української мови і літератури ліцею ім. Дмтра Бахматюка Ольга Сербін. Дякуємо за вашу благородну справу! Усіх благ вам, дорогі освітяни!', photo='/static/images/gromada.jpg') 202 | # article9 = Article(title='КАЛУШАНКИ ВИГРАЛИ НАВЧАННЯ ДЛЯ РЕАЛІЗАЦІЇ БІЗНЕС-ІДЕЙ', author='Калуська Міська Рада', date_published=datetime(2023, 9, 26), tags='Громада', content='Авторки трьох кращих бізнес-ідей отримали приємний бонус для подальшого розвитку: 10 годин консультацій із ведення бухгалтерського обліку. Хто із якими ідеями переміг? Наталія Охтирок: ця талановита майстриня створює оригінальні ексклюзивні сумки ручної роботи. Її бізнес не лише відзначається креативністю, але й підтримує ручну роботу та індивідуальний підхід до кожного виробу. Мар\'яна Шептинська: майбутня підприємиця планує відкрити свою власну крафтову кав\'ярню під назвою «Dolce». Її відданість і пристрасть до кави обіцяють створити місце, де люди зможуть насолоджуватися смачними десертами, ароматною кавою та приємною атмосферою. Інна Сорока: переможниця планує відкрити станцію самообслуговування автомобілів «Зробіть самі!». Її ідея спрямована на забезпечення зручності та доступності автомобільного обслуговування для місцевих жителів та водіїв. Проєкт реалізується ГО «УФРА» в межах програми міжнародної співпраці EU4Business: відновлення, конкурентоспроможність та інтернаціоналізація МСП, яку спільно фінансують Європейський Союз і уряд Німеччини. Цей грантовий конкурс виконує Фонд розвитку підприємництва, а стратегічним виконавцем програми є німецька федеральна компанія Deutsche Gesellschaft für Internationale Zusammenarbeit (GIZ) GmbH. Успіхів калушанкам на шляху до реалізації мрій! У відео – більше про Demo Day «Вона може!».', photo='/static/images/gro.jpg') 203 | # article0 = Article(title='Калуські «Золоті сурми» та «Сузір’я» виступили в Франції', author='Калуська Міська Рада', date_published=datetime(2023, 9, 26), tags='Культура', content='Це вже не вперше, коли «Золоті сурми» і «Сузір’я» прославляють Україну та наше місто на міжнародному рівні! Цього разу Гімн України звучав поруч з духовими оркестрами Голландії, Індії, Бельгії, Італії, Франції! Відрадно, що у цей надскладний для нашої держави час, мистецький тил не зупиняється, продовжує розвиватися, дивувати та захоплювати своїх глядачів, доводячи всьому світові, що українці – незламна нація! Під час виступів наших колективів глядачі піднімалися зі своїх місць, вигукуючи «Україна» та запрошували виступати «на біс». Протягом чотирьох днів світ милувався калуськими талантами! Втомлені, але щасливі учасники колективів разом зі своїми наставниками повертаються додому. Щиро радіємо успіхам і дякуємо, що своєю наполегливо працею й талантом примножуєте мистецькі здобутки міста!', photo='/static/images/cult.jpg') 204 | # db.session.add(article1) 205 | # db.session.add(article2) 206 | # db.session.add(article3) 207 | # db.session.add(article4) 208 | # db.session.add(article5) 209 | # db.session.add(article6) 210 | # db.session.add(article7) 211 | # db.session.add(article8) 212 | # db.session.add(article9) 213 | # db.session.add(article0) 214 | # db.session.commit() 215 | # app_ctx.pop() 216 | 217 | if __name__ == "__main__": 218 | app.run(debug=False) -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | .filter-container__ { 2 | position: relative; 3 | 4 | } 5 | 6 | .filter-button__ { 7 | background-color: #333; 8 | color: #fff; 9 | padding: 10px 20px; 10 | border: none; 11 | cursor: pointer; 12 | } 13 | 14 | .filter-form__ { 15 | display: block; 16 | position: absolute; 17 | width: 300px; 18 | background-color: #fff; 19 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 20 | border-radius: 5px; 21 | padding: 20px; 22 | text-align: center; 23 | transform: translateX(-100%); 24 | transition: transform 0.3s ease-in-out; 25 | } 26 | 27 | .form__group__ { 28 | margin-bottom: 15px; 29 | } 30 | 31 | .form__label__ { 32 | display: block; 33 | font-weight: bold; 34 | } 35 | 36 | .form__input__ { 37 | width: 100%; 38 | padding: 10px; 39 | border: 1px solid #ccc; 40 | border-radius: 4px; 41 | 42 | } 43 | 44 | 45 | 46 | 47 | .cont { 48 | display: flex; 49 | justify-content: center; 50 | align-items: center; 51 | height: 100vh; 52 | } 53 | 54 | 55 | .form__ { 56 | width: 400px; 57 | padding: 32px; 58 | border-radius: 10px; 59 | box-shadow: 0 4px 16px #ccc; 60 | font-family: sans-serif; 61 | letter-spacing: 1px; 62 | } 63 | 64 | .form__input, 65 | .form__button { 66 | font-family: sans-serif; 67 | letter-spacing: 1px; 68 | font-size: 16px; 69 | } 70 | 71 | .form__title { 72 | text-align: center; 73 | margin-bottom: 32px; 74 | font-weight: normal; 75 | } 76 | 77 | .form__group { 78 | position: relative; 79 | margin-bottom: 32px; 80 | } 81 | 82 | .form__label { 83 | position: absolute; 84 | top: 0; 85 | left: 0; 86 | z-index: -1; 87 | color: #9e9e9e; 88 | transition: 0.3s; 89 | } 90 | 91 | .form__input { 92 | width: 100%; 93 | padding-bottom: 10px; 94 | border: none; 95 | border-bottom: 1px solid #e0e0e0; 96 | background-color: transparent; 97 | outline: none; 98 | transition: 0.3s; 99 | } 100 | 101 | .form__input:focus { 102 | border-bottom: 1px solid #1a73a8; 103 | } 104 | 105 | .form__button { 106 | padding: 10px 20px; 107 | border: none; 108 | border-radius: 5px; 109 | color: #fff; 110 | background-color: #0071f0; 111 | outline: none; 112 | cursor: pointer; 113 | transition: 0.3s; 114 | } 115 | 116 | .form__button:focus, 117 | .form__button:hover { 118 | background-color: rgba(0, 113, 240, 0.7); 119 | } 120 | 121 | .form__input:focus ~ .form__label, 122 | .form__input:not(:placeholder-shown) ~ .form__label { 123 | top: -18px; 124 | font-size: 12px; 125 | color: #e0e0e0; 126 | } 127 | 128 | .line__ { 129 | border-bottom: 3px solid #000000; 130 | width: 50px; 131 | margin: 5px auto 5px auto; 132 | } 133 | 134 | .container__ { 135 | width: 80%; 136 | margin: 40px auto auto auto; 137 | display: flex; 138 | flex-wrap: wrap; 139 | justify-content: space-between; 140 | gap: 30px; 141 | } 142 | 143 | .container__article { 144 | width: 80%; 145 | margin: 40px auto auto auto; 146 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -15px rgba(0, 0, 0, 0.12); 147 | border-radius: 10px; 148 | padding: 10px; 149 | } 150 | 151 | .article-box__ { 152 | display: flex; 153 | flex-direction: row; 154 | padding-bottom: 20px; 155 | } 156 | 157 | .article-box__ img { 158 | max-width: 45%; 159 | height: auto; 160 | padding-right: 20px; 161 | } 162 | 163 | .article-box__ p { 164 | font-family: 'Ubuntu Condensed', sans-serif; 165 | font-size: 18px; 166 | } 167 | 168 | .gray-text2 { 169 | color: gray; 170 | } 171 | 172 | .comments__ { 173 | margin-top: 20px; 174 | margin-left: 20px; 175 | padding: 10px; 176 | border-radius: 10px; 177 | width: 400px; 178 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -15px rgba(0, 0, 0, 0.12); 179 | } 180 | 181 | .btnbtn { 182 | margin: 10px 0; 183 | } 184 | 185 | .last_news { 186 | font-family: 'Young Serif', serif; 187 | text-align: center; 188 | margin-top: 40px; 189 | } 190 | 191 | .gray__text { 192 | color: gray; 193 | font-size: 16px; 194 | padding-left: 10px; 195 | } 196 | 197 | .img__img { 198 | width: 100%; 199 | height: 250px; 200 | } 201 | 202 | .name_title { 203 | font-family: 'Ubuntu Condensed', sans-serif; 204 | padding-top: 10px; 205 | padding-left: 10px; 206 | padding-right: 10px; 207 | font-size: 18px; 208 | flex: 0; 209 | } 210 | 211 | .learn__more { 212 | text-decoration: none; 213 | padding-left: 10px; 214 | transition: 0.3s; 215 | } 216 | 217 | .learn__more:hover { 218 | text-decoration: underline; 219 | 220 | } 221 | 222 | .bot__ { 223 | display: flex; 224 | justify-content: space-around; 225 | } 226 | 227 | .save-article-btn { 228 | border: none; 229 | border-radius: 5px; 230 | color: #fff; 231 | background-color: #0071f0; 232 | transition: 0.3s; 233 | } 234 | 235 | .save-article-btn:hover { 236 | background-color: #1a73a8; 237 | } 238 | 239 | .news-card { 240 | height: 480px; 241 | min-height: 480px; 242 | width: 340px; 243 | min-width: 340px; 244 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -15px rgba(0, 0, 0, 0.12); 245 | padding-bottom: 5px; 246 | margin-top: 50px; 247 | margin-left: auto; 248 | margin-right: auto; 249 | display: flex; 250 | flex-direction: column; 251 | } 252 | 253 | -------------------------------------------------------------------------------- /static/images/calendar3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/images/cult.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/cult.jpg -------------------------------------------------------------------------------- /static/images/daily_news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/daily_news.png -------------------------------------------------------------------------------- /static/images/dorz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/dorz.png -------------------------------------------------------------------------------- /static/images/foot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/foot.jpg -------------------------------------------------------------------------------- /static/images/gro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/gro.jpg -------------------------------------------------------------------------------- /static/images/gromada.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/gromada.jpg -------------------------------------------------------------------------------- /static/images/kmr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/kmr.jpg -------------------------------------------------------------------------------- /static/images/portal-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/portal-icon.png -------------------------------------------------------------------------------- /static/images/syd.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/syd.webp -------------------------------------------------------------------------------- /static/images/taryf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/taryf.jpg -------------------------------------------------------------------------------- /static/images/ukr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/ukr.jpg -------------------------------------------------------------------------------- /static/images/zal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sancheskaa/Python-advanced-exam/212e8bd482e0906c0549d509e022ce17bffb280d/static/images/zal.jpg -------------------------------------------------------------------------------- /templates/article.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Article 5 | {% endblock %} 6 | 7 | {% block body %} 8 |

Article

9 |

10 | 11 |
12 |

{{ article.title }}

13 | 14 |

by {{ article.author }}

15 | 16 |
17 | 18 |

{{ article.content }}

19 |
20 | 21 |

Tags: {{ article.tags }}

22 |

{{ article.date_published.strftime('%Y-%m-%d') }}

23 | 24 | {% if comments %} 25 |

Comments:

26 | {% endif %} 27 | {% for comment in comments %} 28 |

{{ comment.author.username }} : {{ comment.text }}

29 | {% endfor %} 30 | 31 | {% if current_user.is_authenticated %} 32 |
33 |
34 | 35 | 36 | 37 |
38 |
39 | {% endif %} 40 | 41 |
42 | 43 | {% endblock %} 44 | 45 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | {% block title %}{% endblock %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 37 | 38 | {% block search %}{% endblock %} 39 | 40 | 41 |
42 | {% if not current_user.is_authenticated %} 43 | 44 | 45 | {% endif %} 46 | {% if current_user.is_authenticated %} 47 | 48 | {% endif %} 49 |
50 |
51 |
52 |
53 | 54 | 55 | {% block body %}{% endblock %} 56 | 57 | 58 | 59 |
60 |
61 |
62 | 63 | 64 | 65 | © 2023 Portal News Kalush, Inc. All rights reserved. 66 |
67 | 68 | 73 |
74 |
75 | 76 | 77 | {% block js %}{% endblock %} 78 | 79 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | PortalNewsKalush 5 | {% endblock %} 6 | 7 | {% block search %} 8 | 11 | {% endblock %} 12 | 13 | {% block body %} 14 |

Last News

15 |

16 |
17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | {% for article in articles %} 37 |
38 | 39 | 40 | 41 | 42 | 43 |

{{ article.title }}

44 | 45 | 46 | 47 | 48 | 49 |
50 | Learn more 51 | {% if current_user.is_authenticated %} 52 | 53 | 56 | {% endif %} 57 |
58 | 59 |
60 | {% endfor %} 61 |
62 | 63 | 157 | 158 | {% endblock %} 159 | 160 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Login 5 | {% endblock %} 6 | 7 | {% block body %} 8 |
9 | 10 |
11 |

Login

12 | {% with messages = get_flashed_messages() %} 13 | {% if messages %} 14 |
15 |
    16 | {% for message in messages %} 17 |
  • {{ message }}
  • 18 | {% endfor %} 19 |
20 |
21 | {% endif %} 22 | {% endwith %} 23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 |
33 | 34 | 35 |

Don't have an account? Sign up

36 |
37 |
38 | 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Register 5 | {% endblock %} 6 | 7 | {% block body %} 8 |
9 | 10 |
11 |

Registration

12 | {% with messages = get_flashed_messages() %} 13 | {% if messages %} 14 |
15 |
    16 | {% for message in messages %} 17 |
  • {{ message }}
  • 18 | {% endfor %} 19 |
20 |
21 | {% endif %} 22 | {% endwith %} 23 | 24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 |

Already have an account? Login

39 |
40 |
41 | 42 | 43 | {% endblock %} 44 | 45 | -------------------------------------------------------------------------------- /templates/saved_articles.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %} 4 | Saved Articles 5 | {% endblock %} 6 | 7 | {% block body %} 8 | 9 |

Saved Articles

10 |

11 | 12 |
13 | 14 | {% for saved_article in saved_articles %} 15 |
16 | 17 | 18 | 19 | 20 | 21 |

{{ saved_article.article.title }}

22 |

{{ saved_article.article.author }}

23 |

{{ saved_article.article.tags }}

24 | 25 |

{{ saved_article.article.date_published.strftime('%Y-%m-%d') }}

26 | 27 |
28 | Learn more 29 |
30 | 31 |
32 | {% endfor %} 33 |
34 | 35 | {% endblock %} 36 | 37 | 38 | 39 | 40 | 41 | 42 | --------------------------------------------------------------------------------