Compare commits

...

50 commits

Author SHA1 Message Date
Bart Schuurmans c09f16a513
Merge d28e0a06bb into ad830dd885 2024-04-24 22:27:55 +00:00
Mouse Reeve d28e0a06bb
Merge branch 'main' into django-4.2 2024-04-24 15:27:53 -07:00
Bart Schuurmans 68b1994027 Add merge migration 2024-04-24 16:27:38 +02:00
Bart Schuurmans bcf4e1fac5 Add merge migration 2024-04-24 16:27:38 +02:00
Bart Schuurmans 6f5c817149 Define CSRF_TRUSTED_ORIGINS 2024-04-24 16:27:38 +02:00
Bart Schuurmans 630cfe4018 Replace deprecated CICharField with custom collation for case-insensitivity 2024-04-24 16:27:38 +02:00
Bart Schuurmans cfc3c7c625 Switch from django-redis to the built-in Redis cache backend 2024-04-24 16:27:38 +02:00
Bart Schuurmans 62376dbaeb Migrate from pytz to zoneinfo 2024-04-24 16:27:38 +02:00
Bart Schuurmans 4f400dda63 Update qrcode to 7.4.2 2024-04-24 16:27:13 +02:00
Bart Schuurmans e27ed32f7b Update pytest to 8.1.1 2024-04-24 16:27:13 +02:00
Bart Schuurmans f9bf37a745 Update responses to 0.25.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 065c64dd12 Update pytest-xdist to 3.5.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 8e2f43a9fd Update pytest-env to 1.1.3 2024-04-24 16:27:13 +02:00
Bart Schuurmans 3980d03dc2 Update types-Pillow to 10.2.0.20240331 2024-04-24 16:27:13 +02:00
Bart Schuurmans bd8dcacf74 Update types-requests to 2.31.0.20240311 2024-04-24 16:27:13 +02:00
Bart Schuurmans 2e7c154637 Group version constraints for indirect dependencies and change to >= 2024-04-24 16:27:13 +02:00
Bart Schuurmans d6135b0174 Remove protobuf as a direct dependency 2024-04-24 16:27:13 +02:00
Bart Schuurmans 48566fb046 Update redis-py to 5.0.3 2024-04-24 16:27:13 +02:00
Bart Schuurmans b6b79c92ad Update Markdown to 3.6 2024-04-24 16:27:13 +02:00
Bart Schuurmans 3ef631d556 Update celery to 5.3.6 2024-04-24 16:27:13 +02:00
Bart Schuurmans b7ce4e0f76 Update bleach to 6.1.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 89d8f514db Update aiohttp to 2.9.3 2024-04-24 16:27:13 +02:00
Bart Schuurmans f1637330b6 Update psycopg to 2.9.9 2024-04-24 16:27:13 +02:00
Bart Schuurmans 438951d03f Use headers dict instead of HTTP_* kwargs or request.META 2024-04-24 16:27:13 +02:00
Bart Schuurmans 446821471e Fix mypy errors 2024-04-24 16:27:13 +02:00
Bart Schuurmans 0794605478 Update mypy to 1.7.1 2024-04-24 16:27:13 +02:00
Bart Schuurmans 50e6c3f8bb Update django-stubs to 4.2.7 2024-04-24 16:27:13 +02:00
Bart Schuurmans 90d2f7fb9e Fix constructor arguments to SessionMiddleware in tests 2024-04-24 16:27:13 +02:00
Bart Schuurmans 62de9c25f9 Update python-dateutil to 2.9.0.post0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 56f3ef8c62 Update pyotp to 2.9.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 00bd7e4070 Update pycryptodome to 3.20.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 8ce8e45f86 Update opentelemetry dependencies 2024-04-24 16:27:13 +02:00
Bart Schuurmans 7ae03a81da Update libsass to 0.23.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans bdd98db748 Update django-sass-processor to 1.4 2024-04-24 16:27:13 +02:00
Bart Schuurmans 0780804ecf Update django-model-utils to 4.4.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans c47c3a3eb9 Update django-imagekit to 5.0.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans bffce145e8 Update django-csp to 3.8 2024-04-24 16:27:13 +02:00
Bart Schuurmans 1f15d57568 Update django-celery-beat to 2.6.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 1ebe40e90e Update boto3 to 1.34.74 2024-04-24 16:27:13 +02:00
Bart Schuurmans 05cf301573 Configure STORAGES using OPTIONS instead of subclassing 2024-04-24 16:27:13 +02:00
Bart Schuurmans 7e16601829 Update django-storages to 1.14.2
The problem that boto3 closes files has been worked around in django-storages.
2024-04-24 16:27:13 +02:00
Bart Schuurmans 2277c1dc74 Update pytest-cov to 5.0.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 0fa9208f32 Update environs to 11.0.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans 74f89cfa48 Update pytest-django to 4.8.0 2024-04-24 16:27:13 +02:00
Bart Schuurmans efc8550b38 django.utils.timezone.utc alias is deprecated 2024-04-24 16:27:11 +02:00
Bart Schuurmans 52a86a25ce Reorder operations in save() overrides
Accessing many-to-many relations before saving is no longer allowed.

Reorder all operations consistently:
1. Validations
2. Modify own fields
3. Perform save by calling super().save()
4. Modify related objects and clear caches

Especially clearing caches should be done after actually saving, otherwise the old data can be
re-added immediately by another request before the new data is written.
2024-04-24 15:42:00 +02:00
Bart Schuurmans 9c1c356520 Use new STORAGES setting 2024-04-24 15:41:59 +02:00
Bart Schuurmans fd15f1e552 settings.USE_L10N is deprecated 2024-04-24 15:41:07 +02:00
Bart Schuurmans a4b4948e34 Add migration resulting from Django 4.2 upgrade 2024-04-24 15:41:07 +02:00
Bart Schuurmans 89e3442b31 Upgrade Django to 4.2
- https://docs.djangoproject.com/en/5.0/releases/4.0/
- https://docs.djangoproject.com/en/5.0/releases/4.1/
- https://docs.djangoproject.com/en/5.0/releases/4.2/
2024-04-24 15:41:07 +02:00
47 changed files with 1044 additions and 267 deletions

View file

@ -145,7 +145,9 @@ def load_more_data(connector_id: str, book_id: str) -> None:
"""background the work of getting all 10,000 editions of LoTR"""
connector_info = models.Connector.objects.get(id=connector_id)
connector = load_connector(connector_info)
book = models.Book.objects.select_subclasses().get(id=book_id)
book = models.Book.objects.select_subclasses().get( # type: ignore[no-untyped-call]
id=book_id
)
connector.expand_book_data(book)
@ -156,7 +158,9 @@ def create_edition_task(
"""separate task for each of the 10,000 editions of LoTR"""
connector_info = models.Connector.objects.get(id=connector_id)
connector = load_connector(connector_info)
work = models.Work.objects.select_subclasses().get(id=work_id)
work = models.Work.objects.select_subclasses().get( # type: ignore[no-untyped-call]
id=work_id
)
connector.create_edition_from_data(work, data)

View file

@ -229,7 +229,7 @@ class Connector(AbstractConnector):
data = get_data(url)
except ConnectorException:
return ""
return data.get("extract", "")
return str(data.get("extract", ""))
def get_remote_id_from_model(self, obj: models.BookDataModel) -> str:
"""use get_remote_id to figure out the link from a model obj"""

View file

@ -1,5 +1,5 @@
""" Makes the app aware of the users timezone """
import pytz
import zoneinfo
from django.utils import timezone
@ -12,9 +12,7 @@ class TimezoneMiddleware:
def __call__(self, request):
if request.user.is_authenticated:
timezone.activate(pytz.timezone(request.user.preferred_timezone))
timezone.activate(zoneinfo.ZoneInfo(request.user.preferred_timezone))
else:
timezone.activate(pytz.utc)
response = self.get_response(request)
timezone.deactivate()
return response
timezone.deactivate()
return self.get_response(request)

View file

@ -10,6 +10,7 @@ class Migration(migrations.Migration):
]
operations = [
# The new timezones are "Factory" and "localtime"
migrations.AlterField(
model_name="user",
name="preferred_timezone",

View file

@ -1,9 +1,9 @@
# Generated by Django 3.2.23 on 2024-01-28 02:49
import bookwyrm.storage_backends
import django.core.serializers.json
from django.db import migrations, models
import django.db.models.deletion
from django.core.files.storage import storages
class Migration(migrations.Migration):
@ -30,7 +30,7 @@ class Migration(migrations.Migration):
name="export_data",
field=models.FileField(
null=True,
storage=bookwyrm.storage_backends.ExportsFileStorage,
storage=storages["exports"],
upload_to="",
),
),

View file

@ -0,0 +1,70 @@
# Generated by Django 4.2.11 on 2024-03-29 19:25
import bookwyrm.models.fields
from django.conf import settings
from django.db import migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0198_book_search_vector_author_aliases"),
]
operations = [
migrations.AlterField(
model_name="userblocks",
name="user_object",
field=bookwyrm.models.fields.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_user_object",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="userblocks",
name="user_subject",
field=bookwyrm.models.fields.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_user_subject",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="userfollowrequest",
name="user_object",
field=bookwyrm.models.fields.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_user_object",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="userfollowrequest",
name="user_subject",
field=bookwyrm.models.fields.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_user_subject",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="userfollows",
name="user_object",
field=bookwyrm.models.fields.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_user_object",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AlterField(
model_name="userfollows",
name="user_subject",
field=bookwyrm.models.fields.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="%(class)s_user_subject",
to=settings.AUTH_USER_MODEL,
),
),
]

