0.4.0
This commit is contained in:
parent
6b2c4996d5
commit
8e7b059ad3
97 changed files with 15077 additions and 6892 deletions
|
|
@ -5,7 +5,7 @@ import sys
|
|||
|
||||
|
||||
def main():
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.dev")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.docker")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0001_initial")]
|
||||
|
||||
operations = [migrations.RemoveField(model_name="user", name="username")]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import newsreader.accounts.models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0002_remove_user_username")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0003_auto_20190714_1417")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0004_auto_20190714_1501")]
|
||||
|
||||
operations = [migrations.RemoveField(model_name="user", name="task_interval")]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0005_remove_user_task_interval")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0006_auto_20191116_1253")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ def update_task_name(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0007_auto_20191116_1255")]
|
||||
|
||||
operations = [migrations.RunPython(update_task_name)]
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("django_celery_beat", "0012_periodictask_expire_seconds"),
|
||||
("accounts", "0008_auto_20200422_2243"),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0009_auto_20200524_1218")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0010_auto_20200603_2230")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0011_auto_20200913_2101")]
|
||||
|
||||
operations = [migrations.RemoveField(model_name="user", name="task")]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0012_remove_user_task")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0013_user_auto_mark_read")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0014_auto_20201218_2216")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("accounts", "0015_auto_20201219_1330")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
from django.contrib.auth import views as django_views
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from newsreader.news.collection.reddit import (
|
||||
get_reddit_access_token,
|
||||
get_reddit_authorization_url,
|
||||
)
|
||||
|
||||
|
||||
# PasswordResetView sends the mail
|
||||
# PasswordResetDoneView shows a success message for the above
|
||||
|
|
|
|||
|
|
@ -4,11 +4,6 @@ from django.views.generic import TemplateView
|
|||
|
||||
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
|
||||
# RegistrationCompleteView shows after filling in the registration form
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ from django.views.generic.edit import FormView, ModelFormMixin
|
|||
|
||||
from newsreader.accounts.forms import UserSettingsForm
|
||||
from newsreader.accounts.models import User
|
||||
from newsreader.news.collection.reddit import (
|
||||
get_reddit_access_token,
|
||||
get_reddit_authorization_url,
|
||||
)
|
||||
|
||||
|
||||
class SettingsView(ModelFormMixin, FormView):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import os
|
|||
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.config_from_object("django.conf:settings", namespace="CELERY")
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ DJANGO_PROJECT_DIR = os.path.join(BASE_DIR, "src", "newsreader")
|
|||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
||||
# SECURITY WARNING: don"t run with debug turned on in production!
|
||||
DEBUG = True
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
|
||||
INTERNAL_IPS = ["127.0.0.1", "localhost"]
|
||||
|
|
@ -50,6 +50,8 @@ INSTALLED_APPS = [
|
|||
"newsreader.news.collection",
|
||||
]
|
||||
|
||||
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
"axes.backends.AxesBackend",
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
|
|
@ -93,10 +95,11 @@ WSGI_APPLICATION = "newsreader.wsgi.application"
|
|||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"HOST": os.environ.get("POSTGRES_HOST", ""),
|
||||
"NAME": os.environ.get("POSTGRES_NAME", "newsreader"),
|
||||
"USER": os.environ.get("POSTGRES_USER"),
|
||||
"PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
|
||||
"HOST": os.environ["POSTGRES_HOST"],
|
||||
"PORT": os.environ["POSTGRES_PORT"],
|
||||
"NAME": os.environ["POSTGRES_DB"],
|
||||
"USER": os.environ["POSTGRES_USER"],
|
||||
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,11 +108,11 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"LOCATION": "localhost:11211",
|
||||
"LOCATION": "memcached:11211",
|
||||
},
|
||||
"axes": {
|
||||
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
|
||||
"LOCATION": "localhost:11211",
|
||||
"LOCATION": "memcached:11211",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -128,43 +131,43 @@ LOGGING = {
|
|||
"format": "[{server_time}] {message}",
|
||||
"style": "{",
|
||||
},
|
||||
"syslog": {
|
||||
"class": "logging.Formatter",
|
||||
"format": "[newsreader] {message}",
|
||||
"style": "{",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"level": "INFO",
|
||||
"filters": ["require_debug_true"],
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "timestamped",
|
||||
},
|
||||
"file": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": BASE_DIR / "logs" / "newsreader.log",
|
||||
"backupCount": 5,
|
||||
"maxBytes": 50000000, # 50 mB
|
||||
"formatter": "timestamped",
|
||||
},
|
||||
"celery": {
|
||||
"level": "INFO",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "logging.handlers.SysLogHandler",
|
||||
"formatter": "syslog",
|
||||
"address": "/dev/log",
|
||||
},
|
||||
"syslog": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "logging.handlers.SysLogHandler",
|
||||
"formatter": "syslog",
|
||||
"address": "/dev/log",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": BASE_DIR / "logs" / "celery.log",
|
||||
"backupCount": 5,
|
||||
"maxBytes": 50000000, # 50 mB
|
||||
"formatter": "timestamped",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"django": {"handlers": ["console", "syslog"], "level": "INFO"},
|
||||
"django": {"handlers": ["console"], "level": "INFO"},
|
||||
"django.server": {
|
||||
"handlers": ["console", "syslog"],
|
||||
"handlers": ["console"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"celery": {"handlers": ["celery", "console"], "level": "INFO"},
|
||||
"newsreader": {"handlers": ["syslog", "console"], "level": "INFO"},
|
||||
"celery.task": {"handlers": ["console", "celery"], "level": "INFO"},
|
||||
"newsreader": {
|
||||
"handlers": ["console", "file"],
|
||||
"level": "DEBUG",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -208,9 +211,6 @@ STATICFILES_FINDERS = [
|
|||
# Email
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
# Project settings
|
||||
ENVIRONMENT = "development"
|
||||
|
||||
# Reddit integration
|
||||
REDDIT_CLIENT_ID = "CLIENT_ID"
|
||||
REDDIT_CLIENT_SECRET = "CLIENT_SECRET"
|
||||
|
|
@ -251,7 +251,9 @@ SWAGGER_SETTINGS = {
|
|||
|
||||
# Celery
|
||||
# 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_BROKER_URL = "amqp://guest@rabbitmq:5672"
|
||||
|
||||
REGISTRATION_OPEN = True
|
||||
REGISTRATION_AUTO_LOGIN = True
|
||||
|
|
@ -261,7 +263,6 @@ ACCOUNT_ACTIVATION_DAYS = 7
|
|||
SENTRY_CONFIG = {
|
||||
"dsn": os.environ.get("SENTRY_DSN"),
|
||||
"send_default_pii": False,
|
||||
"environment": ENVIRONMENT,
|
||||
"integrations": [DjangoIntegration(), CeleryIntegration()]
|
||||
if DjangoIntegration and CeleryIntegration
|
||||
else [],
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ AXES_FAILURE_LIMIT = 50
|
|||
AXES_COOLOFF_TIME = None
|
||||
|
||||
try:
|
||||
from .local import * # noqa
|
||||
|
||||
# Optionally use sentry integration
|
||||
from sentry_sdk import init as sentry_init
|
||||
|
||||
from .local import * # noqa
|
||||
|
||||
SENTRY_CONFIG.update({"release": VERSION})
|
||||
|
||||
sentry_init(**SENTRY_CONFIG)
|
||||
|
|
|
|||
|
|
@ -2,34 +2,21 @@ from .base import * # isort:skip
|
|||
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"]
|
||||
|
||||
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
|
||||
|
||||
LOGGING["loggers"].update(
|
||||
{
|
||||
"celery.task": {"handlers": ["console", "celery"], "level": "DEBUG"},
|
||||
}
|
||||
)
|
||||
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
DATABASES = {
|
||||
"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",
|
||||
},
|
||||
}
|
||||
DEBUG = True
|
||||
|
||||
# Project settings
|
||||
VERSION = get_current_version()
|
||||
|
|
@ -40,16 +27,12 @@ ENVIRONMENT = "docker"
|
|||
AXES_FAILURE_LIMIT = 50
|
||||
AXES_COOLOFF_TIME = None
|
||||
|
||||
# Celery
|
||||
# https://docs.celeryproject.org/en/latest/userguide/configuration.html
|
||||
CELERY_BROKER_URL = "amqp://guest:guest@rabbitmq:5672//"
|
||||
|
||||
try:
|
||||
from .local import * # noqa
|
||||
|
||||
# Optionally use sentry integration
|
||||
from sentry_sdk import init as sentry_init
|
||||
|
||||
from .local import * # noqa
|
||||
|
||||
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT})
|
||||
|
||||
sentry_init(**SENTRY_CONFIG)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,16 @@ from .base import * # isort:skip
|
|||
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"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,26 +7,13 @@ from .base import * # isort:skip
|
|||
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ["rss.fudiggity.nl"]
|
||||
ALLOWED_HOSTS = ["127.0.0.1", "localhost", "rss.fudiggity.nl", "django"]
|
||||
ADMINS = [
|
||||
("", email)
|
||||
for email in os.getenv("ADMINS", "").split(",")
|
||||
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 = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1068
src/newsreader/fixtures/fixture.json
Normal file
1068
src/newsreader/fixtures/fixture.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,8 @@
|
|||
function isCSSVariablesSupported() {
|
||||
const isCSSVariablesSupported = () => {
|
||||
return window.CSS && window.CSS.supports('color', 'var(--fake-color');
|
||||
}
|
||||
};
|
||||
|
||||
function changeTheme(e) {
|
||||
const changeTheme = event => {
|
||||
const currentPref = sessionStorage.getItem('t-dark');
|
||||
const isDark = currentPref && currentPref === 'true' ? true : false;
|
||||
|
||||
|
|
@ -14,12 +14,12 @@ function changeTheme(e) {
|
|||
|
||||
try {
|
||||
sessionStorage.setItem('t-dark', !isDark);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function prefersDarkTheme() {
|
||||
const getThemePreference = () => {
|
||||
try {
|
||||
const currentPref = sessionStorage.getItem('t-dark');
|
||||
|
||||
|
|
@ -33,12 +33,12 @@ function prefersDarkTheme() {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function toggleDarkTheme(isDark) {
|
||||
const toggleDarkTheme = isDark => {
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add('dark-theme');
|
||||
} else {
|
||||
|
|
@ -47,30 +47,30 @@ function toggleDarkTheme(isDark) {
|
|||
|
||||
try {
|
||||
sessionStorage.setItem('t-dark', isDark);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function initThemeSelector() {
|
||||
const initThemeSelector = () => {
|
||||
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);
|
||||
}
|
||||
|
||||
themeButton.addEventListener('click', changeTheme);
|
||||
|
||||
mqPrefersDarkTheme.addListener(mq => {
|
||||
toggleDarkTheme(mq.matches);
|
||||
prefersDarkTheme.addListener(mediaQuery => {
|
||||
toggleDarkTheme(mediaQuery.matches);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
||||
const init = () => {
|
||||
if (isCSSVariablesSupported()) {
|
||||
initThemeSelector();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { isEqual } from 'lodash';
|
|||
|
||||
import { fetchCategories } from './actions/categories';
|
||||
|
||||
import ScrollTop from './components/ScrollTop.js';
|
||||
import Sidebar from './components/sidebar/Sidebar.js';
|
||||
import PostList from './components/postlist/PostList.js';
|
||||
import PostModal from './components/PostModal.js';
|
||||
|
|
@ -41,6 +42,8 @@ class App extends React.Component {
|
|||
/>
|
||||
)}
|
||||
|
||||
<ScrollTop />
|
||||
|
||||
{this.props.error && (
|
||||
<Messages messages={[{ type: 'error', text: this.props.error.message }]} />
|
||||
)}
|
||||
|
|
|
|||
40
src/newsreader/js/pages/homepage/components/ScrollTop.js
Normal file
40
src/newsreader/js/pages/homepage/components/ScrollTop.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,13 +21,10 @@ class PostList extends React.Component {
|
|||
}
|
||||
|
||||
checkScrollHeight(e) {
|
||||
const currentHeight = window.scrollY + window.innerHeight;
|
||||
const totalHeight = document.body.offsetHeight;
|
||||
|
||||
const currentPercentage = (currentHeight / totalHeight) * 100;
|
||||
const postList = document.body.querySelector('.posts__list');
|
||||
|
||||
if (this.props.next && !this.props.lastReached) {
|
||||
if (currentPercentage > 60 && !this.props.isFetching) {
|
||||
if (window.scrollY + window.innerHeight >= postList.offsetHeight) {
|
||||
this.paginate();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ from rest_framework.generics import (
|
|||
)
|
||||
from rest_framework.response import Response
|
||||
|
||||
from newsreader.core.pagination import (
|
||||
CursorPagination,
|
||||
LargeResultSetPagination,
|
||||
ResultSetPagination,
|
||||
)
|
||||
from newsreader.core.pagination import CursorPagination
|
||||
from newsreader.news.collection.models import CollectionRule
|
||||
from newsreader.news.collection.serializers import RuleSerializer
|
||||
from newsreader.news.core.filters import ReadFilter
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from newsreader.news.collection.base import (
|
|||
)
|
||||
from newsreader.news.collection.choices import RuleTypeChoices
|
||||
from newsreader.news.collection.exceptions import (
|
||||
StreamDeniedException,
|
||||
StreamException,
|
||||
StreamNotFoundException,
|
||||
StreamParseException,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0002_auto_20190714_1036")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0003_auto_20190714_1417")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0004_auto_20190714_1422")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("collection", "0005_auto_20200303_1932"),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0006_auto_20200412_1955")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0007_collectionrule_enabled")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0008_collectionrule_type")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0009_auto_20200807_2030")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0010_auto_20200913_2101")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0011_auto_20200913_2157")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0012_auto_20201219_1331")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ def reset_default_downvotes(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0013_auto_20201219_1345")]
|
||||
|
||||
operations = [migrations.RunPython(reset_default_downvotes)]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("collection", "0014_auto_20201219_1346")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ from newsreader.news.collection.base import (
|
|||
Scheduler,
|
||||
)
|
||||
from newsreader.news.collection.choices import RuleTypeChoices
|
||||
from newsreader.news.collection.constants import (
|
||||
WHITELISTED_ATTRIBUTES,
|
||||
WHITELISTED_TAGS,
|
||||
)
|
||||
from newsreader.news.collection.exceptions import (
|
||||
BuilderDuplicateException,
|
||||
BuilderException,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from bs4 import BeautifulSoup
|
|||
|
||||
from newsreader.news.collection.exceptions import (
|
||||
StreamDeniedException,
|
||||
StreamException,
|
||||
StreamForbiddenException,
|
||||
StreamNotFoundException,
|
||||
StreamParseException,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ from freezegun import freeze_time
|
|||
|
||||
from newsreader.news.collection.exceptions import (
|
||||
StreamDeniedException,
|
||||
StreamException,
|
||||
StreamForbiddenException,
|
||||
StreamNotFoundException,
|
||||
StreamParseException,
|
||||
StreamTimeOutException,
|
||||
)
|
||||
from newsreader.news.collection.feed import FeedCollector
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ simple_mock_parsed = {
|
|||
"updated": "Sun, 12 Jul 2020 17:21:20 GMT",
|
||||
"updated_parsed": struct_time((2020, 7, 12, 17, 21, 20, 6, 194, 0)),
|
||||
},
|
||||
"headers": {},
|
||||
"namespaces": {
|
||||
"": "http://www.w3.org/2005/Atom",
|
||||
"content": "http://purl.org/rss/1.0/modules/content/",
|
||||
|
|
|
|||
|
|
@ -193,3 +193,35 @@ class TwitterClientTestCase(TestCase):
|
|||
|
||||
self.assertIsNone(user.twitter_oauth_token)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from newsreader.news.collection.exceptions import (
|
|||
StreamNotFoundException,
|
||||
StreamParseException,
|
||||
StreamTimeOutException,
|
||||
StreamTooManyException,
|
||||
)
|
||||
from newsreader.news.collection.tests.factories import TwitterTimelineFactory
|
||||
from newsreader.news.collection.tests.twitter.stream.mocks import simple_mock
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ from newsreader.news.collection.exceptions import (
|
|||
BuilderException,
|
||||
BuilderMissingDataException,
|
||||
BuilderParseException,
|
||||
StreamDeniedException,
|
||||
StreamException,
|
||||
StreamNotFoundException,
|
||||
StreamParseException,
|
||||
|
|
@ -250,39 +249,41 @@ class TwitterClient(PostClient):
|
|||
try:
|
||||
response_data = e.response.json()
|
||||
except JSONDecodeError:
|
||||
logger.exception("Could not parse json for request")
|
||||
continue
|
||||
|
||||
if "errors" in response_data:
|
||||
errors = response_data["errors"]
|
||||
token_expired = any(error["code"] == 89 for error in errors)
|
||||
|
||||
try:
|
||||
import sentry_sdk
|
||||
if token_expired:
|
||||
try:
|
||||
import sentry_sdk
|
||||
|
||||
with sentry_sdk.push_scope() as scope:
|
||||
scope.set_extra("content", response_data)
|
||||
sentry_sdk.capture_message(
|
||||
"Twitter authentication credentials reset"
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
with sentry_sdk.push_scope() as scope:
|
||||
scope.set_extra("content", response_data)
|
||||
sentry_sdk.capture_message(
|
||||
"Twitter authentication credentials reset"
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
stream.rule.user.twitter_oauth_token = None
|
||||
stream.rule.user.twitter_oauth_token_secret = None
|
||||
stream.rule.user.save()
|
||||
stream.rule.user.twitter_oauth_token = None
|
||||
stream.rule.user.twitter_oauth_token_secret = None
|
||||
stream.rule.user.save()
|
||||
|
||||
message = _(
|
||||
"Your Twitter account credentials have expired. Re-authenticate in"
|
||||
" the settings page to keep retrieving Twitter specific information"
|
||||
" from your account."
|
||||
)
|
||||
message = _(
|
||||
"Your Twitter account credentials have expired. Re-authenticate in"
|
||||
" the settings page to keep retrieving Twitter specific information"
|
||||
" from your account."
|
||||
)
|
||||
|
||||
send_mail(
|
||||
"Twitter account needs re-authentication",
|
||||
message,
|
||||
None,
|
||||
[stream.rule.user.email],
|
||||
)
|
||||
send_mail(
|
||||
"Twitter account needs re-authentication",
|
||||
message,
|
||||
None,
|
||||
[stream.rule.user.email],
|
||||
)
|
||||
|
||||
continue
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@ from django.views.generic.edit import CreateView, FormView, UpdateView
|
|||
from django_celery_beat.models import IntervalSchedule
|
||||
|
||||
from newsreader.news.collection.choices import RuleTypeChoices
|
||||
from newsreader.news.collection.forms import (
|
||||
CollectionRuleBulkForm,
|
||||
FeedForm,
|
||||
OPMLImportForm,
|
||||
)
|
||||
from newsreader.news.collection.forms import FeedForm, OPMLImportForm
|
||||
from newsreader.news.collection.models import CollectionRule
|
||||
from newsreader.news.collection.views.base import (
|
||||
CollectionRuleDetailMixin,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ from django.views.generic.edit import FormView
|
|||
from django.views.generic.list import ListView
|
||||
|
||||
from newsreader.news.collection.forms import CollectionRuleBulkForm
|
||||
from newsreader.news.collection.views.base import (
|
||||
CollectionRuleDetailMixin,
|
||||
CollectionRuleViewMixin,
|
||||
)
|
||||
from newsreader.news.collection.views.base import CollectionRuleViewMixin
|
||||
|
||||
|
||||
class CollectionRuleListView(CollectionRuleViewMixin, ListView):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ from rest_framework import status
|
|||
from rest_framework.generics import (
|
||||
GenericAPIView,
|
||||
ListAPIView,
|
||||
ListCreateAPIView,
|
||||
RetrieveUpdateAPIView,
|
||||
RetrieveUpdateDestroyAPIView,
|
||||
get_object_or_404,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0002_auto_20190714_1425")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("core", "0003_post_read"),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("core", "0004_auto_20191116_1315"),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0005_auto_20200412_1955")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0006_auto_20200524_1218")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0007_auto_20200706_2312")]
|
||||
|
||||
operations = [
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from newsreader.news.core.views import (
|
|||
CategoryCreateView,
|
||||
CategoryListView,
|
||||
CategoryUpdateView,
|
||||
NewsView,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,3 +26,4 @@
|
|||
@import './post-message/index';
|
||||
@import './posts/index';
|
||||
@import './posts-info/index';
|
||||
@import './scroll-to-top/index';
|
||||
|
|
|
|||
|
|
@ -5,4 +5,6 @@
|
|||
padding: 0;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
z-index: 1000;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/newsreader/scss/components/scroll-to-top/index.scss
Normal file
1
src/newsreader/scss/components/scroll-to-top/index.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import './scroll-to-top';
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue