Compare commits

...

512 commits

Author SHA1 Message Date
e40d69d5ff Use correct settings module for development
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
2025-05-11 09:44:55 +02:00
83707701e9 Fix template formatting issues
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
2025-05-05 16:49:34 +02:00
116e2c1577 Fix cache permissions
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
see https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#run---mounttypecache
2025-05-05 16:22:07 +02:00
cf96371b90 Fix formatting errors warnings 2025-05-05 15:42:12 +02:00
eadd7a5612 Add missing command invocation
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-05-05 15:34:37 +02:00
62053a1048 Use uv image build with same python version
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline failed
2025-05-05 15:32:51 +02:00
b4340176da Use correct project name
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-05-05 15:16:48 +02:00
433ff9413d Specify javascript build target 2025-05-05 15:14:54 +02:00
91949622b7 Update woodpecker configuration
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-05-05 15:11:55 +02:00
10affeb32f Docker compose refactor
Added shell interpolation for environment variables
2025-05-05 15:02:03 +02:00
e96c6f3528 Use psycopg-binary package
To prevent building the package from source
2025-05-05 14:40:40 +02:00
a534a3b691 Move jest configuration
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline failed
2025-05-04 19:52:24 +02:00
ebbbe99eaf Update package.json 2025-05-04 19:44:55 +02:00
c7f90e233e Move prettier configuration 2025-05-04 19:44:00 +02:00
9ba6824dd3 Remove unused isort configuration 2025-05-04 19:38:45 +02:00
4c5d3aec28 Move coverage configuration to pyproject.toml 2025-05-04 19:38:26 +02:00
dd9aaf467e Add editorconfig configuration 2025-05-04 19:34:25 +02:00
1417c52007 Apply prettier formatting
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
2025-03-28 21:55:35 +01:00
bfd081337b Run formatting / fix lint errors
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-03-28 21:41:47 +01:00
b8559f0499 Remove reddit code
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-03-27 22:02:12 +01:00
b465d0bb8d Remove leftover function binding usages
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline failed
2025-03-27 21:44:21 +01:00
1a54fdbcd1 Remove function binding usage
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline failed
2025-03-24 09:17:30 +01:00
34afcc02b6 Remove requests oathlib
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
2025-03-23 21:16:36 +01:00
1574661c57 Fix ruff errors
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
2025-03-23 21:05:01 +01:00
3160becb72 Remove django-registration-redux
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-03-23 21:01:23 +01:00
105371abaf Use long command options
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-03-23 16:25:03 +01:00
ed37be0c60 Add celery healthcheck & update existing healthcheck 2025-03-23 16:24:33 +01:00
161234defd Bump rabbitmq version 2025-03-23 16:23:45 +01:00
f3ba0f1d09 Update ruff & uv usage
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline failed
ci/woodpecker/push/tests Pipeline was successful
2025-03-23 16:19:15 +01:00
aff565862c Add woodpecker CI configuration
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful
2024-12-26 20:20:21 +01:00
bf43603d65 Update versions 2024-10-13 12:52:06 +02:00
91a7f6325c Update changelog 2024-10-13 12:49:55 +02:00
e33497569a Apply query optimizations for posts 2024-10-13 10:16:57 +02:00
2d5801f226 Update changelog 2024-10-07 21:43:52 +02:00
89d4ebdc49 Add missing VERSION environment variable 2024-10-07 21:42:20 +02:00
174912a967 Update changelog 2024-10-07 21:02:03 +02:00
bb92f07f00 Use full screen height for mobile post layout 2024-10-07 21:00:03 +02:00
fa491120a0 Use line-through to indicate read status 2024-10-07 20:57:08 +02:00
ccde406193 Update CI after branch changes 2024-10-06 21:23:42 +02:00
a498417bad Update changelog 2024-10-06 20:56:09 +02:00
16ebf3bdb3 Apply ruff formatting 2024-10-06 20:47:57 +02:00
99c232fea2 Apply javascript formatting 2024-10-06 20:46:33 +02:00
fbb6405da9 Sidebar refactor 2024-10-06 20:39:05 +02:00
03b5847641 Apply formatting 2024-09-09 20:35:44 +02:00
dfb049ae14 Django 4.2 upgrade 2024-09-07 20:50:38 +02:00
b78f03d3b0 Remove twitter integration 2024-09-06 09:17:23 +02:00
e09b3d6e4c Use root user for development docker containers 2024-09-06 08:48:18 +02:00
cc5b4cc0bb Add missing migration 2024-09-06 08:47:56 +02:00
70a0d5a96d Remove drf-yasg 2024-09-06 08:41:10 +02:00
cc8aafa310 Remove deprecated ruff optiong 2024-09-05 07:08:35 +02:00
57375591b5 Use ruff for formatting/linting 2024-09-05 06:58:35 +02:00
bb74e875e0 Fix typo 2024-08-31 10:21:25 +02:00
bc8ec0257e Update unknown request tests 2024-08-31 10:08:22 +02:00
a041d5f7fa Use uv to manage requirements 2024-08-30 21:05:55 +02:00
e95c2a440e Remove pip-tools & rerun requirements 2024-08-28 09:06:58 +02:00
5fc0742688 Fix multiline linting job 2024-08-28 08:44:41 +02:00
f5f7f99f71 Fix javascript tests 2024-08-26 09:33:00 +02:00
284f64d202 Set default babel preset targets 2024-08-26 09:19:30 +02:00
b34bef899c Fix jest setup 2024-08-25 08:56:44 +02:00
aa0a29fefb Use commonjs module for testing 2024-08-24 21:19:26 +02:00
2a5372166e Add modules: false for test transforms 2024-08-24 21:03:35 +02:00
fd3bf4f542 Update babel plugins 2024-08-24 20:53:59 +02:00
c7fb545096 Update babel config 2024-08-24 20:34:24 +02:00
c7aa431e4a Move .babelrc to babel.config.json 2024-08-24 16:38:53 +02:00
3152c8f14e Update jest setup 2024-08-24 16:15:25 +02:00
9e6be5c807 Remove trailing 's' 2024-08-24 16:00:51 +02:00
106bd6cb4c Add ignore pattern & use correct transform patter 2024-08-24 15:56:14 +02:00
040193a3ed Update jest configuration 2024-08-24 15:49:09 +02:00
d8b04b3329 Remove unknown --system pip flag 2024-08-24 15:40:06 +02:00
b6805c1675 Update CI installation steps 2024-08-24 15:36:14 +02:00
07c685401f Update webpack 2024-08-24 15:19:01 +02:00
8b080a3cee Remove loose option 2024-08-24 15:15:02 +02:00
12c1ac9d17 Remove version from docker-compose 2024-08-24 15:14:42 +02:00
67d7b10632 Fix docker images 2024-08-24 15:14:04 +02:00
1b8b9dcd41 Add .nvmrc 2024-08-24 14:39:59 +02:00
35c9e78809 Update docker images 2024-08-24 14:39:10 +02:00
4935d7d186 Use node lts for CI 2024-08-13 09:22:40 +02:00
2b3e35078d Update webpack configuration 2024-08-13 09:11:54 +02:00
d05e29b5e0 Use uv for dependency management 2024-08-13 09:07:47 +02:00
e9e8fc351c Add volume notes 2024-08-10 14:26:09 +02:00
16168cc9d9 Remove proxy_redirect directive 2023-10-01 21:59:23 +02:00
9097caf438 Use production webpack configuration 2023-09-28 20:34:08 +02:00
0f89fc2447 Update version 2023-09-28 20:30:18 +02:00
b36bf4e0bc Merge branch 'master' into development 2023-09-28 20:03:07 +02:00
40749403b9 Sort posts before storing in redux 2023-09-28 20:02:27 +02:00
15884d3b4e Update CHANGELOG.md 2023-08-13 18:19:08 +02:00
40a0b72d87 Prevent observer from observing while loading posts 2023-08-13 17:45:11 +02:00
a4f5a7bdd7 Use React's ref feature 2023-08-13 17:45:11 +02:00
fedeed15c5 Use IntersectionObserver to paginate 2023-08-13 17:45:11 +02:00
ff6dfcaa05 Set DEBUG=True for gitlab configuration 2023-08-13 17:44:30 +02:00
2790e9c82e Merge branch 'master' into development 2023-07-02 13:08:12 +02:00
f0689ebfab 0.4.2 2023-07-02 12:55:19 +02:00
41f249ed5a 0.4.1 2023-07-02 11:00:47 +02:00
8e04436b68 Update changelog 2023-07-02 10:51:45 +02:00
5b59b189d6 Add missing env vars 2023-07-02 10:50:13 +02:00
8e728200ec Merge branch 'master' into development 2023-07-02 10:34:46 +02:00
8e7b059ad3 0.4.0 2023-07-02 10:23:16 +02:00
df848b1e43 Rerun black 2023-07-02 10:13:19 +02:00
e80579af4b Update changelog 2023-07-02 10:07:47 +02:00
d479b5e5f7 Update rabbitmq 2023-07-02 09:11:12 +02:00
b06af33a19 Update celery 2023-07-02 09:06:21 +02:00
858c2c6eb3 Use docker extensions for env variables 2023-07-02 08:24:11 +02:00
72f8426f72 Downgrade django-celery-beat
As this is the last version support django 2.2
2023-07-01 20:44:08 +02:00
1aea2df2ea Update cache settings 2023-06-29 10:04:45 +02:00
492b8d33ff Add missing build target for development celery 2023-06-29 09:43:43 +02:00
cbc6a73b76 Remove duplicate setting 2023-06-29 09:35:34 +02:00
4b04178a4f Set env files explicitly 2023-06-29 09:35:24 +02:00
ba4b17a8e2 Set correct default settings module 2023-06-28 19:56:06 +02:00
70a1ae306b Set correct celery broker URL 2023-06-28 19:55:19 +02:00
b91f5c8939 Add missing env_file setting & remove redundant database settings 2023-06-28 19:42:44 +02:00
5a73707d61 Split production dependecies & update production configuration 2023-06-28 19:38:44 +02:00
0f66c5eb9b Rebuild dependencies with python 3.9 for now 2023-06-28 09:57:44 +02:00
7f4a3a3e49 Update logging configuration for gitlab 2023-06-28 08:43:05 +02:00
9258d33f4e Update gitlab configuration 2023-06-28 08:37:28 +02:00
a9741d4063 Use django docker image for CI 2023-06-27 20:50:00 +02:00
61827b955d Use debug celery logging for development 2023-06-27 20:47:08 +02:00
b03f2fc902 Update fixture 2023-06-27 20:45:15 +02:00
89d88ccceb Update logging configuration 2023-06-27 20:37:47 +02:00
2a0c0072a4 Update gitlab configuration 2023-06-27 10:29:07 +02:00
6a46dc01e2 Remove variable defaults
These are not set whenever a merged compose file is used
2023-06-27 10:11:07 +02:00
60af3ba4f6 Set development env file 2023-06-27 09:35:33 +02:00
65dae40e9a Update npm packages 2023-06-27 09:35:24 +02:00
bfacd97c73 Update gitignore 2023-06-27 09:28:15 +02:00
3ebba6df47 Use more enviroment variables 2023-06-27 09:06:24 +02:00
b8a9d885f5 Use older python directories for now 2023-06-27 08:35:28 +02:00
fd5f910ac0 Remove redundant override 2023-06-27 08:35:07 +02:00
6ac4e5d5c2 Downgrade docker images for now 2023-06-26 21:25:41 +02:00
ef0c070755 Update docker django image 2023-06-26 20:27:58 +02:00
59f719d7c3 Update gitlab configuration 2023-06-26 20:25:36 +02:00
720f6fdb78 Use Makefile to generate requirements 2023-06-26 20:21:00 +02:00
82a7176629 Increase healthcheck interval 2023-03-05 15:32:15 +01:00
89f23fe668 Initial refactor 2023-03-05 15:21:04 +01:00
12b4aa0b91 Remove unused imports 2022-05-26 12:58:33 +02:00
c48de9c6e1 Rerun isort 2022-05-26 12:00:20 +02:00
e5220eb9a5 Remove deprecated option isort option 2022-05-26 11:59:31 +02:00
1f0a8a54da Use quiet option for CI jobs 2022-05-26 11:57:10 +02:00
bea7afb355 Use python 3.9 to build dependencies 2022-05-26 11:54:21 +02:00
bd48634509 Use coverage run command 2022-05-26 11:44:54 +02:00
d3f9a11f44 Replace node-sass with dart sass 2022-05-26 11:39:59 +02:00
9d05cac15c Update gitlab jobs 2022-05-26 11:15:46 +02:00
20309e70fa Use pip-tools to manage dependencies 2022-05-26 11:12:22 +02:00
53aa8da2dd Update twitter error handling 2021-09-25 10:39:43 +02:00
f5b708aafe Change loading posts from 80% to 100% 2021-07-24 20:18:42 +02:00
85e152f6e8 Update dependencies 2021-07-21 21:14:24 +02:00
da05b3ac2e Add scroll to top/bottom component 2021-07-21 21:13:22 +02:00
e6cfef8d96 Update dependencies 2021-07-08 20:26:14 +02:00
04d95386f5 Update theme js 2021-06-30 21:30:57 +02:00
879c6ebc90 Merge branch 'master' into development 2021-06-30 21:12:46 +02:00
6b2c4996d5 0.3.13.8 2021-06-30 21:12:30 +02:00
7d36763eff Bump versions 2021-06-30 21:11:48 +02:00
ba5001fafa Update dependencies 2021-06-30 21:04:15 +02:00
106a087291 Fix forms not saving 2021-06-30 21:03:17 +02:00
426f857f05 Merge branch 'master' into development 2021-04-27 12:42:15 +02:00
510f7187a8 0.3.13.7 2021-04-27 12:42:02 +02:00
ea541bfe64 Bump version 2021-04-27 12:36:39 +02:00
83829b7d19 Check for Twitter error codes
Important for expired tokens as 401's are returned for various reasons
2021-04-27 12:33:12 +02:00
b67724220a Merge branch 'master' into development 2021-04-25 20:26:59 +02:00
8498303006 0.3.13.6 2021-04-25 20:26:43 +02:00
8f37eec519 Bump version 2021-04-25 20:26:21 +02:00
48d48885c4 Check for response attribute 2021-04-25 20:21:03 +02:00
9e572534aa Use server side PII rules 2021-04-25 20:17:36 +02:00
392900956c Revert "Fix commit hash not displaying in sentry release"
This reverts commit f326a4c923.
2021-04-25 12:53:55 +02:00
b106ebf827 Load dotenv on all environments 2021-04-25 12:53:43 +02:00
f326a4c923 Fix commit hash not displaying in sentry release 2021-04-25 12:49:20 +02:00
fee2a4f17b Use sentry in all environments
It still is optional though
2021-04-25 12:15:12 +02:00
db5780f9f1 Merge branch 'master' into development 2021-04-24 21:13:53 +02:00
d89e1bc6d4 0.3.13.5 2021-04-24 21:13:39 +02:00
9c5378cf67 Bump version 2021-04-24 21:08:45 +02:00
02cbaeb491 Set response keyword 2021-04-24 20:51:01 +02:00
9b1408160d Merge branch 'master' into development 2021-04-24 20:09:20 +02:00
8af8dab6db 0.3.13.4 2021-04-24 20:09:06 +02:00
782671542f Bump version number 2021-04-24 20:02:22 +02:00
e42653f7fd Fix import error 2021-04-24 19:56:28 +02:00
223656f2d2 Merge branch 'master' into development 2021-04-24 15:10:53 +02:00
e008d2f53c 0.3.13.3 2021-04-24 15:06:12 +02:00
501022db3b Bump version numbers 2021-04-24 15:05:50 +02:00
3c4e659bc8 Use sentry's set_extra for debug purposes 2021-04-24 15:04:55 +02:00
104a5575fa Merge branch 'master' into development 2021-04-24 14:24:09 +02:00
0241de95cd 0.3.13.2 2021-04-24 14:21:06 +02:00
d1badbef30 Bump versions 2021-04-24 14:19:51 +02:00
ecb99425e0 Update sentry-sdk 2021-04-24 14:18:33 +02:00
2afeb3c102 Merge branch 'master' into development 2021-04-24 13:37:42 +02:00
1a1bfdfbab 0.3.13.1 2021-04-24 13:29:34 +02:00
1e8a3aedb1 Bump version 2021-04-24 13:27:49 +02:00
5603b23468 Temporarly set exception level for Twitter denied exceptions 2021-04-24 13:24:16 +02:00
0ba632a2a6 Prevent mutual exclusive exception 2021-04-24 13:11:43 +02:00
813222073e Merge branch 'master' into development 2021-04-23 23:05:10 +02:00
712f601e9c 0.3.13 2021-04-23 23:01:31 +02:00
2ed828a243 Update versioning numbers 2021-04-23 23:00:09 +02:00
679dc2a0d6 Update django version 2021-04-23 22:42:18 +02:00
f02a7b6eb7 Send re-authentication notification emails 2021-04-23 22:25:35 +02:00
101058672b Merge branch 'master' into development 2021-03-06 18:44:06 +01:00
73bac1301b 0.3.12.1 2021-03-06 18:43:48 +01:00
3aa3c29613 Bump version 2021-03-06 18:41:41 +01:00
146401117b Add missing background-color 2021-03-06 18:40:27 +01:00
1909916b64 Merge branch 'master' into development 2021-03-06 18:21:47 +01:00
6d09629c8e 0.3.12 2021-03-06 18:21:29 +01:00
e99b6653d8 Bump version 2021-03-06 17:43:16 +01:00
85e02a8147 Add sticky post header 2021-03-06 17:40:36 +01:00
6db7d6c3f5 Update light theme & add sticky navbar 2021-03-06 17:20:22 +01:00
4d0613df2e Merge branch 'master' into development 2021-02-27 16:00:07 +01:00
18dbf2d312 0.3.11 2021-02-27 15:59:50 +01:00
0e108c8110 Bump version numbers 2021-02-27 15:54:34 +01:00
c53e9756dd Update django version 2021-02-27 15:51:44 +01:00
ab1e4c44ec Add saved posts section 2021-02-27 15:50:11 +01:00
8c69e4a27e Merge branch 'master' into development 2021-02-19 09:28:32 +01:00
66edc1e8dd 0.3.10 2021-02-19 09:28:04 +01:00
0eefafe3db Bump version number 2021-02-19 09:23:47 +01:00
9c88cfde59 Add specific color for confirm buttons 2021-02-19 09:22:01 +01:00
a24d06b257 Update font sizes 2021-02-19 09:13:59 +01:00
fef4729e0b Update dependencies 2021-02-18 22:25:54 +01:00
8b7850b17b Merge branch 'master' into development 2021-02-18 22:15:24 +01:00
c61ce0bcb7 0.3.9 2021-02-18 22:14:47 +01:00
091bcdbef3 Bump version numbers 2021-02-18 22:09:02 +01:00
90cb3ad1d4 Bump django version 2021-02-18 22:05:37 +01:00
dfa43fa8a2 Cursor based pagination
Fixes #70
2021-02-18 22:04:15 +01:00
91d1757bde Merge branch 'master' into development 2021-02-07 16:40:03 +01:00
00164bd3b5 0.3.8 2021-02-07 13:39:39 +01:00
f0a1179d23 Update changelog 2021-02-07 13:27:47 +01:00
ee5f59fd7c Update deploy job 2021-02-07 13:26:36 +01:00
039e8b803d Styling changes
- Replace css.gg with fontawesome
- Update more colors (for light & dark themes)
2021-02-07 13:24:44 +01:00
439a54c0ce Merge branch 'master' into development 2021-01-23 17:15:06 +01:00
cf078ee42a 0.3.7 2021-01-23 17:10:43 +01:00
9e25f14c73 Update changelog 2021-01-23 17:10:10 +01:00
282d64a923 Add dark theme
Fixes #69
2021-01-23 17:02:15 +01:00
9095f35545 Update object representations
Fixes #71
2021-01-17 21:44:54 +01:00
4496972205 Move sentry dependency to optional dependencies
Fixes #72
2021-01-17 21:26:20 +01:00
d2a1fd7f3a Use changelog for listing changes 2020-12-20 21:13:43 +01:00
e251f633e1 Specify tag when deploying 2020-12-20 16:44:35 +01:00
0ac8842431 Remove change of working dir
See https://git.fudiggity.nl/sonny/newsreader/-/jobs/4124 for correct deploy flow
2020-12-20 12:58:03 +01:00
55eee6c6ed Merge branch 'master' into development 2020-12-20 12:56:24 +01:00
01f86399b2 0.3.6.3
- Update deploy job
2020-12-20 12:48:12 +01:00
32e85ec05e 0.3.6.2
- Use warning logging level for BuilderSkippedException's
- Change working directory before running ansible
2020-12-20 12:40:07 +01:00
4cb3846e36 Change directory before running ansible 2020-12-20 12:39:21 +01:00
06e4ea33b5 Don't use error logging level for BuilderSkippedExceptions 2020-12-20 12:13:46 +01:00
ceaee1165b Merge branch 'master' into development 2020-12-19 22:14:11 +01:00
db25e240e3 0.3.6.1
- Install ansible required roles
2020-12-19 21:52:25 +01:00
73401b6ca3 Install ansible required roles 2020-12-19 21:52:01 +01:00
517a89d2da Merge branch 'master' into development 2020-12-19 21:04:38 +01:00
04af0f9c5d 0.3.6
- Update deploy job
- Add user manageable reddit filters
2020-12-19 21:03:29 +01:00
57dcabd685 Remove TODO's 2020-12-19 20:57:07 +01:00
f98220f8cc Add user manageable reddit filters 2020-12-19 20:47:18 +01:00
73e823bb05 Update deploy job
The seperated newsreader repo will be used from now on
2020-11-24 12:22:04 +01:00
06bc705c00 Use max-content for post items 2020-11-01 14:02:58 +01:00
4cdb16b2c1 Merge branch 'master' into development 2020-10-31 14:32:53 +01:00
116b6d1308 0.3.5
- Show timezone next to post datetimes
- Take read status in consideration when sorting posts
2020-10-31 14:31:27 +01:00
c1d11ae94e Take read status in consideration when sorting 2020-10-31 14:26:27 +01:00
29f20cca24 Make auto marking optional through a setting
Fixes #68
2020-10-30 23:01:22 +01:00
ffefc76acc Show correct timezone
Timezone is for now converted to django's TIME_ZONE setting for all users
2020-10-27 22:38:49 +01:00
9ad6a1a7b8 Revert "Show timezone in posts"
This reverts commit 6ae3b5c508.

