~cedric/newspipe

677304ba3ecbd204fa2726e6820de84ad09bd868 — Cédric Bonhomme 1 year, 2 months ago 3591660
Added a tiny portion of black magic.
43 files changed, 311 insertions(+), 258 deletions(-)

M instance/production.py
M migrations/env.py
M migrations/versions/16f8fc3cf0cc_add_column_webpage_in_the_user_table.py
M migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py
M migrations/versions/19bdaa6208e_add_icon_column.py
M migrations/versions/1b750a389c22_remove_email_notification_column.py
M migrations/versions/2472eddbf44b_update_of_the_user_model.py
M migrations/versions/25ca960a207_mv_icons_from_feed_tbl_to_icon_tbl.py
M migrations/versions/2c5cc05216fa_adding_tag_handling_capacities.py
M migrations/versions/3f83bfe93fc_add_updated_date_column_to_article_table.py
M migrations/versions/422da2d0234_adding_filters_field.py
M migrations/versions/48f561c0ce6_add_column_entry_id.py
M migrations/versions/5553a6c05fa7_add_column_twitter_in_the_user_table.py
M migrations/versions/661199d8768a_problem_with_the_last_upgrade.py
M migrations/versions/8bf5694c0b9e_add_column_automatic_crawling_to_the_.py
M migrations/versions/957d4c5b8ac9_add_column_is_public_profile_to_the_.py
M migrations/versions/ac35c979311a_removed_activation_key_from_the_user_.py
M migrations/versions/b329a1a7366f_add_new_tables_for_the_bookmarks_and_.py
M migrations/versions/bdd38bd755cb_remove_email_attribute_from_users_.py
M migrations/versions/be2b8b6f33dd_add_column_private_to_the_feeds_table.py
M migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py
M migrations/versions/f700c4237e9d_remove_refresh_rate_column_from_the_.py
M migrations/versions/fa10b0bdd045_add_bio_column_to_user_table.py
M newspipe/bootstrap.py
M newspipe/controllers/feed.py
M newspipe/crawler/default_crawler.py
M newspipe/lib/article_utils.py
M newspipe/lib/feed_utils.py
M newspipe/lib/misc_utils.py
M newspipe/lib/utils.py
M newspipe/notifications/emails.py
M newspipe/notifications/notifications.py
M newspipe/web/__init__.py
M newspipe/web/lib/user_utils.py
M newspipe/web/views/api/v2/article.py
M newspipe/web/views/api/v2/category.py
M newspipe/web/views/api/v2/feed.py
M newspipe/web/views/feed.py
M newspipe/web/views/home.py
M newspipe/web/views/session_mgmt.py
M newspipe/web/views/user.py
M newspipe/web/views/views.py
M runserver.py
M instance/production.py => instance/production.py +18 -16
@@ 1,40 1,42 @@
# webserver
HOST = '127.0.0.1'
HOST = "127.0.0.1"
PORT = 5000
DEBUG = False
TESTING = False
API_ROOT = "/api/v2.0"

SECRET_KEY = 'LCx3BchmHRxFzkEv4BqQJyeXRLXenf'
SECURITY_PASSWORD_SALT = 'L8gTsyrpRQEF8jNWQPyvRfv7U5kJkD'
SECRET_KEY = "LCx3BchmHRxFzkEv4BqQJyeXRLXenf"
SECURITY_PASSWORD_SALT = "L8gTsyrpRQEF8jNWQPyvRfv7U5kJkD"


# misc
ADMIN_EMAIL = 'admin@admin.localhost'
ADMIN_EMAIL = "admin@admin.localhost"
TOKEN_VALIDITY_PERIOD = 3600
LOG_LEVEL = 'info'
LOG_PATH = './var/newspipe.log'
LOG_LEVEL = "info"
LOG_PATH = "./var/newspipe.log"
NB_WORKER = 5
SELF_REGISTRATION = True


# database
DB_CONFIG_DICT = {
    'user': 'user',
    'password': 'password',
    'host': 'localhost',
    'port': 5432
    "user": "user",
    "password": "password",
    "host": "localhost",
    "port": 5432,
}
DATABASE_NAME = 'newspipe'
SQLALCHEMY_DATABASE_URI = 'postgres://{user}:{password}@{host}:{port}/{name}'.format(name=DATABASE_NAME, **DB_CONFIG_DICT)
DATABASE_NAME = "newspipe"
SQLALCHEMY_DATABASE_URI = "postgres://{user}:{password}@{host}:{port}/{name}".format(
    name=DATABASE_NAME, **DB_CONFIG_DICT
)
SQLALCHEMY_TRACK_MODIFICATIONS = False


# crawler
CRAWLING_METHOD = 'default'
CRAWLING_METHOD = "default"
DEFAULT_MAX_ERROR = 3
HTTP_PROXY = ''
USER_AGENT = 'Newspipe (https://git.sr.ht/~cedric/newspipe)'
HTTP_PROXY = ""
USER_AGENT = "Newspipe (https://git.sr.ht/~cedric/newspipe)"
RESOLVE_ARTICLE_URL = False
TIMEOUT = 30
RESOLV = False


@@ 42,7 44,7 @@ FEED_REFRESH_INTERVAL = 0


# notification
MAIL_SERVER = 'localhost'
MAIL_SERVER = "localhost"
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False

M migrations/env.py => migrations/env.py +13 -9
@@ 9,15 9,18 @@ config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
#fileConfig("./alembic.ini")
# fileConfig("./alembic.ini")

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata

config.set_main_option(
    "sqlalchemy.url", current_app.config.get("SQLALCHEMY_DATABASE_URI")
)
target_metadata = current_app.extensions["migrate"].db.metadata


# other values from the config, defined by the needs of env.py,


@@ 25,6 28,7 @@ target_metadata = current_app.extensions['migrate'].db.metadata
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
    """Run migrations in 'offline' mode.



@@ 43,6 47,7 @@ def run_migrations_offline():
    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online():
    """Run migrations in 'online' mode.



