This commit is contained in:
Sonny Bakker 2023-07-02 10:23:16 +02:00
parent 6b2c4996d5
commit 8e7b059ad3
97 changed files with 15077 additions and 6892 deletions

2
.gitignore vendored
View file

@ -115,7 +115,7 @@ celerybeat-schedule
*.sage.py *.sage.py
# Environments # Environments
.env *.env
.venv .venv
env/ env/
venv/ venv/

View file

@ -3,7 +3,6 @@ stages:
- test - test
- lint - lint
- release - release
- deploy
variables: variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
@ -17,9 +16,8 @@ variables:
cache: cache:
key: "$CI_COMMIT_REF_SLUG" key: "$CI_COMMIT_REF_SLUG"
paths: paths:
- .venv/ - env/
- .cache/pip - .cache/pip
- .cache/poetry
- node_modules/ - node_modules/
include: include:
@ -27,4 +25,3 @@ include:
- local: '/gitlab-ci/test.yml' - local: '/gitlab-ci/test.yml'
- local: '/gitlab-ci/lint.yml' - local: '/gitlab-ci/lint.yml'
- local: '/gitlab-ci/release.yml' - local: '/gitlab-ci/release.yml'
- local: '/gitlab-ci/deploy.yml'

View file

@ -1,5 +1,14 @@
# Changelog # Changelog
## 0.4.0
- Add Makefile & use `pip-tools` to generate dependencies
- Add `pyproject.toml`
- Update dependencies
- Update docker-compose setup
- Default to `newsreader.conf.docker` settings module
- Add scroll to top/bottom buttons
## 0.3.13.8 ## 0.3.13.8
- Update dependencies - Update dependencies

45
Makefile Normal file
View file

@ -0,0 +1,45 @@
# Note: run this file from within your virtualenv!
#
#
# Build dependencies
build:
pip-compile \
--resolver=backtracking \
--output-file=requirements/base.txt \
pyproject.toml
# testing
pip-compile \
--resolver=backtracking \
--extra=testing \
--output-file=requirements/testing.txt \
requirements/base.txt \
pyproject.toml
# development
pip-compile \
--resolver=backtracking \
--extra=testing \
--extra=development \
--output-file=requirements/development.txt \
requirements/base.txt \
requirements/testing.txt \
pyproject.toml
# ci
pip-compile \
--resolver=backtracking \
--extra=testing \
--extra=ci \
--output-file=requirements/ci.txt \
requirements/base.txt \
requirements/testing.txt \
pyproject.toml
# production
pip-compile \
--resolver=backtracking \
--extra=production \
--output-file=requirements/production.txt \
requirements/base.txt \
pyproject.toml

5
bin/docker-entrypoint.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/bash
python /app/src/manage.py migrate
exec "$@"

View file

@ -0,0 +1,22 @@
upstream gunicorn {
server django:8000;
}
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/access_log;
error_log /var/log/nginx/error_log;
location /static/ {
root /app;
}
location / {
proxy_pass http://gunicorn;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
}

View file

@ -0,0 +1,34 @@
version: "3.6"
volumes:
static-files:
node-modules:
services:
celery:
build:
target: development
volumes:
- ./src/:/app/src
django:
build:
target: development
command: python /app/src/manage.py runserver 0.0.0.0:8000
ports:
- "${DJANGO_PORT:-8000}:8000"
volumes:
- ./src:/app/src
- static-files:/app/src/newsreader/static
stdin_open: true
tty: true
webpack:
build:
context: .
dockerfile: ./docker/webpack
command: npm run build:watch
volumes:
- ./src/:/app/src
- static-files:/app/src/newsreader/static
- node-modules:/app/node_modules

View file

@ -0,0 +1,19 @@
version: "3.6"
volumes:
logs:
static-files:
services:
nginx:
image: nginx:1.23
depends_on:
django:
condition: service_healthy
ports:
# Note that --env-file should be used to set these correctly
- "${NGINX_HTTP_PORT:-80}:80"
volumes:
- ./config/nginx/conf.d:/etc/nginx/conf.d
- logs:/var/log/nginx
- static-files:/app/static

View file

@ -1,62 +1,102 @@
version: "3" version: "3.6"
volumes: volumes:
logs:
media:
postgres-data: postgres-data:
static-files: static-files:
node-modules:
x-db-env: &db-env
POSTGRES_HOST:
POSTGRES_PORT:
POSTGRES_DB:
POSTGRES_USER:
POSTGRES_PASSWORD:
x-django-env: &django-env
<<: *db-env
DJANGO_SECRET_KEY:
DJANGO_SETTINGS_MODULE:
services: services:
db: db:
image: postgres
environment: environment:
POSTGRES_DB: "newsreader" <<: *db-env
POSTGRES_USER: "newsreader" image: postgres:15
POSTGRES_PASSWORD: "newsreader" healthcheck:
# Note that --env-file should be used to set these correctly
test: /usr/bin/pg_isready --username="${POSTGRES_USER}" --dbname="${POSTGRES_DB}"
interval: 5s
timeout: 10s
retries: 10
volumes: volumes:
- postgres-data:/var/lib/postgresql/data - postgres-data:/var/lib/postgresql/data
rabbitmq: rabbitmq:
image: rabbitmq:3.7 image: rabbitmq:3.12
memcached: memcached:
image: memcached:1.6 image: memcached:1.6
ports:
- "11211:11211"
entrypoint: entrypoint:
- memcached - memcached
- -m 64 - -m 64
celery: celery:
build: build:
context: . context: .
dockerfile: ./docker/django dockerfile: ./docker/django
command: celery worker -n worker1@%h -n worker2@%h --app newsreader --loglevel INFO --concurrency 2 --workdir /app/src/ --beat --scheduler django target: production
args:
<<: *django-env
environment: environment:
- DJANGO_SETTINGS_MODULE=newsreader.conf.docker <<: *django-env
command: |
celery --app newsreader
--workdir /app/src/
worker --loglevel INFO
--concurrency 2
--beat
--scheduler django
-n worker1@%h
-n worker2@%h
depends_on: depends_on:
- rabbitmq rabbitmq:
- memcached condition: service_started
memcached:
condition: service_started
db:
condition: service_healthy
django:
condition: service_healthy
volumes: volumes:
- .:/app - logs:/app/logs
django: django:
build: build:
context: . context: .
dockerfile: ./docker/django dockerfile: ./docker/django
command: python /app/src/manage.py runserver 0.0.0.0:8000 target: production
args:
<<: *django-env
environment: environment:
- DJANGO_SETTINGS_MODULE=newsreader.conf.docker <<: *django-env
ports: entrypoint: /app/bin/docker-entrypoint.sh
- "8000:8000" command: |
gunicorn --bind 0.0.0.0:8000
--workers 3
--chdir /app/src/
newsreader.wsgi:application
healthcheck:
test: /usr/bin/curl --fail http://django:8000 || exit 1
interval: 30s
timeout: 10s
retries: 10
depends_on: depends_on:
- db memcached:
- memcached condition: service_started
db:
condition: service_healthy
volumes: volumes:
- .:/app - logs:/app/logs
- static-files:/app/src/newsreader/static - media:/app/media
stdin_open: true - static-files:/app/static
tty: true
webpack:
build:
context: .
dockerfile: ./docker/webpack
command: npm run build:watch
volumes:
- .:/app
- static-files:/app/src/newsreader/static
- node-modules:/app/node_modules

View file