Datetimes will be converted to the timezone set by the TIME_ZONE django setting
2020-10-27 20:19:58 +01:00
6ae3b5c508 Show timezone in posts
Fixes #67
2020-10-26 22:18:23 +01:00
0a1bf0d5e6 Import from base in docker settings 2020-10-17 22:14:45 +02:00
aaef828837 Merge branch 'master' into development 2020-10-17 17:31:03 +02:00
ee9b36d8ae 0.3.4
- Use warnings for BuilderDuplicateExceptions
- Show user known urls in rules list view
2020-10-17 17:29:42 +02:00
00e0705d12 Show user known urls in rules list view 2020-10-17 17:23:51 +02:00
ccc9726c8a Log warnings for duplicates 2020-10-17 17:13:30 +02:00
ec4f1c9300 Merge branch 'master' into development 2020-10-17 13:21:23 +02:00
9e5e05c056 0.3.3
- Update static configuration
- Builder refactor
- Fix for images stretching to far
2020-10-17 13:19:49 +02:00
51ffd82648 Only clear known static nested folders on startup 2020-10-17 12:01:30 +02:00
ab7a4d9a8a Dont remove fonts after rebuilding 2020-10-15 22:40:45 +02:00
003889d29e Fix images stretching full width 2020-10-15 19:47:02 +02:00
195597afa0 Split rules view buttons into seperate sections 2020-10-15 19:35:48 +02:00
4b9de97d70 Rename feeds -> sources 2020-10-15 19:32:19 +02:00
763d8ee093 Refactor builders to use custom exceptions 2020-10-15 19:30:53 +02:00
b0c6714002 Merge branch 'master' into development 2020-10-06 22:52:43 +02:00
b6921a20e7 0.3.2
- Add user runnable favicon task
- Update messages styling
2020-10-06 22:51:23 +02:00
1c3a33c1d8 Fix failing test 2020-10-06 22:46:33 +02:00
593b06006c Fix broken view 2020-10-06 22:37:14 +02:00
f12639987f Update messages styling 2020-10-06 22:34:24 +02:00
48388a47f6 Add user runnable favicon task 2020-10-06 22:06:19 +02:00
77103eb680 Merge branch 'master' into development 2020-09-27 16:45:49 +02:00
ca5c2f6b55 0.3.1
Use ansible repo's master branch for deployments
2020-09-27 16:45:28 +02:00
d228dc5f45 Merge branch 'master' into development 2020-09-27 16:25:41 +02:00
d4a41a62da 0.3.0
- Add Twitter integration
- Refactor alot of existing code in collection app
- Update webpack font configuration
2020-09-27 16:19:32 +02:00
576ab9a917 Fix isort errors 2020-09-27 16:13:59 +02:00
40a027587b Add Twitter integration
Fixes #46
2020-09-27 16:08:30 +02:00
a7b4271a7d Update font configuration
Fixes #63, See https://webpack.js.org/loaders/file-loader/#publicpath
2020-09-09 20:30:59 +02:00
6120b26a44 Update logging configuration 2020-09-09 19:58:09 +02:00
4074df3f09 Merge branch 'master' into development 2020-09-02 22:10:56 +02:00
805321f66d 0.2.6.4
Update deploy job
2020-09-02 21:37:33 +02:00
84802fd48b Merge branch 'master' into development 2020-09-02 21:10:53 +02:00
65e4f3bb80 0.2.6.3
- Fallback to variable for vault password as file variables get execute permission set
2020-09-01 22:27:31 +02:00
f0df342f61 0.2.6.2
- Update deploy job to use file variables
- Fix truncating values with exotic characters
2020-09-01 22:07:49 +02:00
47eaef40b3 Update deploy job to use file variables 2020-09-01 22:02:47 +02:00
0d9163d363 Fix truncating exotic values
Fixes #65
2020-09-01 19:48:04 +02:00
64a3d2aab5 Merge branch 'master' into development 2020-08-31 23:09:01 +02:00
b035526848 Fix npm vulnerabilities & update deploy job 2020-08-31 23:08:37 +02:00
af7fbaf1e8 Merge branch 'master' into development 2020-08-31 22:49:14 +02:00
30bd140483 0.2.6
- Fix sorting posts by rule
2020-08-31 22:42:18 +02:00
7ee5ad7879 Merge branch 'post-sorting' into 'development'
Fix post sorting by rule

See merge request sonny/newsreader!36
2020-08-31 22:38:59 +02:00
1429e5a7ec Fix post sorting by rule 2020-08-31 22:38:59 +02:00
e0af3dcc20 Merge branch 'master' into development 2020-08-12 20:40:46 +02:00
c94158a3a6 Make vault file executable 2020-08-12 20:31:43 +02:00
34c5318c42 Update deploy job 2020-08-12 20:22:00 +02:00
52a71a3f4e Fix FeedTask collecting reddit rules & update deploy job 2020-08-12 19:58:59 +02:00
d14aff1baa Update deploy job 2020-08-12 19:48:31 +02:00
03ac016dd3 Fix FeedTask collecting reddit rules 2020-08-12 19:38:16 +02:00
e58c5a4559 Merge branch 'master' into development 2020-08-12 09:54:56 +02:00
128284dca3 Show URL's during feed exceptions & use version number in User-Agent 2020-08-12 09:35:45 +02:00
ad51d17d2d Show feed URL's when catching feed client exceptions 2020-08-12 09:31:50 +02:00
bd9573cebc Show current version number in user agent 2020-08-05 21:39:13 +02:00
285da805cb Merge branch 'master' into development 2020-08-05 21:34:34 +02:00
aff108d7fc Allow using non-annotated tags for version 2020-08-04 22:30:17 +02:00
6fb848d90e Fix wrong url in deploy job 2020-08-04 21:06:54 +02:00
cad6a62a17 Merge branch 'development' into 'master'
Development

See merge request sonny/newsreader!35
2020-08-04 20:56:22 +02:00
a820155fc0 Fix category action test
This was the same test as before -.-
2020-08-04 20:56:22 +02:00
78bc696294 Fix white text in transparent error messages 2020-08-04 20:34:09 +02:00
286971649a Add version number to django settings
Fixes #54
2020-08-04 20:20:28 +02:00
4bca6a432f Fix invalid release job 2020-07-30 23:14:15 +02:00
7adb1cddb8 Add release job & update deploy job 2020-07-30 23:09:46 +02:00
cba167c98c Merge branch 'master' into development 2020-07-29 22:50:14 +02:00
bea0257cae Update CI jobs 2020-07-29 22:47:32 +02:00
7af681887f Update CI jobs 2020-07-29 22:43:42 +02:00
6ff996b674 Merge branch 'master' into development 2020-07-27 22:33:01 +02:00
7dab98ef5a Show error info in rule detail pages 2020-07-27 21:11:31 +02:00
7d803bbfa0 Show rule errors
Fixes #56
2020-07-27 20:56:01 +02:00
5a7222d9a4 Merge branch 'master' into development 2020-07-26 22:10:01 +02:00
fd432a87d1 Merge branch 'development' into 'master'
Various improvements

See merge request sonny/newsreader!33
2020-07-26 21:45:51 +02:00
b25cdcf4db Fix category action test
This was the same test as before -.-
2020-07-26 21:45:51 +02:00
a1c11baa3d Update table row error styling 2020-07-26 21:23:19 +02:00
fd16478909 Update rule table styling 2020-07-26 19:41:17 +02:00
6a13c58712 Show icons instead of boolean values in rules table
Fixes #58
2020-07-26 19:02:51 +02:00
629b35b69b Update post modal list styling
Fixes #60
2020-07-26 18:39:49 +02:00
c04f23b3ee Dont't overwrite default checkbox
This caused issues in the admin
2020-07-26 18:33:52 +02:00
2b10612304 Add branding to admin 2020-07-26 12:32:22 +02:00
c52c30fb08 Update default fixture 2020-07-26 12:03:30 +02:00
632b3b14f1 Extend UserAdmin
To allow changing password
2020-07-26 11:57:48 +02:00
ac9e6a7224 Remove rounded component styling 2020-07-25 22:11:41 +02:00
5ec7239a1b Merge branch 'master' into development 2020-07-25 16:55:50 +02:00
f513a494d9 Merge branch 'development' into 'master'
Fix multiline yaml

See merge request sonny/newsreader!32
2020-07-25 16:55:23 +02:00
9d6a79d55d Squashed commit of the following:
commit f1db9b9dc1026760a43028e548572db4e639976e
Author: Sonny <sonnyba871@gmail.com>
Date:   Mon Mar 16 20:47:15 2020 +0100

    Add port setting
2020-07-25 16:55:23 +02:00
2d640eac04 Merge branch 'master' into development 2020-07-25 16:54:18 +02:00
4df50b3a16 Fix multiline yaml statement 2020-07-25 16:53:13 +02:00
96422a1532 Merge branch 'development' into 'master'
0.2.4.4

- Update deploy job

See merge request sonny/newsreader!31
2020-07-25 16:40:37 +02:00
2a40311a87 Update deploy job 2020-07-25 16:40:37 +02:00
2e56d3208b Update deploy job 2020-07-25 16:35:28 +02:00
96f016260d Merge branch 'master' into development 2020-07-22 23:51:10 +02:00
1cae7e0b06 Merge branch 'development' into 'master'
0.2.4.3

See merge request sonny/newsreader!30
2020-07-22 23:49:18 +02:00
a6827e604d Replace rest_framework_swagger with drf_yasg
rest_framework is deprecated see https://github.com/marcgibbons/django-rest-swagger#django-rest-swagger-deprecated-2019-06-04
2020-07-22 23:49:18 +02:00
e73edd5083 Adjust reddit token expiry loglevel
Fixes #51
2020-07-22 23:39:37 +02:00
a3a812b949 Merge branch 'fix-reddit-form-urls' into 'development'
Fix reddit form urls

Closes #53

See merge request sonny/newsreader!29
2020-07-22 23:29:23 +02:00
9a212c6288 Fix reddit form urls 2020-07-22 23:29:23 +02:00
0f5e9e7fca Fix post modal rules not linking properly 2020-07-22 22:56:00 +02:00
296202b69f Merge branch 'fix-reddit-post-urls' into 'development'
Fix reddit post urls

Closes #52

See merge request sonny/newsreader!28
2020-07-22 22:50:17 +02:00
863e8671da Fix reddit post urls 2020-07-22 22:50:17 +02:00
21dff8eb15 Show reddit images/videos or a direct url whenever possbile 2020-07-21 23:04:16 +02:00
b5b174a213 Merge branch 'master' into development 2020-07-13 23:48:00 +02:00
ec45a661e5 Merge branch 'development' into 'master'
0.2.4.2

- Fix reddit duplicate posts

See merge request sonny/newsreader!27
2020-07-13 23:18:52 +02:00
e8947d1182 Squashed commit of the following:
commit 99fd94580f95dcbfb77b73e2de846f76a5709ef9
Author: Sonny <sonnyba871@gmail.com>
Date:   Sat Feb 15 21:45:16 2020 +0100

    Use postgres password

    As of https://gitlab.com/gitlab-com/support-forum/issues/5199
2020-07-13 23:18:52 +02:00
ed8584603e Deduplicate reddit posts 2020-07-13 23:14:43 +02:00
24b1704da6 Update subreddit helptext 2020-07-12 22:33:40 +02:00
5ce5c5cfe1 Set reddit callback url through env var 2020-07-12 22:26:49 +02:00
046117cfb3 Merge branch 'master' into development 2020-07-12 21:09:49 +02:00
fb03d6b86f Merge branch 'development' into 'master'
0.2.4.1

- Fix Reddit redirect url

See merge request sonny/newsreader!26
2020-07-12 20:56:47 +02:00
7d28dd854f Squashed commit of the following:
commit 99fd94580f95dcbfb77b73e2de846f76a5709ef9
Author: Sonny <sonnyba871@gmail.com>
Date:   Sat Feb 15 21:45:16 2020 +0100

    Use postgres password

    As of https://gitlab.com/gitlab-com/support-forum/issues/5199
2020-07-12 20:56:47 +02:00
7cef924c37 Fix reddit redirect url 2020-07-12 20:52:56 +02:00
e57a4c656d Merge branch 'master' into development 2020-07-12 20:50:28 +02:00
854d086340 Merge branch 'development' into 'master'
0.2.4

- Add Reddit integration
- Fix admin favicon

See merge request sonny/newsreader!25
2020-07-12 20:26:14 +02:00
177755e302 Squashed commit of the following:
commit 99fd94580f95dcbfb77b73e2de846f76a5709ef9
Author: Sonny <sonnyba871@gmail.com>
Date:   Sat Feb 15 21:45:16 2020 +0100

    Use postgres password

    As of https://gitlab.com/gitlab-com/support-forum/issues/5199
2020-07-12 20:26:14 +02:00
d4b58624d6 Fix favicon not showing in admin 2020-07-12 20:22:23 +02:00
ec72827bd8 Merge branch '25-sub-reddit-integration' into 'development'
Resolve "(Sub)Reddit integration"

Closes #25

See merge request sonny/newsreader!13
2020-07-12 20:10:57 +02:00
6ce013d0d4 Add reddit integration 2020-07-12 20:10:57 +02:00
6f30571dd1 Merge branch 'master' into development 2020-06-30 22:58:52 +02:00
391796a0c0 0.2.3.10
- Fixed tasks using disabled rules
2020-06-30 22:57:49 +02:00
03d673e6c0 Fixed task passing through disabled rules 2020-06-30 20:27:17 +02:00
78608b2b57 Merge branch 'master' into development 2020-06-29 21:28:43 +02:00
04043fbe98 0.2.3.9
-Ugh this should be fixed now
2020-06-29 21:06:38 +02:00
2254f22023 0.2.3.8
-Update logging configuration
-Fix sentry import error
2020-06-29 20:57:35 +02:00
fcf49fa123 Update logging calls 2020-06-29 20:54:46 +02:00
73ddb785e0 Update logging 2020-06-29 20:42:58 +02:00
3c080e1300 Merge branch 'master' into development 2020-06-28 23:37:35 +02:00
1993338120 0.2.3.7
- Add optional sentry integration
2020-06-28 20:42:44 +02:00
6661b69094 Optionally load sentry 2020-06-28 20:35:58 +02:00
eb09c4f729 Merge branch 'master' into development 2020-06-18 20:31:56 +02:00
2be35bce53 0.2.3.6
- Update logging
- Update FeedDuplicateHandler
2020-06-18 20:29:48 +02:00
017dd9a582 Make poetry/pip less verbose 2020-06-18 20:23:47 +02:00
bcd051e992 Update logging 2020-06-18 20:18:05 +02:00
a46b6fade5 Merge branch 'update-duplicate-handler' into 'development'
Update duplicate handler

See merge request sonny/newsreader!24
2020-06-18 20:07:37 +02:00
0bd47f1bb0 Update duplicate handler 2020-06-18 20:07:37 +02:00
68bba5f835 Merge branch 'master' into development 2020-06-16 21:22:31 +02:00
00f6427c57 0.2.3.5
- Fix sidebar category overflow
2020-06-16 21:04:49 +02:00
722afe8c12 Fix sidebar category overflow 2020-06-16 20:56:38 +02:00
083f728404 Use celery configuration similar to production 2020-06-16 20:48:21 +02:00
c8771cb272 Update post admin 2020-06-16 09:24:52 +02:00
d3ccc16f81 Merge branch 'master' into development 2020-06-16 09:07:16 +02:00
f2200754e8 Merge branch 'development' into 'master'
0.2.3.4

See merge request sonny/newsreader!23
2020-06-16 09:04:35 +02:00
061a2e852d 0.2.3.3
- Fix admin
2020-06-16 09:04:34 +02:00
4dfa755881 Use django.forms.renderers.TemplatesSetting
As this doesn't ignore the DIRS setting in TEMPLATES
2020-06-15 22:56:06 +02:00
18c0046e61 Merge branch 'master' into development 2020-06-07 21:37:47 +02:00
86ced1a62b Merge branch 'development' into 'master'
0.2.3.3

See merge request sonny/newsreader!22
2020-06-07 21:32:49 +02:00
6609b1306b Update django version 2020-06-07 21:32:49 +02:00
3b2384a266 Update django version 2020-06-07 21:29:21 +02:00
2d3eae6e39 Remove django entrypoint script 2020-06-07 10:29:51 +02:00
cbc6d646ce Merge branch 'master' into development 2020-06-03 22:14:50 +02:00
5e13de6c8b Merge branch 'development' into 'master'
0.2.3.1

See merge request sonny/newsreader!21
2020-06-03 20:49:35 +02:00
04ff905ae5 Merge branch 'development' into 'master'
0.2.3.1
2020-06-03 20:49:35 +02:00
ddfd208d1c Merge frontend redesign 2020-06-03 20:46:01 +02:00
27ed0259a1 Merge branch 'master' into development 2020-05-24 13:35:14 +02:00
a22ef354be Update duplicate checker
- deduplicate collected entries
- set default fallback publication_date
2020-05-24 13:28:05 +02:00
61ddef3b63 Merge branch 'master' into development 2020-05-23 17:02:57 +02:00
e3840342b3 Update duplicate handler & publication date saving 2020-05-23 16:53:05 +02:00
a4b5373ed2 Add form / card components & refactor forms 2020-05-23 13:10:46 +02:00
13d33749da Merge branch '20-settings-page' into 'development'
Resolve "Settings page"

Closes #20

See merge request sonny/newsreader!18
2020-05-11 22:44:19 +02:00
bb47e2af8d Resolve "Settings page" 2020-05-11 22:44:19 +02:00
f4adb9635a Skip disabled rules 2020-05-10 23:04:57 +02:00
aeb85bd2cf Fix old url reference 2020-05-10 22:50:13 +02:00
69eaedf89c Rerun autoflake 2020-05-10 22:43:37 +02:00
3c613b59e2 Merge branch '50-user-admin' into 'development'
Resolve "User admin"

Closes #50

See merge request sonny/newsreader!17
2020-05-10 22:36:03 +02:00
ed415f2b5c Resolve "User admin" 2020-05-10 22:36:03 +02:00
d6d19fa9b9 Fix isort warning 2020-05-10 21:58:44 +02:00
7ee727e96e Move nested collection & core app urls inside news app 2020-05-10 21:52:54 +02:00
9285672273 Merge branch '48-feeds-list-view' into 'development'
Resolve "Feeds list view"

Closes #48

See merge request sonny/newsreader!16
2020-05-10 20:11:12 +02:00
bec3488e63 Resolve "Feeds list view" 2020-05-10 20:11:12 +02:00
7fc899937d Refactor much needed docker setup
- Build static files inside seperate container
- Remove unnecessary env variables
2020-05-02 20:21:22 +02:00
d61b3a9498 Merge branch 'master' into development 2020-04-26 21:29:15 +02:00
57e9073f6b Allow admins to be specified through env variables 2020-04-26 21:17:55 +02:00
62da6e0d8e Fix wrong axes handler 2020-04-26 21:12:02 +02:00
b811d1945b Update logging 2020-04-26 21:06:29 +02:00
3152254f43 Merge branch 'master' into development 2020-04-22 23:16:05 +02:00
e9f05868c1 Update data migration 2020-04-22 23:11:24 +02:00
708076b2ab Change collection task to class based task & update behavior 2020-04-22 23:04:53 +02:00
992df528f6 Merge branch 'master' into development 2020-04-19 21:26:22 +02:00
6ff26c71a0 Update navbar styling 2020-04-19 21:13:38 +02:00
9b56cf53e3 Merge branch '44-error-pages' into 'development'
Resolve "Error pages"

Closes #44

See merge request sonny/newsreader!14
2020-04-19 20:59:14 +02:00
6f00da37b9 Resolve "Error pages" 2020-04-19 20:59:14 +02:00
cac71d5475 Merge branch 'master' into development 2020-04-15 22:08:17 +02:00
7d86cea6ec Update project / gitlab ci settings 2020-04-15 21:59:34 +02:00
e495d7c188 Use poetry for dependency management 2020-04-13 17:06:31 +02:00
cda2654573 Merge branch '41-javascript-error-handling' into 'development'
Resolve "Javascript error handling"

Closes #41

See merge request sonny/newsreader!12
2020-04-11 09:49:08 +02:00
ae942f5ef9 Fix category action test
This was the same test as before -.-
2020-04-11 09:49:07 +02:00
73ae0271e4 Update npm commands 2020-03-25 22:38:56 +01:00
66408cc218 Merge branch '42-replace-gulp-with-webpack' into 'development'
Resolve "Replace Gulp with Webpack"

Closes #42

See merge request sonny/newsreader!11
2020-03-25 22:24:32 +01:00
b28d42b97b Resolve "Replace Gulp with Webpack" 2020-03-25 22:24:32 +01:00
16230a11f3 Merge branch '39-celery-task-deduplication' into 'development'
Resolve "Celery task deduplication"