@@ 52,18 57,17 @@ def run_migrations_online():
    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix='sqlalchemy.',
        poolclass=pool.NullPool)
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata
        )
        context.configure(connection=connection, target_metadata=target_metadata)

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:

M migrations/versions/16f8fc3cf0cc_add_column_webpage_in_the_user_table.py => migrations/versions/16f8fc3cf0cc_add_column_webpage_in_the_user_table.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-09-21 08:00:27.160357
"""

# revision identifiers, used by Alembic.
revision = '16f8fc3cf0cc'
down_revision = '957d4c5b8ac9'
revision = "16f8fc3cf0cc"
down_revision = "957d4c5b8ac9"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('user', sa.Column('webpage',
                                        sa.String(), default=""))
    op.add_column("user", sa.Column("webpage", sa.String(), default=""))


def downgrade():
    op.drop_column('user', 'webpage')
    op.drop_column("user", "webpage")

M migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py => migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py +26 -12
@@ 7,8 7,8 @@ Create Date: 2015-03-10 14:20:53.676344
"""

# revision identifiers, used by Alembic.
revision = '17dcb75f3fe'
down_revision = 'cde34831ea'
revision = "17dcb75f3fe"
down_revision = "cde34831ea"

from datetime import datetime
import conf


@@ 18,17 18,31 @@ import sqlalchemy as sa

def upgrade():
    unix_start = datetime(1970, 1, 1)
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column('feed', 'last_modified')
        op.add_column('feed', sa.Column('last_modified', sa.String(),
                      nullable=True, default=unix_start,
                      server_default=str(unix_start)))
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column("feed", "last_modified")
        op.add_column(
            "feed",
            sa.Column(
                "last_modified",
                sa.String(),
                nullable=True,
                default=unix_start,
                server_default=str(unix_start),
            ),
        )


def downgrade():
    unix_start = datetime(1970, 1, 1)
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column('feed', 'last_modified')
        op.add_column('feed', sa.Column('last_modified', sa.DateTime(),
                      nullable=True, default=unix_start,
                      server_default=unix_start))
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column("feed", "last_modified")
        op.add_column(
            "feed",
            sa.Column(
                "last_modified",
                sa.DateTime(),
                nullable=True,
                default=unix_start,
                server_default=unix_start,
            ),
        )

M migrations/versions/19bdaa6208e_add_icon_column.py => migrations/versions/19bdaa6208e_add_icon_column.py +4 -4
@@ 7,8 7,8 @@ Create Date: 2015-07-03 12:09:58.596010
"""

# revision identifiers, used by Alembic.
revision = '19bdaa6208e'
down_revision = '422da2d0234'
revision = "19bdaa6208e"
down_revision = "422da2d0234"

from alembic import op
import sqlalchemy as sa


@@ 16,11 16,11 @@ import sqlalchemy as sa

def upgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.add_column('feed', sa.Column('icon', sa.String(), nullable=True))
    op.add_column("feed", sa.Column("icon", sa.String(), nullable=True))
    ### end Alembic commands ###


def downgrade():
    ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('feed', 'icon')
    op.drop_column("feed", "icon")
    ### end Alembic commands ###

M migrations/versions/1b750a389c22_remove_email_notification_column.py => migrations/versions/1b750a389c22_remove_email_notification_column.py +5 -6
@@ 7,8 7,8 @@ Create Date: 2015-02-25 23:01:07.253429
"""

# revision identifiers, used by Alembic.
revision = '1b750a389c22'
down_revision = '48f561c0ce6'
revision = "1b750a389c22"
down_revision = "48f561c0ce6"

import conf
from alembic import op


@@ 16,10 16,9 @@ import sqlalchemy as sa


def upgrade():
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column('feed', 'email_notification')
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column("feed", "email_notification")


def downgrade():
    op.add_column('feed', sa.Column('email_notification', sa.Boolean(),
                  default=False))
    op.add_column("feed", sa.Column("email_notification", sa.Boolean(), default=False))

M migrations/versions/2472eddbf44b_update_of_the_user_model.py => migrations/versions/2472eddbf44b_update_of_the_user_model.py +19 -18
@@ 7,8 7,8 @@ Create Date: 2016-03-01 22:35:03.659694
"""

# revision identifiers, used by Alembic.
revision = '2472eddbf44b'
down_revision = 'ac35c979311a'
revision = "2472eddbf44b"
down_revision = "ac35c979311a"
branch_labels = None
depends_on = None



@@ 17,22 17,23 @@ import sqlalchemy as sa


def upgrade():
    op.drop_column('user', 'enabled')
    op.add_column('user', sa.Column('is_active', sa.Boolean(), default=False))
    op.add_column('user', sa.Column('is_admin', sa.Boolean(), default=False))
    op.add_column('user', sa.Column('is_api', sa.Boolean(), default=False))
    op.drop_table('role')

    op.drop_column("user", "enabled")
    op.add_column("user", sa.Column("is_active", sa.Boolean(), default=False))
    op.add_column("user", sa.Column("is_admin", sa.Boolean(), default=False))
    op.add_column("user", sa.Column("is_api", sa.Boolean(), default=False))
    op.drop_table("role")


def downgrade():
    op.drop_column('user', 'is_active')
    op.drop_column('user', 'is_admin')
    op.drop_column('user', 'is_api')
    op.create_table('role',
            sa.Column('id', sa.INTEGER(), nullable=False),
            sa.Column('name', sa.VARCHAR(), nullable=True),
            sa.Column('user_id', sa.INTEGER(), nullable=True),
            sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
            sa.PrimaryKeyConstraint('id'),
            sa.UniqueConstraint('name'))
    op.drop_column("user", "is_active")
    op.drop_column("user", "is_admin")
    op.drop_column("user", "is_api")
    op.create_table(
        "role",
        sa.Column("id", sa.INTEGER(), nullable=False),
        sa.Column("name", sa.VARCHAR(), nullable=True),
        sa.Column("user_id", sa.INTEGER(), nullable=True),
        sa.ForeignKeyConstraint(["user_id"], ["user.id"],),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("name"),
    )

M migrations/versions/25ca960a207_mv_icons_from_feed_tbl_to_icon_tbl.py => migrations/versions/25ca960a207_mv_icons_from_feed_tbl_to_icon_tbl.py +18 -16
@@ 7,8 7,8 @@ Create Date: 2015-08-03 14:36:21.626411
"""