@ -1,10 +1,108 @@
FROM python:3.7-buster # stage 1
FROM python:3.9-bullseye as backend
RUN pip install poetry RUN apt-get update && apt-get install -y --no-install-recommends \
vim \
curl \
gettext \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
COPY poetry.lock pyproject.toml /app/ RUN mkdir /app/src
RUN mkdir /app/logs
RUN mkdir /app/media
RUN poetry config virtualenvs.create false && poetry install --no-interaction --extras sentry COPY ./requirements /app/requirements
COPY . /app/ RUN pip install -r requirements/base.txt
# stage 2
FROM node:16-bullseye AS frontend-build
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY ./*.json ./*.js ./.babelrc /app/
RUN npm ci
COPY ./src /app/src
RUN npm run build
# stage 3
FROM python:3.9-bullseye as production
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql-client \
vim \
curl \
gettext \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN mkdir /app/logs
RUN mkdir /app/media
RUN mkdir /app/bin
COPY --from=backend /usr/local/lib/python3.9 /usr/local/lib/python3.9
COPY --from=backend /usr/local/bin/celery /usr/local/bin/celery
COPY ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh
COPY --from=frontend-build /app/src/newsreader/static /app/src/newsreader/static
COPY ./src /app/src
COPY ./requirements /app/requirements
RUN pip install -r requirements/production.txt
RUN useradd -M -u 1000 newsreader
RUN chown -R newsreader:newsreader /app
USER newsreader
ARG POSTGRES_HOST
ARG POSTGRES_PORT
ARG POSTGRES_DB
ARG POSTGRES_USER
ARG POSTGRES_PASSWORD
ARG DJANGO_SECRET_KEY
ARG DJANGO_SETTINGS_MODULE
RUN python src/manage.py collectstatic --noinput
# (optional) stage 4
FROM python:3.9-bullseye as development
RUN apt-get update && apt-get install -y --no-install-recommends \
vim \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN mkdir /app/logs
RUN mkdir /app/media
RUN mkdir /app/bin
COPY ./requirements /app/requirements
COPY ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh
COPY --from=backend /usr/local/lib/python3.9 /usr/local/lib/python3.9
COPY --from=backend /usr/local/bin/celery /usr/local/bin/celery
COPY --from=backend /app/src/ /app/src/
RUN pip install -r requirements/development.txt
RUN useradd -M -u 1000 newsreader
RUN chown -R newsreader:newsreader /app
USER newsreader

View file

@ -1,9 +1,10 @@
FROM node:12 FROM node:16-bullseye
WORKDIR /app WORKDIR /app
RUN mkdir /app/src
COPY package.json package-lock.json /app/ COPY package*.json webpack.*.js .babelrc /app/
RUN npm install RUN npm install
COPY . /app/ COPY ./src /app/src

View file

@ -1,6 +1,6 @@
static: static:
stage: build stage: build
image: node:12 image: node:16-bullseye
before_script: before_script:
- npm install - npm install
script: script:

View file

@ -1,22 +0,0 @@
deploy:
stage: deploy
image: python:3.7
environment:
name: production
url: rss.fudiggity.nl
rules:
- if: $CI_COMMIT_TAG
before_script:
- pip install ansible --quiet
- git clone https://git.fudiggity.nl/ansible/newsreader.git deployment --branch master
- cd deployment
- ansible-galaxy install -r requirements.yml
- mkdir /root/.ssh && echo "$DEPLOY_HOST_KEY" > /root/.ssh/known_hosts
- echo "$DEPLOY_KEY" > deploy_key && chmod 0600 deploy_key
- echo "$VAULT_PASSWORD" > vault
script:
- >
ansible-playbook playbook.yml
--private-key deploy_key
--vault-password-file vault
--extra-vars "app_branch=$CI_COMMIT_TAG"

View file

@ -1,15 +1,12 @@
python-linting: python-linting:
stage: lint stage: lint
image: python:3.7 image: python:3.9-bullseye
before_script: before_script:
- pip install poetry --quiet - pip install -r requirements/ci.txt
- poetry config cache-dir ~/.cache/poetry
- poetry config virtualenvs.in-project true
- poetry install --no-interaction --quiet
script: script:
- poetry run isort src/ --check-only --recursive - isort src/ --check-only
- poetry run black src/ --line-length 88 --check - black src/ --line-length 88 --check
- poetry run autoflake src/ --check --recursive --remove-all-unused-imports --ignore-init-module-imports - autoflake src/ --check --recursive --remove-all-unused-imports --ignore-init-module-imports
only: only:
refs: refs:
- development - development
@ -17,7 +14,7 @@ python-linting:
javascript-linting: javascript-linting:
stage: lint stage: lint
image: node:12 image: node:16-bullseye
before_script: before_script:
- npm install - npm install
script: script:

View file

@ -2,21 +2,17 @@ python-tests:
stage: test stage: test
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
services: services:
- postgres:11 - postgres:15
- memcached:1.5.22 - memcached:1.5.22
image: python:3.7 image: python:3.9-bullseye
before_script: before_script:
- pip install poetry --quiet - pip install -r requirements/ci.txt
- poetry config cache-dir .cache/poetry
- poetry config virtualenvs.in-project true
- poetry install --no-interaction --quiet --extras sentry
script: script:
- poetry run coverage run src/manage.py test newsreader - coverage run ./src/manage.py test newsreader
- poetry run coverage report
javascript-tests: javascript-tests:
stage: test stage: test
image: node:12 image: node:16-bullseye
before_script: before_script:
- npm install - npm install
script: script:

13348
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -48,11 +48,11 @@
"jest": "^24.9.0", "jest": "^24.9.0",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^0.9.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-sass": "^4.14.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"react": "^16.14.0", "react": "^16.14.0",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"redux-mock-store": "^1.5.4", "redux-mock-store": "^1.5.4",
"sass": "^1.52.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"style-loader": "^1.3.0", "style-loader": "^1.3.0",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",

1298
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,50 @@
[tool.poetry] [project]
name = "newsreader" name = 'newsreader'
version = "0.3.13.8" version = '0.4.0.0'
description = "Webapplication for reading RSS feeds" authors = [{name = 'Sonny', email= 'sonnyba871@gmail.com'}]
authors = ["Sonny <sonnyba871@gmail.com>"] license = {text = 'GPL-3.0'}
license = "GPL-3.0" requires-python = '>=3.11'
dependencies = [
'django~=3.2',
'celery~=5.0',
'psycopg2',
[tool.poetry.dependencies] 'django-axes',
python = "^3.7" 'django-celery-beat~=2.5.0',
bleach = "^3.1.4" 'django-registration-redux~=2.7',
Django = "^3.2" 'django-rest-framework',
celery = "^4.4.2" 'drf-yasg',
beautifulsoup4 = "^4.9.0"
django-axes = "^5.3.1"
django-celery-beat = "^2.0.0"
djangorestframework = "^3.11.0"
drf-yasg = "^1.17.1"
django-registration-redux = "^2.7"
lxml = "^4.5.0"
feedparser = "^5.2.1"
python-memcached = "^1.59"
requests = "^2.23.0"
psycopg2-binary = "^2.8.5"
gunicorn = "^20.0.4"
python-dotenv = "^0.12.0"
sentry-sdk = {version = "^1.0.0", optional = true}
ftfy = "^5.8"
requests_oauthlib = "^1.3.0"
[tool.poetry.extras] 'python-memcached',
sentry = ["sentry_sdk"] 'python-dotenv~=0.12',
[tool.poetry.dev-dependencies] 'ftfy~=5.8',
factory-boy = "^2.12.0"
freezegun = "^0.3.15"
django-debug-toolbar = "^2.2"
django-extensions = "^2.2.9"
black = "19.3b0"
isort = "4.3.21"
autoflake = "1.3.1"
tblib = "1.6.0"
coverage = "^5.1"
[build-system] 'requests',
requires = ["poetry>=1.0.10"] 'requests_oauthlib',
build-backend = "poetry.masonry.api"
'feedparser',
'bleach',
'beautifulsoup4',
'lxml'
]
[project.optional-dependencies]
testing = [
'factory-boy',
'freezegun',
'black',
'isort',
'autoflake',
'tblib',
]
development = [
'pip-tools>=6.13.0',
'django-debug-toolbar',
'django-extensions',
]
ci = ['coverage>=5.3.1']
production = ['gunicorn~=20.0', 'sentry-sdk~=1.0']

143
requirements/base.txt Normal file
View file

@ -0,0 +1,143 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --output-file=requirements/base.txt --resolver=backtracking pyproject.toml
#
amqp==5.1.1
# via kombu
asgiref==3.7.2
# via django
beautifulsoup4==4.12.2
# via newsreader (pyproject.toml)
billiard==4.1.0
# via celery
bleach==6.0.0
# via newsreader (pyproject.toml)
celery==5.3.1
# via
# django-celery-beat
# newsreader (pyproject.toml)
certifi==2023.5.7
# via requests
charset-normalizer==3.1.0
# via requests
click==8.1.3
# via
# celery
# click-didyoumean
# click-plugins
# click-repl
click-didyoumean==0.3.0
# via celery
click-plugins==1.1.1
# via celery
click-repl==0.3.0
# via celery
cron-descriptor==1.4.0
# via django-celery-beat
django==3.2.19
# via
# django-axes
# django-celery-beat
# django-timezone-field
# djangorestframework
# drf-yasg
# newsreader (pyproject.toml)
django-axes==6.0.4
# via newsreader (pyproject.toml)
django-celery-beat==2.5.0
# via newsreader (pyproject.toml)
django-registration-redux==2.12
# via newsreader (pyproject.toml)
django-rest-framework==0.1.0
# via newsreader (pyproject.toml)
django-timezone-field==5.1
# via django-celery-beat
djangorestframework==3.14.0
# via
# django-rest-framework
# drf-yasg
drf-yasg==1.21.6
# via newsreader (pyproject.toml)
feedparser==6.0.10
# via newsreader (pyproject.toml)
ftfy==5.9
# via newsreader (pyproject.toml)
idna==3.4
# via requests
inflection==0.5.1
# via drf-yasg
kombu==5.3.1
# via celery
lxml==4.9.2
# via newsreader (pyproject.toml)
oauthlib==3.2.2
# via requests-oauthlib
packaging==23.1
# via drf-yasg
prompt-toolkit==3.0.38
# via click-repl
psycopg2==2.9.6
# via newsreader (pyproject.toml)
python-crontab==2.7.1
# via django-celery-beat
python-dateutil==2.8.2
# via
# celery
# python-crontab
python-dotenv==0.21.1
# via newsreader (pyproject.toml)
python-memcached==1.59
# via newsreader (pyproject.toml)
pytz==2023.3
# via
# django
# django-timezone-field
# djangorestframework
# drf-yasg
pyyaml==6.0
# via drf-yasg
requests==2.31.0
# via
# newsreader (pyproject.toml)
# requests-oauthlib
requests-oauthlib==1.3.1
# via newsreader (pyproject.toml)
sgmllib3k==1.0.0
# via feedparser
six==1.16.0
# via
# bleach
# python-dateutil
# python-memcached
soupsieve==2.4.1
# via beautifulsoup4
sqlparse==0.4.4
# via django
typing-extensions==4.6.3
# via
# asgiref
# kombu
tzdata==2023.3
# via
# celery
# django-celery-beat
uritemplate==4.1.1
# via drf-yasg
urllib3==2.0.3
# via requests
vine==5.0.0
# via
# amqp
# celery
# kombu
wcwidth==0.2.6
# via
# ftfy
# prompt-toolkit
webencodings==0.5.1
# via bleach
# The following packages are considered to be unsafe in a requirements file:
# setuptools

320
requirements/ci.txt Normal file
View file

@ -0,0 +1,320 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --extra=ci --extra=testing --output-file=requirements/ci.txt --resolver=backtracking pyproject.toml requirements/base.txt requirements/testing.txt
#
amqp==5.1.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# kombu
asgiref==3.7.2
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django
autoflake==2.2.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
beautifulsoup4==4.12.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
billiard==4.1.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
black==23.3.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
bleach==6.0.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
celery==5.3.1
# via
# -r requirements/base.txt
# django-celery-beat
# newsreader (pyproject.toml)
certifi==2023.5.7
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
charset-normalizer==3.1.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
click==8.1.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# black
# celery
# click-didyoumean
# click-plugins
# click-repl
click-didyoumean==0.3.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
click-plugins==1.1.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
click-repl==0.3.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
coverage==7.2.7
# via newsreader (pyproject.toml)
cron-descriptor==1.4.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-celery-beat
django==3.2.19
# via
# -r requirements/base.txt
# django-axes
# django-celery-beat
# django-timezone-field
# djangorestframework
# drf-yasg
# newsreader (pyproject.toml)
django-axes==6.0.4
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-celery-beat==2.5.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-registration-redux==2.12
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-rest-framework==0.1.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-timezone-field==5.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-celery-beat
djangorestframework==3.14.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-rest-framework
# drf-yasg
drf-yasg==1.21.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
factory-boy==3.2.1
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
faker==18.11.2
# via
# -r requirements/testing.txt
# factory-boy
feedparser==6.0.10
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
freezegun==1.2.2
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
ftfy==5.9
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
idna==3.4
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
inflection==0.5.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# drf-yasg
isort==5.12.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
kombu==5.3.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
lxml==4.9.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
mypy-extensions==1.0.0
# via
# -r requirements/testing.txt
# black
oauthlib==3.2.2
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests-oauthlib
packaging==23.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# black
# drf-yasg
pathspec==0.11.1
# via
# -r requirements/testing.txt
# black
platformdirs==3.8.0
# via
# -r requirements/testing.txt
# black
prompt-toolkit==3.0.38
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# click-repl
psycopg2==2.9.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pyflakes==3.0.1
# via
# -r requirements/testing.txt
# autoflake
python-crontab==2.7.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-celery-beat
python-dateutil==2.8.2
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
# faker
# freezegun
# python-crontab
python-dotenv==0.21.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
python-memcached==1.59
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pytz==2023.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django
# django-timezone-field
# djangorestframework
# drf-yasg
pyyaml==6.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# drf-yasg
requests==2.31.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
# requests-oauthlib
requests-oauthlib==1.3.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
sgmllib3k==1.0.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# feedparser
six==1.16.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# bleach
# python-dateutil
# python-memcached
soupsieve==2.4.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# beautifulsoup4
sqlparse==0.4.4
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django
tblib==2.0.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
tomli==2.0.1
# via
# -r requirements/testing.txt
# autoflake
# black
typing-extensions==4.6.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# asgiref
# black
# kombu
tzdata==2023.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
# django-celery-beat
uritemplate==4.1.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# drf-yasg
urllib3==2.0.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
vine==5.0.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# amqp
# celery
# kombu
wcwidth==0.2.6
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# ftfy
# prompt-toolkit
webencodings==0.5.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# bleach
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View file

@ -0,0 +1,338 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --extra=development --extra=testing --output-file=requirements/development.txt --resolver=backtracking pyproject.toml requirements/base.txt requirements/testing.txt
#
amqp==5.1.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# kombu
asgiref==3.7.2
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django
autoflake==2.2.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
beautifulsoup4==4.12.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
billiard==4.1.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
black==23.3.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
bleach==6.0.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
build==0.10.0
# via pip-tools
celery==5.3.1
# via
# -r requirements/base.txt
# django-celery-beat
# newsreader (pyproject.toml)
certifi==2023.5.7
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
charset-normalizer==3.1.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
click==8.1.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# black
# celery
# click-didyoumean
# click-plugins
# click-repl
# pip-tools
click-didyoumean==0.3.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
click-plugins==1.1.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
click-repl==0.3.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
cron-descriptor==1.4.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-celery-beat
django==3.2.19
# via
# -r requirements/base.txt
# django-axes
# django-celery-beat
# django-debug-toolbar
# django-extensions
# django-timezone-field
# djangorestframework
# drf-yasg
# newsreader (pyproject.toml)
django-axes==6.0.4
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-celery-beat==2.5.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-debug-toolbar==4.1.0
# via newsreader (pyproject.toml)
django-extensions==3.2.3
# via newsreader (pyproject.toml)
django-registration-redux==2.12
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-rest-framework==0.1.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-timezone-field==5.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-celery-beat
djangorestframework==3.14.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-rest-framework
# drf-yasg
drf-yasg==1.21.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
factory-boy==3.2.1
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
faker==18.11.2
# via
# -r requirements/testing.txt
# factory-boy
feedparser==6.0.10
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
freezegun==1.2.2
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
ftfy==5.9
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
idna==3.4
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
inflection==0.5.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# drf-yasg
isort==5.12.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
kombu==5.3.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
lxml==4.9.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
mypy-extensions==1.0.0
# via
# -r requirements/testing.txt
# black
oauthlib==3.2.2
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests-oauthlib
packaging==23.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# black
# build
# drf-yasg
pathspec==0.11.1
# via
# -r requirements/testing.txt
# black
pip-tools==6.13.0
# via newsreader (pyproject.toml)
platformdirs==3.8.0
# via
# -r requirements/testing.txt
# black
prompt-toolkit==3.0.38
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# click-repl
psycopg2==2.9.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pyflakes==3.0.1
# via
# -r requirements/testing.txt
# autoflake
pyproject-hooks==1.0.0
# via build
python-crontab==2.7.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django-celery-beat
python-dateutil==2.8.2
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
# faker
# freezegun
# python-crontab
python-dotenv==0.21.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
python-memcached==1.59
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pytz==2023.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django
# django-timezone-field
# djangorestframework
# drf-yasg
pyyaml==6.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# drf-yasg
requests==2.31.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
# requests-oauthlib
requests-oauthlib==1.3.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
sgmllib3k==1.0.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# feedparser
six==1.16.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# bleach
# python-dateutil
# python-memcached
soupsieve==2.4.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# beautifulsoup4
sqlparse==0.4.4
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# django
# django-debug-toolbar
tblib==2.0.0
# via
# -r requirements/testing.txt
# newsreader (pyproject.toml)
tomli==2.0.1
# via
# -r requirements/testing.txt
# autoflake
# black
# build
# pyproject-hooks
typing-extensions==4.6.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# asgiref
# black
# kombu
tzdata==2023.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# celery
# django-celery-beat
uritemplate==4.1.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# drf-yasg
urllib3==2.0.3
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# requests
vine==5.0.0
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# amqp
# celery
# kombu
wcwidth==0.2.6
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# ftfy
# prompt-toolkit
webencodings==0.5.1
# via
# -r requirements/base.txt
# -r requirements/testing.txt
# bleach
wheel==0.40.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
# pip
# setuptools

237
requirements/production.txt Normal file
View file

@ -0,0 +1,237 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --extra=production --output-file=requirements/production.txt --resolver=backtracking pyproject.toml requirements/base.txt
#
amqp==5.1.1
# via
# -r requirements/base.txt
# kombu
asgiref==3.7.2
# via
# -r requirements/base.txt
# django
beautifulsoup4==4.12.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
billiard==4.1.0
# via
# -r requirements/base.txt
# celery
bleach==6.0.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
celery==5.3.1
# via
# -r requirements/base.txt
# django-celery-beat
# newsreader (pyproject.toml)
certifi==2023.5.7
# via
# -r requirements/base.txt
# requests
# sentry-sdk
charset-normalizer==3.1.0
# via
# -r requirements/base.txt
# requests
click==8.1.3
# via
# -r requirements/base.txt
# celery
# click-didyoumean
# click-plugins
# click-repl
click-didyoumean==0.3.0
# via
# -r requirements/base.txt
# celery
click-plugins==1.1.1
# via
# -r requirements/base.txt
# celery
click-repl==0.3.0
# via
# -r requirements/base.txt
# celery
cron-descriptor==1.4.0
# via
# -r requirements/base.txt
# django-celery-beat
django==3.2.19
# via
# -r requirements/base.txt
# django-axes
# django-celery-beat
# django-timezone-field
# djangorestframework
# drf-yasg
# newsreader (pyproject.toml)
django-axes==6.0.4
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-celery-beat==2.5.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-registration-redux==2.12
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-rest-framework==0.1.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-timezone-field==5.1
# via
# -r requirements/base.txt
# django-celery-beat
djangorestframework==3.14.0
# via
# -r requirements/base.txt
# django-rest-framework
# drf-yasg
drf-yasg==1.21.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
feedparser==6.0.10
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
ftfy==5.9
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
gunicorn==20.1.0
# via newsreader (pyproject.toml)
idna==3.4
# via
# -r requirements/base.txt
# requests
inflection==0.5.1
# via
# -r requirements/base.txt
# drf-yasg
kombu==5.3.1
# via
# -r requirements/base.txt
# celery
lxml==4.9.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
oauthlib==3.2.2
# via
# -r requirements/base.txt
# requests-oauthlib
packaging==23.1
# via
# -r requirements/base.txt
# drf-yasg
prompt-toolkit==3.0.38
# via
# -r requirements/base.txt
# click-repl
psycopg2==2.9.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
python-crontab==2.7.1
# via
# -r requirements/base.txt
# django-celery-beat
python-dateutil==2.8.2
# via
# -r requirements/base.txt
# celery
# python-crontab
python-dotenv==0.21.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
python-memcached==1.59
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pytz==2023.3
# via
# -r requirements/base.txt
# django
# django-timezone-field
# djangorestframework
# drf-yasg
pyyaml==6.0
# via
# -r requirements/base.txt
# drf-yasg
requests==2.31.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
# requests-oauthlib
requests-oauthlib==1.3.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
sentry-sdk==1.26.0
# via newsreader (pyproject.toml)
sgmllib3k==1.0.0
# via
# -r requirements/base.txt
# feedparser
six==1.16.0
# via
# -r requirements/base.txt
# bleach
# python-dateutil
# python-memcached
soupsieve==2.4.1
# via
# -r requirements/base.txt
# beautifulsoup4
sqlparse==0.4.4
# via
# -r requirements/base.txt
# django
typing-extensions==4.6.3
# via
# -r requirements/base.txt
# asgiref
# kombu
tzdata==2023.3
# via
# -r requirements/base.txt
# celery
# django-celery-beat
uritemplate==4.1.1
# via
# -r requirements/base.txt
# drf-yasg
urllib3==2.0.3
# via
# -r requirements/base.txt
# requests
# sentry-sdk
vine==5.0.0
# via
# -r requirements/base.txt
# amqp
# celery
# kombu
wcwidth==0.2.6
# via
# -r requirements/base.txt
# ftfy
# prompt-toolkit
webencodings==0.5.1
# via
# -r requirements/base.txt
# bleach
# The following packages are considered to be unsafe in a requirements file:
# setuptools

262
requirements/testing.txt Normal file
View file

@ -0,0 +1,262 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --extra=testing --output-file=requirements/testing.txt --resolver=backtracking pyproject.toml requirements/base.txt
#
amqp==5.1.1
# via
# -r requirements/base.txt
# kombu
asgiref==3.7.2
# via
# -r requirements/base.txt
# django
autoflake==2.2.0
# via newsreader (pyproject.toml)
beautifulsoup4==4.12.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
billiard==4.1.0
# via
# -r requirements/base.txt
# celery
black==23.3.0
# via newsreader (pyproject.toml)
bleach==6.0.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
celery==5.3.1
# via
# -r requirements/base.txt
# django-celery-beat
# newsreader (pyproject.toml)
certifi==2023.5.7
# via
# -r requirements/base.txt
# requests
charset-normalizer==3.1.0
# via
# -r requirements/base.txt
# requests
click==8.1.3
# via
# -r requirements/base.txt
# black
# celery
# click-didyoumean
# click-plugins
# click-repl
click-didyoumean==0.3.0
# via
# -r requirements/base.txt
# celery
click-plugins==1.1.1
# via
# -r requirements/base.txt
# celery
click-repl==0.3.0
# via
# -r requirements/base.txt
# celery
cron-descriptor==1.4.0
# via
# -r requirements/base.txt
# django-celery-beat
django==3.2.19
# via
# -r requirements/base.txt
# django-axes
# django-celery-beat
# django-timezone-field
# djangorestframework
# drf-yasg
# newsreader (pyproject.toml)
django-axes==6.0.4
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-celery-beat==2.5.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-registration-redux==2.12
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-rest-framework==0.1.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
django-timezone-field==5.1
# via
# -r requirements/base.txt
# django-celery-beat
djangorestframework==3.14.0
# via
# -r requirements/base.txt
# django-rest-framework
# drf-yasg
drf-yasg==1.21.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
factory-boy==3.2.1
# via newsreader (pyproject.toml)
faker==18.11.2
# via factory-boy
feedparser==6.0.10
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
freezegun==1.2.2
# via newsreader (pyproject.toml)
ftfy==5.9
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
idna==3.4
# via
# -r requirements/base.txt
# requests
inflection==0.5.1
# via
# -r requirements/base.txt
# drf-yasg
isort==5.12.0
# via newsreader (pyproject.toml)
kombu==5.3.1
# via
# -r requirements/base.txt
# celery
lxml==4.9.2
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
mypy-extensions==1.0.0
# via black
oauthlib==3.2.2
# via
# -r requirements/base.txt
# requests-oauthlib
packaging==23.1
# via
# -r requirements/base.txt
# black
# drf-yasg
pathspec==0.11.1
# via black
platformdirs==3.8.0
# via black
prompt-toolkit==3.0.38
# via
# -r requirements/base.txt
# click-repl
psycopg2==2.9.6
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pyflakes==3.0.1
# via autoflake
python-crontab==2.7.1
# via
# -r requirements/base.txt
# django-celery-beat
python-dateutil==2.8.2
# via
# -r requirements/base.txt
# celery
# faker
# freezegun
# python-crontab
python-dotenv==0.21.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
python-memcached==1.59
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
pytz==2023.3
# via
# -r requirements/base.txt
# django
# django-timezone-field
# djangorestframework
# drf-yasg
pyyaml==6.0
# via
# -r requirements/base.txt
# drf-yasg
requests==2.31.0
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
# requests-oauthlib
requests-oauthlib==1.3.1
# via
# -r requirements/base.txt
# newsreader (pyproject.toml)
sgmllib3k==1.0.0
# via
# -r requirements/base.txt
# feedparser
six==1.16.0
# via
# -r requirements/base.txt
# bleach
# python-dateutil
# python-memcached
soupsieve==2.4.1
# via
# -r requirements/base.txt
# beautifulsoup4
sqlparse==0.4.4
# via
# -r requirements/base.txt
# django
tblib==2.0.0
# via newsreader (pyproject.toml)
tomli==2.0.1
# via
# autoflake
# black
typing-extensions==4.6.3
# via
# -r requirements/base.txt
# asgiref
# black
# kombu
tzdata==2023.3
# via
# -r requirements/base.txt
# celery
# django-celery-beat
uritemplate==4.1.1
# via
# -r requirements/base.txt
# drf-yasg
urllib3==2.0.3
# via
# -r requirements/base.txt
# requests
vine==5.0.0
# via
# -r requirements/base.txt
# amqp
# celery
# kombu
wcwidth==0.2.6
# via
# -r requirements/base.txt
# ftfy
# prompt-toolkit
webencodings==0.5.1
# via
# -r requirements/base.txt
# bleach
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View file

@ -5,7 +5,7 @@ import sys
def main(): def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.dev") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.docker")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:

View file

@ -8,7 +8,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [

View file

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0001_initial")] dependencies = [("accounts", "0001_initial")]
operations = [migrations.RemoveField(model_name="user", name="username")] operations = [migrations.RemoveField(model_name="user", name="username")]

View file

@ -6,7 +6,6 @@ import newsreader.accounts.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0002_remove_user_username")] dependencies = [("accounts", "0002_remove_user_username")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0003_auto_20190714_1417")] dependencies = [("accounts", "0003_auto_20190714_1417")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0004_auto_20190714_1501")] dependencies = [("accounts", "0004_auto_20190714_1501")]
operations = [migrations.RemoveField(model_name="user", name="task_interval")] operations = [migrations.RemoveField(model_name="user", name="task_interval")]

View file

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0005_remove_user_task_interval")] dependencies = [("accounts", "0005_remove_user_task_interval")]
operations = [ operations = [

View file

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0006_auto_20191116_1253")] dependencies = [("accounts", "0006_auto_20191116_1253")]
operations = [ operations = [

View file

@ -15,7 +15,6 @@ def update_task_name(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0007_auto_20191116_1255")] dependencies = [("accounts", "0007_auto_20191116_1255")]
operations = [migrations.RunPython(update_task_name)] operations = [migrations.RunPython(update_task_name)]

View file

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("django_celery_beat", "0012_periodictask_expire_seconds"), ("django_celery_beat", "0012_periodictask_expire_seconds"),
("accounts", "0008_auto_20200422_2243"), ("accounts", "0008_auto_20200422_2243"),

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0009_auto_20200524_1218")] dependencies = [("accounts", "0009_auto_20200524_1218")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0010_auto_20200603_2230")] dependencies = [("accounts", "0010_auto_20200603_2230")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0011_auto_20200913_2101")] dependencies = [("accounts", "0011_auto_20200913_2101")]
operations = [migrations.RemoveField(model_name="user", name="task")] operations = [migrations.RemoveField(model_name="user", name="task")]

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0012_remove_user_task")] dependencies = [("accounts", "0012_remove_user_task")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0013_user_auto_mark_read")] dependencies = [("accounts", "0013_user_auto_mark_read")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0014_auto_20201218_2216")] dependencies = [("accounts", "0014_auto_20201218_2216")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("accounts", "0015_auto_20201219_1330")] dependencies = [("accounts", "0015_auto_20201219_1330")]
operations = [ operations = [

View file

@ -1,11 +1,6 @@
from django.contrib.auth import views as django_views from django.contrib.auth import views as django_views
from django.urls import reverse_lazy from django.urls import reverse_lazy
from newsreader.news.collection.reddit import (
get_reddit_access_token,
get_reddit_authorization_url,
)
# PasswordResetView sends the mail # PasswordResetView sends the mail
# PasswordResetDoneView shows a success message for the above # PasswordResetDoneView shows a success message for the above

View file

@ -4,11 +4,6 @@ from django.views.generic import TemplateView
from registration.backends.default import views as registration_views from registration.backends.default import views as registration_views
from newsreader.news.collection.reddit import (
get_reddit_access_token,
get_reddit_authorization_url,
)
# RegistrationView shows a registration form and sends the email # RegistrationView shows a registration form and sends the email
# RegistrationCompleteView shows after filling in the registration form # RegistrationCompleteView shows after filling in the registration form

View file

@ -4,10 +4,6 @@ from django.views.generic.edit import FormView, ModelFormMixin
from newsreader.accounts.forms import UserSettingsForm from newsreader.accounts.forms import UserSettingsForm
from newsreader.accounts.models import User from newsreader.accounts.models import User
from newsreader.news.collection.reddit import (
get_reddit_access_token,
get_reddit_authorization_url,
)
class SettingsView(ModelFormMixin, FormView): class SettingsView(ModelFormMixin, FormView):

View file

@ -3,7 +3,7 @@ import os
from celery import Celery from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.dev") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.docker")
app = Celery("newsreader") app = Celery("newsreader")
app.config_from_object("django.conf:settings", namespace="CELERY") app.config_from_object("django.conf:settings", namespace="CELERY")

View file

@ -21,7 +21,7 @@ DJANGO_PROJECT_DIR = os.path.join(BASE_DIR, "src", "newsreader")
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: don"t run with debug turned on in production! # SECURITY WARNING: don"t run with debug turned on in production!
DEBUG = True DEBUG = False
ALLOWED_HOSTS = ["127.0.0.1", "localhost"] ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
INTERNAL_IPS = ["127.0.0.1", "localhost"] INTERNAL_IPS = ["127.0.0.1", "localhost"]
@ -50,6 +50,8 @@ INSTALLED_APPS = [
"newsreader.news.collection", "newsreader.news.collection",
] ]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"axes.backends.AxesBackend", "axes.backends.AxesBackend",
"django.contrib.auth.backends.ModelBackend", "django.contrib.auth.backends.ModelBackend",
@ -93,10 +95,11 @@ WSGI_APPLICATION = "newsreader.wsgi.application"
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.postgresql", "ENGINE": "django.db.backends.postgresql",
"HOST": os.environ.get("POSTGRES_HOST", ""), "HOST": os.environ["POSTGRES_HOST"],
"NAME": os.environ.get("POSTGRES_NAME", "newsreader"), "PORT": os.environ["POSTGRES_PORT"],
"USER": os.environ.get("POSTGRES_USER"), "NAME": os.environ["POSTGRES_DB"],
"PASSWORD": os.environ.get("POSTGRES_PASSWORD"), "USER": os.environ["POSTGRES_USER"],
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
} }
} }
@ -105,11 +108,11 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache", "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "localhost:11211", "LOCATION": "memcached:11211",
}, },
"axes": { "axes": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache", "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "localhost:11211", "LOCATION": "memcached:11211",
}, },
} }
@ -128,43 +131,43 @@ LOGGING = {
"format": "[{server_time}] {message}", "format": "[{server_time}] {message}",
"style": "{", "style": "{",
}, },
"syslog": {
"class": "logging.Formatter",
"format": "[newsreader] {message}",
"style": "{",
},
}, },
"handlers": { "handlers": {
"console": { "console": {
"level": "INFO", "level": "INFO",
"filters": ["require_debug_true"],
"class": "logging.StreamHandler", "class": "logging.StreamHandler",
"formatter": "timestamped", "formatter": "timestamped",
}, },
"file": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"filename": BASE_DIR / "logs" / "newsreader.log",
"backupCount": 5,
"maxBytes": 50000000, # 50 mB
"formatter": "timestamped",
},
"celery": { "celery": {
"level": "INFO", "level": "INFO",
"filters": ["require_debug_false"], "class": "logging.handlers.RotatingFileHandler",
"class": "logging.handlers.SysLogHandler", "filename": BASE_DIR / "logs" / "celery.log",
"formatter": "syslog", "backupCount": 5,
"address": "/dev/log", "maxBytes": 50000000, # 50 mB
}, "formatter": "timestamped",
"syslog": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "logging.handlers.SysLogHandler",
"formatter": "syslog",
"address": "/dev/log",
}, },
}, },
"loggers": { "loggers": {
"django": {"handlers": ["console", "syslog"], "level": "INFO"}, "django": {"handlers": ["console"], "level": "INFO"},
"django.server": { "django.server": {
"handlers": ["console", "syslog"], "handlers": ["console"],
"level": "INFO", "level": "INFO",
"propagate": False, "propagate": False,
}, },
"celery": {"handlers": ["celery", "console"], "level": "INFO"}, "celery.task": {"handlers": ["console", "celery"], "level": "INFO"},
"newsreader": {"handlers": ["syslog", "console"], "level": "INFO"}, "newsreader": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
}, },
} }
@ -208,9 +211,6 @@ STATICFILES_FINDERS = [
# Email # Email
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# Project settings
ENVIRONMENT = "development"
# Reddit integration # Reddit integration
REDDIT_CLIENT_ID = "CLIENT_ID" REDDIT_CLIENT_ID = "CLIENT_ID"
REDDIT_CLIENT_SECRET = "CLIENT_SECRET" REDDIT_CLIENT_SECRET = "CLIENT_SECRET"
@ -251,7 +251,9 @@ SWAGGER_SETTINGS = {
# Celery # Celery
# https://docs.celeryproject.org/en/stable/userguide/configuration.html # https://docs.celeryproject.org/en/stable/userguide/configuration.html
# Note that celery settings are prefix with CELERY. See src/newsreader/celery.py.
CELERY_WORKER_HIJACK_ROOT_LOGGER = False CELERY_WORKER_HIJACK_ROOT_LOGGER = False
CELERY_BROKER_URL = "amqp://guest@rabbitmq:5672"
REGISTRATION_OPEN = True REGISTRATION_OPEN = True
REGISTRATION_AUTO_LOGIN = True REGISTRATION_AUTO_LOGIN = True
@ -261,7 +263,6 @@ ACCOUNT_ACTIVATION_DAYS = 7
SENTRY_CONFIG = { SENTRY_CONFIG = {
"dsn": os.environ.get("SENTRY_DSN"), "dsn": os.environ.get("SENTRY_DSN"),
"send_default_pii": False, "send_default_pii": False,
"environment": ENVIRONMENT,
"integrations": [DjangoIntegration(), CeleryIntegration()] "integrations": [DjangoIntegration(), CeleryIntegration()]
if DjangoIntegration and CeleryIntegration if DjangoIntegration and CeleryIntegration
else [], else [],

View file

@ -18,11 +18,11 @@ AXES_FAILURE_LIMIT = 50
AXES_COOLOFF_TIME = None AXES_COOLOFF_TIME = None
try: try:
from .local import * # noqa
# Optionally use sentry integration # Optionally use sentry integration
from sentry_sdk import init as sentry_init from sentry_sdk import init as sentry_init
from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION}) SENTRY_CONFIG.update({"release": VERSION})
sentry_init(**SENTRY_CONFIG) sentry_init(**SENTRY_CONFIG)

View file

@ -2,34 +2,21 @@ from .base import * # isort:skip
from .version import get_current_version from .version import get_current_version
SECRET_KEY = "=q(ztyo)b6noom#a164g&s9vcj1aawa^g#ing_ir99=_zl4g&$" ALLOWED_HOSTS = ["django", "127.0.0.1"]
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] INSTALLED_APPS += ["debug_toolbar", "django_extensions"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
LOGGING["loggers"].update(
{
"celery.task": {"handlers": ["console", "celery"], "level": "DEBUG"},
}
)
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DATABASES = { DEBUG = True
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "newsreader",
"USER": "newsreader",
"PASSWORD": "newsreader",
"HOST": "db",
}
}
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "memcached:11211",
},
"axes": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "memcached:11211",
},
}
# Project settings # Project settings
VERSION = get_current_version() VERSION = get_current_version()
@ -40,16 +27,12 @@ ENVIRONMENT = "docker"
AXES_FAILURE_LIMIT = 50 AXES_FAILURE_LIMIT = 50
AXES_COOLOFF_TIME = None AXES_COOLOFF_TIME = None
# Celery
# https://docs.celeryproject.org/en/latest/userguide/configuration.html
CELERY_BROKER_URL = "amqp://guest:guest@rabbitmq:5672//"
try: try:
from .local import * # noqa
# Optionally use sentry integration # Optionally use sentry integration
from sentry_sdk import init as sentry_init from sentry_sdk import init as sentry_init
from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT})
sentry_init(**SENTRY_CONFIG) sentry_init(**SENTRY_CONFIG)

View file

@ -2,7 +2,16 @@ from .base import * # isort:skip
from .version import get_current_version from .version import get_current_version
SECRET_KEY = "29%lkw+&n%^w4k#@_db2mo%*tc&xzb)x7xuq*(0$eucii%4r0c" del LOGGING["handlers"]["file"]
del LOGGING["handlers"]["celery"]
LOGGING["loggers"].update(
{
"celery.task": {"handlers": ["console"], "level": "DEBUG"},
"newsreader": {"handlers": ["console"], "level": "INFO"},
}
)
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

View file

@ -7,26 +7,13 @@ from .base import * # isort:skip
DEBUG = False DEBUG = False
ALLOWED_HOSTS = ["rss.fudiggity.nl"] ALLOWED_HOSTS = ["127.0.0.1", "localhost", "rss.fudiggity.nl", "django"]
ADMINS = [ ADMINS = [
("", email) ("", email)
for email in os.getenv("ADMINS", "").split(",") for email in os.getenv("ADMINS", "").split(",")
if os.environ.get("ADMINS") if os.environ.get("ADMINS")
] ]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ["POSTGRES_HOST"],
"PORT": os.environ["POSTGRES_PORT"],
"NAME": os.environ["POSTGRES_NAME"],
"USER": os.environ["POSTGRES_USER"],
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
}
}
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
function isCSSVariablesSupported() { const isCSSVariablesSupported = () => {
return window.CSS && window.CSS.supports('color', 'var(--fake-color'); return window.CSS && window.CSS.supports('color', 'var(--fake-color');
} };
function changeTheme(e) { const changeTheme = event => {
const currentPref = sessionStorage.getItem('t-dark'); const currentPref = sessionStorage.getItem('t-dark');
const isDark = currentPref && currentPref === 'true' ? true : false; const isDark = currentPref && currentPref === 'true' ? true : false;
@ -14,12 +14,12 @@ function changeTheme(e) {
try { try {
sessionStorage.setItem('t-dark', !isDark); sessionStorage.setItem('t-dark', !isDark);
} catch (e) { } catch (error) {
// do nothing. // do nothing.
} }
} };
function prefersDarkTheme() { const getThemePreference = () => {
try { try {
const currentPref = sessionStorage.getItem('t-dark'); const currentPref = sessionStorage.getItem('t-dark');
@ -33,12 +33,12 @@ function prefersDarkTheme() {
} else { } else {
return false; return false;
} }
} catch (e) { } catch (error) {
return false; return false;
} }
} };
function toggleDarkTheme(isDark) { const toggleDarkTheme = isDark => {
if (isDark) { if (isDark) {
document.documentElement.classList.add('dark-theme'); document.documentElement.classList.add('dark-theme');
} else { } else {
@ -47,30 +47,30 @@ function toggleDarkTheme(isDark) {
try { try {
sessionStorage.setItem('t-dark', isDark); sessionStorage.setItem('t-dark', isDark);
} catch (e) { } catch (error) {
// do nothing. // do nothing.
} }
} };
function initThemeSelector() { const initThemeSelector = () => {
const themeButton = document.getElementsByClassName('theme-switcher')[0]; const themeButton = document.getElementsByClassName('theme-switcher')[0];
const mqPrefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)'); const prefersDarkTheme = window.matchMedia('(prefers-color-scheme: dark)');
if (prefersDarkTheme()) { if (getThemePreference()) {
toggleDarkTheme(true); toggleDarkTheme(true);
} }
themeButton.addEventListener('click', changeTheme); themeButton.addEventListener('click', changeTheme);
mqPrefersDarkTheme.addListener(mq => { prefersDarkTheme.addListener(mediaQuery => {
toggleDarkTheme(mq.matches); toggleDarkTheme(mediaQuery.matches);
}); });
} };
function init() { const init = () => {
if (isCSSVariablesSupported()) { if (isCSSVariablesSupported()) {
initThemeSelector(); initThemeSelector();
} }
} };
init(); init();

View file

@ -5,6 +5,7 @@ import { isEqual } from 'lodash';
import { fetchCategories } from './actions/categories'; import { fetchCategories } from './actions/categories';
import ScrollTop from './components/ScrollTop.js';
import Sidebar from './components/sidebar/Sidebar.js'; import Sidebar from './components/sidebar/Sidebar.js';
import PostList from './components/postlist/PostList.js'; import PostList from './components/postlist/PostList.js';
import PostModal from './components/PostModal.js'; import PostModal from './components/PostModal.js';
@ -41,6 +42,8 @@ class App extends React.Component {
/> />
)} )}
<ScrollTop />
{this.props.error && ( {this.props.error && (
<Messages messages={[{ type: 'error', text: this.props.error.message }]} /> <Messages messages={[{ type: 'error', text: this.props.error.message }]} />
)} )}

View file

@ -0,0 +1,40 @@
import React from 'react';
export default class ScrollTop extends React.Component {
scrollListener = ::this.scrollListener;
state = { showTop: false, showBottom: false };
componentDidMount() {
window.addEventListener('scroll', this.scrollListener);
}
scrollListener() {
const showBottom = window.innerHeight + window.scrollY < document.body.offsetHeight;
this.setState({
showTop: window.pageYOffset > 0 ? true : false,
showBottom: showBottom,
});
}
render() {
return (
<div className="scroll-to-top">
{this.state.showTop && (
<i
className="scroll-to-top__icon scroll-to-top__icon--top"
onClick={() => window.scrollTo(0, 0)}
/>
)}
{this.state.showBottom && (
<i
className="scroll-to-top__icon scroll-to-top__icon--bottom"
onClick={() => window.scrollTo(0, document.body.scrollHeight)}
/>
)}
</div>
);
}
}

View file

@ -21,13 +21,10 @@ class PostList extends React.Component {
} }
checkScrollHeight(e) { checkScrollHeight(e) {
const currentHeight = window.scrollY + window.innerHeight; const postList = document.body.querySelector('.posts__list');
const totalHeight = document.body.offsetHeight;
const currentPercentage = (currentHeight / totalHeight) * 100;
if (this.props.next && !this.props.lastReached) { if (this.props.next && !this.props.lastReached) {
if (currentPercentage > 60 && !this.props.isFetching) { if (window.scrollY + window.innerHeight >= postList.offsetHeight) {
this.paginate(); this.paginate();
} }
} }

View file

@ -7,11 +7,7 @@ from rest_framework.generics import (
) )
from rest_framework.response import Response from rest_framework.response import Response
from newsreader.core.pagination import ( from newsreader.core.pagination import CursorPagination
CursorPagination,
LargeResultSetPagination,
ResultSetPagination,
)
from newsreader.news.collection.models import CollectionRule from newsreader.news.collection.models import CollectionRule
from newsreader.news.collection.serializers import RuleSerializer from newsreader.news.collection.serializers import RuleSerializer
from newsreader.news.core.filters import ReadFilter from newsreader.news.core.filters import ReadFilter

View file

@ -18,7 +18,6 @@ from newsreader.news.collection.base import (
) )
from newsreader.news.collection.choices import RuleTypeChoices from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.exceptions import ( from newsreader.news.collection.exceptions import (
StreamDeniedException,
StreamException, StreamException,
StreamNotFoundException, StreamNotFoundException,
StreamParseException, StreamParseException,

View file

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [] dependencies = []

View file

@ -7,7 +7,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [

View file

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0002_auto_20190714_1036")] dependencies = [("collection", "0002_auto_20190714_1036")]
operations = [ operations = [

View file

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0003_auto_20190714_1417")] dependencies = [("collection", "0003_auto_20190714_1417")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0004_auto_20190714_1422")] dependencies = [("collection", "0004_auto_20190714_1422")]
operations = [ operations = [

View file

@ -7,7 +7,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("collection", "0005_auto_20200303_1932"), ("collection", "0005_auto_20200303_1932"),

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0006_auto_20200412_1955")] dependencies = [("collection", "0006_auto_20200412_1955")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0007_collectionrule_enabled")] dependencies = [("collection", "0007_collectionrule_enabled")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0008_collectionrule_type")] dependencies = [("collection", "0008_collectionrule_type")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0009_auto_20200807_2030")] dependencies = [("collection", "0009_auto_20200807_2030")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0010_auto_20200913_2101")] dependencies = [("collection", "0010_auto_20200913_2101")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0011_auto_20200913_2157")] dependencies = [("collection", "0011_auto_20200913_2157")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0012_auto_20201219_1331")] dependencies = [("collection", "0012_auto_20201219_1331")]
operations = [ operations = [

View file

@ -12,7 +12,6 @@ def reset_default_downvotes(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0013_auto_20201219_1345")] dependencies = [("collection", "0013_auto_20201219_1345")]
operations = [migrations.RunPython(reset_default_downvotes)] operations = [migrations.RunPython(reset_default_downvotes)]

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("collection", "0014_auto_20201219_1346")] dependencies = [("collection", "0014_auto_20201219_1346")]
operations = [ operations = [

View file

@ -23,10 +23,6 @@ from newsreader.news.collection.base import (
Scheduler, Scheduler,
) )
from newsreader.news.collection.choices import RuleTypeChoices from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.constants import (
WHITELISTED_ATTRIBUTES,
WHITELISTED_TAGS,
)
from newsreader.news.collection.exceptions import ( from newsreader.news.collection.exceptions import (
BuilderDuplicateException, BuilderDuplicateException,
BuilderException, BuilderException,

View file

@ -6,7 +6,6 @@ from bs4 import BeautifulSoup
from newsreader.news.collection.exceptions import ( from newsreader.news.collection.exceptions import (
StreamDeniedException, StreamDeniedException,
StreamException,
StreamForbiddenException, StreamForbiddenException,
StreamNotFoundException, StreamNotFoundException,
StreamParseException, StreamParseException,

View file

@ -11,10 +11,8 @@ from freezegun import freeze_time
from newsreader.news.collection.exceptions import ( from newsreader.news.collection.exceptions import (
StreamDeniedException, StreamDeniedException,
StreamException,
StreamForbiddenException, StreamForbiddenException,
StreamNotFoundException, StreamNotFoundException,
StreamParseException,
StreamTimeOutException, StreamTimeOutException,
) )
from newsreader.news.collection.feed import FeedCollector from newsreader.news.collection.feed import FeedCollector

View file

@ -206,6 +206,7 @@ simple_mock_parsed = {
"updated": "Sun, 12 Jul 2020 17:21:20 GMT", "updated": "Sun, 12 Jul 2020 17:21:20 GMT",
"updated_parsed": struct_time((2020, 7, 12, 17, 21, 20, 6, 194, 0)), "updated_parsed": struct_time((2020, 7, 12, 17, 21, 20, 6, 194, 0)),
}, },
"headers": {},
"namespaces": { "namespaces": {
"": "http://www.w3.org/2005/Atom", "": "http://www.w3.org/2005/Atom",
"content": "http://purl.org/rss/1.0/modules/content/", "content": "http://purl.org/rss/1.0/modules/content/",

View file

@ -193,3 +193,35 @@ class TwitterClientTestCase(TestCase):
self.assertIsNone(user.twitter_oauth_token) self.assertIsNone(user.twitter_oauth_token)
self.assertIsNone(user.twitter_oauth_token_secret) self.assertIsNone(user.twitter_oauth_token_secret)
def test_client_does_not_reset_token(self):
"""
The user's token and refresh token should not be reset when an generic
exception is caught
"""
user = UserFactory(
twitter_oauth_token=str(uuid4()), twitter_oauth_token_secret=str(uuid4())
)
timeline = TwitterTimelineFactory(user=user)
response = Mock(json=lambda: {"errors": [{"code": 100}]})
self.mocked_read.side_effect = StreamException(
message="Generic message", response=response
)
with TwitterClient([timeline]) as client:
for data, stream in client:
with self.subTest(data=data, stream=stream):
self.assertIsNone(data)
self.assertIsNone(stream)
self.assertEquals(stream.rule.error, "")
self.assertEquals(stream.rule.succeeded, False)
self.mocked_read.assert_called()
user.refresh_from_db()
timeline.refresh_from_db()
self.assertIsNotNone(user.twitter_oauth_token)
self.assertIsNotNone(user.twitter_oauth_token_secret)

View file

@ -10,7 +10,6 @@ from newsreader.news.collection.exceptions import (
StreamNotFoundException, StreamNotFoundException,
StreamParseException, StreamParseException,
StreamTimeOutException, StreamTimeOutException,
StreamTooManyException,
) )
from newsreader.news.collection.tests.factories import TwitterTimelineFactory from newsreader.news.collection.tests.factories import TwitterTimelineFactory
from newsreader.news.collection.tests.twitter.stream.mocks import simple_mock from newsreader.news.collection.tests.twitter.stream.mocks import simple_mock

View file

@ -28,7 +28,6 @@ from newsreader.news.collection.exceptions import (
BuilderException, BuilderException,
BuilderMissingDataException, BuilderMissingDataException,
BuilderParseException, BuilderParseException,
StreamDeniedException,
StreamException, StreamException,
StreamNotFoundException, StreamNotFoundException,
StreamParseException, StreamParseException,
@ -250,39 +249,41 @@ class TwitterClient(PostClient):
try: try:
response_data = e.response.json() response_data = e.response.json()
except JSONDecodeError: except JSONDecodeError:
logger.exception("Could not parse json for request")
continue continue
if "errors" in response_data: if "errors" in response_data:
errors = response_data["errors"] errors = response_data["errors"]
token_expired = any(error["code"] == 89 for error in errors) token_expired = any(error["code"] == 89 for error in errors)
try: if token_expired:
import sentry_sdk try:
import sentry_sdk
with sentry_sdk.push_scope() as scope: with sentry_sdk.push_scope() as scope:
scope.set_extra("content", response_data) scope.set_extra("content", response_data)
sentry_sdk.capture_message( sentry_sdk.capture_message(
"Twitter authentication credentials reset" "Twitter authentication credentials reset"
) )
except ImportError: except ImportError:
pass pass
stream.rule.user.twitter_oauth_token = None stream.rule.user.twitter_oauth_token = None
stream.rule.user.twitter_oauth_token_secret = None stream.rule.user.twitter_oauth_token_secret = None
stream.rule.user.save() stream.rule.user.save()
message = _( message = _(
"Your Twitter account credentials have expired. Re-authenticate in" "Your Twitter account credentials have expired. Re-authenticate in"
" the settings page to keep retrieving Twitter specific information" " the settings page to keep retrieving Twitter specific information"
" from your account." " from your account."
) )
send_mail( send_mail(
"Twitter account needs re-authentication", "Twitter account needs re-authentication",
message, message,
None, None,
[stream.rule.user.email], [stream.rule.user.email],
) )
continue continue
finally: finally:

View file

@ -6,11 +6,7 @@ from django.views.generic.edit import CreateView, FormView, UpdateView
from django_celery_beat.models import IntervalSchedule from django_celery_beat.models import IntervalSchedule
from newsreader.news.collection.choices import RuleTypeChoices from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.forms import ( from newsreader.news.collection.forms import FeedForm, OPMLImportForm
CollectionRuleBulkForm,
FeedForm,
OPMLImportForm,
)
from newsreader.news.collection.models import CollectionRule from newsreader.news.collection.models import CollectionRule
from newsreader.news.collection.views.base import ( from newsreader.news.collection.views.base import (
CollectionRuleDetailMixin, CollectionRuleDetailMixin,

View file

@ -6,10 +6,7 @@ from django.views.generic.edit import FormView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from newsreader.news.collection.forms import CollectionRuleBulkForm from newsreader.news.collection.forms import CollectionRuleBulkForm
from newsreader.news.collection.views.base import ( from newsreader.news.collection.views.base import CollectionRuleViewMixin
CollectionRuleDetailMixin,
CollectionRuleViewMixin,
)
class CollectionRuleListView(CollectionRuleViewMixin, ListView): class CollectionRuleListView(CollectionRuleViewMixin, ListView):

View file

@ -2,7 +2,6 @@ from rest_framework import status
from rest_framework.generics import ( from rest_framework.generics import (
GenericAPIView, GenericAPIView,
ListAPIView, ListAPIView,
ListCreateAPIView,
RetrieveUpdateAPIView, RetrieveUpdateAPIView,
RetrieveUpdateDestroyAPIView, RetrieveUpdateDestroyAPIView,
get_object_or_404, get_object_or_404,

View file

@ -8,7 +8,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [

View file

@ -7,7 +7,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("core", "0001_initial")] dependencies = [("core", "0001_initial")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("core", "0002_auto_20190714_1425")] dependencies = [("core", "0002_auto_20190714_1425")]
operations = [ operations = [

View file

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("core", "0003_post_read"), ("core", "0003_post_read"),

View file

@ -7,7 +7,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("core", "0004_auto_20191116_1315"), ("core", "0004_auto_20191116_1315"),

View file

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("core", "0005_auto_20200412_1955")] dependencies = [("core", "0005_auto_20200412_1955")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("core", "0006_auto_20200524_1218")] dependencies = [("core", "0006_auto_20200524_1218")]
operations = [ operations = [

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("core", "0007_auto_20200706_2312")] dependencies = [("core", "0007_auto_20200706_2312")]
operations = [ operations = [

View file

@ -14,7 +14,6 @@ from newsreader.news.core.views import (
CategoryCreateView, CategoryCreateView,
CategoryListView, CategoryListView,
CategoryUpdateView, CategoryUpdateView,
NewsView,
) )

View file

@ -26,3 +26,4 @@
@import './post-message/index'; @import './post-message/index';
@import './posts/index'; @import './posts/index';
@import './posts-info/index'; @import './posts-info/index';
@import './scroll-to-top/index';

View file

@ -5,4 +5,6 @@
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
z-index: 1000;
} }

View file

@ -0,0 +1,33 @@
.scroll-to-top {
display: flex;
gap: 10px;
position: fixed;
right: 15%;
bottom: 0;
margin: 0 0 20px 0;
&:hover {
cursor: pointer;
}
&__icon {
font-style: initial;
padding: 10px;
background-color: var(--lightest-accent-color);
&--top:before {
@include font-awesome;
content: "\f062";
}
&--bottom:before {
@include font-awesome;
content: "\f063";
}
}
}

View file

@ -0,0 +1 @@
@import './scroll-to-top';

View file

@ -1,6 +1,6 @@
from django.core.cache import cache from time import monotonic
from celery.five import monotonic from django.core.cache import cache
LOCK_EXPIRE = 60 * 10 # 10 minutes LOCK_EXPIRE = 60 * 10 # 10 minutes