Closes #39

See merge request sonny/newsreader!10
2020-03-24 19:40:33 +01:00
a3a2033e37 Resolve "Celery task deduplication" 2020-03-24 19:40:33 +01:00
7c888c1461 Merge branch 'new-icons' into 'development'
Icon refactor

See merge request sonny/newsreader!8
2020-03-22 22:26:14 +01:00
ab5d9ea46d Icon refactor 2020-03-22 22:26:14 +01:00
dc2fb681d0 Merge branch '40-set-default_from_email' into 'development'
Resolve "Set DEFAULT_FROM_EMAIL"

Closes #40

See merge request sonny/newsreader!9
2020-03-22 20:55:11 +01:00
fdb90525a8 Set default_from_email 2020-03-22 20:50:51 +01:00
6bfb84dab9 Remove manual tag & multiline statement 2020-03-22 20:27:15 +01:00
b5b59c5baf Remove deploy_hosts 2020-03-22 20:18:28 +01:00
770ace2f5d Set correct file permissions 2020-03-22 19:24:26 +01:00
f6100416c3 Add (known) ssh host key 2020-03-22 19:17:54 +01:00
99ba773b91 Another attempt 2020-03-22 19:03:37 +01:00
fdec1efcf0 Attempt 2 2020-03-22 18:50:40 +01:00
9ecca7a80b Install git in deploy stage 2020-03-22 18:41:00 +01:00
e4e4e97cfd Add a deployment stage 2020-03-22 18:34:05 +01:00
420481f18a Squashed commit of the following:
commit f1db9b9dc1026760a43028e548572db4e639976e
Author: Sonny <sonnyba871@gmail.com>
Date:   Mon Mar 16 20:47:15 2020 +0100

    Add port setting
2020-03-20 21:58:22 +01:00
4da301eb3e Add production settings 2020-03-15 21:10:10 +01:00
4345a006a6 Merge branch 'fix-data-error' into 'development'
Fix data errors

See merge request sonny/newsreader!7
2020-03-05 00:02:40 +01:00
533561ba1e Fix data errors 2020-03-05 00:02:40 +01:00
afc3c11775 Set celery logging level 2020-03-02 23:34:57 +01:00
8b47fd8216 Merge branch '28-swagger-response-examples' into 'development'
Resolve "Swagger response examples"

Closes #28

See merge request sonny/newsreader!6
2020-03-02 20:14:38 +01:00
a87d4f387f Replace rest_framework_swagger with drf_yasg
rest_framework is deprecated see https://github.com/marcgibbons/django-rest-swagger#django-rest-swagger-deprecated-2019-06-04
2020-03-02 20:14:38 +01:00
acd9bd30cb Update isort 2020-03-01 22:35:48 +01:00
c22fdfe4ce More formatting ugh 2020-03-01 22:31:03 +01:00
21b9e4f0fd Set black to default line length (88) 2020-03-01 22:25:47 +01:00
a5de001f35 Apply hooks 2020-03-01 22:17:09 +01:00
b3bff398d9 Update db settings 2020-03-01 22:07:40 +01:00
Sonny
61bc7e9b04 Add cache setting to dev settings 2020-03-01 22:07:25 +01:00
961535dd60 Add axes integration & add cache configuration 2020-02-27 19:47:45 +01:00
ab0b24b3d2 Remove type annotations 2020-02-23 10:39:55 +01:00
3045702a1e Merge static refactor 2020-02-22 22:44:09 +01:00
5976870d38 Squashed commit of the following:
commit 99fd94580f95dcbfb77b73e2de846f76a5709ef9
Author: Sonny <sonnyba871@gmail.com>
Date:   Sat Feb 15 21:45:16 2020 +0100

    Use postgres password

    As of https://gitlab.com/gitlab-com/support-forum/issues/5199
2020-02-16 12:21:25 +01:00
ee22e0a0ae Merge branch '14-opml-import-export' into 'development'
Resolve "OPML import/export"

Closes #14

See merge request sonny/newsreader!5
2020-02-08 12:26:29 +01:00
61e45ed0cc [#14] opml import export 2020-02-08 12:18:45 +01:00
6c6ea6c481 Merge branch '23-whitelist-more-html-elements' into 'development'
Resolve "Whitelist more HTML elements"

Closes #23

See merge request sonny/newsreader!4
2020-02-03 20:42:32 +01:00
c13f968234 Resolve "Whitelist more HTML elements" 2020-02-03 20:42:31 +01:00
36076dbc40 Some style changes 2020-02-02 15:43:37 +01:00
f9bce3507c Redux actions refactor 2020-02-01 21:42:29 +01:00
e1e6571bb0 Update post serializer fields 2020-01-31 21:29:11 +01:00
c3b087e004 Squashed commit of the following:
commit f6174179405cbf696415b17bbfcb157b6c3415cf
Author: sonny <sonnyba871@gmail.com>
Date:   Thu Jan 2 23:26:49 2020 +0100

    redux tests
2020-01-30 20:29:32 +01:00
62e763604e Install all javascript dependencies by default 2020-01-18 19:44:11 +01:00
12693dac10 Fix auto read marking with category selected 2020-01-02 20:50:09 +01:00
06ce82bf0c Merge category view tests into single file 2020-01-02 20:15:35 +01:00
28620eab29 Rerun prettier 2020-01-01 21:46:35 +01:00
68534cd541 Remove deprecated npm options 2020-01-01 21:39:07 +01:00
5a630ea4b3 Various changes
- Update npm dependencies
- Update default fixture to exclude default celery task
2020-01-01 16:38:10 +01:00
8acf3bbc7f Rerun black 2019-12-31 14:46:42 +01:00
38d9d74db4 Collection rule pages 2019-12-31 14:33:14 +01:00
d345bc2595 Add missing model to fixture 2019-11-30 16:27:50 +01:00
08e4d043b9 Update default fixture 2019-11-30 00:02:04 +01:00
a9edd520a7 Fix negative unread count 2019-11-28 21:18:45 +01:00
a4102130d2 Merge branch 'account-management' into 'development'
Account management

See merge request sonny/newsreader!3
2019-11-27 22:10:02 +01:00
b2829716b0 Account management 2019-11-27 22:10:02 +01:00
94f4ed6327 Redux refactor 2019-11-24 16:41:46 +01:00
b952d70d92 Category pages 2019-11-19 12:21:34 +01:00
a350cc280d Restructure scss file layout & task 2019-11-15 23:31:02 +01:00
bb0c2e792c #24 task-creation 2019-11-14 20:58:57 +01:00
fb709fe1d0 Merge branch 'main-page' into 'development'
Article page

See merge request sonny/newsreader!2
2019-10-28 21:35:20 +01:00
858f84aaad Refactor endpoint tests
Replace force_login calls with login call from client class in setUp
2019-10-28 21:35:19 +01:00
61702e720a Add swagger integration 2019-08-25 10:06:51 +02:00
752ba62aee Add pre-commit config 2019-08-18 11:49:04 +02:00
8c3bc408b9 Add a default fixture 2019-08-10 22:28:00 +02:00
0658d6404f Add docker specific files 2019-08-10 21:12:59 +02:00
679414a703 Add Login page 2019-07-20 09:57:10 +02:00
b1c5be61f1 increase duplicate handler's history range 2019-07-15 08:29:24 +02:00
a74ffae9a7 Celery integration 2019-07-14 18:44:15 +02:00
1b774a7208 Construct datetime without converting to localtime 2019-07-12 20:20:58 +02:00
8f314e0ca6 Remove leftover print statement 2019-07-10 22:01:15 +02:00
a95db91726 Add truncate tests for title and author fields 2019-07-10 21:55:28 +02:00
88cf5f9bd4 Add gitlab ci settings 2019-07-09 20:33:05 +02:00
a798b9858c Set the default content type 2019-07-09 19:17:39 +02:00
9c6be7357d Add post/category/rule endpoints 2019-07-08 22:54:24 +02:00
982c5bb132 Update isort & rerun formatting 2019-07-01 12:11:44 +02:00
ed658c4dfd Handle request exceptions 2019-07-01 11:38:23 +02:00
cfd064ec85 Add missing requirements 2019-07-01 09:39:48 +02:00
48a9b25545 Favicon fetcher 2019-07-01 09:36:01 +02:00
c75de1c469 Add type hinting 2019-06-23 12:52:49 +02:00
69e4e7b269 Update project requirements 2019-06-22 21:55:47 +02:00
c508cca080 Merge first implementation 2019-06-22 09:29:56 +00:00
abacb72b30 Add gitignore 2019-04-07 15:34:33 +00:00
381 changed files with 18195 additions and 16759 deletions

View file

@ -1,11 +0,0 @@
{
"presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-transform-runtime",
"@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", {loose: true}],
]
}

View file

@ -1,16 +0,0 @@
[run]
source = ./src/newsreader/
omit =
**/tests/**
**/migrations/**
**/conf/**
**/apps.py
**/admin.py
**/tests.py
**/urls.py
**/wsgi.py
**/celery.py
**/__init__.py
[html]
directory = coverage

25
.editorconfig Normal file
View file

@ -0,0 +1,25 @@
# https://editorconfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
trim_trailing_whitespace = true
[*.py]
indent_style = space
indent_size = 4
[*.{yaml,yml,toml,md}]
indent_style = space
indent_size = 2
[Dockerfile*]
indent_style = space
indent_size = 4
[*.json]
indent_style = space
indent_size = 2

3
.gitignore vendored
View file

@ -35,6 +35,7 @@ eggs/
lib/
!src/newsreader/scss/lib
!src/newsreader/js/lib
lib64/
parts/
@ -114,7 +115,7 @@ celerybeat-schedule
*.sage.py
# Environments
.env
*.env
.venv
env/
venv/

View file

@ -1,28 +0,0 @@
stages:
- build
- test
- lint
- deploy
variables:
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:
- .venv/
- .cache/pip
- .cache/poetry
- node_modules/
include:
- local: '/gitlab-ci/build.yml'
- local: '/gitlab-ci/test.yml'
- local: '/gitlab-ci/lint.yml'
- local: '/gitlab-ci/deploy.yml'

View file