# revision identifiers, used by Alembic.
revision = '25ca960a207'
down_revision = '19bdaa6208e'
revision = "25ca960a207"
down_revision = "19bdaa6208e"

from alembic import op
import sqlalchemy as sa


@@ 17,20 17,22 @@ import conf


def upgrade():
    op.create_table('icon',
            sa.Column('url', sa.String(), nullable=False),
            sa.Column('content', sa.String(), nullable=True),
            sa.Column('mimetype', sa.String(), nullable=True),
            sa.PrimaryKeyConstraint('url'))
    op.add_column('feed', sa.Column('icon_url', sa.String(), nullable=True))
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.create_foreign_key(None, 'feed', 'icon', ['icon_url'], ['url'])
        op.drop_column('feed', 'icon')
    op.create_table(
        "icon",
        sa.Column("url", sa.String(), nullable=False),
        sa.Column("content", sa.String(), nullable=True),
        sa.Column("mimetype", sa.String(), nullable=True),
        sa.PrimaryKeyConstraint("url"),
    )
    op.add_column("feed", sa.Column("icon_url", sa.String(), nullable=True))
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.create_foreign_key(None, "feed", "icon", ["icon_url"], ["url"])
        op.drop_column("feed", "icon")


def downgrade():
    op.add_column('feed', sa.Column('icon', sa.VARCHAR(), nullable=True))
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_constraint(None, 'feed', type_='foreignkey')
        op.drop_column('feed', 'icon_url')
    op.drop_table('icon')
    op.add_column("feed", sa.Column("icon", sa.VARCHAR(), nullable=True))
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_constraint(None, "feed", type_="foreignkey")
        op.drop_column("feed", "icon_url")
    op.drop_table("icon")

M migrations/versions/2c5cc05216fa_adding_tag_handling_capacities.py => migrations/versions/2c5cc05216fa_adding_tag_handling_capacities.py +9 -9
@@ 7,8 7,8 @@ Create Date: 2016-11-08 07:41:13.923531
"""

# revision identifiers, used by Alembic.
revision = '2c5cc05216fa'
down_revision = 'be2b8b6f33dd'
revision = "2c5cc05216fa"
down_revision = "be2b8b6f33dd"
branch_labels = None
depends_on = None



@@ 17,14 17,14 @@ import sqlalchemy as sa


def upgrade():
    op.create_table('tag',
            sa.Column('text', sa.String(), nullable=False),
            sa.Column('article_id', sa.Integer(), nullable=False),
            sa.ForeignKeyConstraint(['article_id'], ['article.id'],
                                    ondelete='CASCADE'),
            sa.PrimaryKeyConstraint('text', 'article_id')
    op.create_table(
        "tag",
        sa.Column("text", sa.String(), nullable=False),
        sa.Column("article_id", sa.Integer(), nullable=False),
        sa.ForeignKeyConstraint(["article_id"], ["article.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("text", "article_id"),
    )


def downgrade():
    op.drop_table('tag')
    op.drop_table("tag")

M migrations/versions/3f83bfe93fc_add_updated_date_column_to_article_table.py => migrations/versions/3f83bfe93fc_add_updated_date_column_to_article_table.py +4 -4
@@ 7,16 7,16 @@ Create Date: 2016-02-12 21:51:40.868539
"""

# revision identifiers, used by Alembic.
revision = '3f83bfe93fc'
down_revision = '25ca960a207'
revision = "3f83bfe93fc"
down_revision = "25ca960a207"

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.add_column('article', sa.Column('updated_date', sa.DateTime(), nullable=True))
    op.add_column("article", sa.Column("updated_date", sa.DateTime(), nullable=True))


def downgrade():
    op.drop_column('article', 'updated_date')
    op.drop_column("article", "updated_date")

M migrations/versions/422da2d0234_adding_filters_field.py => migrations/versions/422da2d0234_adding_filters_field.py +4 -4
@@ 7,16 7,16 @@ Create Date: 2015-05-18 23:03:15.809549
"""

# revision identifiers, used by Alembic.
revision = '422da2d0234'
down_revision = '17dcb75f3fe'
revision = "422da2d0234"
down_revision = "17dcb75f3fe"

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.add_column('feed', sa.Column('filters', sa.PickleType(), nullable=True))
    op.add_column("feed", sa.Column("filters", sa.PickleType(), nullable=True))


def downgrade():
    op.drop_column('feed', 'filters')
    op.drop_column("feed", "filters")

M migrations/versions/48f561c0ce6_add_column_entry_id.py => migrations/versions/48f561c0ce6_add_column_entry_id.py +4 -4
@@ 7,7 7,7 @@ Create Date: 2015-02-18 21:17:19.346998
"""

# revision identifiers, used by Alembic.
revision = '48f561c0ce6'
revision = "48f561c0ce6"
down_revision = None
branch_labels = None
depends_on = None


@@ 18,9 18,9 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('article', sa.Column('entry_id', sa.String(), nullable=True))
    op.add_column("article", sa.Column("entry_id", sa.String(), nullable=True))


def downgrade():
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column('article', 'entry_id')
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column("article", "entry_id")

