Compare commits

...

108 commits
v0.7.4 ... main

Author SHA1 Message Date
Mouse Reeve
ba1f180c83
Merge pull request #3508 from bookwyrm-social/dependabot/pip/django-4.2.18
Bump django from 4.2.17 to 4.2.18
2025-02-13 20:05:13 -08:00
dependabot[bot]
924f377e4e
Bump django from 4.2.17 to 4.2.18
Bumps [django](https://github.com/django/django) from 4.2.17 to 4.2.18.
- [Commits](https://github.com/django/django/compare/4.2.17...4.2.18)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-14 03:14:17 +00:00
Mouse Reeve
eef66b5fc4
Merge pull request #3507 from bookwyrm-social/update-linter-ubuntu
GitHub emailed me to say we need to use a later ubuntu version for these
2025-02-13 19:13:28 -08:00
Mouse Reeve
4138a2b6aa GitHub emailed me to say we need to use a later ubuntu version for these 2025-02-13 18:39:40 -08:00
Mouse Reeve
03bab92ee6
Merge pull request #3492 from bookwyrm-social/inventaire-titles
Alters get_description code for inventaire queries
2025-01-10 18:21:17 -08:00
Mouse Reeve
d9d614b3bc Updates test string for inventaire 2025-01-10 18:11:57 -08:00
Mouse Reeve
609b7f58c8 Alters get_description code for inventaire queries 2025-01-10 18:01:48 -08:00
Mouse Reeve
3916666897
Merge pull request #3478 from bookwyrm-social/dependabot/pip/django-4.2.17
Bump django from 4.2.16 to 4.2.17
2024-12-06 16:15:01 -08:00
dependabot[bot]
305ef9195b
Bump django from 4.2.16 to 4.2.17
Bumps [django](https://github.com/django/django) from 4.2.16 to 4.2.17.
- [Commits](https://github.com/django/django/compare/4.2.16...4.2.17)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-06 18:58:00 +00:00
Mouse Reeve
de8ba3cf86
Merge pull request #3476 from hughrun/block-invites
Prevent invite requests from blocked domains
2024-11-30 19:25:03 -08:00
Hugh Rundle
023e62294e
Prevent invite requests from blocked domains
Prevents form submission when requesting an email invite using an address from a blocked domain.

Fixes #3366
2024-11-30 15:54:37 +11:00
Mouse Reeve
61845678c4
Merge pull request #3473 from bookwyrm-social/dependabot/pip/aiohttp-3.10.11
Bump aiohttp from 3.10.2 to 3.10.11
2024-11-18 18:49:03 -08:00
dependabot[bot]
0a48c7c81b
Bump aiohttp from 3.10.2 to 3.10.11
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.2 to 3.10.11.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.2...v3.10.11)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 21:30:35 +00:00
Mouse Reeve
d16faac6da
Merge pull request #3460 from hughrun/3426
fix failing test from #3432
2024-10-20 18:50:46 -07:00
Hugh Rundle
750cedd078
fix failing test 2024-10-21 12:41:16 +11:00
Mouse Reeve
14dba48415
Merge pull request #3437 from hughrun/published_date
Fix post dates being inconsistent
2024-10-17 15:39:13 -07:00
Mouse Reeve
09bbaf0671
Merge pull request #3442 from bookwyrm-social/dependabot/pip/django-4.2.16
Bump django from 4.2.15 to 4.2.16
2024-10-17 15:33:48 -07:00
Mouse Reeve
f2eb3fdccb
Merge pull request #3458 from bookwyrm-social/update_locales
Update locales
2024-10-17 15:33:33 -07:00
Mouse Reeve
20026b968d
Merge pull request #3454 from Guanchishan/patch-3
show Wikidata link on author page
2024-10-17 15:33:15 -07:00
Mouse Reeve
7ecf3b65d7
Merge pull request #3451 from Guanchishan/patch-1
Fix IntegrityError caused by duplicate periodic task creation
2024-10-17 15:23:31 -07:00
Mouse Reeve
0a3fc58669
Check for wikidata when determining if links are present 2024-10-17 15:18:48 -07:00
Mouse Reeve
0ccbf8d739 Fixes inconsistent wording in source text 2024-10-17 14:56:20 -07:00
Mouse Reeve
8594c8c0f6 Updates locales 2024-10-17 14:55:07 -07:00
Mouse Reeve
f1b0e4b9d6
Merge pull request #3432 from hughrun/3426
add test for resolving users with aliases
2024-10-17 14:49:33 -07:00
Mouse Reeve
1923db31b1
Merge pull request #3434 from hughrun/signed_requests
sign all AP requests
2024-10-17 14:13:55 -07:00
Davidzdh
f8650bbb1c
show Wikidata link on author page
Supporting Wikidata is very wonderful. And how about make Wikidata link visible on author page? 
Modified bookwyrm\templates\author\author.html
2024-10-13 19:02:33 +09:00
Davidzdh
6143aa66e3
Fix IntegrityError when creating periodic tasks
Change PeriodicTask.objects.get_or_create() to PeriodicTask.objects.update_or_create().

This change prevents a potential IntegrityError when creating a periodic task due to duplicate primary key. By using update_or_create, if the record already exists, it will be updated instead of attempting to insert a new record with the same primary key, ensuring the process completes without error.
2024-10-11 20:28:27 +09:00
dependabot[bot]
999829dc8b
Bump django from 4.2.15 to 4.2.16
Bumps [django](https://github.com/django/django) from 4.2.15 to 4.2.16.
- [Commits](https://github.com/django/django/compare/4.2.15...4.2.16)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-08 21:28:47 +00:00
Hugh Rundle
514c54f30a
black formatting 2024-09-19 19:46:03 +10:00
Adeodato Simó
6745a9a3f5
Add test for #3365 fix (#5) 2024-09-19 19:38:33 +10:00
Hugh Rundle
bee9dafc9d
update tests 2024-09-18 19:51:43 +10:00
Hugh Rundle
dd45823790
enable localised dates 2024-09-18 19:40:44 +10:00
Hugh Rundle
a61ca162b0
Fix post dates being inconsistent
Makes all dates fixed except if in the last day, in which case they are relative times.

Fixes #3365
2024-09-14 14:33:02 +10:00
Hugh Rundle
cfe10fa874
update error message 2024-09-09 10:19:03 +10:00
Mouse Reeve
13381b9b4d
Merge pull request #3435 from hughrun/docker-version
docker-compose 'version' has been deprecated
2024-09-08 17:16:10 -07:00
Hugh Rundle
c0ce22a607
docker-compose 'version' has been deprecated 2024-09-09 10:04:16 +10:00
Hugh Rundle
9c89e0fbaf
sign all AP requests
We have been attempting unsigned GET requests and then if they fail,
sending another signed request within the same loop.

This not only causes single-threaded instances to fail, confusing new
users, but is also unnecessary, since servers that don't require
requests to be signed will just ignore the signed headers.

Signing all AP requests simplifies things and should see a
marginal increase in speed when interacting with new servers
that require signed payloads.
2024-09-09 09:46:04 +10:00
Hugh Rundle
1a93ba0cac
check alias with new user data
Instead of breaking a bunch of existing tests...
2024-09-09 08:47:59 +10:00
Hugh Rundle
ed55e052f6
add test for resolving users with aliases 2024-09-08 18:11:47 +10:00
Mouse Reeve
cbdb59d3cf
Merge pull request #3408 from bookwyrm-social/dependabot/pip/aiohttp-3.10.2
Bump aiohttp from 3.9.4 to 3.10.2
2024-09-02 05:39:26 -07:00
Dato Simó
80248d3c1d Convert min_confidence param to string to appease mypy 2024-08-31 20:16:41 -03:00
Mouse Reeve
904aa6c49a
Merge pull request #3394 from matthewmincher/feature/user-shelf-preview-order
Order user shelf previews by book shelved date
2024-08-27 18:38:21 -07:00
Mouse Reeve
4123478058
Merge pull request #3423 from bookwyrm-social/misc-tests
Adds some unit tests
2024-08-27 18:33:43 -07:00
Mouse Reeve
3555ef9d2e
Merge pull request #3416 from bookwyrm-social/edit-status-header
Sets edit status header to indicate status type
2024-08-27 14:37:26 -07:00
Hugh Rundle
23dfe3924d
Merge pull request #3418 from bookwyrm-social/hide-ratings
Hide ratings
2024-08-27 19:06:07 +10:00
Mouse Reeve
abb97e6044
Merge branch 'main' into edit-status-header 2024-08-26 13:34:49 -07:00
Mouse Reeve
a6912dc0c2
Merge branch 'main' into hide-ratings 2024-08-26 12:35:47 -07:00
Mouse Reeve
df607a0e45
Merge pull request #3419 from bookwyrm-social/test-fix
Uses workaround for test that fails ~sometimes~
2024-08-26 12:35:32 -07:00
Mouse Reeve
bd773f41c7 Uses much simpler approach to ensuring test result order 2024-08-26 11:33:01 -07:00
Mouse Reeve
9f5ca7ae60 Uses workaround for test that fails ~sometimes~ 2024-08-25 17:53:43 -07:00
Mouse Reeve
698a0b113c Adds some unit tests 2024-08-24 14:54:52 -07:00
Mouse Reeve
c9155bdd78 Adds migration 2024-08-23 18:17:36 -07:00
Mouse Reeve
23471f698c Prettifies javascript 2024-08-23 17:41:02 -07:00
Mouse Reeve
612098475a Adds option to show ratings to the user settings panel 2024-08-23 17:34:52 -07:00
Mouse Reeve
f53bb62b2b Allow users to hide ratings in the UI 2024-08-23 17:32:03 -07:00
Mouse Reeve
142ed70d4d Sets edit status header to indicate status type
Fixes #2671
2024-08-23 16:42:39 -07:00
Mouse Reeve
413c26bc5e
Merge pull request #3135 from hughrun/csv
csv import and export fixes
2024-08-23 16:29:04 -07:00
Mouse Reeve
7ff1ab974e
Merge pull request #3411 from timothyjrogers/fix-link-verification-modal-unauthenticated-buttons
Narrowed is_authenticated check in verfication_modal to only restrict…
2024-08-12 19:11:07 -07:00
Tim Rogers
cf61279e18 Narrowed is_authenticated check in verfication_modal to only restrict report button 2024-08-12 19:56:26 -05:00
Mouse Reeve
6ec3783736
Merge branch 'main' into dependabot/pip/aiohttp-3.10.2 2024-08-11 17:12:21 -07:00
Mouse Reeve
cf753afaa9
Merge pull request #3410 from timothyjrogers/trim-search-whitespace
Trim search whitespace
2024-08-11 17:12:04 -07:00
Tim Rogers
f68e33ffc6 Added additional test cases for searches with leading and trailing whitespace 2024-08-11 14:02:24 -05:00
Tim Rogers
2bb77d9bf8 Updated search view to trim leading and trailing whitespace for author, book, and list query values 2024-08-11 13:01:48 -05:00
Hugh Rundle
7fc54c509c
fixes for bookwyrm csv import
- fix tests
- revert change to GenericImporter tests
- import the review name
- add extra properties to ImportItem
2024-08-10 16:37:30 +10:00
Mouse Reeve
95c2798fc7
Merge pull request #3401 from dato/comment_progress_keep_start_date
Fix reading progress `start_date` bug
2024-08-09 13:07:01 -07:00
Mouse Reeve
66dd39e6d8
Merge pull request #3151 from dato/celery_inmem
In-memory Celery backend for tests
2024-08-09 13:02:53 -07:00
Mouse Reeve
96a2fa5afc
Merge pull request #3402 from dato/ostatus_no_empty_titles
Avoid empty <title> in templates
2024-08-09 12:43:06 -07:00
Mouse Reeve
9e73ab048b
Merge pull request #3407 from bookwyrm-social/dependabot/pip/django-4.2.15
Bump django from 4.2.14 to 4.2.15
2024-08-09 12:40:24 -07:00
dependabot[bot]
fc33edb749
Bump aiohttp from 3.9.4 to 3.10.2
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.4 to 3.10.2.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.4...v3.10.2)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-09 18:22:34 +00:00
dependabot[bot]
b45435803a
Bump django from 4.2.14 to 4.2.15
Bumps [django](https://github.com/django/django) from 4.2.14 to 4.2.15.
- [Commits](https://github.com/django/django/compare/4.2.14...4.2.15)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 19:33:05 +00:00
Adeodato Simó
69962bb7c9 Avoid empty <title> in templates
Newer versions of tidylib complain about this.
2024-07-28 21:58:26 -03:00
Hugh Rundle
8aba8caae9
merge migration 2024-07-28 21:08:14 +10:00
Adeodato Simó
3d28019146 Use in-memory backends for Celery in tests and workflows
At the moment, this doesn't have much of an effect since most task
calls are mock'd out.
2024-07-28 06:42:11 -03:00
Adeodato Simó
f4133e0236 celerywyrm: allow broker and result backend from the environment
This allows to easily configure an in-memory transport for tests.
2024-07-28 06:42:11 -03:00
Adeodato Simó
041f2fc413 edit_readthrough: set start_date/finish_date iff present in request
Fixes: #3164
2024-07-28 05:58:38 -03:00
Adeodato Simó
812221456b Add failing test case for comment progress clobbering start_date 2024-07-28 05:56:25 -03:00
Mouse Reeve
43577f3ca0
Merge branch 'main' into csv 2024-07-27 12:26:21 -07:00
Mouse Reeve
7f773b3dbd
Merge pull request #3398 from bookwyrm-social/pylint3
Upgrade pylint to 3.x
2024-07-27 12:03:21 -07:00
Mouse Reeve
7311526f2e
Merge pull request #3375 from bookwyrm-social/pr-template
Reorganizes PR template a bit
2024-07-27 12:01:32 -07:00
Mouse Reeve
355a6071ff
Merge branch 'main' into pylint3 2024-07-27 11:48:58 -07:00
Mouse Reeve
e41b27e5f4
Merge branch 'main' into pr-template 2024-07-27 11:48:48 -07:00
Mouse Reeve
eef055159e
Merge pull request #3400 from dato/upgrade_sqlparse
Add an up-to-date sqlparse to requirements.txt
2024-07-27 11:48:38 -07:00
Adeodato Simó
08876512eb Actions: run makemigrations check with increased verbosity 2024-07-27 15:27:09 -03:00
Adeodato Simó
afd44e109c Add an up-to-date sqlparse to requirements.txt
This is used in utils/db.py to format triggers.

A migration is needed for minor whitespace fixes between
0.4.4 and 0.5.1.
2024-07-27 15:19:43 -03:00
Mouse Reeve
f0e5c334e7
Merge pull request #3399 from dato/pylint_useless_suppression_cleanup
pylint suppression cleanup
2024-07-27 09:21:17 -07:00
Mouse Reeve
300eeac0e1
Merge pull request #3374 from bookwyrm-social/landing-page-books
Use a simpler query for books to show on the landing page
2024-07-27 08:47:02 -07:00
Mouse Reeve
dc54f28e17 Type fix 2024-07-27 08:44:44 -07:00
Mouse Reeve
8ce29849a9
Merge pull request #3393 from bookwyrm-social/dependabot/pip/django-4.2.14
Bump django from 4.2.11 to 4.2.14
2024-07-27 08:36:17 -07:00
Adeodato Simó
0b87aacfce pylint: enable useless-suppression lint and perform cleanup 2024-07-27 03:47:35 -03:00
Adeodato Simó
81ee5e945f pylint: remove unused global suppressions 2024-07-27 03:42:27 -03:00
Adeodato Simó
1a0fbac76c pylint: upgrade to 3.2.6
This only requires fixing:

    E0606: Possibly using variable 'results' before assignment
    E0606: Possibly using variable 'input_type' before assignment
2024-07-27 03:38:47 -03:00
Adeodato Simó
2cdbddca09 .pylintrc: use symbolic names for message suppressions 2024-07-27 03:10:47 -03:00
Hugh Rundle
1608ca6401
improve formatting 2024-07-27 13:16:34 +10:00
Hugh Rundle
93c6b76dab
Merge branch 'main' into csv 2024-07-27 13:04:38 +10:00
Hugh Rundle
94dfbbcc05
fix BookwyrmBooksImporter and tests
- change class attribute to instance attribute for mappings
- remove comment from test
- order import retry jobs in generic importer test

This last change seems innocuous but I may be missing something more fundamental  - it was otherwise failing when multiple tests are run, I think because running tests in parallel led to import jobs getting out of order?
2024-07-27 12:55:15 +10:00
Matthew Mincher
f6eb4f4f27
Add test for shelf order 2024-07-21 13:42:44 +01:00
Matthew Mincher
acc68147dc
Order user shelf previews by book shelved date 2024-07-21 12:39:05 +01:00
Bart Schuurmans
ab307388f4
Merge pull request #3384 from lo48576/fix/css-path-prefix-with-s3-backend
Fix CSS path prefix when S3 storage is used
2024-07-14 13:24:02 +02:00
dependabot[bot]
8a235bcda1
Bump django from 4.2.11 to 4.2.14
Bumps [django](https://github.com/django/django) from 4.2.11 to 4.2.14.
- [Commits](https://github.com/django/django/compare/4.2.11...4.2.14)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 22:16:44 +00:00
Hugh Rundle
06d6360082
merge latest changes and add tests 2024-06-30 18:54:59 +10:00
Hugh Rundle
e5b260e3ee
Merge branch 'main' into csv 2024-06-29 16:03:15 +10:00
Mouse Reeve
3236003107
Merge pull request #3378 from hughrun/get-books-for-user
possible fix for #3372 - user export timeouts
2024-06-22 20:24:36 -07:00
YOSHIOKA Takuma
1a2f434514
Fix CSS path prefix when S3 storage is used
django-sass-processor 1.4 looks up OPTIONS using `sass_processor`
instead of `staticfiles`.

Fixes #3383.
2024-06-13 05:03:10 +09:00
Hugh Rundle
1d4119e853
LOL
remove Q import so pylint doesn't grumble
2024-06-09 10:59:11 +10:00
Hugh Rundle
261e794c1c
possible fix for #3372 - user export timeouts
This definitely needs to be tested on a large DB but I believe it may fix the timeouts b.s. gets when running user exports.

Instead of a gigantic single DB query with heaps of joins, we instead just do a series of simple queries and then use union()
to pull them into a de-duped queryset.

If I understand the results from explain() correctly, this is a massive reduction in DB work:

Unique  (cost=195899.15..198201.71 rows=11808 width=19220)

vs

Unique  (cost=150.28..153.44 rows=16 width=19220)
2024-06-09 10:34:22 +10:00
Mouse Reeve
2e675474a9 Reorganizes PR template a bit
I found the template a little overwhelming, so this is an attempt to
make it a little more navigable and slightly less effortful.
2024-06-08 08:31:34 -07:00
Mouse Reeve
3f08d6d8c4 Use a simpler query for books to show on the landing page 2024-06-08 08:18:54 -07:00
Hugh Rundle
539a9fa212
csv import and export fixes
Adds shelved and published dates for books and their imported reviews.
Provides option to create new (custom) shelves when importing books.

fixes #3004
fixes #2846
fixes #2666
fixes #2411
2023-11-25 17:34:12 +11:00
153 changed files with 9768 additions and 4977 deletions

View file

@ -1,10 +1,5 @@
<!--
Thanks for contributing!
Please ensure the name of your PR is written in imperative present tense. For example:
- "fix color contrast on submit buttons"
- "add 'favourite food' value to Author model"
Thanks for contributing! This template has some checkboxes that help keep track of what changes go into a release.
To check (tick) a list item, replace the space between square brackets with an x, like this:
@ -12,24 +7,23 @@ To check (tick) a list item, replace the space between square brackets with an x
You can find more information and tips for BookWyrm contributors at https://docs.joinbookwyrm.com/contributing.html
-->
## Are you finished?
### Linters
## Description
<!--
Please run linters on your code before submitting your PR.
If you miss this step it is likely that the GitHub task runners will fail.
Describe what your pull request does here
-->
- [ ] I have checked my code with `black`, `pylint`, and `mypy`, or `./bw-dev formatters`
### Tests
<!-- Check one -->
<!--
For pull requests that relate or close an issue, please include them
below. We like to follow [Github's guidance on linking issues to pull requests](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
- [ ] My changes do not need new tests
- [ ] All tests I have added are passing
- [ ] I have written tests but need help to make them pass
- [ ] I have not written tests and need help to write them
For example having the text: "closes #1234" would connect the current pull
request to issue 1234. And when we merge the pull request, Github will
automatically close the issue.
-->
- Related Issue #
- Closes #
## What type of Pull Request is this?
<!-- Check all that apply -->
@ -48,21 +42,6 @@ If you miss this step it is likely that the GitHub task runners will fail.
### Details of breaking or configuration changes (if any of above checked)
## Description
<!--
Describe what your pull request does here.
For pull requests that relate or close an issue, please include them
below. We like to follow [Github's guidance on linking issues to pull requests](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
For example having the text: "closes #1234" would connect the current pull
request to issue 1234. And when we merge the pull request, Github will
automatically close the issue.
-->
- Related Issue #
- Closes #
## Documentation
<!--
@ -76,3 +55,14 @@ Our documentation is maintained in a separate repository at https://github.com/b
- [ ] I have created a matching pull request in the Documentation repository
- [ ] I intend to create a matching pull request in the Documentation repository after this PR is merged
<!-- Amazing! Thanks for filling that out. Your PR will need to have passing tests and happy linters before we can merge
You will need to check your code with `black`, `pylint`, and `mypy`, or `./bw-dev formatters`
-->
### Tests
<!-- Check one -->
- [ ] My changes do not need new tests
- [ ] All tests I have added are passing
- [ ] I have written tests but need help to make them pass
- [ ] I have not written tests and need help to write them

View file

@ -15,7 +15,7 @@ on:
jobs:
lint:
name: Lint with stylelint and ESLint.
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.

View file

@ -10,7 +10,7 @@ on:
jobs:
lint:
name: Lint with Prettier
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.

View file

@ -48,7 +48,7 @@ jobs:
- name: Set up .env
run: cp .env.example .env
- name: Check migrations up-to-date
run: python ./manage.py makemigrations --check
run: python ./manage.py makemigrations --check -v 3
- name: Run Tests
run: pytest -n 3

View file

@ -3,7 +3,19 @@ ignore=migrations
load-plugins=pylint.extensions.no_self_use
[MESSAGES CONTROL]
disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801,C3001,import-error
disable =
cyclic-import,
duplicate-code,
fixme,
no-member,
raise-missing-from,
too-few-public-methods,
too-many-ancestors,
too-many-instance-attributes,
unnecessary-lambda-assignment,
unsubscriptable-object,
enable =
useless-suppression
[FORMAT]
max-line-length=88

View file

@ -369,17 +369,13 @@ def resolve_remote_id(
# load the data and create the object
try:
data = get_data(remote_id)
data = get_activitypub_data(remote_id)
except ConnectionError:
logger.info("Could not connect to host for remote_id: %s", remote_id)
return None
except requests.HTTPError as e:
if (e.response is not None) and e.response.status_code == 401:
# This most likely means it's a mastodon with secure fetch enabled.
data = get_activitypub_data(remote_id)
else:
logger.info("Could not connect to host for remote_id: %s", remote_id)
return None
logger.exception("HTTP error - remote_id: %s - error: %s", remote_id, e)
return None
# determine the model implicitly, if not provided
# or if it's a model with subclasses like Status, check again
if not model or hasattr(model.objects, "select_subclasses"):

View file

@ -67,7 +67,6 @@ class Edition(Book):
type: str = "Edition"
# pylint: disable=invalid-name
@dataclass(init=False)
class Work(Book):
"""work instance of a book object"""

View file

@ -18,7 +18,6 @@ class OrderedCollection(ActivityObject):
type: str = "OrderedCollection"
# pylint: disable=invalid-name
@dataclass(init=False)
class OrderedCollectionPrivate(OrderedCollection):
"""an ordered collection with privacy settings"""

View file

@ -22,7 +22,6 @@ class Verb(ActivityObject):
self.object.to_model(allow_external_connections=allow_external_connections)
# pylint: disable=invalid-name
@dataclass(init=False)
class Create(Verb):
"""Create activity"""
@ -33,7 +32,6 @@ class Create(Verb):
type: str = "Create"
# pylint: disable=invalid-name
@dataclass(init=False)
class Delete(Verb):
"""Create activity"""
@ -63,7 +61,6 @@ class Delete(Verb):
# if we can't find it, we don't need to delete it because we don't have it
# pylint: disable=invalid-name
@dataclass(init=False)
class Update(Verb):
"""Update activity"""
@ -227,7 +224,6 @@ class Like(Verb):
self.to_model(allow_external_connections=allow_external_connections)
# pylint: disable=invalid-name
@dataclass(init=False)
class Announce(Verb):
"""boosting a status"""

View file

@ -32,7 +32,7 @@ class ActivityStream(RedisStore):
stream_id = self.stream_id(user_id)
return f"{stream_id}-unread-by-type"
def get_rank(self, obj): # pylint: disable=no-self-use
def get_rank(self, obj):
"""statuses are sorted by date published"""
return obj.published_date.timestamp()

View file

@ -33,7 +33,6 @@ class BookwyrmConfig(AppConfig):
name = "bookwyrm"
verbose_name = "BookWyrm"
# pylint: disable=no-self-use
def ready(self):
"""set up OTLP and preview image files, if desired"""
if settings.OTEL_EXPORTER_OTLP_ENDPOINT or settings.OTEL_EXPORTER_CONSOLE:

View file

@ -36,7 +36,6 @@ def search(
...
# pylint: disable=arguments-differ
def search(
query: str,
*,

View file

@ -86,7 +86,7 @@ class AbstractMinimalConnector(ABC):
),
"User-Agent": USER_AGENT,
}
params = {"min_confidence": min_confidence}
params = {"min_confidence": str(min_confidence)}
try:
async with session.get(url, headers=headers, params=params) as response:
if not response.ok:

View file

@ -222,9 +222,10 @@ class Connector(AbstractConnector):
def get_description(self, links: JsonDict) -> str:
"""grab an extracted excerpt from wikipedia"""
link = links.get("enwiki")
if not link:
if not link or not link.get("title"):
return ""
url = f"{self.base_url}/api/data?action=wp-extract&lang=en&title={link}"
title = link.get("title")
url = f"{self.base_url}/api/data?action=wp-extract&lang=en&title={title}"
try:
data = get_data(url)
except ConnectorException:

View file

@ -2,7 +2,7 @@
from bookwyrm import models, settings
def site_settings(request): # pylint: disable=unused-argument
def site_settings(request):
"""include the custom info about the site"""
request_protocol = "https://"
if not request.is_secure():

View file

@ -15,9 +15,9 @@ class StyledForm(ModelForm):
css_classes["number"] = "input"
css_classes["checkbox"] = "checkbox"
css_classes["textarea"] = "textarea"
# pylint: disable=super-with-arguments
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
input_type = ""
if hasattr(visible.field.widget, "input_type"):
input_type = visible.field.widget.input_type
if isinstance(visible.field.widget, Textarea):

View file

@ -18,6 +18,7 @@ class EditUserForm(CustomForm):
"email",
"summary",
"show_goal",
"show_ratings",
"show_suggested_users",
"manually_approves_followers",
"default_post_privacy",

View file

@ -34,7 +34,6 @@ class LoginForm(CustomForm):
def add_invalid_password_error(self):
"""We don't want to be too specific about this"""
# pylint: disable=attribute-defined-outside-init
self.non_field_errors = _("Username or password are incorrect")
@ -65,6 +64,10 @@ class InviteRequestForm(CustomForm):
if email and models.User.objects.filter(email=email).exists():
self.add_error("email", _("A user with this email already exists."))
email_domain = email.split("@")[-1]
if email and models.EmailBlocklist.objects.filter(domain=email_domain).exists():
self.add_error("email", _("This email address cannot be registered."))
class Meta:
model = models.InviteRequest
fields = ["email", "answer"]

View file

@ -5,8 +5,6 @@ from django import forms
class ArrayWidget(forms.widgets.TextInput):
"""Inputs for postgres array fields"""
# pylint: disable=unused-argument
# pylint: disable=no-self-use
def value_from_datadict(self, data, files, name):
"""get all values for this name"""
return [i for i in data.getlist(name) if i]

View file

@ -1,7 +1,7 @@
""" import classes """
from .importer import Importer
from .bookwyrm_import import BookwyrmImporter
from .bookwyrm_import import BookwyrmImporter, BookwyrmBooksImporter
from .calibre_import import CalibreImporter
from .goodreads_import import GoodreadsImporter
from .librarything_import import LibrarythingImporter

View file

@ -3,6 +3,7 @@ from django.http import QueryDict
from bookwyrm.models import User
from bookwyrm.models.bookwyrm_import_job import BookwyrmImportJob
from . import Importer
class BookwyrmImporter:
@ -22,3 +23,17 @@ class BookwyrmImporter:
user=user, archive_file=archive_file, required=required
)
return job
class BookwyrmBooksImporter(Importer):
"""
Handle reading a csv from BookWyrm.
Goodreads is the default importer, we basically just use the same structure
But BookWyrm has additional attributes in the csv
"""
service = "BookWyrm"
row_mappings_guesses = Importer.row_mappings_guesses + [
("shelf_name", ["shelf_name"]),
("review_published", ["review_published"]),
]

View file

@ -18,17 +18,26 @@ class Importer:
row_mappings_guesses = [
("id", ["id", "book id"]),
("title", ["title"]),
("authors", ["author", "authors", "primary author"]),
("isbn_10", ["isbn10", "isbn", "isbn/uid"]),
("isbn_13", ["isbn13", "isbn", "isbns", "isbn/uid"]),
("authors", ["author_text", "author", "authors", "primary author"]),
("isbn_10", ["isbn_10", "isbn10", "isbn", "isbn/uid"]),
("isbn_13", ["isbn_13", "isbn13", "isbn", "isbns", "isbn/uid"]),
("shelf", ["shelf", "exclusive shelf", "read status", "bookshelf"]),
("review_name", ["review name"]),
("review_body", ["my review", "review"]),
("review_name", ["review_name", "review name"]),
("review_body", ["review_content", "my review", "review"]),
("rating", ["my rating", "rating", "star rating"]),
("date_added", ["date added", "entry date", "added"]),
("date_started", ["date started", "started"]),
("date_finished", ["date finished", "last date read", "date read", "finished"]),
(
"date_added",
["shelf_date", "date_added", "date added", "entry date", "added"],
),
("date_started", ["start_date", "date started", "started"]),
(
"date_finished",
["finish_date", "date finished", "last date read", "date read", "finished"],
),
]
# TODO: stopped
date_fields = ["date_added", "date_started", "date_finished"]
shelf_mapping_guesses = {
"to-read": ["to-read", "want to read"],
@ -36,9 +45,14 @@ class Importer:
"reading": ["currently-reading", "reading", "currently reading"],
}
# pylint: disable=too-many-locals
# pylint: disable=too-many-arguments
def create_job(
self, user: User, csv_file: Iterable[str], include_reviews: bool, privacy: str
self,
user: User,
csv_file: Iterable[str],
include_reviews: bool,
privacy: str,
create_shelves: bool = True,
) -> ImportJob:
"""check over a csv and creates a database entry for the job"""
csv_reader = csv.DictReader(csv_file, delimiter=self.delimiter)
@ -55,6 +69,7 @@ class Importer:
job = ImportJob.objects.create(
user=user,
include_reviews=include_reviews,
create_shelves=create_shelves,
privacy=privacy,
mappings=mappings,
source=self.service,
@ -114,7 +129,7 @@ class Importer:
shelf = [
s for (s, gs) in self.shelf_mapping_guesses.items() if shelf_name in gs
]
return shelf[0] if shelf else None
return shelf[0] if shelf else normalized_row.get("shelf") or None
# pylint: disable=no-self-use
def normalize_row(
@ -149,6 +164,7 @@ class Importer:
job = ImportJob.objects.create(
user=user,
include_reviews=original_job.include_reviews,
create_shelves=original_job.create_shelves,
privacy=original_job.privacy,
source=original_job.source,
# TODO: allow users to adjust mappings

View file

@ -20,7 +20,7 @@ class LibrarythingImporter(Importer):
def normalize_row(
self, entry: dict[str, str], mappings: dict[str, Optional[str]]
) -> dict[str, Optional[str]]: # pylint: disable=no-self-use
) -> dict[str, Optional[str]]:
"""use the dataclass to create the formatted row of data"""
normalized = {
k: _remove_brackets(entry.get(v) if v else None)

View file

@ -18,7 +18,7 @@ class ListsStream(RedisStore):
return f"{user}-lists"
return f"{user.id}-lists"
def get_rank(self, obj): # pylint: disable=no-self-use
def get_rank(self, obj):
"""lists are sorted by updated date"""
return obj.updated_date.timestamp()

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.23 on 2023-11-25 05:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0188_theme_loads"),
]
operations = [
migrations.AddField(
model_name="importjob",
name="create_shelves",
field=models.BooleanField(default=True),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 4.2.11 on 2024-06-29 06:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0189_importjob_create_shelves"),
("bookwyrm", "0206_merge_20240415_1537"),
]
operations = []

View file

@ -0,0 +1,51 @@
# Generated by Django 4.2.11 on 2024-07-27 18:18
from django.db import migrations, models
import pgtrigger.compiler
import pgtrigger.migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0206_merge_20240415_1537"),
]
operations = [
pgtrigger.migrations.RemoveTrigger(
model_name="author",
name="reset_book_search_vector_on_author_edit",
),
pgtrigger.migrations.RemoveTrigger(
model_name="book",
name="update_search_vector_on_book_edit",
),
pgtrigger.migrations.AddTrigger(
model_name="author",
trigger=pgtrigger.compiler.Trigger(
name="reset_book_search_vector_on_author_edit",
sql=pgtrigger.compiler.UpsertTriggerSql(
func="WITH updated_books AS (SELECT book_id FROM bookwyrm_book_authors WHERE author_id = new.id) UPDATE bookwyrm_book SET search_vector = '' FROM updated_books WHERE id = updated_books.book_id;RETURN NEW;",
hash="4eeb17d1c9c53f543615bcae1234bd0260adefcc",
operation='UPDATE OF "name", "aliases"',
pgid="pgtrigger_reset_book_search_vector_on_author_edit_a50c7",
table="bookwyrm_author",
when="AFTER",
),
),
),
pgtrigger.migrations.AddTrigger(
model_name="book",
trigger=pgtrigger.compiler.Trigger(
name="update_search_vector_on_book_edit",
sql=pgtrigger.compiler.UpsertTriggerSql(
func="WITH author_names AS (SELECT array_to_string(bookwyrm_author.name || bookwyrm_author.aliases, ' ') AS name_and_aliases FROM bookwyrm_author LEFT JOIN bookwyrm_book_authors ON bookwyrm_author.id = bookwyrm_book_authors.author_id WHERE bookwyrm_book_authors.book_id = new.id) SELECT setweight(coalesce(nullif(to_tsvector('english', new.title), ''), to_tsvector('simple', new.title)), 'A') || setweight(to_tsvector('english', coalesce(new.subtitle, '')), 'B') || (SELECT setweight(to_tsvector('simple', coalesce(array_to_string(array_agg(name_and_aliases), ' '), '')), 'C') FROM author_names) || setweight(to_tsvector('english', coalesce(new.series, '')), 'D') INTO new.search_vector;RETURN NEW;",
hash="676d929ce95beff671544b6add09cf9360b6f299",
operation='INSERT OR UPDATE OF "title", "subtitle", "series", "search_vector"',
pgid="pgtrigger_update_search_vector_on_book_edit_bec58",
table="bookwyrm_book",
when="BEFORE",
),
),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 4.2.11 on 2024-07-28 11:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0207_merge_20240629_0626"),
("bookwyrm", "0207_sqlparse_update"),
]
operations = []

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.15 on 2024-08-24 01:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0208_merge_0207_merge_20240629_0626_0207_sqlparse_update"),
]
operations = [
migrations.AddField(
model_name="user",
name="show_ratings",
field=models.BooleanField(default=True),
),
]

View file

@ -31,7 +31,7 @@ logger = logging.getLogger(__name__)
PropertyField = namedtuple("PropertyField", ("set_activity_from_field"))
# pylint: disable=invalid-name
def set_activity_from_property_field(activity, obj, field):
"""assign a model property value to the activity json"""
activity[field[1]] = getattr(obj, field[0])

View file

@ -133,7 +133,6 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
related_models = [
(r.remote_field.name, r.related_model) for r in self._meta.related_objects
]
# pylint: disable=protected-access
for related_field, related_model in related_models:
# Skip the ManyToMany fields that arent auto-created. These
# should have a corresponding OneToMany field in the model for

View file

@ -7,7 +7,6 @@ from boto3.session import Session as BotoSession
from s3_tar import S3Tar
from django.db.models import BooleanField, FileField, JSONField
from django.db.models import Q
from django.core.serializers.json import DjangoJSONEncoder
from django.core.files.base import ContentFile
from django.core.files.storage import storages
@ -28,7 +27,7 @@ logger = logging.getLogger(__name__)
class BookwyrmAwsSession(BotoSession):
"""a boto session that always uses settings.AWS_S3_ENDPOINT_URL"""
def client(self, *args, **kwargs): # pylint: disable=arguments-differ
def client(self, *args, **kwargs):
kwargs["endpoint_url"] = settings.AWS_S3_ENDPOINT_URL
return super().client("s3", *args, **kwargs)
@ -315,19 +314,28 @@ def export_book(user: User, edition: Edition):
def get_books_for_user(user):
"""Get all the books and editions related to a user"""
"""
Get all the books and editions related to a user.
editions = (
Edition.objects.select_related("parent_work")
.filter(
Q(shelves__user=user)
| Q(readthrough__user=user)
| Q(review__user=user)
| Q(list__user=user)
| Q(comment__user=user)
| Q(quotation__user=user)
)
.distinct()
We use union() instead of Q objects because it creates
multiple simple queries in stead of a much more complex DB query
that can time out.
"""
shelf_eds = Edition.objects.select_related("parent_work").filter(shelves__user=user)
rt_eds = Edition.objects.select_related("parent_work").filter(
readthrough__user=user
)
review_eds = Edition.objects.select_related("parent_work").filter(review__user=user)
list_eds = Edition.objects.select_related("parent_work").filter(list__user=user)
comment_eds = Edition.objects.select_related("parent_work").filter(
comment__user=user
)
quote_eds = Edition.objects.select_related("parent_work").filter(
quotation__user=user
)
editions = shelf_eds.union(rt_eds, review_eds, list_eds, comment_eds, quote_eds)
return editions

View file

@ -193,8 +193,7 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
def __init__(self, activitypub_field="preferredUsername", **kwargs):
self.activitypub_field = activitypub_field
# I don't totally know why pylint is mad at this, but it makes it work
super(ActivitypubFieldMixin, self).__init__( # pylint: disable=bad-super-call
super(ActivitypubFieldMixin, self).__init__(
_("username"),
max_length=150,
unique=True,
@ -234,7 +233,6 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, max_length=255, choices=PrivacyLevels, default="public")
# pylint: disable=invalid-name
def set_field_from_activity(
self, instance, data, overwrite=True, allow_external_connections=True
):
@ -276,7 +274,6 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
if hasattr(instance, "mention_users"):
mentions = [u.remote_id for u in instance.mention_users.all()]
# this is a link to the followers list
# pylint: disable=protected-access
followers = instance.user.followers_url
if instance.privacy == "public":
activity["to"] = [self.public]
@ -444,7 +441,7 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
self.alt_field = alt_field
super().__init__(*args, **kwargs)
# pylint: disable=arguments-differ,arguments-renamed,too-many-arguments
# pylint: disable=arguments-renamed,too-many-arguments
def set_field_from_activity(
self, instance, data, save=True, overwrite=True, allow_external_connections=True
):

View file

@ -4,6 +4,7 @@ import math
import re
import dateutil.parser
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@ -59,6 +60,7 @@ class ImportJob(models.Model):
created_date = models.DateTimeField(default=timezone.now)
updated_date = models.DateTimeField(default=timezone.now)
include_reviews: bool = models.BooleanField(default=True)
create_shelves: bool = models.BooleanField(default=True)
mappings = models.JSONField()
source = models.CharField(max_length=100)
privacy = models.CharField(max_length=255, default="public", choices=PrivacyLevels)
@ -245,11 +247,26 @@ class ImportItem(models.Model):
"""the goodreads shelf field"""
return self.normalized_data.get("shelf")
@property
def shelf_name(self):
"""the goodreads shelf field"""
return self.normalized_data.get("shelf_name")
@property
def review(self):
"""a user-written review, to be imported with the book data"""
return self.normalized_data.get("review_body")
@property
def review_name(self):
"""a user-written review name, to be imported with the book data"""
return self.normalized_data.get("review_name")
@property
def review_published(self):
"""date the review was published - included in BookWyrm export csv"""
return self.normalized_data.get("review_published", None)
@property
def rating(self):
"""x/5 star rating for a book"""
@ -352,7 +369,7 @@ def import_item_task(item_id):
try:
item.resolve()
except Exception as err: # pylint: disable=broad-except
except Exception as err:
item.fail_reason = _("Error loading book")
item.save()
item.update_job()
@ -368,7 +385,7 @@ def import_item_task(item_id):
item.update_job()
def handle_imported_book(item):
def handle_imported_book(item): # pylint: disable=too-many-branches
"""process a csv and then post about it"""
job = item.job
if job.complete:
@ -385,13 +402,31 @@ def handle_imported_book(item):
item.book = item.book.edition
existing_shelf = ShelfBook.objects.filter(book=item.book, user=user).exists()
if job.create_shelves and item.shelf and not existing_shelf:
# shelve the book if it hasn't been shelved already
# shelve the book if it hasn't been shelved already
if item.shelf and not existing_shelf:
desired_shelf = Shelf.objects.get(identifier=item.shelf, user=user)
shelved_date = item.date_added or timezone.now()
shelfname = getattr(item, "shelf_name", item.shelf)
try:
shelf = Shelf.objects.get(name=shelfname, user=user)
except ObjectDoesNotExist:
try:
shelf = Shelf.objects.get(identifier=item.shelf, user=user)
except ObjectDoesNotExist:
shelf = Shelf.objects.create(
user=user,
identifier=item.shelf,
name=shelfname,
privacy=job.privacy,
)
ShelfBook(
book=item.book, shelf=desired_shelf, user=user, shelved_date=shelved_date
book=item.book,
shelf=shelf,
user=user,
shelved_date=shelved_date,
).save(priority=IMPORT_TRIGGERED)
for read in item.reads:
@ -408,19 +443,25 @@ def handle_imported_book(item):
read.save()
if job.include_reviews and (item.rating or item.review) and not item.linked_review:
# we don't know the publication date of the review,
# but "now" is a bad guess
published_date_guess = item.date_read or item.date_added
# we don't necessarily know the publication date of the review,
# but "now" is a bad guess unless we have no choice
published_date_guess = (
item.review_published or item.date_read or item.date_added or timezone.now()
)
if item.review:
# pylint: disable=consider-using-f-string
review_title = "Review of {!r} on {!r}".format(
item.book.title,
job.source,
)
review_name = getattr(item, "review_name", review_title)
review = Review.objects.filter(
user=user,
book=item.book,
name=review_title,
name=review_name,
rating=item.rating,
published_date=published_date_guess,
).first()
@ -428,7 +469,7 @@ def handle_imported_book(item):
review = Review(
user=user,
book=item.book,
name=review_title,
name=review_name,
content=item.review,
rating=item.rating,
published_date=published_date_guess,

View file

@ -135,7 +135,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
status = "follow_request"
activity_serializer = activitypub.Follow
def save(self, *args, broadcast=True, **kwargs): # pylint: disable=arguments-differ
def save(self, *args, broadcast=True, **kwargs):
"""make sure the follow or block relationship doesn't already exist"""
# if there's a request for a follow that already exists, accept it
# without changing the local database state

View file

@ -98,7 +98,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
self.thread_id = self.id
super().save(broadcast=False, update_fields=["thread_id"])
def delete(self, *args, **kwargs): # pylint: disable=unused-argument
def delete(self, *args, **kwargs):
""" "delete" a status"""
if hasattr(self, "boosted_status"):
# okay but if it's a boost really delete it
@ -213,7 +213,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
**kwargs,
).serialize()
def to_activity_dataclass(self, pure=False): # pylint: disable=arguments-differ
def to_activity_dataclass(self, pure=False):
"""return tombstone if the status is deleted"""
if self.deleted:
return activitypub.Tombstone(

View file

@ -141,7 +141,6 @@ class User(OrderedCollectionPageMixin, AbstractUser):
hide_follows = fields.BooleanField(default=False)
# migration fields
moved_to = fields.RemoteIdField(
null=True, unique=False, activitypub_field="movedTo", deduplication_field=False
)
@ -158,6 +157,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
show_suggested_users = models.BooleanField(default=True)
discoverable = fields.BooleanField(default=False)
show_guided_tour = models.BooleanField(default=True)
show_ratings = models.BooleanField(default=True)
# feed options
feed_status_types = DjangoArrayField(
@ -409,7 +409,6 @@ class User(OrderedCollectionPageMixin, AbstractUser):
def delete(self, *args, **kwargs):
"""We don't actually delete the database entry"""
# pylint: disable=attribute-defined-outside-init
self.is_active = False
self.allow_reactivation = False
self.is_deleted = True
@ -452,7 +451,6 @@ class User(OrderedCollectionPageMixin, AbstractUser):
def deactivate(self):
"""Disable the user but allow them to reactivate"""
# pylint: disable=attribute-defined-outside-init
self.is_active = False
self.deactivation_reason = "self_deactivation"
self.allow_reactivation = True
@ -460,7 +458,6 @@ class User(OrderedCollectionPageMixin, AbstractUser):
def reactivate(self):
"""Now you want to come back, huh?"""
# pylint: disable=attribute-defined-outside-init
if not self.allow_reactivation:
return
self.is_active = True

View file

@ -420,7 +420,6 @@ def save_and_cleanup(image, instance=None):
return True
# pylint: disable=invalid-name
@app.task(queue=IMAGES)
def generate_site_preview_image_task():
"""generate preview_image for the website"""
@ -445,7 +444,6 @@ def generate_site_preview_image_task():
save_and_cleanup(image, instance=site)
# pylint: disable=invalid-name
@app.task(queue=IMAGES)
def generate_edition_preview_image_task(book_id):
"""generate preview_image for a book"""

View file

@ -404,6 +404,13 @@ if USE_S3:
"default_acl": "public-read",
},
},
"sass_processor": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"location": "static",
"default_acl": "public-read",
},
},
"exports": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {

View file

@ -6,7 +6,7 @@ from base64 import b64encode, b64decode
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15 # pylint: disable=no-name-in-module
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
MAX_SIGNATURE_AGE = 300
@ -84,7 +84,6 @@ class Signature:
self.headers = headers
self.signature = signature
# pylint: disable=invalid-name
@classmethod
def parse(cls, request):
"""extract and parse a signature from an http request"""

View file

@ -14,6 +14,10 @@ let BookWyrm = new (class {
.querySelectorAll("[data-controls]")
.forEach((button) => button.addEventListener("click", this.toggleAction.bind(this)));
document
.querySelectorAll("[data-disappear]")
.forEach((button) => button.addEventListener("click", this.hideSelf.bind(this)));
document
.querySelectorAll(".interaction")
.forEach((button) => button.addEventListener("submit", this.interact.bind(this)));
@ -181,6 +185,18 @@ let BookWyrm = new (class {
this.addRemoveClass(visible, "is-hidden", true);
}
/**
* Hide the element you just clicked
*
* @param {Event} event
* @return {undefined}
*/
hideSelf(event) {
let trigger = event.currentTarget;
this.addRemoveClass(trigger, "is-hidden", true);
}
/**
* Execute actions on targets based on triggers.
*

View file

@ -34,7 +34,6 @@ class SuggestedUsers(RedisStore):
def get_counts_from_rank(self, rank): # pylint: disable=no-self-use
"""calculate mutuals count and shared books count from rank"""
# pylint: disable=c-extension-no-member
return {
"mutuals": math.floor(rank),
# "shared_books": int(1 / (-1 * (rank % 1 - 1))) - 1,
@ -128,7 +127,6 @@ def get_annotated_users(viewer, *args, **kwargs):
),
distinct=True,
),
# pylint: disable=line-too-long
# shared_books=Count(
# "shelfbook",
# filter=Q(
@ -202,7 +200,7 @@ def update_suggestions_on_unfollow(sender, instance, **kwargs):
@receiver(signals.post_save, sender=models.User)
# pylint: disable=unused-argument, too-many-arguments
# pylint: disable=unused-argument
def update_user(sender, instance, created, update_fields=None, **kwargs):
"""an updated user, neat"""
# a new user is found, create suggestions for them

View file

@ -28,7 +28,7 @@
<meta itemprop="name" content="{{ author.name }}">
{% firstof author.aliases author.born author.died as details %}
{% firstof author.wikipedia_link author.website author.openlibrary_key author.inventaire_id author.isni author.isfdb as links %}
{% firstof author.wikipedia_link author.website author.openlibrary_key author.inventaire_id author.isni author.isfdb author.wikidata as links %}
{% if details or links %}
<div class="column is-3">
{% if details %}
@ -73,6 +73,14 @@
</div>
{% endif %}
{% if author.wikidata %}
<div>
<a itemprop="sameAs" href="https://www.wikidata.org/wiki/{{ author.wikidata }}" rel="nofollow noopener noreferrer" target="_blank">
{% trans "View on Wikidata" %}
</a>
</div>
{% endif %}
{% if author.website %}
<div>
<a itemprop="sameAs" href="{{ author.website }}" rel="nofollow noopener noreferrer" target="_blank">

View file

@ -21,9 +21,10 @@ Is that where you'd like to go?
<div class="is-flex-grow-1">
<a href="{% url 'report-link' link.id %}">{% trans "Report spam" %}</a>
</div>
{% endif %}
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
<a href="{{ link.url }}" target="_blank" rel="nofollow noopener noreferrer" noreferrer" class="button is-primary">{% trans "Continue" %}</a>
{% endif %}
{% endblock %}

View file

@ -2,10 +2,31 @@
{% load i18n %}
{% load utilities %}
{% block title %}{% trans "Edit status" %}{% endblock %}
{% block title %}
{% if draft.status_type == "Review" %}
{% trans "Edit review" %}
{% elif draft.status_type == "Quotation" %}
{% trans "Edit quote" %}
{% elif draft.status_type == "Comment" %}
{% trans "Edit comment" %}
{% else %}
{% trans "Edit status" %}
{% endif %}
{% endblock %}
{% block content %}
<header class="block content">
<h1>{% trans "Edit status" %}</h1>
<h1>
{% if draft.status_type == "Review" %}
{% trans "Edit review" %}
{% elif draft.status_type == "Quotation" %}
{% trans "Edit quote" %}
{% elif draft.status_type == "Comment" %}
{% trans "Edit comment" %}
{% else %}
{% trans "Edit status" %}
{% endif %}
</h1>
</header>
{% with 0|uuid as uuid %}

View file

@ -69,6 +69,9 @@
<option value="Calibre" {% if current == 'Calibre' %}selected{% endif %}>
{% trans "Calibre (CSV)" %}
</option>
<option value="BookWyrm" {% if current == 'BookWyrm' %}selected{% endif %}>
{% trans "BookWyrm (CSV)" %}
</option>
</select>
</div>
@ -93,9 +96,14 @@
<input type="checkbox" name="include_reviews" checked> {% trans "Include reviews" %}
</label>
</div>
<div class="field">
<label class="label">
<input type="checkbox" name="create_shelves" checked> {% trans "Create new shelves if they do not exist" %}
</label>
</div>
<div class="field">
<label class="label" for="privacy_import">
{% trans "Privacy setting for imported reviews:" %}
{% trans "Privacy setting for imported reviews and shelves:" %}
</label>
{% include 'snippets/privacy_select.html' with no_label=True privacy_uuid="import" %}
</div>

View file

@ -15,6 +15,6 @@
{% blocktrans trimmed count counter=notification.related_link_domains.count with display_count=notification.related_link_domains.count|intcomma %}
A new <a href="{{ path }}">link domain</a> needs review
{% plural %}
{{ display_count }} new <a href="{{ path }}">link domains</a> need moderation
{{ display_count }} new <a href="{{ path }}">link domains</a> need review
{% endblocktrans %}
{% endblock %}

View file

@ -3,8 +3,10 @@
{% load utilities %}
{% block heading %}
{% block title %}
{% blocktrans with username=user.localname sitename=site.name %}Follow {{ username }} on the fediverse{% endblocktrans %}
{% endblock %}
{% endblock %}
{% block content %}
<div class="block card">

View file

@ -2,6 +2,10 @@
{% load i18n %}
{% load utilities %}
{% block title %}
{% blocktrans with display_name=user.display_name %}You are now following {{ display_name }}!{% endblocktrans %}
{% endblock %}
{% block content %}
<div class="block card">
<div class="card-content">

View file

@ -69,6 +69,12 @@
{% trans "Show reading goal prompt in feed" %}
</label>
</div>
<div class="field">
<label class="checkbox label" for="id_show_ratings">
{{ form.show_ratings }}
{% trans "Show ratings" %}
</label>
</div>
<div class="field">
<label class="checkbox label" for="id_show_suggested_users">
{{ form.show_suggested_users }}

View file

@ -1,8 +1,19 @@
{% spaceless %}
{% load utilities %}
{% load i18n %}
<span class="stars">
{% with 0|uuid as uuid %}
<span class="stars tag">
{% if not request.user.show_ratings %}
<button type="button" data-controls="rating-{{ uuid }}" id="rating-button-{{ uuid }}" aria-pressed="false" data-disappear>
<em>{% trans "Show rating" %} </em>
</button>
{% endif %}
{% if rating %}
<span class="{% if not request.user.show_ratings %}is-hidden{% endif %}" id="rating-{{ uuid }}">
<span class="is-sr-only">
{% blocktranslate trimmed with rating=rating|floatformat:0 count counter=rating|floatformat:0|add:0 %}
{{ rating }} star
@ -19,8 +30,11 @@
aria-hidden="true"
></span>
{% endfor %}
</span>
{% else %}
<span class="no-rating">{% trans "No rating" %}</span>
{% endif %}
</span>
{% endwith %}
{% endspaceless %}

View file

@ -71,14 +71,8 @@ def get_landing_books():
"""list of books for the landing page"""
return list(
set(
models.Edition.objects.filter(
review__published_date__isnull=False,
review__deleted=False,
review__user__local=True,
review__privacy__in=["public", "unlisted"],
)
.exclude(cover__exact="")
models.Edition.objects.exclude(cover__exact="")
.distinct()
.order_by("-review__published_date")[:6]
.order_by("-updated_date")[:6]
)
)

View file

@ -1,6 +1,7 @@
""" template filters """
from dateutil.relativedelta import relativedelta
from django import template
from django.conf import settings
from django.contrib.humanize.templatetags.humanize import naturaltime, naturalday
from django.template.loader import select_template
from django.utils import timezone
@ -60,8 +61,8 @@ def get_published_date(date):
delta = relativedelta(now, date)
if delta.years:
return naturalday(date)
if delta.days:
return naturalday(date, "M j")
if delta.days or delta.months:
return naturalday(date, settings.MONTH_DAY_FORMAT)
return naturaltime(date)

View file

@ -46,6 +46,18 @@ class BaseActivity(TestCase):
# don't try to load the user icon
del self.userdata["icon"]
remote_datafile = pathlib.Path(__file__).parent.joinpath(
"../data/ap_user_external.json"
)
self.remote_userdata = json.loads(remote_datafile.read_bytes())
del self.remote_userdata["icon"]
alias_datafile = pathlib.Path(__file__).parent.joinpath(
"../data/ap_user_aliased.json"
)
self.alias_userdata = json.loads(alias_datafile.read_bytes())
del self.alias_userdata["icon"]
image_path = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg"
)
@ -118,6 +130,48 @@ class BaseActivity(TestCase):
self.assertEqual(result.remote_id, "https://example.com/user/mouse")
self.assertEqual(result.name, "MOUSE?? MOUSE!!")
@responses.activate
def test_resolve_remote_alias(self, *_):
"""look up or load user who has an unknown alias"""
self.assertEqual(models.User.objects.count(), 1)
# remote user with unknown user as an alias
responses.add(
responses.GET,
"https://example.com/user/moose",
json=self.alias_userdata,
status=200,
)
responses.add(
responses.GET,
"https://example.com/user/ali",
json=self.remote_userdata,
status=200,
)
with patch("bookwyrm.models.user.set_remote_server.delay"):
result = resolve_remote_id(
"https://example.com/user/moose", model=models.User
)
self.assertTrue(
models.User.objects.filter(
remote_id="https://example.com/user/moose"
).exists()
) # moose has been added to DB
self.assertTrue(
models.User.objects.filter(
remote_id="https://example.com/user/ali"
).exists()
) # Ali has been added to DB
self.assertIsInstance(result, models.User)
self.assertEqual(result.name, "moose?? moose!!")
alias = models.User.objects.last()
self.assertEqual(alias.name, "Ali As")
self.assertEqual(result.also_known_as.first(), alias) # Ali is alias of Moose
def test_to_model_invalid_model(self, *_):
"""catch mismatch between activity type and model type"""
instance = ActivityObject(id="a", type="b")

View file

@ -115,7 +115,6 @@ class AbstractConnector(TestCase):
@responses.activate
def test_get_or_create_author(self):
"""load an author"""
# pylint: disable=attribute-defined-outside-init
self.connector.author_mappings = [
Mapping("id"),
Mapping("name"),
@ -141,7 +140,6 @@ class AbstractConnector(TestCase):
def test_update_author_from_remote(self):
"""trigger the function that looks up the remote data"""
author = models.Author.objects.create(name="Test", openlibrary_key="OL123A")
# pylint: disable=attribute-defined-outside-init
self.connector.author_mappings = [
Mapping("id"),
Mapping("name"),

View file

@ -273,7 +273,9 @@ class Inventaire(TestCase):
json={"extract": "hi hi"},
)
extract = self.connector.get_description({"enwiki": "test_path"})
extract = self.connector.get_description(
{"enwiki": {"title": "test_path", "badges": "hello"}}
)
self.assertEqual(extract, "hi hi")
def test_remote_id_from_model(self):

View file

@ -0,0 +1,40 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "https://example.com/user/moose",
"type": "Person",
"preferredUsername": "moose",
"name": "moose?? moose!!",
"inbox": "https://example.com/user/moose/inbox",
"outbox": "https://example.com/user/moose/outbox",
"followers": "https://example.com/user/moose/followers",
"following": "https://example.com/user/moose/following",
"summary": "",
"publicKey": {
"id": "https://example.com/user/moose/#main-key",
"owner": "https://example.com/user/moose",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6QisDrjOQvkRo/MqNmSYPwqtt\nCxg/8rCW+9jKbFUKvqjTeKVotEE85122v/DCvobCCdfQuYIFdVMk+dB1xJ0iPGPg\nyU79QHY22NdV9mFKA2qtXVVxb5cxpA4PlwOHM6PM/k8B+H09OUrop2aPUAYwy+vg\n+MXyz8bAXrIS1kq6fQIDAQAB\n-----END PUBLIC KEY-----"
},
"endpoints": {
"sharedInbox": "https://example.com/inbox"
},
"bookwyrmUser": true,
"manuallyApprovesFollowers": false,
"discoverable": false,
"alsoKnownAs": ["https://example.com/user/ali"],
"devices": "",
"tag": [],
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://example.com/images/avatars/AL-3-crop-50.png"
}
}

View file

@ -0,0 +1,40 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "https://example.com/user/ali",
"type": "Person",
"preferredUsername": "alias",
"name": "Ali As",
"inbox": "https://example.com/user/ali/inbox",
"outbox": "https://example.com/user/ali/outbox",
"followers": "https://example.com/user/ali/followers",
"following": "https://example.com/user/ali/following",
"summary": "",
"publicKey": {
"id": "https://example.com/user/ali/#main-key",
"owner": "https://example.com/user/ali",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6QisDrjOQvkRo/MqNmSYPwqtt\nCxg/8rCW+9jKbFUKvqjTeKVotEE85122v/DCvobCCdfQuYIFdVMk+dB1xJ0iPGPg\nyU79QHY22NdV9mFKA2qtXVVxb5cxpA4PlwOHM6PM/k8B+H09OUrop2aPUAYwy+vg\n+MXyz8bAXrIS1kq6fQIDAQAB\n-----END PUBLIC KEY-----"
},
"endpoints": {
"sharedInbox": "https://example.com/inbox"
},
"bookwyrmUser": true,
"manuallyApprovesFollowers": false,
"alsoKnownAs": [],
"discoverable": false,
"devices": "",
"tag": [],
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://example.com/images/avatars/ALIAS-2-crop-50.png"
}
}

View file

@ -0,0 +1,4 @@
title,author_text,remote_id,openlibrary_key,inventaire_id,librarything_key,goodreads_key,bnf_id,viaf,wikidata,asin,aasin,isfdb,isbn_10,isbn_13,oclc_number,start_date,finish_date,stopped_date,rating,review_name,review_cw,review_content,review_published,shelf,shelf_name,shelf_date
我穿我自己,琅俨,https://example.com/book/2010,,,,,,,,,,,,,,,,,,,,,,to-read,To Read,2024-08-10
Ottolenghi Simple,Yotam Ottolenghi,https://example.com/book/2,OL43065148M,,,,,,,,,,0449017036,9780449017036,,2022-08-10,2022-10-10,,4,Too much tahini,,...in his hummus,2022-11-10,cooking-9,Cooking,2024-08-10
The Blue Bedspread,Raj Kamal Jha,https://example.com/book/270,OL7425890M,,,,,,,,,,0375503129,9780375503122,41754476,2001-06-01,2001-07-10,,5,,,,,read,Read,2024-08-10
1 title author_text remote_id openlibrary_key inventaire_id librarything_key goodreads_key bnf_id viaf wikidata asin aasin isfdb isbn_10 isbn_13 oclc_number start_date finish_date stopped_date rating review_name review_cw review_content review_published shelf shelf_name shelf_date
2 我穿我自己 琅俨 https://example.com/book/2010 to-read To Read 2024-08-10
3 Ottolenghi Simple Yotam Ottolenghi https://example.com/book/2 OL43065148M 0449017036 9780449017036 2022-08-10 2022-10-10 4 Too much tahini ...in his hummus 2022-11-10 cooking-9 Cooking 2024-08-10
4 The Blue Bedspread Raj Kamal Jha https://example.com/book/270 OL7425890M 0375503129 9780375503122 41754476 2001-06-01 2001-07-10 5 read Read 2024-08-10

View file

@ -0,0 +1,182 @@
""" testing bookwyrm csv import """
import pathlib
from unittest.mock import patch
import datetime
from django.test import TestCase
from bookwyrm import models
from bookwyrm.importers import BookwyrmBooksImporter
from bookwyrm.models.import_job import handle_imported_book
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
class BookwyrmBooksImport(TestCase):
"""importing from BookWyrm csv"""
def setUp(self):
"""use a test csv"""
self.importer = BookwyrmBooksImporter()
datafile = pathlib.Path(__file__).parent.joinpath("../data/bookwyrm.csv")
# pylint: disable-next=consider-using-with
self.csv = open(datafile, "r", encoding=self.importer.encoding)
def tearDown(self):
"""close test csv"""
self.csv.close()
@classmethod
def setUpTestData(cls):
"""populate database"""
with (
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
patch("bookwyrm.activitystreams.populate_stream_task.delay"),
patch("bookwyrm.lists_stream.populate_lists_task.delay"),
):
cls.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True
)
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work")
cls.book = models.Edition.objects.create(
title="Example Edition",
remote_id="https://example.com/book/1",
parent_work=work,
)
def test_create_job(self, *_):
"""creates the import job entry and checks csv"""
import_job = self.importer.create_job(
self.local_user, self.csv, False, "public"
)
import_items = models.ImportItem.objects.filter(job=import_job).all()
self.assertEqual(len(import_items), 3)
self.assertEqual(import_items[0].index, 0)
self.assertEqual(import_items[0].normalized_data["isbn_13"], "")
self.assertEqual(import_items[0].normalized_data["isbn_10"], "")
self.assertEqual(import_items[0].shelf_name, "To Read")
self.assertEqual(import_items[1].index, 1)
self.assertEqual(import_items[1].normalized_data["isbn_13"], "9780449017036")
self.assertEqual(import_items[1].normalized_data["isbn_10"], "0449017036")
self.assertEqual(import_items[1].shelf_name, "Cooking")
self.assertEqual(import_items[2].index, 2)
self.assertEqual(import_items[2].normalized_data["isbn_13"], "9780375503122")
self.assertEqual(import_items[2].normalized_data["isbn_10"], "0375503129")
self.assertEqual(import_items[2].shelf_name, "Read")
def test_create_retry_job(self, *_):
"""trying again with items that didn't import"""
import_job = self.importer.create_job(
self.local_user, self.csv, False, "unlisted"
)
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
retry = self.importer.create_retry_job(
self.local_user, import_job, import_items
)
self.assertNotEqual(import_job, retry)
self.assertEqual(retry.user, self.local_user)
self.assertEqual(retry.include_reviews, False)
self.assertEqual(retry.privacy, "unlisted")
retry_items = models.ImportItem.objects.filter(job=retry).all()
self.assertEqual(len(retry_items), 2)
self.assertEqual(retry_items[0].index, 0)
self.assertEqual(retry_items[0].data["title"], "我穿我自己")
self.assertEqual(retry_items[1].index, 1)
self.assertEqual(retry_items[1].data["author_text"], "Yotam Ottolenghi")
def test_handle_imported_book(self, *_):
"""import added a book, this adds related connections"""
shelf = self.local_user.shelf_set.filter(
identifier=models.Shelf.READ_FINISHED
).first()
self.assertIsNone(shelf.books.first())
import_job = self.importer.create_job(
self.local_user, self.csv, False, "public"
)
import_item = import_job.items.last()
import_item.book = self.book
import_item.save()
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
handle_imported_book(import_item)
shelf.refresh_from_db()
self.assertEqual(shelf.books.first(), self.book)
self.assertEqual(
shelf.shelfbook_set.first().shelved_date, make_date(2024, 8, 10)
)
readthrough = models.ReadThrough.objects.get(user=self.local_user)
self.assertEqual(readthrough.book, self.book)
self.assertEqual(readthrough.start_date, make_date(2001, 6, 1))
self.assertEqual(readthrough.finish_date, make_date(2001, 7, 10))
def test_create_new_shelf(self, *_):
"""import added a book, was a new shelf created?"""
shelf = self.local_user.shelf_set.filter(identifier="cooking").first()
self.assertIsNone(shelf)
import_job = self.importer.create_job(
self.local_user, self.csv, False, "public"
)
import_item = models.ImportItem.objects.filter(job=import_job).all()[1]
import_item.book = self.book
import_item.save()
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
handle_imported_book(import_item)
shelf_after = self.local_user.shelf_set.filter(identifier="cooking-9").first()
self.assertEqual(shelf_after.books.first(), self.book)
@patch("bookwyrm.activitystreams.add_status_task.delay")
def test_handle_imported_book_review(self, *_):
"""review import"""
import_job = self.importer.create_job(
self.local_user, self.csv, True, "unlisted"
)
import_item = import_job.items.get(index=1)
import_item.book = self.book
import_item.save()
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
handle_imported_book(import_item)
review = models.Review.objects.get(book=self.book, user=self.local_user)
self.assertEqual(review.name, "Too much tahini")
self.assertEqual(review.content, "...in his hummus")
self.assertEqual(review.rating, 4)
self.assertEqual(review.published_date, make_date(2022, 11, 10))
self.assertEqual(review.privacy, "unlisted")
@patch("bookwyrm.activitystreams.add_status_task.delay")
def test_handle_imported_book_rating(self, *_):
"""rating import"""
import_job = self.importer.create_job(
self.local_user, self.csv, True, "followers"
)
import_item = import_job.items.filter(index=2).first()
import_item.book = self.book
import_item.save()
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"):
handle_imported_book(import_item)
review = models.ReviewRating.objects.get(book=self.book, user=self.local_user)
self.assertIsInstance(review, models.ReviewRating)
self.assertEqual(review.rating, 5)
self.assertEqual(review.published_date, make_date(2001, 7, 10))
self.assertEqual(review.privacy, "followers")

View file

@ -63,7 +63,9 @@ class GenericImporter(TestCase):
self.assertEqual(import_job.include_reviews, False)
self.assertEqual(import_job.privacy, "public")
import_items = models.ImportItem.objects.filter(job=import_job).all()
import_items = (
models.ImportItem.objects.filter(job=import_job).all().order_by("id")
)
self.assertEqual(len(import_items), 4)
self.assertEqual(import_items[0].index, 0)
self.assertEqual(import_items[0].normalized_data["id"], "38")

View file

@ -20,7 +20,7 @@ from bookwyrm.models.activitypub_mixin import (
from bookwyrm.settings import PAGE_LENGTH
# pylint: disable=invalid-name,too-many-public-methods
# pylint: disable=too-many-public-methods
@patch("bookwyrm.activitystreams.add_status_task.delay")
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
class ActivitypubMixins(TestCase):

View file

@ -42,7 +42,7 @@ class BaseModel(TestCase):
def test_remote_id(self):
"""these should be generated"""
self.test_model.id = 1 # pylint: disable=invalid-name
self.test_model.id = 1
expected = self.test_model.get_remote_id()
self.assertEqual(expected, f"{BASE_URL}/bookwyrmtestmodel/1")

View file

@ -13,7 +13,7 @@ from bookwyrm.utils.tar import BookwyrmTarFile
from bookwyrm.models import bookwyrm_import_job
class BookwyrmImport(TestCase): # pylint: disable=too-many-public-methods
class BookwyrmImport(TestCase):
"""testing user import functions"""
def setUp(self):

View file

@ -0,0 +1,107 @@
""" testing models """
from unittest.mock import patch
from django.test import TestCase
from bookwyrm import models
from bookwyrm.models.job import ChildJob, ParentJob
class TestParentJob(TestCase):
"""job manager"""
@classmethod
def setUpTestData(cls):
"""we're trying to transport user data"""
with (
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
patch("bookwyrm.activitystreams.populate_stream_task.delay"),
patch("bookwyrm.lists_stream.populate_lists_task.delay"),
):
cls.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True
)
def test_complete_job(self):
"""mark a job as complete"""
job = ParentJob.objects.create(user=self.local_user)
self.assertFalse(job.complete)
self.assertEqual(job.status, "pending")
job.complete_job()
job.refresh_from_db()
self.assertTrue(job.complete)
self.assertEqual(job.status, "complete")
def test_complete_job_with_children(self):
"""mark a job with children as complete"""
job = ParentJob.objects.create(user=self.local_user)
child = ChildJob.objects.create(parent_job=job)
self.assertFalse(child.complete)
self.assertEqual(child.status, "pending")
job.complete_job()
child.refresh_from_db()
self.assertEqual(child.status, "stopped")
def test_pending_child_jobs(self):
"""queryset of child jobs for a parent"""
job = ParentJob.objects.create(user=self.local_user)
child = ChildJob.objects.create(parent_job=job)
ChildJob.objects.create(parent_job=job, complete=True)
self.assertEqual(job.pending_child_jobs.count(), 1)
self.assertEqual(job.pending_child_jobs.first(), child)
class TestChildJob(TestCase):
"""job manager"""
@classmethod
def setUpTestData(cls):
"""we're trying to transport user data"""
with (
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
patch("bookwyrm.activitystreams.populate_stream_task.delay"),
patch("bookwyrm.lists_stream.populate_lists_task.delay"),
):
cls.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True
)
def test_complete_job(self):
"""a child job completed, so its parent is complete"""
job = ParentJob.objects.create(user=self.local_user)
child = ChildJob.objects.create(parent_job=job)
self.assertFalse(job.complete)
child.complete_job()
job.refresh_from_db()
self.assertTrue(job.complete)
self.assertEqual(job.status, "complete")
def test_complete_job_with_siblings(self):
"""a child job completed, but its parent is not complete"""
job = ParentJob.objects.create(user=self.local_user)
child = ChildJob.objects.create(parent_job=job)
ChildJob.objects.create(parent_job=job)
self.assertFalse(job.complete)
child.complete_job()
job.refresh_from_db()
self.assertFalse(job.complete)
def test_set_status(self):
"""a parent job is activated when a child task is activated"""
job = ParentJob.objects.create(user=self.local_user)
child = ChildJob.objects.create(parent_job=job)
self.assertEqual(job.status, "pending")
child.set_status("active")
job.refresh_from_db()
self.assertEqual(job.status, "active")

View file

@ -6,7 +6,6 @@ from django.test import TestCase
from bookwyrm import models, settings
# pylint: disable=unused-argument
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
@patch("bookwyrm.lists_stream.populate_lists_task.delay")

View file

@ -109,4 +109,17 @@ class StatusDisplayTags(TestCase):
2022, 1, 8, 0, 0, tzinfo=datetime.timezone.utc
)
result = status_display.get_published_date(date)
self.assertEqual(result, "Jan 1")
self.assertEqual(result, "January 1")
with patch("django.utils.timezone.now") as timezone_mock:
timezone_mock.return_value = datetime.datetime(
# bookwyrm-social#3365: bug with exact month deltas
2022,
3,
1,
0,
0,
tzinfo=datetime.timezone.utc,
)
result = status_display.get_published_date(date)
self.assertEqual(result, "January 1")

View file

@ -89,3 +89,17 @@ class UtilitiesTags(TestCase):
result = utilities.get_isni_bio(data, self.author)
self.assertEqual(result, "Author of <em>One\\Dtwo</em>")
def test_id_to_username(self, *_):
"""given an arbitrary remote id, return the username"""
self.assertEqual(
utilities.id_to_username("http://example.com/rat"), "rat@example.com"
)
self.assertEqual(utilities.id_to_username(None), "a new user account")
def test_get_file_size(self, *_):
"""display the size of a file in human readable terms"""
self.assertEqual(utilities.get_file_size(5), "5.0 bytes")
self.assertEqual(utilities.get_file_size(5120), "5.00 KB")
self.assertEqual(utilities.get_file_size(5242880), "5.00 MB")
self.assertEqual(utilities.get_file_size(5368709000), "5.00 GB")

View file

@ -10,7 +10,7 @@ class MergeBookDataModel(TestCase):
"""test merging of subclasses of BookDataModel"""
@classmethod
def setUpTestData(cls): # pylint: disable=invalid-name
def setUpTestData(cls):
"""shared data"""
models.SiteSettings.objects.create()

View file

@ -42,7 +42,7 @@ def validate_html(html):
validator.feed(str(html.content))
class HtmlValidator(HTMLParser): # pylint: disable=abstract-method
class HtmlValidator(HTMLParser):
"""Checks for custom html validation requirements"""
def __init__(self):

View file

@ -14,7 +14,6 @@ from bookwyrm.tests.validate_html import validate_html
class ImportUserViews(TestCase):
"""user import views"""
# pylint: disable=invalid-name
def setUp(self):
"""we need basic test data and mocks"""
self.factory = RequestFactory()

View file

@ -11,7 +11,6 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class Inbox(TestCase):
"""readthrough tests"""

View file

@ -7,7 +7,6 @@ import responses
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxAdd(TestCase):
"""inbox tests"""

View file

@ -7,7 +7,6 @@ import responses
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxActivities(TestCase):
"""inbox tests"""

View file

@ -6,7 +6,6 @@ from django.test import TestCase
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxBlock(TestCase):
"""inbox tests"""

View file

@ -9,7 +9,6 @@ from bookwyrm import models, views
from bookwyrm.activitypub import ActivitySerializerError
# pylint: disable=too-many-public-methods
class TransactionInboxCreate(TransactionTestCase):
"""readthrough tests"""

View file

@ -7,7 +7,6 @@ from django.test import TestCase
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxActivities(TestCase):
"""inbox tests"""

View file

@ -7,7 +7,6 @@ from django.test import TestCase
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxRelationships(TestCase):
"""inbox tests"""

View file

@ -6,7 +6,6 @@ from django.test import TestCase
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxActivities(TestCase):
"""inbox tests"""

View file

@ -6,7 +6,6 @@ from django.test import TestCase
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxRemove(TestCase):
"""inbox tests"""

View file

@ -8,7 +8,6 @@ from django.test import TestCase
from bookwyrm import models, views
# pylint: disable=too-many-public-methods
class InboxUpdate(TestCase):
"""inbox tests"""
@ -161,7 +160,6 @@ class InboxUpdate(TestCase):
datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_edition.json")
bookdata = json.loads(datafile.read_bytes())
del bookdata["authors"]
# pylint: disable=line-too-long
link_data = {
"href": "https://openlibrary.org/books/OL11645413M/Queen_Victoria/daisy",
"mediaType": "Daisy",

View file

@ -11,7 +11,6 @@ from bookwyrm import forms, models, views
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=too-many-public-methods
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
class LoginViews(TestCase):

View file

@ -11,7 +11,6 @@ from bookwyrm import models, views
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=unused-argument
class ListViews(TestCase):
"""list view"""

View file

@ -11,7 +11,6 @@ from bookwyrm import models, views
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=unused-argument
class ListViews(TestCase):
"""list view"""

View file

@ -13,7 +13,6 @@ from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=unused-argument
# pylint: disable=too-many-public-methods
class ListViews(TestCase):
"""list view"""

View file

@ -7,8 +7,6 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
# pylint: disable=unused-argument
# pylint: disable=too-many-public-methods
class ListItemViews(TestCase):
"""list view"""

View file

@ -11,7 +11,7 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=unused-argument
class ListViews(TestCase):
"""lists of lists"""

View file

@ -18,7 +18,7 @@ class ExportViews(TestCase):
"""viewing and creating statuses"""
@classmethod
def setUpTestData(cls): # pylint: disable=invalid-name
def setUpTestData(cls):
"""we need basic test data and mocks"""
with (
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
@ -41,7 +41,6 @@ class ExportViews(TestCase):
bnf_id="beep",
)
# pylint: disable=invalid-name
def setUp(self):
"""individual test setup"""
self.factory = RequestFactory()

View file

@ -11,7 +11,7 @@ from django.test.client import RequestFactory
from bookwyrm import forms, models, views
# pylint: disable=too-many-public-methods
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
class TwoFactorViews(TestCase):

View file

@ -9,7 +9,7 @@ from django.test.client import RequestFactory
from bookwyrm import models, views
from bookwyrm.tests.validate_html import validate_html
# pylint: disable=unused-argument
class DirectoryViews(TestCase):
"""tag views"""

View file

@ -10,7 +10,6 @@ from bookwyrm import models, views
from bookwyrm.settings import USER_AGENT
# pylint: disable=too-many-public-methods
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
class OutboxView(TestCase):
"""sends out activities"""

View file

@ -103,6 +103,20 @@ class Views(TestCase):
connector_results = response.context_data["remote_results"]
self.assertEqual(connector_results[0]["results"][0].title, "Mock Book")
def test_search_books_extra_whitespace(self):
"""just the search page"""
view = views.Search.as_view()
request = self.factory.get("", {"q": " Test Book ", "remote": False})
request.user = self.local_user
with patch("bookwyrm.views.search.is_api_request") as is_api:
is_api.return_value = False
response = view(request)
self.assertIsInstance(response, TemplateResponse)
validate_html(response.render())
local_results = response.context_data["results"]
self.assertEqual(local_results[0].title, "Test Book")
def test_search_book_anonymous(self):
"""Don't search remote for logged out user"""
view = views.Search.as_view()
@ -150,6 +164,17 @@ class Views(TestCase):
validate_html(response.render())
self.assertEqual(response.context_data["results"][0], self.local_user)
def test_search_users_extra_whitespace(self):
"""searches remote connectors"""
view = views.Search.as_view()
request = self.factory.get("", {"q": " mouse ", "type": "user"})
request.user = self.local_user
response = view(request)
self.assertIsInstance(response, TemplateResponse)
validate_html(response.render())
self.assertEqual(response.context_data["results"][0], self.local_user)
def test_search_users_logged_out(self):
"""searches remote connectors"""
view = views.Search.as_view()
@ -181,3 +206,21 @@ class Views(TestCase):
self.assertIsInstance(response, TemplateResponse)
validate_html(response.render())
self.assertEqual(response.context_data["results"][0], booklist)
def test_search_lists_extra_whitespace(self):
"""searches remote connectors"""
with (
patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"),
patch("bookwyrm.lists_stream.remove_list_task.delay"),
):
booklist = models.List.objects.create(
user=self.local_user, name="test list"
)
view = views.Search.as_view()
request = self.factory.get("", {"q": " test ", "type": "list"})
request.user = self.local_user
response = view(request)
self.assertIsInstance(response, TemplateResponse)
validate_html(response.render())
self.assertEqual(response.context_data["results"][0], booklist)

View file

@ -1,9 +1,11 @@
""" test for app action functionality """
import json
from unittest.mock import patch
import dateutil
from django.core.exceptions import PermissionDenied
from django.test import TestCase, TransactionTestCase
from django.test.client import RequestFactory
from django.utils import timezone
from bookwyrm import forms, models, views
from bookwyrm.views.status import find_mentions, find_or_create_hashtags
@ -167,6 +169,37 @@ class StatusViews(TestCase):
self.assertEqual(status.rating, 4.0)
self.assertIsNone(status.edited_date)
def test_create_status_progress(self, *_):
"""create a status that updates a readthrough"""
start_date = timezone.make_aware(dateutil.parser.parse("2024-07-27"))
readthrough = models.ReadThrough.objects.create(
book=self.book, user=self.local_user, start_date=start_date
)
self.assertEqual(start_date, readthrough.start_date)
self.assertIsNone(readthrough.progress)
view = views.CreateStatus.as_view()
form = forms.CommentForm(
{
"progress": 1,
"progress_mode": "PG",
"content": "I started the book",
"id": readthrough.id,
"book": self.book.id,
"user": self.local_user.id,
"privacy": "public",
}
)
request = self.factory.post("", form.data)
request.user = self.local_user
view(request, "comment")
readthrough.refresh_from_db()
self.assertEqual(1, readthrough.progress)
self.assertEqual(start_date, readthrough.start_date) # not overwritten
def test_create_status_wrong_user(self, *_):
"""You can't compose statuses for someone else"""
view = views.CreateStatus.as_view()

View file

@ -1,6 +1,8 @@
""" test for app action functionality """
from unittest.mock import patch
import datetime
from django.contrib.auth.models import AnonymousUser
from django.http.response import Http404
from django.template.response import TemplateResponse
@ -12,6 +14,11 @@ from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.tests.validate_html import validate_html
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
class UserViews(TestCase):
"""view user and edit profile"""
@ -36,6 +43,10 @@ class UserViews(TestCase):
cls.book = models.Edition.objects.create(
title="test", parent_work=models.Work.objects.create(title="test work")
)
cls.book_recently_shelved = models.Edition.objects.create(
title="recently shelved",
parent_work=models.Work.objects.create(title="recent shelved"),
)
with (
patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"),
patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"),
@ -45,6 +56,14 @@ class UserViews(TestCase):
book=cls.book,
user=cls.local_user,
shelf=cls.local_user.shelf_set.first(),
shelved_date=make_date(2020, 10, 21),
)
models.ShelfBook.objects.create(
book=cls.book_recently_shelved,
user=cls.local_user,
shelf=cls.local_user.shelf_set.first(),
shelved_date=make_date(2024, 7, 1),
)
models.SiteSettings.objects.create()
@ -119,6 +138,23 @@ class UserViews(TestCase):
with self.assertRaises(Http404):
view(request, "rat")
def test_user_page_activity_sorted(self):
"""the most recently shelved book should be displayed first"""
view = views.User.as_view()
request = self.factory.get("")
request.user = self.local_user
with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False
result = view(request, "mouse")
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.status_code, 200)
first_shelf = result.context_data["shelves"][0]
first_book = first_shelf["books"][0]
self.assertEqual(first_book, self.book_recently_shelved)
def test_followers_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Relationships.as_view()

View file

@ -52,7 +52,6 @@ class PartialDate(datetime):
Use subclasses to specify precision. If `dt` is naive, `ValueError`
is raised.
"""
# pylint: disable=invalid-name
if timezone.is_naive(dt):
raise ValueError("naive datetime not accepted")
return cls.combine(dt.date(), dt.time(), tzinfo=dt.tzinfo)

View file

@ -88,6 +88,7 @@ class CeleryStatus(View):
def post(self, request):
"""Submit form to clear queues"""
form = ClearCeleryForm(request.POST)
results = []
if form.is_valid():
if len(celery.control.ping()) != 0:
return HttpResponse(

View file

@ -43,7 +43,6 @@ class Dashboard(View):
) or not re.match(regex.DOMAIN, settings.EMAIL_SENDER_DOMAIN)
data["email_config_error"] = email_config_error
# pylint: disable=line-too-long
data[
"email_sender"
] = f"{settings.EMAIL_SENDER_NAME}@{settings.EMAIL_SENDER_DOMAIN}"
@ -84,7 +83,7 @@ class Dashboard(View):
schedule, _ = IntervalSchedule.objects.get_or_create(
**schedule_form.cleaned_data
)
PeriodicTask.objects.get_or_create(
PeriodicTask.objects.update_or_create(
interval=schedule,
name="check-for-updates",
task="bookwyrm.models.site.check_for_updates_task",

View file

@ -152,7 +152,7 @@ class FederatedServer(View):
}
return TemplateResponse(request, "settings/federation/instance.html", data)
def post(self, request, server): # pylint: disable=unused-argument
def post(self, request, server):
"""update note"""
server = get_object_or_404(models.FederatedServer, id=server)
server.notes = request.POST.get("notes")

View file

@ -63,7 +63,6 @@ class ImportList(View):
}
return TemplateResponse(request, "settings/imports/imports.html", data)
# pylint: disable=unused-argument
def post(self, request, import_id):
"""Mark an import as complete"""
import_job = get_object_or_404(models.ImportJob, id=import_id)
@ -95,7 +94,6 @@ def enable_imports(request):
@require_POST
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
# pylint: disable=unused-argument
def set_import_size_limit(request):
"""Limit the amount of books users can import at once"""
site = models.SiteSettings.objects.get()
@ -120,7 +118,6 @@ def set_user_import_completed(request, import_id):
@require_POST
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
# pylint: disable=unused-argument
def set_user_import_limit(request):
"""Limit how ofter users can import and export their account"""
site = models.SiteSettings.objects.get()

View file

@ -204,7 +204,6 @@ def resolve_book(request):
@login_required
@require_POST
@permission_required("bookwyrm.edit_book", raise_exception=True)
# pylint: disable=unused-argument
def update_book_from_remote(request, book_id, connector_identifier):
"""load the remote data for this book"""
connector = connector_manager.load_connector(

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