@ -1,12 +0,0 @@
[settings]
include_trailing_comma = true
line_length = 88
multi_line_output = 3
skip = env/, venv/
default_section = THIRDPARTY
known_first_party = newsreader
known_django = django
sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
lines_between_types=1
lines_after_imports=2
lines_between_types=1

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
lts/*

View file

@ -1,10 +0,0 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 90,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
}

10
.woodpecker/build.yaml Normal file
View file

@ -0,0 +1,10 @@
when:
- event: push
- event: pull_request
- event: manual
steps:
- image: node:lts-alpine
commands:
- npm install
- npm run build:prod

18
.woodpecker/lint.yaml Normal file
View file

@ -0,0 +1,18 @@
when:
- event: push
- event: pull_request
- event: manual
steps:
- name: python linting
image: ghcr.io/astral-sh/uv:python3.11-alpine
commands:
- uv sync --group ci
- uv run --no-sync -- ruff check src/
- uv run --no-sync -- ruff format --check src/
- name: javascript linting
image: node:lts-alpine
commands:
- npm ci
- npm run lint

37
.woodpecker/tests.yaml Normal file
View file

@ -0,0 +1,37 @@
when:
- event: push
- event: pull_request
- event: manual
services:
- name: postgres
image: postgres:15
environment:
POSTGRES_NAME: &db-name newsreader
POSTGRES_USER: &db-user newsreader
POSTGRES_PASSWORD: &db-password sekrit
- name: memcached
image: memcached:1.5.22
steps:
- name: python tests
image: ghcr.io/astral-sh/uv:python3.11-alpine
environment:
DJANGO_SETTINGS_MODULE: "newsreader.conf.ci"
DJANGO_SECRET_KEY: sekrit
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: *db-name
POSTGRES_USER: *db-user
POSTGRES_PASSWORD: *db-password
commands:
- pip install uv
- uv sync --group ci
- uv run --no-sync -- coverage run ./src/manage.py test newsreader
- uv run --no-sync -- coverage report --show-missing
- name: javascript tests
image: node:lts-alpine
commands:
- npm ci
- npm test

149
CHANGELOG.md Normal file
View file

@ -0,0 +1,149 @@
# Changelog
## 0.5.3
- Apply query optimizations for retrieving posts
## 0.5.2
- Add missing `VERSION` environment variable
## 0.5.1
- Use line-through styling for read posts
- Use full height for post layout
## 0.5.0
- Upgrade python to 3.11
- Upgrade django to 4.2
- Migrate from pip-tools to uv
- Migrate from black to ruff for formatting
- Upgrade webpack to 5.9 (with various tooling)
- Styling refactor
- Mobile/tablet layout added
## 0.4.4
- Sort posts before storing in redux store
## 0.4.3
- Use `IntersectionObserver` to paginate
## 0.4.2
- Set `SECURE_PROXY_SSL_HEADER` setting for production
## 0.4.1
- Add missing env variables
## 0.4.0
- Add Makefile & use `pip-tools` to generate dependencies
- Add `pyproject.toml`
- Update dependencies
- Update docker-compose setup
- Default to `newsreader.conf.docker` settings module
- Add scroll to top/bottom buttons
## 0.3.13.8
- Update dependencies
- Fix csrf_token's not rendering
## 0.3.13.7
- Check for Twitter error codes in response
## 0.3.13.6
- Try to load sentry by default for all environments
## 0.3.13.5
- Set response keyword argument
## 0.3.13.4
- Fix import error
## 0.3.13.3
- Use sentry's set_extra to provide extra debug variables
## 0.3.13.2
- Update sentry-sdk
## 0.3.13.1
- Fix mutual exclusive exception for email settings
- Temporarly set exception level for StreamDeniedException exceptions
## 0.3.13
- Update django to 3.2
- Notify users of expired credentials
## 0.3.12.1
- Add missing background-color
## 0.3.12
- Update light theme
- Sticky navbar
- Sticky post modal header
## 0.3.11
- Add saved posts section
- Bump django version
## 0.3.10
- Add custom color for confirm buttons
- Update font sizes
## 0.3.9
- Cursor based pagination
- Updated django version
## 0.3.8
- Update light / dark theme
- Replace css.gg with fontawesome
- Update deploy job
## 0.3.7
- Add a dark theme
- Update object representations
- Move sentry to optional dependency
- Add CHANGELOG.md
## 0.3.6.3
- Update deploy job
## 0.3.6.2
- Use warning logging level for BuilderSkippedException's
- Change working directory before running ansible
## 0.3.6.1
- Install ansible required roles
## 0.3.6
- Update deploy job
- Add user manageable reddit filters
## 0.3.5
- Show timezone next to post datetimes
- Take read status in consideration when sorting posts

84
Dockerfile Normal file
View file

@ -0,0 +1,84 @@
# stage 1
FROM python:3.11-alpine AS backend
ARG USER_ID=1000
ARG GROUP_ID=1000
ARG UV_LINK_MODE=copy
RUN apk update \
&& apk add --no-cache \
vim \
curl \
gettext
RUN addgroup -g $USER_ID newsreader && adduser -Du $GROUP_ID -G newsreader newsreader
RUN mkdir --parents /app/src /app/logs /app/media /app/bin /app/static \
&& chown -R newsreader:newsreader /app
WORKDIR /app
USER newsreader
COPY --chown=newsreader:newsreader uv.lock pyproject.toml /app/
COPY --from=ghcr.io/astral-sh/uv:python3.11-alpine /usr/local/bin/uv /bin/uv
RUN --mount=type=cache,uid=$USER_ID,gid=$GROUP_ID,target=/home/newsreader/.cache/uv \
uv sync --frozen --no-default-groups --no-install-project
COPY --chown=newsreader:newsreader ./bin/docker-entrypoint.sh /app/bin/docker-entrypoint.sh
VOLUME ["/app/logs", "/app/media", "/app/static"]
# stage 2
FROM node:lts-alpine AS frontend-build
ARG BUILD_ARG=prod
WORKDIR /app
RUN chown node:node /app
USER node
COPY --chown=node:node ./package*.json ./webpack.*.js ./babel.config.js /app/
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
npm ci
COPY --chown=node:node ./src /app/src
RUN npm run build:$BUILD_ARG
# stage 3
FROM backend AS production
COPY --from=frontend-build --chown=newsreader:newsreader \
/app/src/newsreader/static /app/src/newsreader/static
RUN --mount=type=cache,uid=$USER_ID,gid=$GROUP_ID,target=/home/newsreader/.cache/uv \
uv sync --frozen --only-group production --extra sentry
COPY --chown=newsreader:newsreader ./src /app/src
ENV DJANGO_SETTINGS_MODULE=newsreader.conf.production
# Note that the static volume will have to be recreated to be pre-populated
# correctly with the latest static files. See
# https://docs.docker.com/storage/volumes/#populate-a-volume-using-a-container
RUN uv run --no-sync -- src/manage.py collectstatic --noinput
# (optional) stage 4
FROM backend AS development
RUN --mount=type=cache,uid=$USER_ID,gid=$GROUP_ID,target=/home/newsreader/.cache/uv \
uv sync --frozen --group development
ENV DJANGO_SETTINGS_MODULE=newsreader.conf.docker

21
babel.config.js Normal file
View file

@ -0,0 +1,21 @@
module.exports = api => {
const isTest = api.env('test');
const preset = [
"@babel/preset-env", { targets: 'defaults' }
];
const testPreset = [
"@babel/preset-env", { targets: { node: process.versions.node } }
];
const plugins = [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-transform-react-jsx",
"@babel/plugin-proposal-class-properties"
]
return {
"presets": [isTest ? testPreset : preset],
"plugins": plugins
}
}

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

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

View file

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

View file

@ -0,0 +1,36 @@
volumes:
static-files:
services:
django:
build: &app-development-build
target: development
command: uv run --no-sync -- /app/src/manage.py runserver 0.0.0.0:8000
environment: &django-env
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE:-newsreader.conf.docker}
ports:
- "${DJANGO_PORT:-8000}:8000"
volumes:
- ./src:/app/src
- static-files:/app/src/newsreader/static
stdin_open: true
tty: true
celery:
build:
<<: *app-development-build
environment:
<<: *django-env
volumes:
- ./src/:/app/src
webpack:
build:
target: frontend-build
context: .
args:
BUILD_ARG: "dev"
command: npm run build:watch
volumes:
- ./src/:/app/src
- static-files:/app/src/newsreader/static

View file

@ -0,0 +1,16 @@
volumes:
logs:
static-files:
services:
nginx:
image: nginx:1.23
depends_on:
django:
condition: service_healthy
ports:
- "${NGINX_HTTP_PORT:-80}:80"
volumes:
- ./config/nginx/conf.d:/etc/nginx/conf.d
- logs:/var/log/nginx
- static-files:/app/static

View file

@ -1,56 +1,126 @@
version: '3'
volumes:
logs:
media:
postgres-data:
static-files:
node-modules:
x-db-connection-env: &db-connection-env
POSTGRES_HOST: ${POSTGRES_HOST:-db}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
POSTGRES_DB: &pg-database ${POSTGRES_DB:-newsreader}
POSTGRES_USER: &pg-user ${POSTGRES_USER:-newsreader}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-newsreader}
x-db-env: &db-env
<<: *db-connection-env
PGUSER: *pg-user
PGDATABASE: *pg-database
x-django-env: &django-env
<<: *db-connection-env
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,django}
INTERNAL_IPS: ${INTERNAL_IPS:-localhost,127.0.0.1,django}
# see token_urlsafe from python's secret module to generate one
DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY:-Ojg68lYsP3kq2r5JgozUzKVSRFywm17BTMS5iwpLM44}
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE:-newsreader.conf.production}
ADMINS: ${ADMINS:-""}
VERSION: ${VERSION:-""}
# Email
EMAIL_HOST: ${EMAIL_HOST:-localhost}
EMAIL_PORT: ${EMAIL_PORT:-25}
EMAIL_HOST_USER: ${EMAIL_HOST_USER:-""}
EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD:-""}
EMAIL_USE_TLS: ${EMAIL_USE_TLS:-no}
EMAIL_USE_SSL: ${EMAIL_USE_SSL:-no}
EMAIL_DEFAULT_FROM: ${EMAIL_DEFAULT_FROM:-webmaster@localhost}
# Sentry
SENTRY_DSN: ${SENTRY_DSN:-""}
services:
db:
image: postgres
environment:
POSTGRES_DB: "newsreader"
POSTGRES_USER: "newsreader"
POSTGRES_PASSWORD: "newsreader"
<<: *db-env
image: postgres:15
healthcheck:
test: /usr/bin/pg_isready
start_period: 10s
interval: 5s
timeout: 10s
retries: 10
volumes:
- postgres-data:/var/lib/postgresql/data
rabbitmq:
image: rabbitmq:3.7
image: rabbitmq:4
memcached:
image: memcached:1.5.22
ports:
- "11211:11211"
image: memcached:1.6
entrypoint:
- memcached
- -m 64
django:
build: &app-build
context: .
target: production
environment:
<<: *django-env
entrypoint: ["/bin/sh", "/app/bin/docker-entrypoint.sh"]
command: |
uv run --no-sync --
gunicorn
--bind 0.0.0.0:8000
--workers 3
--chdir /app/src/
newsreader.wsgi:application
healthcheck:
test: /usr/bin/curl --fail http://django:8000 || exit 1
start_period: 10s
interval: 10s
timeout: 10s
retries: 5
depends_on:
memcached:
condition: service_started
db:
condition: service_healthy
volumes:
- logs:/app/logs
- media:/app/media
- static-files:/app/static
celery:
build:
context: .
dockerfile: ./docker/django
command: celery worker --app newsreader --loglevel INFO --beat --scheduler django --workdir /app/src/
<<: *app-build
environment:
- DJANGO_SETTINGS_MODULE=newsreader.conf.docker
<<: *django-env
command: |
uv run --no-sync --
celery
--app newsreader
--workdir /app/src/
worker --loglevel INFO
--concurrency 2
--beat
--scheduler django
-n worker1@%h
-n worker2@%h
healthcheck:
test: uv run --no-sync -- celery --app newsreader status || exit 1
start_period: 10s
interval: 10s
timeout: 10s
retries: 5
depends_on:
- rabbitmq
django:
build:
context: .
dockerfile: ./docker/django
command: src/entrypoint.sh
environment:
- DJANGO_SETTINGS_MODULE=newsreader.conf.docker
ports:
- '8000:8000'
depends_on:
- db
rabbitmq:
condition: service_started
django:
condition: service_healthy
volumes:
- .:/app
- static-files:/app/src/newsreader/static
webpack:
build:
context: .
dockerfile: ./docker/webpack
command: npm run build:watch
volumes:
- .:/app
- static-files:/app/src/newsreader/static
- node-modules:/app/node_modules
- logs:/app/logs

View file

@ -1,10 +0,0 @@
FROM python:3.7-buster
RUN pip install poetry
WORKDIR /app
COPY poetry.lock pyproject.toml /app/
RUN poetry config virtualenvs.create false && poetry install --no-interaction
COPY . /app/

View file

@ -1,9 +0,0 @@
FROM node:12
WORKDIR /app
COPY package.json package-lock.json /app/
RUN npm install
COPY . /app/

View file

@ -1,7 +0,0 @@
static:
stage: build
image: node:12
before_script:
- npm install
script:
- npm run build

View file

@ -1,16 +0,0 @@
deploy:
stage: deploy
image: debian:buster
environment:
name: production
url: rss.fudiggity.nl
before_script:
- apt-get update && apt-get install -y ansible git
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@git.fudiggity.nl/sonny/ansible-playbooks.git deployment
- mkdir /root/.ssh
- echo "192.168.178.63 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILbtcdgJBhVCKsO88cV19EYefDTopdYejEQCp1pYr1Ga" > /root/.ssh/known_hosts
- echo "$DEPLOY_KEY" > deployment/deploy_key && chmod 0600 deployment/deploy_key
script:
- ansible-playbook deployment/playbook.yml --inventory deployment/apps.yml --limit newsreader --user ansible --private-key deployment/deploy_key
only:
- master

View file

@ -1,22 +0,0 @@
python-linting:
stage: lint
allow_failure: true
image: python:3.7.4-slim-stretch
before_script:
- pip install poetry
- poetry config cache-dir ~/.cache/poetry
- poetry config virtualenvs.in-project true
- poetry install --no-interaction
script:
- poetry run isort src/ --check-only --recursive
- poetry run black src/ --line-length 88 --check
- poetry run autoflake src/ --check --recursive --remove-all-unused-imports --ignore-init-module-imports
javascript-linting:
stage: lint
allow_failure: true
image: node:12
before_script:
- npm install
script:
- npm run lint

View file

@ -1,23 +0,0 @@
python-tests:
stage: test
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
services:
- postgres:11
- memcached:1.5.22
image: python:3.7.4-slim-stretch
before_script:
- pip install poetry
- poetry config cache-dir .cache/poetry
- poetry config virtualenvs.in-project true
- poetry install --no-interaction
script:
- poetry run coverage run src/manage.py test newsreader
- poetry run coverage report
javascript-tests:
stage: test
image: node:12
before_script:
- npm install
script:
- npm test

View file

@ -1,188 +0,0 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// Respect "browser" field in package.json when resolving modules
// browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/tmp/jest_rs",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: null,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
// A path to a custom dependency extractor
// dependencyExtractor: null,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: null,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: null,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: null,
// Run tests from one or more projects
// projects: null,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: null,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
rootDir: 'src/newsreader/js/tests/',
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'node',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: null,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: null,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: null,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};

16850
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,61 +1,75 @@
{
"name": "newsreader",
"version": "0.1.0",
"version": "0.5.3",
"description": "Application for viewing RSS feeds",
"main": "index.js",
"scripts": {
"lint": "npx prettier \"src/newsreader/js/**/*.js\" --check",
"format": "npx prettier \"src/newsreader/js/**/*.js\" --write",
"build": "npx webpack --config webpack.dev.babel.js",
"build:watch": "npx webpack --config webpack.dev.babel.js --watch",
"build:dev": "npx webpack --config webpack.dev.babel.js",
"build:prod": "npx webpack --config webpack.prod.babel.js",
"test": "npx jest",
"test:watch": "npm test -- --watch"
},
"repository": {
"type": "git",
"url": "[git@git.fudiggity.nl:5000]:sonny/newsreader.git"
"url": "forgejo.fudiggity.nl:sonny/newsreader"
},
"author": "Sonny",
"license": "GPL-3.0-or-later",
"dependencies": {
"css.gg": "^1.0.6",
"@fortawesome/fontawesome-free": "^5.15.2",
"js-cookie": "^2.2.1",
"lodash": "^4.17.15",
"lodash": "^4.17.20",
"object-assign": "^4.1.1",
"react-redux": "^7.1.3",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-function-bind": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-syntax-function-bind": "^7.7.4",
"@babel/plugin-transform-react-jsx": "^7.7.7",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.7",
"@babel/register": "^7.7.7",
"@babel/runtime": "^7.7.7",
"babel-jest": "^24.9.0",
"babel-loader": "^8.1.0",
"@babel/core": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.13",
"@babel/plugin-proposal-function-bind": "^7.12.13",
"@babel/plugin-transform-react-jsx": "^7.12.13",
"@babel/preset-env": "^7.12.13",
"@babel/register": "^7.12.13",
"@babel/runtime": "^7.12.13",
"babel-jest": "^29.7.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.2",
"fetch-mock": "^8.3.1",
"jest": "^24.9.0",
"mini-css-extract-plugin": "^0.9.0",
"node-fetch": "^2.6.0",
"node-sass": "^4.13.1",
"css-loader": "^7.1.2",
"fetch-mock": "^8.3.2",
"jest": "^29.7.0",
"mini-css-extract-plugin": "^2.9.1",
"node-fetch": "^2.6.1",
"prettier": "^1.19.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"redux-mock-store": "^1.5.4",
"sass": "^1.52.1",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-merge": "^4.2.2"
},
"prettier": {
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 90,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
},
"jest": {
"roots": [
"src/newsreader/js/tests/"
],
"clearMocks": true,
"coverageDirectory": "coverage"
}
}