M migrations/versions/5553a6c05fa7_add_column_twitter_in_the_user_table.py => migrations/versions/5553a6c05fa7_add_column_twitter_in_the_user_table.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-10-06 11:02:41.356322
"""

# revision identifiers, used by Alembic.
revision = '5553a6c05fa7'
down_revision = 'f700c4237e9d'
revision = "5553a6c05fa7"
down_revision = "f700c4237e9d"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('user', sa.Column('twitter',
                                        sa.String(), default=""))
    op.add_column("user", sa.Column("twitter", sa.String(), default=""))


def downgrade():
    op.drop_column('user', 'twitter')
    op.drop_column("user", "twitter")

M migrations/versions/661199d8768a_problem_with_the_last_upgrade.py => migrations/versions/661199d8768a_problem_with_the_last_upgrade.py +4 -4
@@ 7,8 7,8 @@ Create Date: 2016-02-13 11:33:14.183576
"""

# revision identifiers, used by Alembic.
revision = '661199d8768a'
down_revision = '3f83bfe93fc'
revision = "661199d8768a"
down_revision = "3f83bfe93fc"
branch_labels = None
depends_on = None



@@ 17,8 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('article', sa.Column('updated_date', sa.DateTime(), nullable=True))
    op.add_column("article", sa.Column("updated_date", sa.DateTime(), nullable=True))


def downgrade():
    op.drop_column('article', 'updated_date')
    op.drop_column("article", "updated_date")

M migrations/versions/8bf5694c0b9e_add_column_automatic_crawling_to_the_.py => migrations/versions/8bf5694c0b9e_add_column_automatic_crawling_to_the_.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-10-06 13:47:32.784711
"""

# revision identifiers, used by Alembic.
revision = '8bf5694c0b9e'
down_revision = '5553a6c05fa7'
revision = "8bf5694c0b9e"
down_revision = "5553a6c05fa7"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('user', sa.Column('automatic_crawling',
                                        sa.Boolean(), default=True))
    op.add_column("user", sa.Column("automatic_crawling", sa.Boolean(), default=True))


def downgrade():
    op.drop_column('user', 'automatic_crawling')
    op.drop_column("user", "automatic_crawling")

M migrations/versions/957d4c5b8ac9_add_column_is_public_profile_to_the_.py => migrations/versions/957d4c5b8ac9_add_column_is_public_profile_to_the_.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-09-20 14:35:31.302555
"""

# revision identifiers, used by Alembic.
revision = '957d4c5b8ac9'
down_revision = '2472eddbf44b'
revision = "957d4c5b8ac9"
down_revision = "2472eddbf44b"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('user', sa.Column('is_public_profile',
                                        sa.Boolean(), default=False))
    op.add_column("user", sa.Column("is_public_profile", sa.Boolean(), default=False))


def downgrade():
    op.drop_column('user', 'is_public_profile')
    op.drop_column("user", "is_public_profile")

M migrations/versions/ac35c979311a_removed_activation_key_from_the_user_.py => migrations/versions/ac35c979311a_removed_activation_key_from_the_user_.py +8 -7
@@ 7,8 7,8 @@ Create Date: 2016-02-18 08:54:43.786641
"""

# revision identifiers, used by Alembic.
revision = 'ac35c979311a'
down_revision = '661199d8768a'
revision = "ac35c979311a"
down_revision = "661199d8768a"
branch_labels = None
depends_on = None



@@ 17,11 17,12 @@ import sqlalchemy as sa


def upgrade():
    op.drop_column('user', 'activation_key')
    op.add_column('user', sa.Column('enabled', sa.Boolean(), default=False))
    op.drop_column("user", "activation_key")
    op.add_column("user", sa.Column("enabled", sa.Boolean(), default=False))


def downgrade():
    op.drop_column('user', 'enabled')
    op.add_column('user', sa.Column('activation_key', sa.String(),
                                        nullable=False, default=''))
    op.drop_column("user", "enabled")
    op.add_column(
        "user", sa.Column("activation_key", sa.String(), nullable=False, default="")
    )

M migrations/versions/b329a1a7366f_add_new_tables_for_the_bookmarks_and_.py => migrations/versions/b329a1a7366f_add_new_tables_for_the_bookmarks_and_.py +39 -40
@@ 7,8 7,8 @@ Create Date: 2017-05-23 21:42:37.636307
"""

# revision identifiers, used by Alembic.
revision = 'b329a1a7366f'
down_revision = '2c5cc05216fa'
revision = "b329a1a7366f"
down_revision = "2c5cc05216fa"
branch_labels = None
depends_on = None



@@ 18,48 18,47 @@ import sqlalchemy as sa


