diff --git a/src/newsreader/accounts/templates/accounts/components/settings-form.html b/src/newsreader/accounts/templates/accounts/components/settings-form.html index 51d4450..f5e7065 100644 --- a/src/newsreader/accounts/templates/accounts/components/settings-form.html +++ b/src/newsreader/accounts/templates/accounts/components/settings-form.html @@ -4,14 +4,25 @@ {% block actions %}
+ {% include "components/form/confirm-button.html" %} + {% trans "Change password" %} - + + {% if favicon_task_allowed %} + + {% trans "Fetch favicons" %} + + {% else %} + + {% endif %} + + {% trans "Third party integrations" %} - - {% include "components/form/confirm-button.html" %}
{% endblock actions %} diff --git a/src/newsreader/accounts/templates/accounts/views/password-change.html b/src/newsreader/accounts/templates/accounts/views/password-change.html index fb8a98b..d6eb918 100644 --- a/src/newsreader/accounts/templates/accounts/views/password-change.html +++ b/src/newsreader/accounts/templates/accounts/views/password-change.html @@ -2,7 +2,7 @@ {% block content %}
- {% url 'accounts:settings' as cancel_url %} + {% url 'accounts:settings:home' as cancel_url %} {% include "components/form/form.html" with form=form title="Change password" confirm_text="Change password" cancel_url=cancel_url %}
{% endblock %} diff --git a/src/newsreader/accounts/templates/accounts/views/reddit.html b/src/newsreader/accounts/templates/accounts/views/reddit.html index 5d4f539..353ca72 100644 --- a/src/newsreader/accounts/templates/accounts/views/reddit.html +++ b/src/newsreader/accounts/templates/accounts/views/reddit.html @@ -13,7 +13,7 @@ {% endif %}

- {% trans "Return to integrations page" %} + {% trans "Return to integrations page" %}

diff --git a/src/newsreader/accounts/templates/accounts/views/twitter.html b/src/newsreader/accounts/templates/accounts/views/twitter.html index e2c51aa..6df1a97 100644 --- a/src/newsreader/accounts/templates/accounts/views/twitter.html +++ b/src/newsreader/accounts/templates/accounts/views/twitter.html @@ -13,7 +13,7 @@ {% endif %}

- {% trans "Return to integrations page" %} + {% trans "Return to integrations page" %}