1159
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,40 +1,81 @@
[tool.poetry]
[project]
name = "newsreader"
version = "0.2"
description = "Webapplication for reading RSS feeds"
authors = ["Sonny <sonnyba871@gmail.com>"]
license = "GPL-3.0"
version = "0.5.3"
authors = [{ name = "Sonny" }]
license = { text = "GPL-3.0" }
requires-python = ">=3.11"
dependencies = [
"django~=4.2",
"celery~=5.4",
"psycopg[binary]",
"django-axes",
"django-celery-beat~=2.7.0",
"django-rest-framework",
"djangorestframework-camel-case",
"pymemcache",
"python-dotenv~=1.0.1",
"ftfy~=6.2",
"requests",
"feedparser",
"bleach",
"beautifulsoup4",
"lxml",
]
[tool.poetry.dependencies]
python = "^3.7"
bleach = "^3.1.4"
Django = "^3.0.5"
celery = "^4.4.2"
beautifulsoup4 = "^4.9.0"
django-axes = "^5.3.1"
django-celery-beat = "^2.0.0"
djangorestframework = "^3.11.0"
drf-yasg = "^1.17.1"
django-registration-redux = "^2.7"
lxml = "^4.5.0"
feedparser = "^5.2.1"
python-memcached = "^1.59"
requests = "^2.23.0"
psycopg2-binary = "^2.8.5"
gunicorn = "^20.0.4"
python-dotenv = "^0.12.0"
[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"]
[tool.poetry.dev-dependencies]
factory-boy = "^2.12.0"
freezegun = "^0.3.15"
django-debug-toolbar = "^2.2"
django-extensions = "^2.2.9"
black = "19.3b0"
isort = "4.3.21"
autoflake = "1.3.1"
tblib = "1.6.0"
coverage = "^5.1"
[project.optional-dependencies]
sentry = ["sentry-sdk~=2.0"]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
[tool.uv]
environments = ["sys_platform == 'linux'"]
default-groups = ["test-tools"]
[tool.ruff]
include = ["pyproject.toml", "src/**/*.py"]
line-length = 88
[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 = ["newsreader"]
section-order = [
"future",
"standard-library",
"django",
"third-party",
"first-party",
"local-folder",
]
[tool.ruff.lint.isort.sections]
django = ["django"]
[tool.coverage.run]
source = ["./src/newsreader/"]
omit = [
"**/tests/**",
"**/migrations/**",
"**/conf/**",
"**/apps.py",
"**/admin.py",
"**/tests.py",
"**/urls.py",
"**/wsgi.py",
"**/celery.py",
"**/__init__.py"
]

View file

@ -1,5 +0,0 @@
#!/bin/bash
# This file should only be used in conjuction with docker-compose
python /app/src/manage.py migrate
python /app/src/manage.py runserver 0.0.0.0:8000

View file

@ -1,11 +1,12 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.dev")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "newsreader.conf.docker")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:

View file

@ -1,27 +1,36 @@
from django import forms
from django.contrib import admin
from django.utils.translation import ugettext as _
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
from django.contrib.auth.forms import UserChangeForm
from django.utils.translation import gettext as _
from newsreader.accounts.models import User
class UserAdmin(admin.ModelAdmin):
class UserAdminForm(UserChangeForm):
class Meta:
widgets = {
"email": forms.EmailInput(attrs={"size": "50"}),
}
class UserAdmin(DjangoUserAdmin):
list_display = ("email", "last_name", "date_joined", "is_active")
list_filter = ("is_active", "is_staff", "is_superuser")
ordering = ("email",)
search_fields = ["email", "last_name", "first_name"]
readonly_fields = ("last_login", "date_joined")
form = UserAdminForm
fieldsets = (
(
_("User settings"),
{"fields": ("email", "first_name", "last_name", "is_active")},
{"fields": ("email", "password", "first_name", "last_name", "is_active")},
),
(
_("Permission settings"),
{
"classes": ("collapse",),
"fields": ("is_staff", "is_superuser", "groups", "user_permissions"),
},
{"classes": ("collapse",), "fields": ("is_staff", "is_superuser")},
),
(_("Misc settings"), {"fields": ("date_joined", "last_login")}),
)

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = "accounts"
name = "newsreader.accounts"

View file

@ -1,9 +1,11 @@
from django import forms
from newsreader.accounts.models import User
from newsreader.core.forms import CheckboxInput
class UserSettingsForm(forms.ModelForm):
class Meta:
model = User
fields = ("first_name", "last_name")
fields = ("first_name", "last_name", "auto_mark_read")
widgets = {"auto_mark_read": CheckboxInput}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,20 @@
# Generated by Django 3.0.5 on 2020-06-03 20:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("accounts", "0009_auto_20200524_1218")]
operations = [
migrations.AddField(
model_name="user",
name="reddit_access_token",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name="user",
name="reddit_refresh_token",
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -0,0 +1,20 @@
# Generated by Django 3.0.7 on 2020-09-13 19:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("accounts", "0010_auto_20200603_2230")]
operations = [
migrations.AddField(
model_name="user",
name="twitter_oauth_token",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name="user",
name="twitter_oauth_token_secret",
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -0,0 +1,9 @@
# Generated by Django 3.0.7 on 2020-09-26 15:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("accounts", "0011_auto_20200913_2101")]
operations = [migrations.RemoveField(model_name="user", name="task")]

View file

@ -0,0 +1,19 @@
# Generated by Django 3.0.7 on 2020-10-27 21:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("accounts", "0012_remove_user_task")]
operations = [
migrations.AddField(
model_name="user",
name="auto_mark_read",
field=models.BooleanField(
default=True,
help_text="Wether posts should be marked as read after x amount of seconds of reading",
verbose_name="Auto read marking",
),
)
]

View file

@ -0,0 +1,48 @@
# Generated by Django 3.0.7 on 2020-12-18 21:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("accounts", "0013_user_auto_mark_read")]
operations = [
migrations.AddField(
model_name="user",
name="reddit_allow_nfsw",
field=models.BooleanField(default=False, verbose_name="Allow NSFW posts"),
),
migrations.AddField(
model_name="user",
name="reddit_allow_spoiler",
field=models.BooleanField(default=False, verbose_name="Allow spoilers"),
),
migrations.AddField(
model_name="user",
name="reddit_allow_viewed",
field=models.BooleanField(
default=True, verbose_name="Allow already seen posts"
),
),
migrations.AddField(
model_name="user",
name="reddit_comments_min",
field=models.PositiveIntegerField(
default=0, verbose_name="Minimum amount of comments"
),
),
migrations.AddField(
model_name="user",
name="reddit_downvotes_max",
field=models.PositiveIntegerField(
default=0, verbose_name="Maximum amount of downvotes"
),
),
migrations.AddField(
model_name="user",
name="reddit_upvotes_min",
field=models.PositiveIntegerField(
default=0, verbose_name="Minimum amount of upvotes"
),
),
]

View file

@ -0,0 +1,16 @@
# Generated by Django 3.0.7 on 2020-12-19 12:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("accounts", "0014_auto_20201218_2216")]
operations = [
migrations.RemoveField(model_name="user", name="reddit_allow_nfsw"),
migrations.RemoveField(model_name="user", name="reddit_allow_spoiler"),
migrations.RemoveField(model_name="user", name="reddit_allow_viewed"),
migrations.RemoveField(model_name="user", name="reddit_comments_min"),
migrations.RemoveField(model_name="user", name="reddit_downvotes_max"),
migrations.RemoveField(model_name="user", name="reddit_upvotes_min"),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2 on 2021-04-23 20:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("accounts", "0015_auto_20201219_1330")]
operations = [
migrations.AlterField(
model_name="user",
name="first_name",
field=models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
)
]

View file

@ -0,0 +1,20 @@
# Generated by Django 3.2.25 on 2024-09-06 07:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounts", "0016_alter_user_first_name"),
]
operations = [
migrations.RemoveField(
model_name="user",
name="twitter_oauth_token",
),
migrations.RemoveField(
model_name="user",
name="twitter_oauth_token_secret",
),
]

View file

@ -0,0 +1,20 @@
# 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",
),
]

View file

@ -1,11 +1,9 @@
import json
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models
from django.utils.translation import gettext as _
from django_celery_beat.models import IntervalSchedule, PeriodicTask
from django_celery_beat.models import PeriodicTask
class UserManager(DjangoUserManager):
@ -41,13 +39,13 @@ class UserManager(DjangoUserManager):
class User(AbstractUser):
email = models.EmailField(_("email address"), unique=True)
task = models.OneToOneField(
PeriodicTask,
on_delete=models.CASCADE,
null=True,
blank=True,
editable=False,
verbose_name="collection task",
# settings
auto_mark_read = models.BooleanField(
_("Auto read marking"),
default=True,
help_text=_(
"Wether posts should be marked as read after x amount of seconds of reading"
),
)
username = None
@ -57,24 +55,8 @@ class User(AbstractUser):
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if not self.task:
task_interval, _ = IntervalSchedule.objects.get_or_create(
every=1, period=IntervalSchedule.HOURS
)
self.task, _ = PeriodicTask.objects.get_or_create(
enabled=True,
interval=task_interval,
name=f"{self.email}-collection-task",
task="newsreader.news.collection.tasks.FeedTask",
args=json.dumps([self.pk]),
)
self.save()
def delete(self, *args, **kwargs):
self.task.delete()
tasks = PeriodicTask.objects.filter(name__contains=self.email)
tasks.delete()
return super().delete(*args, **kwargs)

View file

@ -2,17 +2,23 @@
{% load i18n %}
{% block actions %}
<section class="section form__section--last">
<fieldset class="fieldset form__fieldset">
{% include "components/form/cancel-button.html" %}
</fieldset>
<section class="section form__section--last">
<fieldset class="fieldset form__fieldset">
{% include "components/form/confirm-button.html" %}
<fieldset class="fieldset form__fieldset">
<a class="link button button--primary" href="{% url 'accounts:password-change' %}">
{% trans "Change password" %}
</a>
<a class="link button button--primary" href="{% url 'accounts:password-change' %}">
{% trans "Change password" %}
</a>
{% include "components/form/confirm-button.html" %}
</fieldset>
</section>
{% if favicon_task_allowed %}
<a class="link button button--primary" href="{% url 'accounts:settings:favicon' %}">
{% trans "Fetch favicons" %}
</a>
{% else %}
<button class="button button--primary button--disabled" disabled>
{% trans "Fetch favicons" %}
</button>
{% endif %}
</fieldset>
</section>
{% endblock actions %}

View file

@ -1,7 +1,9 @@
{% extends "base.html" %}
{% extends "sidebar.html" %}
{% block content %}
<main id="login--page" class="main">
{% include "accounts/components/login-form.html" with form=form title="Login" confirm_text="Login" %}
<main id="login--page" class="main" data-render-sidebar=true>
<div class="main__container">
{% include "accounts/components/login-form.html" with form=form title="Login" confirm_text="Login" %}
</div>
</main>
{% endblock %}

View file

@ -1,8 +1,12 @@
{% extends "base.html" %}
{% extends "sidebar.html" %}
{% block content %}
<main id="password-change--page" class="main">
{% url 'accounts:settings' as cancel_url %}
{% include "components/form/form.html" with form=form title="Change password" confirm_text="Change password" cancel_url=cancel_url %}
{% url 'accounts:settings:home' as cancel_url %}
<main id="password-change--page" class="main" data-render-sidebar=true>
<div class="main__container">
{% include "components/form/form.html" with form=form title="Change password" confirm_text="Change password" cancel_url=cancel_url %}
</div>
</main>
{% endblock %}

View file

@ -1,7 +1,9 @@
{% extends "base.html" %}
{% extends "sidebar.html" %}
{% block content %}
<main id="settings--page" class="main">
{% include "accounts/components/settings-form.html" with form=form title="User profile" confirm_text="Save" %}
<main id="settings--page" class="main" data-render-sidebar=true>
<div class="main__container">
{% include "accounts/components/settings-form.html" with form=form title="User profile" confirm_text="Save" %}
</div>
</main>
{% endblock %}

View file

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

View file

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

View file

@ -0,0 +1,37 @@
from unittest.mock import patch
from django.core.cache import cache
from django.test import TestCase
from django.urls import reverse
from newsreader.accounts.tests.factories import UserFactory
class FaviconRedirectViewTestCase(TestCase):
def setUp(self):
self.user = UserFactory(email="test@test.nl", password="test")
self.client.force_login(self.user)
self.patch = patch("newsreader.accounts.views.favicon.FaviconTask")
self.mocked_task = self.patch.start()
def tearDown(self):
cache.clear()
def test_simple(self):
response = self.client.get(reverse("accounts:settings:favicon"))
self.assertRedirects(response, reverse("accounts:settings:home"))
self.mocked_task.delay.assert_called_once_with(self.user.pk)
self.assertEqual(1, cache.get(f"{self.user.email}-favicon-task"))
def test_not_active(self):
cache.set(f"{self.user.email}-favicon-task", 1)
response = self.client.get(reverse("accounts:settings:favicon"))
self.assertRedirects(response, reverse("accounts:settings:home"))
self.mocked_task.delay.assert_not_called()

View file

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

View file

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

View file

@ -5,25 +5,27 @@ from newsreader.accounts.models import User
from newsreader.accounts.tests.factories import UserFactory
class UserSettingsViewTestCase(TestCase):
class SettingsViewTestCase(TestCase):
def setUp(self):
self.user = UserFactory(password="test")
self.user = UserFactory(email="test@test.nl", password="test")
self.client.force_login(self.user)
self.url = reverse("accounts:settings:home")
def test_simple(self):
response = self.client.get(reverse("accounts:settings"))
response = self.client.get(self.url)
self.assertEquals(response.status_code, 200)
def test_user_credential_change(self):
response = self.client.post(
reverse("accounts:settings"),
reverse("accounts:settings:home"),
{"first_name": "First name", "last_name": "Last name"},
)
user = User.objects.get()
self.assertRedirects(response, reverse("accounts:settings"))
self.assertRedirects(response, reverse("accounts:settings:home"))
self.assertEquals(user.first_name, "First name")
self.assertEquals(user.last_name, "Last name")

View file

@ -1,22 +1,21 @@
from django.test import TestCase
from django_celery_beat.models import PeriodicTask
from django_celery_beat.models import IntervalSchedule, PeriodicTask
from newsreader.accounts.models import User
from newsreader.accounts.tests.factories import UserFactory
class UserTestCase(TestCase):
def test_task_is_created(self):
user = User.objects.create(email="durp@burp.nl", task=None)
task = PeriodicTask.objects.get(name=f"{user.email}-collection-task")
user.refresh_from_db()
self.assertEquals(task, user.task)
self.assertEquals(PeriodicTask.objects.count(), 1)
def test_task_is_deleted(self):
user = User.objects.create(email="durp@burp.nl", task=None)
user = UserFactory(email="durp@burp.nl")
interval = IntervalSchedule.objects.create(
every=1, period=IntervalSchedule.HOURS
)
PeriodicTask.objects.create(
name=f"{user.email}-feed", task="FeedTask", interval=interval
)
user.delete()
self.assertEquals(PeriodicTask.objects.count(), 0)

View file

@ -1,10 +1,8 @@
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.urls import include, path
from newsreader.accounts.views import (
ActivationCompleteView,
ActivationResendView,
ActivationView,
FaviconRedirectView,
LoginView,
LogoutView,
PasswordChangeView,
@ -12,33 +10,21 @@ from newsreader.accounts.views import (
PasswordResetConfirmView,
PasswordResetDoneView,
PasswordResetView,
RegistrationClosedView,
RegistrationCompleteView,
RegistrationView,
SettingsView,
)
settings_patterns = [
# Misc
path("favicon/", login_required(FaviconRedirectView.as_view()), name="favicon"),
path("", login_required(SettingsView.as_view()), name="home"),
]
urlpatterns = [
# Auth
path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"),
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/<str:activation_key>/",
ActivationView.as_view(),
name="activate",
),
# Password
path("password-reset/", PasswordResetView.as_view(), name="password-reset"),
path(
"password-reset/done/",
@ -60,5 +46,6 @@ urlpatterns = [
login_required(PasswordChangeView.as_view()),
name="password-change",
),
path("settings/", login_required(SettingsView.as_view()), name="settings"),
# Settings
path("settings/", include((settings_patterns, "settings"))),
]

View file

@ -1,115 +0,0 @@
from django.contrib.auth import views as django_views
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import TemplateView
from django.views.generic.edit import FormView, ModelFormMixin
from registration.backends.default import views as registration_views
from newsreader.accounts.forms import UserSettingsForm
from newsreader.accounts.models import User
class LoginView(django_views.LoginView):
template_name = "accounts/views/login.html"
success_url = reverse_lazy("index")
class LogoutView(django_views.LogoutView):
next_page = reverse_lazy("accounts:login")
# 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(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(TemplateView):
template_name = "registration/registration_complete.html"
class RegistrationClosedView(TemplateView):
template_name = "registration/registration_closed.html"
# Redirects or renders failed activation template
class ActivationView(registration_views.ActivationView):
template_name = "registration/activation_failure.html"
def get_success_url(self, user):
return ("accounts:activate-complete", (), {})
class ActivationCompleteView(TemplateView):
template_name = "registration/activation_complete.html"
# Renders activation form resend or resend_activation_complete
class ActivationResendView(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
)
# PasswordResetView sends the mail
# PasswordResetDoneView shows a success message for the above
# PasswordResetConfirmView checks the link the user clicked and
# prompts for a new password
# PasswordResetCompleteView shows a success message for the above
class PasswordResetView(django_views.PasswordResetView):
template_name = "password-reset/password-reset.html"
subject_template_name = "password-reset/password-reset-subject.txt"
email_template_name = "password-reset/password-reset-email.html"
success_url = reverse_lazy("accounts:password-reset-done")
class PasswordResetDoneView(django_views.PasswordResetDoneView):
template_name = "password-reset/password-reset-done.html"
class PasswordResetConfirmView(django_views.PasswordResetConfirmView):
template_name = "password-reset/password-reset-confirm.html"
success_url = reverse_lazy("accounts:password-reset-complete")
class PasswordResetCompleteView(django_views.PasswordResetCompleteView):
template_name = "password-reset/password-reset-complete.html"
class PasswordChangeView(django_views.PasswordChangeView):
template_name = "accounts/views/password-change.html"
success_url = reverse_lazy("accounts:settings")
class SettingsView(ModelFormMixin, FormView):
template_name = "accounts/views/settings.html"
success_url = reverse_lazy("accounts:settings")
form_class = UserSettingsForm
model = User
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def get_object(self, **kwargs):
return self.request.user
def get_form_kwargs(self):
return {**super().get_form_kwargs(), "instance": self.request.user}

View file

@ -0,0 +1,23 @@
from newsreader.accounts.views.auth import LoginView, LogoutView
from newsreader.accounts.views.favicon import FaviconRedirectView
from newsreader.accounts.views.password import (
PasswordChangeView,
PasswordResetCompleteView,
PasswordResetConfirmView,
PasswordResetDoneView,
PasswordResetView,
)
from newsreader.accounts.views.settings import SettingsView
__all__ = [
"LoginView",
"LogoutView",
"FaviconRedirectView",
"PasswordChangeView",
"PasswordResetCompleteView",
"PasswordResetConfirmView",
"PasswordResetDoneView",
"PasswordResetView",
"SettingsView",
]

View file

@ -0,0 +1,13 @@
from django.contrib.auth import views as django_views
from django.urls import reverse_lazy
from newsreader.utils.views import NavListMixin
class LoginView(NavListMixin, django_views.LoginView):
template_name = "accounts/views/login.html"
success_url = reverse_lazy("index")
class LogoutView(django_views.LogoutView):
next_page = reverse_lazy("accounts:login")

View file

@ -0,0 +1,26 @@
from django.contrib import messages
from django.core.cache import cache
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import RedirectView
from newsreader.news.collection.tasks import FaviconTask
class FaviconRedirectView(RedirectView):
url = reverse_lazy("accounts:settings:home")
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
user = request.user
task_active = cache.get(f"{user.email}-favicon-task")
if not task_active:
FaviconTask.delay(user.pk)
messages.success(request, _("Favicons are being fetched"))
cache.set(f"{user.email}-favicon-task", 1, 18000) # 5 hours
return response
messages.error(request, _("Limit reached, try again later"))
return response

View file

@ -0,0 +1,34 @@
from django.contrib.auth import views as django_views
from django.urls import reverse_lazy
from newsreader.utils.views import NavListMixin
# PasswordResetView sends the mail
# PasswordResetDoneView shows a success message for the above
# PasswordResetConfirmView checks the link the user clicked and
# prompts for a new password
# PasswordResetCompleteView shows a success message for the above
class PasswordResetView(NavListMixin, django_views.PasswordResetView):
template_name = "password-reset/password-reset.html"
subject_template_name = "password-reset/password-reset-subject.txt"
email_template_name = "password-reset/password-reset-email.html"
success_url = reverse_lazy("accounts:password-reset-done")
class PasswordResetDoneView(NavListMixin, django_views.PasswordResetDoneView):
template_name = "password-reset/password-reset-done.html"
class PasswordResetConfirmView(NavListMixin, django_views.PasswordResetConfirmView):
template_name = "password-reset/password-reset-confirm.html"
success_url = reverse_lazy("accounts:password-reset-complete")
class PasswordResetCompleteView(NavListMixin, django_views.PasswordResetCompleteView):
template_name = "password-reset/password-reset-complete.html"
class PasswordChangeView(NavListMixin, django_views.PasswordChangeView):
template_name = "accounts/views/password-change.html"
success_url = reverse_lazy("accounts:settings")

View file

@ -0,0 +1,32 @@
from django.core.cache import cache
from django.urls import reverse_lazy
from django.views.generic.edit import FormView, ModelFormMixin
from newsreader.accounts.forms import UserSettingsForm
from newsreader.accounts.models import User
from newsreader.utils.views import NavListMixin
class SettingsView(NavListMixin, ModelFormMixin, FormView):
template_name = "accounts/views/settings.html"
success_url = reverse_lazy("accounts:settings:home")
form_class = UserSettingsForm
model = User
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return {
**context,
"favicon_task_allowed": not cache.get(f"{self.request.user}-favicon-task"),
}
def get_object(self, **kwargs):
return self.request.user
def get_form_kwargs(self):
return {**super().get_form_kwargs(), "instance": self.request.user}

View file

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

View file

@ -1,18 +1,25 @@
import os
from dotenv import load_dotenv
from pathlib import Path
from newsreader.conf.utils import get_env, get_root_dir
BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
DJANGO_PROJECT_DIR = os.path.join(BASE_DIR, "src", "newsreader")
load_dotenv()
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: don"t run with debug turned on in production!
DEBUG = True
try:
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
except ImportError:
CeleryIntegration = None
DjangoIntegration = None
ALLOWED_HOSTS = ["127.0.0.1"]
INTERNAL_IPS = ["127.0.0.1"]
BASE_DIR = get_root_dir()
DJANGO_PROJECT_DIR = BASE_DIR / "src" / "newsreader"
DEBUG = False
ALLOWED_HOSTS = get_env("ALLOWED_HOSTS", split=",", default=["127.0.0.1", "localhost"])
INTERNAL_IPS = get_env("INTERNAL_IPS", split=",", default=["127.0.0.1", "localhost"])
# Application definition
INSTALLED_APPS = [
@ -22,20 +29,22 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.forms",
# third party apps
"rest_framework",
"drf_yasg",
"celery",
"django_celery_beat",
"registration",
"axes",
# app modules
"newsreader.accounts",
"newsreader.utils",
"newsreader.news",
"newsreader.news.core",
"newsreader.news.collection",
]
SECRET_KEY = get_env("DJANGO_SECRET_KEY", default="")
AUTHENTICATION_BACKENDS = [
"axes.backends.AxesBackend",
"django.contrib.auth.backends.ModelBackend",
@ -54,14 +63,15 @@ MIDDLEWARE = [
ROOT_URLCONF = "newsreader.urls"
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")],
"DIRS": [DJANGO_PROJECT_DIR / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
@ -72,31 +82,30 @@ TEMPLATES = [
WSGI_APPLICATION = "newsreader.wsgi.application"
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ.get("POSTGRES_HOST", ""),
"NAME": os.environ.get("POSTGRES_NAME", "newsreader"),
"USER": os.environ.get("POSTGRES_USER"),
"PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
"HOST": get_env("POSTGRES_HOST", default=""),
"PORT": get_env("POSTGRES_PORT", default=""),
"NAME": get_env("POSTGRES_DB", default=""),
"USER": get_env("POSTGRES_USER", default=""),
"PASSWORD": get_env("POSTGRES_PASSWORD", default=""),
}
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "localhost:11211",
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "memcached:11211",
},
"axes": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "localhost:11211",
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "memcached:11211",
},
}
# Logging
# https://docs.djangoproject.com/en/2.2/topics/logging/#configuring-logging
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
@ -105,61 +114,51 @@ LOGGING = {
"require_debug_true": {"()": "django.utils.log.RequireDebugTrue"},
},
"formatters": {
"django.server": {
"timestamped": {
"()": "django.utils.log.ServerFormatter",
"format": "[{server_time}] {message}",
"style": "{",
},
"syslog": {"class": "logging.Formatter", "format": "{message}", "style": "{"},
},
"handlers": {
"console": {
"level": "INFO",
"filters": ["require_debug_true"],
"class": "logging.StreamHandler",
"formatter": "timestamped",
},
"django.server": {
"file": {
"level": "DEBUG",
"class": "logging.handlers.RotatingFileHandler",
"filename": BASE_DIR / "logs" / "newsreader.log",
"backupCount": 5,
"maxBytes": 50000000, # 50 mB
"formatter": "timestamped",
},
"celery": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "django.server",
},
"mail_admins": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "django.utils.log.AdminEmailHandler",
},
"syslog": {
"level": "INFO",
"filters": ["require_debug_false"],
"class": "logging.handlers.SysLogHandler",
"formatter": "syslog",
"address": "/dev/log",
},
"syslog_errors": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "logging.handlers.SysLogHandler",
"formatter": "syslog",
"address": "/dev/log",
"class": "logging.handlers.RotatingFileHandler",
"filename": BASE_DIR / "logs" / "celery.log",
"backupCount": 5,
"maxBytes": 50000000, # 50 mB
"formatter": "timestamped",
},
},
"loggers": {
"django": {
"handlers": ["console", "mail_admins", "syslog_errors"],
"level": "INFO",
},
"django": {"handlers": ["console"], "level": "INFO"},
"django.server": {
"handlers": ["django.server"],
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"celery": {"handlers": ["syslog", "console"], "level": "INFO"},
"celery.task": {"handlers": ["syslog", "console"], "level": "INFO"},
"celery.task": {"handlers": ["console", "celery"], "level": "INFO"},
"newsreader": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
},
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
@ -174,8 +173,6 @@ AUTH_USER_MODEL = "accounts.User"
LOGIN_REDIRECT_URL = "/"
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Europe/Amsterdam"
@ -183,19 +180,31 @@ USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = [os.path.join(DJANGO_PROJECT_DIR, "static")]
STATIC_ROOT = BASE_DIR / "static"
STATICFILES_DIRS = (DJANGO_PROJECT_DIR / "static",)
# https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-STATICFILES_FINDERS
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
DEFAULT_FROM_EMAIL = "newsreader@rss.fudiggity.nl"
# Email
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
DEFAULT_FROM_EMAIL = get_env(
"EMAIL_DEFAULT_FROM", required=False, default="webmaster@localhost"
)
EMAIL_HOST = get_env("EMAIL_HOST", required=False, default="localhost")
EMAIL_PORT = get_env("EMAIL_PORT", cast=int, required=False, default=25)
EMAIL_HOST_USER = get_env("EMAIL_HOST_USER", required=False, default="")
EMAIL_HOST_PASSWORD = get_env("EMAIL_HOST_PASSWORD", required=False, default="")
EMAIL_USE_TLS = get_env("EMAIL_USE_TLS", required=False, default=False)
EMAIL_USE_SSL = get_env("EMAIL_USE_SSL", required=False, default=False)
# Third party settings
AXES_HANDLER = "axes.handlers.cache.AxesCacheHandler"
@ -212,7 +221,12 @@ REST_FRAMEWORK = {
"rest_framework.permissions.IsAuthenticated",
"newsreader.accounts.permissions.IsOwner",
),
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
"DEFAULT_RENDERER_CLASSES": (
"djangorestframework_camel_case.render.CamelCaseJSONRenderer",
),
"DEFAULT_PARSER_CLASSES": (
"djangorestframework_camel_case.parser.CamelCaseJSONParser",
),
}
SWAGGER_SETTINGS = {
@ -223,8 +237,15 @@ SWAGGER_SETTINGS = {
# Celery
# https://docs.celeryproject.org/en/stable/userguide/configuration.html
# Note that celery settings are prefix with CELERY. See src/newsreader/celery.py.
CELERY_WORKER_HIJACK_ROOT_LOGGER = False
CELERY_BROKER_URL = "amqp://guest@rabbitmq:5672"
REGISTRATION_OPEN = True
REGISTRATION_AUTO_LOGIN = True
ACCOUNT_ACTIVATION_DAYS = 7
# Sentry
SENTRY_CONFIG = {
"dsn": get_env("SENTRY_DSN", default="", required=False),
"send_default_pii": False,
"integrations": [DjangoIntegration(), CeleryIntegration()]
if DjangoIntegration and CeleryIntegration
else [],
}

46
src/newsreader/conf/ci.py Normal file
View file

@ -0,0 +1,46 @@
from .base import * # noqa: F403
from .utils import get_current_version
DEBUG = True
del LOGGING["handlers"]["file"] # noqa: F405
del LOGGING["handlers"]["celery"] # noqa: F405
LOGGING["loggers"].update( # noqa: F405
{
"celery.task": {"handlers": ["console"], "level": "DEBUG"},
"newsreader": {"handlers": ["console"], "level": "INFO"},
}
)
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
AXES_ENABLED = False
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "memcached:11211",
},
"axes": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "memcached:11211",
},
}
# Project settings
VERSION = get_current_version()
ENVIRONMENT = "ci"
try:
# Optionally use sentry integration
from sentry_sdk import init as sentry_init
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) # noqa: F405
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -1,35 +1,35 @@
from .base import * # isort:skip
from .base import * # noqa: F403
from .utils import get_current_version
SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl"
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar", "django_extensions"]
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
]
},
}
]
TEMPLATES[0]["OPTIONS"]["context_processors"].append( # noqa: F405
"django.template.context_processors.debug",
)
# Project settings
VERSION = get_current_version()
# Third party settings
AXES_FAILURE_LIMIT = 50
AXES_COOLOFF_TIME = None
try:
# Optionally use sentry integration
from sentry_sdk import init as sentry_init
from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION}) # noqa: F405
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -1,29 +1,43 @@
from .dev import * # isort:skip
from .base import * # noqa: F403
from .utils import get_current_version
SECRET_KEY = "=q(ztyo)b6noom#a164g&s9vcj1aawa^g#ing_ir99=_zl4g&$"
DEBUG = True
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "newsreader",
"USER": "newsreader",
"PASSWORD": "newsreader",
"HOST": "db",
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
LOGGING["loggers"].update( # noqa: F405
{
"celery.task": {"handlers": ["console", "celery"], "level": "DEBUG"},
}
}
)
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "memcached:11211",
},
"axes": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "memcached:11211",
},
}
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# Celery
# https://docs.celeryproject.org/en/latest/userguide/configuration.html
CELERY_BROKER_URL = "amqp://guest:guest@rabbitmq:5672//"
TEMPLATES[0]["OPTIONS"]["context_processors"].append( # noqa: F405
"django.template.context_processors.debug",
)
# Project settings
VERSION = get_current_version()
ENVIRONMENT = "docker"
# Third party settings
# Axes
AXES_FAILURE_LIMIT = 50
AXES_COOLOFF_TIME = None
try:
# Optionally use sentry integration
from sentry_sdk import init as sentry_init
from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) # noqa: F405
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -1,19 +0,0 @@
from .base import * # isort:skip
SECRET_KEY = "29%lkw+&n%^w4k#@_db2mo%*tc&xzb)x7xuq*(0$eucii%4r0c"
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
AXES_ENABLED = False
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "memcached:11211",
},
"axes": {
"BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
"LOCATION": "memcached:11211",
},
}