def upgrade():
    op.drop_table('tag')
    op.create_table('article_tag',
            sa.Column('text', sa.String(), nullable=False),
            sa.Column('article_id', sa.Integer(), nullable=False),
            sa.ForeignKeyConstraint(['article_id'], ['article.id'],
                                    ondelete='CASCADE'),
            sa.PrimaryKeyConstraint('text', 'article_id')
    op.drop_table("tag")
    op.create_table(
        "article_tag",
        sa.Column("text", sa.String(), nullable=False),
        sa.Column("article_id", sa.Integer(), nullable=False),
        sa.ForeignKeyConstraint(["article_id"], ["article.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("text", "article_id"),
    )
    op.create_table('bookmark',
            sa.Column('id', sa.Integer(), nullable=False),
            sa.Column('href', sa.String(), default=""),
            sa.Column('title', sa.String(), default=""),
            sa.Column('description', sa.String(), default=""),
            sa.Column('shared', sa.Boolean(), default=False),
            sa.Column('to_read', sa.Boolean(), default=False),
            sa.Column('time', sa.DateTime(), default=datetime.utcnow),
            sa.Column('user_id', sa.Integer(), nullable=False),
            sa.ForeignKeyConstraint(['user_id'], ['user.id'],
                                    ondelete='CASCADE'),
            sa.PrimaryKeyConstraint('id')
    op.create_table(
        "bookmark",
        sa.Column("id", sa.Integer(), nullable=False),
        sa.Column("href", sa.String(), default=""),
        sa.Column("title", sa.String(), default=""),
        sa.Column("description", sa.String(), default=""),
        sa.Column("shared", sa.Boolean(), default=False),
        sa.Column("to_read", sa.Boolean(), default=False),
        sa.Column("time", sa.DateTime(), default=datetime.utcnow),
        sa.Column("user_id", sa.Integer(), nullable=False),
        sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_table('bookmark_tag',
            sa.Column('id', sa.Integer(), nullable=False),
            sa.Column('text', sa.String(), nullable=False),
            sa.Column('user_id', sa.Integer(), nullable=False),
            sa.Column('bookmark_id', sa.Integer(), nullable=False),
            sa.ForeignKeyConstraint(['bookmark_id'], ['bookmark.id'],
                                    ondelete='CASCADE'),
            sa.ForeignKeyConstraint(['user_id'], ['user.id'],
                                    ondelete='CASCADE'),
            sa.PrimaryKeyConstraint('id')
    op.create_table(
        "bookmark_tag",
        sa.Column("id", sa.Integer(), nullable=False),
        sa.Column("text", sa.String(), nullable=False),
        sa.Column("user_id", sa.Integer(), nullable=False),
        sa.Column("bookmark_id", sa.Integer(), nullable=False),
        sa.ForeignKeyConstraint(["bookmark_id"], ["bookmark.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )


def downgrade():
    op.drop_table('article_tag')
    op.drop_table('bookmark_tag')
    op.drop_table('bookmark')
    op.create_table('tag',
            sa.Column('text', sa.String(), nullable=False),
            sa.Column('article_id', sa.Integer(), nullable=False),
            sa.ForeignKeyConstraint(['article_id'], ['article.id'],
                                    ondelete='CASCADE'),
            sa.PrimaryKeyConstraint('text', 'article_id')
    op.drop_table("article_tag")
    op.drop_table("bookmark_tag")
    op.drop_table("bookmark")
    op.create_table(
        "tag",
        sa.Column("text", sa.String(), nullable=False),
        sa.Column("article_id", sa.Integer(), nullable=False),
        sa.ForeignKeyConstraint(["article_id"], ["article.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("text", "article_id"),
    )

M migrations/versions/bdd38bd755cb_remove_email_attribute_from_users_.py => migrations/versions/bdd38bd755cb_remove_email_attribute_from_users_.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2018-04-04 23:26:52.517804
"""

# revision identifiers, used by Alembic.
revision = 'bdd38bd755cb'
down_revision = 'b329a1a7366f'
revision = "bdd38bd755cb"
down_revision = "b329a1a7366f"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.drop_column('user', 'email')
    op.drop_column("user", "email")


def downgrade():
    op.add_column('user', sa.Column('email', sa.String(254), unique=True,
                                    index=True))
    op.add_column("user", sa.Column("email", sa.String(254), unique=True, index=True))

M migrations/versions/be2b8b6f33dd_add_column_private_to_the_feeds_table.py => migrations/versions/be2b8b6f33dd_add_column_private_to_the_feeds_table.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-10-24 13:28:55.964803
"""

# revision identifiers, used by Alembic.
revision = 'be2b8b6f33dd'
down_revision = 'fa10b0bdd045'
revision = "be2b8b6f33dd"
down_revision = "fa10b0bdd045"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('feed', sa.Column('private',
                                        sa.Boolean(), default=False))
    op.add_column("feed", sa.Column("private", sa.Boolean(), default=False))


def downgrade():
    op.drop_column('feed', 'private')
    op.drop_column("feed", "private")

M migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py => migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py +40 -19
@@ 7,8 7,8 @@ Create Date: 2015-03-04 22:59:44.665979
"""

# revision identifiers, used by Alembic.
revision = 'cde34831ea'
down_revision = '1b750a389c22'
revision = "cde34831ea"
down_revision = "1b750a389c22"
import conf
from datetime import datetime



@@ 19,26 19,47 @@ import sqlalchemy as sa
def upgrade():
    unix_start = datetime(1970, 1, 1)
    # commands auto generated by Alembic - please adjust! ###
    op.add_column('feed', sa.Column('error_count', sa.Integer(), nullable=True,
            default=0, server_default="0"))
    op.add_column('feed', sa.Column('last_error', sa.String(), nullable=True))
    op.add_column('feed', sa.Column('last_modified', sa.DateTime(),
            nullable=True, default=unix_start, server_default=str(unix_start)))
    op.add_column('feed', sa.Column('last_retrieved', sa.DateTime(),
            nullable=True, default=unix_start, server_default=str(unix_start)))
    op.add_column('feed', sa.Column('etag', sa.String(), nullable=True))
    op.add_column('user', sa.Column('refresh_rate', sa.Integer(),
            nullable=True, default=60))
    op.add_column(
        "feed",
        sa.Column(
            "error_count", sa.Integer(), nullable=True, default=0, server_default="0"
        ),
    )
    op.add_column("feed", sa.Column("last_error", sa.String(), nullable=True))
    op.add_column(
        "feed",
        sa.Column(
            "last_modified",
            sa.DateTime(),
            nullable=True,
            default=unix_start,
            server_default=str(unix_start),
        ),
    )
    op.add_column(
        "feed",
        sa.Column(
            "last_retrieved",
            sa.DateTime(),
            nullable=True,
            default=unix_start,
            server_default=str(unix_start),
        ),
    )
    op.add_column("feed", sa.Column("etag", sa.String(), nullable=True))
    op.add_column(
        "user", sa.Column("refresh_rate", sa.Integer(), nullable=True, default=60)
    )
    # end Alembic commands ###


def downgrade():
    # commands auto generated by Alembic - please adjust! ###
    if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column('user', 'refresh_rate')
        op.drop_column('feed', 'last_modified')
        op.drop_column('feed', 'last_error')
        op.drop_column('feed', 'error_count')
        op.drop_column('feed', 'last_retrieved')
        op.drop_column('feed', 'etag')
    if "sqlite" not in conf.SQLALCHEMY_DATABASE_URI:
        op.drop_column("user", "refresh_rate")
        op.drop_column("feed", "last_modified")
        op.drop_column("feed", "last_error")
        op.drop_column("feed", "error_count")
        op.drop_column("feed", "last_retrieved")
        op.drop_column("feed", "etag")
    # end Alembic commands ###

M migrations/versions/f700c4237e9d_remove_refresh_rate_column_from_the_.py => migrations/versions/f700c4237e9d_remove_refresh_rate_column_from_the_.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-10-05 08:47:51.384069
"""

# revision identifiers, used by Alembic.
revision = 'f700c4237e9d'
down_revision = '16f8fc3cf0cc'
revision = "f700c4237e9d"
down_revision = "16f8fc3cf0cc"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.drop_column('user', 'refresh_rate')
    op.drop_column("user", "refresh_rate")


def downgrade():
    op.add_column('user', sa.Column('refresh_rate', sa.Integer(),
                  default=60))
    op.add_column("user", sa.Column("refresh_rate", sa.Integer(), default=60))

M migrations/versions/fa10b0bdd045_add_bio_column_to_user_table.py => migrations/versions/fa10b0bdd045_add_bio_column_to_user_table.py +4 -5
@@ 7,8 7,8 @@ Create Date: 2016-10-07 10:43:04.428178
"""

# revision identifiers, used by Alembic.
revision = 'fa10b0bdd045'
down_revision = '8bf5694c0b9e'
revision = "fa10b0bdd045"
down_revision = "8bf5694c0b9e"
branch_labels = None
depends_on = None



@@ 17,9 17,8 @@ import sqlalchemy as sa


def upgrade():
    op.add_column('user', sa.Column('bio',
                                        sa.String(5000), default=""))
    op.add_column("user", sa.Column("bio", sa.String(5000), default=""))


def downgrade():
    op.drop_column('user', 'bio')
    op.drop_column("user", "bio")

M newspipe/bootstrap.py => newspipe/bootstrap.py +1 -1
@@ 62,7 62,7 @@ else:
# application.config["SERVER_NAME"] = domain
# application.config["PREFERRED_URL_SCHEME"] = scheme

set_logging(application.config['LOG_PATH'])
set_logging(application.config["LOG_PATH"])

db = SQLAlchemy(application)


M newspipe/controllers/feed.py => newspipe/controllers/feed.py +1 -1
@@ 10,7 10,7 @@ from newspipe.lib.utils import clear_string

logger = logging.getLogger(__name__)
DEFAULT_LIMIT = 5
DEFAULT_MAX_ERROR = application.config['DEFAULT_MAX_ERROR']
DEFAULT_MAX_ERROR = application.config["DEFAULT_MAX_ERROR"]


class FeedController(AbstractController):

M newspipe/crawler/default_crawler.py => newspipe/crawler/default_crawler.py +7 -3
@@ 40,7 40,11 @@ from newspipe.models import User
from newspipe.controllers import FeedController, ArticleController
from newspipe.lib.utils import newspipe_get
from newspipe.lib.feed_utils import construct_feed_from, is_parsing_ok
from newspipe.lib.article_utils import construct_article, extract_id, get_article_content
from newspipe.lib.article_utils import (
    construct_article,
    extract_id,
    get_article_content,
)

logger = logging.getLogger(__name__)



@@ 163,9 167,9 @@ async def retrieve_feed(queue, users, feed_id=None):
        if feed_id is not None:
            filters["id"] = feed_id
        filters["enabled"] = True
        filters["error_count__lt"] = application.config['DEFAULT_MAX_ERROR']
        filters["error_count__lt"] = application.config["DEFAULT_MAX_ERROR"]
        filters["last_retrieved__lt"] = datetime.now() - timedelta(
            minutes=application.config['FEED_REFRESH_INTERVAL']
            minutes=application.config["FEED_REFRESH_INTERVAL"]
        )
        feeds = FeedController().read(**filters).all()


M newspipe/lib/article_utils.py => newspipe/lib/article_utils.py +6 -1
@@ 77,7 77,12 @@ def get_article_content(entry):
async def get_article_details(entry, fetch=True):
    article_link = entry.get("link")
    article_title = html.unescape(entry.get("title", ""))
    if fetch and application.config['CRAWLER_RESOLV'] and article_link or not article_title:
    if (
        fetch
        and application.config["CRAWLER_RESOLV"]
        and article_link
        or not article_title
    ):
        try:
            # resolves URL behind proxies (like feedproxy.google.com)
            response = await newspipe_get(article_link, timeout=5)

M newspipe/lib/feed_utils.py => newspipe/lib/feed_utils.py +4 -1
@@ 39,7 39,10 @@ def escape_keys(*keys):

@escape_keys("title", "description")
def construct_feed_from(url=None, fp_parsed=None, feed=None, query_site=True):
    requests_kwargs = {"headers": {"User-Agent": application.config['CRAWLER_USER_AGENT']}, "verify": False}
    requests_kwargs = {
        "headers": {"User-Agent": application.config["CRAWLER_USER_AGENT"]},
        "verify": False,
    }
    if url is None and fp_parsed is not None:
        url = fp_parsed.get("url")
    if url is not None and fp_parsed is None:

M newspipe/lib/misc_utils.py => newspipe/lib/misc_utils.py +2 -2
@@ 101,7 101,7 @@ def fetch(id, feed_id=None):
    """
    cmd = [
        sys.executable,
        application.config['BASE_DIR'] + "/manager.py",
        application.config["BASE_DIR"] + "/manager.py",
        "fetch_asyncio",
        "--user_id=" + str(id),
    ]


@@ 154,7 154,7 @@ def load_stop_words():
    Load the stop words and return them in a list.
    """
    stop_words_lists = glob.glob(
        os.path.join(application.config['BASE_DIR'], "web/var/stop_words/*.txt")
        os.path.join(application.config["BASE_DIR"], "web/var/stop_words/*.txt")
    )
    stop_words = []


M newspipe/lib/utils.py => newspipe/lib/utils.py +2 -2
@@ 93,8 93,8 @@ async def newspipe_get(url, **kwargs):
    request_kwargs = {
        "verify": False,
        "allow_redirects": True,
        "timeout": application.config['CRAWLER_TIMEOUT'],
        "headers": {"User-Agent": application.config['CRAWLER_USER_AGENT']},
        "timeout": application.config["CRAWLER_TIMEOUT"],
        "headers": {"User-Agent": application.config["CRAWLER_USER_AGENT"]},
    }
    request_kwargs.update(kwargs)
    return requests.get(url, **request_kwargs)

M newspipe/notifications/emails.py => newspipe/notifications/emails.py +14 -6
@@ 33,8 33,11 @@ logger = logging.getLogger(__name__)
@async_maker
def send_async_email(mfrom, mto, msg):
    try:
        s = smtplib.SMTP(application.config['NOTIFICATION_HOST'])
        s.login(application.config['NOTIFICATION_USERNAME'], application.config['NOTIFICATION_PASSWORD'])
        s = smtplib.SMTP(application.config["NOTIFICATION_HOST"])
        s.login(
            application.config["NOTIFICATION_USERNAME"],
            application.config["NOTIFICATION_PASSWORD"],
        )
    except Exception:
        logger.exception("send_async_email raised:")
    else:


@@ 56,7 59,7 @@ def send_smtp(to="", bcc="", subject="", plaintext="", html=""):
    # Create message container - the correct MIME type is multipart/alternative.
    msg = MIMEMultipart("alternative")
    msg["Subject"] = subject
    msg["From"] = application.config['NOTIFICATION_EMAIL']
    msg["From"] = application.config["NOTIFICATION_EMAIL"]
    msg["To"] = to
    msg["BCC"] = bcc



@@ 71,12 74,17 @@ def send_smtp(to="", bcc="", subject="", plaintext="", html=""):
    msg.attach(part2)

    try:
        s = smtplib.SMTP(application.config['NOTIFICATION_HOST'])
        s.login(application.config['NOTIFICATION_USERNAME'], application.config['NOTIFICATION_PASSWORD'])
        s = smtplib.SMTP(application.config["NOTIFICATION_HOST"])
        s.login(
            application.config["NOTIFICATION_USERNAME"],
            application.config["NOTIFICATION_PASSWORD"],
        )
    except Exception:
        logger.exception("send_smtp raised:")
    else:
        s.sendmail(
            application.config['NOTIFICATION_EMAIL'], msg["To"] + ", " + msg["BCC"], msg.as_string()
            application.config["NOTIFICATION_EMAIL"],
            msg["To"] + ", " + msg["BCC"],
            msg.as_string(),
        )
        s.quit()

M newspipe/notifications/notifications.py => newspipe/notifications/notifications.py +4 -4
@@ 33,20 33,20 @@ def new_account_notification(user, email):
    """
    token = generate_confirmation_token(user.nickname)
    expire_time = datetime.datetime.now() + datetime.timedelta(
        seconds=application.config['TOKEN_VALIDITY_PERIOD']
        seconds=application.config["TOKEN_VALIDITY_PERIOD"]
    )

    plaintext = render_template(
        "emails/account_activation.txt",
        user=user,
        platform_url=application.config['PLATFORM_URL'],
        platform_url=application.config["PLATFORM_URL"],
        token=token,
        expire_time=expire_time,
    )

    emails.send(
        to=email,
        bcc=application.config['NOTIFICATION_EMAIL'],
        bcc=application.config["NOTIFICATION_EMAIL"],
        subject="[Newspipe] Account creation",
        plaintext=plaintext,
    )


@@ 59,7 59,7 @@ def new_password_notification(user, password):
    plaintext = render_template("emails/new_password.txt", user=user, password=password)
    emails.send(
        to=user.email,
        bcc=application.config['NOTIFICATION_EMAIL'],
        bcc=application.config["NOTIFICATION_EMAIL"],
        subject="[Newspipe] New password",
        plaintext=plaintext,
    )

M newspipe/web/__init__.py => newspipe/web/__init__.py +3 -5
@@ 12,11 12,9 @@ BASE_DIR = os.path.abspath(os.path.dirname(__file__))

__version__ = (
    os.environ.get("PKGVER")
    or subprocess.run(["git",
                       "-C",
                       BASE_DIR,
                       "describe",
                       "--tags"], stdout=subprocess.PIPE)
    or subprocess.run(
        ["git", "-C", BASE_DIR, "describe", "--tags"], stdout=subprocess.PIPE
    )
    .stdout.decode()
    .strip()
)

M newspipe/web/lib/user_utils.py => newspipe/web/lib/user_utils.py +1 -1
@@ 14,7 14,7 @@ def confirm_token(token):
        nickname = serializer.loads(
            token,
            salt=application.config["SECURITY_PASSWORD_SALT"],
            max_age=application.config['TOKEN_VALIDITY_PERIOD'],
            max_age=application.config["TOKEN_VALIDITY_PERIOD"],
        )
    except:
        return False

M newspipe/web/views/api/v2/article.py => newspipe/web/views/api/v2/article.py +1 -1
@@ 49,7 49,7 @@ class ArticlesChallenge(PyAggAbstractResource):
        return result or None, 200 if result else 204


api = Api(current_app, prefix=application.config['API_ROOT'])
api = Api(current_app, prefix=application.config["API_ROOT"])

api.add_resource(ArticleNewAPI, "/article", endpoint="article_new.json")
api.add_resource(ArticleAPI, "/article/<int:obj_id>", endpoint="article.json")

M newspipe/web/views/api/v2/category.py => newspipe/web/views/api/v2/category.py +1 -1
@@ 22,7 22,7 @@ class CategoriesAPI(PyAggResourceMulti):
    controller_cls = CategoryController


api = Api(current_app, prefix=application.config['API_ROOT'])
api = Api(current_app, prefix=application.config["API_ROOT"])
api.add_resource(CategoryNewAPI, "/category", endpoint="category_new.json")
api.add_resource(CategoryAPI, "/category/<int:obj_id>", endpoint="category.json")
api.add_resource(CategoriesAPI, "/categories", endpoint="categories.json")

M newspipe/web/views/api/v2/feed.py => newspipe/web/views/api/v2/feed.py +1 -1
@@ 39,7 39,7 @@ class FetchableFeedAPI(PyAggAbstractResource):
        return result or None, 200 if result else 204


api = Api(current_app, prefix=application.config['API_ROOT'])
api = Api(current_app, prefix=application.config["API_ROOT"])

api.add_resource(FeedNewAPI, "/feed", endpoint="feed_new.json")
api.add_resource(FeedAPI, "/feed/<int:obj_id>", endpoint="feed.json")

M newspipe/web/views/feed.py => newspipe/web/views/feed.py +3 -3
@@ 179,7 179,7 @@ def bookmarklet():
        )
    feed = feed_contr.create(**feed)
    flash(gettext("Feed was successfully created."), "success")
    if feed.enabled and application.confg['CRAWLING_METHOD'] == "default":
    if feed.enabled and application.confg["CRAWLING_METHOD"] == "default":
        misc_utils.fetch(current_user.id, feed.id)
        flash(gettext("Downloading articles for the new feed..."), "info")
    return redirect(url_for("feed.form", feed_id=feed.id))


@@ 286,7 286,7 @@ def process_form(feed_id=None):
        "success",
    )

    if application.confg['CRAWLING_METHOD'] == "default":
    if application.confg["CRAWLING_METHOD"] == "default":
        misc_utils.fetch(current_user.id, new_feed.id)
        flash(gettext("Downloading articles for the new feed..."), "info")



@@ 335,7 335,7 @@ def export():
    if not include_private:
        filter["private"] = False
    if not include_exceeded_error_count:
        filter["error_count__lt"] = application.confg['DEFAULT_MAX_ERROR']
        filter["error_count__lt"] = application.confg["DEFAULT_MAX_ERROR"]

    user = UserController(current_user.id).get(id=current_user.id)
    feeds = FeedController(current_user.id).read(**filter)

M newspipe/web/views/home.py => newspipe/web/views/home.py +1 -1
@@ 181,7 181,7 @@ def fetch(feed_id=None):
    Triggers the download of news.
    News are downloaded in a separated process.
    """
    if application.config['CRAWLING_METHOD'] == "default" and current_user.is_admin:
    if application.config["CRAWLING_METHOD"] == "default" and current_user.is_admin:
        misc_utils.fetch(current_user.id, feed_id)
        flash(gettext("Downloading articles..."), "info")
    else:

M newspipe/web/views/session_mgmt.py => newspipe/web/views/session_mgmt.py +1 -1
@@ 99,7 99,7 @@ def logout():

@current_app.route("/signup", methods=["GET", "POST"])
def signup():
    if not application.config['SELF_REGISTRATION']:
    if not application.config["SELF_REGISTRATION"]:
        flash(gettext("Self-registration is disabled."), "warning")
        return redirect(url_for("home"))
    if current_user.is_authenticated:

M newspipe/web/views/user.py => newspipe/web/views/user.py +1 -1
@@ 115,7 115,7 @@ def management():
            else:
                try:
                    nb = import_opml(current_user.nickname, data.read())
                    if application.config['CRAWLING_METHOD'] == "classic":
                    if application.config["CRAWLING_METHOD"] == "classic":
                        misc_utils.fetch(current_user.id, None)
                        flash(str(nb) + "  " + gettext("feeds imported."), "success")
                        flash(gettext("Downloading articles..."), "info")

M newspipe/web/views/views.py => newspipe/web/views/views.py +7 -7
@@ 24,7 24,7 @@ def authentication_required(error):

@current_app.errorhandler(403)
def authentication_failed(error):
    if application.conf['API_ROOT'] in request.url:
    if application.conf["API_ROOT"] in request.url:
        return error
    flash(gettext("Forbidden."), "danger")
    return redirect(url_for("login"))


@@ 70,7 70,7 @@ def popular():
    filters = {}
    filters["created_date__gt"] = not_added_before
    filters["private"] = False
    filters["error_count__lt"] = application.config['DEFAULT_MAX_ERROR']
    filters["error_count__lt"] = application.config["DEFAULT_MAX_ERROR"]
    feeds = FeedController().count_by_link(**filters)
    sorted_feeds = sorted(list(feeds.items()), key=operator.itemgetter(1), reverse=True)
    return render_template("popular.html", popular=sorted_feeds)


@@ 79,7 79,7 @@ def popular():
@current_app.route("/about", methods=["GET"])
@etag_match
def about():
    return render_template("about.html", contact=application.config['ADMIN_EMAIL'])
    return render_template("about.html", contact=application.config["ADMIN_EMAIL"])


@current_app.route("/about/more", methods=["GET"])


@@ 88,9 88,7 @@ def about_more():
    version = __version__.split("-")
    if len(version) == 1:
        newspipe_version = version[0]
        version_url = "https://git.sr.ht/~cedric/newspipe/refs/{}".format(
            version[0]
        )
        version_url = "https://git.sr.ht/~cedric/newspipe/refs/{}".format(version[0])
    else:
        newspipe_version = "{} - {}".format(version[0], version[2][1:])
        version_url = "https://git.sr.ht/~cedric/newspipe/commit/{}".format(


@@ 101,7 99,9 @@ def about_more():
        "about_more.html",
        newspipe_version=newspipe_version,
        version_url=version_url,
        registration=[application.config['SELF_REGISTRATION'] and "Open" or "Closed"][0],
        registration=[application.config["SELF_REGISTRATION"] and "Open" or "Closed"][
            0
        ],
        python_version="{}.{}.{}".format(*sys.version_info[:3]),
        nb_users=UserController().read().count(),
    )

M runserver.py => runserver.py +3 -3
@@ 62,7 62,7 @@ with application.app_context():

if __name__ == "__main__":
    application.run(
        host=application.config['HOST'],
        port=application.config['PORT'],
        debug=application.config['DEBUG']
        host=application.config["HOST"],
        port=application.config["PORT"],
        debug=application.config["DEBUG"],
    )