Skip to content

Commit 1d95b5e

Browse files
committed
Переписали проект с использованием blueprints. Добавили меню и декоратор @admin_required
1 parent 89947f5 commit 1d95b5e

20 files changed

+204
-142
lines changed

create_admin.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import sys
33

44
from webapp import create_app
5-
from webapp.model import db, User
5+
from webapp.db import db
6+
from webapp.news.models import User
67

78
app = create_app()
89

webapp/__init__.py

+11-48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from flask import Flask, flash, render_template, redirect, url_for
2-
from flask_login import LoginManager, current_user, login_required, login_user, logout_user
2+
from flask_login import LoginManager, current_user, login_required
33

4-
from webapp.forms import LoginForm
5-
from webapp.model import db, News, User
6-
from webapp.weather import weather_by_city
4+
from webapp.db import db
5+
from webapp.admin.views import blueprint as admin_blueprint
6+
from webapp.news.views import blueprint as news_blueprint
7+
from webapp.user.models import User
8+
from webapp.user.views import blueprint as user_blueprint
79

810
def create_app():
911
app = Flask(__name__)
@@ -12,53 +14,14 @@ def create_app():
1214

1315
login_manager = LoginManager()
1416
login_manager.init_app(app)
15-
login_manager.login_view = 'login'
17+
login_manager.login_view = 'user.login'
18+
19+
app.register_blueprint(admin_blueprint)
20+
app.register_blueprint(news_blueprint)
21+
app.register_blueprint(user_blueprint)
1622

1723
@login_manager.user_loader
1824
def load_user(user_id):
1925
return User.query.get(user_id)
2026

21-
@app.route('/')
22-
def index():
23-
title = "Новости Python"
24-
weather = weather_by_city(app.config['WEATHER_DEFAULT_CITY'])
25-
news_list = News.query.order_by(News.published.desc()).all()
26-
return render_template('index.html', page_title=title, weather=weather, news_list=news_list)
27-
28-
@app.route('/login')
29-
def login():
30-
if current_user.is_authenticated:
31-
return redirect(url_for('index'))
32-
title = "Авторизация"
33-
login_form = LoginForm()
34-
return render_template('login.html', page_title=title, form=login_form)
35-
36-
@app.route('/process-login', methods=['POST'])
37-
def process_login():
38-
form = LoginForm()
39-
40-
if form.validate_on_submit():
41-
user = User.query.filter(User.username == form.username.data).first()
42-
if user and user.check_password(form.password.data):
43-
login_user(user)
44-
flash('Вы успешно вошли на сайт')
45-
return redirect(url_for('index'))
46-
47-
flash('Неправильные имя пользователя или пароль')
48-
return redirect(url_for('login'))
49-
50-
@app.route('/logout')
51-
def logout():
52-
logout_user()
53-
flash('Вы успешно разлогинились')
54-
return redirect(url_for('index'))
55-
56-
@app.route('/admin')
57-
@login_required
58-
def admin_index():
59-
if current_user.is_admin:
60-
return 'Привет админ!'
61-
else:
62-
return 'Ты не админ!'
63-
6427
return app

webapp/admin/__init__.py

Whitespace-only changes.

webapp/admin/views.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from flask import Blueprint, render_template
2+
3+
from webapp.user.decorators import admin_required
4+
5+
blueprint = Blueprint('admin', __name__, url_prefix='/admin')
6+
7+
@blueprint.route('/')
8+
@admin_required
9+
def admin_index():
10+
title = "Панель управления"
11+
return render_template('admin/index.html', page_title=title)

webapp/db.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from flask_sqlalchemy import SQLAlchemy
2+
3+
db = SQLAlchemy()

webapp/news/__init__.py

Whitespace-only changes.

