добавил краткое описание и форматирование в заголовок, описание и текст статьи
parent
23e8a2e492
commit
7c6fc02529
|
@ -1,8 +1,8 @@
|
||||||
"""initial_migration
|
"""initial_migration
|
||||||
|
|
||||||
Revision ID: af05a1cbd975
|
Revision ID: 8c415e462cb3
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2023-09-19 22:57:11.510132
|
Create Date: 2023-09-22 12:19:22.923813
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import flask_security
|
import flask_security
|
||||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = "af05a1cbd975"
|
revision = "8c415e462cb3"
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
@ -20,8 +20,8 @@ def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table(
|
op.create_table(
|
||||||
"role",
|
"role",
|
||||||
sa.Column("id", sa.Integer(), nullable=False),
|
|
||||||
sa.Column("name", sa.String(length=80), nullable=False),
|
sa.Column("name", sa.String(length=80), nullable=False),
|
||||||
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column("description", sa.String(length=255), nullable=True),
|
sa.Column("description", sa.String(length=255), nullable=True),
|
||||||
sa.Column("permissions", flask_security.datastore.AsaList(), nullable=True),
|
sa.Column("permissions", flask_security.datastore.AsaList(), nullable=True),
|
||||||
sa.Column(
|
sa.Column(
|
||||||
|
@ -78,8 +78,9 @@ def upgrade():
|
||||||
"post",
|
"post",
|
||||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||||
sa.Column("author", sa.Integer(), nullable=True),
|
sa.Column("author", sa.Integer(), nullable=True),
|
||||||
sa.Column("slug", sa.String(length=30), nullable=False),
|
sa.Column("slug", sa.String(length=100), nullable=False),
|
||||||
sa.Column("title", sa.String(length=50), nullable=False),
|
sa.Column("title", sa.Text(), nullable=False),
|
||||||
|
sa.Column("description", sa.Text(), nullable=False),
|
||||||
sa.Column("published", sa.Boolean(), nullable=True),
|
sa.Column("published", sa.Boolean(), nullable=True),
|
||||||
sa.Column("create_datetime", sa.DateTime(), nullable=True),
|
sa.Column("create_datetime", sa.DateTime(), nullable=True),
|
||||||
sa.Column("update_datetime", sa.DateTime(), nullable=True),
|
sa.Column("update_datetime", sa.DateTime(), nullable=True),
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import abort, redirect, request, url_for
|
from flask import abort, g, redirect, request, url_for
|
||||||
from flask_admin.contrib import sqla
|
from flask_admin.contrib import sqla
|
||||||
from flask_ckeditor import CKEditorField
|
from flask_ckeditor import CKEditorField
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
|
@ -72,6 +72,7 @@ class PostView(MyAdminView):
|
||||||
tags="Тэги",
|
tags="Тэги",
|
||||||
slug="Слаг",
|
slug="Слаг",
|
||||||
title="Заголовок",
|
title="Заголовок",
|
||||||
|
description="Краткое описание статьи",
|
||||||
author="Автор",
|
author="Автор",
|
||||||
published="Опубликовано",
|
published="Опубликовано",
|
||||||
create_datetime="Дата создания",
|
create_datetime="Дата создания",
|
||||||
|
@ -79,6 +80,10 @@ class PostView(MyAdminView):
|
||||||
text="Текст",
|
text="Текст",
|
||||||
)
|
)
|
||||||
|
|
||||||
form_overrides = dict(text=CKEditorField)
|
form_overrides = dict(
|
||||||
|
title=CKEditorField,
|
||||||
|
description=CKEditorField,
|
||||||
|
text=CKEditorField,
|
||||||
|
)
|
||||||
create_template = "admin/edit.html"
|
create_template = "admin/edit.html"
|
||||||
edit_template = "admin/edit.html"
|
edit_template = "admin/edit.html"
|
||||||
|
|
|
@ -76,6 +76,10 @@ def create_app(test_config=None):
|
||||||
|
|
||||||
app.register_blueprint(bp_cli)
|
app.register_blueprint(bp_cli)
|
||||||
|
|
||||||
|
from pyproger.blog.blog import bp as bp_blog
|
||||||
|
|
||||||
|
app.register_blueprint(bp_blog)
|
||||||
|
|
||||||
@security.context_processor
|
@security.context_processor
|
||||||
def security_context_processor():
|
def security_context_processor():
|
||||||
return dict(
|
return dict(
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint(
|
||||||
|
"bp_blog",
|
||||||
|
__name__,
|
||||||
|
template_folder="templates/blog",
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import urls
|
|
@ -0,0 +1,43 @@
|
||||||
|
import locale
|
||||||
|
|
||||||
|
from flask import render_template, render_template_string, request
|
||||||
|
|
||||||
|
from ..dbase.database import get_paginated_posts, get_post
|
||||||
|
from .blog import bp
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_ALL, "")
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/", methods=["GET"], defaults={"page": 1})
|
||||||
|
@bp.route("/<int:page>", methods=["GET"])
|
||||||
|
def index(page=1):
|
||||||
|
per_page = 3
|
||||||
|
posts, total_pages = get_paginated_posts(page, per_page)
|
||||||
|
list_pages = [
|
||||||
|
x for x in range(1, total_pages + 1) if x >= page - 2 and x <= page + 2
|
||||||
|
]
|
||||||
|
return render_template(
|
||||||
|
"blog/index.html",
|
||||||
|
request=request,
|
||||||
|
posts=posts,
|
||||||
|
title="pyproger - разговоры про питон",
|
||||||
|
menu_title="pyproger",
|
||||||
|
page=page,
|
||||||
|
total_pages=total_pages,
|
||||||
|
list_pages=list_pages,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/post/")
|
||||||
|
@bp.route("/post/<path:slug>")
|
||||||
|
def post(slug=None):
|
||||||
|
if slug:
|
||||||
|
current_post = get_post(slug)
|
||||||
|
return render_template(
|
||||||
|
"blog/postview.html",
|
||||||
|
title=f"pyproger - {current_post.Post.title}",
|
||||||
|
menu_title="pyproger",
|
||||||
|
post=current_post,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return render_template_string("noup")
|
|
@ -0,0 +1,24 @@
|
||||||
|
from . import db
|
||||||
|
from .models import Post, User
|
||||||
|
|
||||||
|
|
||||||
|
def get_paginated_posts(page, per_page):
|
||||||
|
quer = (
|
||||||
|
db.session.query(Post, User)
|
||||||
|
.join(User, Post.author == User.id)
|
||||||
|
.order_by(Post.create_datetime.desc())
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
)
|
||||||
|
total_pages = quer.total // per_page + [0, 1][quer.total % per_page != 0]
|
||||||
|
|
||||||
|
return quer, total_pages
|
||||||
|
|
||||||
|
|
||||||
|
def get_post(slug):
|
||||||
|
post_query = (
|
||||||
|
db.session.query(Post, User)
|
||||||
|
.join(User, Post.author == User.id)
|
||||||
|
.filter(Post.slug == slug)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
return post_query
|
|
@ -1,5 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from flask_security import current_user
|
||||||
from flask_security.models import fsqla
|
from flask_security.models import fsqla
|
||||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
|
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
|
||||||
|
|
||||||
|
@ -69,8 +70,9 @@ class Post(db.Model):
|
||||||
autoincrement=True,
|
autoincrement=True,
|
||||||
)
|
)
|
||||||
author = Column(Integer, db.ForeignKey("user.id"))
|
author = Column(Integer, db.ForeignKey("user.id"))
|
||||||
slug = Column(String(30), nullable=False)
|
slug = Column(String(100), nullable=False)
|
||||||
title = Column(String(50), nullable=False)
|
title = Column(Text, nullable=False)
|
||||||
|
description = Column(Text, nullable=False)
|
||||||
published = Column(Boolean, default=False)
|
published = Column(Boolean, default=False)
|
||||||
tags = db.relationship("Tag", secondary=tag_post)
|
tags = db.relationship("Tag", secondary=tag_post)
|
||||||
|
|
||||||
|
@ -82,5 +84,6 @@ class Post(db.Model):
|
||||||
update_datetime = Column(
|
update_datetime = Column(
|
||||||
DateTime(),
|
DateTime(),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
onupdate=datetime.datetime.utcnow(),
|
||||||
)
|
)
|
||||||
text = Column(Text)
|
text = Column(Text)
|
||||||
|
|
|
@ -8,5 +8,7 @@ if you have set the configuration variables more than CKEDITOR_SERVE_LOCAL and C
|
||||||
or you need to config the CKEditor textarea, use the line below to register the configuration.
|
or you need to config the CKEditor textarea, use the line below to register the configuration.
|
||||||
The name value should be the name of the CKEditor form field.
|
The name value should be the name of the CKEditor form field.
|
||||||
#}
|
#}
|
||||||
|
{{ ckeditor.config(name='Title') }}
|
||||||
|
|
||||||
{{ ckeditor.config(name='Text') }}
|
{{ ckeditor.config(name='Text') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{page_lang}}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>
|
||||||
|
{% block title %}
|
||||||
|
{% endblock title %}
|
||||||
|
</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="main-container" class="container-fluid">
|
||||||
|
|
||||||
|
<nav class="navbar p-2 navbar-expand-lg bg-dark border-bottom border-bottom-dark rounded-bottom-4"
|
||||||
|
data-bs-theme="dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for(".index")}}">
|
||||||
|
{% block menu_title %}
|
||||||
|
|
||||||
|
{% endblock menu_title %}</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
|
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Переключатель навигации">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
text
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div id="content-container" class="container-fluid">
|
||||||
|
{% block content %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,58 @@
|
||||||
|
{% extends 'blog/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{title}}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block menu_title %}
|
||||||
|
{{ menu_title }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="conteiner">
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container px-4 px-lg-5">
|
||||||
|
<div class="list-group justify-content-center">
|
||||||
|
|
||||||
|
{% for p in posts %}
|
||||||
|
<a href="{{ url_for(".post", slug=p.Post.slug )}}" class="list-group-item list-group-item-action border-0">
|
||||||
|
<h5 class="post-title">{{ p.Post.title | safe }}</h5>
|
||||||
|
{{ p.Post.description | safe}}
|
||||||
|
</a>
|
||||||
|
<small>
|
||||||
|
Опубликовал
|
||||||
|
<a href="mailto:{{p.User.email}}">
|
||||||
|
{{ p.User.username}}
|
||||||
|
</a>
|
||||||
|
{{ p.Post.create_datetime.strftime('%d %B, %Y') }}
|
||||||
|
</small>
|
||||||
|
<hr class="my-4" />
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="container">
|
||||||
|
<ul class="pagination justify-content-start"
|
||||||
|
{% if posts.has_prev %}
|
||||||
|
<li class="page-item"><a class="page-link" href="{{ url_for(".index", page=posts.prev_num)}}"><<</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><a class="page-link" ><<</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for i in list_pages %}
|
||||||
|
{% if i == page %}
|
||||||
|
<li class="page-item active"><a class="page-link" href="{{ url_for(".index", page=i)}}">{{ i }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item"><a class="page-link" href="{{ url_for(".index", page=i)}}">{{ i }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if posts.has_next %}
|
||||||
|
<li class="page-item"><a class="page-link" href="{{ url_for(".index", page=posts.next_num)}}">>></a></li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><a class="page-link" >>></a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends 'blog/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{title}}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block menu_title %}
|
||||||
|
{{ menu_title }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Main Content-->
|
||||||
|
<div class="conteiner">
|
||||||
|
<br>
|
||||||
|
<h4>{{ post.Post.title | safe }}</h4>
|
||||||
|
{{ post.Post.text | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<a href="{{ url_for('admin.index') }}">админ панель</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue