From ccde406193a53e83f0c09b04fecd3bec6e0b77d8 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 6 Oct 2024 21:23:42 +0200 Subject: [PATCH 01/39] Update CI after branch changes --- gitlab-ci/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitlab-ci/lint.yml b/gitlab-ci/lint.yml index 5f36469..6511f9a 100644 --- a/gitlab-ci/lint.yml +++ b/gitlab-ci/lint.yml @@ -9,7 +9,7 @@ python-linting: - ./.venv/bin/ruff format --check src/ only: refs: - - development + - main - merge_requests javascript-linting: @@ -21,5 +21,5 @@ javascript-linting: - npm run lint only: refs: - - development + - main - merge_requests From fa491120a03e09e4c0fc84d2ee3684b843a59297 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Mon, 7 Oct 2024 20:57:08 +0200 Subject: [PATCH 02/39] Use line-through to indicate read status --- src/newsreader/scss/components/post/_post.scss | 6 +----- src/newsreader/scss/components/posts/_posts.scss | 2 +- src/newsreader/scss/partials/_root.scss | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/newsreader/scss/components/post/_post.scss b/src/newsreader/scss/components/post/_post.scss index b7d5ef4..c8b96ee 100644 --- a/src/newsreader/scss/components/post/_post.scss +++ b/src/newsreader/scss/components/post/_post.scss @@ -85,7 +85,7 @@ font-size: map-deep-get($post, "header-font-size"); &--read { - color: var(--read-color); + text-decoration: line-through; } } @@ -196,10 +196,6 @@ background-color: var(--confirm-color); color: $white; width: 100px; - - &--saved { - color: var(--read-color); - } } } } diff --git a/src/newsreader/scss/components/posts/_posts.scss b/src/newsreader/scss/components/posts/_posts.scss index 9978e73..a8a89e6 100644 --- a/src/newsreader/scss/components/posts/_posts.scss +++ b/src/newsreader/scss/components/posts/_posts.scss @@ -63,7 +63,7 @@ font-size: map-deep-get($post, header-font-size); &--read { - color: var(--read-color); + text-decoration: line-through; } &:hover { diff --git a/src/newsreader/scss/partials/_root.scss b/src/newsreader/scss/partials/_root.scss index 4a85e8e..afc9617 100644 --- a/src/newsreader/scss/partials/_root.scss +++ b/src/newsreader/scss/partials/_root.scss @@ -5,7 +5,6 @@ --font-color: #{$font-color}; --link-color: #{$link-color}; --selected-color: #{$selected-color}; - --read-color: #{$read-color}; --confirm-color: #{$confirm-color}; --confirm-font-color: #{$confirm-font-color}; @@ -27,7 +26,6 @@ --font-color: #{$dark-font-color}; --link-color: #{$dark-link-color}; - --read-color: #{$dark-read-color}; --confirm-color: #{$dark-confirm-color}; --confirm-font-color: #{$dark-confirm-font-color}; From bb92f07f003fb8e90776a196684bf89e4fe1e50b Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Mon, 7 Oct 2024 21:00:03 +0200 Subject: [PATCH 03/39] Use full screen height for mobile post layout --- src/newsreader/scss/components/post/_post.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/newsreader/scss/components/post/_post.scss b/src/newsreader/scss/components/post/_post.scss index c8b96ee..200d63f 100644 --- a/src/newsreader/scss/components/post/_post.scss +++ b/src/newsreader/scss/components/post/_post.scss @@ -17,7 +17,7 @@ @media (max-width: $mobile-breakpoint) { width: 100%; - height: 80%; + height: 100%; margin: 0; } From 174912a967a70ad74989a7e1b3d3ee4c8d95978d Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Mon, 7 Oct 2024 21:02:03 +0200 Subject: [PATCH 04/39] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a63cefa..5e4e650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.5.1 + +- Use line-through styling for read posts +- Use full height for post layout + ## 0.5.0 - Upgrade python to 3.11 From 89d4ebdc493f45d5e6d41dad05a19e07fb5049f4 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Mon, 7 Oct 2024 21:42:20 +0200 Subject: [PATCH 05/39] Add missing `VERSION` environment variable --- docker-compose.yml | 1 + src/newsreader/conf/version.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 55f7ad0..9afa9a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ x-django-build-env: &django-build-env x-django-env: &django-env <<: *django-build-env + VERSION: # Email EMAIL_HOST: diff --git a/src/newsreader/conf/version.py b/src/newsreader/conf/version.py index 873c45b..806adef 100644 --- a/src/newsreader/conf/version.py +++ b/src/newsreader/conf/version.py @@ -3,8 +3,10 @@ import subprocess def get_current_version(debug=True): - if "VERSION" in os.environ: - return os.environ["VERSION"] + version = os.environ.get("VERSION") + + if version: + return version if debug: try: From 2d5801f226eafb176dcc5d509997af89cbe473ad Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Mon, 7 Oct 2024 21:43:52 +0200 Subject: [PATCH 06/39] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e4e650..76c9b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.5.2 + +- Add missing `VERSION` environment variable + ## 0.5.1 - Use line-through styling for read posts From e33497569a292ae2cd916a7f2425383f750ff8b9 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 10:16:57 +0200 Subject: [PATCH 07/39] Apply query optimizations for posts --- .../js/pages/homepage/actions/posts.js | 4 +-- .../js/tests/homepage/actions/post.test.js | 14 ++++----- .../js/tests/homepage/reducers/post.test.js | 2 +- .../tests/homepage/reducers/selected.test.js | 4 +-- src/newsreader/news/collection/endpoints.py | 7 +++-- .../tests/endpoints/rule/list/tests.py | 23 +------------- src/newsreader/news/core/endpoints.py | 23 +++++++------- src/newsreader/news/core/filters.py | 27 ----------------- .../tests/endpoints/category/list/tests.py | 30 ++----------------- .../core/tests/endpoints/post/list/tests.py | 10 +++---- 10 files changed, 37 insertions(+), 107 deletions(-) diff --git a/src/newsreader/js/pages/homepage/actions/posts.js b/src/newsreader/js/pages/homepage/actions/posts.js index 6a0cd7a..68ebe32 100644 --- a/src/newsreader/js/pages/homepage/actions/posts.js +++ b/src/newsreader/js/pages/homepage/actions/posts.js @@ -124,10 +124,10 @@ export const fetchPostsBySection = (section, next = false) => { switch (section.type) { case RULE_TYPE: - url = next ? next : `/api/rules/${section.id}/posts/?read=false`; + url = next ? next : `/api/rules/${section.id}/posts/`; break; case CATEGORY_TYPE: - url = next ? next : `/api/categories/${section.id}/posts/?read=false`; + url = next ? next : `/api/categories/${section.id}/posts/`; break; } diff --git a/src/newsreader/js/tests/homepage/actions/post.test.js b/src/newsreader/js/tests/homepage/actions/post.test.js index d30e549..6fd5d3d 100644 --- a/src/newsreader/js/tests/homepage/actions/post.test.js +++ b/src/newsreader/js/tests/homepage/actions/post.test.js @@ -304,10 +304,10 @@ describe('post actions', () => { type: constants.RULE_TYPE, }; - fetchMock.getOnce('/api/rules/4/posts/?read=false', { + fetchMock.getOnce('/api/rules/4/posts/', { body: { count: 2, - next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar', previous: null, results: posts, }, @@ -325,7 +325,7 @@ describe('post actions', () => { { type: actions.REQUEST_POSTS }, { type: actions.RECEIVE_POSTS, - next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/rules/4/posts/?cursor=jabadabar', posts, }, ]; @@ -373,10 +373,10 @@ describe('post actions', () => { type: constants.CATEGORY_TYPE, }; - fetchMock.getOnce('/api/categories/1/posts/?read=false', { + fetchMock.getOnce('/api/categories/1/posts/', { body: { count: 2, - next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar', previous: null, results: posts, }, @@ -394,7 +394,7 @@ describe('post actions', () => { { type: actions.REQUEST_POSTS }, { type: actions.RECEIVE_POSTS, - next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar&read=false', + next: 'https://durp.com/api/categories/4/posts/?cursor=jabadabar', posts, }, ]; @@ -600,7 +600,7 @@ describe('post actions', () => { const errorMessage = 'Page not found'; - fetchMock.getOnce(`/api/rules/${rule.id}/posts/?read=false`, () => { + fetchMock.getOnce(`/api/rules/${rule.id}/posts/`, () => { throw new Error(errorMessage); }); diff --git a/src/newsreader/js/tests/homepage/reducers/post.test.js b/src/newsreader/js/tests/homepage/reducers/post.test.js index 249a9f1..59f0e36 100644 --- a/src/newsreader/js/tests/homepage/reducers/post.test.js +++ b/src/newsreader/js/tests/homepage/reducers/post.test.js @@ -81,7 +81,7 @@ describe('post actions', () => { const action = { type: actions.RECEIVE_POSTS, - next: 'https://durp.com/api/rules/4/posts/?page=2&read=false', + next: 'https://durp.com/api/rules/4/posts/?page=2', posts, }; diff --git a/src/newsreader/js/tests/homepage/reducers/selected.test.js b/src/newsreader/js/tests/homepage/reducers/selected.test.js index 40561a3..0d1a7ae 100644 --- a/src/newsreader/js/tests/homepage/reducers/selected.test.js +++ b/src/newsreader/js/tests/homepage/reducers/selected.test.js @@ -254,13 +254,13 @@ describe('selected reducer', () => { const action = { type: postActions.RECEIVE_POSTS, - next: 'https://durp.com/api/rules/4/posts/?page=2&read=false', + next: 'https://durp.com/api/rules/4/posts/?page=2', posts, }; const expectedState = { ...defaultState, - next: 'https://durp.com/api/rules/4/posts/?page=2&read=false', + next: 'https://durp.com/api/rules/4/posts/?page=2', lastReached: false, }; diff --git a/src/newsreader/news/collection/endpoints.py b/src/newsreader/news/collection/endpoints.py index 1b11d31..24918c9 100644 --- a/src/newsreader/news/collection/endpoints.py +++ b/src/newsreader/news/collection/endpoints.py @@ -1,3 +1,4 @@ +from django.db.models import Prefetch from rest_framework import status from rest_framework.generics import ( GenericAPIView, @@ -10,7 +11,6 @@ from rest_framework.response import Response 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 from newsreader.news.core.models import Post from newsreader.news.core.serializers import PostSerializer @@ -24,7 +24,6 @@ class NestedRuleView(ListAPIView): queryset = CollectionRule.objects.prefetch_related("posts").all() serializer_class = PostSerializer pagination_class = CursorPagination - filter_backends = [ReadFilter] def get_queryset(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field @@ -33,7 +32,9 @@ class NestedRuleView(ListAPIView): # filtered on the user. filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - rule = get_object_or_404(self.queryset, **filter_kwargs) + prefetch = Prefetch("posts", queryset=Post.objects.filter(read=False)) + queryset = CollectionRule.objects.prefetch_related(prefetch) + rule = get_object_or_404(queryset, **filter_kwargs) self.check_object_permissions(self.request, rule) return rule.posts.order_by("-publication_date") diff --git a/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py b/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py index 51334b5..24d4b9a 100644 --- a/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py +++ b/src/newsreader/news/collection/tests/endpoints/rule/list/tests.py @@ -202,7 +202,7 @@ class NestedRuleListViewTestCase(TestCase): with self.subTest(post=post): self.assertEqual(post["rule"]["id"], rule.pk) - def test_unread_posts(self): + def test_posts(self): rule = FeedFactory.create(user=self.user) FeedPostFactory.create_batch(size=10, rule=rule, read=False) @@ -210,7 +210,6 @@ class NestedRuleListViewTestCase(TestCase): response = self.client.get( reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}), - {"read": "false"}, ) data = response.json() @@ -221,23 +220,3 @@ class NestedRuleListViewTestCase(TestCase): for post in data["results"]: with self.subTest(post=post): self.assertEqual(post["read"], False) - - def test_read_posts(self): - rule = FeedFactory.create(user=self.user) - - FeedPostFactory.create_batch(size=20, rule=rule, read=False) - FeedPostFactory.create_batch(size=10, rule=rule, read=True) - - response = self.client.get( - reverse("api:news:collection:rules-nested-posts", kwargs={"pk": rule.pk}), - {"read": "true"}, - ) - - data = response.json() - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(data["results"]), 10) - - for post in data["results"]: - with self.subTest(post=post): - self.assertEqual(post["read"], True) diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index 184515b..6ed9f39 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -1,3 +1,4 @@ +from django.db.models import Prefetch from rest_framework import status from rest_framework.generics import ( GenericAPIView, @@ -11,18 +12,19 @@ from rest_framework.response import Response from newsreader.accounts.permissions import IsPostOwner 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, SavedFilter +from newsreader.news.core.filters import SavedFilter from newsreader.news.core.models import Category, Post from newsreader.news.core.serializers import CategorySerializer, PostSerializer class ListPostView(ListAPIView): - queryset = Post.objects.all() + queryset = Post.objects.filter(read=False) serializer_class = PostSerializer permission_classes = (IsAuthenticated, IsPostOwner) pagination_class = CursorPagination - filter_backends = [ReadFilter, SavedFilter] + filter_backends = [SavedFilter] class DetailPostView(RetrieveUpdateAPIView): @@ -63,10 +65,8 @@ class NestedRuleCategoryView(ListAPIView): class NestedPostCategoryView(ListAPIView): - queryset = Category.objects.prefetch_related("rules", "rules__posts").all() serializer_class = PostSerializer pagination_class = CursorPagination - filter_backends = [ReadFilter] def get_queryset(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field @@ -75,13 +75,16 @@ class NestedPostCategoryView(ListAPIView): # filtered on the user. filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - category = get_object_or_404(self.queryset, **filter_kwargs) + rules_queryset = CollectionRule.objects.filter(user=self.request.user) + prefetch = Prefetch("rules", queryset=rules_queryset, to_attr="user_rules") + category_queryset = Category.objects.prefetch_related(prefetch).filter( + user=self.request.user + ) + category = get_object_or_404(category_queryset, **filter_kwargs) + self.check_object_permissions(self.request, category) - rules = category.rules.values_list("id", flat=True) - queryset = Post.objects.filter(rule__in=rules) - - return queryset + return Post.objects.filter(rule__in=category.user_rules, read=False) class CategoryReadView(GenericAPIView): diff --git a/src/newsreader/news/core/filters.py b/src/newsreader/news/core/filters.py index 05f9157..9883304 100644 --- a/src/newsreader/news/core/filters.py +++ b/src/newsreader/news/core/filters.py @@ -4,33 +4,6 @@ from rest_framework import filters from rest_framework.compat import coreapi, coreschema -class ReadFilter(filters.BaseFilterBackend): - query_param = "read" - - def filter_queryset(self, request, queryset, view): - key = request.query_params.get(self.query_param, None) - available_values = {"True": True, "true": True, "False": False, "false": False} - - if not key or key not in available_values.keys(): - return queryset - - value = available_values[key] - return queryset.filter(read=value) - - def get_schema_fields(self, view): - return [ - coreapi.Field( - name=self.query_param, - required=False, - location="query", - schema=coreschema.String( - title=str(self.query_param), - description=str(_("Wether posts should be read or not")), - ), - ) - ] - - class SavedFilter(filters.BaseFilterBackend): query_param = "saved" diff --git a/src/newsreader/news/core/tests/endpoints/category/list/tests.py b/src/newsreader/news/core/tests/endpoints/category/list/tests.py index 34060ea..610dc58 100644 --- a/src/newsreader/news/core/tests/endpoints/category/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/category/list/tests.py @@ -409,7 +409,7 @@ class NestedCategoryPostView(TestCase): reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) ) - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 404) def test_ordering(self): category = CategoryFactory.create(user=self.user) @@ -503,9 +503,9 @@ class NestedCategoryPostView(TestCase): self.assertEqual(posts[0]["rule"]["id"], guardian_rule.pk) self.assertEqual(posts[1]["rule"]["id"], guardian_rule.pk) - def test_unread_posts(self): + def test_posts(self): category = CategoryFactory.create(user=self.user) - rule = FeedFactory(category=category) + rule = FeedFactory(category=category, user=self.user) FeedPostFactory.create_batch(size=10, rule=rule, read=False) FeedPostFactory.create_batch(size=10, rule=rule, read=True) @@ -514,7 +514,6 @@ class NestedCategoryPostView(TestCase): reverse( "api:news:core:categories-nested-posts", kwargs={"pk": category.pk} ), - {"read": "false"}, ) data = response.json() @@ -525,26 +524,3 @@ class NestedCategoryPostView(TestCase): for post in posts: self.assertEqual(post["read"], False) - - def test_read_posts(self): - category = CategoryFactory.create(user=self.user) - rule = FeedFactory(category=category) - - FeedPostFactory.create_batch(size=20, rule=rule, read=False) - FeedPostFactory.create_batch(size=10, rule=rule, read=True) - - response = self.client.get( - reverse( - "api:news:core:categories-nested-posts", kwargs={"pk": category.pk} - ), - {"read": "true"}, - ) - - data = response.json() - posts = data["results"] - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(data["results"]), 10) - - for post in posts: - self.assertEqual(post["read"], True) diff --git a/src/newsreader/news/core/tests/endpoints/post/list/tests.py b/src/newsreader/news/core/tests/endpoints/post/list/tests.py index f88e575..467fd05 100644 --- a/src/newsreader/news/core/tests/endpoints/post/list/tests.py +++ b/src/newsreader/news/core/tests/endpoints/post/list/tests.py @@ -53,25 +53,23 @@ class PostListViewTestCase(TestCase): with self.subTest(post=post): self.assertEqual(data["results"][index]["id"], post.pk) - def test_read_posts(self): + def test_posts(self): rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) FeedPostFactory.create_batch(size=20, rule=rule, read=False) FeedPostFactory.create_batch(size=10, rule=rule, read=True) - response = self.client.get( - reverse("api:news:core:posts-list"), {"read": "true"} - ) + response = self.client.get(reverse("api:news:core:posts-list")) data = response.json() posts = data["results"] self.assertEqual(response.status_code, 200) - self.assertEqual(len(data["results"]), 10) + self.assertEqual(len(data["results"]), 20) for post in posts: with self.subTest(post=post): - self.assertEqual(post["read"], True) + self.assertEqual(post["read"], False) def test_saved_posts(self): rule = FeedFactory(user=self.user, category=CategoryFactory(user=self.user)) From 91a7f6325c6ff91aa104ba7338318817d1e13e4c Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 12:49:55 +0200 Subject: [PATCH 08/39] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c9b44..b1341d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.5.3 + +- Apply query optimizations for retrieving posts + ## 0.5.2 - Add missing `VERSION` environment variable From bf43603d6509dad4bbded42ed6f03a675489dbb9 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 13 Oct 2024 12:52:06 +0200 Subject: [PATCH 09/39] Update versions --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 128b311..3b251da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "newsreader", - "version": "0.4.4", + "version": "0.5.3", "description": "Application for viewing RSS feeds", "main": "index.js", "scripts": { diff --git a/pyproject.toml b/pyproject.toml index d937a6f..ad04c68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = 'newsreader' -version = '0.4.4' +version = '0.5.3' authors = [{name = 'Sonny', email= 'sonnyba871@gmail.com'}] license = {text = 'GPL-3.0'} requires-python = '>=3.11' From aff565862ca47a1000685e9e615bdcb83dc2bee4 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Thu, 26 Dec 2024 20:20:21 +0100 Subject: [PATCH 10/39] Add woodpecker CI configuration --- .gitlab-ci.yml | 29 ------------------- .woodpecker/build.yaml | 8 ++++++ .woodpecker/lint.yaml | 19 +++++++++++++ .woodpecker/tests.yaml | 36 ++++++++++++++++++++++++ gitlab-ci/build.yml | 7 ----- gitlab-ci/lint.yml | 25 ---------------- gitlab-ci/release.yml | 12 -------- gitlab-ci/test.yml | 20 ------------- src/newsreader/conf/{gitlab.py => ci.py} | 2 +- 9 files changed, 64 insertions(+), 94 deletions(-) delete mode 100644 .gitlab-ci.yml create mode 100644 .woodpecker/build.yaml create mode 100644 .woodpecker/lint.yaml create mode 100644 .woodpecker/tests.yaml delete mode 100644 gitlab-ci/build.yml delete mode 100644 gitlab-ci/lint.yml delete mode 100644 gitlab-ci/release.yml delete mode 100644 gitlab-ci/test.yml rename src/newsreader/conf/{gitlab.py => ci.py} (97%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index bf64808..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,29 +0,0 @@ -stages: - - build - - test - - lint - - release - -variables: - UV_CACHE_DIR: "$CI_PROJECT_DIR/.cache/uv" - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - DJANGO_SETTINGS_MODULE: "newsreader.conf.gitlab" - POSTGRES_HOST: "$POSTGRES_HOST" - POSTGRES_DB: "$POSTGRES_NAME" - POSTGRES_NAME: "$POSTGRES_NAME" - POSTGRES_USER: "$POSTGRES_USER" - POSTGRES_PASSWORD: "$POSTGRES_PASSWORD" - -cache: - key: "$CI_COMMIT_REF_SLUG" - paths: - - .cache/pip - - .cache/uv - - node_modules/ - - .venv/ - -include: - - local: '/gitlab-ci/build.yml' - - local: '/gitlab-ci/test.yml' - - local: '/gitlab-ci/lint.yml' - - local: '/gitlab-ci/release.yml' diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml new file mode 100644 index 0000000..ba795b4 --- /dev/null +++ b/.woodpecker/build.yaml @@ -0,0 +1,8 @@ +when: + - event: push + +steps: + - image: node:lts + commands: + - npm install + - npm run build diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml new file mode 100644 index 0000000..1bb119e --- /dev/null +++ b/.woodpecker/lint.yaml @@ -0,0 +1,19 @@ +when: + - event: push + branch: main + - event: pull_request + +steps: + - name: python linting + image: python:3.11 + commands: + - pip install uv + - uv sync --extra testing --extra ci + - ./.venv/bin/ruff check src/ + - ./.venv/bin/ruff format --check src/ + + - name: javascript linting + image: node:lts + commands: + - npm install + - npm run lint diff --git a/.woodpecker/tests.yaml b/.woodpecker/tests.yaml new file mode 100644 index 0000000..09649aa --- /dev/null +++ b/.woodpecker/tests.yaml @@ -0,0 +1,36 @@ +when: + - event: push + +services: + - name: postgres + image: postgres:15 + environment: + POSTGRES_NAME: newsreader + POSTGRES_USER: newsreader + POSTGRES_PASSWORD: sekrit + - name: memcached + image: memcached:1.5.22 + +steps: + - name: python tests + image: python:3.11 + environment: + DJANGO_SETTINGS_MODULE: "newsreader.conf.ci" + DJANGO_SECRET_KEY: sekrit + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_DB: newsreader + POSTGRES_NAME: newsreader + POSTGRES_USER: newsreader + POSTGRES_PASSWORD: sekrit + commands: + - pip install uv + - uv sync --extra testing --extra ci + - ./.venv/bin/coverage run ./src/manage.py test newsreader + - ./.venv/bin/coverage report --show-missing + + - name: javascript tests + image: node:lts + commands: + - npm install + - npm test diff --git a/gitlab-ci/build.yml b/gitlab-ci/build.yml deleted file mode 100644 index 5a86f07..0000000 --- a/gitlab-ci/build.yml +++ /dev/null @@ -1,7 +0,0 @@ -static: - stage: build - image: node:lts - before_script: - - npm install - script: - - npm run build diff --git a/gitlab-ci/lint.yml b/gitlab-ci/lint.yml deleted file mode 100644 index 6511f9a..0000000 --- a/gitlab-ci/lint.yml +++ /dev/null @@ -1,25 +0,0 @@ -python-linting: - stage: lint - image: python:3.11 - before_script: - - pip install uv - - uv sync --extra testing --extra ci - script: - - ./.venv/bin/ruff check src/ - - ./.venv/bin/ruff format --check src/ - only: - refs: - - main - - merge_requests - -javascript-linting: - stage: lint - image: node:lts - before_script: - - npm install - script: - - npm run lint - only: - refs: - - main - - merge_requests diff --git a/gitlab-ci/release.yml b/gitlab-ci/release.yml deleted file mode 100644 index 571be3c..0000000 --- a/gitlab-ci/release.yml +++ /dev/null @@ -1,12 +0,0 @@ -release: - stage: release - image: registry.gitlab.com/gitlab-org/release-cli:latest - rules: - - if: $CI_COMMIT_TAG - script: - - echo 'running release job' - release: - name: 'Release $CI_COMMIT_TAG' - description: './CHANGELOG.md' - tag_name: '$CI_COMMIT_TAG' - ref: '$CI_COMMIT_TAG' diff --git a/gitlab-ci/test.yml b/gitlab-ci/test.yml deleted file mode 100644 index 69bc93f..0000000 --- a/gitlab-ci/test.yml +++ /dev/null @@ -1,20 +0,0 @@ -python-tests: - stage: test - coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/' - services: - - postgres:15 - - memcached:1.5.22 - image: python:3.11 - before_script: - - pip install uv - - uv sync --extra testing --extra ci - script: - - ./.venv/bin/coverage run ./src/manage.py test newsreader - -javascript-tests: - stage: test - image: node:lts - before_script: - - npm install - script: - - npm test diff --git a/src/newsreader/conf/gitlab.py b/src/newsreader/conf/ci.py similarity index 97% rename from src/newsreader/conf/gitlab.py rename to src/newsreader/conf/ci.py index e08556d..40c4a2f 100644 --- a/src/newsreader/conf/gitlab.py +++ b/src/newsreader/conf/ci.py @@ -33,7 +33,7 @@ CACHES = { # Project settings VERSION = get_current_version() -ENVIRONMENT = "gitlab" +ENVIRONMENT = "ci" try: # Optionally use sentry integration From f3ba0f1d091db6a4c8b58e91562037128ed6f2d0 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 16:19:15 +0100 Subject: [PATCH 11/39] Update ruff & uv usage --- .woodpecker/lint.yaml | 2 +- .woodpecker/tests.yaml | 2 +- docker/django | 6 ++-- pyproject.toml | 34 +++++++++++++------- uv.lock | 73 ++++++++++++++++++------------------------ 5 files changed, 58 insertions(+), 59 deletions(-) diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml index 1bb119e..bc25a32 100644 --- a/.woodpecker/lint.yaml +++ b/.woodpecker/lint.yaml @@ -8,7 +8,7 @@ steps: image: python:3.11 commands: - pip install uv - - uv sync --extra testing --extra ci + - uv sync --group ci - ./.venv/bin/ruff check src/ - ./.venv/bin/ruff format --check src/ diff --git a/.woodpecker/tests.yaml b/.woodpecker/tests.yaml index 09649aa..fed2254 100644 --- a/.woodpecker/tests.yaml +++ b/.woodpecker/tests.yaml @@ -25,7 +25,7 @@ steps: POSTGRES_PASSWORD: sekrit commands: - pip install uv - - uv sync --extra testing --extra ci + - uv sync --group ci - ./.venv/bin/coverage run ./src/manage.py test newsreader - ./.venv/bin/coverage report --show-missing diff --git a/docker/django b/docker/django index ab8821d..77f0c1d 100644 --- a/docker/django +++ b/docker/django @@ -15,7 +15,7 @@ RUN mkdir /app/media COPY uv.lock pyproject.toml /app/ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv -RUN uv sync --frozen --no-install-project +RUN uv sync --frozen --no-default-groups --no-install-project # stage 2 @@ -58,7 +58,7 @@ COPY --from=frontend-build /app/src/newsreader/static /app/src/newsreader/static COPY ./src /app/src -RUN uv sync --frozen --extra production +RUN uv sync --frozen --only-group production --extra sentry RUN useradd -M -u 1000 newsreader RUN chown -R newsreader:newsreader /app @@ -99,4 +99,4 @@ COPY ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh COPY --from=backend /app/src/ /app/src/ COPY --from=backend /bin/uv /bin/uv -RUN uv sync --frozen --extra testing --extra development +RUN uv sync --frozen --group development diff --git a/pyproject.toml b/pyproject.toml index ad04c68..fc497d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = 'newsreader' version = '0.5.3' -authors = [{name = 'Sonny', email= 'sonnyba871@gmail.com'}] +authors = [{ name = 'Sonny', email= 'sonny871@hotmail.com' }] license = {text = 'GPL-3.0'} requires-python = '>=3.11' dependencies = [ @@ -24,35 +24,45 @@ dependencies = [ 'lxml', ] -[project.optional-dependencies] -testing = [ - 'factory-boy', - 'freezegun', - 'tblib', - "ruff>=0.6.3", -] +[dependency-groups] +test-tools = ['ruff', 'factory_boy', 'freezegun'] development = [ 'django-debug-toolbar', 'django-stubs', 'django-extensions', ] ci = ['coverage~=7.6.1'] -production = ['gunicorn~=23.0', 'sentry-sdk~=2.0'] +production = ['gunicorn~=23.0'] + +[project.optional-dependencies] +sentry = ['sentry-sdk~=2.0'] [tool.uv] environments = ["sys_platform == 'linux'"] +default-groups = ['test-tools'] [tool.ruff] include = ['pyproject.toml', 'src/**/*.py'] line-length = 88 -[tool.ruff.lint.isort] -default-section = 'third-party' -known-first-party = ['newsreader'] +[tool.ruff.lint] +select = ['E4', 'E7', 'E9', 'F', 'I'] +[tool.ruff.lint.isort] lines-between-types=1 lines-after-imports=2 +default-section = 'third-party' +known-first-party = ['transip_client'] +section-order = [ + 'future', + 'standard-library', + 'django', + 'third-party', + 'first-party', + 'local-folder', +] + [tool.ruff.lint.isort.sections] django = ['django'] diff --git a/uv.lock b/uv.lock index fec9fbe..629a7b3 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.11" resolution-markers = [ "sys_platform == 'linux'", @@ -124,9 +125,6 @@ wheels = [ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows' and sys_platform == 'linux'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, @@ -169,15 +167,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, ] -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - [[package]] name = "coverage" version = "7.6.1" @@ -366,26 +355,26 @@ sdist = { url = "https://files.pythonhosted.org/packages/f4/87/647ce93053cb5e35e [[package]] name = "factory-boy" -version = "3.3.1" +version = "3.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "faker", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/3d/8070dde623341401b1c80156583d4c793058fe250450178218bb6e45526c/factory_boy-3.3.1.tar.gz", hash = "sha256:8317aa5289cdfc45f9cae570feb07a6177316c82e34d14df3c2e1f22f26abef0", size = 163924 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/98/75cacae9945f67cfe323829fc2ac451f64517a8a330b572a06a323997065/factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03", size = 164146 } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/cf/44ec67152f3129d0114c1499dd34f0a0a0faf43d9c2af05bc535746ca482/factory_boy-3.3.1-py2.py3-none-any.whl", hash = "sha256:7b1113c49736e1e9995bc2a18f4dbf2c52cf0f841103517010b1d825712ce3ca", size = 36878 }, + { url = "https://files.pythonhosted.org/packages/27/8d/2bc5f5546ff2ccb3f7de06742853483ab75bf74f36a92254702f8baecc79/factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc", size = 37036 }, ] [[package]] name = "faker" -version = "28.0.0" +version = "37.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil", marker = "sys_platform == 'linux'" }, + { name = "tzdata", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/d6/f1a81065124b23d1b56367570a4e00531005b4cca76a739b50ebf79a026d/faker-28.0.0.tar.gz", hash = "sha256:0d3c0399204aaf8205cc1750db443474ca0436f177126b2c27b798e8336cc74f", size = 1782120 } +sdist = { url = "https://files.pythonhosted.org/packages/37/62/80f15fe1b5abf3e5b09815178d7eb63a150fc7fcfebd5271ca4aab1d885a/faker-37.0.2.tar.gz", hash = "sha256:948bd27706478d3aa0b6f9f58b9f25207098f6ca79852c7b49c44a8ced2bc59b", size = 1875441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/09/74f194bc3f5fe1d88cd4a882d51669431f82b17327840bd7fdd7b516c97e/Faker-28.0.0-py3-none-any.whl", hash = "sha256:6a3a08be54c37e05f7943d7ba5211d252c1de737687a46ad6f29209d8d5db11f", size = 1820129 }, + { url = "https://files.pythonhosted.org/packages/a9/8b/b738d3d79ee4502ca966a2a4fa6833c11f50130127bdd57729e9b29c6d2f/faker-37.0.2-py3-none-any.whl", hash = "sha256:8955706c56c28099585e9e2b6f814eb0a3a227eb36a2ee3eb9ab577c4764eacc", size = 1918397 }, ] [[package]] @@ -507,7 +496,7 @@ wheels = [ [[package]] name = "newsreader" -version = "0.4.4" +version = "0.5.3" source = { virtual = "." } dependencies = [ { name = "beautifulsoup4", marker = "sys_platform == 'linux'" }, @@ -530,6 +519,11 @@ dependencies = [ ] [package.optional-dependencies] +sentry = [ + { name = "sentry-sdk", marker = "sys_platform == 'linux'" }, +] + +[package.dev-dependencies] ci = [ { name = "coverage", marker = "sys_platform == 'linux'" }, ] @@ -540,13 +534,11 @@ development = [ ] production = [ { name = "gunicorn", marker = "sys_platform == 'linux'" }, - { name = "sentry-sdk", marker = "sys_platform == 'linux'" }, ] -testing = [ +test-tools = [ { name = "factory-boy", marker = "sys_platform == 'linux'" }, { name = "freezegun", marker = "sys_platform == 'linux'" }, { name = "ruff", marker = "sys_platform == 'linux'" }, - { name = "tblib", marker = "sys_platform == 'linux'" }, ] [package.metadata] @@ -554,30 +546,36 @@ requires-dist = [ { name = "beautifulsoup4" }, { name = "bleach" }, { name = "celery", specifier = "~=5.4" }, - { name = "coverage", marker = "extra == 'ci'", specifier = "~=7.6.1" }, { name = "django", specifier = "~=4.2" }, { name = "django-axes" }, { name = "django-celery-beat", specifier = "~=2.7.0" }, - { name = "django-debug-toolbar", marker = "extra == 'development'" }, - { name = "django-extensions", marker = "extra == 'development'" }, { name = "django-registration-redux", specifier = "~=2.7" }, { name = "django-rest-framework" }, - { name = "django-stubs", marker = "extra == 'development'" }, { name = "djangorestframework-camel-case" }, - { name = "factory-boy", marker = "extra == 'testing'" }, { name = "feedparser" }, - { name = "freezegun", marker = "extra == 'testing'" }, { name = "ftfy", specifier = "~=6.2" }, - { name = "gunicorn", marker = "extra == 'production'", specifier = "~=23.0" }, { name = "lxml" }, { name = "psycopg" }, { name = "pymemcache" }, { name = "python-dotenv", specifier = "~=1.0.1" }, { name = "requests" }, { name = "requests-oauthlib" }, - { name = "ruff", marker = "extra == 'testing'", specifier = ">=0.6.3" }, - { name = "sentry-sdk", marker = "extra == 'production'", specifier = "~=2.0" }, - { name = "tblib", marker = "extra == 'testing'" }, + { name = "sentry-sdk", marker = "extra == 'sentry'", specifier = "~=2.0" }, +] +provides-extras = ["sentry"] + +[package.metadata.requires-dev] +ci = [{ name = "coverage", specifier = "~=7.6.1" }] +development = [ + { name = "django-debug-toolbar" }, + { name = "django-extensions" }, + { name = "django-stubs" }, +] +production = [{ name = "gunicorn", specifier = "~=23.0" }] +test-tools = [ + { name = "factory-boy" }, + { name = "freezegun" }, + { name = "ruff" }, ] [[package]] @@ -758,15 +756,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/a5/b2860373aa8de1e626b2bdfdd6df4355f0565b47e51f7d0c54fe70faf8fe/sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", size = 44156 }, ] -[[package]] -name = "tblib" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/df/4f2cd7eaa6d41a7994d46527349569d46e34d9cdd07590b5c5b0dcf53de3/tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6", size = 30616 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129", size = 12478 }, -] - [[package]] name = "types-pyyaml" version = "6.0.12.20240808" From 161234defd0d10e1420e25d0cb4e7445a43a047a Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 16:23:45 +0100 Subject: [PATCH 12/39] Bump rabbitmq version --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9afa9a4..44e9555 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,7 +52,7 @@ services: - postgres-data:/var/lib/postgresql/data rabbitmq: - image: rabbitmq:3.12 + image: rabbitmq:4 memcached: image: memcached:1.6 From ed37be0c6041d71a7527aefb90cc8b5366e8bd8d Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 16:24:33 +0100 Subject: [PATCH 13/39] Add celery healthcheck & update existing healthcheck --- docker-compose.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 44e9555..f29e719 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,6 +78,11 @@ services: --scheduler django -n worker1@%h -n worker2@%h + healthcheck: + test: celery --app newsreader status || exit 1 + interval: 10s + timeout: 10s + retries: 5 depends_on: rabbitmq: condition: service_started @@ -107,9 +112,9 @@ services: newsreader.wsgi:application healthcheck: test: /usr/bin/curl --fail http://django:8000 || exit 1 - interval: 30s + interval: 10s timeout: 10s - retries: 10 + retries: 5 depends_on: memcached: condition: service_started From 105371abafbbfb61e64b881a493a14cac40cb03e Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 16:25:03 +0100 Subject: [PATCH 14/39] Use long command options --- docker/django | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/django b/docker/django index 77f0c1d..6e079c8 100644 --- a/docker/django +++ b/docker/django @@ -60,8 +60,8 @@ COPY ./src /app/src RUN uv sync --frozen --only-group production --extra sentry -RUN useradd -M -u 1000 newsreader -RUN chown -R newsreader:newsreader /app +RUN useradd --no-create-home --uid 1000 newsreader +RUN chown --recursive newsreader:newsreader /app USER newsreader From 3160becb72710262562de62033a9d217580aadda Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 21:01:23 +0100 Subject: [PATCH 15/39] Remove django-registration-redux --- pyproject.toml | 1 - src/newsreader/accounts/tests/factories.py | 10 -- .../accounts/tests/test_activation.py | 99 ---------------- .../accounts/tests/test_registration.py | 110 ------------------ .../accounts/tests/test_resend_activation.py | 77 ------------ src/newsreader/accounts/urls.py | 24 ---- src/newsreader/accounts/views/__init__.py | 15 +-- src/newsreader/accounts/views/registration.py | 56 --------- src/newsreader/conf/base.py | 7 +- src/newsreader/conf/production.py | 2 - .../components/nav-list/nav-list.html | 19 ++- .../registration/activation_complete.html | 26 ----- .../registration/activation_email.html | 72 ------------ .../registration/activation_email.txt | 52 --------- .../registration/activation_email_subject.txt | 28 ----- .../registration/activation_failure.html | 22 ---- .../activation_resend_complete.html | 23 ---- .../registration/activation_resend_form.html | 12 -- .../registration/registration_closed.html | 13 --- .../registration/registration_complete.html | 22 ---- .../registration/registration_form.html | 13 --- src/newsreader/utils/views.py | 1 - uv.lock | 11 -- 23 files changed, 11 insertions(+), 704 deletions(-) delete mode 100644 src/newsreader/accounts/tests/test_activation.py delete mode 100644 src/newsreader/accounts/tests/test_registration.py delete mode 100644 src/newsreader/accounts/tests/test_resend_activation.py delete mode 100644 src/newsreader/accounts/views/registration.py delete mode 100755 src/newsreader/templates/registration/activation_complete.html delete mode 100644 src/newsreader/templates/registration/activation_email.html delete mode 100644 src/newsreader/templates/registration/activation_email.txt delete mode 100644 src/newsreader/templates/registration/activation_email_subject.txt delete mode 100644 src/newsreader/templates/registration/activation_failure.html delete mode 100644 src/newsreader/templates/registration/activation_resend_complete.html delete mode 100644 src/newsreader/templates/registration/activation_resend_form.html delete mode 100755 src/newsreader/templates/registration/registration_closed.html delete mode 100755 src/newsreader/templates/registration/registration_complete.html delete mode 100644 src/newsreader/templates/registration/registration_form.html diff --git a/pyproject.toml b/pyproject.toml index fc497d4..1db5f34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ dependencies = [ 'psycopg', 'django-axes', 'django-celery-beat~=2.7.0', - 'django-registration-redux~=2.7', 'django-rest-framework', 'djangorestframework-camel-case', 'pymemcache', diff --git a/src/newsreader/accounts/tests/factories.py b/src/newsreader/accounts/tests/factories.py index fc13d74..746db80 100644 --- a/src/newsreader/accounts/tests/factories.py +++ b/src/newsreader/accounts/tests/factories.py @@ -5,8 +5,6 @@ from django.utils.crypto import get_random_string import factory -from registration.models import RegistrationProfile - from newsreader.accounts.models import User @@ -29,11 +27,3 @@ class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User - - -class RegistrationProfileFactory(factory.django.DjangoModelFactory): - user = factory.SubFactory(UserFactory) - activation_key = factory.LazyFunction(get_activation_key) - - class Meta: - model = RegistrationProfile diff --git a/src/newsreader/accounts/tests/test_activation.py b/src/newsreader/accounts/tests/test_activation.py deleted file mode 100644 index 45d0909..0000000 --- a/src/newsreader/accounts/tests/test_activation.py +++ /dev/null @@ -1,99 +0,0 @@ -import datetime - -from django.conf import settings -from django.test import TestCase -from django.urls import reverse -from django.utils.translation import gettext as _ - -from registration.models import RegistrationProfile - -from newsreader.accounts.models import User - - -class ActivationTestCase(TestCase): - def setUp(self): - self.register_url = reverse("accounts:register") - self.register_success_url = reverse("accounts:register-complete") - self.success_url = reverse("accounts:activate-complete") - - def test_activation(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.register_url, data) - self.assertRedirects(response, self.register_success_url) - - register_profile = RegistrationProfile.objects.get() - - kwargs = {"activation_key": register_profile.activation_key} - response = self.client.get(reverse("accounts:activate", kwargs=kwargs)) - - self.assertRedirects(response, self.success_url) - - def test_expired_key(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.register_url, data) - - register_profile = RegistrationProfile.objects.get() - user = register_profile.user - - user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS) - user.save() - - kwargs = {"activation_key": register_profile.activation_key} - response = self.client.get(reverse("accounts:activate", kwargs=kwargs)) - - self.assertEqual(200, response.status_code) - self.assertContains(response, _("Account activation failed")) - - user.refresh_from_db() - self.assertFalse(user.is_active) - - def test_invalid_key(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.register_url, data) - self.assertRedirects(response, self.register_success_url) - - kwargs = {"activation_key": "not-a-valid-key"} - response = self.client.get(reverse("accounts:activate", kwargs=kwargs)) - - self.assertContains(response, _("Account activation failed")) - - user = User.objects.get() - - self.assertEquals(user.is_active, False) - - def test_activated_key(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.register_url, data) - self.assertRedirects(response, self.register_success_url) - - register_profile = RegistrationProfile.objects.get() - - kwargs = {"activation_key": register_profile.activation_key} - response = self.client.get(reverse("accounts:activate", kwargs=kwargs)) - - self.assertRedirects(response, self.success_url) - - # try this a second time - response = self.client.get(reverse("accounts:activate", kwargs=kwargs)) - - self.assertRedirects(response, self.success_url) diff --git a/src/newsreader/accounts/tests/test_registration.py b/src/newsreader/accounts/tests/test_registration.py deleted file mode 100644 index 27c87bf..0000000 --- a/src/newsreader/accounts/tests/test_registration.py +++ /dev/null @@ -1,110 +0,0 @@ -from django.core import mail -from django.test import TransactionTestCase as TestCase -from django.test.utils import override_settings -from django.urls import reverse -from django.utils.translation import gettext as _ - -from registration.models import RegistrationProfile - -from newsreader.accounts.models import User -from newsreader.accounts.tests.factories import UserFactory - - -class RegistrationTestCase(TestCase): - def setUp(self): - self.url = reverse("accounts:register") - self.success_url = reverse("accounts:register-complete") - self.disallowed_url = reverse("accounts:register-closed") - - def test_simple(self): - response = self.client.get(self.url) - - self.assertEquals(response.status_code, 200) - - def test_registration(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.url, data) - self.assertRedirects(response, self.success_url) - - self.assertEquals(User.objects.count(), 1) - self.assertEquals(RegistrationProfile.objects.count(), 1) - - user = User.objects.get() - - self.assertEquals(user.is_active, False) - self.assertEquals(len(mail.outbox), 1) - - def test_existing_email(self): - UserFactory(email="test@test.com") - - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.url, data) - self.assertEquals(response.status_code, 200) - - self.assertEquals(User.objects.count(), 1) - self.assertContains(response, _("User with this Email address already exists")) - - def test_pending_registration(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.url, data) - self.assertRedirects(response, self.success_url) - - self.assertEquals(User.objects.count(), 1) - self.assertEquals(RegistrationProfile.objects.count(), 1) - - user = User.objects.get() - - self.assertEquals(user.is_active, False) - self.assertEquals(len(mail.outbox), 1) - - response = self.client.post(self.url, data) - self.assertEquals(response.status_code, 200) - self.assertContains(response, _("User with this Email address already exists")) - - def test_disabled_account(self): - UserFactory(email="test@test.com", is_active=False) - - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.url, data) - self.assertEquals(response.status_code, 200) - - self.assertEquals(User.objects.count(), 1) - self.assertContains(response, _("User with this Email address already exists")) - - @override_settings(REGISTRATION_OPEN=False) - def test_registration_closed(self): - response = self.client.get(self.url) - - self.assertRedirects(response, self.disallowed_url) - - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.url, data) - self.assertRedirects(response, self.disallowed_url) - - self.assertEquals(User.objects.count(), 0) - self.assertEquals(RegistrationProfile.objects.count(), 0) diff --git a/src/newsreader/accounts/tests/test_resend_activation.py b/src/newsreader/accounts/tests/test_resend_activation.py deleted file mode 100644 index 974a2cd..0000000 --- a/src/newsreader/accounts/tests/test_resend_activation.py +++ /dev/null @@ -1,77 +0,0 @@ -from django.core import mail -from django.test import TransactionTestCase as TestCase -from django.urls import reverse -from django.utils.translation import gettext as _ - -from registration.models import RegistrationProfile - -from newsreader.accounts.tests.factories import RegistrationProfileFactory, UserFactory - - -class ResendActivationTestCase(TestCase): - def setUp(self): - self.url = reverse("accounts:activate-resend") - self.success_url = reverse("accounts:activate-complete") - self.register_url = reverse("accounts:register") - - def test_simple(self): - response = self.client.get(self.url) - - self.assertEquals(response.status_code, 200) - - def test_resent_form(self): - data = { - "email": "test@test.com", - "password1": "test12456", - "password2": "test12456", - } - - response = self.client.post(self.register_url, data) - - register_profile = RegistrationProfile.objects.get() - original_kwargs = {"activation_key": register_profile.activation_key} - - response = self.client.post(self.url, {"email": "test@test.com"}) - - self.assertContains(response, _("We have sent an email to")) - - self.assertEquals(len(mail.outbox), 2) - - register_profile.refresh_from_db() - - kwargs = {"activation_key": register_profile.activation_key} - response = self.client.get(reverse("accounts:activate", kwargs=kwargs)) - - self.assertRedirects(response, self.success_url) - - register_profile.refresh_from_db() - user = register_profile.user - - self.assertEquals(user.is_active, True) - - # test the old activation code - response = self.client.get(reverse("accounts:activate", kwargs=original_kwargs)) - - self.assertEquals(response.status_code, 200) - self.assertContains(response, _("Account activation failed")) - - def test_existing_account(self): - user = UserFactory(is_active=True) - RegistrationProfileFactory(user=user, activated=True) - - response = self.client.post(self.url, {"email": user.email}) - self.assertEquals(response.status_code, 200) - - # default behaviour is to show success page but not send an email - self.assertContains(response, _("We have sent an email to")) - - self.assertEquals(len(mail.outbox), 0) - - def test_no_account(self): - response = self.client.post(self.url, {"email": "fake@mail.com"}) - self.assertEquals(response.status_code, 200) - - # default behaviour is to show success page but not send an email - self.assertContains(response, _("We have sent an email to")) - - self.assertEquals(len(mail.outbox), 0) diff --git a/src/newsreader/accounts/urls.py b/src/newsreader/accounts/urls.py index cf1a546..b363f82 100644 --- a/src/newsreader/accounts/urls.py +++ b/src/newsreader/accounts/urls.py @@ -2,9 +2,6 @@ from django.contrib.auth.decorators import login_required from django.urls import include, path from newsreader.accounts.views import ( - ActivationCompleteView, - ActivationResendView, - ActivationView, FaviconRedirectView, IntegrationsView, LoginView, @@ -17,9 +14,6 @@ from newsreader.accounts.views import ( RedditRevokeRedirectView, RedditTemplateView, RedditTokenRedirectView, - RegistrationClosedView, - RegistrationCompleteView, - RegistrationView, SettingsView, ) @@ -53,24 +47,6 @@ urlpatterns = [ # Auth path("login/", LoginView.as_view(), name="login"), path("logout/", LogoutView.as_view(), name="logout"), - # Register - path("register/", RegistrationView.as_view(), name="register"), - path( - "register/complete/", - RegistrationCompleteView.as_view(), - name="register-complete", - ), - path("register/closed/", RegistrationClosedView.as_view(), name="register-closed"), - path( - "activate/complete/", ActivationCompleteView.as_view(), name="activate-complete" - ), - path("activate/resend/", ActivationResendView.as_view(), name="activate-resend"), - path( - # This URL should be placed after all activate/ url's (see arg) - "activate//", - ActivationView.as_view(), - name="activate", - ), # Password path("password-reset/", PasswordResetView.as_view(), name="password-reset"), path( diff --git a/src/newsreader/accounts/views/__init__.py b/src/newsreader/accounts/views/__init__.py index a174395..f5e926b 100644 --- a/src/newsreader/accounts/views/__init__.py +++ b/src/newsreader/accounts/views/__init__.py @@ -13,14 +13,7 @@ from newsreader.accounts.views.password import ( PasswordResetDoneView, PasswordResetView, ) -from newsreader.accounts.views.registration import ( - ActivationCompleteView, - ActivationResendView, - ActivationView, - RegistrationClosedView, - RegistrationCompleteView, - RegistrationView, -) + from newsreader.accounts.views.settings import SettingsView __all__ = [ @@ -36,11 +29,5 @@ __all__ = [ "PasswordResetConfirmView", "PasswordResetDoneView", "PasswordResetView", - "ActivationCompleteView", - "ActivationResendView", - "ActivationView", - "RegistrationClosedView", - "RegistrationCompleteView", - "RegistrationView", "SettingsView", ] diff --git a/src/newsreader/accounts/views/registration.py b/src/newsreader/accounts/views/registration.py deleted file mode 100644 index 755c960..0000000 --- a/src/newsreader/accounts/views/registration.py +++ /dev/null @@ -1,56 +0,0 @@ -from django.shortcuts import render -from django.urls import reverse_lazy -from django.views.generic import TemplateView - -from registration.backends.default import views as registration_views - -from newsreader.utils.views import NavListMixin - - -# RegistrationView shows a registration form and sends the email -# RegistrationCompleteView shows after filling in the registration form -# ActivationView is send within the activation email and activates the account -# ActivationCompleteView shows the success screen when activation was succesful -# ActivationResendView can be used when activation links are expired -# RegistrationClosedView shows when registration is disabled -class RegistrationView(NavListMixin, registration_views.RegistrationView): - disallowed_url = reverse_lazy("accounts:register-closed") - template_name = "registration/registration_form.html" - success_url = reverse_lazy("accounts:register-complete") - - -class RegistrationCompleteView(NavListMixin, TemplateView): - template_name = "registration/registration_complete.html" - - -class RegistrationClosedView(NavListMixin, TemplateView): - template_name = "registration/registration_closed.html" - - -# Redirects or renders failed activation template -class ActivationView(NavListMixin, registration_views.ActivationView): - template_name = "registration/activation_failure.html" - - def get_success_url(self, user): - return ("accounts:activate-complete", (), {}) - - -class ActivationCompleteView(NavListMixin, TemplateView): - template_name = "registration/activation_complete.html" - - -# Renders activation form resend or resend_activation_complete -class ActivationResendView(NavListMixin, registration_views.ResendActivationView): - template_name = "registration/activation_resend_form.html" - - def render_form_submitted_template(self, form): - """ - Renders resend activation complete template with the submitted email. - - """ - email = form.cleaned_data["email"] - context = {"email": email} - - return render( - self.request, "registration/activation_resend_complete.html", context - ) diff --git a/src/newsreader/conf/base.py b/src/newsreader/conf/base.py index bc22f2b..d17234a 100644 --- a/src/newsreader/conf/base.py +++ b/src/newsreader/conf/base.py @@ -16,7 +16,7 @@ except ImportError: BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent -DJANGO_PROJECT_DIR = os.path.join(BASE_DIR, "src", "newsreader") +DJANGO_PROJECT_DIR = BASE_DIR / "src" / "newsreader" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ @@ -39,7 +39,6 @@ INSTALLED_APPS = [ "rest_framework", "celery", "django_celery_beat", - "registration", "axes", # app modules "newsreader.accounts", @@ -256,10 +255,6 @@ SWAGGER_SETTINGS = { CELERY_WORKER_HIJACK_ROOT_LOGGER = False CELERY_BROKER_URL = "amqp://guest@rabbitmq:5672" -REGISTRATION_OPEN = True -REGISTRATION_AUTO_LOGIN = True -ACCOUNT_ACTIVATION_DAYS = 7 - # Sentry SENTRY_CONFIG = { "dsn": os.environ.get("SENTRY_DSN"), diff --git a/src/newsreader/conf/production.py b/src/newsreader/conf/production.py index 8dbdc1e..2042e29 100644 --- a/src/newsreader/conf/production.py +++ b/src/newsreader/conf/production.py @@ -57,8 +57,6 @@ REDDIT_REDIRECT_URL = os.environ.get("REDDIT_CALLBACK_URL", "") # Third party settings AXES_HANDLER = "axes.handlers.database.AxesDatabaseHandler" -REGISTRATION_OPEN = False - # Optionally use sentry integration try: from sentry_sdk import init as sentry_init diff --git a/src/newsreader/templates/components/nav-list/nav-list.html b/src/newsreader/templates/components/nav-list/nav-list.html index 50ecfd8..1c76122 100644 --- a/src/newsreader/templates/components/nav-list/nav-list.html +++ b/src/newsreader/templates/components/nav-list/nav-list.html @@ -1,17 +1,16 @@ diff --git a/src/newsreader/templates/registration/activation_complete.html b/src/newsreader/templates/registration/activation_complete.html deleted file mode 100755 index 4990231..0000000 --- a/src/newsreader/templates/registration/activation_complete.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "sidebar.html" %} -{% load i18n %} - -{% comment %} -**registration/activation_complete.html** - -Used after successful account activation. This template has no context -variables of its own, and should simply inform the user that their -account is now active. -{% endcomment %} - -{% block content %} - {% trans "Account activated" as header_text %} - - {% if user.is_authenticated %} - {% trans "Your account is activated. You can now log in." as content %} - {% else %} - {% trans "Your account is activated." as content %} - {% endif %} - -
-
- {% include "components/card/card.html" with header_text=header_text content=content %} -
-
-{% endblock %} diff --git a/src/newsreader/templates/registration/activation_email.html b/src/newsreader/templates/registration/activation_email.html deleted file mode 100644 index 8773b29..0000000 --- a/src/newsreader/templates/registration/activation_email.html +++ /dev/null @@ -1,72 +0,0 @@ -{% load i18n %} - - - - - {{ site.name }} {% trans "registration" %} - - - -

- {% blocktrans with site_name=site.name %} - You (or someone pretending to be you) have asked to register an account at - {{ site_name }}. If this wasn't you, please ignore this email - and your address will be removed from our records. - {% endblocktrans %} -

-

- {% blocktrans %} - To activate this account, please click the following link within the next - {{ expiration_days }} days: - {% endblocktrans %} -

- -

- - {{site.domain}}{% url 'accounts:activate' activation_key %} - -

-

- {% blocktrans with site_name=site.name %} - Sincerely, - {{ site_name }} Management - {% endblocktrans %} -

- - - - - -{% comment %} -**registration/activation_email.html** - -Used to generate the html body of the activation email. Should display a -link the user can click to activate the account. This template has the -following context: - -``activation_key`` - The activation key for the new account. - -``expiration_days`` - The number of days remaining during which the account may be - activated. - -``site`` - An object representing the site on which the user registered; - depending on whether ``django.contrib.sites`` is installed, this - may be an instance of either ``django.contrib.sites.models.Site`` - (if the sites application is installed) or - ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the - documentation for the Django sites framework - `_ for - details regarding these objects' interfaces. - -``user`` - The new user account - -``request`` - ``HttpRequest`` instance for better flexibility. - For example it can be used to compute absolute register URL: - - {{ request.scheme }}://{{ request.get_host }}{% url 'accounts:activate' activation_key %} -{% endcomment %} diff --git a/src/newsreader/templates/registration/activation_email.txt b/src/newsreader/templates/registration/activation_email.txt deleted file mode 100644 index d07e785..0000000 --- a/src/newsreader/templates/registration/activation_email.txt +++ /dev/null @@ -1,52 +0,0 @@ -{% load i18n %} -{% blocktrans with site_name=site.name %} -You (or someone pretending to be you) have asked to register an account at -{{ site_name }}. If this wasn't you, please ignore this email -and your address will be removed from our records. -{% endblocktrans %} -{% blocktrans %} -To activate this account, please click the following link within the next -{{ expiration_days }} days: -{% endblocktrans %} - -http://{{site.domain}}{% url 'accounts:activate' activation_key %} - -{% blocktrans with site_name=site.name %} -Sincerely, -{{ site_name }} Management -{% endblocktrans %} - - -{% comment %} -**registration/activation_email.txt** - -Used to generate the text body of the activation email. Should display a -link the user can click to activate the account. This template has the -following context: - -``activation_key`` - The activation key for the new account. - -``expiration_days`` - The number of days remaining during which the account may be - activated. - -``site`` - An object representing the site on which the user registered; - depending on whether ``django.contrib.sites`` is installed, this - may be an instance of either ``django.contrib.sites.models.Site`` - (if the sites application is installed) or - ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the - documentation for the Django sites framework - `_ for - details regarding these objects' interfaces. - -``user`` - The new user account - -``request`` - ``HttpRequest`` instance for better flexibility. - For example it can be used to compute absolute register URL: - - {{ request.scheme }}://{{ request.get_host }}{% url 'accounts:activate' activation_key %} -{% endcomment %} diff --git a/src/newsreader/templates/registration/activation_email_subject.txt b/src/newsreader/templates/registration/activation_email_subject.txt deleted file mode 100644 index da0ddeb..0000000 --- a/src/newsreader/templates/registration/activation_email_subject.txt +++ /dev/null @@ -1,28 +0,0 @@ -{% load i18n %}{% trans "Account activation on" %} {{ site.name }} - - -{% comment %} -**registration/activation_email_subject.txt** - -Used to generate the subject line of the activation email. Because the -subject line of an email must be a single line of text, any output -from this template will be forcibly condensed to a single line before -being used. This template has the following context: - -``activation_key`` - The activation key for the new account. - -``expiration_days`` - The number of days remaining during which the account may be - activated. - -``site`` - An object representing the site on which the user registered; - depending on whether ``django.contrib.sites`` is installed, this - may be an instance of either ``django.contrib.sites.models.Site`` - (if the sites application is installed) or - ``django.contrib.sites.requests.RequestSite`` (if not). Consult `the - documentation for the Django sites framework - `_ for - details regarding these objects' interfaces. -{% endcomment %} diff --git a/src/newsreader/templates/registration/activation_failure.html b/src/newsreader/templates/registration/activation_failure.html deleted file mode 100644 index d20629a..0000000 --- a/src/newsreader/templates/registration/activation_failure.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "sidebar.html" %} -{% load i18n %} - -{% comment %} -**registration/activate.html** - -Used if account activation fails. With the default setup, has the following context: - -``activation_key`` - The activation key used during the activation attempt. -{% endcomment %} - -{% block content %} - {% trans "Activation Failure" as header_text %} - {% trans "Account activation failed." as content %} - -
-
- {% include "components/card/card.html" with header_text=header_text content=content %} -
-
-{% endblock %} diff --git a/src/newsreader/templates/registration/activation_resend_complete.html b/src/newsreader/templates/registration/activation_resend_complete.html deleted file mode 100644 index 6d01fee..0000000 --- a/src/newsreader/templates/registration/activation_resend_complete.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} -{% load static i18n %} - -{% block title %}{% trans "Account Activation Resent" %}{% endblock %} - -{% comment %} -**registration/resend_activation_complete.html** -Used after form for resending account activation is submitted. By default has -the following context: - -``email`` - The email address submitted in the resend activation form. -{% endcomment %} - -{% block content %} -
- {% trans "Account activation resent" as header_text %} - {% blocktrans asvar content %} - We have sent an email to {{ email }} with further instructions. - {% endblocktrans %} - {% include "components/card/card.html" with header_text=header_text content=content %} -
-{% endblock %} diff --git a/src/newsreader/templates/registration/activation_resend_form.html b/src/newsreader/templates/registration/activation_resend_form.html deleted file mode 100644 index 3910d39..0000000 --- a/src/newsreader/templates/registration/activation_resend_form.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "sidebar.html" %} -{% load static %} - -{% block content %} - {% url "accounts:login" as cancel_url %} - -
-
- {% include "components/form/form.html" with form=form title="Resend activation code" cancel_url=cancel_url confirm_text="Resend code" %} -
-
-{% endblock %} diff --git a/src/newsreader/templates/registration/registration_closed.html b/src/newsreader/templates/registration/registration_closed.html deleted file mode 100755 index 75091b7..0000000 --- a/src/newsreader/templates/registration/registration_closed.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "sidebar.html" %} -{% load static i18n %} - -{% block content %} - {% trans "Registration is closed" as header_text %} - {% trans "Sorry, but registration is closed at this moment. Come back later." as content %} - -
-
- {% include "components/card/card.html" with header_text=header_text content=content %} -
-
-{% endblock %} diff --git a/src/newsreader/templates/registration/registration_complete.html b/src/newsreader/templates/registration/registration_complete.html deleted file mode 100755 index b2281bb..0000000 --- a/src/newsreader/templates/registration/registration_complete.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "sidebar.html" %} -{% load i18n %} - -{% comment %} -**registration/registration_complete.html** - -Used after successful completion of the registration form. This -template has no context variables of its own, and should simply inform -the user that an email containing account-activation information has -been sent. -{% endcomment %} - -{% block content %} - {% trans "Activation email sent" as header_text %} - {% trans "Please check your email to complete the registration process." as content %} - -
-
- {% include "components/card/card.html" with header_text=header_text content=content %} -
-
-{% endblock %} diff --git a/src/newsreader/templates/registration/registration_form.html b/src/newsreader/templates/registration/registration_form.html deleted file mode 100644 index dfaa6d3..0000000 --- a/src/newsreader/templates/registration/registration_form.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "sidebar.html" %} -{% load static %} - - -{% block content %} - {% url "accounts:login" as cancel_url %} - -
-
- {% include "components/form/form.html" with form=form title="Register" cancel_url=cancel_url confirm_text="Register" %} -
-
-{% endblock %} diff --git a/src/newsreader/utils/views.py b/src/newsreader/utils/views.py index 3ba752a..8325aa5 100644 --- a/src/newsreader/utils/views.py +++ b/src/newsreader/utils/views.py @@ -21,7 +21,6 @@ class NavListMixin(ContextMixin): unauthenticated_links = { "Login": reverse("accounts:login"), - "Register": reverse("accounts:register"), } if self.request.user.is_authenticated: diff --git a/uv.lock b/uv.lock index 629a7b3..84ca47a 100644 --- a/uv.lock +++ b/uv.lock @@ -276,15 +276,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/7e/ba12b9660642663f5273141018d2bec0a1cae1711f4f6d1093920e157946/django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401", size = 229868 }, ] -[[package]] -name = "django-registration-redux" -version = "2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/47/960fb3f88d0080a94fffb6fc98ff986012d3a968c030036cf79abdd40242/django-registration-redux-2.13.tar.gz", hash = "sha256:9793a05b32b1d7342c6ef3e0140b2951b7dbde058b3ba6e8a232b534928279f9", size = 125883 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/7f/715af6a1322a1bc8fc84befa469a048dd5983eeac49f464ffa01bff33f00/django_registration_redux-2.13-py2.py3-none-any.whl", hash = "sha256:547c86ad9b951cf743075b5486f304e51e450b45d04e5ef04392838a9cff3da8", size = 218441 }, -] - [[package]] name = "django-rest-framework" version = "0.1.0" @@ -505,7 +496,6 @@ dependencies = [ { name = "django", marker = "sys_platform == 'linux'" }, { name = "django-axes", marker = "sys_platform == 'linux'" }, { name = "django-celery-beat", marker = "sys_platform == 'linux'" }, - { name = "django-registration-redux", marker = "sys_platform == 'linux'" }, { name = "django-rest-framework", marker = "sys_platform == 'linux'" }, { name = "djangorestframework-camel-case", marker = "sys_platform == 'linux'" }, { name = "feedparser", marker = "sys_platform == 'linux'" }, @@ -549,7 +539,6 @@ requires-dist = [ { name = "django", specifier = "~=4.2" }, { name = "django-axes" }, { name = "django-celery-beat", specifier = "~=2.7.0" }, - { name = "django-registration-redux", specifier = "~=2.7" }, { name = "django-rest-framework" }, { name = "djangorestframework-camel-case" }, { name = "feedparser" }, From 1574661c572a43cb3a2f463438f6f6626b1f1bdc Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 21:05:01 +0100 Subject: [PATCH 16/39] Fix ruff errors --- src/newsreader/accounts/views/__init__.py | 2 +- src/newsreader/conf/production.py | 3 +- src/newsreader/news/collection/endpoints.py | 1 + .../news/collection/forms/__init__.py | 1 + src/newsreader/news/collection/models.py | 2 +- .../collection/tests/favicon/builder/tests.py | 10 +++---- .../collection/tests/feed/builder/tests.py | 24 +++++++-------- .../collection/tests/reddit/builder/tests.py | 30 +++++++++---------- src/newsreader/news/core/endpoints.py | 1 + 9 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/newsreader/accounts/views/__init__.py b/src/newsreader/accounts/views/__init__.py index f5e926b..e62755c 100644 --- a/src/newsreader/accounts/views/__init__.py +++ b/src/newsreader/accounts/views/__init__.py @@ -13,9 +13,9 @@ from newsreader.accounts.views.password import ( PasswordResetDoneView, PasswordResetView, ) - from newsreader.accounts.views.settings import SettingsView + __all__ = [ "LoginView", "LogoutView", diff --git a/src/newsreader/conf/production.py b/src/newsreader/conf/production.py index 2042e29..8615aa2 100644 --- a/src/newsreader/conf/production.py +++ b/src/newsreader/conf/production.py @@ -1,10 +1,9 @@ import os +from .base import * # noqa: F403 from .version import get_current_version -from .base import * # noqa: F403 - DEBUG = False SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") diff --git a/src/newsreader/news/collection/endpoints.py b/src/newsreader/news/collection/endpoints.py index 24918c9..c5c5580 100644 --- a/src/newsreader/news/collection/endpoints.py +++ b/src/newsreader/news/collection/endpoints.py @@ -1,4 +1,5 @@ from django.db.models import Prefetch + from rest_framework import status from rest_framework.generics import ( GenericAPIView, diff --git a/src/newsreader/news/collection/forms/__init__.py b/src/newsreader/news/collection/forms/__init__.py index 4482b5c..daf73b0 100644 --- a/src/newsreader/news/collection/forms/__init__.py +++ b/src/newsreader/news/collection/forms/__init__.py @@ -2,6 +2,7 @@ from newsreader.news.collection.forms.feed import FeedForm, OPMLImportForm from newsreader.news.collection.forms.reddit import SubRedditForm from newsreader.news.collection.forms.rules import CollectionRuleBulkForm + __all__ = [ "FeedForm", "OPMLImportForm", diff --git a/src/newsreader/news/collection/models.py b/src/newsreader/news/collection/models.py index c538ae6..1eb148c 100644 --- a/src/newsreader/news/collection/models.py +++ b/src/newsreader/news/collection/models.py @@ -1,5 +1,5 @@ -from django.db import models from django.conf import settings +from django.db import models from django.urls import reverse from django.utils.translation import gettext as _ diff --git a/src/newsreader/news/collection/tests/favicon/builder/tests.py b/src/newsreader/news/collection/tests/favicon/builder/tests.py index af9b33d..a670f8a 100644 --- a/src/newsreader/news/collection/tests/favicon/builder/tests.py +++ b/src/newsreader/news/collection/tests/favicon/builder/tests.py @@ -5,12 +5,12 @@ from django.test import TestCase from newsreader.news.collection.favicon import FaviconBuilder from newsreader.news.collection.tests.factories import CollectionRuleFactory from newsreader.news.collection.tests.favicon.builder.mocks import ( - simple_mock, - mock_without_url, - mock_without_header, - mock_with_weird_path, - mock_with_other_url, mock_with_multiple_icons, + mock_with_other_url, + mock_with_weird_path, + mock_without_header, + mock_without_url, + simple_mock, ) diff --git a/src/newsreader/news/collection/tests/feed/builder/tests.py b/src/newsreader/news/collection/tests/feed/builder/tests.py index 420d95e..3a32e76 100644 --- a/src/newsreader/news/collection/tests/feed/builder/tests.py +++ b/src/newsreader/news/collection/tests/feed/builder/tests.py @@ -11,21 +11,21 @@ from newsreader.news.core.models import Post from newsreader.news.core.tests.factories import FeedPostFactory from .mocks import ( - multiple_mock, + mock_with_html, + mock_with_long_author, + mock_with_long_exotic_title, + mock_with_long_title, + mock_with_longer_content_detail, + mock_with_multiple_content_detail, + mock_with_shorter_content_detail, + mock_with_update_entries, + mock_without_author, + mock_without_body, + mock_without_entries, mock_without_identifier, mock_without_publish_date, mock_without_url, - mock_without_body, - mock_without_author, - mock_without_entries, - mock_with_update_entries, - mock_with_html, - mock_with_long_author, - mock_with_long_title, - mock_with_long_exotic_title, - mock_with_longer_content_detail, - mock_with_shorter_content_detail, - mock_with_multiple_content_detail, + multiple_mock, ) diff --git a/src/newsreader/news/collection/tests/reddit/builder/tests.py b/src/newsreader/news/collection/tests/reddit/builder/tests.py index d9196a0..5144edf 100644 --- a/src/newsreader/news/collection/tests/reddit/builder/tests.py +++ b/src/newsreader/news/collection/tests/reddit/builder/tests.py @@ -6,24 +6,24 @@ from django.test import TestCase from newsreader.news.collection.reddit import RedditBuilder from newsreader.news.collection.tests.factories import SubredditFactory from newsreader.news.collection.tests.reddit.builder.mocks import ( - simple_mock, - empty_mock, - unknown_mock, - unsanitized_mock, author_mock, - title_mock, - duplicate_mock, - image_mock, - external_image_mock, - video_mock, - external_video_mock, - external_gifv_mock, - nsfw_mock, - spoiler_mock, - seen_mock, - upvote_mock, comment_mock, downvote_mock, + duplicate_mock, + empty_mock, + external_gifv_mock, + external_image_mock, + external_video_mock, + image_mock, + nsfw_mock, + seen_mock, + simple_mock, + spoiler_mock, + title_mock, + unknown_mock, + unsanitized_mock, + upvote_mock, + video_mock, ) from newsreader.news.core.models import Post from newsreader.news.core.tests.factories import RedditPostFactory diff --git a/src/newsreader/news/core/endpoints.py b/src/newsreader/news/core/endpoints.py index 6ed9f39..092ec6f 100644 --- a/src/newsreader/news/core/endpoints.py +++ b/src/newsreader/news/core/endpoints.py @@ -1,4 +1,5 @@ from django.db.models import Prefetch + from rest_framework import status from rest_framework.generics import ( GenericAPIView, From 34afcc02b69268b1d634e7e39f3c40caae1dcffb Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sun, 23 Mar 2025 21:16:36 +0100 Subject: [PATCH 17/39] Remove requests oathlib --- pyproject.toml | 1 - uv.lock | 24 ------------------------ 2 files changed, 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1db5f34..c722183 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ dependencies = [ 'python-dotenv~=1.0.1', 'ftfy~=6.2', 'requests', - 'requests_oauthlib', 'feedparser', 'bleach', 'beautifulsoup4', diff --git a/uv.lock b/uv.lock index 84ca47a..c108e5c 100644 --- a/uv.lock +++ b/uv.lock @@ -505,7 +505,6 @@ dependencies = [ { name = "pymemcache", marker = "sys_platform == 'linux'" }, { name = "python-dotenv", marker = "sys_platform == 'linux'" }, { name = "requests", marker = "sys_platform == 'linux'" }, - { name = "requests-oauthlib", marker = "sys_platform == 'linux'" }, ] [package.optional-dependencies] @@ -548,7 +547,6 @@ requires-dist = [ { name = "pymemcache" }, { name = "python-dotenv", specifier = "~=1.0.1" }, { name = "requests" }, - { name = "requests-oauthlib" }, { name = "sentry-sdk", marker = "extra == 'sentry'", specifier = "~=2.0" }, ] provides-extras = ["sentry"] @@ -567,15 +565,6 @@ test-tools = [ { name = "ruff" }, ] -[[package]] -name = "oauthlib" -version = "3.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 }, -] - [[package]] name = "packaging" version = "24.1" @@ -666,19 +655,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib", marker = "sys_platform == 'linux'" }, - { name = "requests", marker = "sys_platform == 'linux'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 }, -] - [[package]] name = "ruff" version = "0.6.3" From 1a54fdbcd1e90618fe2f75b3b1c71895854d3ecc Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Sat, 8 Feb 2025 10:05:45 +0100 Subject: [PATCH 18/39] Remove function binding usage --- babel.config.js | 30 +++++++++---------- src/newsreader/js/components/Selector.js | 4 +-- src/newsreader/js/pages/categories/App.js | 10 ++----- .../js/pages/homepage/components/ScrollTop.js | 4 +-- .../homepage/components/postlist/PostList.js | 4 +-- .../homepage/components/sidebar/ReadButton.js | 4 +-- 6 files changed, 21 insertions(+), 35 deletions(-) diff --git a/babel.config.js b/babel.config.js index 20d9e41..ed19be2 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,23 +1,21 @@ module.exports = api => { const isTest = api.env('test'); - const preset = [ - "@babel/preset-env" , { targets: 'defaults' } + const preset = [ + "@babel/preset-env", { targets: 'defaults' } + ]; + const testPreset = [ + "@babel/preset-env", { targets: { node: process.versions.node } } ]; - const testPreset = [ - "@babel/preset-env", { targets: { node: process.versions.node } } - ]; - const plugins = [ - "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-transform-react-jsx", - "@babel/plugin-syntax-function-bind", - "@babel/plugin-proposal-function-bind", - "@babel/plugin-proposal-class-properties" - ] + const plugins = [ + "@babel/plugin-syntax-dynamic-import", + "@babel/plugin-transform-react-jsx", + "@babel/plugin-proposal-class-properties" + ] - return { - "presets": [isTest ? testPreset: preset], - "plugins": plugins - } + return { + "presets": [isTest ? testPreset : preset], + "plugins": plugins + } } diff --git a/src/newsreader/js/components/Selector.js b/src/newsreader/js/components/Selector.js index 8b701f5..c6b117a 100644 --- a/src/newsreader/js/components/Selector.js +++ b/src/newsreader/js/components/Selector.js @@ -1,6 +1,4 @@ class Selector { - onClick = ::this.onClick; - inputs = []; constructor() { @@ -11,7 +9,7 @@ class Selector { selectAllInput.onchange = this.onClick; } - onClick(e) { + onClick = (e) => { const targetValue = e.target.checked; this.inputs.forEach(input => { diff --git a/src/newsreader/js/pages/categories/App.js b/src/newsreader/js/pages/categories/App.js index b20ff1d..ac237c3 100644 --- a/src/newsreader/js/pages/categories/App.js +++ b/src/newsreader/js/pages/categories/App.js @@ -9,10 +9,6 @@ import Messages from '../../components/Messages.js'; import Sidebar from '../../components/Sidebar.js'; class App extends React.Component { - selectCategory = ::this.selectCategory; - deselectCategory = ::this.deselectCategory; - deleteCategory = ::this.deleteCategory; - constructor(props) { super(props); @@ -24,15 +20,15 @@ class App extends React.Component { }; } - selectCategory(categoryId) { + selectCategory = (categoryId) => { this.setState({ selectedCategoryId: categoryId }); } - deselectCategory() { + deselectCategory = () => { this.setState({ selectedCategoryId: null }); } - deleteCategory(categoryId) { + deleteCategory = (categoryId) => { const url = `/api/categories/${categoryId}/`; const options = { method: 'DELETE', diff --git a/src/newsreader/js/pages/homepage/components/ScrollTop.js b/src/newsreader/js/pages/homepage/components/ScrollTop.js index 9cca7f7..a9927f2 100644 --- a/src/newsreader/js/pages/homepage/components/ScrollTop.js +++ b/src/newsreader/js/pages/homepage/components/ScrollTop.js @@ -1,8 +1,6 @@ import React from 'react'; export default class ScrollTop extends React.Component { - scrollListener = ::this.scrollListener; - state = { listenerAttached: false, showTop: false, @@ -17,7 +15,7 @@ export default class ScrollTop extends React.Component { } } - scrollListener() { + scrollListener = () => { const postList = this.props.postListNode; const elementEnd = postList.scrollTop + postList.offsetHeight >= postList.scrollHeight; diff --git a/src/newsreader/js/pages/homepage/components/postlist/PostList.js b/src/newsreader/js/pages/homepage/components/postlist/PostList.js index 8bb354a..9c534ee 100644 --- a/src/newsreader/js/pages/homepage/components/postlist/PostList.js +++ b/src/newsreader/js/pages/homepage/components/postlist/PostList.js @@ -4,13 +4,11 @@ import { isEqual } from 'lodash'; import { fetchPostsBySection, fetchSavedPosts } from '../../actions/posts.js'; import { SAVED_TYPE } from '../../constants.js'; -import { filterPosts } from './filters.js'; import LoadingIndicator from '../../../../components/LoadingIndicator.js'; import PostItem from './PostItem.js'; class PostList extends React.Component { - handleIntersect = ::this.handleIntersect; lastPostRef = null; observer = null; @@ -33,7 +31,7 @@ class PostList extends React.Component { this.observer.disconnect(); } - handleIntersect(entries) { + handleIntersect = (entries) => { entries.every(entry => { if (entry.isIntersecting) { this.observer.unobserve(entry.target); diff --git a/src/newsreader/js/pages/homepage/components/sidebar/ReadButton.js b/src/newsreader/js/pages/homepage/components/sidebar/ReadButton.js index 3711c85..fe4175b 100644 --- a/src/newsreader/js/pages/homepage/components/sidebar/ReadButton.js +++ b/src/newsreader/js/pages/homepage/components/sidebar/ReadButton.js @@ -5,9 +5,7 @@ import Cookies from 'js-cookie'; import { markRead } from '../../actions/selected.js'; class ReadButton extends React.Component { - markSelectedRead = ::this.markSelectedRead; - - markSelectedRead() { + markSelectedRead = () => { const token = Cookies.get('csrftoken'); if (this.props.selected.unread > 0) { From b465d0bb8ddb7088454693af0ff87b7c230f726c Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Thu, 27 Mar 2025 21:44:21 +0100 Subject: [PATCH 19/39] Remove leftover function binding usages --- src/newsreader/js/components/Messages.js | 4 +--- src/newsreader/js/pages/homepage/components/PostModal.js | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/newsreader/js/components/Messages.js b/src/newsreader/js/components/Messages.js index 3fed5de..e3d776e 100644 --- a/src/newsreader/js/components/Messages.js +++ b/src/newsreader/js/components/Messages.js @@ -3,9 +3,7 @@ import React from 'react'; class Messages extends React.Component { state = { messages: this.props.messages }; - close = ::this.close; - - close(index) { + close = (index) => { const newMessages = this.state.messages.filter((message, currentIndex) => { return currentIndex != index; }); diff --git a/src/newsreader/js/pages/homepage/components/PostModal.js b/src/newsreader/js/pages/homepage/components/PostModal.js index 46410b7..a1a698d 100644 --- a/src/newsreader/js/pages/homepage/components/PostModal.js +++ b/src/newsreader/js/pages/homepage/components/PostModal.js @@ -7,7 +7,6 @@ import { SAVED_TYPE, SUBREDDIT } from '../constants.js'; import { formatDatetime } from '../../../utils.js'; class PostModal extends React.Component { - modalListener = ::this.modalListener; readTimer = null; componentDidMount() { @@ -32,7 +31,7 @@ class PostModal extends React.Component { window.removeEventListener('click', this.modalListener); } - modalListener(e) { + modalListener = (e) => { const targetClassName = e.target.className; if (this.props.post && targetClassName == 'modal post-modal') { From b8559f04999fdcfce4f667889cab7bf99f272fe9 Mon Sep 17 00:00:00 2001 From: Sonny Bakker Date: Thu, 27 Mar 2025 22:02:12 +0100 Subject: [PATCH 20/39] Remove reddit code --- docker-compose.yml | 5 - src/newsreader/accounts/admin.py | 10 - ...emove_user_reddit_access_token_and_more.py | 21 + src/newsreader/accounts/models.py | 4 - .../accounts/components/settings-form.html | 38 +- .../accounts/views/integrations.html | 47 - .../templates/accounts/views/reddit.html | 22 - .../accounts/tests/test_integrations.py | 275 - src/newsreader/accounts/urls.py | 23 - src/newsreader/accounts/views/__init__.py | 10 - src/newsreader/accounts/views/integrations.py | 156 - src/newsreader/conf/base.py | 10 - src/newsreader/conf/production.py | 5 - src/newsreader/js/pages/homepage/App.js | 2 - .../js/pages/homepage/components/PostModal.js | 7 +- .../homepage/components/postlist/PostItem.js | 9 +- .../homepage/components/postlist/PostList.js | 1 - src/newsreader/js/pages/homepage/constants.js | 1 - src/newsreader/js/pages/homepage/index.js | 3 +- src/newsreader/news/collection/choices.py | 8 - .../news/collection/forms/__init__.py | 2 - .../news/collection/forms/reddit.py | 57 - ...llectionrule_reddit_allow_nfsw_and_more.py | 46 + src/newsreader/news/collection/models.py | 32 - src/newsreader/news/collection/reddit.py | 419 -- src/newsreader/news/collection/tasks.py | 95 - .../news/collection/views/rules.html | 252 +- .../collection/views/subreddit-create.html | 13 - .../collection/views/subreddit-update.html | 14 - .../news/collection/tests/factories.py | 6 - .../news/collection/tests/reddit/__init__.py | 0 .../tests/reddit/builder/__init__.py | 0 .../collection/tests/reddit/builder/mocks.py | 5246 ----------------- .../collection/tests/reddit/builder/tests.py | 472 -- .../tests/reddit/client/__init__.py | 0 .../collection/tests/reddit/client/mocks.py | 160 - .../collection/tests/reddit/client/tests.py | 163 - .../tests/reddit/collector/__init__.py | 0 .../tests/reddit/collector/mocks.py | 1662 ------ .../tests/reddit/collector/tests.py | 201 - .../tests/reddit/stream/__init__.py | 0 .../collection/tests/reddit/stream/mocks.py | 3289 ----------- .../collection/tests/reddit/stream/tests.py | 144 - .../collection/tests/reddit/test_scheduler.py | 142 - .../news/collection/tests/views/test_crud.py | 14 - .../tests/views/test_subreddit_views.py | 133 - src/newsreader/news/collection/urls.py | 13 - .../news/collection/views/__init__.py | 7 +- .../news/collection/views/reddit.py | 27 - src/newsreader/news/core/tests/factories.py | 8 - src/newsreader/news/core/views.py | 3 - src/newsreader/scss/components/index.scss | 2 - .../integrations/_integrations.scss | 13 - .../scss/components/integrations/index.scss | 1 - .../scss/elements/button/_button.scss | 15 +- src/newsreader/scss/pages/index.scss | 1 - .../scss/pages/integrations/index.scss | 5 - src/newsreader/scss/partials/_colors.scss | 3 - 58 files changed, 215 insertions(+), 13102 deletions(-) create mode 100644 src/newsreader/accounts/migrations/0018_remove_user_reddit_access_token_and_more.py delete mode 100644 src/newsreader/accounts/templates/accounts/views/integrations.html delete mode 100644 src/newsreader/accounts/templates/accounts/views/reddit.html delete mode 100644 src/newsreader/accounts/tests/test_integrations.py delete mode 100644 src/newsreader/accounts/views/integrations.py delete mode 100644 src/newsreader/news/collection/forms/reddit.py create mode 100644 src/newsreader/news/collection/migrations/0018_remove_collectionrule_reddit_allow_nfsw_and_more.py delete mode 100644 src/newsreader/news/collection/reddit.py delete mode 100644 src/newsreader/news/collection/templates/news/collection/views/subreddit-create.html delete mode 100644 src/newsreader/news/collection/templates/news/collection/views/subreddit-update.html delete mode 100644 src/newsreader/news/collection/tests/reddit/__init__.py delete mode 100644 src/newsreader/news/collection/tests/reddit/builder/__init__.py delete mode 100644 src/newsreader/news/collection/tests/reddit/builder/mocks.py delete mode 100644 src/newsreader/news/collection/tests/reddit/builder/tests.py delete mode 100644 src/newsreader/news/collection/tests/reddit/client/__init__.py delete mode 100644 src/newsreader/news/collection/tests/reddit/client/mocks.py delete mode 100644 src/newsreader/news/collection/tests/reddit/client/tests.py delete mode 100644 src/newsreader/news/collection/tests/reddit/collector/__init__.py delete mode 100644 src/newsreader/news/collection/tests/reddit/collector/mocks.py delete mode 100644 src/newsreader/news/collection/tests/reddit/collector/tests.py delete mode 100644 src/newsreader/news/collection/tests/reddit/stream/__init__.py delete mode 100644 src/newsreader/news/collection/tests/reddit/stream/mocks.py delete mode 100644 src/newsreader/news/collection/tests/reddit/stream/tests.py delete mode 100644 src/newsreader/news/collection/tests/reddit/test_scheduler.py delete mode 100644 src/newsreader/news/collection/tests/views/test_subreddit_views.py delete mode 100644 src/newsreader/news/collection/views/reddit.py delete mode 100644 src/newsreader/scss/components/integrations/_integrations.scss delete mode 100644 src/newsreader/scss/components/integrations/index.scss delete mode 100644 src/newsreader/scss/pages/integrations/index.scss diff --git a/docker-compose.yml b/docker-compose.yml index f29e719..02f1fab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,11 +29,6 @@ x-django-env: &django-env EMAIL_USE_SSL: EMAIL_DEFAULT_FROM: - # Reddit - REDDIT_CLIENT_ID: - REDDIT_CLIENT_SECRET: - REDDIT_CALLBACK_URL: - # Sentry SENTRY_DSN: diff --git a/src/newsreader/accounts/admin.py b/src/newsreader/accounts/admin.py index 38bc2f3..8ae55cb 100644 --- a/src/newsreader/accounts/admin.py +++ b/src/newsreader/accounts/admin.py @@ -11,12 +11,6 @@ class UserAdminForm(UserChangeForm): class Meta: widgets = { "email": forms.EmailInput(attrs={"size": "50"}), - "reddit_access_token": forms.PasswordInput( - attrs={"size": "90"}, render_value=True - ), - "reddit_refresh_token": forms.PasswordInput( - attrs={"size": "90"}, render_value=True - ), } @@ -34,10 +28,6 @@ class UserAdmin(DjangoUserAdmin): _("User settings"), {"fields": ("email", "password", "first_name", "last_name", "is_active")}, ), - ( - _("Reddit settings"), - {"fields": ("reddit_access_token", "reddit_refresh_token")}, - ), ( _("Permission settings"), {"classes": ("collapse",), "fields": ("is_staff", "is_superuser")}, diff --git a/src/newsreader/accounts/migrations/0018_remove_user_reddit_access_token_and_more.py b/src/newsreader/accounts/migrations/0018_remove_user_reddit_access_token_and_more.py new file mode 100644 index 0000000..19bda0c --- /dev/null +++ b/src/newsreader/accounts/migrations/0018_remove_user_reddit_access_token_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.16 on 2025-03-26 08:46 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0017_auto_20240906_0914'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='reddit_access_token', + ), + migrations.RemoveField( + model_name='user', + name='reddit_refresh_token', + ), + ] diff --git a/src/newsreader/accounts/models.py b/src/newsreader/accounts/models.py index e78786f..15cc97e 100644 --- a/src/newsreader/accounts/models.py +++ b/src/newsreader/accounts/models.py @@ -39,10 +39,6 @@ class UserManager(DjangoUserManager): class User(AbstractUser): email = models.EmailField(_("email address"), unique=True) - # reddit settings - reddit_refresh_token = models.CharField(max_length=255, blank=True, null=True) - reddit_access_token = models.CharField(max_length=255, blank=True, null=True) - # settings auto_mark_read = models.BooleanField( _("Auto read marking"), diff --git a/src/newsreader/accounts/templates/accounts/components/settings-form.html b/src/newsreader/accounts/templates/accounts/components/settings-form.html index f5e7065..6e81e79 100644 --- a/src/newsreader/accounts/templates/accounts/components/settings-form.html +++ b/src/newsreader/accounts/templates/accounts/components/settings-form.html @@ -2,27 +2,23 @@ {% load i18n %} {% block actions %} -
-
- {% include "components/form/confirm-button.html" %} +
+
+ {% include "components/form/confirm-button.html" %} - - {% trans "Change password" %} - + + {% trans "Change password" %} + - {% if favicon_task_allowed %} - - {% trans "Fetch favicons" %} - - {% else %} - - {% endif %} - - - {% trans "Third party integrations" %} - -
-
+ {% if favicon_task_allowed %} + + {% trans "Fetch favicons" %} + + {% else %} + + {% endif %} +
+
{% endblock actions %} diff --git a/src/newsreader/accounts/templates/accounts/views/integrations.html b/src/newsreader/accounts/templates/accounts/views/integrations.html deleted file mode 100644 index 559d3d2..0000000 --- a/src/newsreader/accounts/templates/accounts/views/integrations.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "sidebar.html" %} -{% load i18n %} - -{% block content %} -
-
-
- {% include "components/header/header.html" with title="Integrations" only %} - -
-

Reddit

-
- {% if reddit_authorization_url %} - - {% trans "Authorize account" %} - - {% else %} - - {% endif %} - - {% if reddit_refresh_url %} - - {% trans "Refresh token" %} - - {% else %} - - {% endif %} - - {% if reddit_revoke_url %} - - {% trans "Deauthorize account" %} - - {% else %} - - {% endif %} -
-
-
-
-
-{% endblock %} diff --git a/src/newsreader/accounts/templates/accounts/views/reddit.html b/src/newsreader/accounts/templates/accounts/views/reddit.html deleted file mode 100644 index 9fa8378..0000000 --- a/src/newsreader/accounts/templates/accounts/views/reddit.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "sidebar.html" %} -{% load i18n %} - -{% block content %} -
-
-
- {% if error %} -

{% trans "Reddit authorization failed" %}

-

{{ error }}

- {% elif access_token and refresh_token %} -

{% trans "Reddit account is linked" %}

-

{% trans "Your reddit account was successfully linked." %}

- {% endif %} - -

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

-
-
-
-{% endblock %} diff --git a/src/newsreader/accounts/tests/test_integrations.py b/src/newsreader/accounts/tests/test_integrations.py deleted file mode 100644 index de0b142..0000000 --- a/src/newsreader/accounts/tests/test_integrations.py +++ /dev/null @@ -1,275 +0,0 @@ -from unittest.mock import patch -from urllib.parse import urlencode -from uuid import uuid4 - -from django.core.cache import cache -from django.test import TestCase -from django.urls import reverse - -from bs4 import BeautifulSoup - -from newsreader.accounts.tests.factories import UserFactory -from newsreader.news.collection.exceptions import ( - StreamException, - StreamTooManyException, -) - - -class IntegrationsViewTestCase(TestCase): - def setUp(self): - self.user = UserFactory(email="test@test.nl", password="test") - self.client.force_login(self.user) - - self.url = reverse("accounts:settings:integrations") - - -class RedditIntegrationsTestCase(IntegrationsViewTestCase): - def test_reddit_authorization(self): - self.user.reddit_refresh_token = None - self.user.save() - - response = self.client.get(self.url) - - soup = BeautifulSoup(response.content, features="lxml") - button = soup.find("a", class_="link button button--reddit") - - self.assertEquals(button.text.strip(), "Authorize account") - - def test_reddit_refresh_token(self): - self.user.reddit_refresh_token = "jadajadajada" - self.user.reddit_access_token = None - self.user.save() - - response = self.client.get(self.url) - - soup = BeautifulSoup(response.content, features="lxml") - button = soup.find("a", class_="link button button--reddit") - - self.assertEquals(button.text.strip(), "Refresh token") - - def test_reddit_revoke(self): - self.user.reddit_refresh_token = "jadajadajada" - self.user.reddit_access_token = None - self.user.save() - - response = self.client.get(self.url) - - soup = BeautifulSoup(response.content, features="lxml") - buttons = soup.find_all("a", class_="link button button--reddit") - - self.assertIn( - "Deauthorize account", [button.text.strip() for button in buttons] - ) - - -class RedditTemplateViewTestCase(TestCase): - def setUp(self): - self.user = UserFactory(email="test@test.nl", password="test") - self.client.force_login(self.user) - - self.base_url = reverse("accounts:settings:reddit-template") - self.state = str(uuid4()) - - self.patch = patch("newsreader.news.collection.reddit.post") - self.mocked_post = self.patch.start() - - def tearDown(self): - patch.stopall() - - def test_simple(self): - response = self.client.get(self.base_url) - - self.assertEquals(response.status_code, 200) - self.assertContains(response, "Return to integrations page") - - def test_successful_authorization(self): - self.mocked_post.return_value.json.return_value = { - "access_token": "1001010412", - "refresh_token": "134510143", - } - - cache.set(f"{self.user.email}-reddit-auth", self.state) - - params = {"state": self.state, "code": "Valid code"} - url = f"{self.base_url}?{urlencode(params)}" - - response = self.client.get(url) - - self.mocked_post.assert_called_once() - - self.assertEquals(response.status_code, 200) - self.assertContains(response, "Your reddit account was successfully linked.") - - self.user.refresh_from_db() - - self.assertEquals(self.user.reddit_access_token, "1001010412") - self.assertEquals(self.user.reddit_refresh_token, "134510143") - - self.assertEquals(cache.get(f"{self.user.email}-reddit-auth"), None) - - def test_error(self): - params = {"error": "Denied authorization"} - - url = f"{self.base_url}?{urlencode(params)}" - - response = self.client.get(url) - - self.assertEquals(response.status_code, 200) - self.assertContains(response, "Denied authorization") - - def test_invalid_state(self): - cache.set(f"{self.user.email}-reddit-auth", str(uuid4())) - - params = {"code": "Valid code", "state": "Invalid state"} - - url = f"{self.base_url}?{urlencode(params)}" - - response = self.client.get(url) - - self.assertEquals(response.status_code, 200) - self.assertContains( - response, "The saved state for Reddit authorization did not match" - ) - - def test_stream_error(self): - self.mocked_post.side_effect = StreamTooManyException - - cache.set(f"{self.user.email}-reddit-auth", self.state) - - params = {"state": self.state, "code": "Valid code"} - url = f"{self.base_url}?{urlencode(params)}" - - response = self.client.get(url) - - self.mocked_post.assert_called_once() - - self.assertEquals(response.status_code, 200) - self.assertContains(response, "Too many requests") - - self.user.refresh_from_db() - - self.assertEquals(self.user.reddit_access_token, None) - self.assertEquals(self.user.reddit_refresh_token, None) - - self.assertEquals(cache.get(f"{self.user.email}-reddit-auth"), self.state) - - def test_unexpected_json(self): - self.mocked_post.return_value.json.return_value = {"message": "Happy eastern"} - - cache.set(f"{self.user.email}-reddit-auth", self.state) - - params = {"state": self.state, "code": "Valid code"} - url = f"{self.base_url}?{urlencode(params)}" - - response = self.client.get(url) - - self.mocked_post.assert_called_once() - - self.assertEquals(response.status_code, 200) - self.assertContains(response, "Access and refresh token not found in response") - - self.user.refresh_from_db() - - self.assertEquals(self.user.reddit_access_token, None) - self.assertEquals(self.user.reddit_refresh_token, None) - - self.assertEquals(cache.get(f"{self.user.email}-reddit-auth"), self.state) - - -class RedditTokenRedirectViewTestCase(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.integrations.RedditTokenTask") - self.mocked_task = self.patch.start() - - def tearDown(self): - cache.clear() - - def test_simple(self): - response = self.client.get(reverse("accounts:settings:reddit-refresh")) - - self.assertRedirects(response, reverse("accounts:settings:integrations")) - - self.mocked_task.delay.assert_called_once_with(self.user.pk) - - self.assertEquals(1, cache.get(f"{self.user.email}-reddit-refresh")) - - def test_not_active(self): - cache.set(f"{self.user.email}-reddit-refresh", 1) - - response = self.client.get(reverse("accounts:settings:reddit-refresh")) - - self.assertRedirects(response, reverse("accounts:settings:integrations")) - - self.mocked_task.delay.assert_not_called() - - -class RedditRevokeRedirectViewTestCase(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.integrations.revoke_reddit_token") - self.mocked_revoke = self.patch.start() - - def test_simple(self): - self.user.reddit_access_token = "jadajadajada" - self.user.reddit_refresh_token = "jadajadajada" - self.user.save() - - self.mocked_revoke.return_value = True - - response = self.client.get(reverse("accounts:settings:reddit-revoke")) - - self.assertRedirects(response, reverse("accounts:settings:integrations")) - - self.mocked_revoke.assert_called_once_with(self.user) - - self.user.refresh_from_db() - - self.assertEquals(self.user.reddit_access_token, None) - self.assertEquals(self.user.reddit_refresh_token, None) - - def test_no_refresh_token(self): - self.user.reddit_refresh_token = None - self.user.save() - - response = self.client.get(reverse("accounts:settings:reddit-revoke")) - - self.assertRedirects(response, reverse("accounts:settings:integrations")) - - self.mocked_revoke.assert_not_called() - - def test_unsuccessful_response(self): - self.user.reddit_access_token = "jadajadajada" - self.user.reddit_refresh_token = "jadajadajada" - self.user.save() - - self.mocked_revoke.return_value = False - - response = self.client.get(reverse("accounts:settings:reddit-revoke")) - - self.assertRedirects(response, reverse("accounts:settings:integrations")) - - self.user.refresh_from_db() - - self.assertEquals(self.user.reddit_access_token, "jadajadajada") - self.assertEquals(self.user.reddit_refresh_token, "jadajadajada") - - def test_stream_exception(self): - self.user.reddit_access_token = "jadajadajada" - self.user.reddit_refresh_token = "jadajadajada" - self.user.save() - - self.mocked_revoke.side_effect = StreamException - - response = self.client.get(reverse("accounts:settings:reddit-revoke")) - - self.assertRedirects(response, reverse("accounts:settings:integrations")) - - self.user.refresh_from_db() - - self.assertEquals(self.user.reddit_access_token, "jadajadajada") - self.assertEquals(self.user.reddit_refresh_token, "jadajadajada") diff --git a/src/newsreader/accounts/urls.py b/src/newsreader/accounts/urls.py index b363f82..18a9b21 100644 --- a/src/newsreader/accounts/urls.py +++ b/src/newsreader/accounts/urls.py @@ -3,7 +3,6 @@ from django.urls import include, path from newsreader.accounts.views import ( FaviconRedirectView, - IntegrationsView, LoginView, LogoutView, PasswordChangeView, @@ -11,33 +10,11 @@ from newsreader.accounts.views import ( PasswordResetConfirmView, PasswordResetDoneView, PasswordResetView, - RedditRevokeRedirectView, - RedditTemplateView, - RedditTokenRedirectView, SettingsView, ) 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/", 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"), diff --git a/src/newsreader/accounts/views/__init__.py b/src/newsreader/accounts/views/__init__.py index e62755c..d20e6bb 100644 --- a/src/newsreader/accounts/views/__init__.py +++ b/src/newsreader/accounts/views/__init__.py @@ -1,11 +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, - RedditTemplateView, - RedditTokenRedirectView, -) from newsreader.accounts.views.password import ( PasswordChangeView, PasswordResetCompleteView, @@ -20,10 +14,6 @@ __all__ = [ "LoginView", "LogoutView", "FaviconRedirectView", - "IntegrationsView", - "RedditRevokeRedirectView", - "RedditTemplateView", - "RedditTokenRedirectView", "PasswordChangeView", "PasswordResetCompleteView", "PasswordResetConfirmView", diff --git a/src/newsreader/accounts/views/integrations.py b/src/newsreader/accounts/views/integrations.py deleted file mode 100644 index 1235195..0000000 --- a/src/newsreader/accounts/views/integrations.py +++ /dev/null @@ -1,156 +0,0 @@ -import logging - -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, TemplateView - -from newsreader.news.collection.exceptions import StreamException -from newsreader.news.collection.reddit import ( - get_reddit_access_token, - get_reddit_authorization_url, - revoke_reddit_token, -) -from newsreader.news.collection.tasks import RedditTokenTask -from newsreader.utils.views import NavListMixin - - -logger = logging.getLogger(__name__) - - -class IntegrationsView(NavListMixin, TemplateView): - template_name = "accounts/views/integrations.html" - - def get_context_data(self, **kwargs): - return { - **super().get_context_data(**kwargs), - **self.get_reddit_context(**kwargs), - } - - def get_reddit_context(self, **kwargs): - user = self.request.user - reddit_authorization_url = None - reddit_refresh_url = None - - reddit_task_active = cache.get(f"{user.email}-reddit-refresh") - - if ( - user.reddit_refresh_token - and not user.reddit_access_token - and not reddit_task_active - ): - reddit_refresh_url = reverse_lazy("accounts:settings:reddit-refresh") - - if not user.reddit_refresh_token: - reddit_authorization_url = get_reddit_authorization_url(user) - - return { - "reddit_authorization_url": reddit_authorization_url, - "reddit_refresh_url": reddit_refresh_url, - "reddit_revoke_url": ( - reverse_lazy("accounts:settings:reddit-revoke") - if not reddit_authorization_url - else None - ), - } - - -class RedditTemplateView(NavListMixin, TemplateView): - template_name = "accounts/views/reddit.html" - - def get(self, request, *args, **kwargs): - context = self.get_context_data(**kwargs) - - error = request.GET.get("error", None) - state = request.GET.get("state", None) - code = request.GET.get("code", None) - - if error: - return self.render_to_response({**context, "error": error}) - - if not code or not state: - return self.render_to_response(context) - - cached_state = cache.get(f"{request.user.email}-reddit-auth") - - if state != cached_state: - return self.render_to_response( - { - **context, - "error": _( - "The saved state for Reddit authorization did not match" - ), - } - ) - - try: - access_token, refresh_token = get_reddit_access_token(code, request.user) - - return self.render_to_response( - { - **context, - "access_token": access_token, - "refresh_token": refresh_token, - } - ) - except StreamException as e: - return self.render_to_response({**context, "error": str(e)}) - except KeyError: - return self.render_to_response( - { - **context, - "error": _("Access and refresh token not found in response"), - } - ) - - -class RedditTokenRedirectView(RedirectView): - url = reverse_lazy("accounts:settings:integrations") - - def get(self, request, *args, **kwargs): - response = super().get(request, *args, **kwargs) - - user = request.user - task_active = cache.get(f"{user.email}-reddit-refresh") - - if not task_active: - RedditTokenTask.delay(user.pk) - messages.success(request, _("Access token is being retrieved")) - cache.set(f"{user.email}-reddit-refresh", 1, 300) - return response - - messages.error(request, _("Unable to retrieve token")) - return response - - -class RedditRevokeRedirectView(RedirectView): - url = reverse_lazy("accounts:settings:integrations") - - def get(self, request, *args, **kwargs): - response = super().get(request, *args, **kwargs) - - user = request.user - - if not user.reddit_refresh_token: - messages.error(request, _("No reddit account is linked to this account")) - return response - - try: - is_revoked = revoke_reddit_token(user) - except StreamException: - logger.exception(f"Unable to revoke reddit token for {user.pk}") - - messages.error(request, _("Unable to revoke reddit token")) - return response - - if not is_revoked: - messages.error(request, _("Unable to revoke reddit token")) - return response - - user.reddit_access_token = None - user.reddit_refresh_token = None - user.save() - - messages.success(request, _("Reddit account deathorized")) - return response diff --git a/src/newsreader/conf/base.py b/src/newsreader/conf/base.py index d17234a..5bee027 100644 --- a/src/newsreader/conf/base.py +++ b/src/newsreader/conf/base.py @@ -209,16 +209,6 @@ STATICFILES_FINDERS = [ # Email EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" -# Reddit integration -REDDIT_CLIENT_ID = "CLIENT_ID" -REDDIT_CLIENT_SECRET = "CLIENT_SECRET" -REDDIT_REDIRECT_URL = ( - "http://127.0.0.1:8000/accounts/settings/integrations/reddit/callback/" -) - -# Twitter integration -TWITTER_URL = "https://twitter.com" - # Third party settings AXES_HANDLER = "axes.handlers.cache.AxesCacheHandler" AXES_CACHE = "axes" diff --git a/src/newsreader/conf/production.py b/src/newsreader/conf/production.py index 8615aa2..ea22f30 100644 --- a/src/newsreader/conf/production.py +++ b/src/newsreader/conf/production.py @@ -48,11 +48,6 @@ EMAIL_USE_SSL = bool(os.environ.get("EMAIL_USE_SSL")) VERSION = get_current_version(debug=False) ENVIRONMENT = "production" -# Reddit integration -REDDIT_CLIENT_ID = os.environ.get("REDDIT_CLIENT_ID", "") -REDDIT_CLIENT_SECRET = os.environ.get("REDDIT_CLIENT_SECRET", "") -REDDIT_REDIRECT_URL = os.environ.get("REDDIT_CALLBACK_URL", "") - # Third party settings AXES_HANDLER = "axes.handlers.database.AxesDatabaseHandler" diff --git a/src/newsreader/js/pages/homepage/App.js b/src/newsreader/js/pages/homepage/App.js index 08c0330..e840407 100644 --- a/src/newsreader/js/pages/homepage/App.js +++ b/src/newsreader/js/pages/homepage/App.js @@ -33,7 +33,6 @@ class App extends React.Component {