Compare commits

...

651 commits
v0.2.1 ... main

Author SHA1 Message Date
Mouse Reeve
e3471fcc35
Merge pull request #2148 from hughrun/quotes
add page numbers to comment and quote statuses
2022-06-10 17:35:15 -07:00
Mouse Reeve
2993989d27
Merge pull request #2149 from cincodenada/preview-generation-memory
Update preview image generation to only query ids
2022-06-10 17:25:05 -07:00
Joel Bradshaw
7f5d47a36f Use values_list with flat, yay! 2022-06-07 23:15:34 -07:00
Mouse Reeve
3aa159bc89
Merge branch 'main' into preview-generation-memory 2022-06-05 18:39:59 -07:00
Mouse Reeve
8d082bc189
Merge branch 'main' into quotes 2022-06-05 15:42:01 -07:00
Mouse Reeve
08231f52ff
Merge pull request #2150 from cincodenada/fix-pylint
Fix pylint config for pylint 2.14.0
2022-06-05 15:41:32 -07:00
Joel Bradshaw
6584cb6404 Go back to one requirements.txt, simplify workflow
The workflow can now use .pylintrc and the pylint req in
requirements.txt rather than having the options inline and installing it
separately
2022-06-05 14:57:42 -07:00
Joel Bradshaw
b3603c04c5 Add pylint to bw-dev
Because pylint requires the app to be fully parseable with all its
dependencies, we run it in the web container, and add pylint as a dev
dependency.
2022-06-05 14:49:21 -07:00
Joel Bradshaw
6d6ab9a531 Add .pylintrc with fixes for new pylint version 2022-06-05 14:38:03 -07:00
Joel Bradshaw
b744ff7836 Run black 2022-06-05 13:40:01 -07:00
Joel Bradshaw
482005f304 Update preview image generation to only query ids
Previously we were querying the full book objects just to get a list of
id's, which is much slower and also takes a lot more memory, which can
cause the process to be killed on memory-limited machines with a large
number of books.

Instead, since we're just dispatching jobs here, we can just ask for the
id's, which is faster and much more practical memory-wise.

The map is a little annoying, I didn't see a way to directly get just a
list of the value of one field, so we have to get a list of
dictionairies with one key and then pull that key out. Whatevs.
2022-06-05 13:07:44 -07:00
Hugh Rundle
4de9989d8e add page numbers to comment and quote statuses
This adds the page number for quote and comment statuses where a page number is provided:

- all ActivityPub posts
- Explore cards for comments (quotes already have the page number)

This responds to #2136
2022-06-05 16:02:25 +10:00
Mouse Reeve
d5bbb759e0
Merge pull request #2146 from bookwyrm-social/locales
Updates locales for stopped reading strings
2022-05-31 17:09:44 -07:00
Mouse Reeve
077c9bfe46 Updates locales for stopped reading strings 2022-05-31 16:53:46 -07:00
Mouse Reeve
9d5e113b92
Merge pull request #2145 from bookwyrm-social/about-layout
Clip column in about page
2022-05-31 14:05:43 -07:00
Mouse Reeve
4c050d0999
Merge pull request #2141 from bookwyrm-social/ol-search-rank
Use relative list order ranking in OpenLibrary search
2022-05-31 13:11:49 -07:00
Mouse Reeve
20f452ebf4 Clip column in about page
Text in the superlatives section can cause this column to expand outside
the container.
2022-05-31 12:23:59 -07:00
Mouse Reeve
374fdcf467 Use relative list order ranking in openlibrary search
Set OpenLibrary search condifidence based on the provided result order,
just using 1/(list index), so the first has rank 1, the second 0.5, the
third 0.33, et cetera.
2022-05-31 10:22:49 -07:00
Mouse Reeve
355e7039f0
Merge pull request #2139 from bookwyrm-social/search-refactor
Search refactor
2022-05-31 10:22:17 -07:00
Mouse Reeve
c3b35760a2 Updates test mocks for remote search 2022-05-31 09:37:54 -07:00
Mouse Reeve
969db13ff2 Safely return None in remote search return_first 2022-05-31 08:49:23 -07:00
Mouse Reeve
05fd30cfcf Pylint fixes in connector tests 2022-05-31 08:37:07 -07:00
Mouse Reeve
5e99002aad Raise priority for external connectors in initdb
By default, OpenLibrary and Inventaire were prioritzed below other
BookWyrm nodes. In practice, people have gotten better search results
from these connectors, hence the change. With the search refactor, this
has much less impact, but it will show these search results higher in
the list.

If the results page shows all the connectors' results integrated, this
field should be removed entirely.
2022-05-31 08:25:02 -07:00
Mouse Reeve
a053f20961 Re-implements return first option
Since we get all the results quickly now, this aggregates all the
results that came back and sorts them by confidence, and returns the
highest confidence result. The confidences aren't great on free text
search, but conceptually that's how it should work at least.

It may make sense to aggregate the search results in all contexts, but
I'll propose that in a separate PR.
2022-05-31 08:20:59 -07:00
Mouse Reeve
98ed03b6b4 Python formatting and test update 2022-05-30 17:00:34 -07:00
Mouse Reeve
83ee5a756f Filter intentaire results by confidence 2022-05-30 16:42:37 -07:00
Mouse Reeve
af19d728d2 Removes outdated unit tests 2022-05-30 16:16:10 -07:00
Mouse Reeve
87fe984462 Combines search formatter and parser function
The parser was extracting the list of search results from the json
object returned by the search endpoint, and the formatter was converting
an individual json entry into a SearchResult object. This just merged
them into one function, because they are never used separately.
2022-05-30 12:52:31 -07:00
Mouse Reeve
525e2a591d More error handing
Adds logging and error handling for some of the numerous ways a request
could fail (the remote site is down, the url is blocked, etc).

I also have the results boxes open by default, which makes it more
legible imo.
2022-05-30 12:40:13 -07:00
Mouse Reeve
45f2199c71 Gather and wait on async requests
This sends out the request tasks all at once and then aggregates the
results, instead of just running them one after another asynchronously.
2022-05-30 12:05:22 -07:00
Mouse Reeve
5e81ec75fb Set request headers in async search get request
Gotta ask for json
2022-05-30 11:19:16 -07:00
Mouse Reeve
9a9cef7766 Verify url before async search
The database lookup doesn't work during the asyn process, so this change
loops through the connectors and grabs the formatted urls before sending
it to the async handler.
2022-05-30 11:16:05 -07:00
Mouse Reeve
0adda36da7 Remove search endpoints from Connector
Instead of having individual search functions that make individual
requests, the connectors will always be searched asynchronously
together. The process_seach_response combines the parse and format
functions, which could probably be merged into one over-rideable
function.

The current to-do on this is to remove Inventaire search results that
are below the confidence threshhold after search, which used to happen
in the `search` function.
2022-05-30 10:37:24 -07:00
Mouse Reeve
9c03bf782e Make an async request to all search connectors
This is the untest first pass at re-arranging remote search to work in
parallel rather than sequence. It moves a couple functions around
(raise_not_valid_url, for example, needs to be in connector_manager.py
now to avoid circular imports). It adds a function to Connector objects
that generates a search result (either to the isbn endpoint or the free
text endpoint) based on the query, which was previously done as part of
the search.