diff --git a/src/newsreader/accounts/tests/test_favicon.py b/src/newsreader/accounts/tests/test_favicon.py new file mode 100644 index 0000000..d3eb56b --- /dev/null +++ b/src/newsreader/accounts/tests/test_favicon.py @@ -0,0 +1,37 @@ +from unittest.mock import patch + +from django.core.cache import cache +from django.test import TestCase +from django.urls import reverse + +from newsreader.accounts.tests.factories import UserFactory + + +class FaviconRedirectViewTestCase(TestCase): + def setUp(self): + self.user = UserFactory(email="test@test.nl", password="test") + self.client.force_login(self.user) + + self.patch = patch("newsreader.accounts.views.favicon.FaviconTask") + self.mocked_task = self.patch.start() + + def tearDown(self): + cache.clear() + + def test_simple(self): + response = self.client.get(reverse("accounts:settings:favicon")) + + self.assertRedirects(response, reverse("accounts:settings:home")) + + self.mocked_task.delay.assert_called_once_with(self.user.pk) + + self.assertEqual(1, cache.get(f"{self.user.email}-favicon-task")) + + def test_not_active(self): + cache.set(f"{self.user.email}-favicon-task", 1) + + response = self.client.get(reverse("accounts:settings:favicon")) + + self.assertRedirects(response, reverse("accounts:settings:home")) + + self.mocked_task.delay.assert_not_called() diff --git a/src/newsreader/accounts/tests/test_integrations.py b/src/newsreader/accounts/tests/test_integrations.py index cdc9546..fbee223 100644 --- a/src/newsreader/accounts/tests/test_integrations.py +++ b/src/newsreader/accounts/tests/test_integrations.py @@ -22,7 +22,7 @@ class IntegrationsViewTestCase(TestCase): self.user = UserFactory(email="test@test.nl", password="test") self.client.force_login(self.user) - self.url = reverse("accounts:integrations") + self.url = reverse("accounts:settings:integrations") class RedditIntegrationsTestCase(IntegrationsViewTestCase): @@ -69,7 +69,7 @@ class RedditTemplateViewTestCase(TestCase): self.user = UserFactory(email="test@test.nl", password="test") self.client.force_login(self.user) - self.base_url = reverse("accounts:reddit-template") + self.base_url = reverse("accounts:settings:reddit-template") self.state = str(uuid4()) self.patch = patch("newsreader.news.collection.reddit.post") @@ -190,9 +190,9 @@ class RedditTokenRedirectViewTestCase(TestCase): cache.clear() def test_simple(self): - response = self.client.get(reverse("accounts:reddit-refresh")) + response = self.client.get(reverse("accounts:settings:reddit-refresh")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.mocked_task.delay.assert_called_once_with(self.user.pk) @@ -201,9 +201,9 @@ class RedditTokenRedirectViewTestCase(TestCase): def test_not_active(self): cache.set(f"{self.user.email}-reddit-refresh", 1) - response = self.client.get(reverse("accounts:reddit-refresh")) + response = self.client.get(reverse("accounts:settings:reddit-refresh")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.mocked_task.delay.assert_not_called() @@ -223,9 +223,9 @@ class RedditRevokeRedirectViewTestCase(TestCase): self.mocked_revoke.return_value = True - response = self.client.get(reverse("accounts:reddit-revoke")) + response = self.client.get(reverse("accounts:settings:reddit-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.mocked_revoke.assert_called_once_with(self.user) @@ -238,9 +238,9 @@ class RedditRevokeRedirectViewTestCase(TestCase): self.user.reddit_refresh_token = None self.user.save() - response = self.client.get(reverse("accounts:reddit-revoke")) + response = self.client.get(reverse("accounts:settings:reddit-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.mocked_revoke.assert_not_called() @@ -251,9 +251,9 @@ class RedditRevokeRedirectViewTestCase(TestCase): self.mocked_revoke.return_value = False - response = self.client.get(reverse("accounts:reddit-revoke")) + response = self.client.get(reverse("accounts:settings:reddit-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.user.refresh_from_db() @@ -267,9 +267,9 @@ class RedditRevokeRedirectViewTestCase(TestCase): self.mocked_revoke.side_effect = StreamException - response = self.client.get(reverse("accounts:reddit-revoke")) + response = self.client.get(reverse("accounts:settings:reddit-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.user.refresh_from_db() @@ -293,9 +293,9 @@ class TwitterRevokeRedirectView(TestCase): self.user.twitter_oauth_token_secret = "jadajadajada" self.user.save() - response = self.client.get(reverse("accounts:twitter-revoke")) + response = self.client.get(reverse("accounts:settings:twitter-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.user.refresh_from_db() @@ -307,9 +307,9 @@ class TwitterRevokeRedirectView(TestCase): self.user.twitter_oauth_token_secret = None self.user.save() - response = self.client.get(reverse("accounts:twitter-revoke")) + response = self.client.get(reverse("accounts:settings:twitter-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.mocked_post.assert_not_called() @@ -320,9 +320,9 @@ class TwitterRevokeRedirectView(TestCase): self.mocked_post.side_effect = StreamException - response = self.client.get(reverse("accounts:twitter-revoke")) + response = self.client.get(reverse("accounts:settings:twitter-revoke")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) self.user.refresh_from_db() @@ -346,7 +346,7 @@ class TwitterAuthRedirectViewTestCase(TestCase): text="oauth_token=foo&oauth_token_secret=bar" ) - response = self.client.get(reverse("accounts:twitter-auth")) + response = self.client.get(reverse("accounts:settings:twitter-auth")) self.assertRedirects( response, @@ -363,9 +363,9 @@ class TwitterAuthRedirectViewTestCase(TestCase): def test_stream_exception(self): self.mocked_post.side_effect = StreamException - response = self.client.get(reverse("accounts:twitter-auth")) + response = self.client.get(reverse("accounts:settings:twitter-auth")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) cached_token = cache.get(f"twitter-{self.user.email}-token") cached_secret = cache.get(f"twitter-{self.user.email}-secret") @@ -376,9 +376,9 @@ class TwitterAuthRedirectViewTestCase(TestCase): def test_unexpected_contents(self): self.mocked_post.return_value = Mock(text="foo=bar&oauth_token_secret=bar") - response = self.client.get(reverse("accounts:twitter-auth")) + response = self.client.get(reverse("accounts:settings:twitter-auth")) - self.assertRedirects(response, reverse("accounts:integrations")) + self.assertRedirects(response, reverse("accounts:settings:integrations")) cached_token = cache.get(f"twitter-{self.user.email}-token") cached_secret = cache.get(f"twitter-{self.user.email}-secret") @@ -413,7 +413,7 @@ class TwitterTemplateViewTestCase(TestCase): ) response = self.client.get( - f"{reverse('accounts:twitter-template')}?{urlencode(params)}" + f"{reverse('accounts:settings:twitter-template')}?{urlencode(params)}" ) self.assertContains(response, _("Twitter account is linked")) @@ -430,7 +430,7 @@ class TwitterTemplateViewTestCase(TestCase): params = {"denied": "true", "oauth_token": "foo", "oauth_verifier": "barfoo"} response = self.client.get( - f"{reverse('accounts:twitter-template')}?{urlencode(params)}" + f"{reverse('accounts:settings:twitter-template')}?{urlencode(params)}" ) self.assertContains(response, _("Twitter authorization failed")) @@ -453,7 +453,7 @@ class TwitterTemplateViewTestCase(TestCase): params = {"denied": "", "oauth_token": "boo", "oauth_verifier": "barfoo"} response = self.client.get( - f"{reverse('accounts:twitter-template')}?{urlencode(params)}" + f"{reverse('accounts:settings:twitter-template')}?{urlencode(params)}" ) self.assertContains(response, _("OAuth tokens failed to match")) @@ -471,7 +471,7 @@ class TwitterTemplateViewTestCase(TestCase): params = {"denied": "", "oauth_token": "foo", "oauth_verifier": "barfoo"} response = self.client.get( - f"{reverse('accounts:twitter-template')}?{urlencode(params)}" + f"{reverse('accounts:settings:twitter-template')}?{urlencode(params)}" ) self.assertContains(response, _("No matching tokens found for this user")) @@ -495,7 +495,7 @@ class TwitterTemplateViewTestCase(TestCase): self.mocked_post.side_effect = StreamException response = self.client.get( - f"{reverse('accounts:twitter-template')}?{urlencode(params)}" + f"{reverse('accounts:settings:twitter-template')}?{urlencode(params)}" ) self.assertContains(response, _("Failed requesting access token")) @@ -523,7 +523,7 @@ class TwitterTemplateViewTestCase(TestCase): ) response = self.client.get( - f"{reverse('accounts:twitter-template')}?{urlencode(params)}" + f"{reverse('accounts:settings:twitter-template')}?{urlencode(params)}" ) self.assertContains(response, _("No credentials found in Twitter response")) diff --git a/src/newsreader/accounts/tests/test_settings.py b/src/newsreader/accounts/tests/test_settings.py index 42db736..5a12637 100644 --- a/src/newsreader/accounts/tests/test_settings.py +++ b/src/newsreader/accounts/tests/test_settings.py @@ -10,7 +10,7 @@ class SettingsViewTestCase(TestCase): self.user = UserFactory(email="test@test.nl", password="test") self.client.force_login(self.user) - self.url = reverse("accounts:settings") + self.url = reverse("accounts:settings:home") def test_simple(self): response = self.client.get(self.url) @@ -19,13 +19,13 @@ class SettingsViewTestCase(TestCase): def test_user_credential_change(self): response = self.client.post( - reverse("accounts:settings"), + reverse("accounts:settings:home"), {"first_name": "First name", "last_name": "Last name"}, ) user = User.objects.get() - self.assertRedirects(response, reverse("accounts:settings")) + self.assertRedirects(response, reverse("accounts:settings:home")) self.assertEquals(user.first_name, "First name") self.assertEquals(user.last_name, "Last name") diff --git a/src/newsreader/accounts/urls.py b/src/newsreader/accounts/urls.py index 3cdd1b1..0eaee5c 100644 --- a/src/newsreader/accounts/urls.py +++ b/src/newsreader/accounts/urls.py @@ -1,10 +1,11 @@ from django.contrib.auth.decorators import login_required -from django.urls import path +from django.urls import include, path from newsreader.accounts.views import ( ActivationCompleteView, ActivationResendView, ActivationView, + FaviconRedirectView, IntegrationsView, LoginView, LogoutView, @@ -26,6 +27,46 @@ from newsreader.accounts.views import ( ) +settings_patterns = [ + # Integrations + path( + "integrations/reddit/callback/", + login_required(RedditTemplateView.as_view()), + name="reddit-template", + ), + path( + "integrations/reddit/refresh/", + login_required(RedditTokenRedirectView.as_view()), + name="reddit-refresh", + ), + path( + "integrations/reddit/revoke/", + login_required(RedditRevokeRedirectView.as_view()), + name="reddit-revoke", + ), + path( + "integrations/twitter/auth/", + login_required(TwitterAuthRedirectView.as_view()), + name="twitter-auth", + ), + path( + "integrations/twitter/callback/", + login_required(TwitterTemplateView.as_view()), + name="twitter-template", + ), + path( + "integrations/twitter/revoke/", + login_required(TwitterRevokeRedirectView.as_view()), + name="twitter-revoke", + ), + path( + "integrations/", login_required(IntegrationsView.as_view()), name="integrations" + ), + # Misc + path("favicon/", login_required(FaviconRedirectView.as_view()), name="favicon"), + path("", login_required(SettingsView.as_view()), name="home"), +] + urlpatterns = [ # Auth path("login/", LoginView.as_view(), name="login"), @@ -70,42 +111,6 @@ urlpatterns = [ login_required(PasswordChangeView.as_view()), name="password-change", ), - # Integrations - path( - "settings/integrations/reddit/callback/", - login_required(RedditTemplateView.as_view()), - name="reddit-template", - ), - path( - "settings/integrations/reddit/refresh/", - login_required(RedditTokenRedirectView.as_view()), - name="reddit-refresh", - ), - path( - "settings/integrations/reddit/revoke/", - login_required(RedditRevokeRedirectView.as_view()), - name="reddit-revoke", - ), - path( - "settings/integrations/twitter/auth/", - login_required(TwitterAuthRedirectView.as_view()), - name="twitter-auth", - ), - path( - "settings/integrations/twitter/callback/", - login_required(TwitterTemplateView.as_view()), - name="twitter-template", - ), - path( - "settings/integrations/twitter/revoke/", - login_required(TwitterRevokeRedirectView.as_view()), - name="twitter-revoke", - ), - path( - "settings/integrations", - login_required(IntegrationsView.as_view()), - name="integrations", - ), # Settings - path("settings/", login_required(SettingsView.as_view()), name="settings"), + path("settings/", include((settings_patterns, "settings"))), ] diff --git a/src/newsreader/accounts/views/__init__.py b/src/newsreader/accounts/views/__init__.py index 81dd1fc..3be2b81 100644 --- a/src/newsreader/accounts/views/__init__.py +++ b/src/newsreader/accounts/views/__init__.py @@ -1,4 +1,5 @@ from newsreader.accounts.views.auth import LoginView, LogoutView +from newsreader.accounts.views.favicon import FaviconRedirectView from newsreader.accounts.views.integrations import ( IntegrationsView, RedditRevokeRedirectView, diff --git a/src/newsreader/accounts/views/favicon.py b/src/newsreader/accounts/views/favicon.py new file mode 100644 index 0000000..1b85399 --- /dev/null +++ b/src/newsreader/accounts/views/favicon.py @@ -0,0 +1,26 @@ +from django.contrib import messages +from django.core.cache import cache +from django.urls import reverse_lazy +from django.utils.translation import gettext as _ +from django.views.generic import RedirectView + +from newsreader.news.collection.tasks import FaviconTask + + +class FaviconRedirectView(RedirectView): + url = reverse_lazy("accounts:settings:home") + + def get(self, request, *args, **kwargs): + response = super().get(request, *args, **kwargs) + + user = request.user + task_active = cache.get(f"{user.email}-favicon-task") + + if not task_active: + FaviconTask.delay(user.pk) + messages.success(request, _("Favicons are being fetched")) + cache.set(f"{user.email}-favicon-task", 1, 18000) # 5 hours + return response + + messages.error(request, _("Limit reached, try again later")) + return response diff --git a/src/newsreader/accounts/views/integrations.py b/src/newsreader/accounts/views/integrations.py index 62d71fc..e6ed605 100644 --- a/src/newsreader/accounts/views/integrations.py +++ b/src/newsreader/accounts/views/integrations.py @@ -53,7 +53,7 @@ class IntegrationsView(TemplateView): and not user.reddit_access_token and not reddit_task_active ): - reddit_refresh_url = reverse_lazy("accounts:reddit-refresh") + reddit_refresh_url = reverse_lazy("accounts:settings:reddit-refresh") if not user.reddit_refresh_token: reddit_authorization_url = get_reddit_authorization_url(user) @@ -62,7 +62,7 @@ class IntegrationsView(TemplateView): "reddit_authorization_url": reddit_authorization_url, "reddit_refresh_url": reddit_refresh_url, "reddit_revoke_url": ( - reverse_lazy("accounts:reddit-revoke") + reverse_lazy("accounts:settings:reddit-revoke") if not reddit_authorization_url else None ), @@ -72,10 +72,10 @@ class IntegrationsView(TemplateView): twitter_revoke_url = None if self.request.user.has_twitter_auth: - twitter_revoke_url = reverse_lazy("accounts:twitter-revoke") + twitter_revoke_url = reverse_lazy("accounts:settings:twitter-revoke") return { - "twitter_auth_url": reverse_lazy("accounts:twitter-auth"), + "twitter_auth_url": reverse_lazy("accounts:settings:twitter-auth"), "twitter_revoke_url": twitter_revoke_url, } @@ -130,7 +130,7 @@ class RedditTemplateView(TemplateView): class RedditTokenRedirectView(RedirectView): - url = reverse_lazy("accounts:integrations") + url = reverse_lazy("accounts:settings:integrations") def get(self, request, *args, **kwargs): response = super().get(request, *args, **kwargs) @@ -149,7 +149,7 @@ class RedditTokenRedirectView(RedirectView): class RedditRevokeRedirectView(RedirectView): - url = reverse_lazy("accounts:integrations") + url = reverse_lazy("accounts:settings:integrations") def get(self, request, *args, **kwargs): response = super().get(request, *args, **kwargs) @@ -181,7 +181,7 @@ class RedditRevokeRedirectView(RedirectView): class TwitterRevokeRedirectView(RedirectView): - url = reverse_lazy("accounts:integrations") + url = reverse_lazy("accounts:settings:integrations") def get(self, request, *args, **kwargs): if not request.user.has_twitter_auth: @@ -212,7 +212,7 @@ class TwitterRevokeRedirectView(RedirectView): class TwitterAuthRedirectView(RedirectView): - url = reverse_lazy("accounts:integrations") + url = reverse_lazy("accounts:settings:integrations") def get(self, request, *args, **kwargs): oauth = OAuth( diff --git a/src/newsreader/accounts/views/settings.py b/src/newsreader/accounts/views/settings.py index 1603252..aac24fb 100644 --- a/src/newsreader/accounts/views/settings.py +++ b/src/newsreader/accounts/views/settings.py @@ -1,3 +1,4 @@ +from django.core.cache import cache from django.urls import reverse_lazy from django.views.generic.edit import FormView, ModelFormMixin @@ -11,7 +12,7 @@ from newsreader.news.collection.reddit import ( class SettingsView(ModelFormMixin, FormView): template_name = "accounts/views/settings.html" - success_url = reverse_lazy("accounts:settings") + success_url = reverse_lazy("accounts:settings:home") form_class = UserSettingsForm model = User @@ -19,6 +20,14 @@ class SettingsView(ModelFormMixin, FormView): self.object = self.get_object() return super().get(request, *args, **kwargs) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + return { + **context, + "favicon_task_allowed": not cache.get(f"{self.request.user}-favicon-task"), + } + def get_object(self, **kwargs): return self.request.user diff --git a/src/newsreader/js/components/Messages.js b/src/newsreader/js/components/Messages.js index 843677c..150b003 100644 --- a/src/newsreader/js/components/Messages.js +++ b/src/newsreader/js/components/Messages.js @@ -22,7 +22,7 @@ class Messages extends React.Component { ); }); - return ; + return ; } } diff --git a/src/newsreader/news/collection/favicon.py b/src/newsreader/news/collection/favicon.py index 639e7f6..1ca21e6 100644 --- a/src/newsreader/news/collection/favicon.py +++ b/src/newsreader/news/collection/favicon.py @@ -126,7 +126,7 @@ class FaviconCollector(Collector): feed_client, favicon_client = (FeedClient, FaviconClient) url_builder, favicon_builder = (WebsiteURLBuilder, FaviconBuilder) - def collect(self, rules=None): + def collect(self, rules=[]): streams = [] with self.feed_client(rules=rules) as client: diff --git a/src/newsreader/news/collection/management/commands/collect.py b/src/newsreader/news/collection/management/commands/collect.py deleted file mode 100644 index 7d928f0..0000000 --- a/src/newsreader/news/collection/management/commands/collect.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.core.management.base import BaseCommand - -from newsreader.news.collection.feed import FeedCollector - - -class Command(BaseCommand): - help = "Collects Atom/RSS feeds" - - def handle(self, *args, **options): - collector = FeedCollector() - collector.collect() diff --git a/src/newsreader/news/collection/management/commands/fetch_favicons.py b/src/newsreader/news/collection/management/commands/fetch_favicons.py deleted file mode 100644 index 1ee96cf..0000000 --- a/src/newsreader/news/collection/management/commands/fetch_favicons.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.core.management.base import BaseCommand - -from newsreader.news.collection.favicon import FaviconCollector - - -class Command(BaseCommand): - help = "Fetch favicons for collection rules" - - def handle(self, *args, **options): - collector = FaviconCollector() - collector.collect() diff --git a/src/newsreader/news/collection/tasks.py b/src/newsreader/news/collection/tasks.py index 926b05b..b82bf66 100644 --- a/src/newsreader/news/collection/tasks.py +++ b/src/newsreader/news/collection/tasks.py @@ -147,7 +147,49 @@ class TwitterTimelineTask(app.Task): raise Reject(reason="Task already running", requeue=False) +class FaviconTask(app.Task): + name = "FaviconTask" + ignore_result = True + + def run(self, user_pk): + from newsreader.news.collection.favicon import FaviconCollector + + try: + user = User.objects.get(pk=user_pk) + except ObjectDoesNotExist: + message = f"User {user_pk} does not exist" + logger.exception(message) + + raise Reject(reason=message, requeue=False) + + with MemCacheLock("f{user.email}-favicon-task", self.app.oid) as acquired: + if acquired: + logger.info(f"Running favicon task for user {user_pk}") + + rules = user.rules.enabled().filter(type=RuleTypeChoices.feed) + + collector = FaviconCollector() + collector.collect(rules=rules) + + third_party_rules = user.rules.enabled().exclude( + type=RuleTypeChoices.feed + ) + + for rule in third_party_rules: + if rule.type == RuleTypeChoices.subreddit: + rule.favicon = "https://www.reddit.com/favicon.ico" + rule.save() + elif rule.type == RuleTypeChoices.twitter_timeline: + rule.favicon = "https://abs.twimg.com/favicons/favicon.ico" + rule.save() + else: + logger.warning(f"Cancelling task due to existing lock") + + raise Reject(reason="Task already running", requeue=False) + + FeedTask = app.register_task(FeedTask()) +FaviconTask = app.register_task(FaviconTask()) RedditTask = app.register_task(RedditTask()) RedditTokenTask = app.register_task(RedditTokenTask()) TwitterTimelineTask = app.register_task(TwitterTimelineTask()) diff --git a/src/newsreader/scss/components/messages/_messages.scss b/src/newsreader/scss/components/messages/_messages.scss index 74d88b5..b1ba9d0 100644 --- a/src/newsreader/scss/components/messages/_messages.scss +++ b/src/newsreader/scss/components/messages/_messages.scss @@ -3,12 +3,10 @@ flex-direction: column; align-items: center; - position: fixed; - top: 0; width: 100%; margin: 5px 0 20px 0; - color: $white; + color: $font-color; &__item { width: 80%; @@ -17,7 +15,7 @@ padding: 20px 15px; margin: 5px 0; - background-color: $blue; + background-color: $transparant-blue; &--error { background-color: $transparant-red; @@ -27,7 +25,6 @@ background-color: $transparant-orange; } - // TODO check this color &--success { background-color: $transparant-green; } @@ -39,4 +36,28 @@ --ggs: 2; } } + + &--fixed { + position: fixed; + top: 0; + } + + &--fixed &__item { + color: $white; + background-color: $blue; + } + + &--fixed &__item--error { + color: $white; + background-color: $red; + } + + &--fixed &__item--warning { + background-color: $orange; + } + + &--fixed &__item--success { + color: $white; + background-color: $green; + } } diff --git a/src/newsreader/templates/base.html b/src/newsreader/templates/base.html index 3f677c0..efaf9f2 100644 --- a/src/newsreader/templates/base.html +++ b/src/newsreader/templates/base.html @@ -17,7 +17,7 @@ - + {% if request.user.is_superuser %} {% endif %}