webapp/news/models.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from webapp.db import db
2+
3+
class News(db.Model):
4+
id = db.Column(db.Integer, primary_key=True)
5+
title = db.Column(db.String, nullable=False)
6+
url = db.Column(db.String, unique=True, nullable=False)
7+
published = db.Column(db.DateTime, nullable=False)
8+
text = db.Column(db.Text, nullable=True)
9+
10+
def __repr__(self):
11+
return '<News {} {}>'.format(self.title, self.url)

webapp/news/views.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from flask import Blueprint, current_app, render_template
2+
3+
from webapp.news.models import News
4+
from webapp.weather import weather_by_city
5+
6+
blueprint = Blueprint('news', __name__)
7+
8+
@blueprint.route('/')
9+
def index():
10+
title = "Новости Python"
11+
weather = weather_by_city(current_app.config['WEATHER_DEFAULT_CITY'])
12+
news_list = News.query.order_by(News.published.desc()).all()
13+
return render_template('news/index.html', page_title=title, weather=weather, news_list=news_list)

webapp/python_org_news.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import requests
44
from bs4 import BeautifulSoup
55

6-
from webapp.model import db, News
6+
from webapp.db import db
7+
from webapp.news.models import News
78

89
def get_html(url):
910
try:

webapp/templates/admin/index.html

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}Контент админки{% endblock %}

webapp/templates/login.html renamed to webapp/templates/base.html

+3-29
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,10 @@
1111
<title>{{ page_title }}</title>
1212
</head>
1313
<body>
14+
{% include 'menu.html' %}
1415
<div class="container">
15-
<h1>{{ page_title }}</h1>
16-
<div class="row">
17-
<div class="col-4">
18-
{% with messages = get_flashed_messages() %}
19-
{% if messages %}
20-
<div class="alert alert-warning" role="alert">
21-
{% for message in messages %}
22-
{{ message }}<br>
23-
{% endfor %}
24-
</div>
25-
{% endif %}
26-
{% endwith %}
27-
<form action="{{ url_for('process_login') }}" method="POST">
28-
{{ form.hidden_tag() }}
29-
<div class="form-group">
30-
{{ form.username.label }}
31-
{{ form.username() }}
32-
</div>
33-
<div class="form-group">
34-
{{ form.password.label }}
35-
{{ form.password() }}
36-
</div>
37-
{{ form.submit() }}
38-
</form>
39-
</div>
40-
<div class="col-8">
41-
42-
</div>
43-
</div>
16+
<h1>{{ page_title }}</h1>
17+
{% block content %}{% endblock %}
4418
</div>
4519
<!-- Optional JavaScript -->
4620
<!-- jQuery first, then Popper.js, then Bootstrap JS -->

webapp/templates/index.html

-50
This file was deleted.

webapp/templates/menu.html

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<nav class="navbar navbar-expand-lg navbar-light bg-light">
2+
<a class="navbar-brand" href="#">Новости Python</a>
3+
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
4+
<span class="navbar-toggler-icon"></span>
5+
</button>
6+
7+
<div class="collapse navbar-collapse" id="navbarSupportedContent">
8+
<ul class="navbar-nav mr-auto">
9+
<li class="nav-item active">
10+
<a class="nav-link" href="/">Главная страница</a>
11+
</li>
12+
<li class="nav-item">
13+
<a class="nav-link" href="{{ url_for('user.login') }}">Логин</a>
14+
</li>
15+
</ul>
16+
<form class="form-inline my-2 my-lg-0">
17+
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
18+
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Искать</button>
19+
</form>
20+
</div>
21+
</nav>

webapp/templates/news/index.html

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}
4+
<div class="row">
5+
<div class="col-8">
6+
{% with messages = get_flashed_messages() %}
7+
{% if messages %}
8+
<div class="alert alert-warning" role="alert">
9+
{% for message in messages %}
10+
{{ message }}<br>
11+
{% endfor %}
12+
</div>
13+
{% endif %}
14+
{% endwith %}
15+
<h2>Новости</h2>
16+
{% for news in news_list %}
17+
<h3><a href="{{ news.url }}">{{ news.title }}</a></h3>
18+
<p>{{ news.published.strftime('%d.%m.%Y') }}</p>
19+
<hr />
20+
{% endfor %}
21+
</div>
22+
<div class="col-4">
23+
<h2>Прогноз погоды</h2>
24+
{% if weather %}
25+
{{ weather.temp_C }}, ощущается как {{ weather.FeelsLikeC }}
26+
{% else %}
27+
Сервис погоды временно недоступен
28+
{% endif %}
29+
</div>
30+
</div>
31+
{% endblock %}