View file

@ -1,51 +1,32 @@
import os
from newsreader.conf.utils import get_env
from dotenv import load_dotenv
from .base import * # noqa: F403
from .utils import get_current_version
from .base import * # isort:skip
load_dotenv()
DEBUG = False
ALLOWED_HOSTS = ["rss.fudiggity.nl"]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
ADMINS = [
("", email)
for email in os.getenv("ADMINS", "").split(",")
if os.environ.get("ADMINS")
("", email) for email in get_env("ADMINS", split=",", required=False, default=[])
]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ["POSTGRES_HOST"],
"PORT": os.environ["POSTGRES_PORT"],
"NAME": os.environ["POSTGRES_NAME"],
"USER": os.environ["POSTGRES_USER"],
"PASSWORD": os.environ["POSTGRES_PASSWORD"],
}
}
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
]
},
}
]
# Project settings
VERSION = get_current_version(debug=False)
ENVIRONMENT = "production"
# 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
SENTRY_CONFIG.update( # noqa: F405
{"release": VERSION, "environment": ENVIRONMENT, "debug": False}
)
sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError:
pass

View file

@ -0,0 +1,85 @@
import logging
import os
import subprocess
from pathlib import Path
from typing import Any, Iterable, Type
logger = logging.getLogger(__name__)
def get_env(
name: str,
cast: Type = str,
required: bool = True,
default: Any = None,
split: str = "",
) -> Any:
if cast is not str and split:
raise TypeError(f"Split is not possible with {cast}")
value = os.getenv(name)
if not value:
if required:
logger.warning(f"Missing environment variable: {name}")
return default
bool_mapping = {"yes": True, "true": True, "false": False, "no": False}
if cast is bool:
_value = bool_mapping.get(value.lower())
if not value:
raise ValueError(f"Unknown boolean value: {_value}")
return _value
value = value if not cast else cast(value)
return value if not split else value.split(split)
def get_current_version(debug: bool = True) -> str:
version = get_env("VERSION", required=False)
if version:
return version
if debug:
try:
output = subprocess.check_output(
["git", "show", "--no-patch", "--format=%H"], universal_newlines=True
)
return output.strip()
except (subprocess.CalledProcessError, OSError):
return ""
try:
output = subprocess.check_output(
["git", "describe", "--tags"], universal_newlines=True
)
return output.strip()
except (subprocess.CalledProcessError, OSError):
return ""
ROOT_MARKERS = ("pyproject.toml", "package.json", "README.md", "CHANGELOG.md")
def get_root_dir() -> Path:
file = Path(__file__)
return _traverse_dirs(file.parent, ROOT_MARKERS)
def _traverse_dirs(path: Path, root_markers: Iterable[str]) -> Path:
if path.parent == path:
raise OSError("Root directory detected")
files = (file.name for file in path.iterdir())
if not any((marker for marker in root_markers if marker in files)):
return _traverse_dirs(path.parent, root_markers)
return path

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class CoreConfig(AppConfig):
name = "core"
name = "newsreader.core"

