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

View file

@ -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:

View file

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

View file

@ -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")]

View file

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

View file

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

View file

@ -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")]

View file

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

View file

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

View file

@ -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)]

View file

@ -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"),

View file

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

View file

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

View file

@ -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")]

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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")

View file

@ -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 [],

View file

@ -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)

View file

@ -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)

View file

@ -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"

View file

@ -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

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');
}
};
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();

View file

@ -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 }]} />
)}

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) {
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();
}
}

View file

@ -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

View file

@ -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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)]

View file

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

View file

@ -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,

View file

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

View file

@ -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

View file

@ -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/",

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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,

View file

@ -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):

View file

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

View file

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

View file

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

View file

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

View file

@ -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"),

View file

@ -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"),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,4 +5,6 @@
padding: 0;
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