View file

@ -0,0 +1,633 @@
# Generated by Django 4.2.11 on 2024-04-01 20:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0199_alter_userblocks_user_object_and_more"),
]
operations = [
migrations.AlterField(
model_name="user",
name="preferred_timezone",
field=models.CharField(
choices=[
("Africa/Abidjan", "Africa/Abidjan"),
("Africa/Accra", "Africa/Accra"),
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
("Africa/Algiers", "Africa/Algiers"),
("Africa/Asmara", "Africa/Asmara"),
("Africa/Asmera", "Africa/Asmera"),
("Africa/Bamako", "Africa/Bamako"),
("Africa/Bangui", "Africa/Bangui"),
("Africa/Banjul", "Africa/Banjul"),
("Africa/Bissau", "Africa/Bissau"),
("Africa/Blantyre", "Africa/Blantyre"),
("Africa/Brazzaville", "Africa/Brazzaville"),
("Africa/Bujumbura", "Africa/Bujumbura"),
("Africa/Cairo", "Africa/Cairo"),
("Africa/Casablanca", "Africa/Casablanca"),
("Africa/Ceuta", "Africa/Ceuta"),
("Africa/Conakry", "Africa/Conakry"),
("Africa/Dakar", "Africa/Dakar"),
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
("Africa/Djibouti", "Africa/Djibouti"),
("Africa/Douala", "Africa/Douala"),
("Africa/El_Aaiun", "Africa/El_Aaiun"),
("Africa/Freetown", "Africa/Freetown"),
("Africa/Gaborone", "Africa/Gaborone"),
("Africa/Harare", "Africa/Harare"),
("Africa/Johannesburg", "Africa/Johannesburg"),
("Africa/Juba", "Africa/Juba"),
("Africa/Kampala", "Africa/Kampala"),
("Africa/Khartoum", "Africa/Khartoum"),
("Africa/Kigali", "Africa/Kigali"),
("Africa/Kinshasa", "Africa/Kinshasa"),
("Africa/Lagos", "Africa/Lagos"),
("Africa/Libreville", "Africa/Libreville"),
("Africa/Lome", "Africa/Lome"),
("Africa/Luanda", "Africa/Luanda"),
("Africa/Lubumbashi", "Africa/Lubumbashi"),
("Africa/Lusaka", "Africa/Lusaka"),
("Africa/Malabo", "Africa/Malabo"),
("Africa/Maputo", "Africa/Maputo"),
("Africa/Maseru", "Africa/Maseru"),
("Africa/Mbabane", "Africa/Mbabane"),
("Africa/Mogadishu", "Africa/Mogadishu"),
("Africa/Monrovia", "Africa/Monrovia"),
("Africa/Nairobi", "Africa/Nairobi"),
("Africa/Ndjamena", "Africa/Ndjamena"),
("Africa/Niamey", "Africa/Niamey"),
("Africa/Nouakchott", "Africa/Nouakchott"),
("Africa/Ouagadougou", "Africa/Ouagadougou"),
("Africa/Porto-Novo", "Africa/Porto-Novo"),
("Africa/Sao_Tome", "Africa/Sao_Tome"),
("Africa/Timbuktu", "Africa/Timbuktu"),
("Africa/Tripoli", "Africa/Tripoli"),
("Africa/Tunis", "Africa/Tunis"),
("Africa/Windhoek", "Africa/Windhoek"),
("America/Adak", "America/Adak"),
("America/Anchorage", "America/Anchorage"),
("America/Anguilla", "America/Anguilla"),
("America/Antigua", "America/Antigua"),
("America/Araguaina", "America/Araguaina"),
(
"America/Argentina/Buenos_Aires",
"America/Argentina/Buenos_Aires",
),
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
(
"America/Argentina/ComodRivadavia",
"America/Argentina/ComodRivadavia",
),
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
(
"America/Argentina/Rio_Gallegos",
"America/Argentina/Rio_Gallegos",
),
("America/Argentina/Salta", "America/Argentina/Salta"),
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
("America/Aruba", "America/Aruba"),
("America/Asuncion", "America/Asuncion"),
("America/Atikokan", "America/Atikokan"),
("America/Atka", "America/Atka"),
("America/Bahia", "America/Bahia"),
("America/Bahia_Banderas", "America/Bahia_Banderas"),
("America/Barbados", "America/Barbados"),
("America/Belem", "America/Belem"),
("America/Belize", "America/Belize"),
("America/Blanc-Sablon", "America/Blanc-Sablon"),
("America/Boa_Vista", "America/Boa_Vista"),
("America/Bogota", "America/Bogota"),
("America/Boise", "America/Boise"),
("America/Buenos_Aires", "America/Buenos_Aires"),
("America/Cambridge_Bay", "America/Cambridge_Bay"),
("America/Campo_Grande", "America/Campo_Grande"),
("America/Cancun", "America/Cancun"),
("America/Caracas", "America/Caracas"),
("America/Catamarca", "America/Catamarca"),
("America/Cayenne", "America/Cayenne"),
("America/Cayman", "America/Cayman"),
("America/Chicago", "America/Chicago"),
("America/Chihuahua", "America/Chihuahua"),
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
("America/Coral_Harbour", "America/Coral_Harbour"),
("America/Cordoba", "America/Cordoba"),
("America/Costa_Rica", "America/Costa_Rica"),
("America/Creston", "America/Creston"),
("America/Cuiaba", "America/Cuiaba"),
("America/Curacao", "America/Curacao"),
("America/Danmarkshavn", "America/Danmarkshavn"),
("America/Dawson", "America/Dawson"),
("America/Dawson_Creek", "America/Dawson_Creek"),
("America/Denver", "America/Denver"),
("America/Detroit", "America/Detroit"),
("America/Dominica", "America/Dominica"),
("America/Edmonton", "America/Edmonton"),
("America/Eirunepe", "America/Eirunepe"),
("America/El_Salvador", "America/El_Salvador"),
("America/Ensenada", "America/Ensenada"),
("America/Fort_Nelson", "America/Fort_Nelson"),
("America/Fort_Wayne", "America/Fort_Wayne"),
("America/Fortaleza", "America/Fortaleza"),
("America/Glace_Bay", "America/Glace_Bay"),
("America/Godthab", "America/Godthab"),
("America/Goose_Bay", "America/Goose_Bay"),
("America/Grand_Turk", "America/Grand_Turk"),
("America/Grenada", "America/Grenada"),
("America/Guadeloupe", "America/Guadeloupe"),
("America/Guatemala", "America/Guatemala"),
("America/Guayaquil", "America/Guayaquil"),
("America/Guyana", "America/Guyana"),
("America/Halifax", "America/Halifax"),
("America/Havana", "America/Havana"),
("America/Hermosillo", "America/Hermosillo"),
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
("America/Indiana/Knox", "America/Indiana/Knox"),
("America/Indiana/Marengo", "America/Indiana/Marengo"),
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
("America/Indiana/Vevay", "America/Indiana/Vevay"),
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
("America/Indiana/Winamac", "America/Indiana/Winamac"),
("America/Indianapolis", "America/Indianapolis"),
("America/Inuvik", "America/Inuvik"),
("America/Iqaluit", "America/Iqaluit"),
("America/Jamaica", "America/Jamaica"),
("America/Jujuy", "America/Jujuy"),
("America/Juneau", "America/Juneau"),
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
("America/Knox_IN", "America/Knox_IN"),
("America/Kralendijk", "America/Kralendijk"),
("America/La_Paz", "America/La_Paz"),
("America/Lima", "America/Lima"),
("America/Los_Angeles", "America/Los_Angeles"),
("America/Louisville", "America/Louisville"),
("America/Lower_Princes", "America/Lower_Princes"),
("America/Maceio", "America/Maceio"),
("America/Managua", "America/Managua"),
("America/Manaus", "America/Manaus"),
("America/Marigot", "America/Marigot"),
("America/Martinique", "America/Martinique"),
("America/Matamoros", "America/Matamoros"),
("America/Mazatlan", "America/Mazatlan"),
("America/Mendoza", "America/Mendoza"),
("America/Menominee", "America/Menominee"),
("America/Merida", "America/Merida"),
("America/Metlakatla", "America/Metlakatla"),
("America/Mexico_City", "America/Mexico_City"),
("America/Miquelon", "America/Miquelon"),
("America/Moncton", "America/Moncton"),
("America/Monterrey", "America/Monterrey"),
("America/Montevideo", "America/Montevideo"),
("America/Montreal", "America/Montreal"),
("America/Montserrat", "America/Montserrat"),
("America/Nassau", "America/Nassau"),
("America/New_York", "America/New_York"),
("America/Nipigon", "America/Nipigon"),
("America/Nome", "America/Nome"),
("America/Noronha", "America/Noronha"),
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
("America/North_Dakota/Center", "America/North_Dakota/Center"),
(
"America/North_Dakota/New_Salem",
"America/North_Dakota/New_Salem",
),
("America/Nuuk", "America/Nuuk"),
("America/Ojinaga", "America/Ojinaga"),
("America/Panama", "America/Panama"),
("America/Pangnirtung", "America/Pangnirtung"),
("America/Paramaribo", "America/Paramaribo"),
("America/Phoenix", "America/Phoenix"),
("America/Port-au-Prince", "America/Port-au-Prince"),
("America/Port_of_Spain", "America/Port_of_Spain"),
("America/Porto_Acre", "America/Porto_Acre"),
("America/Porto_Velho", "America/Porto_Velho"),
("America/Puerto_Rico", "America/Puerto_Rico"),
("America/Punta_Arenas", "America/Punta_Arenas"),
("America/Rainy_River", "America/Rainy_River"),
("America/Rankin_Inlet", "America/Rankin_Inlet"),
("America/Recife", "America/Recife"),
("America/Regina", "America/Regina"),
("America/Resolute", "America/Resolute"),
("America/Rio_Branco", "America/Rio_Branco"),
("America/Rosario", "America/Rosario"),
("America/Santa_Isabel", "America/Santa_Isabel"),
("America/Santarem", "America/Santarem"),
("America/Santiago", "America/Santiago"),
("America/Santo_Domingo", "America/Santo_Domingo"),
("America/Sao_Paulo", "America/Sao_Paulo"),
("America/Scoresbysund", "America/Scoresbysund"),
("America/Shiprock", "America/Shiprock"),
("America/Sitka", "America/Sitka"),
("America/St_Barthelemy", "America/St_Barthelemy"),
("America/St_Johns", "America/St_Johns"),
("America/St_Kitts", "America/St_Kitts"),
("America/St_Lucia", "America/St_Lucia"),
("America/St_Thomas", "America/St_Thomas"),
("America/St_Vincent", "America/St_Vincent"),
("America/Swift_Current", "America/Swift_Current"),
("America/Tegucigalpa", "America/Tegucigalpa"),
("America/Thule", "America/Thule"),
("America/Thunder_Bay", "America/Thunder_Bay"),
("America/Tijuana", "America/Tijuana"),
("America/Toronto", "America/Toronto"),
("America/Tortola", "America/Tortola"),
("America/Vancouver", "America/Vancouver"),
("America/Virgin", "America/Virgin"),
("America/Whitehorse", "America/Whitehorse"),
("America/Winnipeg", "America/Winnipeg"),
("America/Yakutat", "America/Yakutat"),
("America/Yellowknife", "America/Yellowknife"),
("Antarctica/Casey", "Antarctica/Casey"),
("Antarctica/Davis", "Antarctica/Davis"),
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
("Antarctica/Macquarie", "Antarctica/Macquarie"),
("Antarctica/Mawson", "Antarctica/Mawson"),
("Antarctica/McMurdo", "Antarctica/McMurdo"),
("Antarctica/Palmer", "Antarctica/Palmer"),
("Antarctica/Rothera", "Antarctica/Rothera"),
("Antarctica/South_Pole", "Antarctica/South_Pole"),
("Antarctica/Syowa", "Antarctica/Syowa"),
("Antarctica/Troll", "Antarctica/Troll"),
("Antarctica/Vostok", "Antarctica/Vostok"),
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
("Asia/Aden", "Asia/Aden"),
("Asia/Almaty", "Asia/Almaty"),
("Asia/Amman", "Asia/Amman"),
("Asia/Anadyr", "Asia/Anadyr"),
("Asia/Aqtau", "Asia/Aqtau"),
("Asia/Aqtobe", "Asia/Aqtobe"),
("Asia/Ashgabat", "Asia/Ashgabat"),
("Asia/Ashkhabad", "Asia/Ashkhabad"),
("Asia/Atyrau", "Asia/Atyrau"),
("Asia/Baghdad", "Asia/Baghdad"),
("Asia/Bahrain", "Asia/Bahrain"),
("Asia/Baku", "Asia/Baku"),
("Asia/Bangkok", "Asia/Bangkok"),
("Asia/Barnaul", "Asia/Barnaul"),
("Asia/Beirut", "Asia/Beirut"),
("Asia/Bishkek", "Asia/Bishkek"),
("Asia/Brunei", "Asia/Brunei"),
("Asia/Calcutta", "Asia/Calcutta"),
("Asia/Chita", "Asia/Chita"),
("Asia/Choibalsan", "Asia/Choibalsan"),
("Asia/Chongqing", "Asia/Chongqing"),
("Asia/Chungking", "Asia/Chungking"),
("Asia/Colombo", "Asia/Colombo"),
("Asia/Dacca", "Asia/Dacca"),
("Asia/Damascus", "Asia/Damascus"),
("Asia/Dhaka", "Asia/Dhaka"),
("Asia/Dili", "Asia/Dili"),
("Asia/Dubai", "Asia/Dubai"),
("Asia/Dushanbe", "Asia/Dushanbe"),
("Asia/Famagusta", "Asia/Famagusta"),
("Asia/Gaza", "Asia/Gaza"),
("Asia/Harbin", "Asia/Harbin"),
("Asia/Hebron", "Asia/Hebron"),
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
("Asia/Hong_Kong", "Asia/Hong_Kong"),
("Asia/Hovd", "Asia/Hovd"),
("Asia/Irkutsk", "Asia/Irkutsk"),
("Asia/Istanbul", "Asia/Istanbul"),
("Asia/Jakarta", "Asia/Jakarta"),
("Asia/Jayapura", "Asia/Jayapura"),
("Asia/Jerusalem", "Asia/Jerusalem"),
("Asia/Kabul", "Asia/Kabul"),
("Asia/Kamchatka", "Asia/Kamchatka"),
("Asia/Karachi", "Asia/Karachi"),
("Asia/Kashgar", "Asia/Kashgar"),
("Asia/Kathmandu", "Asia/Kathmandu"),
("Asia/Katmandu", "Asia/Katmandu"),
("Asia/Khandyga", "Asia/Khandyga"),
("Asia/Kolkata", "Asia/Kolkata"),
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
("Asia/Kuching", "Asia/Kuching"),
("Asia/Kuwait", "Asia/Kuwait"),
("Asia/Macao", "Asia/Macao"),
("Asia/Macau", "Asia/Macau"),
("Asia/Magadan", "Asia/Magadan"),
("Asia/Makassar", "Asia/Makassar"),
("Asia/Manila", "Asia/Manila"),
("Asia/Muscat", "Asia/Muscat"),
("Asia/Nicosia", "Asia/Nicosia"),
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
("Asia/Novosibirsk", "Asia/Novosibirsk"),
("Asia/Omsk", "Asia/Omsk"),
("Asia/Oral", "Asia/Oral"),
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
("Asia/Pontianak", "Asia/Pontianak"),
("Asia/Pyongyang", "Asia/Pyongyang"),
("Asia/Qatar", "Asia/Qatar"),
("Asia/Qostanay", "Asia/Qostanay"),
("Asia/Qyzylorda", "Asia/Qyzylorda"),
("Asia/Rangoon", "Asia/Rangoon"),
("Asia/Riyadh", "Asia/Riyadh"),
("Asia/Saigon", "Asia/Saigon"),
("Asia/Sakhalin", "Asia/Sakhalin"),
("Asia/Samarkand", "Asia/Samarkand"),
("Asia/Seoul", "Asia/Seoul"),
("Asia/Shanghai", "Asia/Shanghai"),
("Asia/Singapore", "Asia/Singapore"),
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
("Asia/Taipei", "Asia/Taipei"),
("Asia/Tashkent", "Asia/Tashkent"),
("Asia/Tbilisi", "Asia/Tbilisi"),
("Asia/Tehran", "Asia/Tehran"),
("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
("Asia/Thimbu", "Asia/Thimbu"),
("Asia/Thimphu", "Asia/Thimphu"),
("Asia/Tokyo", "Asia/Tokyo"),
("Asia/Tomsk", "Asia/Tomsk"),
("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
("Asia/Urumqi", "Asia/Urumqi"),
("Asia/Ust-Nera", "Asia/Ust-Nera"),
("Asia/Vientiane", "Asia/Vientiane"),
("Asia/Vladivostok", "Asia/Vladivostok"),
("Asia/Yakutsk", "Asia/Yakutsk"),
("Asia/Yangon", "Asia/Yangon"),
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
("Asia/Yerevan", "Asia/Yerevan"),
("Atlantic/Azores", "Atlantic/Azores"),
("Atlantic/Bermuda", "Atlantic/Bermuda"),
("Atlantic/Canary", "Atlantic/Canary"),
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
("Atlantic/Faeroe", "Atlantic/Faeroe"),
("Atlantic/Faroe", "Atlantic/Faroe"),
("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
("Atlantic/Madeira", "Atlantic/Madeira"),
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
("Atlantic/St_Helena", "Atlantic/St_Helena"),
("Atlantic/Stanley", "Atlantic/Stanley"),
("Australia/ACT", "Australia/ACT"),
("Australia/Adelaide", "Australia/Adelaide"),
("Australia/Brisbane", "Australia/Brisbane"),
("Australia/Broken_Hill", "Australia/Broken_Hill"),
("Australia/Canberra", "Australia/Canberra"),
("Australia/Currie", "Australia/Currie"),
("Australia/Darwin", "Australia/Darwin"),
("Australia/Eucla", "Australia/Eucla"),
("Australia/Hobart", "Australia/Hobart"),
("Australia/LHI", "Australia/LHI"),
("Australia/Lindeman", "Australia/Lindeman"),
("Australia/Lord_Howe", "Australia/Lord_Howe"),
("Australia/Melbourne", "Australia/Melbourne"),
("Australia/NSW", "Australia/NSW"),
("Australia/North", "Australia/North"),
("Australia/Perth", "Australia/Perth"),
("Australia/Queensland", "Australia/Queensland"),
("Australia/South", "Australia/South"),
("Australia/Sydney", "Australia/Sydney"),
("Australia/Tasmania", "Australia/Tasmania"),
("Australia/Victoria", "Australia/Victoria"),
("Australia/West", "Australia/West"),
("Australia/Yancowinna", "Australia/Yancowinna"),
("Brazil/Acre", "Brazil/Acre"),
("Brazil/DeNoronha", "Brazil/DeNoronha"),
("Brazil/East", "Brazil/East"),
("Brazil/West", "Brazil/West"),
("CET", "CET"),
("CST6CDT", "CST6CDT"),
("Canada/Atlantic", "Canada/Atlantic"),
("Canada/Central", "Canada/Central"),
("Canada/Eastern", "Canada/Eastern"),
("Canada/Mountain", "Canada/Mountain"),
("Canada/Newfoundland", "Canada/Newfoundland"),
("Canada/Pacific", "Canada/Pacific"),
("Canada/Saskatchewan", "Canada/Saskatchewan"),
("Canada/Yukon", "Canada/Yukon"),
("Chile/Continental", "Chile/Continental"),
("Chile/EasterIsland", "Chile/EasterIsland"),
("Cuba", "Cuba"),
("EET", "EET"),
("EST", "EST"),
("EST5EDT", "EST5EDT"),
("Egypt", "Egypt"),
("Eire", "Eire"),
("Etc/GMT", "Etc/GMT"),
("Etc/GMT+0", "Etc/GMT+0"),
("Etc/GMT+1", "Etc/GMT+1"),
("Etc/GMT+10", "Etc/GMT+10"),
("Etc/GMT+11", "Etc/GMT+11"),
("Etc/GMT+12", "Etc/GMT+12"),
("Etc/GMT+2", "Etc/GMT+2"),
("Etc/GMT+3", "Etc/GMT+3"),
("Etc/GMT+4", "Etc/GMT+4"),
("Etc/GMT+5", "Etc/GMT+5"),
("Etc/GMT+6", "Etc/GMT+6"),
("Etc/GMT+7", "Etc/GMT+7"),
("Etc/GMT+8", "Etc/GMT+8"),
("Etc/GMT+9", "Etc/GMT+9"),
("Etc/GMT-0", "Etc/GMT-0"),
("Etc/GMT-1", "Etc/GMT-1"),
("Etc/GMT-10", "Etc/GMT-10"),
("Etc/GMT-11", "Etc/GMT-11"),
("Etc/GMT-12", "Etc/GMT-12"),
("Etc/GMT-13", "Etc/GMT-13"),
("Etc/GMT-14", "Etc/GMT-14"),
("Etc/GMT-2", "Etc/GMT-2"),
("Etc/GMT-3", "Etc/GMT-3"),
("Etc/GMT-4", "Etc/GMT-4"),
("Etc/GMT-5", "Etc/GMT-5"),
("Etc/GMT-6", "Etc/GMT-6"),
("Etc/GMT-7", "Etc/GMT-7"),
("Etc/GMT-8", "Etc/GMT-8"),
("Etc/GMT-9", "Etc/GMT-9"),
("Etc/GMT0", "Etc/GMT0"),
("Etc/Greenwich", "Etc/Greenwich"),
("Etc/UCT", "Etc/UCT"),
("Etc/UTC", "Etc/UTC"),
("Etc/Universal", "Etc/Universal"),
("Etc/Zulu", "Etc/Zulu"),
("Europe/Amsterdam", "Europe/Amsterdam"),
("Europe/Andorra", "Europe/Andorra"),
("Europe/Astrakhan", "Europe/Astrakhan"),
("Europe/Athens", "Europe/Athens"),
("Europe/Belfast", "Europe/Belfast"),
("Europe/Belgrade", "Europe/Belgrade"),
("Europe/Berlin", "Europe/Berlin"),
("Europe/Bratislava", "Europe/Bratislava"),
("Europe/Brussels", "Europe/Brussels"),
("Europe/Bucharest", "Europe/Bucharest"),
("Europe/Budapest", "Europe/Budapest"),
("Europe/Busingen", "Europe/Busingen"),
("Europe/Chisinau", "Europe/Chisinau"),
("Europe/Copenhagen", "Europe/Copenhagen"),
("Europe/Dublin", "Europe/Dublin"),
("Europe/Gibraltar", "Europe/Gibraltar"),
("Europe/Guernsey", "Europe/Guernsey"),
("Europe/Helsinki", "Europe/Helsinki"),
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
("Europe/Istanbul", "Europe/Istanbul"),
("Europe/Jersey", "Europe/Jersey"),
("Europe/Kaliningrad", "Europe/Kaliningrad"),
("Europe/Kiev", "Europe/Kiev"),
("Europe/Kirov", "Europe/Kirov"),
("Europe/Kyiv", "Europe/Kyiv"),
("Europe/Lisbon", "Europe/Lisbon"),
("Europe/Ljubljana", "Europe/Ljubljana"),
("Europe/London", "Europe/London"),
("Europe/Luxembourg", "Europe/Luxembourg"),
("Europe/Madrid", "Europe/Madrid"),
("Europe/Malta", "Europe/Malta"),
("Europe/Mariehamn", "Europe/Mariehamn"),
("Europe/Minsk", "Europe/Minsk"),
("Europe/Monaco", "Europe/Monaco"),
("Europe/Moscow", "Europe/Moscow"),
("Europe/Nicosia", "Europe/Nicosia"),
("Europe/Oslo", "Europe/Oslo"),
("Europe/Paris", "Europe/Paris"),
("Europe/Podgorica", "Europe/Podgorica"),
("Europe/Prague", "Europe/Prague"),
("Europe/Riga", "Europe/Riga"),
("Europe/Rome", "Europe/Rome"),
("Europe/Samara", "Europe/Samara"),
("Europe/San_Marino", "Europe/San_Marino"),
("Europe/Sarajevo", "Europe/Sarajevo"),
("Europe/Saratov", "Europe/Saratov"),
("Europe/Simferopol", "Europe/Simferopol"),
("Europe/Skopje", "Europe/Skopje"),
("Europe/Sofia", "Europe/Sofia"),
("Europe/Stockholm", "Europe/Stockholm"),
("Europe/Tallinn", "Europe/Tallinn"),
("Europe/Tirane", "Europe/Tirane"),
("Europe/Tiraspol", "Europe/Tiraspol"),
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
("Europe/Uzhgorod", "Europe/Uzhgorod"),
("Europe/Vaduz", "Europe/Vaduz"),
("Europe/Vatican", "Europe/Vatican"),
("Europe/Vienna", "Europe/Vienna"),
("Europe/Vilnius", "Europe/Vilnius"),
("Europe/Volgograd", "Europe/Volgograd"),
("Europe/Warsaw", "Europe/Warsaw"),
("Europe/Zagreb", "Europe/Zagreb"),
("Europe/Zaporozhye", "Europe/Zaporozhye"),
("Europe/Zurich", "Europe/Zurich"),
("Factory", "Factory"),
("GB", "GB"),
("GB-Eire", "GB-Eire"),
("GMT", "GMT"),
("GMT+0", "GMT+0"),
("GMT-0", "GMT-0"),
("GMT0", "GMT0"),
("Greenwich", "Greenwich"),
("HST", "HST"),
("Hongkong", "Hongkong"),
("Iceland", "Iceland"),
("Indian/Antananarivo", "Indian/Antananarivo"),
("Indian/Chagos", "Indian/Chagos"),
("Indian/Christmas", "Indian/Christmas"),
("Indian/Cocos", "Indian/Cocos"),
("Indian/Comoro", "Indian/Comoro"),
("Indian/Kerguelen", "Indian/Kerguelen"),
("Indian/Mahe", "Indian/Mahe"),
("Indian/Maldives", "Indian/Maldives"),
("Indian/Mauritius", "Indian/Mauritius"),
("Indian/Mayotte", "Indian/Mayotte"),
("Indian/Reunion", "Indian/Reunion"),
("Iran", "Iran"),
("Israel", "Israel"),
("Jamaica", "Jamaica"),
("Japan", "Japan"),
("Kwajalein", "Kwajalein"),
("Libya", "Libya"),
("MET", "MET"),
("MST", "MST"),
("MST7MDT", "MST7MDT"),
("Mexico/BajaNorte", "Mexico/BajaNorte"),
("Mexico/BajaSur", "Mexico/BajaSur"),
("Mexico/General", "Mexico/General"),
("NZ", "NZ"),
("NZ-CHAT", "NZ-CHAT"),
("Navajo", "Navajo"),
("PRC", "PRC"),
("PST8PDT", "PST8PDT"),
("Pacific/Apia", "Pacific/Apia"),
("Pacific/Auckland", "Pacific/Auckland"),
("Pacific/Bougainville", "Pacific/Bougainville"),
("Pacific/Chatham", "Pacific/Chatham"),
("Pacific/Chuuk", "Pacific/Chuuk"),
("Pacific/Easter", "Pacific/Easter"),
("Pacific/Efate", "Pacific/Efate"),
("Pacific/Enderbury", "Pacific/Enderbury"),
("Pacific/Fakaofo", "Pacific/Fakaofo"),
("Pacific/Fiji", "Pacific/Fiji"),
("Pacific/Funafuti", "Pacific/Funafuti"),
("Pacific/Galapagos", "Pacific/Galapagos"),
("Pacific/Gambier", "Pacific/Gambier"),
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
("Pacific/Guam", "Pacific/Guam"),
("Pacific/Honolulu", "Pacific/Honolulu"),
("Pacific/Johnston", "Pacific/Johnston"),
("Pacific/Kanton", "Pacific/Kanton"),
("Pacific/Kiritimati", "Pacific/Kiritimati"),
("Pacific/Kosrae", "Pacific/Kosrae"),
("Pacific/Kwajalein", "Pacific/Kwajalein"),
("Pacific/Majuro", "Pacific/Majuro"),
("Pacific/Marquesas", "Pacific/Marquesas"),
("Pacific/Midway", "Pacific/Midway"),
("Pacific/Nauru", "Pacific/Nauru"),
("Pacific/Niue", "Pacific/Niue"),
("Pacific/Norfolk", "Pacific/Norfolk"),
("Pacific/Noumea", "Pacific/Noumea"),
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
("Pacific/Palau", "Pacific/Palau"),
("Pacific/Pitcairn", "Pacific/Pitcairn"),
("Pacific/Pohnpei", "Pacific/Pohnpei"),
("Pacific/Ponape", "Pacific/Ponape"),
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
("Pacific/Rarotonga", "Pacific/Rarotonga"),
("Pacific/Saipan", "Pacific/Saipan"),
("Pacific/Samoa", "Pacific/Samoa"),
("Pacific/Tahiti", "Pacific/Tahiti"),
("Pacific/Tarawa", "Pacific/Tarawa"),
("Pacific/Tongatapu", "Pacific/Tongatapu"),
("Pacific/Truk", "Pacific/Truk"),
("Pacific/Wake", "Pacific/Wake"),
("Pacific/Wallis", "Pacific/Wallis"),
("Pacific/Yap", "Pacific/Yap"),
("Poland", "Poland"),
("Portugal", "Portugal"),
("ROC", "ROC"),
("ROK", "ROK"),
("Singapore", "Singapore"),
("Turkey", "Turkey"),
("UCT", "UCT"),
("US/Alaska", "US/Alaska"),
("US/Aleutian", "US/Aleutian"),
("US/Arizona", "US/Arizona"),
("US/Central", "US/Central"),
("US/East-Indiana", "US/East-Indiana"),
("US/Eastern", "US/Eastern"),
("US/Hawaii", "US/Hawaii"),
("US/Indiana-Starke", "US/Indiana-Starke"),
("US/Michigan", "US/Michigan"),
("US/Mountain", "US/Mountain"),
("US/Pacific", "US/Pacific"),
("US/Samoa", "US/Samoa"),
("UTC", "UTC"),
("Universal", "Universal"),
("W-SU", "W-SU"),
("WET", "WET"),
("Zulu", "Zulu"),
("localtime", "localtime"),
],
default="UTC",
max_length=255,
),
),
]

View file

@ -0,0 +1,39 @@
# Generated by Django 4.2.11 on 2024-04-01 21:09
import bookwyrm.models.fields
from django.db import migrations, models
from django.contrib.postgres.operations import CreateCollation
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0200_alter_user_preferred_timezone"),
]
operations = [
CreateCollation(
"case_insensitive",
provider="icu",
locale="und-u-ks-level2",
deterministic=False,
),
migrations.AlterField(
model_name="hashtag",
name="name",
field=bookwyrm.models.fields.CharField(
db_collation="case_insensitive", max_length=256
),
),
migrations.AlterField(
model_name="user",
name="localname",
field=models.CharField(
db_collation="case_insensitive",
max_length=255,
null=True,
unique=True,
validators=[bookwyrm.models.fields.validate_localname],
),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 4.2.11 on 2024-04-10 20:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0201_alter_hashtag_name_alter_user_localname"),
("bookwyrm", "0204_merge_20240409_1042"),
]
operations = []

View file

@ -0,0 +1,13 @@
# Generated by Django 4.2.11 on 2024-04-15 15:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0205_merge_20240410_2022"),
("bookwyrm", "0205_merge_20240413_0232"),
]
operations = []

View file

@ -50,7 +50,7 @@ class Author(BookDataModel):
if self.isni:
self.isni = re.sub(r"\s", "", self.isni)
return super().save(*args, **kwargs)
super().save(*args, **kwargs)
@property
def isni_link(self):

View file

@ -103,7 +103,7 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
else:
self.origin_id = self.remote_id
self.remote_id = None
return super().save(*args, **kwargs)
super().save(*args, **kwargs)
# pylint: disable=arguments-differ
def broadcast(self, activity, sender, software="bookwyrm", **kwargs):
@ -323,7 +323,7 @@ class Book(BookDataModel):
if not isinstance(self, (Edition, Work)):
raise ValueError("Books should be added as Editions or Works")
return super().save(*args, **kwargs)
super().save(*args, **kwargs)
def get_remote_id(self):
"""editions and works both use "book" instead of model_name"""
@ -400,10 +400,11 @@ class Work(OrderedCollectionPageMixin, Book):
def save(self, *args, **kwargs):
"""set some fields on the edition object"""
super().save(*args, **kwargs)
# set rank
for edition in self.editions.all():
edition.save()
return super().save(*args, **kwargs)
@property
def default_edition(self):
@ -526,16 +527,16 @@ class Edition(Book):
# set rank
self.edition_rank = self.get_rank()
# clear author cache
if self.id:
for author_id in self.authors.values_list("id", flat=True):
cache.delete(f"author-books-{author_id}")
# Create sort title by removing articles from title
if self.sort_title in [None, ""]:
self.sort_title = self.guess_sort_title()
return super().save(*args, **kwargs)
super().save(*args, **kwargs)
# clear author cache
if self.id:
for author_id in self.authors.values_list("id", flat=True):
cache.delete(f"author-books-{author_id}")
@transaction.atomic
def repair(self):

View file

@ -10,9 +10,9 @@ from django.db.models import BooleanField, FileField, JSONField
from django.db.models import Q
from django.core.serializers.json import DjangoJSONEncoder
from django.core.files.base import ContentFile
from django.utils.module_loading import import_string
from django.core.files.storage import storages
from bookwyrm import settings, storage_backends
from bookwyrm import settings
from bookwyrm.models import AnnualGoal, ReadThrough, ShelfBook, ListItem
from bookwyrm.models import Review, Comment, Quotation
@ -35,8 +35,7 @@ class BookwyrmAwsSession(BotoSession):
def select_exports_storage():
"""callable to allow for dependency on runtime configuration"""
cls = import_string(settings.EXPORTS_STORAGE)
return cls()
return storages["exports"]
class BookwyrmExportJob(ParentJob):
@ -116,7 +115,7 @@ def create_archive_task(job_id):
if settings.USE_S3:
# Storage for writing temporary files
exports_storage = storage_backends.ExportsS3Storage()
exports_storage = storages["exports"]
# Handle for creating the final archive
s3_tar = S3Tar(
@ -136,7 +135,7 @@ def create_archive_task(job_id):
)
# Add images to TAR
images_storage = storage_backends.ImagesStorage()
images_storage = storages["default"]
if user.avatar:
add_file_to_s3_tar(s3_tar, images_storage, user.avatar)

View file

@ -2,18 +2,19 @@
from bookwyrm import activitypub
from .activitypub_mixin import ActivitypubMixin
from .base_model import BookWyrmModel
from .fields import CICharField
from .fields import CharField
class Hashtag(ActivitypubMixin, BookWyrmModel):
"a hashtag which can be used in statuses"
name = CICharField(
name = CharField(
max_length=256,
blank=False,
null=False,
activitypub_field="name",
deduplication_field=True,
db_collation="case_insensitive",
)
name_field = "name"

View file

@ -32,13 +32,15 @@ class ReadThrough(BookWyrmModel):
def save(self, *args, **kwargs):
"""update user active time"""
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 or self.stopped_date:
self.is_active = False
super().save(*args, **kwargs)
cache.delete(f"latest_read_through-{self.user_id}-{self.book_id}")
self.user.update_active_date()
def create_update(self):
"""add update to the readthrough"""
if self.progress:

View file

@ -38,14 +38,16 @@ class UserRelationship(BookWyrmModel):
def save(self, *args, **kwargs):
"""clear the template cache"""
clear_cache(self.user_subject, self.user_object)
super().save(*args, **kwargs)
clear_cache(self.user_subject, self.user_object)
def delete(self, *args, **kwargs):
"""clear the template cache"""
clear_cache(self.user_subject, self.user_object)
super().delete(*args, **kwargs)
clear_cache(self.user_subject, self.user_object)
class Meta:
"""relationships should be unique"""

View file

@ -44,6 +44,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
"""set the identifier"""
super().save(*args, priority=priority, **kwargs)
if not self.identifier:
# this needs the auto increment ID from the save() above
self.identifier = self.get_identifier()
super().save(*args, **kwargs, broadcast=False)
@ -103,7 +104,11 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
def save(self, *args, priority=BROADCAST, **kwargs):
if not self.user:
self.user = self.shelf.user
if self.id and self.user.local:
is_update = self.id is not None
super().save(*args, priority=priority, **kwargs)
if is_update and self.user.local:
# remove all caches related to all editions of this book
cache.delete_many(
[
@ -111,7 +116,6 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
for book in self.book.parent_work.editions.all()
]
)
super().save(*args, priority=priority, **kwargs)
def delete(self, *args, **kwargs):
if self.id and self.user.local:

View file

@ -139,13 +139,15 @@ class SiteSettings(SiteModel):
def save(self, *args, **kwargs):
"""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.invite_question_text:
self.invite_question_text = "What is your favourite book?"
super().save(*args, **kwargs)
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(SiteModel):

View file

@ -459,9 +459,10 @@ class Review(BookStatus):
def save(self, *args, **kwargs):
"""clear rating caches"""
super().save(*args, **kwargs)
if self.book.parent_work:
cache.delete(f"book-rating-{self.book.parent_work.id}")
super().save(*args, **kwargs)
class ReviewRating(Review):

View file

@ -1,18 +1,19 @@
""" database schema for user data """
import datetime
import re
import zoneinfo
from urllib.parse import urlparse
from uuid import uuid4
from django.apps import apps
from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import ArrayField, CICharField
from django.contrib.postgres.fields import ArrayField as DjangoArrayField
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
from django.dispatch import receiver
from django.db import models, transaction, IntegrityError
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from model_utils import FieldTracker
import pytz
from bookwyrm import activitypub
from bookwyrm.connectors import get_data, ConnectorException
@ -75,11 +76,12 @@ class User(OrderedCollectionPageMixin, AbstractUser):
summary = fields.HtmlField(null=True, blank=True)
local = models.BooleanField(default=False)
bookwyrm_user = fields.BooleanField(default=True)
localname = CICharField(
localname = models.CharField(
max_length=255,
null=True,
unique=True,
validators=[fields.validate_localname],
db_collation="case_insensitive",
)
# name is your display name, which you can change at will
name = fields.CharField(max_length=100, null=True, blank=True)
@ -156,7 +158,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
show_guided_tour = models.BooleanField(default=True)
# feed options
feed_status_types = ArrayField(
feed_status_types = DjangoArrayField(
models.CharField(max_length=10, blank=False, choices=FeedFilterChoices),
size=8,
default=get_feed_filter_choices,
@ -165,8 +167,8 @@ class User(OrderedCollectionPageMixin, AbstractUser):
summary_keys = models.JSONField(null=True)
preferred_timezone = models.CharField(
choices=[(str(tz), str(tz)) for tz in pytz.all_timezones],
default=str(pytz.utc),
choices=[(str(tz), str(tz)) for tz in sorted(zoneinfo.available_timezones())],
default=str(datetime.timezone.utc),
max_length=255,
)
preferred_language = models.CharField(

View file

@ -256,11 +256,8 @@ if env.bool("USE_DUMMY_CACHE", False):
else:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": REDIS_ACTIVITY_URL,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
"file_resubmit": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
@ -346,8 +343,6 @@ TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Imagekit generated thumbnails
@ -370,6 +365,7 @@ if (USE_HTTPS and PORT == 443) or (not USE_HTTPS and PORT == 80):
else:
NETLOC = f"{DOMAIN}:{PORT}"
BASE_URL = f"{PROTOCOL}://{NETLOC}"
CSRF_TRUSTED_ORIGINS = [BASE_URL]
USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +{BASE_URL})"
@ -390,18 +386,40 @@ if USE_S3:
AWS_DEFAULT_ACL = "public-read"
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"}
AWS_S3_URL_PROTOCOL = env("AWS_S3_URL_PROTOCOL", f"{PROTOCOL}:")
# Storages
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"location": "images",
"default_acl": "public-read",
"file_overwrite": False,
},
},
"staticfiles": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"location": "static",
"default_acl": "public-read",
},
},
"exports": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"location": "images",
"default_acl": None,
"file_overwrite": False,
},
},
}
# S3 Static settings
STATIC_LOCATION = "static"
STATIC_URL = f"{AWS_S3_URL_PROTOCOL}//{AWS_S3_CUSTOM_DOMAIN}/{STATIC_LOCATION}/"
STATIC_FULL_URL = STATIC_URL
STATICFILES_STORAGE = "bookwyrm.storage_backends.StaticStorage"
# S3 Media settings
MEDIA_LOCATION = "images"
MEDIA_URL = f"{AWS_S3_URL_PROTOCOL}//{AWS_S3_CUSTOM_DOMAIN}/{MEDIA_LOCATION}/"
MEDIA_FULL_URL = MEDIA_URL
DEFAULT_FILE_STORAGE = "bookwyrm.storage_backends.ImagesStorage"
# S3 Exports settings
EXPORTS_STORAGE = "bookwyrm.storage_backends.ExportsS3Storage"
# Content Security Policy
CSP_DEFAULT_SRC = [
"'self'",
@ -421,36 +439,62 @@ elif USE_AZURE:
AZURE_ACCOUNT_KEY = env("AZURE_ACCOUNT_KEY")
AZURE_CONTAINER = env("AZURE_CONTAINER")
AZURE_CUSTOM_DOMAIN = env("AZURE_CUSTOM_DOMAIN")
# Storages
STORAGES = {
"default": {
"BACKEND": "storages.backends.azure_storage.AzureStorage",
"OPTIONS": {
"location": "images",
"overwrite_files": False,
},
},
"staticfiles": {
"BACKEND": "storages.backends.azure_storage.AzureStorage",
"OPTIONS": {
"location": "static",
},
},
"exports": {
"BACKEND": None, # not implemented yet
},
}
# Azure Static settings
STATIC_LOCATION = "static"
STATIC_URL = (
f"{PROTOCOL}://{AZURE_CUSTOM_DOMAIN}/{AZURE_CONTAINER}/{STATIC_LOCATION}/"
)
STATIC_FULL_URL = STATIC_URL
STATICFILES_STORAGE = "bookwyrm.storage_backends.AzureStaticStorage"
# Azure Media settings
MEDIA_LOCATION = "images"
MEDIA_URL = (
f"{PROTOCOL}://{AZURE_CUSTOM_DOMAIN}/{AZURE_CONTAINER}/{MEDIA_LOCATION}/"
)
MEDIA_FULL_URL = MEDIA_URL
DEFAULT_FILE_STORAGE = "bookwyrm.storage_backends.AzureImagesStorage"
# Azure Exports settings
EXPORTS_STORAGE = None # not implemented yet
# Content Security Policy
CSP_DEFAULT_SRC = ["'self'", AZURE_CUSTOM_DOMAIN] + CSP_ADDITIONAL_HOSTS
CSP_SCRIPT_SRC = ["'self'", AZURE_CUSTOM_DOMAIN] + CSP_ADDITIONAL_HOSTS
else:
# Storages
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
"exports": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
"OPTIONS": {
"location": "exports",
},
},
}
# Static settings
STATIC_URL = "/static/"
STATIC_FULL_URL = BASE_URL + STATIC_URL
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
# Media settings
MEDIA_URL = "/images/"
MEDIA_FULL_URL = BASE_URL + MEDIA_URL
DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
# Exports settings
EXPORTS_STORAGE = "bookwyrm.storage_backends.ExportsFileStorage"
# Content Security Policy
CSP_DEFAULT_SRC = ["'self'"] + CSP_ADDITIONAL_HOSTS
CSP_SCRIPT_SRC = ["'self'"] + CSP_ADDITIONAL_HOSTS

View file

@ -1,79 +0,0 @@
"""Handles backends for storages"""
import os
from tempfile import SpooledTemporaryFile
from django.core.files.storage import FileSystemStorage
from storages.backends.s3boto3 import S3Boto3Storage
from storages.backends.azure_storage import AzureStorage
class StaticStorage(S3Boto3Storage): # pylint: disable=abstract-method
"""Storage class for Static contents"""
location = "static"
default_acl = "public-read"
class ImagesStorage(S3Boto3Storage): # pylint: disable=abstract-method
"""Storage class for Image files"""
location = "images"
default_acl = "public-read"
file_overwrite = False
"""
This is our custom version of S3Boto3Storage that fixes a bug in
boto3 where the passed in file is closed upon upload.
From:
https://github.com/matthewwithanm/django-imagekit/issues/391#issuecomment-275367006
https://github.com/boto/boto3/issues/929
https://github.com/matthewwithanm/django-imagekit/issues/391
"""
def _save(self, name, content):
"""
We create a clone of the content file as when this is passed to
boto3 it wrongly closes the file upon upload where as the storage
backend expects it to still be open
"""
# Seek our content back to the start
content.seek(0, os.SEEK_SET)
# Create a temporary file that will write to disk after a specified
# size. This file will be automatically deleted when closed by
# boto3 or after exiting the `with` statement if the boto3 is fixed
with SpooledTemporaryFile() as content_autoclose:
# Write our original content into our copy that will be closed by boto3
content_autoclose.write(content.read())
# Upload the object which will auto close the
# content_autoclose instance
return super()._save(name, content_autoclose)
class AzureStaticStorage(AzureStorage): # pylint: disable=abstract-method
"""Storage class for Static contents"""
location = "static"
class AzureImagesStorage(AzureStorage): # pylint: disable=abstract-method
"""Storage class for Image files"""
location = "images"
overwrite_files = False
class ExportsFileStorage(FileSystemStorage): # pylint: disable=abstract-method
"""Storage class for exports contents with local files"""
location = "exports"
overwrite_files = False
class ExportsS3Storage(S3Boto3Storage): # pylint: disable=abstract-method
"""Storage class for exports contents with S3"""
location = "exports"
default_acl = None
overwrite_files = False

View file

@ -1,8 +1,7 @@
""" testing activitystreams """
from datetime import datetime
from datetime import datetime, timezone
from unittest.mock import patch
from django.test import TestCase
from django.utils import timezone
from bookwyrm import activitystreams, models

View file

@ -1,5 +1,5 @@
""" testing activitystreams """
from datetime import datetime, timedelta
import datetime
from unittest.mock import patch
from django.test import TestCase
@ -71,8 +71,8 @@ class ActivitystreamsSignals(TestCase):
user=self.remote_user,
content="hi",
privacy="public",
created_date=datetime(2022, 5, 16, tzinfo=timezone.utc),
published_date=datetime(2022, 5, 14, tzinfo=timezone.utc),
created_date=datetime.datetime(2022, 5, 16, tzinfo=datetime.timezone.utc),
published_date=datetime.datetime(2022, 5, 14, tzinfo=datetime.timezone.utc),
)
with patch("bookwyrm.activitystreams.add_status_task.apply_async") as mock:
activitystreams.add_status_on_create_command(models.Status, status, False)
@ -87,7 +87,7 @@ class ActivitystreamsSignals(TestCase):
user=self.remote_user,
content="hi",
privacy="public",
published_date=timezone.now() - timedelta(days=1),
published_date=timezone.now() - datetime.timedelta(days=1),
)
with patch("bookwyrm.activitystreams.add_status_task.apply_async") as mock:
activitystreams.add_status_on_create_command(models.Status, status, False)

View file

@ -2,7 +2,6 @@
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
@ -13,7 +12,7 @@ from bookwyrm.models.import_job import handle_imported_book
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC)
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")

View file

@ -3,7 +3,6 @@ from collections import namedtuple
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
import responses
@ -16,7 +15,7 @@ from bookwyrm.models.import_job import handle_imported_book
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC)
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")

View file

@ -2,7 +2,6 @@
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
@ -13,7 +12,7 @@ from bookwyrm.models.import_job import handle_imported_book
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC)
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")

View file

@ -2,7 +2,6 @@
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
@ -13,7 +12,7 @@ from bookwyrm.models.import_job import handle_imported_book
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC)
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")

View file

@ -2,7 +2,6 @@
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
@ -13,7 +12,7 @@ from bookwyrm.models.import_job import handle_imported_book
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime.datetime(*args, tzinfo=pytz.UTC)
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")

View file

@ -1,10 +1,10 @@
""" testing models """
import datetime
from datetime import timezone
import json
import pathlib
from unittest.mock import patch
from django.utils import timezone
from django.test import TestCase
import responses

View file

@ -1,5 +1,5 @@
""" style fixes and lookups for templates """
from datetime import datetime
import datetime
from unittest.mock import patch
from django.test import TestCase
@ -95,14 +95,18 @@ class StatusDisplayTags(TestCase):
def test_get_published_date(self, *_):
"""date formatting"""
date = datetime(2020, 1, 1, 0, 0, tzinfo=timezone.utc)
date = datetime.datetime(2020, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
with patch("django.utils.timezone.now") as timezone_mock:
timezone_mock.return_value = datetime(2022, 1, 1, 0, 0, tzinfo=timezone.utc)
timezone_mock.return_value = datetime.datetime(
2022, 1, 1, 0, 0, tzinfo=datetime.timezone.utc
)
result = status_display.get_published_date(date)
self.assertEqual(result, "Jan. 1, 2020")
date = datetime(2022, 1, 1, 0, 0, tzinfo=timezone.utc)
date = datetime.datetime(2022, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
with patch("django.utils.timezone.now") as timezone_mock:
timezone_mock.return_value = datetime(2022, 1, 8, 0, 0, tzinfo=timezone.utc)
timezone_mock.return_value = datetime.datetime(
2022, 1, 8, 0, 0, tzinfo=datetime.timezone.utc
)
result = status_display.get_published_date(date)
self.assertEqual(result, "Jan 1")

View file

@ -1,8 +1,9 @@
""" test searching for books """
import datetime
from datetime import timezone
from django.db import connection
from django.test import TestCase
from django.utils import timezone
from bookwyrm import book_search, models
from bookwyrm.connectors.abstract_connector import AbstractMinimalConnector

View file

@ -1,10 +1,10 @@
""" test partial_date module """
import datetime
from datetime import timezone
import unittest
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.utils import translation
from bookwyrm.utils import partial_date

View file

@ -72,12 +72,12 @@ class Signature(TestCase):
urlsplit(self.rat.inbox).path,
data=data,
content_type="application/json",
**{
"HTTP_DATE": now,
"HTTP_SIGNATURE": signature,
"HTTP_DIGEST": digest,
"HTTP_CONTENT_TYPE": "application/activity+json; charset=utf-8",
"HTTP_HOST": NETLOC,
headers={
"date": now,
"signature": signature,
"digest": digest,
"content-type": "application/activity+json; charset=utf-8",
"host": NETLOC,
},
)

View file

@ -123,8 +123,8 @@ class ImportViews(TestCase):
"""Give people a sense of the timing"""
models.ImportJob.objects.create(
user=self.local_user,
created_date=datetime.datetime(2000, 1, 1),
updated_date=datetime.datetime(2001, 1, 1),
created_date=datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc),
updated_date=datetime.datetime(2001, 1, 1, tzinfo=datetime.timezone.utc),
status="complete",
complete=True,
mappings={},

View file

@ -134,7 +134,10 @@ class Inbox(TestCase):
"""check for blocked servers"""
request = self.factory.post(
"",
HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)",
headers={
# pylint: disable-next=line-too-long
"user-agent": "http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)",
},
)
self.assertIsNone(views.inbox.raise_is_blocked_user_agent(request))

View file

@ -78,7 +78,7 @@ class DeleteUserViews(TestCase):
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.local_user
middleware = SessionMiddleware()
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()
@ -105,7 +105,7 @@ class DeleteUserViews(TestCase):
view = views.DeactivateUser.as_view()
request = self.factory.post("")
request.user = self.local_user
middleware = SessionMiddleware()
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()
@ -137,7 +137,7 @@ class DeleteUserViews(TestCase):
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.local_user
middleware = SessionMiddleware()
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()
@ -159,7 +159,7 @@ class DeleteUserViews(TestCase):
form.data["password"] = "password"
request = self.factory.post("", form.data)
request.user = self.local_user
middleware = SessionMiddleware()
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()

View file

@ -101,7 +101,7 @@ class ViewsHelpers(TestCase):
request = self.factory.post("", form.data)
request.user = self.local_user
middleware = SessionMiddleware()
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()

View file

@ -1,7 +1,6 @@
"""testing the annual summary page"""
from datetime import datetime
import datetime
from unittest.mock import patch
import pytz
from django.contrib.auth.models import AnonymousUser
from django.http import Http404
@ -15,7 +14,7 @@ from bookwyrm.tests.validate_html import validate_html
def make_date(*args):
"""helper function to easily generate a date obj"""
return datetime(*args, tzinfo=pytz.UTC)
return datetime.datetime(*args, tzinfo=datetime.timezone.utc)
class AnnualSummary(TestCase):

View file

@ -113,11 +113,20 @@ class ViewsHelpers(TestCase): # pylint: disable=too-many-public-methods
request = self.factory.get(
"",
{"q": "Test Book"},
HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)",
headers={
# pylint: disable-next=line-too-long
"user-agent": "http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)",
},
)
self.assertFalse(views.helpers.is_bookwyrm_request(request))
request = self.factory.get("", {"q": "Test Book"}, HTTP_USER_AGENT=USER_AGENT)
request = self.factory.get(
"",
{"q": "Test Book"},
headers={
"user-agent": USER_AGENT,
},
)
self.assertTrue(views.helpers.is_bookwyrm_request(request))
def test_handle_remote_webfinger_invalid(self, *_):
@ -271,8 +280,12 @@ class ViewsHelpers(TestCase): # pylint: disable=too-many-public-methods
def test_redirect_to_referer_outside_domain(self, *_):
"""safely send people on their way"""
request = self.factory.get("/path")
request.META = {"HTTP_REFERER": "http://outside.domain/name"}
request = self.factory.get(
"/path",
headers={
"referer": "http://outside.domain/name",
},
)
result = views.helpers.redirect_to_referer(
request, "user-feed", self.local_user.localname
)
@ -280,21 +293,33 @@ class ViewsHelpers(TestCase): # pylint: disable=too-many-public-methods
def test_redirect_to_referer_outside_domain_with_fallback(self, *_):
"""invalid domain with regular params for the redirect function"""
request = self.factory.get("/path")
request.META = {"HTTP_REFERER": "https://outside.domain/name"}
request = self.factory.get(
"/path",
headers={
"referer": "http://outside.domain/name",
},
)
result = views.helpers.redirect_to_referer(request)
self.assertEqual(result.url, "/")
def test_redirect_to_referer_valid_domain(self, *_):
"""redirect to within the app"""
request = self.factory.get("/path")
request.META = {"HTTP_REFERER": f"{BASE_URL}/and/a/path"}
request = self.factory.get(
"/path",
headers={
"referer": f"{BASE_URL}/and/a/path",
},
)
result = views.helpers.redirect_to_referer(request)
self.assertEqual(result.url, f"{BASE_URL}/and/a/path")
def test_redirect_to_referer_with_get_args(self, *_):
"""if the path has get params (like sort) they are preserved"""
request = self.factory.get("/path")
request.META = {"HTTP_REFERER": f"{BASE_URL}/and/a/path?sort=hello"}
request = self.factory.get(
"/path",
headers={
"referer": f"{BASE_URL}/and/a/path?sort=hello",
},
)
result = views.helpers.redirect_to_referer(request)
self.assertEqual(result.url, f"{BASE_URL}/and/a/path?sort=hello")

View file

@ -122,7 +122,7 @@ class OutboxView(TestCase):
privacy="public",
)
request = self.factory.get("", {"page": 1}, HTTP_USER_AGENT=USER_AGENT)
request = self.factory.get("", {"page": 1}, headers={"user-agent": USER_AGENT})
result = views.Outbox.as_view()(request, "mouse")
data = json.loads(result.content)

View file

@ -1,8 +1,7 @@
""" tests updating reading progress """
from datetime import datetime
from datetime import datetime, timezone
from unittest.mock import patch
from django.test import TestCase, Client
from django.utils import timezone
from bookwyrm import models

View file

@ -6,7 +6,7 @@ def clean(input_text: str) -> str:
"""Run through "bleach" """
return bleach.clean(
input_text,
tags=[
tags={
"p",
"blockquote",
"br",
@ -20,7 +20,7 @@ def clean(input_text: str) -> str:
"ul",
"ol",
"li",
],
},
attributes=["href", "rel", "src", "alt", "data-mention"],
strip=True,
)

View file

@ -231,7 +231,7 @@ def maybe_redirect_local_path(request, model):
def redirect_to_referer(request, *args, **kwargs):
"""Redirect to the referrer, if it's in our domain, with get params"""
# make sure the refer is part of this instance
validated = validate_url_domain(request.META.get("HTTP_REFERER"))
validated = validate_url_domain(request.headers.get("referer", ""))
if validated:
return redirect(validated)

View file

@ -1,5 +1,5 @@
""" class views for login/register views """
import pytz
import zoneinfo
from django.contrib.auth import login
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect
@ -57,9 +57,11 @@ class Register(View):
email = form.data["email"]
password = form.data["password"]
try:
preferred_timezone = pytz.timezone(form.data.get("preferred_timezone"))
except pytz.exceptions.UnknownTimeZoneError:
preferred_timezone = pytz.utc
preferred_timezone = zoneinfo.ZoneInfo(
form.data.get("preferred_timezone", "")
)
except (ValueError, zoneinfo.ZoneInfoNotFoundError):
preferred_timezone = zoneinfo.ZoneInfo("UTC")
# make sure the email isn't blocked as spam
email_domain = email.split("@")[-1]

View file

@ -14,9 +14,9 @@ from django.urls import reverse
from django.utils.decorators import method_decorator
from django.shortcuts import redirect
from storages.backends.s3boto3 import S3Boto3Storage
from storages.backends.s3 import S3Storage
from bookwyrm import models, storage_backends
from bookwyrm import models
from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob
from bookwyrm import settings
@ -220,17 +220,16 @@ class ExportUser(View):
class ExportArchive(View):
"""Serve the archive file"""
# TODO: how do we serve s3 files?
def get(self, request, archive_id):
"""download user export file"""
export = BookwyrmExportJob.objects.get(task_id=archive_id, user=request.user)
if isinstance(export.export_data.storage, storage_backends.ExportsS3Storage):
if settings.USE_S3:
# make custom_domain None so we can sign the url
# see https://github.com/jschneier/django-storages/issues/944
storage = S3Boto3Storage(querystring_auth=True, custom_domain=None)
storage = S3Storage(querystring_auth=True, custom_domain=None)
try:
url = S3Boto3Storage.url(
url = S3Storage.url(
storage,
f"/exports/{export.task_id}.tar.gz",
expire=settings.S3_SIGNED_URL_EXPIRY,
@ -239,16 +238,17 @@ class ExportArchive(View):
raise Http404()
return redirect(url)
if isinstance(export.export_data.storage, storage_backends.ExportsFileStorage):
try:
return HttpResponse(
export.export_data,
content_type="application/gzip",
headers={
"Content-Disposition": 'attachment; filename="bookwyrm-account-export.tar.gz"' # pylint: disable=line-too-long
},
)
except FileNotFoundError:
raise Http404()
if settings.USE_AZURE:
# not implemented
return HttpResponseServerError()
return HttpResponseServerError()
try:
return HttpResponse(
export.export_data,
content_type="application/gzip",
headers={
"Content-Disposition": 'attachment; filename="bookwyrm-account-export.tar.gz"' # pylint: disable=line-too-long
},
)
except FileNotFoundError:
raise Http404()

View file

@ -1,63 +1,63 @@
aiohttp==3.9.4
bleach==5.0.1
boto3==1.26.57
bleach==6.1.0
boto3==1.34.74
bw-file-resubmit==0.6.0rc2
celery==5.3.1
celery==5.3.6
colorthief==0.2.1
Django==3.2.25
django-celery-beat==2.5.0
Django==4.2.11
django-celery-beat==2.6.0
django-compressor==4.4
django-csp==3.7
django-imagekit==4.1.0
django-model-utils==4.3.1
django-csp==3.8
django-imagekit==5.0.0
django-model-utils==4.4.0
django-pgtrigger==4.11.0
django-redis==5.2.0
django-sass-processor==1.2.2
django-storages==1.13.2
django-sass-processor==1.4
django-storages==1.14.2
django-storages[azure]
environs==9.5.0
environs==11.0.0
flower==2.0.1
grpcio==1.57.0 # Not a direct dependency, pinned to get a security fix
libsass==0.22.0
Markdown==3.4.1
opentelemetry-api==1.16.0
opentelemetry-exporter-otlp-proto-grpc==1.16.0
opentelemetry-instrumentation-celery==0.37b0
opentelemetry-instrumentation-django==0.37b0
opentelemetry-instrumentation-psycopg2==0.37b0
opentelemetry-sdk==1.16.0
hiredis==2.3.2
libsass==0.23.0
Markdown==3.6
opentelemetry-api==1.24.0
opentelemetry-exporter-otlp-proto-grpc==1.24.0
opentelemetry-instrumentation-celery==0.45b0
opentelemetry-instrumentation-django==0.45b0
opentelemetry-instrumentation-psycopg2==0.45b0
opentelemetry-sdk==1.24.0
Pillow==10.3.0
pilkit>=3.0 # dependency of django-imagekit, 2.0 is incompatible with Pillow>=10
protobuf==3.20.*
psycopg2==2.9.5
pycryptodome==3.19.1
pyotp==2.8.0
python-dateutil==2.8.2
pytz>=2022.7
qrcode==7.3.1
redis==4.5.4
psycopg2==2.9.9
pycryptodome==3.20.0
pyotp==2.9.0
python-dateutil==2.9.0.post0
qrcode==7.4.2
redis==5.0.3
requests==2.31.0
responses==0.22.0
responses==0.25.0
s3-tar==0.1.13
setuptools>=65.5.1 # Not a direct dependency, pinned to get a security fix
tornado==6.3.3 # Not a direct dependency, pinned to get a security fix
# Indirect dependencies with version constraints for security fixes
grpcio>=1.57.0
setuptools>=65.5.1
tornado>=6.3.3
# Dev
black==22.*
celery-types==0.18.0
django-stubs[compatible-mypy]==4.2.4
mypy==1.5.1
celery-types==0.22.0
django-stubs[compatible-mypy]==4.2.7
mypy==1.7.1
pylint==2.15.0
pytest==6.2.5
pytest-cov==2.10.1
pytest-django==4.1.0
pytest-env==0.6.2
pytest-xdist==2.3.0
pytest==8.1.1
pytest-cov==5.0.0
pytest-django==4.8.0
pytest-env==1.1.3
pytest-xdist==3.5.0
pytidylib==0.3.2
types-bleach==6.0.0.4
types-bleach==6.1.0.20240331
types-dataclasses==0.6.6
types-Markdown==3.4.2.10
types-Pillow==10.2.0.20240311
types-psycopg2==2.9.21.11
types-python-dateutil==2.8.19.14
types-requests==2.31.0.2
types-Markdown==3.6.0.20240316
types-Pillow==10.2.0.20240331
types-psycopg2==2.9.21.20240311
types-python-dateutil==2.9.0.20240316
types-requests==2.31.0.20240311