I also lowered the timeout to 8 seconds by default.
2022-05-30 10:15:22 -07:00
Mouse Reeve
7905be7de2
Merge pull request #2138 from bookwyrm-social/ratings-query
Use general ratings rather than privacy filtered
2022-05-30 09:33:05 -07:00
Mouse Reeve
fb3c7205af Updates unit tests 2022-05-30 09:17:51 -07:00
Mouse Reeve
fc3b609ada Use general ratings rather than privacy filtered
The original system customized how a rating is displayed to every user
based on the privacy settings of the reviews and, relatedly, who the
user follows. This is cool, but the query is too complicated to load in
sessions, and the initial load, which isn't mitigated by caching, is too
much and causes timeouts for many users. Also the cache clearing wasn't
working correctly because I put in a wildcard, which does not work.
2022-05-30 08:42:48 -07:00
Mouse Reeve
4e3c346780
Merge pull request #2134 from bookwyrm-social/stopped-shelf-fixes
Stopped shelf fixes
2022-05-26 13:12:57 -07:00
Mouse Reeve
74925a379a Prettier 2022-05-26 12:54:31 -07:00
Mouse Reeve
4e0e6ed5a4 Tick javascript cache and version number 2022-05-26 12:49:04 -07:00
Mouse Reeve
09db4e48f4 Hide rather than remove current shelve list items 2022-05-26 12:46:34 -07:00
Mouse Reeve
c5f5d4d994 Only show "stop" option when a book is in progress 2022-05-26 12:27:44 -07:00
Mouse Reeve
4905652e22 Handle stopped reading special case in javascript
This should be refactored, but maybe not today
2022-05-26 12:23:13 -07:00
Mouse Reeve
4c5d2570ab Save and display stopped date in readthrough 2022-05-26 11:53:33 -07:00
Mouse Reeve
dfe0656eb4 Typo fix 2022-05-26 11:38:36 -07:00
Mouse Reeve
375c5a8789 Adds stopped date separate from finish date on readthrough 2022-05-26 11:36:37 -07:00
Mouse Reeve
1f6fbd8d29 Fixes stopped reading button logic
The stopped state is similar to finished
2022-05-26 11:28:54 -07:00
Mouse Reeve
9b4a498661 Don't show a button for the shelf a book is currently on
This will lead to nonsensical modal states
2022-05-26 11:19:49 -07:00
Mouse Reeve
92dbfec5f8 Adds status header for stopped reading statuses 2022-05-26 11:10:14 -07:00
Mouse Reeve
6848616ff1 Fixes reading status field in stop modal
The value of the reading status needs to match one of the database
options for `reading_status` in the `Comment` model
2022-05-26 11:09:11 -07:00
Mouse Reeve
007751c8cb Adds error logging to status views 2022-05-26 10:58:11 -07:00
Mouse Reeve
23c6019340 Adds merge migration 2022-05-26 10:23:32 -07:00
Mouse Reeve
77a7dfa924
Merge pull request #2133 from bookwyrm-social/activitypub-connection-erorr
Don't throw an error when unable to connect to remote data
2022-05-26 10:12:18 -07:00
Mouse Reeve
88b2cffcf2
Merge pull request #2035 from bookwyrm-social/stopped-shelf
Stopped shelf
2022-05-26 10:11:32 -07:00
Mouse Reeve
9d275db322 Updates ignore boost logic that no longer produces errors 2022-05-26 09:57:39 -07:00
Mouse Reeve
3e54a5f4a3 Python formatting 2022-05-26 09:00:45 -07:00
Mouse Reeve
0bfe1e9dfc Don't throw an error when unable to connect to remote data 2022-05-25 13:24:11 -07:00
Mouse Reeve
f4226b050f
Merge pull request #2129 from bookwyrm-social/locales
Updates locales (changes to German, Romanian)
2022-05-23 18:02:45 -07:00
Mouse Reeve
b8ddafffbe
Merge pull request #2130 from bookwyrm-social/followers-hidden
Make an exception for yourself when followers are hidden
2022-05-23 18:02:34 -07:00
Mouse Reeve
0f7317f8fe Make an exception for yourself when followers are hidden 2022-05-23 15:31:05 -07:00
Mouse Reeve
867981b2a4 Updates locales (changes to German, Romanian) 2022-05-23 15:20:35 -07:00
Mouse Reeve
6d5923bb8f
Merge pull request #2128 from bookwyrm-social/multiple-authors
Multiple authors not added when editing book
2022-05-23 14:07:54 -07:00
Mouse Reeve
3ed685e341
Merge pull request #2126 from bookwyrm-social/black-update
Updates black version
2022-05-23 13:59:19 -07:00
Mouse Reeve
9172d7ff4e
Merge pull request #2127 from bookwyrm-social/add-book
Corrects redirect to confirm mode when adding book
2022-05-23 13:59:12 -07:00
Mouse Reeve
69f192e78c Fixes error in add author code returning too soon 2022-05-23 13:57:14 -07:00
Mouse Reeve
b2c587e082 Adds unit test for add author code when editing book 2022-05-23 13:51:58 -07:00
Mouse Reeve
efd1fd82a9 Corrects redirect to confirm mode when adding book 2022-05-23 13:02:06 -07:00
Mouse Reeve
ae2006c726 Updates black version 2022-05-23 12:46:45 -07:00
Mouse Reeve
1843959d10
Merge pull request #2093 from Ryuno-Ki/calibre-import
Calibre import. Fixes #627
2022-05-23 12:37:50 -07:00
Mouse Reeve
212bd49e6c
Merge pull request #2125 from bookwyrm-social/edit-author
Fixes edit author paths
2022-05-23 12:26:07 -07:00
André Jaenisch
d837146b66
Make black happy
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-05-23 20:59:28 +02:00
André Jaenisch
b564e514fd
Handle parsed dates that already have a timezone on import.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-05-23 20:52:57 +02:00
André Jaenisch
12541d5f1c
Map timestamp to date_added to avoid integrity error.
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-05-23 20:52:26 +02:00
Mouse Reeve
d8b2ab74d1 Fixes edit author paths 2022-05-23 11:08:04 -07:00
Mouse Reeve
065095776f
Merge pull request #2119 from bookwyrm-social/edit-book
Fixes urls in edit book form
2022-05-19 10:47:10 -07:00
Mouse Reeve
6d7bb33683 Fixes urls in edit book form 2022-05-19 09:32:01 -07:00
Mouse Reeve
cbd43c42a9
Merge pull request #2115 from bookwyrm-social/book-langs
Prevent error when a book language has a null value
2022-05-16 11:41:07 -07:00
Mouse Reeve
8d2da587d9 Prevent error when a book language has a null value 2022-05-16 11:06:11 -07:00
Mouse Reeve
39b6364e62
Merge pull request #2114 from bookwyrm-social/sentry-error-article
Fixes exception when receiving Article type activities
2022-05-16 10:38:15 -07:00
Mouse Reeve
b2775c5160 Check unsupported types before attempting to serialize 2022-05-16 10:21:54 -07:00
Mouse Reeve
fd43b56d31 Fixes celery error encountering Article type activities 2022-05-16 10:17:21 -07:00
Mouse Reeve
17864da8a2
Merge pull request #2113 from bookwyrm-social/status-priority
Fixes how backdated statuses are prioritized
2022-05-16 09:49:39 -07:00
Mouse Reeve
fdd4691e00 Adds unit test 2022-05-16 09:41:34 -07:00
Mouse Reeve
876d9c2695 Fixes how backdated statuses are prioritized 2022-05-16 09:24:01 -07:00
Mouse Reeve
fd66961ab8
Merge pull request #2104 from maxheadroom/main
add automatic restart of containers
2022-05-16 08:07:49 -07:00
Mouse Reeve
ae8edce197
Merge pull request #2111 from maeserichar/fix_broken_links
Fix broken links in README
2022-05-16 08:05:44 -07:00
Mouse Reeve
241169650d
Merge pull request #2007 from viviicat/url-names
Add names of books/lists/authors/etc as slugs, redirect to slugified version of the page
2022-05-16 08:04:58 -07:00
Mouse Reeve
23eb1c1b10
Merge pull request #1942 from willhoh/isbn_search
Isbn check befor search
2022-05-16 08:01:31 -07:00
Ricardo Rodríguez
643a3509dd docs: Fix broken links in README 2022-05-15 13:01:25 +02:00
Mouse Reeve
a5f9efc2b5
Merge pull request #2110 from bookwyrm-social/locales
Updates locales
2022-05-14 08:43:35 -07:00
Mouse Reeve
8c0ad7e73d Updates locales 2022-05-14 08:29:25 -07:00
Falko Zurell
d0b7474744
add automatic restart of containers
Added ```restart: unless-stopped``` to keep containers up and running after a reboot.
2022-05-09 11:00:28 +02:00
Mouse Reeve
49e6eb8f68
Merge pull request #2092 from bookwyrm-social/locale-updates
Updates locales
2022-05-06 12:50:49 -07:00
Mouse Reeve
ba7c39404b
Merge pull request #2103 from denmch/bugfix-profile-link
Replace user|username with request.user.localname
2022-05-06 12:19:34 -07:00
Den McHenry
80b0206e0d Replace user|username with request.user.localname 2022-05-06 10:29:25 -07:00
André Jaenisch
62c7661fb9
Reformat tests using black
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-05-05 21:31:56 +02:00
André Jaenisch
22fcb61fb2
Write tests for Calibre importer
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-05-05 13:08:01 +02:00
André Jaenisch
6bd9b725e2
Refactor hard-coded strings with a reference to a static property
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-05-05 13:07:25 +02:00
André Jaenisch
eeb1cc7197
Use a default shelf because Calibre indicates no reading status
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-04-30 19:08:31 +02:00
André Jaenisch
3626db3c1a
Add Calibre importer for CSV exports
Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
2022-04-30 15:25:35 +02:00
Mouse Reeve
95c043cc92 Updates locales 2022-04-29 15:44:31 -07:00
Mouse Reeve
a4a06fa32c
Merge pull request #2090 from bookwyrm-social/dashboard-warning
Fixes invite request alert count
2022-04-29 15:41:57 -07:00
Mouse Reeve
966bec1d18 Fixes invite request alert count 2022-04-26 08:33:15 -07:00
Mouse Reeve
708dc4d613
Merge pull request #2089 from bookwyrm-social/no-confirmation
Show clearer behavior when no email confirmation is needed after all
2022-04-26 08:24:35 -07:00
Mouse Reeve
a6cb46356f Show clearer behavior when no email confirmation is needed after all 2022-04-26 08:14:31 -07:00
Mouse Reeve
34be995125
Merge pull request #2087 from bookwyrm-social/locales
Updates locales
2022-04-26 07:50:16 -07:00
Mouse Reeve
676a51411f Updates locales 2022-04-26 07:41:23 -07:00
Mouse Reeve
93ec53f523
Merge pull request #2085 from bookwyrm-social/dependabot/pip/django-3.2.13
Bump django from 3.2.12 to 3.2.13
2022-04-25 09:20:12 -07:00
dependabot[bot]
3559bb5630
Bump django from 3.2.12 to 3.2.13
Bumps [django](https://github.com/django/django) from 3.2.12 to 3.2.13.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.12...3.2.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-22 22:47:05 +00:00
Mouse Reeve
358c507839
Merge pull request #2080 from viviicat/dark-read-hist-editions
Further dark theme fixes
2022-04-09 07:53:17 -07:00
Vivianne Langdon
64b623df32 fixes for bulma not having good dark support 2022-04-09 00:06:10 -07:00
Vivianne Langdon
d3992802f2 use a new has-text-default instead of has-text-black 2022-04-08 23:14:30 -07:00
Vivianne Langdon
b0d3eaeb40 allow empty slugs, for non-url-friendly book names 2022-04-08 22:11:05 -07:00
Vivianne
5a2bf64864
Merge branch 'bookwyrm-social:main' into url-names 2022-04-08 21:45:37 -07:00
Mouse Reeve
300eea3b94
Merge pull request #2074 from bookwyrm-social/pylint-tests-dir
Include test files in pylint
2022-04-08 14:39:44 -07:00
Mouse Reeve
8b7f664da3
Merge pull request #2078 from bookwyrm-social/locales
Consistent formatting for "BookWyrm" name
2022-04-08 14:34:46 -07:00
Mouse Reeve
2c394a2518 Fixes typo 2022-04-08 14:29:42 -07:00
Mouse Reeve
8ea1171764 Python formatting 2022-04-08 14:24:14 -07:00
Mouse Reeve
9921a1e754 Various pylint complaince fixes 2022-04-08 14:23:37 -07:00
Mouse Reeve
a92bf785dd Updates init files for pylint 2022-04-08 14:16:05 -07:00
Mouse Reeve
3c1f95a83a Updates locales 2022-04-08 14:01:13 -07:00
Mouse Reeve
6455476df7 Consistent formatting for "BookWyrm" name 2022-04-08 13:59:10 -07:00
Mouse Reeve
e0fa0b859a
Merge pull request #2077 from bookwyrm-social/main-dropdown
Uses details for user menu in main navbar
2022-04-08 13:58:15 -07:00
Mouse Reeve
ae8fed3e82 Removes stray dash from template 2022-04-08 13:50:06 -07:00
Mouse Reeve
51f5c9562d Uses details for user menu in main navbar 2022-04-08 13:45:17 -07:00
Mouse Reeve
1a6e98b546
Merge pull request #2073 from bookwyrm-social/update-locales
Updates locales
2022-04-04 15:38:21 -07:00
Mouse Reeve
fe85784ceb
Merge pull request #2072 from bookwyrm-social/zsh-complete
Adds zsh-specific completions file
2022-04-04 15:26:31 -07:00
Mouse Reeve
9e803043b2 Include test files in pylint 2022-04-04 15:24:39 -07:00
Mouse Reeve
bada50cee9 Updates locales 2022-04-04 15:20:15 -07:00
Mouse Reeve
b718e01a5c Adds zsh-specific completions file 2022-04-04 15:17:18 -07:00
Mouse Reeve
4c09477aa2
Improves instance list admin view (#2068)
* Removes irrelevent initial federated server data

* Adds secondary search order to instance list

* Show last updated date

* Adds filters to federated server view

* Updates unit tests
2022-04-02 09:16:07 -07:00
Mouse Reeve
ae86829a7e
Adds Finnish locale (#2069)
* Adds Finnish locale
2022-03-31 08:20:52 -07:00
Mouse Reeve
c7261780a8
Updates locales (#2065) 2022-03-26 14:34:15 -07:00
Mouse Reeve
71cbe611de Merge migration 2022-03-26 13:07:27 -07:00
Mouse Reeve
ec21d20b90 Merge branch 'main' into stopped-shelf 2022-03-26 13:06:06 -07:00
Mouse Reeve
701a644c31
Export user book data as csv (#1556)
Simple book data export
2022-03-26 13:04:59 -07:00
Mouse Reeve
0728864fe0
Merge pull request #2064 from bookwyrm-social/ui-fixes
Misc theme fixes
2022-03-26 11:48:59 -07:00
Mouse Reeve
3ebc800a9b Fixes progress bar color in dark mode 2022-03-26 11:38:00 -07:00
Mouse Reeve
23ff58a62b Fixes scrollbar colors in dark mode 2022-03-26 11:35:24 -07:00
Mouse Reeve
0666a2d02f Remove transparent class on interaction buttons 2022-03-26 11:07:58 -07:00
Mouse Reeve
b23f4a7e18 Clip statuses 2022-03-26 11:00:53 -07:00
Mouse Reeve
7cbf78c5fd
Merge pull request #2056 from bookwyrm-social/duplicate-follow-requests
Trigger rebroadcast of follow requests
2022-03-26 10:42:06 -07:00
Mouse Reeve
00c36de745
Merge pull request #2062 from bookwyrm-social/locales
Adds Romanian locale
2022-03-26 10:41:07 -07:00
Mouse Reeve
85f507d6b9 Python formatting 2022-03-26 10:34:02 -07:00
Mouse Reeve
5cf52cff54 Formats migration 2022-03-26 10:32:07 -07:00
Mouse Reeve
c527e0e411
Merge pull request #2061 from bookwyrm-social/link-typo
Fixes typo in about link
2022-03-26 10:30:59 -07:00
Mouse Reeve
a1487ccae5
Merge branch 'main' into duplicate-follow-requests 2022-03-26 10:28:58 -07:00
Mouse Reeve
2d7902ff89 Resolve second integrity error 2022-03-26 10:27:49 -07:00
Mouse Reeve
dc171776f8
Merge branch 'main' into link-typo 2022-03-26 10:21:52 -07:00
Mouse Reeve
44af09336c
Merge branch 'main' into locales 2022-03-26 10:21:43 -07:00
Mouse Reeve
566182c046
Merge pull request #2063 from bookwyrm-social/pylint-warning
Avoid new pylint complaint
2022-03-26 10:21:34 -07:00
Mouse Reeve
90277a1697 Avoid new pylint complaint 2022-03-26 10:07:06 -07:00
Mouse Reeve
a6ae55608a Adds Romanian locale 2022-03-26 10:03:50 -07:00
Mouse Reeve
27e23e76ae Fixes typo in about link 2022-03-26 09:43:49 -07:00
Mouse Reeve
4f24b05d60 Clear cache regardless of view success 2022-03-24 13:10:49 -07:00
Mouse Reeve
6808b70d3f
Merge pull request #2055 from bookwyrm-social/reports
Paginate reports
2022-03-24 11:35:48 -07:00
Mouse Reeve
a3b9c621af Trigger rebroadcast of follow requests 2022-03-24 11:35:05 -07:00
Mouse Reeve
0166cca0b7 Show created date and follower counts in admin view
Adds "admin_mode" to user_preview
2022-03-24 11:17:35 -07:00
Mouse Reeve
82f87a3ff5 Adds colored icon for user status in admin table 2022-03-24 10:55:32 -07:00
Mouse Reeve
533642bf7e Adds link to admin view 2022-03-24 10:43:17 -07:00
Mouse Reeve
951b611881 Paginates results 2022-03-24 10:40:42 -07:00
Mouse Reeve
88e915409b
Merge pull request #2048 from bookwyrm-social/import-help
Import help
2022-03-21 13:09:51 -07:00
Mouse Reeve
43cc017b44 Removes tooltip component 2022-03-21 12:32:53 -07:00
Mouse Reeve
9e792a8901 Italics for null state text on import page, to be consistent 2022-03-21 12:26:07 -07:00
Mouse Reeve
34166b8a2f Uses help instead of tooltip for goodreads export info 2022-03-21 12:24:47 -07:00
Mouse Reeve
3f7afc9014 Adds prompt to import books in null state of suggested books 2022-03-21 12:24:31 -07:00
Mouse Reeve
a29db4840c
Merge pull request #2046 from bookwyrm-social/resend-flow
Resend flow
2022-03-19 15:28:30 -07:00
Mouse Reeve
1b53c81351 Updates tests 2022-03-19 15:16:20 -07:00
Mouse Reeve
78ac252dae Python formatting 2022-03-19 12:08:57 -07:00
Mouse Reeve
f2ab890b5a Adds fallback form to modal 2022-03-19 12:07:07 -07:00
Mouse Reeve
4386d2ddb9 Switches resend email to modal 2022-03-19 12:00:16 -07:00
Mouse Reeve
5655b94bad
Merge pull request #2044 from bookwyrm-social/date-picker
Uses custom date select widget for publication dates
2022-03-19 09:33:53 -07:00
Mouse Reeve
b134c0c2fd
Merge pull request #2045 from bookwyrm-social/dashboard-display
Dashboard display small fixes
2022-03-19 09:33:19 -07:00
Mouse Reeve
f0a87e2a20 Use fullwidth tables in admin views 2022-03-19 09:16:28 -07:00
Mouse Reeve
7f6a98e764 Don't let site settings form get too wide 2022-03-19 09:11:59 -07:00
Mouse Reeve
1cfe3b3f94 Re-orders site settings registration toggles
Having require email confirm next to allow registration seems better to
me
2022-03-19 09:09:25 -07:00
Mouse Reeve
45672c2b70 Adds missing widgets file 2022-03-19 09:04:50 -07:00
Mouse Reeve
68e3a71b18 Consistent height for instance stats 2022-03-19 09:01:04 -07:00
Mouse Reeve
09e040ec11 Equal height for dashboard notifications 2022-03-19 08:59:41 -07:00
Mouse Reeve
dc9f8fccb7 Adds widgets file 2022-03-19 08:48:10 -07:00
Mouse Reeve
a701bfcf8e Uses custom date select widget for publication dates 2022-03-19 08:45:10 -07:00
Mouse Reeve
db5f509475
Merge pull request #2042 from bookwyrm-social/progress-updates
Fixes progress updates
2022-03-18 19:30:54 -07:00
Mouse Reeve
55dc998d03 Retain start date when updating from modal 2022-03-18 19:20:43 -07:00
Mouse Reeve
287b5603d6 Fixes progress updates 2022-03-18 19:11:58 -07:00
Mouse Reeve
f52b8fc028
Merge pull request #2040 from bookwyrm-social/edit-book
Fixes edit book page
2022-03-18 07:37:32 -07:00
Mouse Reeve
713391f468 Fixes edit book page 2022-03-18 07:28:07 -07:00
Mouse Reeve
576d0ee189
Merge pull request #2038 from bookwyrm-social/build-script
Comments out build script step in update command
2022-03-17 10:36:23 -07:00
Mouse Reeve
184a463097 Comments out build script step in update command 2022-03-17 10:25:03 -07:00
Mouse Reeve
ee78cc7393
Merge pull request #2037 from bookwyrm-social/celery-queues
Adds auto-create queues option to config
2022-03-17 10:06:49 -07:00
Mouse Reeve
ee1850ed15
Merge pull request #2034 from bookwyrm-social/locales
Updates locales
2022-03-17 10:01:38 -07:00
Mouse Reeve
20e71dc0cd
Merge pull request #2036 from bookwyrm-social/author-performance
Simplifies query possibly causing author page performance issues
2022-03-17 10:01:26 -07:00
Mouse Reeve
117db78983 Adds auto-create queues option to config 2022-03-17 09:57:25 -07:00
Mouse Reeve
a584f077b7 Simplifies query likely causing author page performance issues 2022-03-17 09:51:54 -07:00
Mouse Reeve
c8fa031c23 Updates locales 2022-03-17 09:21:06 -07:00
Mouse Reeve
2047365d31
Merge pull request #1973 from bookwyrm-social/add-edition
Create another edition for existing work
2022-03-17 08:51:13 -07:00
Mouse Reeve
ef4e06ad52
Merge pull request #2033 from bookwyrm-social/author-sort
Reverts author view changes
2022-03-17 08:51:06 -07:00
Mouse Reeve
9e0d6ed512
Merge branch 'main' into author-sort 2022-03-17 08:31:45 -07:00
Mouse Reeve
0101d2561a Python formatting 2022-03-17 08:18:44 -07:00
Mouse Reeve
a684d86d15 Fixes subjects in add edition view 2022-03-17 08:02:59 -07:00
Mouse Reeve
26f0501e2f SHow editions link on all book pages 2022-03-17 07:40:55 -07:00
Mouse Reeve
997a671cfb Consistent style for edit book confirm mode 2022-03-17 07:34:59 -07:00
Mouse Reeve
3ca611fb7c
Merge pull request #2031 from viviicat/dark-theme-fixes
Fixes for dark theme
2022-03-17 07:27:19 -07:00
Mouse Reeve
cf58d0ad5c Reverts author view changes 2022-03-17 07:22:22 -07:00
Vivianne Langdon
3050b33084 add success-light and warning-light! 2022-03-16 20:21:13 -07:00
Mouse Reeve
5255abb2af Fixes create book view unit test 2022-03-16 17:55:41 -07:00
Mouse Reeve
0617b9424b
Merge pull request #2029 from bookwyrm-social/merge-error
Removes file added by merge
2022-03-16 17:31:02 -07:00
Mouse Reeve
178f26192b Removes file added by merge 2022-03-16 17:15:53 -07:00
Mouse Reeve
0688dfa3aa
Merge pull request #2028 from bookwyrm-social/user-question-resolve
Resolves merge conflicts on user invite question PR
2022-03-16 17:09:35 -07:00
Mouse Reeve
8b061f9432 Manually updates migration 2022-03-16 16:53:16 -07:00
Mouse Reeve
7b3b357756 Merge branch 'main' into form-conflict 2022-03-16 16:51:57 -07:00
Mouse Reeve
44e68cd0a4 Whitespace fix 2022-03-16 16:50:34 -07:00
Mouse Reeve
922cc61a5f
Merge pull request #1998 from oragegu/question_invite_correct
Custom question option and field for spammed bookwyrm instances
2022-03-16 16:49:00 -07:00
Mouse Reeve
108981a226 Creates fresh migration and removes merges 2022-03-16 16:35:03 -07:00
Mouse Reeve
0cf2c07069
Merge branch 'main' into url-names 2022-03-16 16:32:07 -07:00
Mouse Reeve
ffec47ad9a
Merge pull request #2027 from bookwyrm-social/locales
Adds context and fixes whitespace in translation strings
2022-03-16 16:29:38 -07:00
Mouse Reeve
68dc5962ee Merge branch 'main' into add-edition 2022-03-16 16:16:55 -07:00
Mouse Reeve
da100cd114 Adds context and fixes whitespace in translation strings
Also updates locales
2022-03-16 16:13:15 -07:00
Mouse Reeve
159b73d860 Fixes errors in migration 2022-03-16 13:54:25 -07:00
Mouse Reeve
819458e82a Improves error reporting on activitypub parser 2022-03-16 13:53:54 -07:00
Mouse Reeve
f2b0b306e9
Merge pull request #1934 from tversteeg/partially-read-shelf
Add 'Stopped Reading' shelf
2022-03-16 13:51:15 -07:00
Mouse Reeve
eee325b662
Merge pull request #2026 from bookwyrm-social/missing-file
Adds missing update script
2022-03-16 13:41:43 -07:00
Mouse Reeve
ea69f9087f Adds missing update script 2022-03-16 13:32:37 -07:00
Mouse Reeve
8969958e51
Merge pull request #2025 from bookwyrm-social/upgrade-scripts
Run miscellaneous scripts during update
2022-03-16 13:03:38 -07:00
Mouse Reeve
2b2aa078ad
Merge branch 'main' into upgrade-scripts 2022-03-16 12:54:25 -07:00
Mouse Reeve
f4e828e2fb
Merge pull request #1974 from bookwyrm-social/celerybeat
Schedules automod tasks
2022-03-16 12:54:06 -07:00
Mouse Reeve
ee973c7d72 Adds celerybeat update script 2022-03-16 12:53:27 -07:00
Mouse Reeve
78b03efe45 Updates bw-dev command and ticks version number 2022-03-16 12:53:05 -07:00
Mouse Reeve
820279166a Adds update script 2022-03-16 12:39:49 -07:00
Thomas Versteeg
b3f03164cc Apply black 2022-03-15 09:28:40 +01:00
Thomas Versteeg
ee414598bf
Merge branch 'main' into partially-read-shelf 2022-03-15 08:28:02 +00:00
Mouse Reeve
81b7dca4b9 Merge branch 'main' into celerybeat 2022-03-14 15:13:09 -07:00
Mouse Reeve
13b82c2740
Merge pull request #2020 from bookwyrm-social/multi-input
Array input field for forms
2022-03-14 15:07:00 -07:00
Mouse Reeve
a37f83c458 Get the field working 2022-03-14 14:55:41 -07:00
Mouse Reeve
716e357060 Use plus icon on add field button 2022-03-14 12:54:50 -07:00
Mouse Reeve
35e6dede09 Script to remove input fields 2022-03-14 12:41:41 -07:00
Mouse Reeve
a2f2104a08 Create non-functional UI for editing array fields 2022-03-14 12:41:41 -07:00
Mouse Reeve
916d6a417d
Merge pull request #2018 from bookwyrm-social/locales
Adds scanner translation strings
2022-03-14 12:40:53 -07:00
Mouse Reeve
486f70c7fb Adds scanner translation strings 2022-03-14 12:31:29 -07:00
Mouse Reeve
d848b950dc
Merge pull request #2017 from bookwyrm-social/forms
Moves forms into separate files
2022-03-14 12:21:42 -07:00
Mouse Reeve
19202e2cd7 Fixes name of user forms file 2022-03-14 12:12:51 -07:00
Mouse Reeve
d3f723a07d Splits forms into separate files 2022-03-14 12:06:50 -07:00
Mouse Reeve
7169f7ba20 Creates forms directory 2022-03-14 11:43:58 -07:00
Mouse Reeve
c0db081120
Merge pull request #2016 from bookwyrm-social/duplicate-script
Moves duplicate field script into its own file
2022-03-14 11:09:48 -07:00
Mouse Reeve
bfb8fc800a Moves duplicate field script into its own file 2022-03-14 10:59:24 -07:00
Mouse Reeve
e0c0bebf65
Merge pull request #2015 from bookwyrm-social/admin-view-fix
Improves user admin view
2022-03-14 09:50:31 -07:00
Mouse Reeve
6ddf1aad91
Merge pull request #2000 from Tak/edit-subjects
Allow book subjects to be edited
2022-03-14 09:46:05 -07:00
Mouse Reeve
f42e863434 Updates user admin filters 2022-03-14 09:38:03 -07:00
Mouse Reeve
488d702473 Separate admin user list into tabbed lists 2022-03-14 09:27:18 -07:00
Mouse Reeve
bf68b70fba Use breadcrumbs in use admin view 2022-03-14 09:10:48 -07:00
Mouse Reeve
e4f94780eb Uses translated fields in admin user info 2022-03-14 09:10:42 -07:00
Corentin Feys
771fa5a00a
Merge branch 'main' into question_invite_correct 2022-03-13 23:44:20 +01:00
corentin-feys
a0ae96923c fix to pass invite request pytest 2022-03-13 23:34:49 +01:00
corentin-feys
74a1697cda fix to pass register pytest 2022-03-13 23:04:14 +01:00
corentin-feys
3e9cb2acb1 Revert to stable commit
This reverts commit d542be943f.
2022-03-13 22:56:41 +01:00
Mouse Reeve
57cba4eb7a
Merge pull request #2013 from bookwyrm-social/theme-path
Manually add theme path rather than options
2022-03-13 13:30:32 -07:00
Mouse Reeve
c08459cf5d Tick version number 2022-03-13 13:19:40 -07:00
Mouse Reeve
37beb5a8f4 Tick javascript cache buster 2022-03-13 13:19:02 -07:00
Mouse Reeve
3885ae789b Manually add theme path rather than options 2022-03-13 13:15:42 -07:00
Mouse Reeve
753cd36f86
Merge pull request #2012 from bookwyrm-social/catch-update-book-error
Catch update book error
2022-03-13 12:53:09 -07:00
Mouse Reeve
c7efa23405 Display error message for remote failure 2022-03-13 12:38:29 -07:00
Mouse Reeve
739b394ccc Catch error when trying to update book 2022-03-13 12:31:21 -07:00
Mouse Reeve
b32f3c1b7b
Merge pull request #1976 from viviicat/code-scanning
Add barcode scanning support
2022-03-13 12:26:31 -07:00
Mouse Reeve
5bd25ba740
Merge pull request #2011 from bookwyrm-social/update-locales
Updates locales
2022-03-13 12:14:42 -07:00
Mouse Reeve
2f124e00d1 Updates locales 2022-03-13 11:57:46 -07:00
Mouse Reeve
a69a9a401b
Merge pull request #2010 from bookwyrm-social/themes-text
Fixes instructions on admin themes view
2022-03-13 11:47:58 -07:00
Mouse Reeve
0c87ee1d4b Fixes instructions on admin themes view 2022-03-13 11:36:31 -07:00
Mouse Reeve
9f19fb698b
Merge pull request #2009 from viviicat/sass-in-prod
add SASS_PROCESSOR_ENABLED = True
2022-03-13 11:31:24 -07:00
Orage Pika
bcdee8071c fixing errors from check 2022-03-13 15:49:09 +01:00
Orage Pika
c99fe2bdc3 fixing errors from checks 2022-03-13 13:37:09 +01:00
Orage Pika
d2e6dfc07b fixing errors from checks 2022-03-13 13:31:38 +01:00
Orage Pika
4fb3cbfc29 fixing errors from checks 2022-03-13 13:30:37 +01:00
Orage Pika
d542be943f bw-dev black and fixing things according to the warning messages of the checks. 2022-03-13 13:23:58 +01:00
OragePika, aka "FANS DON'T CARE
974c569fc1
Merge branch 'main' into question_invite_correct 2022-03-13 13:14:23 +01:00
Vivianne Langdon
306f177d55 add SASS_PROCESSOR_ENABLED = True 2022-03-12 21:12:25 -07:00
Mouse Reeve
4cfa4046a1
Merge pull request #2008 from viviicat/fish-completions
Add autocompletions for fish shell
2022-03-12 06:39:09 -08:00
Vivianne Langdon
3358c233ea remove no-up autocomplete 2022-03-12 04:38:42 -08:00
Vivianne Langdon
4530d4917a make more compact with a function 2022-03-12 04:34:03 -08:00
Vivianne Langdon
822868bf87 fish autocompletions 2022-03-12 04:34:03 -08:00
Thomas Versteeg
5d8404f797 Add merge migration 2022-03-12 11:45:09 +01:00
Thomas Versteeg
9e6dfb4706
Merge branch 'main' into partially-read-shelf 2022-03-12 10:38:56 +00:00
corentin-feys
1b4e532f90 Added invite question migration 2022-03-12 11:19:14 +01:00
corentin-feys
5dbee33185 invite_question_text resets to default question when left blank 2022-03-12 11:13:42 +01:00
corentin-feys
7337f378c0 Hide the answer column from invites when invite questions are disabled 2022-03-12 11:10:44 +01:00
Vivianne Langdon
a4391f35c1 black 2022-03-11 22:31:40 -08:00
Vivianne Langdon
d6767e42fc fix variable clash 2022-03-11 22:28:05 -08:00
Vivianne Langdon
cf53134577 disable linting unused-argument 2022-03-11 21:19:20 -08:00
Vivianne Langdon
598a0587cf Fix issue with tabs on bottom of book page 2022-03-11 21:10:22 -08:00
Vivianne Langdon
f2d7bdbf27 in progress fixes for pylint 2022-03-11 20:14:45 -08:00
Vivianne Langdon
594fa5d058 Black 2022-03-11 20:00:13 -08:00
Vivianne
9fa8caba45
Merge branch 'bookwyrm-social:main' into url-names 2022-03-11 19:55:06 -08:00
Mouse Reeve
02a93bd730
Merge pull request #2006 from bookwyrm-social/log-level
Log info, not exception, for expected errors
2022-03-11 16:05:03 -08:00
Mouse Reeve
72d6a4ce52 Log info, not exception, for expected errors 2022-03-11 14:55:54 -08:00
Vivianne Langdon
5d25da93d5 revert previously changed unit tests 2022-03-11 04:25:50 -08:00
Vivianne Langdon
d9ac326c29 No more remote id with slug, just add slug in local path. 2022-03-11 04:18:52 -08:00
Mouse Reeve
e0ffcddd3c
Merge pull request #2003 from bookwyrm-social/modal-button-pattern
Consistent positioning of success buttons
2022-03-10 14:29:39 -08:00
Mouse Reeve
f2bf52ccb9
Merge pull request #2002 from bookwyrm-social/cw-refactor
Refactors content warning field
2022-03-10 14:29:16 -08:00
Mouse Reeve
dbf925f176 Removes trailing whitespace 2022-03-10 10:52:39 -08:00
Mouse Reeve
9977b33a8d Group, list, and shelve form buttons 2022-03-10 10:49:33 -08:00
Mouse Reeve
d4be0ca58b Report modal 2022-03-10 10:02:18 -08:00
Mouse Reeve
20453a9977 Delete readthrough and add cover modals 2022-03-10 09:57:55 -08:00
Mouse Reeve
8b4c9483ea Udates sync modals 2022-03-10 09:49:27 -08:00
Mouse Reeve
cbcd5c7a57 Adds elided page range to editions 2022-03-10 09:49:17 -08:00
Mouse Reeve
672eee9c9c Updates add file link modal 2022-03-10 09:41:32 -08:00
Mouse Reeve
d7eb118a07 Updates readthrough modal 2022-03-10 09:35:05 -08:00
Mouse Reeve
1657f28c5e Updates verification modal button positions 2022-03-10 09:30:39 -08:00
Mouse Reeve
cc2b774fb5 Updates wording on content warning field 2022-03-10 09:16:50 -08:00
Mouse Reeve
bcd83ee802 Fixes list notes icon 2022-03-10 09:16:44 -08:00
Mouse Reeve
a922b8fd04 Uses details to show/hide content warning field 2022-03-10 09:03:24 -08:00
Mouse Reeve
e2476d1ad3 Move content warning toggle out of post options block 2022-03-10 08:38:06 -08:00
OragePika, aka "FANS DON'T CARE
3868421bed
Merge branch 'main' into question_invite_correct 2022-03-10 01:42:47 +01:00
Orage Pika
0c429ee6d7 bw-dev blacked 2022-03-09 16:04:58 +01:00
Levi Bard
0c0d0b6299 Allow book subjects to be edited 2022-03-09 10:33:59 +01:00
Mouse Reeve
ad1969162f
Merge pull request #1999 from bookwyrm-social/locales
Locale updates
2022-03-08 12:43:33 -08:00
Mouse Reeve
a751884762 Locale updates 2022-03-08 12:12:39 -08:00
corentin-feys
bb7d080f65 Replaced tabs with spaces 2022-03-07 21:20:42 +01:00
corentin-feys
35f115bc0a removed unneeded code for invite question 2022-03-07 19:42:57 +01:00
corentin-feys
e1e03ebd22 removed unneded migrations 2022-03-07 19:41:24 +01:00
Orage Pika
5fbb5c655b custom questions 2022-03-07 18:49:59 +01:00
Mouse Reeve
34a4c18397
Merge branch 'main' into partially-read-shelf 2022-03-05 19:23:35 -08:00
Mouse Reeve
723ec8d461
Merge pull request #1994 from bookwyrm-social/theme-file
Adds custom compile management command
2022-03-04 17:58:46 -08:00
Mouse Reeve
f26106fffd Python formatting 2022-03-04 12:42:43 -08:00
Mouse Reeve
b0c0af9617 Adds custom compile management command 2022-03-04 12:40:06 -08:00
Vivianne Langdon
8838875879 Fix failure to 404 2022-03-02 04:07:13 -08:00
Vivianne Langdon
81594892ef Fix test for unit test requests 2022-03-02 03:42:29 -08:00
Vivianne Langdon
05f11e68c5 Hopefully knocking out many of the unit test fails 2022-03-02 03:11:02 -08:00
Vivianne Langdon
440e2f8806 black 2022-03-02 01:47:08 -08:00
Vivianne Langdon
2b483488aa Remove slugs from shelf as their id has text in it already 2022-03-02 01:37:58 -08:00
Vivianne Langdon
846963ad18 Fix accidental change to post 2022-03-02 01:16:30 -08:00
Vivianne Langdon
d8181d6d66 Redirect to correct url with slug 2022-03-02 01:12:32 -08:00
Vivianne Langdon
ebf463fc91 Generation of slugs and new urls to handle slugs
- TODO: redirect to correct slug if not found.
2022-03-02 00:21:23 -08:00
Vivianne
3ee3e9a13c
Merge branch 'main' into code-scanning 2022-03-01 18:33:40 -08:00
Mouse Reeve
0751a56474
Merge pull request #1990 from bookwyrm-social/themes-fix
Temporary fix for broken themes
2022-03-01 14:52:47 -08:00
Mouse Reeve
a99d482167 Temporary fix 2022-03-01 14:38:50 -08:00
Mouse Reeve
f7c6c70c5e Ticks version 2022-03-01 12:15:56 -08:00
Mouse Reeve
12ad88ac29
Merge pull request #1975 from bookwyrm-social/themes
Themes
2022-03-01 12:03:22 -08:00
Mouse Reeve
886448efc4
Merge pull request #1988 from bookwyrm-social/superlives
Keeps "superlatives" on about page local
2022-03-01 12:03:12 -08:00
Mouse Reeve
04ab584082 Update locales 2022-03-01 11:49:58 -08:00
Mouse Reeve
07daa24a72 Merge branch 'main' into themes 2022-03-01 11:46:02 -08:00
Mouse Reeve
89c8aa83f4 Tweaks preferences wording 2022-03-01 11:44:39 -08:00
Mouse Reeve
38535f811c Python formatting 2022-03-01 11:39:08 -08:00
Mouse Reeve
41ea7db8b6 Removes hardcoded white in announcements 2022-03-01 11:06:15 -08:00
Mouse Reeve
55f1ce12cf Second attempt at fixing tests using context processors 2022-03-01 11:05:47 -08:00
Mouse Reeve
4cdbdd8d0b
Merge pull request #1977 from viviicat/dark-theme
Dark theme
2022-03-01 11:00:05 -08:00
Mouse Reeve
9422a07414 Safer query for request user for tests 2022-03-01 10:36:19 -08:00
Mouse Reeve
5d7e6b872a Fixes localizing query for superlatives 2022-03-01 10:34:17 -08:00
Mouse Reeve
d1d743281a Cleans up display of superlatives on about page 2022-03-01 10:28:51 -08:00
Mouse Reeve
c7c90f9e9b Removes test print statement 2022-03-01 10:09:53 -08:00
Mouse Reeve
39fb402456
Merge pull request #1987 from viviicat/fix-reply-exception
Fix for TypeError/no focus when clicking on a reply
2022-03-01 09:58:31 -08:00
Mouse Reeve
f4dc07b6b9 Select theme in context processors 2022-03-01 09:53:02 -08:00
Vivianne Langdon
8e9bacc527 Fix red for <code> being too harsh 2022-02-28 23:31:57 -08:00
Vivianne Langdon
b4222bead4 Remove uuid for status reply panel
- The focus target did not include this uuid, so was throwing `Uncaught TypeError: node is null` when clicking on Reply button.
- I wasn't able to figure out how to share the uuid between blocks but it doesn't seem like the uuid is really needed -- the same block has other ids that do not have the uuid in them.
2022-02-28 23:01:33 -08:00
Vivianne Langdon
c0fed31fb0 eslint 2022-02-28 21:28:33 -08:00
Vivianne Langdon
40bb9112fd .prettierignore to vendor 2022-02-28 21:23:44 -08:00
Vivianne Langdon
62b4133e58 Move quagga to vendor. 2022-02-28 21:22:49 -08:00
Vivianne
8c92869fc0
Merge branch 'main' into code-scanning 2022-02-28 21:17:32 -08:00
Mouse Reeve
2c8fa5cd9b
Merge branch 'themes' into dark-theme 2022-02-28 13:32:37 -08:00
Mouse Reeve
a6883b5b87 Adds merge migration 2022-02-28 13:30:02 -08:00
Mouse Reeve
043fd54d70
Merge branch 'main' into themes 2022-02-28 13:27:05 -08:00
Mouse Reeve
e1ea847441
Merge pull request #1986 from bookwyrm-social/instance-refresh
Instance refresh
2022-02-28 13:26:22 -08:00
Mouse Reeve
142ecdf6aa
Merge pull request #1984 from bookwyrm-social/hide-follows
Option for users to hide their followers/following lists
2022-02-28 13:13:40 -08:00
Mouse Reeve
9efd67f6bf
Merge pull request #1985 from Strubbl/patch-1
Update README.md
2022-02-28 13:13:15 -08:00
Mouse Reeve
81cfcff939 Reload instance data 2022-02-28 13:11:01 -08:00
Mouse Reeve
c91b08303b More breadcrumbs 2022-02-28 12:31:28 -08:00
Mouse Reeve
f4aa202292 Adds counts of blocked and federated instances 2022-02-28 12:27:54 -08:00
Mouse Reeve
991461221d Adds breadcrumbs instead of "back" link 2022-02-28 12:24:45 -08:00
Strubbl
1888e8c8ed
Update README.md
fix link to instances
2022-02-28 21:23:00 +01:00
Mouse Reeve
00d0d9d5de Avoid whitespace when adding instance 2022-02-28 12:20:04 -08:00
Mouse Reeve
3dbbe0089c Show if user follows you 2022-02-28 12:07:06 -08:00
Mouse Reeve
ec93d1812a Block access to follow views 2022-02-28 12:04:47 -08:00
Thomas Versteeg
1e3f9246d6 Produce a proper status 2022-02-28 20:56:59 +01:00
Mouse Reeve
5837c37a32 Hide followers info slug 2022-02-28 11:55:54 -08:00
Mouse Reeve
e90cb52f23 Add option to hide follows 2022-02-28 11:48:49 -08:00
Thomas Versteeg
539775f370 Merge remote-tracking branch 'upstream/main' into partially-read-shelf 2022-02-28 20:44:55 +01:00
Mouse Reeve
6c17aa7630
Merge pull request #1983 from bookwyrm-social/shelf-name-translation
Shelf name translation
2022-02-28 11:28:53 -08:00
Mouse Reeve
5cdcac0682
Merge pull request #1981 from bookwyrm-social/list-duplicate-books
Show meaningful message when you try to add a duplicate book to a list
2022-02-28 11:21:18 -08:00
Mouse Reeve
b7111589c3
Merge pull request #1982 from bookwyrm-social/deleted-user-reviews
Remove reviews from deleted users
2022-02-28 11:20:44 -08:00
Mouse Reeve
7d6032e110 Fixes calls to filter 2022-02-28 11:18:03 -08:00
Mouse Reeve
ffb4098cfb Fixes translation of "remove from shelf" string 2022-02-28 11:07:12 -08:00
Mouse Reeve
0f5fd6be15 Move translations to filter 2022-02-28 11:05:12 -08:00
Mouse Reeve
374dd24fa8 Remove reviews from deleted users 2022-02-28 10:47:08 -08:00
Mouse Reeve
142cc5437a Move sorting to separate function 2022-02-28 10:41:40 -08:00
Mouse Reeve
99fc3aaf25 Avoid showing success and failure 2022-02-28 10:31:58 -08:00
Mouse Reeve
b2b3ba653e Refactors how success/failure messages how on list page 2022-02-28 10:29:58 -08:00
Mouse Reeve
202696f913 Don't show lists a book is already on in add form 2022-02-28 10:03:24 -08:00
Mouse Reeve
c82042f506 Delete themes 2022-02-28 09:55:23 -08:00
Mouse Reeve
106ef2e3a4 Fixes reference to theme in layout 2022-02-28 09:46:05 -08:00
Mouse Reeve
295d9c42d7 Adds theme to user settings form 2022-02-28 09:45:34 -08:00
Mouse Reeve
a00ee8a706 Adds link to set instance-wide theme 2022-02-28 09:43:31 -08:00
Mouse Reeve
f54d4863fe Updates .gitignore for themes 2022-02-28 09:37:51 -08:00
Mouse Reeve
2d516812b4 Fixes icons by moving import to theme 2022-02-28 09:34:54 -08:00
Mouse Reeve
f5fb5ae045 Removes instance config file
I'd be happy to re-add this if it's useful, but I think it's confusing
to have in addition to themes
2022-02-28 09:34:30 -08:00
Mouse Reeve
4d3e709b2a
Update layout.html 2022-02-28 09:23:03 -08:00
Mouse Reeve
3283302093
Merge branch 'themes' into dark-theme 2022-02-27 20:21:17 -08:00
Vivianne Langdon
3a9ff2c2ea Refer to canvas by type
- Fixes stylelint without adding an exclusion
2022-02-27 14:08:11 -08:00
Vivianne Langdon
c0380cca5a stylelint 2022-02-27 14:04:30 -08:00
Vivianne Langdon
1d4539c4c0 Don't pretty the min.js file 2022-02-27 14:03:45 -08:00
Vivianne Langdon
fbe7e860e8 Prettier 2022-02-27 14:01:25 -08:00
Mouse Reeve
8259d16ee9 Check available themes in form 2022-02-27 11:20:11 -08:00
Mouse Reeve
005b69177c
Merge branch 'themes' into dark-theme 2022-02-27 10:54:15 -08:00
Mouse Reeve
c8d3222c33
Rename dark.scss to bookwyrm-dark.scss 2022-02-27 10:52:07 -08:00
Mouse Reeve
fd0f739418
Rename light.scss to bookwyrm-light.scss 2022-02-27 10:51:49 -08:00
Mouse Reeve
8850b68b52 Show theme options 2022-02-27 10:48:33 -08:00
Mouse Reeve
cc015536fa Adds theme instructions 2022-02-27 10:12:47 -08:00
Mouse Reeve
3dfbb3272e Theme selector 2022-02-27 10:00:50 -08:00
Mouse Reeve
6e96c1eee7 Avoid linter error 2022-02-27 08:09:17 -08:00
Vivianne Langdon
e4d7dd7ee4 Fix progress bar 2022-02-27 05:29:33 -08:00
Vivianne Langdon
40319302b7 Initial theme 2022-02-27 05:20:29 -08:00
Vivianne Langdon
789626a9da Stray line 2022-02-27 00:39:45 -08:00
Vivianne Langdon
f5c66b5b4a Adjust layout more
Ensure camera select box is never hidden.
2022-02-27 00:33:54 -08:00
Vivianne Langdon
43f62ef5d7 d'oh, fix event leak 2022-02-27 00:18:38 -08:00
Vivianne Langdon
9f67a74340 Show grant access dialog every time we initialize 2022-02-27 00:04:25 -08:00
Vivianne Langdon
e71a5e3bdf Add barcode icon and use other icons
Minor formatting and messaging tweaks
2022-02-27 00:00:22 -08:00
Vivianne Langdon
f4d5b7b4d2 Adjusted message 2022-02-26 23:28:52 -08:00
Vivianne Langdon
9b0874f889 Fix barcode button 2022-02-26 23:28:13 -08:00
Vivianne Langdon
cdddf73e29 Improve layout for some camera types 2022-02-26 23:22:44 -08:00
Vivianne Langdon
464050deaa Implement switching cameras
Also, use session storage to remember last selected camera deviceId, if any
2022-02-26 21:32:01 -08:00
Vivianne Langdon
fee6ffcbd8 Fix formatting in chrome
Was using experimental selector, forget about it.
Also reduce jumping around of video size
2022-02-26 21:29:18 -08:00
Vivianne Langdon
8d0e549480 Improve visuals and quality of scanning 2022-02-26 20:19:26 -08:00
Vivianne Langdon
649ffe571a Fix typos with searching 2022-02-26 18:29:38 -08:00
Vivianne Langdon
fcc8b6aaab Move to sass 2022-02-26 18:19:12 -08:00
Vivianne Langdon
340b306d2e Fix path 2022-02-26 18:08:07 -08:00
Vivianne Langdon
48c8166e58 Merge branch 'main' into code-scanning 2022-02-26 17:54:33 -08:00
Mouse Reeve
43269429ac Use selected theme 2022-02-26 13:40:06 -08:00
Mouse Reeve
e15193e100 Adds themes 2022-02-26 12:44:20 -08:00
Mouse Reeve
6b5bebdf78 Cleans up scheduler form 2022-02-26 10:45:43 -08:00
Mouse Reeve
0870eccad9 Adds unscheduler 2022-02-26 10:24:23 -08:00
Mouse Reeve
2a436800c4 Schedules automod task 2022-02-26 10:14:47 -08:00
Mouse Reeve
95e9119817 Adds django celery beat 2022-02-26 08:44:19 -08:00
Mouse Reeve
a5571c65bc
Update 0134_alter_stopped_reading.py 2022-02-25 18:25:41 -08:00
Mouse Reeve
b511928400
Create 0134_alter_stopped_reading.py 2022-02-25 18:08:30 -08:00
Mouse Reeve
eea7d9d4e5 Retain author across saves 2022-02-25 17:57:18 -08:00
Mouse Reeve
29a6d74ff2 Python formatting 2022-02-25 17:23:13 -08:00
Mouse Reeve
b001c31f97 Save author along with added edition 2022-02-25 16:54:03 -08:00
Mouse Reeve
c67f92af46 Add editions view 2022-02-25 16:40:34 -08:00
Mouse Reeve
1d99e455e8 Adds link to add edition to editions page 2022-02-25 16:40:21 -08:00
Thomas Versteeg
8deee2220e Fix stopped reading status model in non-javascript environment 2022-02-25 22:39:42 +01:00
Mouse Reeve
ab1c7c6d0a
Merge pull request #1971 from bookwyrm-social/scroll-bug
Removes scrollIntoView script behavior for tabs
2022-02-25 13:21:20 -08:00
Thomas Versteeg
5eb113af6b Create merge migration 2022-02-25 22:03:49 +01:00
Thomas Versteeg
e9dfa42e11
Merge branch 'main' into partially-read-shelf 2022-02-25 21:00:29 +00:00
Mouse Reeve
02808f88e6 eslint fixes 2022-02-25 13:00:23 -08:00
Mouse Reeve
65bd3945e7 Prettify tabs file 2022-02-25 12:52:35 -08:00
Mouse Reeve
cec7625e1e Moves tabs script to main scripts directory
It's our own custom script at this point
2022-02-25 12:47:46 -08:00
Mouse Reeve
5d4efd457a Removes scrollIntoView script behavior for tabs 2022-02-25 12:39:44 -08:00
Mouse Reeve
ac36aa9327
Merge pull request #1969 from bookwyrm-social/author-book-sort
Sort author books by rating
2022-02-25 12:30:06 -08:00
Mouse Reeve
ad1d768be9
Merge pull request #1970 from bookwyrm-social/locales
Updates locales
2022-02-25 12:29:54 -08:00
Mouse Reeve
ee8c1659ab Updates locales 2022-02-25 12:13:20 -08:00
Mouse Reeve
8ca2b55e7e Sort author books by rating 2022-02-25 12:04:21 -08:00
Mouse Reeve
6d1d62cf2f View for starting to edit a book with existing data 2022-02-25 11:50:25 -08:00
Mouse Reeve
f1f7b21d43
Merge pull request #1968 from bookwyrm-social/automod
Automod
2022-02-24 17:51:40 -08:00
Mouse Reeve
689be8c94b Only scan local data 2022-02-24 17:42:28 -08:00
Mouse Reeve
84ef214ca1 Valid template markup 2022-02-24 17:36:49 -08:00
Mouse Reeve
eb8b9fdaed Fixes bugs in model task 2022-02-24 17:33:22 -08:00
Mouse Reeve
1aa6b99d1f Adds tests 2022-02-24 17:33:05 -08:00
Mouse Reeve
f446828175 Fixes template typo 2022-02-24 14:39:09 -08:00
Mouse Reeve
84b9a19339 Expands scanned fields 2022-02-24 13:29:17 -08:00
Mouse Reeve
93f82fbf18 Adds notifications 2022-02-24 13:20:18 -08:00
Mouse Reeve
ad41f19dc5 Updates report model 2022-02-24 13:00:41 -08:00
Mouse Reeve
3ce8b3390e Adds task 2022-02-24 12:16:18 -08:00
Mouse Reeve
e837da37db Adds task 2022-02-24 12:15:08 -08:00
Mouse Reeve
12f67dc0ce Adds automod view 2022-02-24 11:18:43 -08:00
Mouse Reeve
c8b4d5ecf1 Adds model for creating automated moderation flags 2022-02-24 09:58:37 -08:00
Mouse Reeve
20df7cbdd6
Merge pull request #1966 from bookwyrm-social/missing-tag
Adds missing tag
2022-02-21 09:24:19 -08:00
Mouse Reeve
2457315ed8 Adds missing tag 2022-02-21 08:44:04 -08:00
Mouse Reeve
70601612f8
Removes unused dependency (#1965) 2022-02-20 18:42:14 -08:00
Mouse Reeve
4672294d7c
Cache fix (#1961) 2022-02-19 16:34:48 -08:00
Mouse Reeve
c77e5a1a90
Split css (#1959)
Divides the css into sub-files and normalizes how colors are defined.

Co-authored-by: Joachim <joachim.robert@protonmail.com>
2022-02-19 16:34:17 -08:00
Mouse Reeve
6daaffeaa7
Compiles css framework from sass (#1956)
* Compiles css framework from sass

* Adds watch commands

* Copies existing css to sass file

* Moves sass out of static path

* Removes global linter

I wasn't sure how to customize this, and it's not providing a lot of
additional value on top of the domain-specific linters

* Reverts invalid change to dockerfile

* Changes stylelint path

* Remove unused bulma files

* Properly minifies generated css

* Fixes regression in thread display

* rgba function only works with percents for whatever reason

* Hush stylelint

* Removes trailing zeros

* Compile sass in Django

Co-authored-by: Joachim <joachim.robert@protonmail.com>

* Python formatting

* Updates linter

* Updates commands

* Adds css-config file

Co-authored-by: Joachim <joachim.robert@protonmail.com>

* Stylelint fix

* Removes unused compiled bulma files

Co-authored-by: Joachim <joachim.robert@protonmail.com>
2022-02-19 15:29:47 -08:00
Mouse Reeve
c5f8715c59
Merge pull request #1957 from bookwyrm-social/env-errors
Removes inline comments that are causing errors
2022-02-19 08:11:29 -08:00
Mouse Reeve
57fd675857 Ticks javscript cache buster 2022-02-19 07:52:05 -08:00
Mouse Reeve
275f3cbedb Removes inline comments that are causing erros 2022-02-18 21:07:39 -08:00
Mouse Reeve
cd65cfafce
Merge pull request #1955 from bookwyrm-social/dev-tools-tweaks
Updates dev tools
2022-02-18 09:21:45 -08:00
Mouse Reeve
7d68c23ce5 Updates dev tools 2022-02-18 08:48:44 -08:00
Mouse Reeve
1d44c2bf1f
Merge pull request #1954 from bookwyrm-social/locales
Updates locales for new view
2022-02-17 21:14:28 -08:00
Mouse Reeve
3d2ea40ad4 Updates locales for new view 2022-02-17 20:24:03 -08:00
Mouse Reeve
731ab88604
Merge pull request #1953 from bookwyrm-social/cache-translation
Fixes cache translation
2022-02-17 19:13:35 -08:00
Mouse Reeve
98736925f7 Fixes cache translation 2022-02-17 18:59:35 -08:00
Mouse Reeve
9eb798e932
Merge pull request #1952 from bookwyrm-social/settings
Fixes admin settings view
2022-02-17 18:10:17 -08:00
Mouse Reeve
edf3b61602 Show error or success states 2022-02-17 18:00:19 -08:00
Mouse Reeve
1aac665094 Fixes settings form 2022-02-17 17:50:57 -08:00
Mouse Reeve
64a83b38b5
Merge pull request #1951 from bookwyrm-social/accessibility-fixes
Accessibility fixes
2022-02-17 17:21:17 -08:00
Mouse Reeve
1b63c19a9c Fixes error in previous PR 2022-02-17 17:10:12 -08:00
Mouse Reeve
5d098b3c10 Removes duplicate selector 2022-02-17 17:01:21 -08:00
Mouse Reeve
31a61713d9 Runs stylelint fix 2022-02-17 16:53:53 -08:00
Mouse Reeve
d510299ae4 Reverts change to shelve button dropdown 2022-02-17 16:51:54 -08:00
Mouse Reeve
561eaeaf54
Merge pull request #1935 from joachimesque/accessibility/fixes
[Accessibility] Fixes
2022-02-17 16:50:32 -08:00
Mouse Reeve
98cad7c51a
Merge pull request #1950 from bookwyrm-social/release-notice
Adds notice to admin about available updates
2022-02-17 16:37:22 -08:00
Mouse Reeve
3869f0cc1a Python formatting 2022-02-17 16:28:55 -08:00
Mouse Reeve
9132c054f2 Adds notice to admin about available updates 2022-02-17 16:23:15 -08:00
Mouse Reeve
ebf905e20b
Merge pull request #1949 from bookwyrm-social/admin-setup
Flow for creating the admin account on a new instance
2022-02-17 16:04:30 -08:00
Mouse Reeve
92f3357977 Anchor link to dev chat 2022-02-17 15:53:58 -08:00
Mouse Reeve
31d362d715 Adds setup views tests 2022-02-17 14:55:48 -08:00
Mouse Reeve
3b0fc9785e Removes unused file 2022-02-17 13:31:38 -08:00
Mouse Reeve
08e378a539 Fixes register tests 2022-02-17 13:27:44 -08:00
Mouse Reeve
be479fe4cb Adds warnings about misconfigurations 2022-02-17 13:22:33 -08:00
Mouse Reeve
f6e2ec02aa Adds overview page to setup 2022-02-17 13:02:07 -08:00
Mouse Reeve
1b9688832a Don't use my name as the default flower user 2022-02-17 12:18:35 -08:00
Mouse Reeve
bf75dff338 Contextualize admin code command output 2022-02-17 11:53:29 -08:00
Mouse Reeve
637f7c9cb9 Initialize site settings in install mode 2022-02-17 11:51:48 -08:00
Mouse Reeve
679b55d9ad Updates bw-dev and adds setup command 2022-02-17 11:50:16 -08:00
Mouse Reeve
63558bb75e Python formatting 2022-02-17 11:31:52 -08:00
Mouse Reeve
54b0bf02b5
Merge pull request #1948 from bookwyrm-social/locales
Updates locales
2022-02-17 11:29:36 -08:00
Mouse Reeve
2883c42534 Disable registration by default 2022-02-17 11:27:05 -08:00
Mouse Reeve
23d0d3e2b7 Register admin user 2022-02-17 11:25:11 -08:00
Mouse Reeve
ed536e6b41 Adds command to get admin code 2022-02-17 10:59:28 -08:00
Mouse Reeve
b4e0749f73 Disallow registration in install mode and adds redirects 2022-02-17 10:52:12 -08:00
Mouse Reeve
8e3c39d319 Adds admin key field to admin user setup form 2022-02-17 10:39:08 -08:00
Mouse Reeve
4eb4efee9d Create admin account markup 2022-02-17 10:22:44 -08:00
Mouse Reeve
c31ec7dbd5 Adds setup view 2022-02-17 10:03:02 -08:00
Mouse Reeve
e9397eaedd Adds setup templates 2022-02-17 10:02:27 -08:00
Mouse Reeve
1a8f4a916e
Merge pull request #1891 from bookwyrm-social/openlibrary-author-fields
Openlibrary author fields
2022-02-17 09:00:04 -08:00
Mouse Reeve
0da759bfd5 Updates locales 2022-02-17 08:59:05 -08:00
Mouse Reeve
c04d2d285b Python formatting 2022-02-17 08:42:12 -08:00
Thomas Versteeg
d67dac4519
Merge branch 'main' into partially-read-shelf 2022-02-17 16:34:10 +00:00
Mouse Reeve
88999168bc
Merge pull request #1946 from bookwyrm-social/use-https-defualt
Sets default USE_HTTPS value based on debug
2022-02-17 08:30:49 -08:00
Mouse Reeve
a2c4dd4f9f Updates migration and database fields 2022-02-17 08:25:01 -08:00
Mouse Reeve
39691bed3a Merge branch 'main' into openlibrary-author-fields 2022-02-16 18:06:04 -08:00
Mouse Reeve
839d91e4d4
Merge pull request #1941 from bookwyrm-social/dev-tools
Adds dev tools docker image
2022-02-16 18:05:22 -08:00
Mouse Reeve
cfc1302b23 Fixes spacing 2022-02-16 17:53:03 -08:00
Mouse Reeve
954e914638 Linebreaks? who knows. 2022-02-16 17:50:30 -08:00
Mouse Reeve
7afb5bc493 Stylelint needs libraries 2022-02-16 17:47:58 -08:00
Mouse Reeve
862b6f49bd Correct npm package names 2022-02-16 17:45:04 -08:00
Mouse Reeve
4b2ac4fa10 Re-adds yarn.lock 2022-02-16 17:40:59 -08:00
Mouse Reeve
e23e108e3a Merge branch 'main' into dev-tools 2022-02-16 17:39:03 -08:00
Mouse Reeve
c054ccc84b Try ci with yarn instead of npx 2022-02-16 17:36:44 -08:00
Mouse Reeve
60b2453d4d Fixes bw-dev stylelint command 2022-02-16 17:34:14 -08:00
Mouse Reeve
9038afd7f1 Changes npm install 2022-02-16 13:16:05 -08:00
Mouse Reeve
c1853e03ab Updates stylelint command 2022-02-16 13:14:48 -08:00
Mouse Reeve
6f90c80494 Gets stylelint command working 2022-02-16 13:13:11 -08:00
Mouse Reeve
1412fa507c Gets prettier command working 2022-02-16 12:54:03 -08:00
Mouse Reeve
d593a3a503 Sets default USE_HTTPS value based on debug 2022-02-15 13:15:04 -08:00
Mouse Reeve
55177990e3 Adds stylelint-config-recommended 2022-02-15 12:47:15 -08:00
Mouse Reeve
c19b9d7575 Updates stylelintrc filename and removes yarn 2022-02-15 12:46:28 -08:00
Mouse Reeve
f28d60b94f Tries adding packages 2022-02-15 12:41:22 -08:00
Mouse Reeve
450d4cdace Try using npx instead of yarn for stylelint 2022-02-15 12:39:14 -08:00
Mouse Reeve
7ff1ad7c83 Try again with yarn added as separate step 2022-02-15 12:37:28 -08:00
Mouse Reeve
4428c0f14d Remoes yarn line 2022-02-15 12:34:28 -08:00
Mouse Reeve
09978fc195 Tries npm install with yarn 2022-02-15 12:29:59 -08:00
Mouse Reeve
714bb081ea Updates workflows 2022-02-15 12:25:35 -08:00
Mouse Reeve
a07239c6a9
Merge pull request #1933 from bookwyrm-social/announcements
Cleans up code for announcements
2022-02-15 10:40:56 -08:00
Mouse Reeve
6fa29a6293 Fixes tests 2022-02-15 10:32:04 -08:00
Vivianne Langdon
82cb170a91 Switch version of quagga to new fork quagga2 2022-02-15 02:39:16 -08:00
Vivianne Langdon
1e04385f0c Worked more on visuals of modal
Need to finish camera selection mode.
2022-02-15 02:38:57 -08:00
Thomas Versteeg
d63e5ab2d2 Fix tests 2022-02-14 18:12:08 +01:00
Willi Hohenstein
03ff8c248d Added input control and better char replacement 2022-02-14 17:38:45 +01:00
Vivianne Langdon
5ae4eb9b8f Super messy initial working version. To clean up. 2022-02-14 02:56:05 -08:00
Willi Hohenstein
0b02287378 add docstring 2022-02-13 20:49:44 +01:00
Willi Hohenstein
526a1c6ef4 removed unnecessary code 2022-02-13 20:31:06 +01:00
Willi Hohenstein
54eeeb5798 fix style to pass tests 2022-02-13 20:30:11 +01:00
Willi Hohenstein
3c05cecb50 function moved 2022-02-13 19:07:25 +01:00
Willi Hohenstein
a4b08d7213 add test with valid isbn10 2022-02-13 17:10:32 +01:00
Willi Hohenstein
5801ef011f add isbn check 2022-02-13 09:35:15 +01:00
Willi Hohenstein
27c26b4d16 add test for dashed ISBN 2022-02-13 09:34:28 +01:00
Mouse Reeve
b601ac6f91 Adds dev tools docker image 2022-02-12 14:06:18 -08:00
Mouse Reeve
1a2c85a327
Merge pull request #1938 from willhoh/main
Change cover class
2022-02-12 11:03:32 -08:00
Mouse Reeve
5df8bf03e6 Fixes black versioning error 2022-02-12 11:00:24 -08:00
Mouse Reeve
7fdf07c6ec Updates tests 2022-02-12 10:56:55 -08:00
Thomas Versteeg
c88b34814f Rename 'Partially Read' to 'Stopped Reading' 2022-02-12 19:49:54 +01:00
Willi Hohenstein
164e0686b9 add class for correct display 2022-02-12 19:35:44 +01:00
Mouse Reeve
9827cef9a9 Python formatting 2022-02-12 10:34:16 -08:00
Mouse Reeve
1761db5444
Merge pull request #1920 from bookwyrm-social/branch-convergence
Moves towards single branch setup
2022-02-12 10:31:55 -08:00
Mouse Reeve
72818d4ab2
Merge pull request #1939 from bookwyrm-social/list-bug
Fixes display name of list contributor comments
2022-02-12 10:29:41 -08:00
Mouse Reeve
a2d9bf50c5 Fixes display name of list contributor comments 2022-02-12 10:18:14 -08:00
Mouse Reeve
df0467a662
Merge pull request #1930 from bookwyrm-social/dependabot/pip/django-3.2.12
Bump django from 3.2.11 to 3.2.12
2022-02-12 10:10:38 -08:00
Mouse Reeve
6bf6b118bf
Merge pull request #1937 from bookwyrm-social/stylelint-prettier
Adds stylelint fix to bw-dev
2022-02-12 09:58:26 -08:00
Mouse Reeve
d49e9b4dcd Updates packages 2022-02-12 09:41:18 -08:00
Mouse Reeve
30ad3bba0b Suggest stylelint --fix in CI and add to bw-dev 2022-02-12 09:00:38 -08:00
Willi Hohenstein
67ea18c840 Changed cover class for proper image size in mobile 2022-02-12 17:58:36 +01:00
Joachim
c33cf60624 aria-hidden elements do not contain focusable elements
https://dequeuniversity.com/rules/axe/4.3/aria-hidden-focus
2022-02-12 16:33:16 +01:00
Joachim
7877524116 ARIA button, link, and menuitem must have an accessible name
https://dequeuniversity.com/rules/axe/4.3/aria-command-name

In this case, the menuitem wasn't displayed (because the div was hidden). I prefer not to include the menuitem in that situation.
2022-02-12 16:25:40 +01:00
Joachim
f742cc023b ARIA attributes must conform to valid values
https://dequeuniversity.com/rules/axe/4.3/aria-valid-attr-value
2022-02-12 16:18:56 +01:00
Joachim
b27b6a5980 Certain ARIA roles must contain particular children
https://dequeuniversity.com/rules/axe/4.3/aria-required-children

In order to make this work, I had to translate Bulma's style so it doesn't use `ul` and `li` anymore.

The JS code had to be adapted, I also changed `button` to `tab` (seemed like a more relevant name) and added a `scrollIntoView()` on load the active tab is always visible.
2022-02-12 16:14:35 +01:00
Joachim
bbb89605a5 Page must have one main landmark
https://dequeuniversity.com/rules/axe/4.3/landmark-one-main
2022-02-12 16:10:40 +01:00
Willi Hohenstein
6cb480d111 Merge branch 'main' of github.com:willhoh/bookwyrm 2022-02-12 15:36:30 +01:00
Thomas Versteeg
bc89dd7041 Change shelf Finished label
When the shelf is read the label is 'Finished', otherwise it's 'Until'.
2022-02-12 11:19:00 +01:00
Mouse Reeve
89de03bffe Adds breadcrumbs 2022-02-11 14:58:40 -05:00
Mouse Reeve
b95f0ed287 Use color in announcement 2022-02-11 14:43:37 -05:00
Mouse Reeve
01b52f023a Adds edit announcement view 2022-02-11 14:42:47 -05:00
Mouse Reeve
cfa91e2570 Adds color options to announcements 2022-02-11 14:00:01 -05:00
Mouse Reeve
ee23aba994 Use details and summary for announcement panel 2022-02-11 13:50:55 -05:00
Mouse Reeve
62741a5d2e Allow html in announcement header 2022-02-11 13:36:08 -05:00
Mouse Reeve
059fd84d06 Adds delete button to announcements list view
It's handy to have it there
2022-02-11 13:33:42 -05:00
Thomas Versteeg
2b27889457 Add 'Partially Read' shelf 2022-02-11 14:33:46 +01:00
dependabot[bot]
046f516091
Bump django from 3.2.11 to 3.2.12
Bumps [django](https://github.com/django/django) from 3.2.11 to 3.2.12.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.11...3.2.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-10 13:02:16 +00:00
Mouse Reeve
542957364c
Merge pull request #1929 from bookwyrm-social/link-federation
Use correct model type for federated links
2022-02-09 09:30:26 -08:00
Mouse Reeve
bd846bba34
Merge pull request #1927 from bookwyrm-social/update-command
Refresh web and celery in update command
2022-02-09 09:25:15 -08:00
Mouse Reeve
c06817e9ff Python formatting 2022-02-09 12:23:01 -05:00
Mouse Reeve
6323b0e700 Use correct model type for federated links 2022-02-09 12:20:11 -05:00
Mouse Reeve
e9e4f70ae4 Refresh web and celery in update command 2022-02-08 09:17:42 -05:00
Mouse Reeve
74fd13fb22 Consistent ordering of items in docker-compose file 2022-02-06 06:37:03 -08:00
Mouse Reeve
4b88ea142f
Merge pull request #1922 from bookwyrm-social/version
Tick version number
2022-02-04 20:21:49 -08:00
Mouse Reeve
73b611d68d Tick version number 2022-02-04 20:12:26 -08:00
Mouse Reeve
d65a80d9ea
Merge pull request #1921 from bookwyrm-social/author-page-query
Fixes author page query
2022-02-04 19:51:24 -08:00
Mouse Reeve
a73960a0da Python formatting 2022-02-04 19:44:03 -08:00
Mouse Reeve
49ceb2a978 Fixes warning in author view tests 2022-02-04 19:41:21 -08:00
Mouse Reeve
a9a6fd1242 Adds test for author page bug 2022-02-04 19:34:37 -08:00
Mouse Reeve
28a8edfdc4 Fixes author page query 2022-02-04 19:06:47 -08:00
Mouse Reeve
bc7aa91b97 Fixes pylint complaints 2022-02-04 18:50:57 -08:00
Mouse Reeve
28fe0b0bc6
Merge pull request #1919 from bookwyrm-social/locales
Updates locales
2022-02-04 18:37:05 -08:00
Mouse Reeve
7811a9920e Copy config from prod branch to main 2022-02-04 18:36:12 -08:00
Mouse Reeve
d8c3699adc Updates locales 2022-02-04 18:21:48 -08:00
Mouse Reeve
0601f68685
Merge pull request #1918 from bookwyrm-social/ports
Don't expose unnecessary ports
2022-02-04 18:12:05 -08:00
Mouse Reeve
ea035b9fbe Don't expose unnecessary ports 2022-02-04 17:58:29 -08:00
Mouse Reeve
70bd6b9a65
Merge pull request #1917 from bookwyrm-social/list-page-error
Fixes errors in how lists with notes display
2022-02-04 15:44:49 -08:00
Mouse Reeve
18768a23f3 Fixes test 2022-02-04 15:30:58 -08:00
Mouse Reeve
0c3b6e6938
Merge pull request #1900 from willhoh/main
Added check for bocked or pending domains. Fixes #1850
2022-02-04 12:11:41 -08:00
Mouse Reeve
547d246375 Fixes 500 error 2022-02-04 12:09:20 -08:00
Mouse Reeve
0683ce1c33 Proper markdown formatting 2022-02-04 12:07:26 -08:00
Mouse Reeve
fefb7e582a Fixes list note display 2022-02-04 12:02:10 -08:00
Mouse Reeve
719df5621c Unit test to catch error on list page 2022-02-04 11:58:03 -08:00
Mouse Reeve
82aacf8f2a
Update forms.py 2022-02-04 11:47:18 -08:00
Mouse Reeve
e80a4c16f0
Merge pull request #1915 from bookwyrm-social/url-validation
Adds some simple url validation
2022-02-04 08:45:43 -08:00
Willi Hohenstein
495af09c4c
Merge branch 'bookwyrm-social:main' into main 2022-02-04 14:11:50 +01:00
Willi Hohenstein
a3e2cd77a1 Merge branch 'main' of github.com:bookwyrm-social/bookwyrm 2022-02-04 14:09:15 +01:00
Mouse Reeve
da8e07057c
Merge pull request #1916 from bookwyrm-social/user-view
Fixes duplicted shelves on user view
2022-02-03 19:31:49 -08:00
Mouse Reeve
7c0d51ed14 Fixes duplicted shelves on user view 2022-02-03 18:59:08 -08:00
Mouse Reeve
3e635f497e Adds some simple url validation 2022-02-03 15:11:01 -08:00
Willi Hohenstein
58fb9ba0d4
Merge branch 'bookwyrm-social:main' into main 2022-02-03 23:55:17 +01:00
Mouse Reeve
ebc3f14f22
Merge pull request #1914 from bookwyrm-social/static-close-buttons
Show cancel buttons on modals in static mode
2022-02-03 14:08:36 -08:00
Mouse Reeve
f545184c5b
Merge pull request #1913 from bookwyrm-social/import-ui-fixes
Fixes links on import page
2022-02-03 14:01:28 -08:00
Mouse Reeve
9013b1417a Show cancel buttons on modals in static mode 2022-02-03 13:59:53 -08:00
Mouse Reeve
3b12af63b6 Fixes links on import page 2022-02-03 13:49:33 -08:00
Mouse Reeve
582b84ecaa
Merge pull request #1911 from bookwyrm-social/more-tests
Activitystreams tests
2022-02-03 13:33:15 -08:00
Mouse Reeve
8518b4f877
Merge pull request #1912 from bookwyrm-social/sanitize-tags
Adds allowlist for html attrs
2022-02-03 13:29:40 -08:00
Mouse Reeve
85aad7c219 Another sorting order error 2022-02-03 13:25:44 -08:00
Mouse Reeve
cae7191a2b Python formatting 2022-02-03 13:19:56 -08:00
Mouse Reeve
2c7a6e8518 Correct status order 2022-02-03 13:17:16 -08:00
Mouse Reeve
1f6ecc39ac Adds allowlist for html attrs 2022-02-03 13:15:06 -08:00
Mouse Reeve
7b5bee8d7b Merge branch 'main' into more-tests 2022-02-03 12:48:11 -08:00
Mouse Reeve
3b48d986d5
Merge pull request #1910 from bookwyrm-social/superlative-rating
Fixes rating in about page superlatives
2022-02-03 12:46:07 -08:00
Mouse Reeve
5a3ce5e328 Fixes rating in about page superlatives 2022-02-03 11:48:56 -08:00
Mouse Reeve
a370602903
Merge pull request #1860 from cincodenada/image-generation-add-cjk
Use Source Han Sans for preview images generation (bis)
2022-02-03 11:11:38 -08:00
Mouse Reeve
c58a3ac114
Merge branch 'main' into main 2022-02-03 10:40:27 -08:00
Mouse Reeve
e4b53266b3
Merge pull request #1909 from bookwyrm-social/list-notes-edit
Fixes add/edit notes form on list page
2022-02-03 10:39:22 -08:00
Mouse Reeve
a46ab96d9b Fixes add/edit notes form on list page 2022-02-03 10:30:30 -08:00
Mouse Reeve
baf28c523a
Merge pull request #1908 from bookwyrm-social/fix-shelf-view
Fix shelf names on books page
2022-02-03 08:58:16 -08:00
Willi Hohenstein
1b313c2b62 added check of existing url for book
sould also fix #1899
2022-02-02 22:34:30 +01:00
Willi Hohenstein
c2c33fe1e8 fixed merge conflict 2022-02-02 20:20:16 +01:00
Willi Hohenstein
4503dd6864 Merge branch 'main' of github.com:willhoh/bookwyrm 2022-02-02 19:51:23 +01:00
Willi Hohenstein
e37982d285 added domain pending or blocked check
fixes #1850
2022-02-02 19:35:26 +01:00
Joel Bradshaw
060f515aea Be even more conservative on errors
This runs at startup of anything, so we should be extra sure to not
break anything, and lots of things can go wrong downloading files from
the internet
2022-02-01 21:59:07 -08:00
Joel Bradshaw
d6abd9b32d Ensure directory exists, don't crash if we fail to write
We should be creating the directory because the static tree from the
repo isn't actually copied into the container, so we can't rely on it
existing.

And if we can't write it, we should catch that error instead of crashing
the whole thing, oops!
2022-02-01 21:45:13 -08:00
Mouse Reeve
9611815b44 Extract wikipedia and inventaire ids 2022-01-30 12:02:18 -08:00
Mouse Reeve
44dad43f36 Load new fields via connector 2022-01-30 11:41:33 -08:00
Mouse Reeve
c4b8e7949d Add more book identifier fields 2022-01-30 11:41:31 -08:00
Mouse Reeve
224dc4100a Activitstreams tests 2022-01-28 17:32:41 -08:00
Joel Bradshaw
0c53f4e003 Fix linting and formatting 2022-01-25 01:09:27 -08:00
Joel Bradshaw
a1a3aa45f4 Don't log autoload debug
This is just too much
2022-01-25 00:53:15 -08:00
Joel Bradshaw
9e6390662b Download fonts at app startup instead
We can't bake the font into the Docker image as such, because we mount
the volumes which blows away anything we have in the app tree
beforehand.

We could stash it somewhere in the image and then copy it from there on
app startup or something, but at that point we might as well just
download it as part of the app startup.
2022-01-25 00:53:01 -08:00
Joachim
766a0cc652 Fix tests 2022-01-24 23:16:15 -08:00
Joachim
6f5115c716 Use Source Han Sans for preview images generation 2022-01-24 23:16:15 -08:00
Joel Bradshaw
284eb620dd Add Source Han font for preview generation to Docker image
Include the license and a README explaining things in the repo itself.

Depending on an external source for this is intended to be temporary,
the goal is to have a Bookywrm-managed source for these, but this should
be stable enough for now.

We build it into the Dockerfile to make it available without adding it
to the git repo itself, because git history is forever and we don't want
to bake large files into the history.

Theoretically it would make sense to gate this download on the
ENABLE_PREVIEW_IMAGES environment variable, but ENV variables aren't
available at Docker image build time (for sensible reasons), so we just
unconditonally download it.

This does mean users will ultimately download it anyway, but the benefit
to doing this over adding it to the git history is that if we switch
fonts, or update this one, or change strategies altogether, this version
of the font will no longer have to be downloaded.

Additionally, the font won't be downloaded until the application is
actually built, which involves a bunch of other downloading (of Docker
images and the like), so it's a reasonable time to do it.
2022-01-24 23:16:14 -08:00
469 changed files with 38909 additions and 13339 deletions

View file

@ -41,7 +41,7 @@ REDIS_BROKER_PASSWORD=redispassword123
# Monitoring for celery
FLOWER_PORT=8888
FLOWER_USER=mouse
FLOWER_USER=admin
FLOWER_PASSWORD=changeme
# Email config
@ -89,3 +89,22 @@ PREVIEW_TEXT_COLOR=#363636
PREVIEW_IMG_WIDTH=1200
PREVIEW_IMG_HEIGHT=630
PREVIEW_DEFAULT_COVER_COLOR=#002549
# Below are example keys if you want to enable automatically
# sending telemetry to an OTLP-compatible service. Many of
# the main monitoring apps have OLTP collectors, including
# NewRelic, DataDog, and Honeycomb.io - consult their
# documentation for setup instructions, and what exactly to
# put below!
#
# Service name is an arbitrary tag that is attached to any
# data sent, used to distinguish different sources. Useful
# for sending prod and dev metrics to the same place and
# keeping them separate, for instance!
# API endpoint for your provider
OTEL_EXPORTER_OTLP_ENDPOINT=
# Any headers required, usually authentication info
OTEL_EXPORTER_OTLP_HEADERS=
# Service name to identify your app
OTEL_SERVICE_NAME=

View file

@ -1,5 +1,5 @@
# @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: Lint Frontend
name: Lint Frontend (run `./bw-dev stylelint` to fix css errors)
on:
push:
@ -8,7 +8,7 @@ on:
- '.github/workflows/**'
- 'static/**'
- '.eslintrc'
- '.stylelintrc'
- '.stylelintrc.js'
pull_request:
branches: [ main, ci, frontend ]
@ -22,17 +22,16 @@ jobs:
- uses: actions/checkout@v2
- name: Install modules
run: yarn
run: npm install stylelint stylelint-config-recommended stylelint-config-standard stylelint-order eslint
# See .stylelintignore for files that are not linted.
- name: Run stylelint
run: >
yarn stylelint bookwyrm/static/**/*.css \
--report-needless-disables \
--report-invalid-scope-disables
npx stylelint bookwyrm/static/css/*.scss bookwyrm/static/css/bookwyrm/**/*.scss \
--config dev-tools/.stylelintrc.js
# See .eslintignore for files that are not linted.
- name: Run ESLint
run: >
yarn eslint bookwyrm/static \
npx eslint bookwyrm/static \
--ext .js,.jsx,.ts,.tsx

View file

@ -1,21 +0,0 @@
# @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: Lint project globally
on:
push:
branches: [ main, ci ]
pull_request:
branches: [ main, ci ]
jobs:
lint:
name: Lint with EditorConfig.
runs-on: ubuntu-20.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: EditorConfig
uses: greut/eclint-action@v0

View file

@ -17,8 +17,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install modules
run: npm install .
run: npm install prettier
# See .stylelintignore for files that are not linted.
- name: Run Prettier
run: npx prettier --check bookwyrm/static/js/*.js

View file

@ -21,8 +21,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pylint
- name: Analysing the code with pylint
run: |
pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801
pylint bookwyrm/

7
.gitignore vendored
View file

@ -16,6 +16,9 @@
# BookWyrm
.env
/images/
bookwyrm/static/css/bookwyrm.css
bookwyrm/static/css/themes/
!bookwyrm/static/css/themes/bookwyrm-*.scss
# Testing
.coverage
@ -24,7 +27,9 @@
.idea
#Node tools
/node_modules/
node_modules/
package-lock.json
yarn.lock
#nginx
nginx/default.conf

1
.prettierignore Normal file
View file

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

6
.pylintrc Normal file
View file

@ -0,0 +1,6 @@
[MAIN]
ignore=migrations
load-plugins=pylint.extensions.no_self_use
[MESSAGES CONTROL]
disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801,C3001

View file

@ -6,6 +6,7 @@ RUN mkdir /app /app/static /app/images
WORKDIR /app
RUN apt-get update && apt-get install -y gettext libgettextpo-dev tidy && apt-get clean
COPY requirements.txt /app/
RUN pip install -r requirements.txt --no-cache-dir
RUN apt-get update && apt-get install -y gettext libgettextpo-dev tidy && apt-get clean

View file

@ -9,21 +9,18 @@ Social reading and reviewing, decentralized with ActivityPub
- [What it is and isn't](#what-it-is-and-isnt)
- [The role of federation](#the-role-of-federation)
- [Features](#features)
- [Book data](#book-data)
- [Set up Bookwyrm](#set-up-bookwyrm)
- [Set up BookWyrm](#set-up-bookwyrm)
## Joining BookWyrm
BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://docs.joinbookwyrm.com/instances.html) list.
You can request an invite by entering your email address at https://bookwyrm.social.
If you'd like to join an instance, you can check out the [instances](https://joinbookwyrm.com/instances/) list.
## Contributing
See [contributing](https://docs.joinbookwyrm.com/how-to-contribute.html) for code, translation or monetary contributions.
See [contributing](https://docs.joinbookwyrm.com/contributing.html) for code, translation or monetary contributions.
## About BookWyrm
### What it is and isn't
BookWyrm is a platform for social reading! You can use it to track what you're reading, review books, and follow your friends. It isn't primarily meant for cataloguing or as a data-source for books, but it does do both of those things to some degree.
BookWyrm is a platform for social reading. You can use it to track what you're reading, review books, and follow your friends. It isn't primarily meant for cataloguing or as a data-source for books, but it does do both of those things to some degree.
### The role of federation
BookWyrm is built on [ActivityPub](http://activitypub.rocks/). With ActivityPub, it inter-operates with different instances of BookWyrm, and other ActivityPub compliant services, like Mastodon. This means you can run an instance for your book club, and still follow your friend who posts on a server devoted to 20th century Russian speculative fiction. It also means that your friend on mastodon can read and comment on a book review that you post on your BookWyrm instance.
@ -78,8 +75,5 @@ Deployment
- [Nginx](https://nginx.org/en/) HTTP server
## Book data
The application is set up to share book and author data between instances, and get book data from arbitrary outside sources. Right now, the only connector is to OpenLibrary, but other connectors could be written.
## Set up Bookwyrm
The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up Bookwyrm in a [developer environment](https://docs.joinbookwyrm.com/developer-environment.html) or [production](https://docs.joinbookwyrm.com/installing-in-production.html).
## Set up BookWyrm
The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up BookWyrm in a [developer environment](https://docs.joinbookwyrm.com/install-dev.html) or [production](https://docs.joinbookwyrm.com/install-prod.html).

View file

@ -1,6 +1,7 @@
""" basics for an activitypub serializer """
from dataclasses import dataclass, fields, MISSING
from json import JSONEncoder
import logging
from django.apps import apps
from django.db import IntegrityError, transaction
@ -8,6 +9,8 @@ from django.db import IntegrityError, transaction
from bookwyrm.connectors import ConnectorException, get_data
from bookwyrm.tasks import app
logger = logging.getLogger(__name__)
class ActivitySerializerError(ValueError):
"""routine problems serializing activitypub json"""
@ -39,12 +42,12 @@ def naive_parse(activity_objects, activity_json, serializer=None):
activity_json["type"] = "PublicKey"
activity_type = activity_json.get("type")
if activity_type in ["Question", "Article"]:
return None
try:
serializer = activity_objects[activity_type]
except KeyError as err:
# we know this exists and that we can't handle it
if activity_type in ["Question"]:
return None
raise ActivitySerializerError(err)
return serializer(activity_objects=activity_objects, **activity_json)
@ -65,7 +68,7 @@ class ActivityObject:
try:
value = kwargs[field.name]
if value in (None, MISSING, {}):
raise KeyError()
raise KeyError("Missing required field", field.name)
try:
is_subclass = issubclass(field.type, ActivityObject)
except TypeError:
@ -227,7 +230,7 @@ def set_related_field(
model_field = getattr(model, related_field_name)
if hasattr(model_field, "activitypub_field"):
setattr(activity, getattr(model_field, "activitypub_field"), instance.remote_id)
item = activity.to_model()
item = activity.to_model(model=model)
# if the related field isn't serialized (attachments on Status), then
# we have to set it post-creation
@ -268,9 +271,9 @@ def resolve_remote_id(
try:
data = get_data(remote_id)
except ConnectorException:
raise ActivitySerializerError(
f"Could not connect to host for remote_id: {remote_id}"
)
logger.exception("Could not connect to host for remote_id: %s", remote_id)
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"):
@ -298,6 +301,7 @@ class Link(ActivityObject):
mediaType: str = None
id: str = None
attributedTo: str = None
availability: str = None
type: str = "Link"
def serialize(self, **kwargs):

View file

@ -16,6 +16,9 @@ class BookData(ActivityObject):
librarythingKey: str = None
goodreadsKey: str = None
bnfId: str = None
viaf: str = None
wikidata: str = None
asin: str = None
lastEditedBy: str = None
links: List[str] = field(default_factory=lambda: [])
fileLinks: List[str] = field(default_factory=lambda: [])
@ -27,8 +30,8 @@ class Book(BookData):
"""serializes an edition or work, abstract"""
title: str
sortTitle: str = ""
subtitle: str = ""
sortTitle: str = None
subtitle: str = None
description: str = ""
languages: List[str] = field(default_factory=lambda: [])
series: str = ""
@ -53,7 +56,6 @@ class Edition(Book):
isbn10: str = ""
isbn13: str = ""
oclcNumber: str = ""
asin: str = ""
pages: int = None
physicalFormat: str = ""
physicalFormatDetail: str = ""

View file

@ -39,4 +39,5 @@ class Person(ActivityObject):
bookwyrmUser: bool = False
manuallyApprovesFollowers: str = False
discoverable: str = False
hideFollows: str = False
type: str = "Person"

View file

@ -298,8 +298,9 @@ def add_status_on_create_command(sender, instance, created):
priority = HIGH
# check if this is an old status, de-prioritize if so
# (this will happen if federation is very slow, or, more expectedly, on csv import)
one_day = 60 * 60 * 24
if (instance.created_date - instance.published_date).seconds > one_day:
if instance.published_date < timezone.now() - timedelta(
days=1
) or instance.created_date < instance.published_date - timedelta(days=1):
priority = LOW
add_status_task.apply_async(

54
bookwyrm/apps.py Normal file
View file

@ -0,0 +1,54 @@
"""Do further startup configuration and initialization"""
import os
import urllib
import logging
from django.apps import AppConfig
from bookwyrm import settings
logger = logging.getLogger(__name__)
def download_file(url, destination):
"""Downloads a file to the given path"""
try:
# Ensure our destination directory exists
os.makedirs(os.path.dirname(destination))
with urllib.request.urlopen(url) as stream:
with open(destination, "b+w") as outfile:
outfile.write(stream.read())
except (urllib.error.HTTPError, urllib.error.URLError):
logger.info("Failed to download file %s", url)
except OSError:
logger.info("Couldn't open font file %s for writing", destination)
except: # pylint: disable=bare-except
logger.info("Unknown error in file download")
class BookwyrmConfig(AppConfig):
"""Handles additional configuration"""
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:
# pylint: disable=import-outside-toplevel
from bookwyrm.telemetry import open_telemetry
open_telemetry.instrumentDjango()
if settings.ENABLE_PREVIEW_IMAGES and settings.FONTS:
# Download any fonts that we don't have yet
logger.debug("Downloading fonts..")
for name, config in settings.FONTS.items():
font_path = os.path.join(
settings.FONT_DIR, config["directory"], config["filename"]
)
if "url" in config and not os.path.exists(font_path):
logger.info("Just a sec, downloading %s", name)
download_file(config["url"], font_path)

View file

@ -148,8 +148,8 @@ class SearchResult:
def __repr__(self):
# pylint: disable=consider-using-f-string
return "<SearchResult key={!r} title={!r} author={!r}>".format(
self.key, self.title, self.author
return "<SearchResult key={!r} title={!r} author={!r} confidence={!r}>".format(
self.key, self.title, self.author, self.confidence
)
def json(self):

View file

@ -2,6 +2,7 @@
from abc import ABC, abstractmethod
import imghdr
import logging
import re
from django.core.files.base import ContentFile
from django.db import transaction
@ -9,7 +10,7 @@ import requests
from requests.exceptions import RequestException
from bookwyrm import activitypub, models, settings
from .connector_manager import load_more_data, ConnectorException
from .connector_manager import load_more_data, ConnectorException, raise_not_valid_url
from .format_mappings import format_mappings
@ -37,62 +38,34 @@ class AbstractMinimalConnector(ABC):
for field in self_fields:
setattr(self, field, getattr(info, field))
def search(self, query, min_confidence=None, timeout=settings.QUERY_TIMEOUT):
"""free text search"""
params = {}
if min_confidence:
params["min_confidence"] = min_confidence
def get_search_url(self, query):
"""format the query url"""
# Check if the query resembles an ISBN
if maybe_isbn(query) and self.isbn_search_url and self.isbn_search_url != "":
return f"{self.isbn_search_url}{query}"
data = self.get_search_data(
f"{self.search_url}{query}",
params=params,
timeout=timeout,
)
results = []
# NOTE: previously, we tried searching isbn and if that produces no results,
# searched as free text. This, instead, only searches isbn if it's isbn-y
return f"{self.search_url}{query}"
for doc in self.parse_search_data(data)[:10]:
results.append(self.format_search_result(doc))
return results
def isbn_search(self, query, timeout=settings.QUERY_TIMEOUT):
"""isbn search"""
params = {}
data = self.get_search_data(
f"{self.isbn_search_url}{query}",
params=params,
timeout=timeout,
)
results = []
# this shouldn't be returning mutliple results, but just in case
for doc in self.parse_isbn_search_data(data)[:10]:
results.append(self.format_isbn_search_result(doc))
return results
def get_search_data(self, remote_id, **kwargs): # pylint: disable=no-self-use
"""this allows connectors to override the default behavior"""
return get_data(remote_id, **kwargs)
def process_search_response(self, query, data, min_confidence):
"""Format the search results based on the formt of the query"""
if maybe_isbn(query):
return list(self.parse_isbn_search_data(data))[:10]
return list(self.parse_search_data(data, min_confidence))[:10]
@abstractmethod
def get_or_create_book(self, remote_id):
"""pull up a book record by whatever means possible"""
@abstractmethod
def parse_search_data(self, data):
def parse_search_data(self, data, min_confidence):
"""turn the result json from a search into a list"""
@abstractmethod
def format_search_result(self, search_result):
"""create a SearchResult obj from json"""
@abstractmethod
def parse_isbn_search_data(self, data):
"""turn the result json from a search into a list"""
@abstractmethod
def format_isbn_search_result(self, search_result):
"""create a SearchResult obj from json"""
class AbstractConnector(AbstractMinimalConnector):
"""generic book data connector"""
@ -129,7 +102,7 @@ class AbstractConnector(AbstractMinimalConnector):
try:
work_data = self.get_work_from_edition_data(data)
except (KeyError, ConnectorException) as err:
logger.exception(err)
logger.info(err)
work_data = data
if not work_data or not edition_data:
@ -250,8 +223,7 @@ def dict_from_mappings(data, mappings):
def get_data(url, params=None, timeout=10):
"""wrapper for request.get"""
# check if the url is blocked
if models.FederatedServer.is_blocked(url):
raise ConnectorException(f"Attempting to load data from blocked url: {url}")
raise_not_valid_url(url)
try:
resp = requests.get(
@ -266,7 +238,7 @@ def get_data(url, params=None, timeout=10):
timeout=timeout,
)
except RequestException as err:
logger.exception(err)
logger.info(err)
raise ConnectorException(err)
if not resp.ok:
@ -274,7 +246,7 @@ def get_data(url, params=None, timeout=10):
try:
data = resp.json()
except ValueError as err:
logger.exception(err)
logger.info(err)
raise ConnectorException(err)
return data
@ -282,6 +254,7 @@ def get_data(url, params=None, timeout=10):
def get_image(url, timeout=10):
"""wrapper for requesting an image"""
raise_not_valid_url(url)
try:
resp = requests.get(
url,
@ -291,7 +264,7 @@ def get_image(url, timeout=10):
timeout=timeout,
)
except RequestException as err:
logger.exception(err)
logger.info(err)
return None, None
if not resp.ok:
@ -300,7 +273,7 @@ def get_image(url, timeout=10):
image_content = ContentFile(resp.content)
extension = imghdr.what(None, image_content.read())
if not extension:
logger.exception("File requested was not an image: %s", url)
logger.info("File requested was not an image: %s", url)
return None, None
return image_content, extension
@ -347,3 +320,9 @@ def unique_physical_format(format_text):
# try a direct match, so saving this would be redundant
return None
return format_text
def maybe_isbn(query):
"""check if a query looks like an isbn"""
isbn = re.sub(r"[\W_]", "", query) # removes filler characters
return len(isbn) in [10, 13] # ISBN10 or ISBN13

View file

@ -10,15 +10,12 @@ class Connector(AbstractMinimalConnector):
def get_or_create_book(self, remote_id):
return activitypub.resolve_remote_id(remote_id, model=models.Edition)
def parse_search_data(self, data):
return data
def format_search_result(self, search_result):
search_result["connector"] = self
return SearchResult(**search_result)
def parse_search_data(self, data, min_confidence):
for search_result in data:
search_result["connector"] = self
yield SearchResult(**search_result)
def parse_isbn_search_data(self, data):
return data
def format_isbn_search_result(self, search_result):
return self.format_search_result(search_result)
for search_result in data:
search_result["connector"] = self
yield SearchResult(**search_result)

View file

@ -1,17 +1,18 @@
""" interface with whatever connectors the app has """
from datetime import datetime
import asyncio
import importlib
import ipaddress
import logging
import re
from urllib.parse import urlparse
import aiohttp
from django.dispatch import receiver
from django.db.models import signals
from requests import HTTPError
from bookwyrm import book_search, models
from bookwyrm.settings import SEARCH_TIMEOUT
from bookwyrm.settings import SEARCH_TIMEOUT, USER_AGENT
from bookwyrm.tasks import app
logger = logging.getLogger(__name__)
@ -21,53 +22,85 @@ class ConnectorException(HTTPError):
"""when the connector can't do what was asked"""
async def get_results(session, url, min_confidence, query, connector):
"""try this specific connector"""
# pylint: disable=line-too-long
headers = {
"Accept": (
'application/json, application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'
),
"User-Agent": USER_AGENT,
}
params = {"min_confidence": min_confidence}
try:
async with session.get(url, headers=headers, params=params) as response:
if not response.ok:
logger.info("Unable to connect to %s: %s", url, response.reason)
return
try:
raw_data = await response.json()
except aiohttp.client_exceptions.ContentTypeError as err:
logger.exception(err)
return
return {
"connector": connector,
"results": connector.process_search_response(
query, raw_data, min_confidence
),
}
except asyncio.TimeoutError:
logger.info("Connection timed out for url: %s", url)
except aiohttp.ClientError as err:
logger.exception(err)
async def async_connector_search(query, items, min_confidence):
"""Try a number of requests simultaneously"""
timeout = aiohttp.ClientTimeout(total=SEARCH_TIMEOUT)
async with aiohttp.ClientSession(timeout=timeout) as session:
tasks = []
for url, connector in items:
tasks.append(
asyncio.ensure_future(
get_results(session, url, min_confidence, query, connector)
)
)
results = await asyncio.gather(*tasks)
return results
def search(query, min_confidence=0.1, return_first=False):
"""find books based on arbitary keywords"""
if not query:
return []
results = []
# Have we got a ISBN ?
isbn = re.sub(r"[\W_]", "", query)
maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13
start_time = datetime.now()
items = []
for connector in get_connectors():
result_set = None
if maybe_isbn and connector.isbn_search_url and connector.isbn_search_url != "":
# Search on ISBN
try:
result_set = connector.isbn_search(isbn)
except Exception as err: # pylint: disable=broad-except
logger.exception(err)
# if this fails, we can still try regular search
# get the search url from the connector before sending
url = connector.get_search_url(query)
try:
raise_not_valid_url(url)
except ConnectorException:
# if this URL is invalid we should skip it and move on
logger.info("Request denied to blocked domain: %s", url)
continue
items.append((url, connector))
# if no isbn search results, we fallback to generic search
if not result_set:
try:
result_set = connector.search(query, min_confidence=min_confidence)
except Exception as err: # pylint: disable=broad-except
# we don't want *any* error to crash the whole search page
logger.exception(err)
continue
if return_first and result_set:
# if we found anything, return it
return result_set[0]
if result_set:
results.append(
{
"connector": connector,
"results": result_set,
}
)
if (datetime.now() - start_time).seconds >= SEARCH_TIMEOUT:
break
# load as many results as we can
results = asyncio.run(async_connector_search(query, items, min_confidence))
results = [r for r in results if r]
if return_first:
return None
# find the best result from all the responses and return that
all_results = [r for con in results for r in con["results"]]
all_results = sorted(all_results, key=lambda r: r.confidence, reverse=True)
return all_results[0] if all_results else None
# failed requests will return None, so filter those out
return results
@ -133,3 +166,20 @@ def create_connector(sender, instance, created, *args, **kwargs):
"""create a connector to an external bookwyrm server"""
if instance.application_type == "bookwyrm":
get_or_create_connector(f"https://{instance.server_name}")
def raise_not_valid_url(url):
"""do some basic reality checks on the url"""
parsed = urlparse(url)
if not parsed.scheme in ["http", "https"]:
raise ConnectorException("Invalid scheme: ", url)
try:
ipaddress.ip_address(parsed.netloc)
raise ConnectorException("Provided url is an IP address: ", url)
except ValueError:
# it's not an IP address, which is good
pass
if models.FederatedServer.is_blocked(url):
raise ConnectorException(f"Attempting to load data from blocked url: {url}")

View file

@ -77,53 +77,42 @@ class Connector(AbstractConnector):
**{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks", "type"]},
}
def search(self, query, min_confidence=None): # pylint: disable=arguments-differ
"""overrides default search function with confidence ranking"""
results = super().search(query)
if min_confidence:
# filter the search results after the fact
return [r for r in results if r.confidence >= min_confidence]
return results
def parse_search_data(self, data):
return data.get("results")
def format_search_result(self, search_result):
images = search_result.get("image")
cover = f"{self.covers_url}/img/entities/{images[0]}" if images else None
# a deeply messy translation of inventaire's scores
confidence = float(search_result.get("_score", 0.1))
confidence = 0.1 if confidence < 150 else 0.999
return SearchResult(
title=search_result.get("label"),
key=self.get_remote_id(search_result.get("uri")),
author=search_result.get("description"),
view_link=f"{self.base_url}/entity/{search_result.get('uri')}",
cover=cover,
confidence=confidence,
connector=self,
)
def parse_search_data(self, data, min_confidence):
for search_result in data.get("results", []):
images = search_result.get("image")
cover = f"{self.covers_url}/img/entities/{images[0]}" if images else None
# a deeply messy translation of inventaire's scores
confidence = float(search_result.get("_score", 0.1))
confidence = 0.1 if confidence < 150 else 0.999
if confidence < min_confidence:
continue
yield SearchResult(
title=search_result.get("label"),
key=self.get_remote_id(search_result.get("uri")),
author=search_result.get("description"),
view_link=f"{self.base_url}/entity/{search_result.get('uri')}",
cover=cover,
confidence=confidence,
connector=self,
)
def parse_isbn_search_data(self, data):
"""got some daaaata"""
results = data.get("entities")
if not results:
return []
return list(results.values())
def format_isbn_search_result(self, search_result):
"""totally different format than a regular search result"""
title = search_result.get("claims", {}).get("wdt:P1476", [])
if not title:
return None
return SearchResult(
title=title[0],
key=self.get_remote_id(search_result.get("uri")),
author=search_result.get("description"),
view_link=f"{self.base_url}/entity/{search_result.get('uri')}",
cover=self.get_cover_url(search_result.get("image")),
connector=self,
)
return
for search_result in list(results.values()):
title = search_result.get("claims", {}).get("wdt:P1476", [])
if not title:
continue
yield SearchResult(
title=title[0],
key=self.get_remote_id(search_result.get("uri")),
author=search_result.get("description"),
view_link=f"{self.base_url}/entity/{search_result.get('uri')}",
cover=self.get_cover_url(search_result.get("image")),
connector=self,
)
def is_work_data(self, data):
return data.get("type") == "work"

View file

@ -68,7 +68,30 @@ class Connector(AbstractConnector):
Mapping("born", remote_field="birth_date"),
Mapping("died", remote_field="death_date"),
Mapping("bio", formatter=get_description),
Mapping("isni", remote_field="remote_ids", formatter=get_isni),
Mapping(
"isni",
remote_field="remote_ids",
formatter=lambda b: get_dict_field(b, "isni"),
),
Mapping(
"asin",
remote_field="remote_ids",
formatter=lambda b: get_dict_field(b, "amazon"),
),
Mapping(
"viaf",
remote_field="remote_ids",
formatter=lambda b: get_dict_field(b, "viaf"),
),
Mapping(
"wikidata",
remote_field="remote_ids",
formatter=lambda b: get_dict_field(b, "wikidata"),
),
Mapping(
"wikipedia_link", remote_field="links", formatter=get_wikipedia_link
),
Mapping("inventaire_id", remote_field="links", formatter=get_inventaire_id),
]
def get_book_data(self, remote_id):
@ -129,39 +152,41 @@ class Connector(AbstractConnector):
image_name = f"{cover_id}-{size}.jpg"
return f"{self.covers_url}/b/id/{image_name}"
def parse_search_data(self, data):
return data.get("docs")
def parse_search_data(self, data, min_confidence):
for idx, search_result in enumerate(data.get("docs")):
# build the remote id from the openlibrary key
key = self.books_url + search_result["key"]
author = search_result.get("author_name") or ["Unknown"]
cover_blob = search_result.get("cover_i")
cover = self.get_cover_url([cover_blob], size="M") if cover_blob else None
def format_search_result(self, search_result):
# build the remote id from the openlibrary key
key = self.books_url + search_result["key"]
author = search_result.get("author_name") or ["Unknown"]
cover_blob = search_result.get("cover_i")
cover = self.get_cover_url([cover_blob], size="M") if cover_blob else None
return SearchResult(
title=search_result.get("title"),
key=key,
author=", ".join(author),
connector=self,
year=search_result.get("first_publish_year"),
cover=cover,
)
# OL doesn't provide confidence, but it does sort by an internal ranking, so
# this confidence value is relative to the list position
confidence = 1 / (idx + 1)
yield SearchResult(
title=search_result.get("title"),
key=key,
author=", ".join(author),
connector=self,
year=search_result.get("first_publish_year"),
cover=cover,
confidence=confidence,
)
def parse_isbn_search_data(self, data):
return list(data.values())
def format_isbn_search_result(self, search_result):
# build the remote id from the openlibrary key
key = self.books_url + search_result["key"]
authors = search_result.get("authors") or [{"name": "Unknown"}]
author_names = [author.get("name") for author in authors]
return SearchResult(
title=search_result.get("title"),
key=key,
author=", ".join(author_names),
connector=self,
year=search_result.get("publish_date"),
)
for search_result in list(data.values()):
# build the remote id from the openlibrary key
key = self.books_url + search_result["key"]
authors = search_result.get("authors") or [{"name": "Unknown"}]
author_names = [author.get("name") for author in authors]
yield SearchResult(
title=search_result.get("title"),
key=key,
author=", ".join(author_names),
connector=self,
year=search_result.get("publish_date"),
)
def load_edition_data(self, olkey):
"""query openlibrary for editions of a work"""
@ -227,11 +252,38 @@ def get_languages(language_blob):
return langs
def get_isni(remote_ids_blob):
def get_dict_field(blob, field_name):
"""extract the isni from the remote id data for the author"""
if not remote_ids_blob or not isinstance(remote_ids_blob, dict):
if not blob or not isinstance(blob, dict):
return None
return remote_ids_blob.get("isni")
return blob.get(field_name)
def get_wikipedia_link(links):
"""extract wikipedia links"""
if not isinstance(links, list):
return None
for link in links:
if not isinstance(link, dict):
continue
if link.get("title") == "wikipedia":
return link.get("url")
return None
def get_inventaire_id(links):
"""extract and format inventaire ids"""
if not isinstance(links, list):
return None
for link in links:
if not isinstance(link, dict):
continue
if link.get("title") == "inventaire.io":
iv_link = link.get("url")
return iv_link.split("/")[-1]
return None
def pick_default_edition(options):

View file

@ -8,8 +8,20 @@ def site_settings(request): # pylint: disable=unused-argument
if not request.is_secure():
request_protocol = "http://"
site = models.SiteSettings.objects.get()
theme = "css/themes/bookwyrm-light.scss"
if (
hasattr(request, "user")
and request.user.is_authenticated
and request.user.theme
):
theme = request.user.theme.path
elif site.default_theme:
theme = site.default_theme.path
return {
"site": models.SiteSettings.objects.get(),
"site": site,
"site_theme": theme,
"active_announcements": models.Announcement.active_announcements(),
"thumbnail_generation_enabled": settings.ENABLE_THUMBNAIL_GENERATION,
"media_full_url": settings.MEDIA_FULL_URL,

View file

@ -1,516 +0,0 @@
""" using django model forms """
import datetime
from collections import defaultdict
from django import forms
from django.forms import ModelForm, PasswordInput, widgets, ChoiceField
from django.forms.widgets import Textarea
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from bookwyrm import models
from bookwyrm.models.fields import ClearableFileInputWithWarning
from bookwyrm.models.user import FeedFilterChoices
class CustomForm(ModelForm):
"""add css classes to the forms"""
def __init__(self, *args, **kwargs):
css_classes = defaultdict(lambda: "")
css_classes["text"] = "input"
css_classes["password"] = "input"
css_classes["email"] = "input"
css_classes["number"] = "input"
css_classes["checkbox"] = "checkbox"
css_classes["textarea"] = "textarea"
# pylint: disable=super-with-arguments
super(CustomForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
if hasattr(visible.field.widget, "input_type"):
input_type = visible.field.widget.input_type
if isinstance(visible.field.widget, Textarea):
input_type = "textarea"
visible.field.widget.attrs["rows"] = 5
visible.field.widget.attrs["class"] = css_classes[input_type]
# pylint: disable=missing-class-docstring
class LoginForm(CustomForm):
class Meta:
model = models.User
fields = ["localname", "password"]
help_texts = {f: None for f in fields}
widgets = {
"password": PasswordInput(),
}
class RegisterForm(CustomForm):
class Meta:
model = models.User
fields = ["localname", "email", "password"]
help_texts = {f: None for f in fields}
widgets = {"password": PasswordInput()}
class RatingForm(CustomForm):
class Meta:
model = models.ReviewRating
fields = ["user", "book", "rating", "privacy"]
class ReviewForm(CustomForm):
class Meta:
model = models.Review
fields = [
"user",
"book",
"name",
"content",
"rating",
"content_warning",
"sensitive",
"privacy",
]
class CommentForm(CustomForm):
class Meta:
model = models.Comment
fields = [
"user",
"book",
"content",
"content_warning",
"sensitive",
"privacy",
"progress",
"progress_mode",
"reading_status",
]
class QuotationForm(CustomForm):
class Meta:
model = models.Quotation
fields = [
"user",
"book",
"quote",
"content",
"content_warning",
"sensitive",
"privacy",
"position",
"position_mode",
]
class ReplyForm(CustomForm):
class Meta:
model = models.Status
fields = [
"user",
"content",
"content_warning",
"sensitive",
"reply_parent",
"privacy",
]
class StatusForm(CustomForm):
class Meta:
model = models.Status
fields = ["user", "content", "content_warning", "sensitive", "privacy"]
class DirectForm(CustomForm):
class Meta:
model = models.Status
fields = ["user", "content", "content_warning", "sensitive", "privacy"]
class EditUserForm(CustomForm):
class Meta:
model = models.User
fields = [
"avatar",
"name",
"email",
"summary",
"show_goal",
"show_suggested_users",
"manually_approves_followers",
"default_post_privacy",
"discoverable",
"preferred_timezone",
"preferred_language",
]
help_texts = {f: None for f in fields}
widgets = {
"avatar": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_avatar"}
),
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"summary": forms.Textarea(attrs={"aria-describedby": "desc_summary"}),
"email": forms.EmailInput(attrs={"aria-describedby": "desc_email"}),
"discoverable": forms.CheckboxInput(
attrs={"aria-describedby": "desc_discoverable"}
),
}
class LimitedEditUserForm(CustomForm):
class Meta:
model = models.User
fields = [
"avatar",
"name",
"summary",
"manually_approves_followers",
"discoverable",
]
help_texts = {f: None for f in fields}
widgets = {
"avatar": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_avatar"}
),
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"summary": forms.Textarea(attrs={"aria-describedby": "desc_summary"}),
"discoverable": forms.CheckboxInput(
attrs={"aria-describedby": "desc_discoverable"}
),
}
class DeleteUserForm(CustomForm):
class Meta:
model = models.User
fields = ["password"]
class UserGroupForm(CustomForm):
class Meta:
model = models.User
fields = ["groups"]
class FeedStatusTypesForm(CustomForm):
class Meta:
model = models.User
fields = ["feed_status_types"]
help_texts = {f: None for f in fields}
widgets = {
"feed_status_types": widgets.CheckboxSelectMultiple(
choices=FeedFilterChoices,
),
}
class CoverForm(CustomForm):
class Meta:
model = models.Book
fields = ["cover"]
help_texts = {f: None for f in fields}
class LinkDomainForm(CustomForm):
class Meta:
model = models.LinkDomain
fields = ["name"]
class FileLinkForm(CustomForm):
class Meta:
model = models.FileLink
fields = ["url", "filetype", "availability", "book", "added_by"]
class EditionForm(CustomForm):
class Meta:
model = models.Edition
exclude = [
"remote_id",
"origin_id",
"created_date",
"updated_date",
"edition_rank",
"authors",
"parent_work",
"shelves",
"connector",
"search_vector",
"links",
"file_links",
]
widgets = {
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
"subtitle": forms.TextInput(attrs={"aria-describedby": "desc_subtitle"}),
"description": forms.Textarea(
attrs={"aria-describedby": "desc_description"}
),
"series": forms.TextInput(attrs={"aria-describedby": "desc_series"}),
"series_number": forms.TextInput(
attrs={"aria-describedby": "desc_series_number"}
),
"languages": forms.TextInput(
attrs={"aria-describedby": "desc_languages_help desc_languages"}
),
"publishers": forms.TextInput(
attrs={"aria-describedby": "desc_publishers_help desc_publishers"}
),
"first_published_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_first_published_date"}
),
"published_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_published_date"}
),
"cover": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_cover"}
),
"physical_format": forms.Select(
attrs={"aria-describedby": "desc_physical_format"}
),
"physical_format_detail": forms.TextInput(
attrs={"aria-describedby": "desc_physical_format_detail"}
),
"pages": forms.NumberInput(attrs={"aria-describedby": "desc_pages"}),
"isbn_13": forms.TextInput(attrs={"aria-describedby": "desc_isbn_13"}),
"isbn_10": forms.TextInput(attrs={"aria-describedby": "desc_isbn_10"}),
"openlibrary_key": forms.TextInput(
attrs={"aria-describedby": "desc_openlibrary_key"}
),
"inventaire_id": forms.TextInput(
attrs={"aria-describedby": "desc_inventaire_id"}
),
"oclc_number": forms.TextInput(
attrs={"aria-describedby": "desc_oclc_number"}
),
"ASIN": forms.TextInput(attrs={"aria-describedby": "desc_ASIN"}),
}
class AuthorForm(CustomForm):
class Meta:
model = models.Author
fields = [
"last_edited_by",
"name",
"aliases",
"bio",
"wikipedia_link",
"born",
"died",
"openlibrary_key",
"inventaire_id",
"librarything_key",
"goodreads_key",
"isni",
]
widgets = {
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"aliases": forms.TextInput(attrs={"aria-describedby": "desc_aliases"}),
"bio": forms.Textarea(attrs={"aria-describedby": "desc_bio"}),
"wikipedia_link": forms.TextInput(
attrs={"aria-describedby": "desc_wikipedia_link"}
),
"born": forms.SelectDateWidget(attrs={"aria-describedby": "desc_born"}),
"died": forms.SelectDateWidget(attrs={"aria-describedby": "desc_died"}),
"oepnlibrary_key": forms.TextInput(
attrs={"aria-describedby": "desc_oepnlibrary_key"}
),
"inventaire_id": forms.TextInput(
attrs={"aria-describedby": "desc_inventaire_id"}
),
"librarything_key": forms.TextInput(
attrs={"aria-describedby": "desc_librarything_key"}
),
"goodreads_key": forms.TextInput(
attrs={"aria-describedby": "desc_goodreads_key"}
),
}
class ImportForm(forms.Form):
csv_file = forms.FileField()
class ExpiryWidget(widgets.Select):
def value_from_datadict(self, data, files, name):
"""human-readable exiration time buckets"""
selected_string = super().value_from_datadict(data, files, name)
if selected_string == "day":
interval = datetime.timedelta(days=1)
elif selected_string == "week":
interval = datetime.timedelta(days=7)
elif selected_string == "month":
interval = datetime.timedelta(days=31) # Close enough?
elif selected_string == "forever":
return None
else:
return selected_string # This will raise
return timezone.now() + interval
class InviteRequestForm(CustomForm):
def clean(self):
"""make sure the email isn't in use by a registered user"""
cleaned_data = super().clean()
email = cleaned_data.get("email")
if email and models.User.objects.filter(email=email).exists():
self.add_error("email", _("A user with this email already exists."))
class Meta:
model = models.InviteRequest
fields = ["email"]
class CreateInviteForm(CustomForm):
class Meta:
model = models.SiteInvite
exclude = ["code", "user", "times_used", "invitees"]
widgets = {
"expiry": ExpiryWidget(
choices=[
("day", _("One Day")),
("week", _("One Week")),
("month", _("One Month")),
("forever", _("Does Not Expire")),
]
),
"use_limit": widgets.Select(
choices=[(i, _(f"{i} uses")) for i in [1, 5, 10, 25, 50, 100]]
+ [(None, _("Unlimited"))]
),
}
class ShelfForm(CustomForm):
class Meta:
model = models.Shelf
fields = ["user", "name", "privacy", "description"]
class GoalForm(CustomForm):
class Meta:
model = models.AnnualGoal
fields = ["user", "year", "goal", "privacy"]
class SiteForm(CustomForm):
class Meta:
model = models.SiteSettings
exclude = []
widgets = {
"instance_short_description": forms.TextInput(
attrs={"aria-describedby": "desc_instance_short_description"}
),
"require_confirm_email": forms.CheckboxInput(
attrs={"aria-describedby": "desc_require_confirm_email"}
),
"invite_request_text": forms.Textarea(
attrs={"aria-describedby": "desc_invite_request_text"}
),
}
class AnnouncementForm(CustomForm):
class Meta:
model = models.Announcement
exclude = ["remote_id"]
widgets = {
"preview": forms.TextInput(attrs={"aria-describedby": "desc_preview"}),
"content": forms.Textarea(attrs={"aria-describedby": "desc_content"}),
"event_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_event_date"}
),
"start_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_start_date"}
),
"end_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_end_date"}
),
"active": forms.CheckboxInput(attrs={"aria-describedby": "desc_active"}),
}
class ListForm(CustomForm):
class Meta:
model = models.List
fields = ["user", "name", "description", "curation", "privacy", "group"]
class ListItemForm(CustomForm):
class Meta:
model = models.ListItem
fields = ["user", "book", "book_list", "notes"]
class GroupForm(CustomForm):
class Meta:
model = models.Group
fields = ["user", "privacy", "name", "description"]
class ReportForm(CustomForm):
class Meta:
model = models.Report
fields = ["user", "reporter", "statuses", "links", "note"]
class EmailBlocklistForm(CustomForm):
class Meta:
model = models.EmailBlocklist
fields = ["domain"]
widgets = {
"avatar": forms.TextInput(attrs={"aria-describedby": "desc_domain"}),
}
class IPBlocklistForm(CustomForm):
class Meta:
model = models.IPBlocklist
fields = ["address"]
class ServerForm(CustomForm):
class Meta:
model = models.FederatedServer
exclude = ["remote_id"]
class SortListForm(forms.Form):
sort_by = ChoiceField(
choices=(
("order", _("List Order")),
("title", _("Book Title")),
("rating", _("Rating")),
),
label=_("Sort By"),
)
direction = ChoiceField(
choices=(
("ascending", _("Ascending")),
("descending", _("Descending")),
),
)
class ReadThroughForm(CustomForm):
def clean(self):
"""make sure the email isn't in use by a registered user"""
cleaned_data = super().clean()
start_date = cleaned_data.get("start_date")
finish_date = cleaned_data.get("finish_date")
if start_date and finish_date and start_date > finish_date:
self.add_error(
"finish_date", _("Reading finish date cannot be before start date.")
)
class Meta:
model = models.ReadThrough
fields = ["user", "book", "start_date", "finish_date"]

View file

@ -0,0 +1,12 @@
""" make forms available to the app """
# site admin
from .admin import *
from .author import *
from .books import *
from .edit_user import *
from .forms import *
from .groups import *
from .landing import *
from .links import *
from .lists import *
from .status import *

141
bookwyrm/forms/admin.py Normal file
View file

@ -0,0 +1,141 @@
""" using django model forms """
import datetime
from django import forms
from django.forms import widgets
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django_celery_beat.models import IntervalSchedule
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class ExpiryWidget(widgets.Select):
def value_from_datadict(self, data, files, name):
"""human-readable exiration time buckets"""
selected_string = super().value_from_datadict(data, files, name)
if selected_string == "day":
interval = datetime.timedelta(days=1)
elif selected_string == "week":
interval = datetime.timedelta(days=7)
elif selected_string == "month":
interval = datetime.timedelta(days=31) # Close enough?
elif selected_string == "forever":
return None
else:
return selected_string # This will raise
return timezone.now() + interval
class CreateInviteForm(CustomForm):
class Meta:
model = models.SiteInvite
exclude = ["code", "user", "times_used", "invitees"]
widgets = {
"expiry": ExpiryWidget(
choices=[
("day", _("One Day")),
("week", _("One Week")),
("month", _("One Month")),
("forever", _("Does Not Expire")),
]
),
"use_limit": widgets.Select(
choices=[(i, _(f"{i} uses")) for i in [1, 5, 10, 25, 50, 100]]
+ [(None, _("Unlimited"))]
),
}
class SiteForm(CustomForm):
class Meta:
model = models.SiteSettings
exclude = ["admin_code", "install_mode"]
widgets = {
"instance_short_description": forms.TextInput(
attrs={"aria-describedby": "desc_instance_short_description"}
),
"require_confirm_email": forms.CheckboxInput(
attrs={"aria-describedby": "desc_require_confirm_email"}
),
"invite_request_text": forms.Textarea(
attrs={"aria-describedby": "desc_invite_request_text"}
),
}
class ThemeForm(CustomForm):
class Meta:
model = models.Theme
fields = ["name", "path"]
widgets = {
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"path": forms.TextInput(
attrs={
"aria-describedby": "desc_path",
"placeholder": "css/themes/theme-name.scss",
}
),
}
class AnnouncementForm(CustomForm):
class Meta:
model = models.Announcement
exclude = ["remote_id"]
widgets = {
"preview": forms.TextInput(attrs={"aria-describedby": "desc_preview"}),
"content": forms.Textarea(attrs={"aria-describedby": "desc_content"}),
"event_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_event_date"}
),
"start_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_start_date"}
),
"end_date": forms.SelectDateWidget(
attrs={"aria-describedby": "desc_end_date"}
),
"active": forms.CheckboxInput(attrs={"aria-describedby": "desc_active"}),
}
class EmailBlocklistForm(CustomForm):
class Meta:
model = models.EmailBlocklist
fields = ["domain"]
widgets = {
"avatar": forms.TextInput(attrs={"aria-describedby": "desc_domain"}),
}
class IPBlocklistForm(CustomForm):
class Meta:
model = models.IPBlocklist
fields = ["address"]
class ServerForm(CustomForm):
class Meta:
model = models.FederatedServer
exclude = ["remote_id"]
class AutoModRuleForm(CustomForm):
class Meta:
model = models.AutoMod
fields = ["string_match", "flag_users", "flag_statuses", "created_by"]
class IntervalScheduleForm(CustomForm):
class Meta:
model = IntervalSchedule
fields = ["every", "period"]
widgets = {
"every": forms.NumberInput(attrs={"aria-describedby": "desc_every"}),
"period": forms.Select(attrs={"aria-describedby": "desc_period"}),
}

47
bookwyrm/forms/author.py Normal file
View file

@ -0,0 +1,47 @@
""" using django model forms """
from django import forms
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class AuthorForm(CustomForm):
class Meta:
model = models.Author
fields = [
"last_edited_by",
"name",
"aliases",
"bio",
"wikipedia_link",
"born",
"died",
"openlibrary_key",
"inventaire_id",
"librarything_key",
"goodreads_key",
"isni",
]
widgets = {
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"aliases": forms.TextInput(attrs={"aria-describedby": "desc_aliases"}),
"bio": forms.Textarea(attrs={"aria-describedby": "desc_bio"}),
"wikipedia_link": forms.TextInput(
attrs={"aria-describedby": "desc_wikipedia_link"}
),
"born": forms.SelectDateWidget(attrs={"aria-describedby": "desc_born"}),
"died": forms.SelectDateWidget(attrs={"aria-describedby": "desc_died"}),
"oepnlibrary_key": forms.TextInput(
attrs={"aria-describedby": "desc_oepnlibrary_key"}
),
"inventaire_id": forms.TextInput(
attrs={"aria-describedby": "desc_inventaire_id"}
),
"librarything_key": forms.TextInput(
attrs={"aria-describedby": "desc_librarything_key"}
),
"goodreads_key": forms.TextInput(
attrs={"aria-describedby": "desc_goodreads_key"}
),
}

104
bookwyrm/forms/books.py Normal file
View file

@ -0,0 +1,104 @@
""" using django model forms """
from django import forms
from bookwyrm import models
from bookwyrm.models.fields import ClearableFileInputWithWarning
from .custom_form import CustomForm
from .widgets import ArrayWidget, SelectDateWidget, Select
# pylint: disable=missing-class-docstring
class CoverForm(CustomForm):
class Meta:
model = models.Book
fields = ["cover"]
help_texts = {f: None for f in fields}
class EditionForm(CustomForm):
class Meta:
model = models.Edition
exclude = [
"remote_id",
"origin_id",
"created_date",
"updated_date",
"edition_rank",
"authors",
"parent_work",
"shelves",
"connector",
"search_vector",
"links",
"file_links",
]
widgets = {
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
"subtitle": forms.TextInput(attrs={"aria-describedby": "desc_subtitle"}),
"description": forms.Textarea(
attrs={"aria-describedby": "desc_description"}
),
"series": forms.TextInput(attrs={"aria-describedby": "desc_series"}),
"series_number": forms.TextInput(
attrs={"aria-describedby": "desc_series_number"}
),
"subjects": ArrayWidget(),
"languages": forms.TextInput(
attrs={"aria-describedby": "desc_languages_help desc_languages"}
),
"publishers": forms.TextInput(
attrs={"aria-describedby": "desc_publishers_help desc_publishers"}
),
"first_published_date": SelectDateWidget(
attrs={"aria-describedby": "desc_first_published_date"}
),
"published_date": SelectDateWidget(
attrs={"aria-describedby": "desc_published_date"}
),
"cover": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_cover"}
),
"physical_format": Select(
attrs={"aria-describedby": "desc_physical_format"}
),
"physical_format_detail": forms.TextInput(
attrs={"aria-describedby": "desc_physical_format_detail"}
),
"pages": forms.NumberInput(attrs={"aria-describedby": "desc_pages"}),
"isbn_13": forms.TextInput(attrs={"aria-describedby": "desc_isbn_13"}),
"isbn_10": forms.TextInput(attrs={"aria-describedby": "desc_isbn_10"}),
"openlibrary_key": forms.TextInput(
attrs={"aria-describedby": "desc_openlibrary_key"}
),
"inventaire_id": forms.TextInput(
attrs={"aria-describedby": "desc_inventaire_id"}
),
"oclc_number": forms.TextInput(
attrs={"aria-describedby": "desc_oclc_number"}
),
"ASIN": forms.TextInput(attrs={"aria-describedby": "desc_ASIN"}),
}
class EditionFromWorkForm(CustomForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# make all fields hidden
for visible in self.visible_fields():
visible.field.widget = forms.HiddenInput()
class Meta:
model = models.Work
fields = [
"title",
"subtitle",
"authors",
"description",
"languages",
"series",
"series_number",
"subjects",
"subject_places",
"cover",
"first_published_date",
]

View file

@ -0,0 +1,26 @@
""" Overrides django's default form class """
from collections import defaultdict
from django.forms import ModelForm
from django.forms.widgets import Textarea
class CustomForm(ModelForm):
"""add css classes to the forms"""
def __init__(self, *args, **kwargs):
css_classes = defaultdict(lambda: "")
css_classes["text"] = "input"
css_classes["password"] = "input"
css_classes["email"] = "input"
css_classes["number"] = "input"
css_classes["checkbox"] = "checkbox"
css_classes["textarea"] = "textarea"
# pylint: disable=super-with-arguments
super(CustomForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
if hasattr(visible.field.widget, "input_type"):
input_type = visible.field.widget.input_type
if isinstance(visible.field.widget, Textarea):
input_type = "textarea"
visible.field.widget.attrs["rows"] = 5
visible.field.widget.attrs["class"] = css_classes[input_type]

View file

@ -0,0 +1,68 @@
""" using django model forms """
from django import forms
from bookwyrm import models
from bookwyrm.models.fields import ClearableFileInputWithWarning
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class EditUserForm(CustomForm):
class Meta:
model = models.User
fields = [
"avatar",
"name",
"email",
"summary",
"show_goal",
"show_suggested_users",
"manually_approves_followers",
"default_post_privacy",
"discoverable",
"hide_follows",
"preferred_timezone",
"preferred_language",
"theme",
]
help_texts = {f: None for f in fields}
widgets = {
"avatar": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_avatar"}
),
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"summary": forms.Textarea(attrs={"aria-describedby": "desc_summary"}),
"email": forms.EmailInput(attrs={"aria-describedby": "desc_email"}),
"discoverable": forms.CheckboxInput(
attrs={"aria-describedby": "desc_discoverable"}
),
}
class LimitedEditUserForm(CustomForm):
class Meta:
model = models.User
fields = [
"avatar",
"name",
"summary",
"manually_approves_followers",
"discoverable",
]
help_texts = {f: None for f in fields}
widgets = {
"avatar": ClearableFileInputWithWarning(
attrs={"aria-describedby": "desc_avatar"}
),
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
"summary": forms.Textarea(attrs={"aria-describedby": "desc_summary"}),
"discoverable": forms.CheckboxInput(
attrs={"aria-describedby": "desc_discoverable"}
),
}
class DeleteUserForm(CustomForm):
class Meta:
model = models.User
fields = ["password"]

64
bookwyrm/forms/forms.py Normal file
View file

@ -0,0 +1,64 @@
""" using django model forms """
from django import forms
from django.forms import widgets
from django.utils.translation import gettext_lazy as _
from bookwyrm import models
from bookwyrm.models.user import FeedFilterChoices
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class FeedStatusTypesForm(CustomForm):
class Meta:
model = models.User
fields = ["feed_status_types"]
help_texts = {f: None for f in fields}
widgets = {
"feed_status_types": widgets.CheckboxSelectMultiple(
choices=FeedFilterChoices,
),
}
class ImportForm(forms.Form):
csv_file = forms.FileField()
class ShelfForm(CustomForm):
class Meta:
model = models.Shelf
fields = ["user", "name", "privacy", "description"]
class GoalForm(CustomForm):
class Meta:
model = models.AnnualGoal
fields = ["user", "year", "goal", "privacy"]
class ReportForm(CustomForm):
class Meta:
model = models.Report
fields = ["user", "reporter", "status", "links", "note"]
class ReadThroughForm(CustomForm):
def clean(self):
"""don't let readthroughs end before they start"""
cleaned_data = super().clean()
start_date = cleaned_data.get("start_date")
finish_date = cleaned_data.get("finish_date")
if start_date and finish_date and start_date > finish_date:
self.add_error(
"finish_date", _("Reading finish date cannot be before start date.")
)
stopped_date = cleaned_data.get("stopped_date")
if start_date and stopped_date and start_date > stopped_date:
self.add_error(
"stopped_date", _("Reading stopped date cannot be before start date.")
)
class Meta:
model = models.ReadThrough
fields = ["user", "book", "start_date", "finish_date", "stopped_date"]

16
bookwyrm/forms/groups.py Normal file
View file

@ -0,0 +1,16 @@
""" using django model forms """
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class UserGroupForm(CustomForm):
class Meta:
model = models.User
fields = ["groups"]
class GroupForm(CustomForm):
class Meta:
model = models.Group
fields = ["user", "privacy", "name", "description"]

45
bookwyrm/forms/landing.py Normal file
View file

@ -0,0 +1,45 @@
""" Forms for the landing pages """
from django.forms import PasswordInput
from django.utils.translation import gettext_lazy as _
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class LoginForm(CustomForm):
class Meta:
model = models.User
fields = ["localname", "password"]
help_texts = {f: None for f in fields}
widgets = {
"password": PasswordInput(),
}
class RegisterForm(CustomForm):
class Meta:
model = models.User
fields = ["localname", "email", "password"]
help_texts = {f: None for f in fields}
widgets = {"password": PasswordInput()}
def clean(self):
"""Check if the username is taken"""
cleaned_data = super().clean()
localname = cleaned_data.get("localname").strip()
if models.User.objects.filter(localname=localname).first():
self.add_error("localname", _("User with this username already exists"))
class InviteRequestForm(CustomForm):
def clean(self):
"""make sure the email isn't in use by a registered user"""
cleaned_data = super().clean()
email = cleaned_data.get("email")
if email and models.User.objects.filter(email=email).exists():
self.add_error("email", _("A user with this email already exists."))
class Meta:
model = models.InviteRequest
fields = ["email", "answer"]

48
bookwyrm/forms/links.py Normal file
View file

@ -0,0 +1,48 @@
""" using django model forms """
from urllib.parse import urlparse
from django.utils.translation import gettext_lazy as _
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class LinkDomainForm(CustomForm):
class Meta:
model = models.LinkDomain
fields = ["name"]
class FileLinkForm(CustomForm):
class Meta:
model = models.FileLink
fields = ["url", "filetype", "availability", "book", "added_by"]
def clean(self):
"""make sure the domain isn't blocked or pending"""
cleaned_data = super().clean()
url = cleaned_data.get("url")
filetype = cleaned_data.get("filetype")
book = cleaned_data.get("book")
domain = urlparse(url).netloc
if models.LinkDomain.objects.filter(domain=domain).exists():
status = models.LinkDomain.objects.get(domain=domain).status
if status == "blocked":
# pylint: disable=line-too-long
self.add_error(
"url",
_(
"This domain is blocked. Please contact your administrator if you think this is an error."
),
)
elif models.FileLink.objects.filter(
url=url, book=book, filetype=filetype
).exists():
# pylint: disable=line-too-long
self.add_error(
"url",
_(
"This link with file type has already been added for this book. If it is not visible, the domain is still pending."
),
)

37
bookwyrm/forms/lists.py Normal file
View file

@ -0,0 +1,37 @@
""" using django model forms """
from django import forms
from django.forms import ChoiceField
from django.utils.translation import gettext_lazy as _
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class ListForm(CustomForm):
class Meta:
model = models.List
fields = ["user", "name", "description", "curation", "privacy", "group"]
class ListItemForm(CustomForm):
class Meta:
model = models.ListItem
fields = ["user", "book", "book_list", "notes"]
class SortListForm(forms.Form):
sort_by = ChoiceField(
choices=(
("order", _("List Order")),
("title", _("Book Title")),
("rating", _("Rating")),
),
label=_("Sort By"),
)
direction = ChoiceField(
choices=(
("ascending", _("Ascending")),
("descending", _("Descending")),
),
)

82
bookwyrm/forms/status.py Normal file
View file

@ -0,0 +1,82 @@
""" using django model forms """
from bookwyrm import models
from .custom_form import CustomForm
# pylint: disable=missing-class-docstring
class RatingForm(CustomForm):
class Meta:
model = models.ReviewRating
fields = ["user", "book", "rating", "privacy"]
class ReviewForm(CustomForm):
class Meta:
model = models.Review
fields = [
"user",
"book",
"name",
"content",
"rating",
"content_warning",
"sensitive",
"privacy",
]
class CommentForm(CustomForm):
class Meta:
model = models.Comment
fields = [
"user",
"book",
"content",
"content_warning",
"sensitive",
"privacy",
"progress",
"progress_mode",
"reading_status",
]
class QuotationForm(CustomForm):
class Meta:
model = models.Quotation
fields = [
"user",
"book",
"quote",
"content",
"content_warning",
"sensitive",
"privacy",
"position",
"position_mode",
]
class ReplyForm(CustomForm):
class Meta:
model = models.Status
fields = [
"user",
"content",
"content_warning",
"sensitive",
"reply_parent",
"privacy",
]
class StatusForm(CustomForm):
class Meta:
model = models.Status
fields = ["user", "content", "content_warning", "sensitive", "privacy"]
class DirectForm(CustomForm):
class Meta:
model = models.Status
fields = ["user", "content", "content_warning", "sensitive", "privacy"]

70
bookwyrm/forms/widgets.py Normal file
View file

@ -0,0 +1,70 @@
""" using django model forms """
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]
class Select(forms.Select):
"""custom template for select widget"""
template_name = "widgets/select.html"
class SelectDateWidget(forms.SelectDateWidget):
"""
A widget that splits date input into two <select> boxes and a numerical year.
"""
template_name = "widgets/addon_multiwidget.html"
select_widget = Select
def get_context(self, name, value, attrs):
"""sets individual widgets"""
context = super().get_context(name, value, attrs)
date_context = {}
year_name = self.year_field % name
date_context["year"] = forms.NumberInput().get_context(
name=year_name,
value=context["widget"]["value"]["year"],
attrs={
**context["widget"]["attrs"],
"id": f"id_{year_name}",
"class": "input",
},
)
month_choices = list(self.months.items())
if not self.is_required:
month_choices.insert(0, self.month_none_value)
month_name = self.month_field % name
date_context["month"] = self.select_widget(
attrs, choices=month_choices
).get_context(
name=month_name,
value=context["widget"]["value"]["month"],
attrs={**context["widget"]["attrs"], "id": f"id_{month_name}"},
)
day_choices = [(i, i) for i in range(1, 32)]
if not self.is_required:
day_choices.insert(0, self.day_none_value)
day_name = self.day_field % name
date_context["day"] = self.select_widget(
attrs,
choices=day_choices,
).get_context(
name=day_name,
value=context["widget"]["value"]["day"],
attrs={**context["widget"]["attrs"], "id": f"id_{day_name}"},
)
subwidgets = []
for field in self._parse_date_fmt():
subwidgets.append(date_context[field]["widget"])
context["widget"]["subwidgets"] = subwidgets
return context

View file

@ -1,6 +1,7 @@
""" import classes """
from .importer import Importer
from .calibre_import import CalibreImporter
from .goodreads_import import GoodreadsImporter
from .librarything_import import LibrarythingImporter
from .openlibrary_import import OpenLibraryImporter

View file

@ -0,0 +1,28 @@
""" handle reading a csv from calibre """
from bookwyrm.models import Shelf
from . import Importer
class CalibreImporter(Importer):
"""csv downloads from Calibre"""
service = "Calibre"
def __init__(self, *args, **kwargs):
# Add timestamp to row_mappings_guesses for date_added to avoid
# integrity error
row_mappings_guesses = []
for field, mapping in self.row_mappings_guesses:
if field in ("date_added",):
row_mappings_guesses.append((field, mapping + ["timestamp"]))
else:
row_mappings_guesses.append((field, mapping))
self.row_mappings_guesses = row_mappings_guesses
super().__init__(*args, **kwargs)
def get_shelf(self, normalized_row):
# Calibre export does not indicate which shelf to use. Go with a default one for now
return Shelf.TO_READ

View file

@ -1,5 +1,8 @@
""" handle reading a tsv from librarything """
import re
from bookwyrm.models import Shelf
from . import Importer
@ -21,7 +24,7 @@ class LibrarythingImporter(Importer):
def get_shelf(self, normalized_row):
if normalized_row["date_finished"]:
return "read"
return Shelf.READ_FINISHED
if normalized_row["date_started"]:
return "reading"
return "to-read"
return Shelf.READING
return Shelf.TO_READ

View file

@ -0,0 +1,23 @@
""" Get your admin code to allow install """
from django.core.management.base import BaseCommand
from bookwyrm import models
def get_admin_code():
"""get that code"""
return models.SiteSettings.objects.get().admin_code
class Command(BaseCommand):
"""command-line options"""
help = "Gets admin code for configuring BookWyrm"
# pylint: disable=unused-argument
def handle(self, *args, **options):
"""execute init"""
self.stdout.write("*******************************************")
self.stdout.write("Use this code to create your admin account:")
self.stdout.write(get_admin_code())
self.stdout.write("*******************************************")

View file

@ -7,6 +7,7 @@ from bookwyrm import settings
r = redis.Redis(
host=settings.REDIS_ACTIVITY_HOST,
port=settings.REDIS_ACTIVITY_PORT,
password=settings.REDIS_ACTIVITY_PASSWORD,
db=settings.REDIS_ACTIVITY_DB_INDEX,
)

View file

@ -10,7 +10,9 @@ class Command(BaseCommand):
help = "Generate preview images"
# pylint: disable=no-self-use
def add_arguments(self, parser):
"""options for how the command is run"""
parser.add_argument(
"--all",
"-a",
@ -38,6 +40,7 @@ class Command(BaseCommand):
preview_images.generate_site_preview_image_task.delay()
self.stdout.write(" OK 🖼")
# pylint: disable=consider-using-f-string
if options["all"]:
# Users
users = models.User.objects.filter(
@ -53,12 +56,17 @@ class Command(BaseCommand):
self.stdout.write(" OK 🖼")
# Books
books = models.Book.objects.select_subclasses().filter()
self.stdout.write(
" → Book preview images ({}): ".format(len(books)), ending=""
book_ids = (
models.Book.objects.select_subclasses()
.filter()
.values_list("id", flat=True)
)
for book in books:
preview_images.generate_edition_preview_image_task.delay(book.id)
self.stdout.write(
" → Book preview images ({}): ".format(len(book_ids)), ending=""
)
for book_id in book_ids:
preview_images.generate_edition_preview_image_task.delay(book_id)
self.stdout.write(".", ending="")
self.stdout.write(" OK 🖼")

View file

@ -89,7 +89,7 @@ def init_connectors():
covers_url="https://inventaire.io",
search_url="https://inventaire.io/api/search?types=works&types=works&search=",
isbn_search_url="https://inventaire.io/api/entities?action=by-uris&uris=isbn%3A",
priority=3,
priority=1,
)
models.Connector.objects.create(
@ -101,25 +101,16 @@ def init_connectors():
covers_url="https://covers.openlibrary.org",
search_url="https://openlibrary.org/search?q=",
isbn_search_url="https://openlibrary.org/api/books?jscmd=data&format=json&bibkeys=ISBN:",
priority=3,
priority=1,
)
def init_federated_servers():
"""big no to nazis"""
built_in_blocks = ["gab.ai", "gab.com"]
for server in built_in_blocks:
models.FederatedServer.objects.create(
server_name=server,
status="blocked",
)
def init_settings():
"""info about the instance"""
models.SiteSettings.objects.create(
support_link="https://www.patreon.com/bookwyrm",
support_title="Patreon",
install_mode=True,
)
@ -162,7 +153,6 @@ class Command(BaseCommand):
"group",
"permission",
"connector",
"federatedserver",
"settings",
"linkdomain",
]
@ -175,8 +165,6 @@ class Command(BaseCommand):
init_permissions()
if not limit or limit == "connector":
init_connectors()
if not limit or limit == "federatedserver":
init_federated_servers()
if not limit or limit == "settings":
init_settings()
if not limit or limit == "linkdomain":

View file

@ -0,0 +1,54 @@
""" Get your admin code to allow install """
from django.core.management.base import BaseCommand
from bookwyrm import models
from bookwyrm.settings import VERSION
# pylint: disable=no-self-use
class Command(BaseCommand):
"""command-line options"""
help = "What version is this?"
def add_arguments(self, parser):
"""specify which function to run"""
parser.add_argument(
"--current",
action="store_true",
help="Version stored in database",
)
parser.add_argument(
"--target",
action="store_true",
help="Version stored in settings",
)
parser.add_argument(
"--update",
action="store_true",
help="Update database version",
)
# pylint: disable=unused-argument
def handle(self, *args, **options):
"""execute init"""
site = models.SiteSettings.objects.get()
current = site.version or "0.0.1"
target = VERSION
if options.get("current"):
print(current)
return
if options.get("target"):
print(target)
return
if options.get("update"):
site.version = target
site.save()
return
if current != target:
print(f"{current}/{target}")
else:
print(current)

View file

@ -0,0 +1,21 @@
# Generated by Django 3.2.11 on 2022-02-04 20:06
import bookwyrm.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0132_alter_user_preferred_language"),
]
operations = [
migrations.AlterField(
model_name="listitem",
name="notes",
field=bookwyrm.models.fields.HtmlField(
blank=True, max_length=300, null=True
),
),
]

View file

@ -0,0 +1,29 @@
# Generated by Django 3.2.11 on 2022-02-11 18:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0133_alter_listitem_notes"),
]
operations = [
migrations.AddField(
model_name="announcement",
name="display_type",
field=models.CharField(
choices=[
("white-ter", "None"),
("primary-light", "Primary"),
("success-light", "Success"),
("link-light", "Link"),
("warning-light", "Warning"),
("danger-light", "Danger"),
],
default="white-ter",
max_length=20,
),
),
]

View file

@ -0,0 +1,58 @@
# Generated by Django 3.2.12 on 2022-02-17 16:24
import bookwyrm.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0134_announcement_display_type"),
]
operations = [
migrations.RenameField(
model_name="author",
old_name="viaf_id",
new_name="viaf",
),
migrations.RemoveField(
model_name="edition",
name="asin",
),
migrations.AddField(
model_name="author",
name="asin",
field=bookwyrm.models.fields.CharField(
blank=True, max_length=255, null=True
),
),
migrations.AddField(
model_name="author",
name="wikidata",
field=bookwyrm.models.fields.CharField(
blank=True, max_length=255, null=True
),
),
migrations.AddField(
model_name="book",
name="asin",
field=bookwyrm.models.fields.CharField(
blank=True, max_length=255, null=True
),
),
migrations.AddField(
model_name="book",
name="viaf",
field=bookwyrm.models.fields.CharField(
blank=True, max_length=255, null=True
),
),
migrations.AddField(
model_name="book",
name="wikidata",
field=bookwyrm.models.fields.CharField(
blank=True, max_length=255, null=True
),
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 3.2.12 on 2022-02-17 17:08
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0135_auto_20220217_1624"),
]
operations = [
migrations.AddField(
model_name="sitesettings",
name="admin_code",
field=models.CharField(default=uuid.uuid4, max_length=50),
),
migrations.AddField(
model_name="sitesettings",
name="install_mode",
field=models.BooleanField(default=False),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2022-02-17 19:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0136_auto_20220217_1708"),
]
operations = [
migrations.AlterField(
model_name="sitesettings",
name="allow_registration",
field=models.BooleanField(default=False),
),
]

View file

@ -0,0 +1,39 @@
# Generated by Django 3.2.12 on 2022-02-24 18:59
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0137_alter_sitesettings_allow_registration"),
]
operations = [
migrations.CreateModel(
name="AutoMod",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("string_match", models.CharField(max_length=200, unique=True)),
("flag_users", models.BooleanField(default=True)),
("flag_statuses", models.BooleanField(default=True)),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View file

@ -0,0 +1,45 @@
# Generated by Django 3.2.12 on 2022-02-24 20:41
from django.db import migrations, models
import django.db.models.deletion
def set_report_statuses(apps, schema_editor):
"""copy over status fields"""
db_alias = schema_editor.connection.alias
report_model = apps.get_model("bookwyrm", "Report")
reports = report_model.objects.using(db_alias).filter(statuses__isnull=False)
for report in reports:
report.status = report.statuses.first()
report.save()
def set_reverse(apps, schema_editor):
"""copy over status fields"""
db_alias = schema_editor.connection.alias
report_model = apps.get_model("bookwyrm", "Report")
reports = report_model.objects.using(db_alias).filter(status__isnull=False)
for report in reports:
report.statuses.set(report.status)
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0138_automod"),
]
operations = [
migrations.AddField(
model_name="report",
name="status",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="reports",
to="bookwyrm.status",
),
),
migrations.RunPython(set_report_statuses, reverse_code=set_reverse),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2.12 on 2022-02-24 20:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0139_report_status"),
]
operations = [
migrations.RemoveField(
model_name="report",
name="statuses",
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 3.2.12 on 2022-02-24 20:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0140_remove_report_statuses"),
]
operations = [
migrations.AlterField(
model_name="report",
name="status",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="bookwyrm.status",
),
),
]

View file

@ -0,0 +1,68 @@
# Generated by Django 3.2.12 on 2022-02-27 17:52
from django.db import migrations, models
import django.db.models.deletion
def add_default_themes(apps, schema_editor):
"""add light and dark themes"""
db_alias = schema_editor.connection.alias
theme_model = apps.get_model("bookwyrm", "Theme")
theme_model.objects.using(db_alias).create(
name="BookWyrm Light",
path="css/themes/bookwyrm-light.scss",
)
theme_model.objects.using(db_alias).create(
name="BookWyrm Dark",
path="css/themes/bookwyrm-dark.scss",
)
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0141_alter_report_status"),
]
operations = [
migrations.CreateModel(
name="Theme",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_date", models.DateTimeField(auto_now_add=True)),
("name", models.CharField(max_length=50, unique=True)),
("path", models.CharField(max_length=50, unique=True)),
],
),
migrations.AddField(
model_name="sitesettings",
name="default_theme",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="bookwyrm.theme",
),
),
migrations.AddField(
model_name="user",
name="theme",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="bookwyrm.theme",
),
),
migrations.RunPython(
add_default_themes, reverse_code=migrations.RunPython.noop
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 3.2.12 on 2022-02-28 19:44
import bookwyrm.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0141_alter_report_status"),
]
operations = [
migrations.AddField(
model_name="user",
name="hide_follows",
field=bookwyrm.models.fields.BooleanField(default=False),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 3.2.12 on 2022-02-28 21:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0142_auto_20220227_1752"),
("bookwyrm", "0142_user_hide_follows"),
]
operations = []

View file

@ -0,0 +1,39 @@
# Generated by Django 3.2.12 on 2022-03-01 18:46
from django.db import migrations, models
def remove_white(apps, schema_editor):
"""don't hardcode white announcements"""
db_alias = schema_editor.connection.alias
announcement_model = apps.get_model("bookwyrm", "Announcement")
announcement_model.objects.using(db_alias).filter(display_type="white-ter").update(
display_type=None
)
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0143_merge_0142_auto_20220227_1752_0142_user_hide_follows"),
]
operations = [
migrations.AlterField(
model_name="announcement",
name="display_type",
field=models.CharField(
blank=True,
choices=[
("primary-light", "Primary"),
("success-light", "Success"),
("link-light", "Link"),
("warning-light", "Warning"),
("danger-light", "Danger"),
],
max_length=20,
null=True,
),
),
migrations.RunPython(remove_white, reverse_code=migrations.RunPython.noop),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2022-03-16 18:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0144_alter_announcement_display_type"),
]
operations = [
migrations.AddField(
model_name="sitesettings",
name="version",
field=models.CharField(blank=True, max_length=10, null=True),
),
]

View file

@ -0,0 +1,80 @@
# Generated by Django 3.2.12 on 2022-03-16 23:20
import bookwyrm.models.fields
from django.db import migrations
from bookwyrm.models import Shelf
def add_shelves(apps, schema_editor):
"""add any superusers to the "admin" group"""
db_alias = schema_editor.connection.alias
shelf_model = apps.get_model("bookwyrm", "Shelf")
users = apps.get_model("bookwyrm", "User")
local_users = users.objects.using(db_alias).filter(local=True)
for user in local_users:
remote_id = f"{user.remote_id}/books/stopped"
shelf_model.objects.using(db_alias).create(
name="Stopped reading",
identifier=Shelf.STOPPED_READING,
user=user,
editable=False,
remote_id=remote_id,
)
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0145_sitesettings_version"),
]
operations = [
migrations.AlterField(
model_name="comment",
name="reading_status",
field=bookwyrm.models.fields.CharField(
blank=True,
choices=[
("to-read", "To-Read"),
("reading", "Reading"),
("read", "Read"),
("stopped-reading", "Stopped-Reading"),
],
max_length=255,
null=True,
),
),
migrations.AlterField(
model_name="quotation",
name="reading_status",
field=bookwyrm.models.fields.CharField(
blank=True,
choices=[
("to-read", "To-Read"),
("reading", "Reading"),
("read", "Read"),
("stopped-reading", "Stopped-Reading"),
],
max_length=255,
null=True,
),
),
migrations.AlterField(
model_name="review",
name="reading_status",
field=bookwyrm.models.fields.CharField(
blank=True,
choices=[
("to-read", "To-Read"),
("reading", "Reading"),
("read", "Read"),
("stopped-reading", "Stopped-Reading"),
],
max_length=255,
null=True,
),
),
migrations.RunPython(add_shelves, reverse_code=migrations.RunPython.noop),
]

View file

@ -0,0 +1,30 @@
# Generated by Django 3.2.12 on 2022-03-16 23:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0145_sitesettings_version"),
]
operations = [
migrations.AddField(
model_name="inviterequest",
name="answer",
field=models.TextField(blank=True, max_length=50, null=True),
),
migrations.AddField(
model_name="sitesettings",
name="invite_question_text",
field=models.CharField(
blank=True, default="What is your favourite book?", max_length=255
),
),
migrations.AddField(
model_name="sitesettings",
name="invite_request_question",
field=models.BooleanField(default=False),
),
]

View file

@ -0,0 +1,38 @@
# Generated by Django 3.2.12 on 2022-03-26 16:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0146_auto_20220316_2352"),
]
operations = [
migrations.AlterField(
model_name="user",
name="preferred_language",
field=models.CharField(
blank=True,
choices=[
("en-us", "English"),
("de-de", "Deutsch (German)"),
("es-es", "Español (Spanish)"),
("gl-es", "Galego (Galician)"),
("it-it", "Italiano (Italian)"),
("fr-fr", "Français (French)"),
("lt-lt", "Lietuvių (Lithuanian)"),
("no-no", "Norsk (Norwegian)"),
("pt-br", "Português do Brasil (Brazilian Portuguese)"),
("pt-pt", "Português Europeu (European Portuguese)"),
("ro-ro", "Română (Romanian)"),
("sv-se", "Svenska (Swedish)"),
("zh-hans", "简体中文 (Simplified Chinese)"),
("zh-hant", "繁體中文 (Traditional Chinese)"),
],
max_length=255,
null=True,
),
),
]

View file

@ -0,0 +1,39 @@
# Generated by Django 3.2.12 on 2022-03-31 14:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0147_alter_user_preferred_language"),
]
operations = [
migrations.AlterField(
model_name="user",
name="preferred_language",
field=models.CharField(
blank=True,
choices=[
("en-us", "English"),
("de-de", "Deutsch (German)"),
("es-es", "Español (Spanish)"),
("gl-es", "Galego (Galician)"),
("it-it", "Italiano (Italian)"),
("fi-fi", "Suomi (Finnish)"),
("fr-fr", "Français (French)"),
("lt-lt", "Lietuvių (Lithuanian)"),
("no-no", "Norsk (Norwegian)"),
("pt-br", "Português do Brasil (Brazilian Portuguese)"),
("pt-pt", "Português Europeu (European Portuguese)"),
("ro-ro", "Română (Romanian)"),
("sv-se", "Svenska (Swedish)"),
("zh-hans", "简体中文 (Simplified Chinese)"),
("zh-hant", "繁體中文 (Traditional Chinese)"),
],
max_length=255,
null=True,
),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 3.2.12 on 2022-03-26 20:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0146_auto_20220316_2320"),
("bookwyrm", "0147_alter_user_preferred_language"),
]
operations = []

View file

@ -0,0 +1,13 @@
# Generated by Django 3.2.13 on 2022-05-26 17:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0148_alter_user_preferred_language"),
("bookwyrm", "0148_merge_20220326_2006"),
]
operations = []

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-05-26 18:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0149_merge_20220526_1716"),
]
operations = [
migrations.AddField(
model_name="readthrough",
name="stopped_date",
field=models.DateTimeField(blank=True, null=True),
),
]

View file

@ -26,10 +26,10 @@ from .group import Group, GroupMember, GroupMemberInvitation
from .import_job import ImportJob, ImportItem
from .site import SiteSettings, SiteInvite
from .site import SiteSettings, Theme, SiteInvite
from .site import PasswordReset, InviteRequest
from .announcement import Announcement
from .antispam import EmailBlocklist, IPBlocklist
from .antispam import EmailBlocklist, IPBlocklist, AutoMod, automod_task
from .notification import Notification

View file

@ -2,10 +2,20 @@
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .base_model import BookWyrmModel
DisplayTypes = [
("primary-light", _("Primary")),
("success-light", _("Success")),
("link-light", _("Link")),
("warning-light", _("Warning")),
("danger-light", _("Danger")),
]
class Announcement(BookWyrmModel):
"""The admin has something to say"""
@ -16,6 +26,9 @@ class Announcement(BookWyrmModel):
start_date = models.DateTimeField(blank=True, null=True)
end_date = models.DateTimeField(blank=True, null=True)
active = models.BooleanField(default=True)
display_type = models.CharField(
max_length=20, choices=DisplayTypes, null=True, blank=True
)
@classmethod
def active_announcements(cls):

View file

@ -1,6 +1,13 @@
""" Lets try NOT to sell viagra """
from django.db import models
from functools import reduce
import operator
from django.apps import apps
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from bookwyrm.tasks import app
from .user import User
@ -33,3 +40,107 @@ class IPBlocklist(models.Model):
"""default sorting"""
ordering = ("-created_date",)
class AutoMod(models.Model):
"""rules to automatically flag suspicious activity"""
string_match = models.CharField(max_length=200, unique=True)
flag_users = models.BooleanField(default=True)
flag_statuses = models.BooleanField(default=True)
created_by = models.ForeignKey("User", on_delete=models.PROTECT)
@app.task(queue="low_priority")
def automod_task():
"""Create reports"""
if not AutoMod.objects.exists():
return
reporter = AutoMod.objects.first().created_by
reports = automod_users(reporter) + automod_statuses(reporter)
if reports:
admins = User.objects.filter(
models.Q(user_permissions__name__in=["moderate_user", "moderate_post"])
| models.Q(is_superuser=True)
).all()
notification_model = apps.get_model(
"bookwyrm", "Notification", require_ready=True
)
for admin in admins:
notification_model.objects.bulk_create(
[
notification_model(
user=admin,
related_report=r,
notification_type="REPORT",
)
for r in reports
]
)
def automod_users(reporter):
"""check users for moderation flags"""
user_rules = AutoMod.objects.filter(flag_users=True).values_list(
"string_match", flat=True
)
if not user_rules:
return []
filters = []
for field in ["username", "summary", "name"]:
filters += [{f"{field}__icontains": r} for r in user_rules]
users = User.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters)),
is_active=True,
local=True,
report__isnull=True, # don't flag users that already have reports
).distinct()
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
return report_model.objects.bulk_create(
[
report_model(
reporter=reporter,
note=_("Automatically generated report"),
user=u,
)
for u in users
]
)
def automod_statuses(reporter):
"""check statues for moderation flags"""
status_rules = AutoMod.objects.filter(flag_statuses=True).values_list(
"string_match", flat=True
)
if not status_rules:
return []
filters = []
for field in ["content", "content_warning", "quotation__quote", "review__name"]:
filters += [{f"{field}__icontains": r} for r in status_rules]
status_model = apps.get_model("bookwyrm", "Status", require_ready=True)
statuses = status_model.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters)),
deleted=False,
local=True,
report__isnull=True, # don't flag statuses that already have reports
).distinct()
report_model = apps.get_model("bookwyrm", "Report", require_ready=True)
return report_model.objects.bulk_create(
[
report_model(
reporter=reporter,
note=_("Automatically generated report"),
user=s.user,
status=s,
)
for s in statuses
]
)

View file

@ -21,9 +21,6 @@ class Author(BookDataModel):
isni = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
viaf_id = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
gutenberg_id = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)

View file

@ -8,6 +8,7 @@ from django.db.models import Q
from django.dispatch import receiver
from django.http import Http404
from django.utils.translation import gettext_lazy as _
from django.utils.text import slugify
from bookwyrm.settings import DOMAIN
from .fields import RemoteIdField
@ -35,10 +36,11 @@ class BookWyrmModel(models.Model):
remote_id = RemoteIdField(null=True, activitypub_field="id")
def get_remote_id(self):
"""generate a url that resolves to the local object"""
"""generate the url that resolves to the local object, without a slug"""
base_path = f"https://{DOMAIN}"
if hasattr(self, "user"):
base_path = f"{base_path}{self.user.local_path}"
model_name = type(self).__name__.lower()
return f"{base_path}/{model_name}/{self.id}"
@ -49,8 +51,20 @@ class BookWyrmModel(models.Model):
@property
def local_path(self):
"""how to link to this object in the local app"""
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
"""how to link to this object in the local app, with a slug"""
local = self.get_remote_id().replace(f"https://{DOMAIN}", "")
name = None
if hasattr(self, "name_field"):
name = getattr(self, self.name_field)
elif hasattr(self, "name"):
name = self.name
if name:
slug = slugify(name)
local = f"{local}/s/{slug}"
return local
def raise_visible_to_user(self, viewer):
"""is a user authorized to view an object?"""

View file

@ -46,6 +46,15 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
bnf_id = fields.CharField( # Bibliothèque nationale de France
max_length=255, blank=True, null=True, deduplication_field=True
)
viaf = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
wikidata = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
asin = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
search_vector = SearchVectorField(null=True)
last_edited_by = fields.ForeignKey(
@ -167,8 +176,8 @@ class Book(BookDataModel):
"""properties of this edition, as a string"""
items = [
self.physical_format if hasattr(self, "physical_format") else None,
self.languages[0] + " language"
if self.languages and self.languages[0] != "English"
f"{self.languages[0]} language"
if self.languages and self.languages[0] and self.languages[0] != "English"
else None,
str(self.published_date.year) if self.published_date else None,
", ".join(self.publishers) if hasattr(self, "publishers") else None,
@ -271,9 +280,6 @@ class Edition(Book):
oclc_number = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
asin = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True
)
pages = fields.IntegerField(blank=True, null=True)
physical_format = fields.CharField(
max_length=255, choices=FormatChoices, null=True, blank=True

View file

@ -125,7 +125,7 @@ class ActivitypubFieldMixin:
"""model_field_name to activitypubFieldName"""
if self.activitypub_field:
return self.activitypub_field
name = self.name.split(".")[-1]
name = self.name.rsplit(".", maxsplit=1)[-1]
components = name.split("_")
return components[0] + "".join(x.title() for x in components[1:])
@ -389,7 +389,7 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
self.alt_field = alt_field
super().__init__(*args, **kwargs)
# pylint: disable=arguments-differ
# pylint: disable=arguments-differ,arguments-renamed
def set_field_from_activity(self, instance, data, save=True, overwrite=True):
"""helper function for assinging a value to the field"""
value = getattr(data, self.get_activitypub_field())

View file

@ -175,9 +175,15 @@ class ImportItem(models.Model):
def date_added(self):
"""when the book was added to this dataset"""
if self.normalized_data.get("date_added"):
return timezone.make_aware(
dateutil.parser.parse(self.normalized_data.get("date_added"))
parsed_date_added = dateutil.parser.parse(
self.normalized_data.get("date_added")
)
if timezone.is_aware(parsed_date_added):
# Keep timezone if import already had one
return parsed_date_added
return timezone.make_aware(parsed_date_added)
return None
@property

View file

@ -142,7 +142,7 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="actor"
)
notes = fields.TextField(blank=True, null=True, max_length=300)
notes = fields.HtmlField(blank=True, null=True, max_length=300)
approved = models.BooleanField(default=True)
order = fields.IntegerField()
endorsement = models.ManyToManyField("User", related_name="endorsers")

View file

@ -27,6 +27,7 @@ class ReadThrough(BookWyrmModel):
)
start_date = models.DateTimeField(blank=True, null=True)
finish_date = models.DateTimeField(blank=True, null=True)
stopped_date = models.DateTimeField(blank=True, null=True)
is_active = models.BooleanField(default=True)
def save(self, *args, **kwargs):
@ -34,7 +35,7 @@ class ReadThrough(BookWyrmModel):
cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}")
self.user.update_active_date()
# an active readthrough must have an unset finish date
if self.finish_date:
if self.finish_date or self.stopped_date:
self.is_active = False
super().save(*args, **kwargs)

View file

@ -39,15 +39,14 @@ class UserRelationship(BookWyrmModel):
def save(self, *args, **kwargs):
"""clear the template cache"""
# invalidate the template cache
cache.delete_many(
[
f"relationship-{self.user_subject.id}-{self.user_object.id}",
f"relationship-{self.user_object.id}-{self.user_subject.id}",
]
)
clear_cache(self.user_subject, self.user_object)
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
"""clear the template cache"""
clear_cache(self.user_subject, self.user_object)
super().delete(*args, **kwargs)
class Meta:
"""relationships should be unique"""
@ -90,7 +89,9 @@ class UserFollows(ActivityMixin, UserRelationship):
user_object=self.user_subject,
)
).exists():
raise IntegrityError()
raise IntegrityError(
"Attempting to follow blocked user", self.user_subject, self.user_object
)
# don't broadcast this type of relationship -- accepts and requests
# are handled by the UserFollowRequest model
super().save(*args, broadcast=False, **kwargs)
@ -98,11 +99,12 @@ class UserFollows(ActivityMixin, UserRelationship):
@classmethod
def from_request(cls, follow_request):
"""converts a follow request into a follow relationship"""
return cls.objects.create(
obj, _ = cls.objects.get_or_create(
user_subject=follow_request.user_subject,
user_object=follow_request.user_object,
remote_id=follow_request.remote_id,
)
return obj
class UserFollowRequest(ActivitypubMixin, UserRelationship):
@ -133,7 +135,9 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
user_object=self.user_subject,
)
).exists():
raise IntegrityError()
raise IntegrityError(
"Attempting to follow blocked user", self.user_subject, self.user_object
)
super().save(*args, **kwargs)
if broadcast and self.user_subject.local and not self.user_object.local:
@ -174,7 +178,8 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
with transaction.atomic():
UserFollows.from_request(self)
self.delete()
if self.id:
self.delete()
def reject(self):
"""generate a Reject for this follow request"""
@ -207,3 +212,13 @@ class UserBlocks(ActivityMixin, UserRelationship):
Q(user_subject=self.user_subject, user_object=self.user_object)
| Q(user_subject=self.user_object, user_object=self.user_subject)
).delete()
def clear_cache(user_subject, user_object):
"""clear relationship cache"""
cache.delete_many(
[
f"relationship-{user_subject.id}-{user_object.id}",
f"relationship-{user_object.id}-{user_subject.id}",
]
)

View file

@ -12,7 +12,12 @@ class Report(BookWyrmModel):
)
note = models.TextField(null=True, blank=True)
user = models.ForeignKey("User", on_delete=models.PROTECT)
statuses = models.ManyToManyField("Status", blank=True)
status = models.ForeignKey(
"Status",
null=True,
blank=True,
on_delete=models.PROTECT,
)
links = models.ManyToManyField("Link", blank=True)
resolved = models.BooleanField(default=False)

View file

@ -6,6 +6,7 @@ from django.db import models
from django.utils import timezone
from bookwyrm import activitypub
from bookwyrm.settings import DOMAIN
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
from .base_model import BookWyrmModel
from . import fields
@ -17,8 +18,9 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
TO_READ = "to-read"
READING = "reading"
READ_FINISHED = "read"
STOPPED_READING = "stopped-reading"
READ_STATUS_IDENTIFIERS = (TO_READ, READING, READ_FINISHED)
READ_STATUS_IDENTIFIERS = (TO_READ, READING, READ_FINISHED, STOPPED_READING)
name = fields.CharField(max_length=100)
identifier = models.CharField(max_length=100)
@ -65,6 +67,11 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
identifier = self.identifier or self.get_identifier()
return f"{base_path}/books/{identifier}"
@property
def local_path(self):
"""No slugs"""
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
def raise_not_deletable(self, viewer):
"""don't let anyone delete a default shelf"""
super().raise_not_deletable(viewer)

View file

@ -1,6 +1,7 @@
""" the particulars for this instance of BookWyrm """
import datetime
from urllib.parse import urljoin
import uuid
from django.db import models, IntegrityError
from django.dispatch import receiver
@ -23,6 +24,14 @@ class SiteSettings(models.Model):
)
instance_description = models.TextField(default="This instance has no description.")
instance_short_description = models.CharField(max_length=255, blank=True, null=True)
default_theme = models.ForeignKey(
"Theme", null=True, blank=True, on_delete=models.SET_NULL
)
version = models.CharField(null=True, blank=True, max_length=10)
# admin setup options
install_mode = models.BooleanField(default=False)
admin_code = models.CharField(max_length=50, default=uuid.uuid4)
# about page
registration_closed_text = models.TextField(
@ -38,10 +47,14 @@ class SiteSettings(models.Model):
privacy_policy = models.TextField(default="Add a privacy policy here.")
# registration
allow_registration = models.BooleanField(default=True)
allow_registration = models.BooleanField(default=False)
allow_invite_requests = models.BooleanField(default=True)
invite_request_question = models.BooleanField(default=False)
require_confirm_email = models.BooleanField(default=True)
invite_question_text = models.CharField(
max_length=255, blank=True, default="What is your favourite book?"
)
# images
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
logo_small = models.ImageField(upload_to="logos/", null=True, blank=True)
@ -91,14 +104,29 @@ class SiteSettings(models.Model):
return urljoin(STATIC_FULL_URL, default_path)
def save(self, *args, **kwargs):
"""if require_confirm_email is disabled, make sure no users are pending"""
"""if require_confirm_email is disabled, make sure no users are pending,
if enabled, make sure invite_question_text is not empty"""
if not self.require_confirm_email:
User.objects.filter(is_active=False, deactivation_reason="pending").update(
is_active=True, deactivation_reason=None
)
if not self.invite_question_text:
self.invite_question_text = "What is your favourite book?"
super().save(*args, **kwargs)
class Theme(models.Model):
"""Theme files"""
created_date = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50, unique=True)
path = models.CharField(max_length=50, unique=True)
def __str__(self):
# pylint: disable=invalid-str-returned
return self.name
class SiteInvite(models.Model):
"""gives someone access to create an account on the instance"""
@ -129,6 +157,7 @@ class InviteRequest(BookWyrmModel):
invite = models.ForeignKey(
SiteInvite, on_delete=models.SET_NULL, null=True, blank=True
)
answer = models.TextField(max_length=50, unique=False, null=True, blank=True)
invite_sent = models.BooleanField(default=False)
ignored = models.BooleanField(default=False)

View file

@ -116,11 +116,8 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements
"""keep notes if they are replies to existing statuses"""
if activity.type == "Announce":
try:
boosted = activitypub.resolve_remote_id(
activity.object, get_activity=True
)
except activitypub.ActivitySerializerError:
boosted = activitypub.resolve_remote_id(activity.object, get_activity=True)
if not boosted:
# if we can't load the status, definitely ignore it
return True
# keep the boost if we would keep the status
@ -227,7 +224,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
@classmethod
def privacy_filter(cls, viewer, privacy_levels=None):
queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels)
return queryset.filter(deleted=False)
return queryset.filter(deleted=False, user__is_active=True)
@classmethod
def direct_filter(cls, queryset, viewer):
@ -265,7 +262,7 @@ class GeneratedNote(Status):
ReadingStatusChoices = models.TextChoices(
"ReadingStatusChoices", ["to-read", "reading", "read"]
"ReadingStatusChoices", ["to-read", "reading", "read", "stopped-reading"]
)
@ -306,10 +303,17 @@ class Comment(BookStatus):
@property
def pure_content(self):
"""indicate the book in question for mastodon (or w/e) users"""
return (
f'{self.content}<p>(comment on <a href="{self.book.remote_id}">'
f'"{self.book.title}"</a>)</p>'
)
if self.progress_mode == "PG" and self.progress and (self.progress > 0):
return_value = (
f'{self.content}<p>(comment on <a href="{self.book.remote_id}">'
f'"{self.book.title}"</a>, page {self.progress})</p>'
)
else:
return_value = (
f'{self.content}<p>(comment on <a href="{self.book.remote_id}">'
f'"{self.book.title}"</a>)</p>'
)
return return_value
activity_serializer = activitypub.Comment
@ -335,10 +339,17 @@ class Quotation(BookStatus):
"""indicate the book in question for mastodon (or w/e) users"""
quote = re.sub(r"^<p>", '<p>"', self.quote)
quote = re.sub(r"</p>$", '"</p>', quote)
return (
f'{quote} <p>-- <a href="{self.book.remote_id}">'
f'"{self.book.title}"</a></p>{self.content}'
)
if self.position_mode == "PG" and self.position and (self.position > 0):
return_value = (
f'{quote} <p>-- <a href="{self.book.remote_id}">'
f'"{self.book.title}"</a>, page {self.position}</p>{self.content}'
)
else:
return_value = (
f'{quote} <p>-- <a href="{self.book.remote_id}">'
f'"{self.book.title}"</a></p>{self.content}'
)
return return_value
activity_serializer = activitypub.Quotation
@ -377,7 +388,7 @@ class Review(BookStatus):
def save(self, *args, **kwargs):
"""clear rating caches"""
if self.book.parent_work:
cache.delete(f"book-rating-{self.book.parent_work.id}-*")
cache.delete(f"book-rating-{self.book.parent_work.id}")
super().save(*args, **kwargs)

View file

@ -136,6 +136,8 @@ class User(OrderedCollectionPageMixin, AbstractUser):
updated_date = models.DateTimeField(auto_now=True)
last_active_date = models.DateTimeField(default=timezone.now)
manually_approves_followers = fields.BooleanField(default=False)
theme = models.ForeignKey("Theme", null=True, blank=True, on_delete=models.SET_NULL)
hide_follows = fields.BooleanField(default=False)
# options to turn features on and off
show_goal = models.BooleanField(default=True)
@ -372,6 +374,10 @@ class User(OrderedCollectionPageMixin, AbstractUser):
"name": "Read",
"identifier": "read",
},
{
"name": "Stopped Reading",
"identifier": "stopped-reading",
},
]
for shelf in shelves:
@ -478,10 +484,13 @@ def set_remote_server(user_id):
get_remote_reviews.delay(user.outbox)
def get_or_create_remote_server(domain):
def get_or_create_remote_server(domain, refresh=False):
"""get info on a remote server"""
server = FederatedServer()
try:
return FederatedServer.objects.get(server_name=domain)
server = FederatedServer.objects.get(server_name=domain)
if not refresh:
return server
except FederatedServer.DoesNotExist:
pass
@ -496,13 +505,15 @@ def get_or_create_remote_server(domain):
application_type = data.get("software", {}).get("name")
application_version = data.get("software", {}).get("version")
except ConnectorException:
if server.id:
return server
application_type = application_version = None
server = FederatedServer.objects.create(
server_name=domain,
application_type=application_type,
application_version=application_version,
)
server.server_name = domain
server.application_type = application_type
server.application_version = application_version
server.save()
return server

View file

@ -4,6 +4,7 @@ import os
import textwrap
from io import BytesIO
from uuid import uuid4
import logging
import colorsys
from colorthief import ColorThief
@ -17,34 +18,49 @@ from django.db.models import Avg
from bookwyrm import models, settings
from bookwyrm.tasks import app
logger = logging.getLogger(__name__)
IMG_WIDTH = settings.PREVIEW_IMG_WIDTH
IMG_HEIGHT = settings.PREVIEW_IMG_HEIGHT
BG_COLOR = settings.PREVIEW_BG_COLOR
TEXT_COLOR = settings.PREVIEW_TEXT_COLOR
DEFAULT_COVER_COLOR = settings.PREVIEW_DEFAULT_COVER_COLOR
DEFAULT_FONT = settings.PREVIEW_DEFAULT_FONT
TRANSPARENT_COLOR = (0, 0, 0, 0)
margin = math.floor(IMG_HEIGHT / 10)
gutter = math.floor(margin / 2)
inner_img_height = math.floor(IMG_HEIGHT * 0.8)
inner_img_width = math.floor(inner_img_height * 0.7)
font_dir = os.path.join(settings.STATIC_ROOT, "fonts/public_sans")
def get_font(font_name, size=28):
"""Loads custom font"""
if font_name == "light":
font_path = os.path.join(font_dir, "PublicSans-Light.ttf")
if font_name == "regular":
font_path = os.path.join(font_dir, "PublicSans-Regular.ttf")
elif font_name == "bold":
font_path = os.path.join(font_dir, "PublicSans-Bold.ttf")
def get_imagefont(name, size):
"""Loads an ImageFont based on config"""
try:
config = settings.FONTS[name]
path = os.path.join(settings.FONT_DIR, config["directory"], config["filename"])
return ImageFont.truetype(path, size)
except KeyError:
logger.error("Font %s not found in config", name)
except OSError:
logger.error("Could not load font %s from file", name)
return ImageFont.load_default()
def get_font(weight, size=28):
"""Gets a custom font with the given weight and size"""
font = get_imagefont(DEFAULT_FONT, size)
try:
font = ImageFont.truetype(font_path, size)
except OSError:
font = ImageFont.load_default()
if weight == "light":
font.set_variation_by_name("Light")
if weight == "bold":
font.set_variation_by_name("Bold")
if weight == "regular":
font.set_variation_by_name("Regular")
except AttributeError:
pass
return font

View file

@ -22,6 +22,7 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method
"ol",
"li",
]
self.allowed_attrs = ["href", "rel", "src", "alt"]
self.tag_stack = []
self.output = []
# if the html appears invalid, we just won't allow any at all
@ -30,7 +31,14 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method
def handle_starttag(self, tag, attrs):
"""check if the tag is valid"""
if self.allow_html and tag in self.allowed_tags:
self.output.append(("tag", self.get_starttag_text()))
allowed_attrs = " ".join(
f'{a}="{v}"' for a, v in attrs if a in self.allowed_attrs
)
reconstructed = f"<{tag}"
if allowed_attrs:
reconstructed += " " + allowed_attrs
reconstructed += ">"
self.output.append(("tag", reconstructed))
self.tag_stack.append(tag)
else:
self.output.append(("data", ""))

View file

@ -6,15 +6,22 @@ import requests
from django.utils.translation import gettext_lazy as _
# pylint: disable=line-too-long
env = Env()
env.read_env()
DOMAIN = env("DOMAIN")
VERSION = "0.2.1"
VERSION = "0.4.0"
RELEASE_API = env(
"RELEASE_API",
"https://api.github.com/repos/bookwyrm-social/bookwyrm/releases/latest",
)
PAGE_LENGTH = env("PAGE_LENGTH", 15)
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
JS_CACHE = "7b5303af"
JS_CACHE = "e678183b"
# email
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
@ -35,6 +42,9 @@ LOCALE_PATHS = [
]
LANGUAGE_COOKIE_NAME = env.str("LANGUAGE_COOKIE_NAME", "django_language")
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Preview image
@ -44,6 +54,16 @@ PREVIEW_TEXT_COLOR = env.str("PREVIEW_TEXT_COLOR", "#363636")
PREVIEW_IMG_WIDTH = env.int("PREVIEW_IMG_WIDTH", 1200)
PREVIEW_IMG_HEIGHT = env.int("PREVIEW_IMG_HEIGHT", 630)
PREVIEW_DEFAULT_COVER_COLOR = env.str("PREVIEW_DEFAULT_COVER_COLOR", "#002549")
PREVIEW_DEFAULT_FONT = env.str("PREVIEW_DEFAULT_FONT", "Source Han Sans")
FONTS = {
"Source Han Sans": {
"directory": "source_han_sans",
"filename": "SourceHanSans-VF.ttf.ttc",
"url": "https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSans-VF.ttf.ttc",
}
}
FONT_DIR = os.path.join(STATIC_ROOT, "fonts")
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
@ -53,7 +73,7 @@ SECRET_KEY = env("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DEBUG", True)
USE_HTTPS = env.bool("USE_HTTPS", False)
USE_HTTPS = env.bool("USE_HTTPS", not DEBUG)
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", ["*"])
@ -67,9 +87,10 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.humanize",
"django_rename_app",
"sass_processor",
"bookwyrm",
"celery",
"django_celery_beat",
"imagekit",
"storages",
]
@ -150,6 +171,9 @@ LOGGING = {
"handlers": ["console", "mail_admins"],
"level": LOG_LEVEL,
},
"django.utils.autoreload": {
"level": "INFO",
},
# Add a bookwyrm-specific logger
"bookwyrm": {
"handlers": ["console"],
@ -158,6 +182,18 @@ LOGGING = {
},
}
STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
"sass_processor.finders.CssFinder",
]
SASS_PROCESSOR_INCLUDE_FILE_PATTERN = r"^.+\.[s]{0,1}(?:a|c)ss$"
SASS_PROCESSOR_ENABLED = True
# minify css is production but not dev
if not DEBUG:
SASS_OUTPUT_STYLE = "compressed"
WSGI_APPLICATION = "bookwyrm.wsgi.application"
@ -176,7 +212,7 @@ STREAMS = [
# Search configuration
# total time in seconds that the instance will spend searching connectors
SEARCH_TIMEOUT = int(env("SEARCH_TIMEOUT", 15))
SEARCH_TIMEOUT = int(env("SEARCH_TIMEOUT", 8))
# timeout for a query to an individual connector
QUERY_TIMEOUT = int(env("QUERY_TIMEOUT", 5))
@ -188,7 +224,6 @@ if env("USE_DUMMY_CACHE", False):
}
}
else:
# pylint: disable=line-too-long
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
@ -223,7 +258,6 @@ AUTH_USER_MODEL = "bookwyrm.User"
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
# pylint: disable=line-too-long
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
@ -250,11 +284,13 @@ LANGUAGES = [
("es-es", _("Español (Spanish)")),
("gl-es", _("Galego (Galician)")),
("it-it", _("Italiano (Italian)")),
("fi-fi", _("Suomi (Finnish)")),
("fr-fr", _("Français (French)")),
("lt-lt", _("Lietuvių (Lithuanian)")),
("no-no", _("Norsk (Norwegian)")),
("pt-br", _("Português do Brasil (Brazilian Portuguese)")),
("pt-pt", _("Português Europeu (European Portuguese)")),
("ro-ro", _("Română (Romanian)")),
("sv-se", _("Svenska (Swedish)")),
("zh-hans", _("简体中文 (Simplified Chinese)")),
("zh-hant", _("繁體中文 (Traditional Chinese)")),
@ -311,13 +347,12 @@ if USE_S3:
MEDIA_FULL_URL = MEDIA_URL
STATIC_FULL_URL = STATIC_URL
DEFAULT_FILE_STORAGE = "bookwyrm.storage_backends.ImagesStorage"
# I don't know if it's used, but the site crashes without it
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
else:
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_URL = "/images/"
MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}"
STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}"
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT", None)
OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS", None)
OTEL_SERVICE_NAME = env("OTEL_SERVICE_NAME", None)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
@charset "utf-8";
@import "vendor/bulma/bulma.sass";
@import "bookwyrm/all.scss";

View file

@ -0,0 +1,164 @@
/** Imports
******************************************************************************/
@import "components/avatar";
@import "components/barcode";
@import "components/book_cover";
@import "components/book_grid";
@import "components/book_list";
@import "components/book_preview_table";
@import "components/breadcrumbs";
@import "components/copy";
@import "components/details";
@import "components/file_input";
@import "components/live_message";
@import "components/shelving";
@import "components/stars";
@import "components/status";
@import "components/tabs";
@import "components/toggle";
@import "overrides/bulma_overrides";
@import "utilities/a11y";
@import "utilities/alignments";
@import "utilities/colors";
@import "utilities/size";
@import "utilities/spacings";
@import "utilities/transitions";
html {
scroll-behavior: smooth;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-thumb {
background: $scrollbar-thumb;
border-radius: 0.5em;
}
::-webkit-scrollbar-track {
background: $scrollbar-track;
}
button {
border: none;
margin: 0;
padding: 0;
width: auto;
overflow: visible;
background: transparent;
/* inherit font, color & alignment from ancestor */
color: inherit;
font: inherit;
text-align: inherit;
/* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */
line-height: normal;
/* Corrects font smoothing for webkit */
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
/* Corrects inability to style clickable `input` types in iOS */
-webkit-appearance: none;
/* Generalizes pointer cursor */
cursor: pointer;
}
button::-moz-focus-inner {
/* Remove excess padding and border in Firefox 4+ */
border: 0;
padding: 0;
}
/* Better accessibility for keyboard users */
*:focus-visible {
outline-style: auto !important;
}
/** Utilities not covered by Bulma
******************************************************************************/
.tag.is-small {
height: auto;
}
.button.is-transparent {
background-color: transparent;
}
.card.is-stretchable {
display: flex;
flex-direction: column;
height: 100%;
}
.card.is-stretchable .card-content {
flex-grow: 1;
}
.preserve-whitespace p {
white-space: pre-wrap !important;
}
.display-inline p {
display: inline !important;
}
button .button-invisible-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 1rem;
box-sizing: border-box;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
background: $invisible-overlay-background-color;
color: white;
opacity: 0;
transition: opacity 0.2s ease;
}
button:hover .button-invisible-overlay,
button:active .button-invisible-overlay,
button:focus-visible .button-invisible-overlay {
opacity: 1;
}
/** States
******************************************************************************/
/* "disabled" for non-buttons */
.is-disabled {
background-color: $pagination-disabled-background-color;
border-color: $pagination-disabled-border-color;
box-shadow: none;
color: $pagination-disabled-color;
opacity: 0.5;
cursor: not-allowed;
}
/* Notifications page
******************************************************************************/
.notification a.icon {
text-decoration: none !important;
}

View file

@ -0,0 +1,7 @@
/** Avatars
******************************************************************************/
.avatar {
vertical-align: middle;
display: inline;
}

View file

@ -0,0 +1,26 @@
/* Barcode scanner CSS */
#barcode-scanner {
position: relative;
max-width: 100%;
text-align: center;
height: calc(70vh - 200px);
video {
height: calc(70vh - 200px);
max-width: 100%;
}
canvas {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: auto;
height: calc(70vh - 200px);
max-width: 100%;
}
}
#barcode-camera-list {
float: right;
}

View file

@ -0,0 +1,70 @@
/** Book covers
*
* - .is-cover gives the behaviour of the cover and its surrounding. (optional)
* - .cover-container gives the dimensions and position (for borders, image and other elements).
* - .book-cover is positioned and sized based on its container.
*
* To have the cover within specific dimensions, specify a width or height for
* standard bulmas named breapoints:
*
* `is-(w|h)-(auto|xs|s|m|l|xl|xxl)[-(mobile|tablet|desktop)]`
*
* The cover will be centered horizontally and vertically within those dimensions.
*
* When using `.column.is-N`, add `.is-w-auto` to the container so that the flex
* calculations are not biased by the default `max-content`.
******************************************************************************/
.column.is-cover {
flex-grow: 0 !important;
}
.column.is-cover,
.column.is-cover + .column {
flex-basis: auto !important;
}
.cover-container {
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: max-content;
max-width: 100%;
overflow: hidden;
}
/* Book cover
* -------------------------------------------------------------------------- */
.book-cover {
display: block;
max-width: 100%;
max-height: 100%;
/* Useful when stretching under-sized images. */
image-rendering: optimizequality;
image-rendering: smooth;
}
/* Cover caption
* -------------------------------------------------------------------------- */
.no-cover .cover-caption {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 0.5em;
font-size: 0.75em;
color: white;
background-color: $no-cover-color;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 1em;
white-space: initial;
text-align: center;
}

View file

@ -0,0 +1,36 @@
/* Books grid
******************************************************************************/
.books-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
align-items: end;
justify-items: stretch;
}
.books-grid > .is-big {
grid-column: span 2;
grid-row: span 2;
justify-self: stretch;
}
.books-grid .book-cover {
width: 100%;
}
.books-grid .book-title {
--height-basis: 1.35rem;
display: block;
margin-top: 0.5rem;
line-height: var(--height-basis);
min-height: calc(2 * var(--height-basis));
}
@media only screen and (min-width: 769px) {
.books-grid {
gap: 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(8em, 1fr));
}
}

View file

@ -0,0 +1,47 @@
/* Book list
******************************************************************************/
ol.ordered-list {
list-style: none;
counter-reset: list-counter;
}
ol.ordered-list li {
counter-increment: list-counter;
}
ol.ordered-list li::before {
content: counter(list-counter);
position: absolute;
left: -20px;
width: 20px;
height: 24px;
background-color: $scheme-main;
border: 1px solid $border;
border-right: 0;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
color: $text-light;
font-size: 0.8em;
font-weight: bold;
}
@media only screen and (max-width: 768px) {
ol.ordered-list li::before {
left: 0;
z-index: 1;
border: 0;
border-right: 1px solid $border;
border-bottom: 1px solid $border;
border-radius: 0;
border-bottom-right-radius: 2px;
}
}
.overflow-wrap-anywhere {
overflow-wrap: anywhere;
min-width: 10em;
}

View file

@ -0,0 +1,49 @@
/* Book preview table
******************************************************************************/
.book-preview td {
vertical-align: middle;
}
@media only screen and (max-width: 768px) {
table.is-mobile,
table.is-mobile tbody {
display: block;
}
table.is-mobile tr {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
border-top: 1px solid $border;
}
table.is-mobile td {
display: block;
box-sizing: border-box;
flex: 1 0 100%;
order: 2;
border-bottom: 0;
}
table.is-mobile td.book-preview-top-row {
order: 1;
flex-basis: auto;
}
table.is-mobile td[data-title]:not(:empty)::before {
content: attr(data-title);
display: block;
font-size: 0.75em;
font-weight: bold;
}
table.is-mobile td:empty {
padding: 0;
}
table.is-mobile th,
table.is-mobile thead {
display: none;
}
}

View file

@ -0,0 +1,13 @@
/* Breadcrumbs
******************************************************************************/
.breadcrumb li:first-child * {
padding-left: 0;
}
.breadcrumb li > * {
align-items: center;
display: flex;
justify-content: center;
padding: 0 0.75em;
}

View file

@ -0,0 +1,30 @@
/* Copy
******************************************************************************/
.horizontal-copy {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
}
.horizontal-copy textarea {
min-width: initial;
white-space: nowrap;
}
.horizontal-copy button {
align-self: stretch;
height: unset;
}
.vertical-copy {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
}
.vertical-copy button {
width: 100%;
}

View file

@ -0,0 +1,130 @@
/** General `details` element styles
******************************************************************************/
details summary {
cursor: pointer;
}
summary::-webkit-details-marker {
display: none;
}
details summary::marker {
content: none;
}
details.detail-pinned-button summary {
position: absolute;
right: 0;
}
details.detail-pinned-button form {
float: left;
width: 100%;
margin-top: 1em;
}
/** Dropdown w/ Details element
******************************************************************************/
details.dropdown[open] summary.dropdown-trigger::before {
content: "";
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
details.dropdown .dropdown-menu {
display: block !important;
}
details.dropdown .dropdown-menu button {
/* Fix weird Safari defaults */
box-sizing: border-box;
}
details.dropdown .dropdown-menu button:focus-visible,
details.dropdown .dropdown-menu a:focus-visible {
outline-style: auto;
outline-offset: -2px;
}
@media only screen and (max-width: 768px) {
details.dropdown[open] summary.dropdown-trigger::before {
background-color: $modal-background-background-color;
z-index: 30;
}
details .dropdown-menu {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex !important;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 100;
}
details .dropdown-menu > * {
pointer-events: all;
}
}
/** Details panel
******************************************************************************/
details.details-panel {
box-shadow: 0 0 0 1px $border;
transition: box-shadow 0.2s ease;
padding: 0.75rem;
}
details[open].details-panel,
details.details-panel:hover {
box-shadow: 0 0 0 1px $border;
}
details.details-panel summary {
position: relative;
}
details summary .details-close {
position: absolute;
right: 0;
top: 0;
transform: rotate(45deg);
transition: transform 0.2s ease;
}
details[open] summary .details-close {
transform: rotate(0deg);
}
@media only screen and (min-width: 769px) {
.details-panel .filters-field:not(:last-child) {
border-right: 1px solid $border;
margin-top: 0.75rem;
margin-bottom: 0.75rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
}
/** Navbar details
******************************************************************************/
#navbar-dropdown .navbar-item {
color: $text;
font-size: 0.875rem;
padding: 0.375rem 3rem 0.375rem 1rem;
white-space: nowrap;
}
#navbar-dropdown .navbar-item:hover {
background-color: $background-secondary;
}

View file

@ -0,0 +1,28 @@
/** File input styles
******************************************************************************/
input[type="file"]::file-selector-button {
-moz-appearance: none;
-webkit-appearance: none;
background-color: $scheme-main;
border-radius: 4px;
border: 1px solid $border;
box-shadow: none;
color: $text;
cursor: pointer;
font-size: 1rem;
height: 2.5em;
justify-content: center;
line-height: 1.5;
padding-bottom: calc(0.5em - 1px);
padding-left: 1em;
padding-right: 1em;
padding-top: calc(0.5em - 1px);
text-align: center;
white-space: nowrap;
}
input[type="file"]::file-selector-button:hover {
border-color: $border-hover;
color: text;
}

View file

@ -0,0 +1,8 @@
/** Transient notification
******************************************************************************/
#live-messages {
position: fixed;
bottom: 1em;
right: 1em;
}

View file

@ -0,0 +1,10 @@
/** Shelving
******************************************************************************/
/** @todo Replace icons with SVG symbols.
@see https://www.youtube.com/watch?v=9xXBYcWgCHA */
.shelf-option:disabled > *::after {
font-family: icomoon; /* stylelint-disable font-family-no-missing-generic-family-keyword */
content: "\e919"; /* icon-check */
margin-left: 0.5em;
}

View file

@ -0,0 +1,52 @@
/** Stars
******************************************************************************/
.stars {
white-space: nowrap;
}
/** Stars in a review form
*
* Specificity makes hovering taking over checked inputs.
*
* \e9d9: filled star
* \e9d7: empty star;
* -------------------------------------------------------------------------- */
.form-rate-stars {
width: max-content;
}
/* All stars are visually filled by default. */
.form-rate-stars .icon::before {
content: "\e9d9"; /* icon-star-full */
}
/* Icons directly following half star inputs are marked as half */
.form-rate-stars input.half:checked ~ .icon::before {
content: "\e9d8"; /* icon-star-half */
}
/* stylelint-disable no-descending-specificity */
.form-rate-stars input.half:checked + input + .icon:hover::before {
content: "\e9d8" !important; /* icon-star-half */
}
/* Icons directly following half check inputs that follow the checked input are emptied. */
.form-rate-stars input.half:checked + input + .icon ~ .icon::before {
content: "\e9d7"; /* icon-star-empty */
}
/* Icons directly following inputs that follow the checked input are emptied. */
.form-rate-stars input:checked ~ input + .icon::before {
content: "\e9d7"; /* icon-star-empty */
}
/* When a label is hovered, repeat the fill-all-then-empty-following pattern. */
.form-rate-stars:hover .icon.icon::before {
content: "\e9d9" !important; /* icon-star-full */
}
.form-rate-stars .icon:hover ~ .icon::before {
content: "\e9d7" !important; /* icon-star-empty */
}

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