View file

@ -0,0 +1,9 @@
from django import forms
class CheckboxInput(forms.CheckboxInput):
template_name = "components/form/checkbox.html"
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
return {**context, **attrs}

View file

@ -1,7 +1,7 @@
from rest_framework.pagination import PageNumberPagination
from rest_framework import pagination
class ResultSetPagination(PageNumberPagination):
class ResultSetPagination(pagination.PageNumberPagination):
page_size_query_param = "count"
max_page_size = 50
page_size = 30
@ -10,3 +10,9 @@ class ResultSetPagination(PageNumberPagination):
class LargeResultSetPagination(ResultSetPagination):
max_page_size = 100
page_size = 50
class CursorPagination(pagination.CursorPagination):
page_size_query_param = "count"
ordering = "-publication_date"
page_size = 30

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -47,7 +47,7 @@
"user" : 2,
"succeeded" : true,
"modified" : "2019-07-20T11:28:16.473Z",
"last_suceeded" : "2019-07-20T11:28:16.316Z",
"last_run" : "2019-07-20T11:28:16.316Z",
"name" : "Hackers News",
"website_url" : null,
"created" : "2019-07-14T13:08:10.374Z",
@ -65,7 +65,7 @@
"error" : null,
"user" : 2,
"succeeded" : true,
"last_suceeded" : "2019-07-20T11:28:15.691Z",
"last_run" : "2019-07-20T11:28:15.691Z",
"name" : "BBC",
"modified" : "2019-07-20T12:07:49.164Z",
"timezone" : "UTC",
@ -85,7 +85,7 @@
"website_url" : null,
"name" : "Ars Technica",
"succeeded" : true,
"last_suceeded" : "2019-07-20T11:28:15.986Z",
"last_run" : "2019-07-20T11:28:15.986Z",
"modified" : "2019-07-20T11:28:16.033Z",
"user" : 2
},
@ -102,7 +102,7 @@
"user" : 2,
"name" : "The Guardian",
"succeeded" : true,
"last_suceeded" : "2019-07-20T11:28:16.078Z",
"last_run" : "2019-07-20T11:28:16.078Z",
"modified" : "2019-07-20T12:07:44.292Z",
"created" : "2019-07-20T11:25:02.089Z",
"website_url" : null,
@ -119,7 +119,7 @@
"website_url" : null,
"created" : "2019-07-20T11:25:30.121Z",
"user" : 2,
"last_suceeded" : "2019-07-20T11:28:15.860Z",
"last_run" : "2019-07-20T11:28:15.860Z",
"succeeded" : true,
"modified" : "2019-07-20T12:07:28.473Z",
"name" : "Tweakers"
@ -139,7 +139,7 @@
"website_url" : null,
"timezone" : "UTC",
"user" : 2,
"last_suceeded" : "2019-07-20T11:28:16.034Z",
"last_run" : "2019-07-20T11:28:16.034Z",
"succeeded" : true,
"modified" : "2019-07-20T12:07:21.704Z",
"name" : "The Verge"

View file

@ -2,7 +2,7 @@ import React from 'react';
const Card = props => {
return (
<div className="card">
<div id={`${props.id}`} className="card">
<div className="card__header">{props.header}</div>
<div className="card__content">{props.content}</div>
<div className="card__footer">{props.footer}</div>

View file

@ -3,26 +3,24 @@ 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;
});
this.setState({ messages: newMessages });
}
};
render() {
const messages = this.state.messages.map((message, index) => {
return (
<li key={index} className={`messages__item messages__item--${message.type}`}>
{message.text} <i className="gg-close" onClick={() => this.close(index)} />
{message.text} <i className="fas fa-times" onClick={() => this.close(index)} />
</li>
);
});
return <ul className="list messages">{messages}</ul>;
return <ul className="list messages messages--fixed">{messages}</ul>;
}
}

View file

@ -0,0 +1,22 @@
import React from 'react';
class NavList extends React.Component {
render() {
const entries = Object.entries(this.props.navLinks);
const links = entries.map(([name, link], index) => {
return (
<li key={index} className="nav-list__item">
<a href={link}>{name}</a>
</li>
);
});
const className = this.props.includeBorder
? 'nav-list nav-list--bordered'
: 'nav-list';
return <ol className={className}>{links}</ol>;
}
}
export default NavList;

View file

@ -1,6 +1,4 @@
class Selector {
onClick = ::this.onClick;
inputs = [];
constructor() {
@ -11,13 +9,13 @@ class Selector {
selectAllInput.onchange = this.onClick;
}
onClick(e) {
onClick = e => {
const targetValue = e.target.checked;
this.inputs.forEach(input => {
input.checked = targetValue;
});
}
};
}
export default Selector;

View file

@ -0,0 +1,25 @@
import React from 'react';
import NavList from './NavList.js';
// TODO: show empty category message
class Sidebar extends React.Component {
render() {
return (
<div className="sidebar">
<div className="sidebar__nav">
<NavList
navLinks={this.props.navLinks}
includeBorder={this.props.includeBorder}
/>
{this.props.children}
</div>
<label htmlFor="menu-input" className="sidebar__close" />
</div>
);
}
}
export default Sidebar;

View file

@ -1,3 +1,5 @@
import './lib/index.js';
import './pages/homepage/index.js';
import './pages/categories/index.js';
import './pages/rules/index.js';
import './pages/default/index.js';

View file

@ -0,0 +1 @@
import './theme.js';

View file

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

View file

@ -6,12 +6,9 @@ import Card from '../../components/Card.js';
import CategoryCard from './components/CategoryCard.js';
import CategoryModal from './components/CategoryModal.js';
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);
@ -23,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',
@ -59,7 +56,7 @@ class App extends React.Component {
text: 'Unable to remove category, try again later',
};
return this.setState({ selectedCategoryId: null, message: message });
}
};
render() {
const { categories } = this.state;
@ -69,6 +66,7 @@ class App extends React.Component {
key={category.pk}
category={category}
showDialog={this.selectCategory}
updateUrl={this.props.updateUrl}
/>
);
});
@ -80,7 +78,7 @@ class App extends React.Component {
const pageHeader = (
<>
<h1 className="h1">Categories</h1>
<a className="link button button--confirm" href="/core/categories/create/">
<a className="link button button--confirm" href={`${this.props.createUrl}/`}>
Create category
</a>
</>
@ -89,15 +87,19 @@ class App extends React.Component {
return (
<>
{this.state.message && <Messages messages={[this.state.message]} />}
<Card header={pageHeader} />
{cards}
{selectedCategory && (
<CategoryModal
category={selectedCategory}
handleCancel={this.deselectCategory}
handleDelete={this.deleteCategory}
/>
)}
<Sidebar navLinks={this.props.navLinks} />
<div className="main__container">
<Card header={pageHeader} />
{cards}
{selectedCategory && (
<CategoryModal
category={selectedCategory}
handleCancel={this.deselectCategory}
handleDelete={this.deleteCategory}
/>
)}
</div>
</>
);
}

View file

@ -11,7 +11,7 @@ const CategoryCard = props => {
if (rule.favicon) {
favicon = <img className="favicon" src={rule.favicon} />;
} else {
favicon = <i className="gg-image" />;
favicon = <i className="fas fa-image" />;
}
return (
@ -33,7 +33,7 @@ const CategoryCard = props => {
<>
<a
className="link button button--primary"
href={`/core/categories/${category.pk}/`}
href={`${props.updateUrl}/${category.pk}/`}
>
Edit
</a>

View file

@ -9,5 +9,19 @@ if (page) {
const dataScript = document.getElementById('categories-data');
const categories = JSON.parse(dataScript.textContent);
ReactDOM.render(<App categories={categories} />, page);
let createUrl = document.getElementById('createUrl').textContent;
let updateUrl = document.getElementById('updateUrl').textContent;
let linkScript = document.getElementById('Links');
let navLinks = JSON.parse(linkScript.textContent);
ReactDOM.render(
<App
categories={categories}
createUrl={createUrl.substring(1, createUrl.length - 2)}
updateUrl={updateUrl.substring(1, updateUrl.length - 4)}
navLinks={navLinks}
/>,
page
);
}

View file

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Sidebar from '../../components/Sidebar';
const mainElements = [...document.getElementsByClassName('main')];
const mainElement = mainElements.find(element => element.dataset.renderSidebar);
if (mainElement) {
let linkScript = document.getElementById('Links');
let navLinks = JSON.parse(linkScript.textContent);
ReactDOM.render(
ReactDOM.createPortal(<Sidebar navLinks={navLinks} />, mainElement),
document.createElement('div')
);
}

Some files were not shown because too many files have changed in this diff Show more