webapp/templates/user/login.html

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}
4+
<div class="row">
5+
<div class="col-4">
6+
{% with messages = get_flashed_messages() %}
7+
{% if messages %}
8+
<div class="alert alert-warning" role="alert">
9+
{% for message in messages %}
10+
{{ message }}<br>
11+
{% endfor %}
12+
</div>
13+
{% endif %}
14+
{% endwith %}
15+
<form action="{{ url_for('user.process_login') }}" method="POST">
16+
{{ form.hidden_tag() }}
17+
<div class="form-group">
18+
{{ form.username.label }}
19+
{{ form.username() }}
20+
</div>
21+
<div class="form-group">
22+
{{ form.password.label }}
23+
{{ form.password() }}
24+
</div>
25+
<div class="form-group form-check">
26+
{{ form.remember_me() }}
27+
{{ form.remember_me.label(class_='form-check-label') }}
28+
</div>
29+
{{ form.submit() }}
30+
</form>
31+
</div>
32+
<div class="col-8">
33+
34+
</div>
35+
</div>
36+
{% endblock %}

webapp/user/__init__.py

Whitespace-only changes.

webapp/user/decorators.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from functools import wraps
2+
3+
from flask import current_app, flash, request, redirect, url_for
4+
from flask_login import config, current_user
5+
6+
def admin_required(func):
7+
@wraps(func)
8+
def decorated_view(*args, **kwargs):
9+
if request.method in config.EXEMPT_METHODS:
10+
return func(*args, **kwargs)
11+
elif current_app.config.get('LOGIN_DISABLED'):
12+
return func(*args, **kwargs)
13+
elif not current_user.is_authenticated:
14+
return current_app.login_manager.unauthorized()
15+
elif not current_user.is_admin:
16+
flash('Эта страница доступна только админам')
17+
return redirect(url_for('news.index'))
18+
return func(*args, **kwargs)
19+
return decorated_view
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from flask_wtf import FlaskForm
2-
from wtforms import StringField, PasswordField, SubmitField
2+
from wtforms import BooleanField, StringField, PasswordField, SubmitField
33
from wtforms.validators import DataRequired
44

55
class LoginForm(FlaskForm):
66
username = StringField('Имя пользователя', validators=[DataRequired()], render_kw={"class": "form-control"})
77
password = PasswordField('Пароль', validators=[DataRequired()], render_kw={"class": "form-control"})
8+
remember_me = BooleanField('Запомнить меня', default=True, render_kw={"class": "form-check-input"})
89
submit = SubmitField('Отправить!', render_kw={"class": "btn btn-primary"})

webapp/model.py renamed to webapp/user/models.py

+1-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
11
from flask_login import UserMixin
2-
from flask_sqlalchemy import SQLAlchemy
32
from werkzeug.security import generate_password_hash, check_password_hash
43

5-
db = SQLAlchemy()
6-
7-
class News(db.Model):
8-
id = db.Column(db.Integer, primary_key=True)
9-
title = db.Column(db.String, nullable=False)
10-
url = db.Column(db.String, unique=True, nullable=False)
11-
published = db.Column(db.DateTime, nullable=False)
12-
text = db.Column(db.Text, nullable=True)
13-
14-
def __repr__(self):
15-
return '<News {} {}>'.format(self.title, self.url)
4+
from webapp.db import db
165

176
class User(db.Model, UserMixin):
187
id = db.Column(db.Integer, primary_key=True)

0 commit comments

Comments
 (0)