diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index fdfbb1f06..1a156cae6 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -1,4 +1,4 @@ -''' bring activitypub functions into the namespace ''' +""" bring activitypub functions into the namespace """ import inspect import sys @@ -21,9 +21,9 @@ from .verbs import Announce, Like # this creates a list of all the Activity types that we can serialize, # so when an Activity comes in from outside, we can check if it's known cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) -activity_objects = {c[0]: c[1] for c in cls_members \ - if hasattr(c[1], 'to_model')} +activity_objects = {c[0]: c[1] for c in cls_members if hasattr(c[1], "to_model")} + def parse(activity_json): - ''' figure out what activity this is and parse it ''' + """ figure out what activity this is and parse it """ return naive_parse(activity_objects, activity_json) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index c732fe1d3..315ff58c8 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -1,4 +1,4 @@ -''' basics for an activitypub serializer ''' +""" basics for an activitypub serializer """ from dataclasses import dataclass, fields, MISSING from json import JSONEncoder @@ -8,46 +8,52 @@ from django.db import IntegrityError, transaction from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.tasks import app + class ActivitySerializerError(ValueError): - ''' routine problems serializing activitypub json ''' + """ routine problems serializing activitypub json """ class ActivityEncoder(JSONEncoder): - ''' used to convert an Activity object into json ''' + """ used to convert an Activity object into json """ + def default(self, o): return o.__dict__ @dataclass class Link: - ''' for tagging a book in a status ''' + """ for tagging a book in a status """ + href: str name: str - type: str = 'Link' + type: str = "Link" @dataclass class Mention(Link): - ''' a subtype of Link for mentioning an actor ''' - type: str = 'Mention' + """ a subtype of Link for mentioning an actor """ + + type: str = "Mention" @dataclass class Signature: - ''' public key block ''' + """ public key block """ + creator: str created: str signatureValue: str - type: str = 'RsaSignature2017' + type: str = "RsaSignature2017" + def naive_parse(activity_objects, activity_json, serializer=None): - ''' this navigates circular import issues ''' + """ this navigates circular import issues """ if not serializer: - if activity_json.get('publicKeyPem'): + if activity_json.get("publicKeyPem"): # ugh - activity_json['type'] = 'PublicKey' + activity_json["type"] = "PublicKey" try: - activity_type = activity_json['type'] + activity_type = activity_json["type"] serializer = activity_objects[activity_type] except KeyError as e: raise ActivitySerializerError(e) @@ -57,14 +63,15 @@ def naive_parse(activity_objects, activity_json, serializer=None): @dataclass(init=False) class ActivityObject: - ''' actor activitypub json ''' + """ actor activitypub json """ + id: str type: str def __init__(self, activity_objects=None, **kwargs): - ''' this lets you pass in an object with fields that aren't in the + """this lets you pass in an object with fields that aren't in the dataclass, which it ignores. Any field in the dataclass is required or - has a default value ''' + has a default value""" for field in fields(self): try: value = kwargs[field.name] @@ -75,7 +82,7 @@ class ActivityObject: except TypeError: is_subclass = False # serialize a model obj - if hasattr(value, 'to_activity'): + if hasattr(value, "to_activity"): value = value.to_activity() # parse a dict into the appropriate activity elif is_subclass and isinstance(value, dict): @@ -83,25 +90,27 @@ class ActivityObject: value = naive_parse(activity_objects, value) else: value = naive_parse( - activity_objects, value, serializer=field.type) + activity_objects, value, serializer=field.type + ) except KeyError: - if field.default == MISSING and \ - field.default_factory == MISSING: - raise ActivitySerializerError(\ - 'Missing required field: %s' % field.name) + if field.default == MISSING and field.default_factory == MISSING: + raise ActivitySerializerError( + "Missing required field: %s" % field.name + ) value = field.default setattr(self, field.name, value) - def to_model(self, model=None, instance=None, allow_create=True, save=True): - ''' convert from an activity to a model instance ''' + """ convert from an activity to a model instance """ model = model or get_model_from_type(self.type) # only reject statuses if we're potentially creating them - if allow_create and \ - hasattr(model, 'ignore_activity') and \ - model.ignore_activity(self): + if ( + allow_create + and hasattr(model, "ignore_activity") + and model.ignore_activity(self) + ): raise ActivitySerializerError() # check for an existing instance @@ -142,8 +151,10 @@ class ActivityObject: field.set_field_from_activity(instance, self) # reversed relationships in the models - for (model_field_name, activity_field_name) in \ - instance.deserialize_reverse_fields: + for ( + model_field_name, + activity_field_name, + ) in instance.deserialize_reverse_fields: # attachments on Status, for example values = getattr(self, activity_field_name) if values is None or values is MISSING: @@ -161,13 +172,12 @@ class ActivityObject: instance.__class__.__name__, related_field_name, instance.remote_id, - item + item, ) return instance - def serialize(self): - ''' convert to dictionary with context attr ''' + """ convert to dictionary with context attr """ data = self.__dict__.copy() # recursively serialize for (k, v) in data.items(): @@ -176,22 +186,19 @@ class ActivityObject: data[k] = v.serialize() except TypeError: pass - data = {k:v for (k, v) in data.items() if v is not None} - data['@context'] = 'https://www.w3.org/ns/activitystreams' + data = {k: v for (k, v) in data.items() if v is not None} + data["@context"] = "https://www.w3.org/ns/activitystreams" return data @app.task @transaction.atomic def set_related_field( - model_name, origin_model_name, related_field_name, - related_remote_id, data): - ''' load reverse related fields (editions, attachments) without blocking ''' - model = apps.get_model('bookwyrm.%s' % model_name, require_ready=True) - origin_model = apps.get_model( - 'bookwyrm.%s' % origin_model_name, - require_ready=True - ) + model_name, origin_model_name, related_field_name, related_remote_id, data +): + """ load reverse related fields (editions, attachments) without blocking """ + model = apps.get_model("bookwyrm.%s" % model_name, require_ready=True) + origin_model = apps.get_model("bookwyrm.%s" % origin_model_name, require_ready=True) with transaction.atomic(): if isinstance(data, str): @@ -205,43 +212,45 @@ def set_related_field( # this must exist because it's the object that triggered this function instance = origin_model.find_existing_by_remote_id(related_remote_id) if not instance: - raise ValueError( - 'Invalid related remote id: %s' % related_remote_id) + raise ValueError("Invalid related remote id: %s" % related_remote_id) # set the origin's remote id on the activity so it will be there when # the model instance is created # edition.parentWork = instance, for example model_field = getattr(model, related_field_name) - if hasattr(model_field, 'activitypub_field'): + if hasattr(model_field, "activitypub_field"): setattr( - activity, - getattr(model_field, 'activitypub_field'), - instance.remote_id + activity, getattr(model_field, "activitypub_field"), instance.remote_id ) item = activity.to_model() # if the related field isn't serialized (attachments on Status), then # we have to set it post-creation - if not hasattr(model_field, 'activitypub_field'): + if not hasattr(model_field, "activitypub_field"): setattr(item, related_field_name, instance) item.save() def get_model_from_type(activity_type): - ''' given the activity, what type of model ''' + """ given the activity, what type of model """ models = apps.get_models() - model = [m for m in models if hasattr(m, 'activity_serializer') and \ - hasattr(m.activity_serializer, 'type') and \ - m.activity_serializer.type == activity_type] + model = [ + m + for m in models + if hasattr(m, "activity_serializer") + and hasattr(m.activity_serializer, "type") + and m.activity_serializer.type == activity_type + ] if not model: raise ActivitySerializerError( - 'No model found for activity type "%s"' % activity_type) + 'No model found for activity type "%s"' % activity_type + ) return model[0] def resolve_remote_id(remote_id, model=None, refresh=False, save=True): - ''' take a remote_id and return an instance, creating if necessary ''' - if model:# a bonus check we can do if we already know the model + """ take a remote_id and return an instance, creating if necessary """ + if model: # a bonus check we can do if we already know the model result = model.find_existing_by_remote_id(remote_id) if result and not refresh: return result @@ -251,11 +260,12 @@ def resolve_remote_id(remote_id, model=None, refresh=False, save=True): data = get_data(remote_id) except (ConnectorException, ConnectionError): raise ActivitySerializerError( - 'Could not connect to host for remote_id in %s model: %s' % \ - (model.__name__, remote_id)) + "Could not connect to host for remote_id in %s model: %s" + % (model.__name__, remote_id) + ) # determine the model implicitly, if not provided if not model: - model = get_model_from_type(data.get('type')) + model = get_model_from_type(data.get("type")) # check for existing items with shared unique identifiers result = model.find_existing(data) diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index 8c32be967..7e552b0a8 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -1,70 +1,75 @@ -''' book and author data ''' +""" book and author data """ from dataclasses import dataclass, field from typing import List from .base_activity import ActivityObject from .image import Image + @dataclass(init=False) class Book(ActivityObject): - ''' serializes an edition or work, abstract ''' + """ serializes an edition or work, abstract """ + title: str - sortTitle: str = '' - subtitle: str = '' - description: str = '' + sortTitle: str = "" + subtitle: str = "" + description: str = "" languages: List[str] = field(default_factory=lambda: []) - series: str = '' - seriesNumber: str = '' + series: str = "" + seriesNumber: str = "" subjects: List[str] = field(default_factory=lambda: []) subjectPlaces: List[str] = field(default_factory=lambda: []) authors: List[str] = field(default_factory=lambda: []) - firstPublishedDate: str = '' - publishedDate: str = '' + firstPublishedDate: str = "" + publishedDate: str = "" - openlibraryKey: str = '' - librarythingKey: str = '' - goodreadsKey: str = '' + openlibraryKey: str = "" + librarythingKey: str = "" + goodreadsKey: str = "" cover: Image = None - type: str = 'Book' + type: str = "Book" @dataclass(init=False) class Edition(Book): - ''' Edition instance of a book object ''' + """ Edition instance of a book object """ + work: str - isbn10: str = '' - isbn13: str = '' - oclcNumber: str = '' - asin: str = '' + isbn10: str = "" + isbn13: str = "" + oclcNumber: str = "" + asin: str = "" pages: int = None - physicalFormat: str = '' + physicalFormat: str = "" publishers: List[str] = field(default_factory=lambda: []) editionRank: int = 0 - type: str = 'Edition' + type: str = "Edition" @dataclass(init=False) class Work(Book): - ''' work instance of a book object ''' - lccn: str = '' - defaultEdition: str = '' + """ work instance of a book object """ + + lccn: str = "" + defaultEdition: str = "" editions: List[str] = field(default_factory=lambda: []) - type: str = 'Work' + type: str = "Work" @dataclass(init=False) class Author(ActivityObject): - ''' author of a book ''' + """ author of a book """ + name: str born: str = None died: str = None aliases: List[str] = field(default_factory=lambda: []) - bio: str = '' - openlibraryKey: str = '' - librarythingKey: str = '' - goodreadsKey: str = '' - wikipediaLink: str = '' - type: str = 'Author' + bio: str = "" + openlibraryKey: str = "" + librarythingKey: str = "" + goodreadsKey: str = "" + wikipediaLink: str = "" + type: str = "Author" diff --git a/bookwyrm/activitypub/image.py b/bookwyrm/activitypub/image.py index 569f83c5d..248e7a4ad 100644 --- a/bookwyrm/activitypub/image.py +++ b/bookwyrm/activitypub/image.py @@ -1,11 +1,13 @@ -''' an image, nothing fancy ''' +""" an image, nothing fancy """ from dataclasses import dataclass from .base_activity import ActivityObject + @dataclass(init=False) class Image(ActivityObject): - ''' image block ''' + """ image block """ + url: str - name: str = '' - type: str = 'Image' - id: str = '' + name: str = "" + type: str = "Image" + id: str = "" diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 705d6eede..f38fea5b0 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -1,4 +1,4 @@ -''' note serializer and children thereof ''' +""" note serializer and children thereof """ from dataclasses import dataclass, field from typing import Dict, List from django.apps import apps @@ -6,64 +6,72 @@ from django.apps import apps from .base_activity import ActivityObject, Link from .image import Image + @dataclass(init=False) class Tombstone(ActivityObject): - ''' the placeholder for a deleted status ''' - type: str = 'Tombstone' + """ the placeholder for a deleted status """ + + type: str = "Tombstone" def to_model(self, *args, **kwargs): - ''' this should never really get serialized, just searched for ''' - model = apps.get_model('bookwyrm.Status') + """ this should never really get serialized, just searched for """ + model = apps.get_model("bookwyrm.Status") return model.find_existing_by_remote_id(self.id) @dataclass(init=False) class Note(ActivityObject): - ''' Note activity ''' + """ Note activity """ + published: str attributedTo: str - content: str = '' + content: str = "" to: List[str] = field(default_factory=lambda: []) cc: List[str] = field(default_factory=lambda: []) replies: Dict = field(default_factory=lambda: {}) - inReplyTo: str = '' - summary: str = '' + inReplyTo: str = "" + summary: str = "" tag: List[Link] = field(default_factory=lambda: []) attachment: List[Image] = field(default_factory=lambda: []) sensitive: bool = False - type: str = 'Note' + type: str = "Note" @dataclass(init=False) class Article(Note): - ''' what's an article except a note with more fields ''' + """ what's an article except a note with more fields """ + name: str - type: str = 'Article' + type: str = "Article" @dataclass(init=False) class GeneratedNote(Note): - ''' just a re-typed note ''' - type: str = 'GeneratedNote' + """ just a re-typed note """ + + type: str = "GeneratedNote" @dataclass(init=False) class Comment(Note): - ''' like a note but with a book ''' + """ like a note but with a book """ + inReplyToBook: str - type: str = 'Comment' + type: str = "Comment" @dataclass(init=False) class Review(Comment): - ''' a full book review ''' + """ a full book review """ + name: str = None rating: int = None - type: str = 'Review' + type: str = "Review" @dataclass(init=False) class Quotation(Comment): - ''' a quote and commentary on a book ''' + """ a quote and commentary on a book """ + quote: str - type: str = 'Quotation' + type: str = "Quotation" diff --git a/bookwyrm/activitypub/ordered_collection.py b/bookwyrm/activitypub/ordered_collection.py index 14b35f3cf..6da608322 100644 --- a/bookwyrm/activitypub/ordered_collection.py +++ b/bookwyrm/activitypub/ordered_collection.py @@ -1,4 +1,4 @@ -''' defines activitypub collections (lists) ''' +""" defines activitypub collections (lists) """ from dataclasses import dataclass, field from typing import List @@ -7,38 +7,46 @@ from .base_activity import ActivityObject @dataclass(init=False) class OrderedCollection(ActivityObject): - ''' structure of an ordered collection activity ''' + """ structure of an ordered collection activity """ + totalItems: int first: str last: str = None name: str = None owner: str = None - type: str = 'OrderedCollection' + type: str = "OrderedCollection" + @dataclass(init=False) class OrderedCollectionPrivate(OrderedCollection): - ''' an ordered collection with privacy settings ''' + """ an ordered collection with privacy settings """ + to: List[str] = field(default_factory=lambda: []) cc: List[str] = field(default_factory=lambda: []) + @dataclass(init=False) class Shelf(OrderedCollectionPrivate): - ''' structure of an ordered collection activity ''' - type: str = 'Shelf' + """ structure of an ordered collection activity """ + + type: str = "Shelf" + @dataclass(init=False) class BookList(OrderedCollectionPrivate): - ''' structure of an ordered collection activity ''' + """ structure of an ordered collection activity """ + summary: str = None - curation: str = 'closed' - type: str = 'BookList' + curation: str = "closed" + type: str = "BookList" @dataclass(init=False) class OrderedCollectionPage(ActivityObject): - ''' structure of an ordered collection activity ''' + """ structure of an ordered collection activity """ + partOf: str orderedItems: List next: str = None prev: str = None - type: str = 'OrderedCollectionPage' + type: str = "OrderedCollectionPage" diff --git a/bookwyrm/activitypub/person.py b/bookwyrm/activitypub/person.py index 7e7d027eb..ba86b036c 100644 --- a/bookwyrm/activitypub/person.py +++ b/bookwyrm/activitypub/person.py @@ -1,4 +1,4 @@ -''' actor serializer ''' +""" actor serializer """ from dataclasses import dataclass, field from typing import Dict @@ -8,15 +8,17 @@ from .image import Image @dataclass(init=False) class PublicKey(ActivityObject): - ''' public key block ''' + """ public key block """ + owner: str publicKeyPem: str - type: str = 'PublicKey' + type: str = "PublicKey" @dataclass(init=False) class Person(ActivityObject): - ''' actor activitypub json ''' + """ actor activitypub json """ + preferredUsername: str inbox: str outbox: str @@ -29,4 +31,4 @@ class Person(ActivityObject): bookwyrmUser: bool = False manuallyApprovesFollowers: str = False discoverable: str = True - type: str = 'Person' + type: str = "Person" diff --git a/bookwyrm/activitypub/response.py b/bookwyrm/activitypub/response.py index 8f3c050bb..07f39c7e1 100644 --- a/bookwyrm/activitypub/response.py +++ b/bookwyrm/activitypub/response.py @@ -2,6 +2,7 @@ from django.http import JsonResponse from .base_activity import ActivityEncoder + class ActivitypubResponse(JsonResponse): """ A class to be used in any place that's serializing responses for @@ -9,10 +10,17 @@ class ActivitypubResponse(JsonResponse): configures some stuff beforehand. Made to be a drop-in replacement of JsonResponse. """ - def __init__(self, data, encoder=ActivityEncoder, safe=False, - json_dumps_params=None, **kwargs): - if 'content_type' not in kwargs: - kwargs['content_type'] = 'application/activity+json' + def __init__( + self, + data, + encoder=ActivityEncoder, + safe=False, + json_dumps_params=None, + **kwargs + ): + + if "content_type" not in kwargs: + kwargs["content_type"] = "application/activity+json" super().__init__(data, encoder, safe, json_dumps_params, **kwargs) diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index 1236338b2..cd7a757be 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -1,4 +1,4 @@ -''' undo wrapper activity ''' +""" undo wrapper activity """ from dataclasses import dataclass from typing import List from django.apps import apps @@ -9,160 +9,173 @@ from .book import Edition @dataclass(init=False) class Verb(ActivityObject): - ''' generic fields for activities - maybe an unecessary level of - abstraction but w/e ''' + """generic fields for activities - maybe an unecessary level of + abstraction but w/e""" + actor: str object: ActivityObject def action(self): - ''' usually we just want to save, this can be overridden as needed ''' + """ usually we just want to save, this can be overridden as needed """ self.object.to_model() @dataclass(init=False) class Create(Verb): - ''' Create activity ''' + """ Create activity """ + to: List cc: List signature: Signature = None - type: str = 'Create' + type: str = "Create" @dataclass(init=False) class Delete(Verb): - ''' Create activity ''' + """ Create activity """ + to: List cc: List - type: str = 'Delete' + type: str = "Delete" def action(self): - ''' find and delete the activity object ''' + """ find and delete the activity object """ obj = self.object.to_model(save=False, allow_create=False) obj.delete() - @dataclass(init=False) class Update(Verb): - ''' Update activity ''' + """ Update activity """ + to: List - type: str = 'Update' + type: str = "Update" def action(self): - ''' update a model instance from the dataclass ''' + """ update a model instance from the dataclass """ self.object.to_model(allow_create=False) @dataclass(init=False) class Undo(Verb): - ''' Undo an activity ''' - type: str = 'Undo' + """ Undo an activity """ + + type: str = "Undo" def action(self): - ''' find and remove the activity object ''' + """ find and remove the activity object """ # this is so hacky but it does make it work.... # (because you Reject a request and Undo a follow model = None - if self.object.type == 'Follow': - model = apps.get_model('bookwyrm.UserFollows') + if self.object.type == "Follow": + model = apps.get_model("bookwyrm.UserFollows") obj = self.object.to_model(model=model, save=False, allow_create=False) obj.delete() @dataclass(init=False) class Follow(Verb): - ''' Follow activity ''' + """ Follow activity """ + object: str - type: str = 'Follow' + type: str = "Follow" def action(self): - ''' relationship save ''' + """ relationship save """ self.to_model() @dataclass(init=False) class Block(Verb): - ''' Block activity ''' + """ Block activity """ + object: str - type: str = 'Block' + type: str = "Block" def action(self): - ''' relationship save ''' + """ relationship save """ self.to_model() @dataclass(init=False) class Accept(Verb): - ''' Accept activity ''' + """ Accept activity """ + object: Follow - type: str = 'Accept' + type: str = "Accept" def action(self): - ''' find and remove the activity object ''' + """ find and remove the activity object """ obj = self.object.to_model(save=False, allow_create=False) obj.accept() @dataclass(init=False) class Reject(Verb): - ''' Reject activity ''' + """ Reject activity """ + object: Follow - type: str = 'Reject' + type: str = "Reject" def action(self): - ''' find and remove the activity object ''' + """ find and remove the activity object """ obj = self.object.to_model(save=False, allow_create=False) obj.reject() @dataclass(init=False) class Add(Verb): - '''Add activity ''' + """Add activity """ + target: str object: Edition - type: str = 'Add' + type: str = "Add" notes: str = None order: int = 0 approved: bool = True def action(self): - ''' add obj to collection ''' + """ add obj to collection """ target = resolve_remote_id(self.target, refresh=False) # we want to related field that isn't the book, this is janky af sorry - model = [t for t in type(target)._meta.related_objects \ - if t.name != 'edition'][0].related_model + model = [t for t in type(target)._meta.related_objects if t.name != "edition"][ + 0 + ].related_model self.to_model(model=model) @dataclass(init=False) class Remove(Verb): - '''Remove activity ''' + """Remove activity """ + target: ActivityObject - type: str = 'Remove' + type: str = "Remove" def action(self): - ''' find and remove the activity object ''' + """ find and remove the activity object """ obj = self.object.to_model(save=False, allow_create=False) obj.delete() @dataclass(init=False) class Like(Verb): - ''' a user faving an object ''' + """ a user faving an object """ + object: str - type: str = 'Like' + type: str = "Like" def action(self): - ''' like ''' + """ like """ self.to_model() @dataclass(init=False) class Announce(Verb): - ''' boosting a status ''' + """ boosting a status """ + object: str - type: str = 'Announce' + type: str = "Announce" def action(self): - ''' boost ''' + """ boost """ self.to_model() diff --git a/bookwyrm/admin.py b/bookwyrm/admin.py index 45af81d99..efe5e9d72 100644 --- a/bookwyrm/admin.py +++ b/bookwyrm/admin.py @@ -1,4 +1,4 @@ -''' models that will show up in django admin for superuser ''' +""" models that will show up in django admin for superuser """ from django.contrib import admin from bookwyrm import models diff --git a/bookwyrm/connectors/__init__.py b/bookwyrm/connectors/__init__.py index cfafd2868..689f27018 100644 --- a/bookwyrm/connectors/__init__.py +++ b/bookwyrm/connectors/__init__.py @@ -1,4 +1,4 @@ -''' bring connectors into the namespace ''' +""" bring connectors into the namespace """ from .settings import CONNECTORS from .abstract_connector import ConnectorException from .abstract_connector import get_data, get_image diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index e6372438e..9f31b337d 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -1,4 +1,4 @@ -''' functionality outline for a book data connector ''' +""" functionality outline for a book data connector """ from abc import ABC, abstractmethod from dataclasses import asdict, dataclass import logging @@ -13,8 +13,11 @@ from .connector_manager import load_more_data, ConnectorException logger = logging.getLogger(__name__) + + class AbstractMinimalConnector(ABC): - ''' just the bare bones, for other bookwyrm instances ''' + """ just the bare bones, for other bookwyrm instances """ + def __init__(self, identifier): # load connector settings info = models.Connector.objects.get(identifier=identifier) @@ -22,31 +25,31 @@ class AbstractMinimalConnector(ABC): # the things in the connector model to copy over self_fields = [ - 'base_url', - 'books_url', - 'covers_url', - 'search_url', - 'isbn_search_url', - 'max_query_count', - 'name', - 'identifier', - 'local' + "base_url", + "books_url", + "covers_url", + "search_url", + "isbn_search_url", + "max_query_count", + "name", + "identifier", + "local", ] for field in self_fields: setattr(self, field, getattr(info, field)) def search(self, query, min_confidence=None): - ''' free text search ''' + """ free text search """ params = {} if min_confidence: - params['min_confidence'] = min_confidence + params["min_confidence"] = min_confidence resp = requests.get( - '%s%s' % (self.search_url, query), + "%s%s" % (self.search_url, query), params=params, headers={ - 'Accept': 'application/json; charset=utf-8', - 'User-Agent': settings.USER_AGENT, + "Accept": "application/json; charset=utf-8", + "User-Agent": settings.USER_AGENT, }, ) if not resp.ok: @@ -55,7 +58,7 @@ class AbstractMinimalConnector(ABC): data = resp.json() except ValueError as e: logger.exception(e) - raise ConnectorException('Unable to parse json response', e) + raise ConnectorException("Unable to parse json response", e) results = [] for doc in self.parse_search_data(data)[:10]: @@ -63,14 +66,14 @@ class AbstractMinimalConnector(ABC): return results def isbn_search(self, query): - ''' isbn search ''' + """ isbn search """ params = {} resp = requests.get( - '%s%s' % (self.isbn_search_url, query), + "%s%s" % (self.isbn_search_url, query), params=params, headers={ - 'Accept': 'application/json; charset=utf-8', - 'User-Agent': settings.USER_AGENT, + "Accept": "application/json; charset=utf-8", + "User-Agent": settings.USER_AGENT, }, ) if not resp.ok: @@ -79,7 +82,7 @@ class AbstractMinimalConnector(ABC): data = resp.json() except ValueError as e: logger.exception(e) - raise ConnectorException('Unable to parse json response', e) + raise ConnectorException("Unable to parse json response", e) results = [] for doc in self.parse_isbn_search_data(data): @@ -88,49 +91,49 @@ class AbstractMinimalConnector(ABC): @abstractmethod def get_or_create_book(self, remote_id): - ''' pull up a book record by whatever means possible ''' + """ pull up a book record by whatever means possible """ @abstractmethod def parse_search_data(self, data): - ''' turn the result json from a search into a list ''' + """ turn the result json from a search into a list """ @abstractmethod def format_search_result(self, search_result): - ''' create a SearchResult obj from json ''' + """ create a SearchResult obj from json """ @abstractmethod def parse_isbn_search_data(self, data): - ''' turn the result json from a search into a list ''' + """ 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 ''' + """ create a SearchResult obj from json """ class AbstractConnector(AbstractMinimalConnector): - ''' generic book data connector ''' + """ generic book data connector """ + def __init__(self, identifier): super().__init__(identifier) # fields we want to look for in book data to copy over # title we handle separately. self.book_mappings = [] - def is_available(self): - ''' check if you're allowed to use this connector ''' + """ check if you're allowed to use this connector """ if self.max_query_count is not None: if self.connector.query_count >= self.max_query_count: return False return True - def get_or_create_book(self, remote_id): - ''' translate arbitrary json into an Activitypub dataclass ''' + """ translate arbitrary json into an Activitypub dataclass """ # first, check if we have the origin_id saved - existing = models.Edition.find_existing_by_remote_id(remote_id) or \ - models.Work.find_existing_by_remote_id(remote_id) + existing = models.Edition.find_existing_by_remote_id( + remote_id + ) or models.Work.find_existing_by_remote_id(remote_id) if existing: - if hasattr(existing, 'get_default_editon'): + if hasattr(existing, "get_default_editon"): return existing.get_default_editon() return existing @@ -154,7 +157,7 @@ class AbstractConnector(AbstractMinimalConnector): edition_data = data if not work_data or not edition_data: - raise ConnectorException('Unable to load book data: %s' % remote_id) + raise ConnectorException("Unable to load book data: %s" % remote_id) with transaction.atomic(): # create activitypub object @@ -168,11 +171,10 @@ class AbstractConnector(AbstractMinimalConnector): load_more_data.delay(self.connector.id, work.id) return edition - def create_edition_from_data(self, work, edition_data): - ''' if we already have the work, we're ready ''' + """ if we already have the work, we're ready """ mapped_data = dict_from_mappings(edition_data, self.book_mappings) - mapped_data['work'] = work.remote_id + mapped_data["work"] = work.remote_id edition_activity = activitypub.Edition(**mapped_data) edition = edition_activity.to_model(model=models.Edition) edition.connector = self.connector @@ -189,9 +191,8 @@ class AbstractConnector(AbstractMinimalConnector): return edition - def get_or_create_author(self, remote_id): - ''' load that author ''' + """ load that author """ existing = models.Author.find_existing_by_remote_id(remote_id) if existing: return existing @@ -203,31 +204,30 @@ class AbstractConnector(AbstractMinimalConnector): # this will dedupe return activity.to_model(model=models.Author) - @abstractmethod def is_work_data(self, data): - ''' differentiate works and editions ''' + """ differentiate works and editions """ @abstractmethod def get_edition_from_work_data(self, data): - ''' every work needs at least one edition ''' + """ every work needs at least one edition """ @abstractmethod def get_work_from_edition_data(self, data): - ''' every edition needs a work ''' + """ every edition needs a work """ @abstractmethod def get_authors_from_data(self, data): - ''' load author data ''' + """ load author data """ @abstractmethod def expand_book_data(self, book): - ''' get more info on a book ''' + """ get more info on a book """ def dict_from_mappings(data, mappings): - ''' create a dict in Activitypub format, using mappings supplies by - the subclass ''' + """create a dict in Activitypub format, using mappings supplies by + the subclass""" result = {} for mapping in mappings: result[mapping.local_field] = mapping.get_value(data) @@ -235,13 +235,13 @@ def dict_from_mappings(data, mappings): def get_data(url): - ''' wrapper for request.get ''' + """ wrapper for request.get """ try: resp = requests.get( url, headers={ - 'Accept': 'application/json; charset=utf-8', - 'User-Agent': settings.USER_AGENT, + "Accept": "application/json; charset=utf-8", + "User-Agent": settings.USER_AGENT, }, ) except (RequestError, SSLError) as e: @@ -260,12 +260,12 @@ def get_data(url): def get_image(url): - ''' wrapper for requesting an image ''' + """ wrapper for requesting an image """ try: resp = requests.get( url, headers={ - 'User-Agent': settings.USER_AGENT, + "User-Agent": settings.USER_AGENT, }, ) except (RequestError, SSLError) as e: @@ -278,7 +278,8 @@ def get_image(url): @dataclass class SearchResult: - ''' standardized search result object ''' + """ standardized search result object """ + title: str key: str author: str @@ -288,17 +289,19 @@ class SearchResult: def __repr__(self): return "".format( - self.key, self.title, self.author) + self.key, self.title, self.author + ) def json(self): - ''' serialize a connector for json response ''' + """ serialize a connector for json response """ serialized = asdict(self) - del serialized['connector'] + del serialized["connector"] return serialized class Mapping: - ''' associate a local database field with a field in an external dataset ''' + """ associate a local database field with a field in an external dataset """ + def __init__(self, local_field, remote_field=None, formatter=None): noop = lambda x: x @@ -307,11 +310,11 @@ class Mapping: self.formatter = formatter or noop def get_value(self, data): - ''' pull a field from incoming json and return the formatted version ''' + """ pull a field from incoming json and return the formatted version """ value = data.get(self.remote_field) if not value: return None try: return self.formatter(value) - except:# pylint: disable=bare-except + except: # pylint: disable=bare-except return None diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index 96b72f267..742d7e858 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -1,10 +1,10 @@ -''' using another bookwyrm instance as a source of book data ''' +""" using another bookwyrm instance as a source of book data """ from bookwyrm import activitypub, models from .abstract_connector import AbstractMinimalConnector, SearchResult class Connector(AbstractMinimalConnector): - ''' this is basically just for search ''' + """ this is basically just for search """ def get_or_create_book(self, remote_id): edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) @@ -17,13 +17,12 @@ class Connector(AbstractMinimalConnector): return data def format_search_result(self, search_result): - search_result['connector'] = self + search_result["connector"] = self return SearchResult(**search_result) def parse_isbn_search_data(self, data): return data def format_isbn_search_result(self, search_result): - search_result['connector'] = self + search_result["connector"] = self return SearchResult(**search_result) - diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index 053e1f9ef..3891d02a4 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -1,4 +1,4 @@ -''' interface with whatever connectors the app has ''' +""" interface with whatever connectors the app has """ import importlib import re from urllib.parse import urlparse @@ -10,24 +10,24 @@ from bookwyrm.tasks import app class ConnectorException(HTTPError): - ''' when the connector can't do what was asked ''' + """ when the connector can't do what was asked """ def search(query, min_confidence=0.1): - ''' find books based on arbitary keywords ''' + """ find books based on arbitary keywords """ results = [] # Have we got a ISBN ? - isbn = re.sub('[\W_]', '', query) - maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13 + isbn = re.sub("[\W_]", "", query) + maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13 - dedup_slug = lambda r: '%s/%s/%s' % (r.title, r.author, r.year) + dedup_slug = lambda r: "%s/%s/%s" % (r.title, r.author, r.year) result_index = set() for connector in get_connectors(): result_set = None if maybe_isbn: # Search on ISBN - if not connector.isbn_search_url or connector.isbn_search_url == '': + if not connector.isbn_search_url or connector.isbn_search_url == "": result_set = [] else: try: @@ -42,32 +42,33 @@ def search(query, min_confidence=0.1): except (HTTPError, ConnectorException): continue - result_set = [r for r in result_set \ - if dedup_slug(r) not in result_index] + result_set = [r for r in result_set if dedup_slug(r) not in result_index] # `|=` concats two sets. WE ARE GETTING FANCY HERE result_index |= set(dedup_slug(r) for r in result_set) - results.append({ - 'connector': connector, - 'results': result_set, - }) + results.append( + { + "connector": connector, + "results": result_set, + } + ) return results def local_search(query, min_confidence=0.1, raw=False): - ''' only look at local search results ''' + """ only look at local search results """ connector = load_connector(models.Connector.objects.get(local=True)) return connector.search(query, min_confidence=min_confidence, raw=raw) def isbn_local_search(query, raw=False): - ''' only look at local search results ''' + """ only look at local search results """ connector = load_connector(models.Connector.objects.get(local=True)) return connector.isbn_search(query, raw=raw) def first_search_result(query, min_confidence=0.1): - ''' search until you find a result that fits ''' + """ search until you find a result that fits """ for connector in get_connectors(): result = connector.search(query, min_confidence=min_confidence) if result: @@ -76,29 +77,29 @@ def first_search_result(query, min_confidence=0.1): def get_connectors(): - ''' load all connectors ''' - for info in models.Connector.objects.order_by('priority').all(): + """ load all connectors """ + for info in models.Connector.objects.order_by("priority").all(): yield load_connector(info) def get_or_create_connector(remote_id): - ''' get the connector related to the author's server ''' + """ get the connector related to the author's server """ url = urlparse(remote_id) identifier = url.netloc if not identifier: - raise ValueError('Invalid remote id') + raise ValueError("Invalid remote id") try: connector_info = models.Connector.objects.get(identifier=identifier) except models.Connector.DoesNotExist: connector_info = models.Connector.objects.create( identifier=identifier, - connector_file='bookwyrm_connector', - base_url='https://%s' % identifier, - books_url='https://%s/book' % identifier, - covers_url='https://%s/images/covers' % identifier, - search_url='https://%s/search?q=' % identifier, - priority=2 + connector_file="bookwyrm_connector", + base_url="https://%s" % identifier, + books_url="https://%s/book" % identifier, + covers_url="https://%s/images/covers" % identifier, + search_url="https://%s/search?q=" % identifier, + priority=2, ) return load_connector(connector_info) @@ -106,7 +107,7 @@ def get_or_create_connector(remote_id): @app.task def load_more_data(connector_id, book_id): - ''' background the work of getting all 10,000 editions of LoTR ''' + """ 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) @@ -114,8 +115,8 @@ def load_more_data(connector_id, book_id): def load_connector(connector_info): - ''' instantiate the connector class ''' + """ instantiate the connector class """ connector = importlib.import_module( - 'bookwyrm.connectors.%s' % connector_info.connector_file + "bookwyrm.connectors.%s" % connector_info.connector_file ) return connector.Connector(connector_info.identifier) diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index 8d227eef1..fb9a4e477 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -1,4 +1,4 @@ -''' openlibrary data connector ''' +""" openlibrary data connector """ import re from bookwyrm import models @@ -9,148 +9,134 @@ from .openlibrary_languages import languages class Connector(AbstractConnector): - ''' instantiate a connector for OL ''' + """ instantiate a connector for OL """ + def __init__(self, identifier): super().__init__(identifier) get_first = lambda a: a[0] get_remote_id = lambda a: self.base_url + a self.book_mappings = [ - Mapping('title'), - Mapping('id', remote_field='key', formatter=get_remote_id), + Mapping("title"), + Mapping("id", remote_field="key", formatter=get_remote_id), + Mapping("cover", remote_field="covers", formatter=self.get_cover_url), + Mapping("sortTitle", remote_field="sort_title"), + Mapping("subtitle"), + Mapping("description", formatter=get_description), + Mapping("languages", formatter=get_languages), + Mapping("series", formatter=get_first), + Mapping("seriesNumber", remote_field="series_number"), + Mapping("subjects"), + Mapping("subjectPlaces", remote_field="subject_places"), + Mapping("isbn13", remote_field="isbn_13", formatter=get_first), + Mapping("isbn10", remote_field="isbn_10", formatter=get_first), + Mapping("lccn", formatter=get_first), + Mapping("oclcNumber", remote_field="oclc_numbers", formatter=get_first), Mapping( - 'cover', remote_field='covers', formatter=self.get_cover_url), - Mapping('sortTitle', remote_field='sort_title'), - Mapping('subtitle'), - Mapping('description', formatter=get_description), - Mapping('languages', formatter=get_languages), - Mapping('series', formatter=get_first), - Mapping('seriesNumber', remote_field='series_number'), - Mapping('subjects'), - Mapping('subjectPlaces', remote_field='subject_places'), - Mapping('isbn13', remote_field='isbn_13', formatter=get_first), - Mapping('isbn10', remote_field='isbn_10', formatter=get_first), - Mapping('lccn', formatter=get_first), - Mapping( - 'oclcNumber', remote_field='oclc_numbers', - formatter=get_first + "openlibraryKey", remote_field="key", formatter=get_openlibrary_key ), + Mapping("goodreadsKey", remote_field="goodreads_key"), + Mapping("asin"), Mapping( - 'openlibraryKey', remote_field='key', - formatter=get_openlibrary_key + "firstPublishedDate", + remote_field="first_publish_date", ), - Mapping('goodreadsKey', remote_field='goodreads_key'), - Mapping('asin'), - Mapping( - 'firstPublishedDate', remote_field='first_publish_date', - ), - Mapping('publishedDate', remote_field='publish_date'), - Mapping('pages', remote_field='number_of_pages'), - Mapping('physicalFormat', remote_field='physical_format'), - Mapping('publishers'), + Mapping("publishedDate", remote_field="publish_date"), + Mapping("pages", remote_field="number_of_pages"), + Mapping("physicalFormat", remote_field="physical_format"), + Mapping("publishers"), ] self.author_mappings = [ - Mapping('id', remote_field='key', formatter=get_remote_id), - Mapping('name'), + Mapping("id", remote_field="key", formatter=get_remote_id), + Mapping("name"), Mapping( - 'openlibraryKey', remote_field='key', - formatter=get_openlibrary_key + "openlibraryKey", remote_field="key", formatter=get_openlibrary_key ), - Mapping('born', remote_field='birth_date'), - Mapping('died', remote_field='death_date'), - Mapping('bio', formatter=get_description), + Mapping("born", remote_field="birth_date"), + Mapping("died", remote_field="death_date"), + Mapping("bio", formatter=get_description), ] - def get_remote_id_from_data(self, data): - ''' format a url from an openlibrary id field ''' + """ format a url from an openlibrary id field """ try: - key = data['key'] + key = data["key"] except KeyError: - raise ConnectorException('Invalid book data') - return '%s%s' % (self.books_url, key) - + raise ConnectorException("Invalid book data") + return "%s%s" % (self.books_url, key) def is_work_data(self, data): - return bool(re.match(r'^[\/\w]+OL\d+W$', data['key'])) - + return bool(re.match(r"^[\/\w]+OL\d+W$", data["key"])) def get_edition_from_work_data(self, data): try: - key = data['key'] + key = data["key"] except KeyError: - raise ConnectorException('Invalid book data') - url = '%s%s/editions' % (self.books_url, key) + raise ConnectorException("Invalid book data") + url = "%s%s/editions" % (self.books_url, key) data = get_data(url) - return pick_default_edition(data['entries']) - + return pick_default_edition(data["entries"]) def get_work_from_edition_data(self, data): try: - key = data['works'][0]['key'] + key = data["works"][0]["key"] except (IndexError, KeyError): - raise ConnectorException('No work found for edition') - url = '%s%s' % (self.books_url, key) + raise ConnectorException("No work found for edition") + url = "%s%s" % (self.books_url, key) return get_data(url) - def get_authors_from_data(self, data): - ''' parse author json and load or create authors ''' - for author_blob in data.get('authors', []): - author_blob = author_blob.get('author', author_blob) + """ parse author json and load or create authors """ + for author_blob in data.get("authors", []): + author_blob = author_blob.get("author", author_blob) # this id is "/authors/OL1234567A" - author_id = author_blob['key'] - url = '%s%s' % (self.base_url, author_id) + author_id = author_blob["key"] + url = "%s%s" % (self.base_url, author_id) yield self.get_or_create_author(url) - def get_cover_url(self, cover_blob): - ''' ask openlibrary for the cover ''' + """ ask openlibrary for the cover """ cover_id = cover_blob[0] - image_name = '%s-L.jpg' % cover_id - return '%s/b/id/%s' % (self.covers_url, image_name) - + image_name = "%s-L.jpg" % cover_id + return "%s/b/id/%s" % (self.covers_url, image_name) def parse_search_data(self, data): - return data.get('docs') - + return data.get("docs") 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'] + key = self.books_url + search_result["key"] + author = search_result.get("author_name") or ["Unknown"] return SearchResult( - title=search_result.get('title'), + title=search_result.get("title"), key=key, - author=', '.join(author), + author=", ".join(author), connector=self, - year=search_result.get('first_publish_year'), + year=search_result.get("first_publish_year"), ) - 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] + 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'), + title=search_result.get("title"), key=key, - author=', '.join(author_names), + author=", ".join(author_names), connector=self, - year=search_result.get('publish_date'), + year=search_result.get("publish_date"), ) def load_edition_data(self, olkey): - ''' query openlibrary for editions of a work ''' - url = '%s/works/%s/editions' % (self.books_url, olkey) + """ query openlibrary for editions of a work """ + url = "%s/works/%s/editions" % (self.books_url, olkey) return get_data(url) - def expand_book_data(self, book): work = book # go from the edition to the work, if necessary @@ -164,7 +150,7 @@ class Connector(AbstractConnector): # who knows, man return - for edition_data in edition_options.get('entries'): + for edition_data in edition_options.get("entries"): # does this edition have ANY interesting data? if ignore_edition(edition_data): continue @@ -172,62 +158,63 @@ class Connector(AbstractConnector): def ignore_edition(edition_data): - ''' don't load a million editions that have no metadata ''' + """ don't load a million editions that have no metadata """ # an isbn, we love to see it - if edition_data.get('isbn_13') or edition_data.get('isbn_10'): - print(edition_data.get('isbn_10')) + if edition_data.get("isbn_13") or edition_data.get("isbn_10"): + print(edition_data.get("isbn_10")) return False # grudgingly, oclc can stay - if edition_data.get('oclc_numbers'): - print(edition_data.get('oclc_numbers')) + if edition_data.get("oclc_numbers"): + print(edition_data.get("oclc_numbers")) return False # if it has a cover it can stay - if edition_data.get('covers'): - print(edition_data.get('covers')) + if edition_data.get("covers"): + print(edition_data.get("covers")) return False # keep non-english editions - if edition_data.get('languages') and \ - 'languages/eng' not in str(edition_data.get('languages')): - print(edition_data.get('languages')) + if edition_data.get("languages") and "languages/eng" not in str( + edition_data.get("languages") + ): + print(edition_data.get("languages")) return False return True def get_description(description_blob): - ''' descriptions can be a string or a dict ''' + """ descriptions can be a string or a dict """ if isinstance(description_blob, dict): - return description_blob.get('value') + return description_blob.get("value") return description_blob def get_openlibrary_key(key): - ''' convert /books/OL27320736M into OL27320736M ''' - return key.split('/')[-1] + """ convert /books/OL27320736M into OL27320736M """ + return key.split("/")[-1] def get_languages(language_blob): - ''' /language/eng -> English ''' + """ /language/eng -> English """ langs = [] for lang in language_blob: - langs.append( - languages.get(lang.get('key', ''), None) - ) + langs.append(languages.get(lang.get("key", ""), None)) return langs def pick_default_edition(options): - ''' favor physical copies with covers in english ''' + """ favor physical copies with covers in english """ if not options: return None if len(options) == 1: return options[0] - options = [e for e in options if e.get('covers')] or options - options = [e for e in options if \ - '/languages/eng' in str(e.get('languages'))] or options - formats = ['paperback', 'hardcover', 'mass market paperback'] - options = [e for e in options if \ - str(e.get('physical_format')).lower() in formats] or options - options = [e for e in options if e.get('isbn_13')] or options - options = [e for e in options if e.get('ocaid')] or options + options = [e for e in options if e.get("covers")] or options + options = [ + e for e in options if "/languages/eng" in str(e.get("languages")) + ] or options + formats = ["paperback", "hardcover", "mass market paperback"] + options = [ + e for e in options if str(e.get("physical_format")).lower() in formats + ] or options + options = [e for e in options if e.get("isbn_13")] or options + options = [e for e in options if e.get("ocaid")] or options return options[0] diff --git a/bookwyrm/connectors/openlibrary_languages.py b/bookwyrm/connectors/openlibrary_languages.py index b687f8b97..2520d1ea1 100644 --- a/bookwyrm/connectors/openlibrary_languages.py +++ b/bookwyrm/connectors/openlibrary_languages.py @@ -1,467 +1,467 @@ -''' key lookups for openlibrary languages ''' +""" key lookups for openlibrary languages """ languages = { - '/languages/eng': 'English', - '/languages/fre': 'French', - '/languages/spa': 'Spanish', - '/languages/ger': 'German', - '/languages/rus': 'Russian', - '/languages/ita': 'Italian', - '/languages/chi': 'Chinese', - '/languages/jpn': 'Japanese', - '/languages/por': 'Portuguese', - '/languages/ara': 'Arabic', - '/languages/pol': 'Polish', - '/languages/heb': 'Hebrew', - '/languages/kor': 'Korean', - '/languages/dut': 'Dutch', - '/languages/ind': 'Indonesian', - '/languages/lat': 'Latin', - '/languages/und': 'Undetermined', - '/languages/cmn': 'Mandarin', - '/languages/hin': 'Hindi', - '/languages/swe': 'Swedish', - '/languages/dan': 'Danish', - '/languages/urd': 'Urdu', - '/languages/hun': 'Hungarian', - '/languages/cze': 'Czech', - '/languages/tur': 'Turkish', - '/languages/ukr': 'Ukrainian', - '/languages/gre': 'Greek', - '/languages/vie': 'Vietnamese', - '/languages/bul': 'Bulgarian', - '/languages/ben': 'Bengali', - '/languages/rum': 'Romanian', - '/languages/cat': 'Catalan', - '/languages/nor': 'Norwegian', - '/languages/tha': 'Thai', - '/languages/per': 'Persian', - '/languages/scr': 'Croatian', - '/languages/mul': 'Multiple languages', - '/languages/fin': 'Finnish', - '/languages/tam': 'Tamil', - '/languages/guj': 'Gujarati', - '/languages/mar': 'Marathi', - '/languages/scc': 'Serbian', - '/languages/pan': 'Panjabi', - '/languages/wel': 'Welsh', - '/languages/tel': 'Telugu', - '/languages/yid': 'Yiddish', - '/languages/kan': 'Kannada', - '/languages/slo': 'Slovak', - '/languages/san': 'Sanskrit', - '/languages/arm': 'Armenian', - '/languages/mal': 'Malayalam', - '/languages/may': 'Malay', - '/languages/bur': 'Burmese', - '/languages/slv': 'Slovenian', - '/languages/lit': 'Lithuanian', - '/languages/tib': 'Tibetan', - '/languages/lav': 'Latvian', - '/languages/est': 'Estonian', - '/languages/nep': 'Nepali', - '/languages/ori': 'Oriya', - '/languages/mon': 'Mongolian', - '/languages/alb': 'Albanian', - '/languages/iri': 'Irish', - '/languages/geo': 'Georgian', - '/languages/afr': 'Afrikaans', - '/languages/grc': 'Ancient Greek', - '/languages/mac': 'Macedonian', - '/languages/bel': 'Belarusian', - '/languages/ice': 'Icelandic', - '/languages/srp': 'Serbian', - '/languages/snh': 'Sinhalese', - '/languages/snd': 'Sindhi', - '/languages/ota': 'Turkish, Ottoman', - '/languages/kur': 'Kurdish', - '/languages/aze': 'Azerbaijani', - '/languages/pus': 'Pushto', - '/languages/amh': 'Amharic', - '/languages/gag': 'Galician', - '/languages/hrv': 'Croatian', - '/languages/sin': 'Sinhalese', - '/languages/asm': 'Assamese', - '/languages/uzb': 'Uzbek', - '/languages/gae': 'Scottish Gaelix', - '/languages/kaz': 'Kazakh', - '/languages/swa': 'Swahili', - '/languages/bos': 'Bosnian', - '/languages/glg': 'Galician ', - '/languages/baq': 'Basque', - '/languages/tgl': 'Tagalog', - '/languages/raj': 'Rajasthani', - '/languages/gle': 'Irish', - '/languages/lao': 'Lao', - '/languages/jav': 'Javanese', - '/languages/mai': 'Maithili', - '/languages/tgk': 'Tajik ', - '/languages/khm': 'Khmer', - '/languages/roh': 'Raeto-Romance', - '/languages/kok': 'Konkani ', - '/languages/sit': 'Sino-Tibetan (Other)', - '/languages/mol': 'Moldavian', - '/languages/kir': 'Kyrgyz', - '/languages/new': 'Newari', - '/languages/inc': 'Indic (Other)', - '/languages/frm': 'French, Middle (ca. 1300-1600)', - '/languages/esp': 'Esperanto', - '/languages/hau': 'Hausa', - '/languages/tag': 'Tagalog', - '/languages/tuk': 'Turkmen', - '/languages/enm': 'English, Middle (1100-1500)', - '/languages/map': 'Austronesian (Other)', - '/languages/pli': 'Pali', - '/languages/fro': 'French, Old (ca. 842-1300)', - '/languages/nic': 'Niger-Kordofanian (Other)', - '/languages/tir': 'Tigrinya', - '/languages/wen': 'Sorbian (Other)', - '/languages/bho': 'Bhojpuri', - '/languages/roa': 'Romance (Other)', - '/languages/tut': 'Altaic (Other)', - '/languages/bra': 'Braj', - '/languages/sun': 'Sundanese', - '/languages/fiu': 'Finno-Ugrian (Other)', - '/languages/far': 'Faroese', - '/languages/ban': 'Balinese', - '/languages/tar': 'Tatar', - '/languages/bak': 'Bashkir', - '/languages/tat': 'Tatar', - '/languages/chu': 'Church Slavic', - '/languages/dra': 'Dravidian (Other)', - '/languages/pra': 'Prakrit languages', - '/languages/paa': 'Papuan (Other)', - '/languages/doi': 'Dogri', - '/languages/lah': 'Lahndā', - '/languages/mni': 'Manipuri', - '/languages/yor': 'Yoruba', - '/languages/gmh': 'German, Middle High (ca. 1050-1500)', - '/languages/kas': 'Kashmiri', - '/languages/fri': 'Frisian', - '/languages/mla': 'Malagasy', - '/languages/egy': 'Egyptian', - '/languages/rom': 'Romani', - '/languages/syr': 'Syriac, Modern', - '/languages/cau': 'Caucasian (Other)', - '/languages/hbs': 'Serbo-Croatian', - '/languages/sai': 'South American Indian (Other)', - '/languages/pro': 'Provençal (to 1500)', - '/languages/cpf': 'Creoles and Pidgins, French-based (Other)', - '/languages/ang': 'English, Old (ca. 450-1100)', - '/languages/bal': 'Baluchi', - '/languages/gla': 'Scottish Gaelic', - '/languages/chv': 'Chuvash', - '/languages/kin': 'Kinyarwanda', - '/languages/zul': 'Zulu', - '/languages/sla': 'Slavic (Other)', - '/languages/som': 'Somali', - '/languages/mlt': 'Maltese', - '/languages/uig': 'Uighur', - '/languages/mlg': 'Malagasy', - '/languages/sho': 'Shona', - '/languages/lan': 'Occitan (post 1500)', - '/languages/bre': 'Breton', - '/languages/sco': 'Scots', - '/languages/sso': 'Sotho', - '/languages/myn': 'Mayan languages', - '/languages/xho': 'Xhosa', - '/languages/gem': 'Germanic (Other)', - '/languages/esk': 'Eskimo languages', - '/languages/akk': 'Akkadian', - '/languages/div': 'Maldivian', - '/languages/sah': 'Yakut', - '/languages/tsw': 'Tswana', - '/languages/nso': 'Northern Sotho', - '/languages/pap': 'Papiamento', - '/languages/bnt': 'Bantu (Other)', - '/languages/oss': 'Ossetic', - '/languages/cre': 'Cree', - '/languages/ibo': 'Igbo', - '/languages/fao': 'Faroese', - '/languages/nai': 'North American Indian (Other)', - '/languages/mag': 'Magahi', - '/languages/arc': 'Aramaic', - '/languages/epo': 'Esperanto', - '/languages/kha': 'Khasi', - '/languages/oji': 'Ojibwa', - '/languages/que': 'Quechua', - '/languages/lug': 'Ganda', - '/languages/mwr': 'Marwari', - '/languages/awa': 'Awadhi ', - '/languages/cor': 'Cornish', - '/languages/lad': 'Ladino', - '/languages/dzo': 'Dzongkha', - '/languages/cop': 'Coptic', - '/languages/nah': 'Nahuatl', - '/languages/cai': 'Central American Indian (Other)', - '/languages/phi': 'Philippine (Other)', - '/languages/moh': 'Mohawk', - '/languages/crp': 'Creoles and Pidgins (Other)', - '/languages/nya': 'Nyanja', - '/languages/wol': 'Wolof ', - '/languages/haw': 'Hawaiian', - '/languages/eth': 'Ethiopic', - '/languages/mis': 'Miscellaneous languages', - '/languages/mkh': 'Mon-Khmer (Other)', - '/languages/alg': 'Algonquian (Other)', - '/languages/nde': 'Ndebele (Zimbabwe)', - '/languages/ssa': 'Nilo-Saharan (Other)', - '/languages/chm': 'Mari', - '/languages/che': 'Chechen', - '/languages/gez': 'Ethiopic', - '/languages/ven': 'Venda', - '/languages/cam': 'Khmer', - '/languages/fur': 'Friulian', - '/languages/ful': 'Fula', - '/languages/gal': 'Oromo', - '/languages/jrb': 'Judeo-Arabic', - '/languages/bua': 'Buriat', - '/languages/ady': 'Adygei', - '/languages/bem': 'Bemba', - '/languages/kar': 'Karen languages', - '/languages/sna': 'Shona', - '/languages/twi': 'Twi', - '/languages/btk': 'Batak', - '/languages/kaa': 'Kara-Kalpak', - '/languages/kom': 'Komi', - '/languages/sot': 'Sotho', - '/languages/tso': 'Tsonga', - '/languages/cpe': 'Creoles and Pidgins, English-based (Other)', - '/languages/gua': 'Guarani', - '/languages/mao': 'Maori', - '/languages/mic': 'Micmac', - '/languages/swz': 'Swazi', - '/languages/taj': 'Tajik', - '/languages/smo': 'Samoan', - '/languages/ace': 'Achinese', - '/languages/afa': 'Afroasiatic (Other)', - '/languages/lap': 'Sami', - '/languages/min': 'Minangkabau', - '/languages/oci': 'Occitan (post 1500)', - '/languages/tsn': 'Tswana', - '/languages/pal': 'Pahlavi', - '/languages/sux': 'Sumerian', - '/languages/ewe': 'Ewe', - '/languages/him': 'Himachali', - '/languages/kaw': 'Kawi', - '/languages/lus': 'Lushai', - '/languages/ceb': 'Cebuano', - '/languages/chr': 'Cherokee', - '/languages/fil': 'Filipino', - '/languages/ndo': 'Ndonga', - '/languages/ilo': 'Iloko', - '/languages/kbd': 'Kabardian', - '/languages/orm': 'Oromo', - '/languages/dum': 'Dutch, Middle (ca. 1050-1350)', - '/languages/bam': 'Bambara', - '/languages/goh': 'Old High German', - '/languages/got': 'Gothic', - '/languages/kon': 'Kongo', - '/languages/mun': 'Munda (Other)', - '/languages/kru': 'Kurukh', - '/languages/pam': 'Pampanga', - '/languages/grn': 'Guarani', - '/languages/gaa': 'Gã', - '/languages/fry': 'Frisian', - '/languages/iba': 'Iban', - '/languages/mak': 'Makasar', - '/languages/kik': 'Kikuyu', - '/languages/cho': 'Choctaw', - '/languages/cpp': 'Creoles and Pidgins, Portuguese-based (Other)', - '/languages/dak': 'Dakota', - '/languages/udm': 'Udmurt ', - '/languages/hat': 'Haitian French Creole', - '/languages/mus': 'Creek', - '/languages/ber': 'Berber (Other)', - '/languages/hil': 'Hiligaynon', - '/languages/iro': 'Iroquoian (Other)', - '/languages/kua': 'Kuanyama', - '/languages/mno': 'Manobo languages', - '/languages/run': 'Rundi', - '/languages/sat': 'Santali', - '/languages/shn': 'Shan', - '/languages/tyv': 'Tuvinian', - '/languages/chg': 'Chagatai', - '/languages/syc': 'Syriac', - '/languages/ath': 'Athapascan (Other)', - '/languages/aym': 'Aymara', - '/languages/bug': 'Bugis', - '/languages/cel': 'Celtic (Other)', - '/languages/int': 'Interlingua (International Auxiliary Language Association)', - '/languages/xal': 'Oirat', - '/languages/ava': 'Avaric', - '/languages/son': 'Songhai', - '/languages/tah': 'Tahitian', - '/languages/tet': 'Tetum', - '/languages/ira': 'Iranian (Other)', - '/languages/kac': 'Kachin', - '/languages/nob': 'Norwegian (Bokmål)', - '/languages/vai': 'Vai', - '/languages/bik': 'Bikol', - '/languages/mos': 'Mooré', - '/languages/tig': 'Tigré', - '/languages/fat': 'Fanti', - '/languages/her': 'Herero', - '/languages/kal': 'Kalâtdlisut', - '/languages/mad': 'Madurese', - '/languages/yue': 'Cantonese', - '/languages/chn': 'Chinook jargon', - '/languages/hmn': 'Hmong', - '/languages/lin': 'Lingala', - '/languages/man': 'Mandingo', - '/languages/nds': 'Low German', - '/languages/bas': 'Basa', - '/languages/gay': 'Gayo', - '/languages/gsw': 'gsw', - '/languages/ine': 'Indo-European (Other)', - '/languages/kro': 'Kru (Other)', - '/languages/kum': 'Kumyk', - '/languages/tsi': 'Tsimshian', - '/languages/zap': 'Zapotec', - '/languages/ach': 'Acoli', - '/languages/ada': 'Adangme', - '/languages/aka': 'Akan', - '/languages/khi': 'Khoisan (Other)', - '/languages/srd': 'Sardinian', - '/languages/arn': 'Mapuche', - '/languages/dyu': 'Dyula', - '/languages/loz': 'Lozi', - '/languages/ltz': 'Luxembourgish', - '/languages/sag': 'Sango (Ubangi Creole)', - '/languages/lez': 'Lezgian', - '/languages/luo': 'Luo (Kenya and Tanzania)', - '/languages/ssw': 'Swazi ', - '/languages/krc': 'Karachay-Balkar', - '/languages/nyn': 'Nyankole', - '/languages/sal': 'Salishan languages', - '/languages/jpr': 'Judeo-Persian', - '/languages/pau': 'Palauan', - '/languages/smi': 'Sami', - '/languages/aar': 'Afar', - '/languages/abk': 'Abkhaz', - '/languages/gon': 'Gondi', - '/languages/nzi': 'Nzima', - '/languages/sam': 'Samaritan Aramaic', - '/languages/sao': 'Samoan', - '/languages/srr': 'Serer', - '/languages/apa': 'Apache languages', - '/languages/crh': 'Crimean Tatar', - '/languages/efi': 'Efik', - '/languages/iku': 'Inuktitut', - '/languages/nav': 'Navajo', - '/languages/pon': 'Ponape', - '/languages/tmh': 'Tamashek', - '/languages/aus': 'Australian languages', - '/languages/oto': 'Otomian languages', - '/languages/war': 'Waray', - '/languages/ypk': 'Yupik languages', - '/languages/ave': 'Avestan', - '/languages/cus': 'Cushitic (Other)', - '/languages/del': 'Delaware', - '/languages/fon': 'Fon', - '/languages/ina': 'Interlingua (International Auxiliary Language Association)', - '/languages/myv': 'Erzya', - '/languages/pag': 'Pangasinan', - '/languages/peo': 'Old Persian (ca. 600-400 B.C.)', - '/languages/vls': 'Flemish', - '/languages/bai': 'Bamileke languages', - '/languages/bla': 'Siksika', - '/languages/day': 'Dayak', - '/languages/men': 'Mende', - '/languages/tai': 'Tai', - '/languages/ton': 'Tongan', - '/languages/uga': 'Ugaritic', - '/languages/yao': 'Yao (Africa)', - '/languages/zza': 'Zaza', - '/languages/bin': 'Edo', - '/languages/frs': 'East Frisian', - '/languages/inh': 'Ingush', - '/languages/mah': 'Marshallese', - '/languages/sem': 'Semitic (Other)', - '/languages/art': 'Artificial (Other)', - '/languages/chy': 'Cheyenne', - '/languages/cmc': 'Chamic languages', - '/languages/dar': 'Dargwa', - '/languages/dua': 'Duala', - '/languages/elx': 'Elamite', - '/languages/fan': 'Fang', - '/languages/fij': 'Fijian', - '/languages/gil': 'Gilbertese', - '/languages/ijo': 'Ijo', - '/languages/kam': 'Kamba', - '/languages/nog': 'Nogai', - '/languages/non': 'Old Norse', - '/languages/tem': 'Temne', - '/languages/arg': 'Aragonese', - '/languages/arp': 'Arapaho', - '/languages/arw': 'Arawak', - '/languages/din': 'Dinka', - '/languages/grb': 'Grebo', - '/languages/kos': 'Kusaie', - '/languages/lub': 'Luba-Katanga', - '/languages/mnc': 'Manchu', - '/languages/nyo': 'Nyoro', - '/languages/rar': 'Rarotongan', - '/languages/sel': 'Selkup', - '/languages/tkl': 'Tokelauan', - '/languages/tog': 'Tonga (Nyasa)', - '/languages/tum': 'Tumbuka', - '/languages/alt': 'Altai', - '/languages/ase': 'American Sign Language', - '/languages/ast': 'Asturian', - '/languages/chk': 'Chuukese', - '/languages/cos': 'Corsican', - '/languages/ewo': 'Ewondo', - '/languages/gor': 'Gorontalo', - '/languages/hmo': 'Hiri Motu', - '/languages/lol': 'Mongo-Nkundu', - '/languages/lun': 'Lunda', - '/languages/mas': 'Masai', - '/languages/niu': 'Niuean', - '/languages/rup': 'Aromanian', - '/languages/sas': 'Sasak', - '/languages/sio': 'Siouan (Other)', - '/languages/sus': 'Susu', - '/languages/zun': 'Zuni', - '/languages/bat': 'Baltic (Other)', - '/languages/car': 'Carib', - '/languages/cha': 'Chamorro', - '/languages/kab': 'Kabyle', - '/languages/kau': 'Kanuri', - '/languages/kho': 'Khotanese', - '/languages/lua': 'Luba-Lulua', - '/languages/mdf': 'Moksha', - '/languages/nbl': 'Ndebele (South Africa)', - '/languages/umb': 'Umbundu', - '/languages/wak': 'Wakashan languages', - '/languages/wal': 'Wolayta', - '/languages/ale': 'Aleut', - '/languages/bis': 'Bislama', - '/languages/gba': 'Gbaya', - '/languages/glv': 'Manx', - '/languages/gul': 'Gullah', - '/languages/ipk': 'Inupiaq', - '/languages/krl': 'Karelian', - '/languages/lam': 'Lamba (Zambia and Congo)', - '/languages/sad': 'Sandawe', - '/languages/sid': 'Sidamo', - '/languages/snk': 'Soninke', - '/languages/srn': 'Sranan', - '/languages/suk': 'Sukuma', - '/languages/ter': 'Terena', - '/languages/tiv': 'Tiv', - '/languages/tli': 'Tlingit', - '/languages/tpi': 'Tok Pisin', - '/languages/tvl': 'Tuvaluan', - '/languages/yap': 'Yapese', - '/languages/eka': 'Ekajuk', - '/languages/hsb': 'Upper Sorbian', - '/languages/ido': 'Ido', - '/languages/kmb': 'Kimbundu', - '/languages/kpe': 'Kpelle', - '/languages/mwl': 'Mirandese', - '/languages/nno': 'Nynorsk', - '/languages/nub': 'Nubian languages', - '/languages/osa': 'Osage', - '/languages/sme': 'Northern Sami', - '/languages/znd': 'Zande languages', + "/languages/eng": "English", + "/languages/fre": "French", + "/languages/spa": "Spanish", + "/languages/ger": "German", + "/languages/rus": "Russian", + "/languages/ita": "Italian", + "/languages/chi": "Chinese", + "/languages/jpn": "Japanese", + "/languages/por": "Portuguese", + "/languages/ara": "Arabic", + "/languages/pol": "Polish", + "/languages/heb": "Hebrew", + "/languages/kor": "Korean", + "/languages/dut": "Dutch", + "/languages/ind": "Indonesian", + "/languages/lat": "Latin", + "/languages/und": "Undetermined", + "/languages/cmn": "Mandarin", + "/languages/hin": "Hindi", + "/languages/swe": "Swedish", + "/languages/dan": "Danish", + "/languages/urd": "Urdu", + "/languages/hun": "Hungarian", + "/languages/cze": "Czech", + "/languages/tur": "Turkish", + "/languages/ukr": "Ukrainian", + "/languages/gre": "Greek", + "/languages/vie": "Vietnamese", + "/languages/bul": "Bulgarian", + "/languages/ben": "Bengali", + "/languages/rum": "Romanian", + "/languages/cat": "Catalan", + "/languages/nor": "Norwegian", + "/languages/tha": "Thai", + "/languages/per": "Persian", + "/languages/scr": "Croatian", + "/languages/mul": "Multiple languages", + "/languages/fin": "Finnish", + "/languages/tam": "Tamil", + "/languages/guj": "Gujarati", + "/languages/mar": "Marathi", + "/languages/scc": "Serbian", + "/languages/pan": "Panjabi", + "/languages/wel": "Welsh", + "/languages/tel": "Telugu", + "/languages/yid": "Yiddish", + "/languages/kan": "Kannada", + "/languages/slo": "Slovak", + "/languages/san": "Sanskrit", + "/languages/arm": "Armenian", + "/languages/mal": "Malayalam", + "/languages/may": "Malay", + "/languages/bur": "Burmese", + "/languages/slv": "Slovenian", + "/languages/lit": "Lithuanian", + "/languages/tib": "Tibetan", + "/languages/lav": "Latvian", + "/languages/est": "Estonian", + "/languages/nep": "Nepali", + "/languages/ori": "Oriya", + "/languages/mon": "Mongolian", + "/languages/alb": "Albanian", + "/languages/iri": "Irish", + "/languages/geo": "Georgian", + "/languages/afr": "Afrikaans", + "/languages/grc": "Ancient Greek", + "/languages/mac": "Macedonian", + "/languages/bel": "Belarusian", + "/languages/ice": "Icelandic", + "/languages/srp": "Serbian", + "/languages/snh": "Sinhalese", + "/languages/snd": "Sindhi", + "/languages/ota": "Turkish, Ottoman", + "/languages/kur": "Kurdish", + "/languages/aze": "Azerbaijani", + "/languages/pus": "Pushto", + "/languages/amh": "Amharic", + "/languages/gag": "Galician", + "/languages/hrv": "Croatian", + "/languages/sin": "Sinhalese", + "/languages/asm": "Assamese", + "/languages/uzb": "Uzbek", + "/languages/gae": "Scottish Gaelix", + "/languages/kaz": "Kazakh", + "/languages/swa": "Swahili", + "/languages/bos": "Bosnian", + "/languages/glg": "Galician ", + "/languages/baq": "Basque", + "/languages/tgl": "Tagalog", + "/languages/raj": "Rajasthani", + "/languages/gle": "Irish", + "/languages/lao": "Lao", + "/languages/jav": "Javanese", + "/languages/mai": "Maithili", + "/languages/tgk": "Tajik ", + "/languages/khm": "Khmer", + "/languages/roh": "Raeto-Romance", + "/languages/kok": "Konkani ", + "/languages/sit": "Sino-Tibetan (Other)", + "/languages/mol": "Moldavian", + "/languages/kir": "Kyrgyz", + "/languages/new": "Newari", + "/languages/inc": "Indic (Other)", + "/languages/frm": "French, Middle (ca. 1300-1600)", + "/languages/esp": "Esperanto", + "/languages/hau": "Hausa", + "/languages/tag": "Tagalog", + "/languages/tuk": "Turkmen", + "/languages/enm": "English, Middle (1100-1500)", + "/languages/map": "Austronesian (Other)", + "/languages/pli": "Pali", + "/languages/fro": "French, Old (ca. 842-1300)", + "/languages/nic": "Niger-Kordofanian (Other)", + "/languages/tir": "Tigrinya", + "/languages/wen": "Sorbian (Other)", + "/languages/bho": "Bhojpuri", + "/languages/roa": "Romance (Other)", + "/languages/tut": "Altaic (Other)", + "/languages/bra": "Braj", + "/languages/sun": "Sundanese", + "/languages/fiu": "Finno-Ugrian (Other)", + "/languages/far": "Faroese", + "/languages/ban": "Balinese", + "/languages/tar": "Tatar", + "/languages/bak": "Bashkir", + "/languages/tat": "Tatar", + "/languages/chu": "Church Slavic", + "/languages/dra": "Dravidian (Other)", + "/languages/pra": "Prakrit languages", + "/languages/paa": "Papuan (Other)", + "/languages/doi": "Dogri", + "/languages/lah": "Lahndā", + "/languages/mni": "Manipuri", + "/languages/yor": "Yoruba", + "/languages/gmh": "German, Middle High (ca. 1050-1500)", + "/languages/kas": "Kashmiri", + "/languages/fri": "Frisian", + "/languages/mla": "Malagasy", + "/languages/egy": "Egyptian", + "/languages/rom": "Romani", + "/languages/syr": "Syriac, Modern", + "/languages/cau": "Caucasian (Other)", + "/languages/hbs": "Serbo-Croatian", + "/languages/sai": "South American Indian (Other)", + "/languages/pro": "Provençal (to 1500)", + "/languages/cpf": "Creoles and Pidgins, French-based (Other)", + "/languages/ang": "English, Old (ca. 450-1100)", + "/languages/bal": "Baluchi", + "/languages/gla": "Scottish Gaelic", + "/languages/chv": "Chuvash", + "/languages/kin": "Kinyarwanda", + "/languages/zul": "Zulu", + "/languages/sla": "Slavic (Other)", + "/languages/som": "Somali", + "/languages/mlt": "Maltese", + "/languages/uig": "Uighur", + "/languages/mlg": "Malagasy", + "/languages/sho": "Shona", + "/languages/lan": "Occitan (post 1500)", + "/languages/bre": "Breton", + "/languages/sco": "Scots", + "/languages/sso": "Sotho", + "/languages/myn": "Mayan languages", + "/languages/xho": "Xhosa", + "/languages/gem": "Germanic (Other)", + "/languages/esk": "Eskimo languages", + "/languages/akk": "Akkadian", + "/languages/div": "Maldivian", + "/languages/sah": "Yakut", + "/languages/tsw": "Tswana", + "/languages/nso": "Northern Sotho", + "/languages/pap": "Papiamento", + "/languages/bnt": "Bantu (Other)", + "/languages/oss": "Ossetic", + "/languages/cre": "Cree", + "/languages/ibo": "Igbo", + "/languages/fao": "Faroese", + "/languages/nai": "North American Indian (Other)", + "/languages/mag": "Magahi", + "/languages/arc": "Aramaic", + "/languages/epo": "Esperanto", + "/languages/kha": "Khasi", + "/languages/oji": "Ojibwa", + "/languages/que": "Quechua", + "/languages/lug": "Ganda", + "/languages/mwr": "Marwari", + "/languages/awa": "Awadhi ", + "/languages/cor": "Cornish", + "/languages/lad": "Ladino", + "/languages/dzo": "Dzongkha", + "/languages/cop": "Coptic", + "/languages/nah": "Nahuatl", + "/languages/cai": "Central American Indian (Other)", + "/languages/phi": "Philippine (Other)", + "/languages/moh": "Mohawk", + "/languages/crp": "Creoles and Pidgins (Other)", + "/languages/nya": "Nyanja", + "/languages/wol": "Wolof ", + "/languages/haw": "Hawaiian", + "/languages/eth": "Ethiopic", + "/languages/mis": "Miscellaneous languages", + "/languages/mkh": "Mon-Khmer (Other)", + "/languages/alg": "Algonquian (Other)", + "/languages/nde": "Ndebele (Zimbabwe)", + "/languages/ssa": "Nilo-Saharan (Other)", + "/languages/chm": "Mari", + "/languages/che": "Chechen", + "/languages/gez": "Ethiopic", + "/languages/ven": "Venda", + "/languages/cam": "Khmer", + "/languages/fur": "Friulian", + "/languages/ful": "Fula", + "/languages/gal": "Oromo", + "/languages/jrb": "Judeo-Arabic", + "/languages/bua": "Buriat", + "/languages/ady": "Adygei", + "/languages/bem": "Bemba", + "/languages/kar": "Karen languages", + "/languages/sna": "Shona", + "/languages/twi": "Twi", + "/languages/btk": "Batak", + "/languages/kaa": "Kara-Kalpak", + "/languages/kom": "Komi", + "/languages/sot": "Sotho", + "/languages/tso": "Tsonga", + "/languages/cpe": "Creoles and Pidgins, English-based (Other)", + "/languages/gua": "Guarani", + "/languages/mao": "Maori", + "/languages/mic": "Micmac", + "/languages/swz": "Swazi", + "/languages/taj": "Tajik", + "/languages/smo": "Samoan", + "/languages/ace": "Achinese", + "/languages/afa": "Afroasiatic (Other)", + "/languages/lap": "Sami", + "/languages/min": "Minangkabau", + "/languages/oci": "Occitan (post 1500)", + "/languages/tsn": "Tswana", + "/languages/pal": "Pahlavi", + "/languages/sux": "Sumerian", + "/languages/ewe": "Ewe", + "/languages/him": "Himachali", + "/languages/kaw": "Kawi", + "/languages/lus": "Lushai", + "/languages/ceb": "Cebuano", + "/languages/chr": "Cherokee", + "/languages/fil": "Filipino", + "/languages/ndo": "Ndonga", + "/languages/ilo": "Iloko", + "/languages/kbd": "Kabardian", + "/languages/orm": "Oromo", + "/languages/dum": "Dutch, Middle (ca. 1050-1350)", + "/languages/bam": "Bambara", + "/languages/goh": "Old High German", + "/languages/got": "Gothic", + "/languages/kon": "Kongo", + "/languages/mun": "Munda (Other)", + "/languages/kru": "Kurukh", + "/languages/pam": "Pampanga", + "/languages/grn": "Guarani", + "/languages/gaa": "Gã", + "/languages/fry": "Frisian", + "/languages/iba": "Iban", + "/languages/mak": "Makasar", + "/languages/kik": "Kikuyu", + "/languages/cho": "Choctaw", + "/languages/cpp": "Creoles and Pidgins, Portuguese-based (Other)", + "/languages/dak": "Dakota", + "/languages/udm": "Udmurt ", + "/languages/hat": "Haitian French Creole", + "/languages/mus": "Creek", + "/languages/ber": "Berber (Other)", + "/languages/hil": "Hiligaynon", + "/languages/iro": "Iroquoian (Other)", + "/languages/kua": "Kuanyama", + "/languages/mno": "Manobo languages", + "/languages/run": "Rundi", + "/languages/sat": "Santali", + "/languages/shn": "Shan", + "/languages/tyv": "Tuvinian", + "/languages/chg": "Chagatai", + "/languages/syc": "Syriac", + "/languages/ath": "Athapascan (Other)", + "/languages/aym": "Aymara", + "/languages/bug": "Bugis", + "/languages/cel": "Celtic (Other)", + "/languages/int": "Interlingua (International Auxiliary Language Association)", + "/languages/xal": "Oirat", + "/languages/ava": "Avaric", + "/languages/son": "Songhai", + "/languages/tah": "Tahitian", + "/languages/tet": "Tetum", + "/languages/ira": "Iranian (Other)", + "/languages/kac": "Kachin", + "/languages/nob": "Norwegian (Bokmål)", + "/languages/vai": "Vai", + "/languages/bik": "Bikol", + "/languages/mos": "Mooré", + "/languages/tig": "Tigré", + "/languages/fat": "Fanti", + "/languages/her": "Herero", + "/languages/kal": "Kalâtdlisut", + "/languages/mad": "Madurese", + "/languages/yue": "Cantonese", + "/languages/chn": "Chinook jargon", + "/languages/hmn": "Hmong", + "/languages/lin": "Lingala", + "/languages/man": "Mandingo", + "/languages/nds": "Low German", + "/languages/bas": "Basa", + "/languages/gay": "Gayo", + "/languages/gsw": "gsw", + "/languages/ine": "Indo-European (Other)", + "/languages/kro": "Kru (Other)", + "/languages/kum": "Kumyk", + "/languages/tsi": "Tsimshian", + "/languages/zap": "Zapotec", + "/languages/ach": "Acoli", + "/languages/ada": "Adangme", + "/languages/aka": "Akan", + "/languages/khi": "Khoisan (Other)", + "/languages/srd": "Sardinian", + "/languages/arn": "Mapuche", + "/languages/dyu": "Dyula", + "/languages/loz": "Lozi", + "/languages/ltz": "Luxembourgish", + "/languages/sag": "Sango (Ubangi Creole)", + "/languages/lez": "Lezgian", + "/languages/luo": "Luo (Kenya and Tanzania)", + "/languages/ssw": "Swazi ", + "/languages/krc": "Karachay-Balkar", + "/languages/nyn": "Nyankole", + "/languages/sal": "Salishan languages", + "/languages/jpr": "Judeo-Persian", + "/languages/pau": "Palauan", + "/languages/smi": "Sami", + "/languages/aar": "Afar", + "/languages/abk": "Abkhaz", + "/languages/gon": "Gondi", + "/languages/nzi": "Nzima", + "/languages/sam": "Samaritan Aramaic", + "/languages/sao": "Samoan", + "/languages/srr": "Serer", + "/languages/apa": "Apache languages", + "/languages/crh": "Crimean Tatar", + "/languages/efi": "Efik", + "/languages/iku": "Inuktitut", + "/languages/nav": "Navajo", + "/languages/pon": "Ponape", + "/languages/tmh": "Tamashek", + "/languages/aus": "Australian languages", + "/languages/oto": "Otomian languages", + "/languages/war": "Waray", + "/languages/ypk": "Yupik languages", + "/languages/ave": "Avestan", + "/languages/cus": "Cushitic (Other)", + "/languages/del": "Delaware", + "/languages/fon": "Fon", + "/languages/ina": "Interlingua (International Auxiliary Language Association)", + "/languages/myv": "Erzya", + "/languages/pag": "Pangasinan", + "/languages/peo": "Old Persian (ca. 600-400 B.C.)", + "/languages/vls": "Flemish", + "/languages/bai": "Bamileke languages", + "/languages/bla": "Siksika", + "/languages/day": "Dayak", + "/languages/men": "Mende", + "/languages/tai": "Tai", + "/languages/ton": "Tongan", + "/languages/uga": "Ugaritic", + "/languages/yao": "Yao (Africa)", + "/languages/zza": "Zaza", + "/languages/bin": "Edo", + "/languages/frs": "East Frisian", + "/languages/inh": "Ingush", + "/languages/mah": "Marshallese", + "/languages/sem": "Semitic (Other)", + "/languages/art": "Artificial (Other)", + "/languages/chy": "Cheyenne", + "/languages/cmc": "Chamic languages", + "/languages/dar": "Dargwa", + "/languages/dua": "Duala", + "/languages/elx": "Elamite", + "/languages/fan": "Fang", + "/languages/fij": "Fijian", + "/languages/gil": "Gilbertese", + "/languages/ijo": "Ijo", + "/languages/kam": "Kamba", + "/languages/nog": "Nogai", + "/languages/non": "Old Norse", + "/languages/tem": "Temne", + "/languages/arg": "Aragonese", + "/languages/arp": "Arapaho", + "/languages/arw": "Arawak", + "/languages/din": "Dinka", + "/languages/grb": "Grebo", + "/languages/kos": "Kusaie", + "/languages/lub": "Luba-Katanga", + "/languages/mnc": "Manchu", + "/languages/nyo": "Nyoro", + "/languages/rar": "Rarotongan", + "/languages/sel": "Selkup", + "/languages/tkl": "Tokelauan", + "/languages/tog": "Tonga (Nyasa)", + "/languages/tum": "Tumbuka", + "/languages/alt": "Altai", + "/languages/ase": "American Sign Language", + "/languages/ast": "Asturian", + "/languages/chk": "Chuukese", + "/languages/cos": "Corsican", + "/languages/ewo": "Ewondo", + "/languages/gor": "Gorontalo", + "/languages/hmo": "Hiri Motu", + "/languages/lol": "Mongo-Nkundu", + "/languages/lun": "Lunda", + "/languages/mas": "Masai", + "/languages/niu": "Niuean", + "/languages/rup": "Aromanian", + "/languages/sas": "Sasak", + "/languages/sio": "Siouan (Other)", + "/languages/sus": "Susu", + "/languages/zun": "Zuni", + "/languages/bat": "Baltic (Other)", + "/languages/car": "Carib", + "/languages/cha": "Chamorro", + "/languages/kab": "Kabyle", + "/languages/kau": "Kanuri", + "/languages/kho": "Khotanese", + "/languages/lua": "Luba-Lulua", + "/languages/mdf": "Moksha", + "/languages/nbl": "Ndebele (South Africa)", + "/languages/umb": "Umbundu", + "/languages/wak": "Wakashan languages", + "/languages/wal": "Wolayta", + "/languages/ale": "Aleut", + "/languages/bis": "Bislama", + "/languages/gba": "Gbaya", + "/languages/glv": "Manx", + "/languages/gul": "Gullah", + "/languages/ipk": "Inupiaq", + "/languages/krl": "Karelian", + "/languages/lam": "Lamba (Zambia and Congo)", + "/languages/sad": "Sandawe", + "/languages/sid": "Sidamo", + "/languages/snk": "Soninke", + "/languages/srn": "Sranan", + "/languages/suk": "Sukuma", + "/languages/ter": "Terena", + "/languages/tiv": "Tiv", + "/languages/tli": "Tlingit", + "/languages/tpi": "Tok Pisin", + "/languages/tvl": "Tuvaluan", + "/languages/yap": "Yapese", + "/languages/eka": "Ekajuk", + "/languages/hsb": "Upper Sorbian", + "/languages/ido": "Ido", + "/languages/kmb": "Kimbundu", + "/languages/kpe": "Kpelle", + "/languages/mwl": "Mirandese", + "/languages/nno": "Nynorsk", + "/languages/nub": "Nubian languages", + "/languages/osa": "Osage", + "/languages/sme": "Northern Sami", + "/languages/znd": "Zande languages", } diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index b3a4d6f9f..60acb59bd 100644 --- a/bookwyrm/connectors/self_connector.py +++ b/bookwyrm/connectors/self_connector.py @@ -1,4 +1,4 @@ -''' using a bookwyrm instance as a source of book data ''' +""" using a bookwyrm instance as a source of book data """ from functools import reduce import operator @@ -10,10 +10,11 @@ from .abstract_connector import AbstractConnector, SearchResult class Connector(AbstractConnector): - ''' instantiate a connector ''' + """ instantiate a connector """ + # pylint: disable=arguments-differ def search(self, query, min_confidence=0.1, raw=False): - ''' search your local database ''' + """ search your local database """ if not query: return [] # first, try searching unqiue identifiers @@ -34,19 +35,18 @@ class Connector(AbstractConnector): return search_results def isbn_search(self, query, raw=False): - ''' search your local database ''' + """ search your local database """ if not query: return [] - filters = [{f: query} for f in ['isbn_10', 'isbn_13']] + filters = [{f: query} for f in ["isbn_10", "isbn_13"]] results = models.Edition.objects.filter( reduce(operator.or_, (Q(**f) for f in filters)) ).distinct() # when there are multiple editions of the same work, pick the default. # it would be odd for this to happen. - results = results.filter(parent_work__default_edition__id=F('id')) \ - or results + results = results.filter(parent_work__default_edition__id=F("id")) or results search_results = [] for result in results: @@ -58,33 +58,30 @@ class Connector(AbstractConnector): break return search_results - def format_search_result(self, search_result): return SearchResult( title=search_result.title, key=search_result.remote_id, author=search_result.author_text, - year=search_result.published_date.year if \ - search_result.published_date else None, + year=search_result.published_date.year + if search_result.published_date + else None, connector=self, - confidence=search_result.rank if \ - hasattr(search_result, 'rank') else 1, + confidence=search_result.rank if hasattr(search_result, "rank") else 1, ) - def format_isbn_search_result(self, search_result): return SearchResult( title=search_result.title, key=search_result.remote_id, author=search_result.author_text, - year=search_result.published_date.year if \ - search_result.published_date else None, + year=search_result.published_date.year + if search_result.published_date + else None, connector=self, - confidence=search_result.rank if \ - hasattr(search_result, 'rank') else 1, + confidence=search_result.rank if hasattr(search_result, "rank") else 1, ) - def is_work_data(self, data): pass @@ -98,11 +95,11 @@ class Connector(AbstractConnector): return None def parse_isbn_search_data(self, data): - ''' it's already in the right format, don't even worry about it ''' + """ it's already in the right format, don't even worry about it """ return data def parse_search_data(self, data): - ''' it's already in the right format, don't even worry about it ''' + """ it's already in the right format, don't even worry about it """ return data def expand_book_data(self, book): @@ -110,44 +107,47 @@ class Connector(AbstractConnector): def search_identifiers(query): - ''' tries remote_id, isbn; defined as dedupe fields on the model ''' - filters = [{f.name: query} for f in models.Edition._meta.get_fields() \ - if hasattr(f, 'deduplication_field') and f.deduplication_field] + """ tries remote_id, isbn; defined as dedupe fields on the model """ + filters = [ + {f.name: query} + for f in models.Edition._meta.get_fields() + if hasattr(f, "deduplication_field") and f.deduplication_field + ] results = models.Edition.objects.filter( reduce(operator.or_, (Q(**f) for f in filters)) ).distinct() # when there are multiple editions of the same work, pick the default. # it would be odd for this to happen. - return results.filter(parent_work__default_edition__id=F('id')) \ - or results + return results.filter(parent_work__default_edition__id=F("id")) or results def search_title_author(query, min_confidence): - ''' searches for title and author ''' - vector = SearchVector('title', weight='A') +\ - SearchVector('subtitle', weight='B') +\ - SearchVector('authors__name', weight='C') +\ - SearchVector('series', weight='D') + """ searches for title and author """ + vector = ( + SearchVector("title", weight="A") + + SearchVector("subtitle", weight="B") + + SearchVector("authors__name", weight="C") + + SearchVector("series", weight="D") + ) - results = models.Edition.objects.annotate( - search=vector - ).annotate( - rank=SearchRank(vector, query) - ).filter( - rank__gt=min_confidence - ).order_by('-rank') + results = ( + models.Edition.objects.annotate(search=vector) + .annotate(rank=SearchRank(vector, query)) + .filter(rank__gt=min_confidence) + .order_by("-rank") + ) # when there are multiple editions of the same work, pick the closest - editions_of_work = results.values( - 'parent_work' - ).annotate( - Count('parent_work') - ).values_list('parent_work') + editions_of_work = ( + results.values("parent_work") + .annotate(Count("parent_work")) + .values_list("parent_work") + ) for work_id in set(editions_of_work): editions = results.filter(parent_work=work_id) - default = editions.filter(parent_work__default_edition=F('id')) + default = editions.filter(parent_work__default_edition=F("id")) default_rank = default.first().rank if default.exists() else 0 # if mutliple books have the top rank, pick the default edition if default_rank == editions.first().rank: diff --git a/bookwyrm/connectors/settings.py b/bookwyrm/connectors/settings.py index e04aedeff..f1674cf7c 100644 --- a/bookwyrm/connectors/settings.py +++ b/bookwyrm/connectors/settings.py @@ -1,3 +1,3 @@ -''' settings book data connectors ''' +""" settings book data connectors """ -CONNECTORS = ['openlibrary', 'self_connector', 'bookwyrm_connector'] +CONNECTORS = ["openlibrary", "self_connector", "bookwyrm_connector"] diff --git a/bookwyrm/context_processors.py b/bookwyrm/context_processors.py index a1471ac48..8f79a6529 100644 --- a/bookwyrm/context_processors.py +++ b/bookwyrm/context_processors.py @@ -1,8 +1,7 @@ -''' customize the info available in context for rendering templates ''' +""" customize the info available in context for rendering templates """ from bookwyrm import models -def site_settings(request):# pylint: disable=unused-argument - ''' include the custom info about the site ''' - return { - 'site': models.SiteSettings.objects.get() - } + +def site_settings(request): # pylint: disable=unused-argument + """ include the custom info about the site """ + return {"site": models.SiteSettings.objects.get()} diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py index 2319d4677..c7536876d 100644 --- a/bookwyrm/emailing.py +++ b/bookwyrm/emailing.py @@ -1,25 +1,27 @@ -''' send emails ''' +""" send emails """ from django.core.mail import send_mail from bookwyrm import models from bookwyrm.tasks import app + def password_reset_email(reset_code): - ''' generate a password reset email ''' + """ generate a password reset email """ site = models.SiteSettings.get() send_email.delay( reset_code.user.email, - 'Reset your password on %s' % site.name, - 'Your password reset link: %s' % reset_code.link + "Reset your password on %s" % site.name, + "Your password reset link: %s" % reset_code.link, ) + @app.task def send_email(recipient, subject, message): - ''' use a task to send the email ''' + """ use a task to send the email """ send_mail( subject, message, - None, # sender will be the config default + None, # sender will be the config default [recipient], - fail_silently=False + fail_silently=False, ) diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index b920fc9c0..3d8839efa 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -1,4 +1,4 @@ -''' using django model forms ''' +""" using django model forms """ import datetime from collections import defaultdict @@ -12,115 +12,125 @@ from bookwyrm import models class CustomForm(ModelForm): - ''' add css classes to the forms ''' + """ 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' + 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" super(CustomForm, self).__init__(*args, **kwargs) for visible in self.visible_fields(): - if hasattr(visible.field.widget, 'input_type'): + 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['cols'] = None - visible.field.widget.attrs['rows'] = None - visible.field.widget.attrs['class'] = css_classes[input_type] + input_type = "textarea" + visible.field.widget.attrs["cols"] = None + visible.field.widget.attrs["rows"] = None + 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'] + fields = ["localname", "password"] help_texts = {f: None for f in fields} widgets = { - 'password': PasswordInput(), + "password": PasswordInput(), } class RegisterForm(CustomForm): class Meta: model = models.User - fields = ['localname', 'email', 'password'] + fields = ["localname", "email", "password"] help_texts = {f: None for f in fields} - widgets = { - 'password': PasswordInput() - } + widgets = {"password": PasswordInput()} class RatingForm(CustomForm): class Meta: model = models.Review - fields = ['user', 'book', 'content', 'rating', 'privacy'] + fields = ["user", "book", "content", "rating", "privacy"] class ReviewForm(CustomForm): class Meta: model = models.Review fields = [ - 'user', 'book', - 'name', 'content', 'rating', - 'content_warning', 'sensitive', - 'privacy'] + "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'] + fields = ["user", "book", "content", "content_warning", "sensitive", "privacy"] class QuotationForm(CustomForm): class Meta: model = models.Quotation fields = [ - 'user', 'book', 'quote', 'content', - 'content_warning', 'sensitive', 'privacy'] + "user", + "book", + "quote", + "content", + "content_warning", + "sensitive", + "privacy", + ] class ReplyForm(CustomForm): class Meta: model = models.Status fields = [ - 'user', 'content', 'content_warning', 'sensitive', - 'reply_parent', 'privacy'] + "user", + "content", + "content_warning", + "sensitive", + "reply_parent", + "privacy", + ] + class StatusForm(CustomForm): class Meta: model = models.Status - fields = [ - 'user', 'content', 'content_warning', 'sensitive', 'privacy'] + fields = ["user", "content", "content_warning", "sensitive", "privacy"] class EditUserForm(CustomForm): class Meta: model = models.User - fields = [ - 'avatar', 'name', 'email', 'summary', 'manually_approves_followers' - ] + fields = ["avatar", "name", "email", "summary", "manually_approves_followers"] help_texts = {f: None for f in fields} class TagForm(CustomForm): class Meta: model = models.Tag - fields = ['name'] + fields = ["name"] help_texts = {f: None for f in fields} - labels = {'name': 'Add a tag'} + labels = {"name": "Add a tag"} class CoverForm(CustomForm): class Meta: model = models.Book - fields = ['cover'] + fields = ["cover"] help_texts = {f: None for f in fields} @@ -128,80 +138,87 @@ class EditionForm(CustomForm): class Meta: model = models.Edition exclude = [ - 'remote_id', - 'origin_id', - 'created_date', - 'updated_date', - 'edition_rank', - - 'authors',# TODO - 'parent_work', - 'shelves', - - 'subjects',# TODO - 'subject_places',# TODO - - 'connector', + "remote_id", + "origin_id", + "created_date", + "updated_date", + "edition_rank", + "authors", # TODO + "parent_work", + "shelves", + "subjects", # TODO + "subject_places", # TODO + "connector", ] + class AuthorForm(CustomForm): class Meta: model = models.Author exclude = [ - 'remote_id', - 'origin_id', - 'created_date', - 'updated_date', + "remote_id", + "origin_id", + "created_date", + "updated_date", ] 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 ''' + """ human-readable exiration time buckets """ selected_string = super().value_from_datadict(data, files, name) - if selected_string == 'day': + if selected_string == "day": interval = datetime.timedelta(days=1) - elif selected_string == 'week': + elif selected_string == "week": interval = datetime.timedelta(days=7) - elif selected_string == 'month': - interval = datetime.timedelta(days=31) # Close enough? - elif selected_string == 'forever': + 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 selected_string # "This will raise return timezone.now() + interval + class CreateInviteForm(CustomForm): class Meta: model = models.SiteInvite - exclude = ['code', 'user', 'times_used'] + exclude = ["code", "user", "times_used"] widgets = { - 'expiry': ExpiryWidget(choices=[ - ('day', _('One Day')), - ('week', _('One Week')), - ('month', _('One Month')), - ('forever', _('Does Not Expire'))]), - 'use_limit': widgets.Select( - choices=[(i, _("%(count)d uses" % {'count': i})) \ - for i in [1, 5, 10, 25, 50, 100]] - + [(None, _('Unlimited'))]) + "expiry": ExpiryWidget( + choices=[ + ("day", _("One Day")), + ("week", _("One Week")), + ("month", _("One Month")), + ("forever", _("Does Not Expire")), + ] + ), + "use_limit": widgets.Select( + choices=[ + (i, _("%(count)d uses" % {"count": i})) + for i in [1, 5, 10, 25, 50, 100] + ] + + [(None, _("Unlimited"))] + ), } + class ShelfForm(CustomForm): class Meta: model = models.Shelf - fields = ['user', 'name', 'privacy'] + fields = ["user", "name", "privacy"] class GoalForm(CustomForm): class Meta: model = models.AnnualGoal - fields = ['user', 'year', 'goal', 'privacy'] + fields = ["user", "year", "goal", "privacy"] class SiteForm(CustomForm): @@ -213,4 +230,4 @@ class SiteForm(CustomForm): class ListForm(CustomForm): class Meta: model = models.List - fields = ['user', 'name', 'description', 'curation', 'privacy'] + fields = ["user", "name", "description", "curation", "privacy"] diff --git a/bookwyrm/goodreads_import.py b/bookwyrm/goodreads_import.py index f5b84e179..fb4e8e0f1 100644 --- a/bookwyrm/goodreads_import.py +++ b/bookwyrm/goodreads_import.py @@ -1,13 +1,14 @@ -''' handle reading a csv from goodreads ''' +""" handle reading a csv from goodreads """ from bookwyrm.importer import Importer -# GoodReads is the default importer, thus Importer follows its structure. For a more complete example of overriding see librarything_import.py +# GoodReads is the default importer, thus Importer follows its structure. For a more complete example of overriding see librarything_import.py + class GoodreadsImporter(Importer): - service = 'GoodReads' + service = "GoodReads" def parse_fields(self, data): - data.update({'import_source': self.service }) + data.update({"import_source": self.service}) # add missing 'Date Started' field - data.update({'Date Started': None }) + data.update({"Date Started": None}) return data diff --git a/bookwyrm/importer.py b/bookwyrm/importer.py index a12884007..2fbb3430f 100644 --- a/bookwyrm/importer.py +++ b/bookwyrm/importer.py @@ -1,4 +1,4 @@ -''' handle reading a csv from an external service, defaults are from GoodReads ''' +""" handle reading a csv from an external service, defaults are from GoodReads """ import csv import logging @@ -8,49 +8,48 @@ from bookwyrm.tasks import app logger = logging.getLogger(__name__) + class Importer: - service = 'Unknown' - delimiter = ',' - encoding = 'UTF-8' - mandatory_fields = ['Title', 'Author'] + service = "Unknown" + delimiter = "," + encoding = "UTF-8" + mandatory_fields = ["Title", "Author"] def create_job(self, user, csv_file, include_reviews, privacy): - ''' check over a csv and creates a database entry for the job''' + """ check over a csv and creates a database entry for the job""" job = ImportJob.objects.create( - user=user, - include_reviews=include_reviews, - privacy=privacy + user=user, include_reviews=include_reviews, privacy=privacy ) - for index, entry in enumerate(list(csv.DictReader(csv_file, delimiter=self.delimiter ))): + for index, entry in enumerate( + list(csv.DictReader(csv_file, delimiter=self.delimiter)) + ): if not all(x in entry for x in self.mandatory_fields): - raise ValueError('Author and title must be in data.') + raise ValueError("Author and title must be in data.") entry = self.parse_fields(entry) self.save_item(job, index, entry) return job - def save_item(self, job, index, data): ImportItem(job=job, index=index, data=data).save() def parse_fields(self, entry): - entry.update({'import_source': self.service }) - return entry + entry.update({"import_source": self.service}) + return entry def create_retry_job(self, user, original_job, items): - ''' retry items that didn't import ''' + """ retry items that didn't import """ job = ImportJob.objects.create( user=user, include_reviews=original_job.include_reviews, privacy=original_job.privacy, - retry=True + retry=True, ) for item in items: self.save_item(job, item.index, item.data) return job - def start_import(self, job): - ''' initalizes a csv import job ''' + """ initalizes a csv import job """ result = import_data.delay(self.service, job.id) job.task_id = result.id job.save() @@ -58,15 +57,15 @@ class Importer: @app.task def import_data(source, job_id): - ''' does the actual lookup work in a celery task ''' + """ does the actual lookup work in a celery task """ job = ImportJob.objects.get(id=job_id) try: for item in job.items.all(): try: item.resolve() - except Exception as e:# pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except logger.exception(e) - item.fail_reason = 'Error loading book' + item.fail_reason = "Error loading book" item.save() continue @@ -74,10 +73,11 @@ def import_data(source, job_id): item.save() # shelves book and handles reviews - handle_imported_book(source, - job.user, item, job.include_reviews, job.privacy) + handle_imported_book( + source, job.user, item, job.include_reviews, job.privacy + ) else: - item.fail_reason = 'Could not find a match for book' + item.fail_reason = "Could not find a match for book" item.save() finally: job.complete = True @@ -85,41 +85,41 @@ def import_data(source, job_id): def handle_imported_book(source, user, item, include_reviews, privacy): - ''' process a csv and then post about it ''' + """ process a csv and then post about it """ if isinstance(item.book, models.Work): item.book = item.book.default_edition if not item.book: return - existing_shelf = models.ShelfBook.objects.filter( - book=item.book, user=user).exists() + existing_shelf = models.ShelfBook.objects.filter(book=item.book, user=user).exists() # shelve the book if it hasn't been shelved already if item.shelf and not existing_shelf: - desired_shelf = models.Shelf.objects.get( - identifier=item.shelf, - user=user - ) - models.ShelfBook.objects.create( - book=item.book, shelf=desired_shelf, user=user) + desired_shelf = models.Shelf.objects.get(identifier=item.shelf, user=user) + models.ShelfBook.objects.create(book=item.book, shelf=desired_shelf, user=user) for read in item.reads: # check for an existing readthrough with the same dates if models.ReadThrough.objects.filter( - user=user, book=item.book, - start_date=read.start_date, - finish_date=read.finish_date - ).exists(): + user=user, + book=item.book, + start_date=read.start_date, + finish_date=read.finish_date, + ).exists(): continue read.book = item.book read.user = user read.save() if include_reviews and (item.rating or item.review): - review_title = 'Review of {!r} on {!r}'.format( - item.book.title, - source, - ) if item.review else '' + review_title = ( + "Review of {!r} on {!r}".format( + item.book.title, + source, + ) + if item.review + else "" + ) # we don't know the publication date of the review, # but "now" is a bad guess diff --git a/bookwyrm/librarything_import.py b/bookwyrm/librarything_import.py index 0584daad9..b3dd9d56b 100644 --- a/bookwyrm/librarything_import.py +++ b/bookwyrm/librarything_import.py @@ -1,4 +1,4 @@ -''' handle reading a csv from librarything ''' +""" handle reading a csv from librarything """ import csv import re import math @@ -9,34 +9,34 @@ from bookwyrm.importer import Importer class LibrarythingImporter(Importer): - service = 'LibraryThing' - delimiter = '\t' - encoding = 'ISO-8859-1' + service = "LibraryThing" + delimiter = "\t" + encoding = "ISO-8859-1" # mandatory_fields : fields matching the book title and author - mandatory_fields = ['Title', 'Primary Author'] + mandatory_fields = ["Title", "Primary Author"] def parse_fields(self, initial): data = {} - data['import_source'] = self.service - data['Book Id'] = initial['Book Id'] - data['Title'] = initial['Title'] - data['Author'] = initial['Primary Author'] - data['ISBN13'] = initial['ISBN'] - data['My Review'] = initial['Review'] - if initial['Rating']: - data['My Rating'] = math.ceil(float(initial['Rating'])) + data["import_source"] = self.service + data["Book Id"] = initial["Book Id"] + data["Title"] = initial["Title"] + data["Author"] = initial["Primary Author"] + data["ISBN13"] = initial["ISBN"] + data["My Review"] = initial["Review"] + if initial["Rating"]: + data["My Rating"] = math.ceil(float(initial["Rating"])) else: - data['My Rating'] = '' - data['Date Added'] = re.sub('\[|\]', '', initial['Entry Date']) - data['Date Started'] = re.sub('\[|\]', '', initial['Date Started']) - data['Date Read'] = re.sub('\[|\]', '', initial['Date Read']) + data["My Rating"] = "" + data["Date Added"] = re.sub("\[|\]", "", initial["Entry Date"]) + data["Date Started"] = re.sub("\[|\]", "", initial["Date Started"]) + data["Date Read"] = re.sub("\[|\]", "", initial["Date Read"]) - data['Exclusive Shelf'] = None - if data['Date Read']: - data['Exclusive Shelf'] = "read" - elif data['Date Started']: - data['Exclusive Shelf'] = "reading" + data["Exclusive Shelf"] = None + if data["Date Read"]: + data["Exclusive Shelf"] = "read" + elif data["Date Started"]: + data["Exclusive Shelf"] = "reading" else: - data['Exclusive Shelf'] = "to-read" + data["Exclusive Shelf"] = "to-read" return data diff --git a/bookwyrm/management/commands/deduplicate_book_data.py b/bookwyrm/management/commands/deduplicate_book_data.py index 044b2a986..edd91a717 100644 --- a/bookwyrm/management/commands/deduplicate_book_data.py +++ b/bookwyrm/management/commands/deduplicate_book_data.py @@ -1,26 +1,20 @@ -''' PROCEED WITH CAUTION: uses deduplication fields to permanently -merge book data objects ''' +""" PROCEED WITH CAUTION: uses deduplication fields to permanently +merge book data objects """ from django.core.management.base import BaseCommand from django.db.models import Count from bookwyrm import models def update_related(canonical, obj): - ''' update all the models with fk to the object being removed ''' + """ update all the models with fk to the object being removed """ # move related models to canonical related_models = [ - (r.remote_field.name, r.related_model) for r in \ - canonical._meta.related_objects] + (r.remote_field.name, r.related_model) for r in canonical._meta.related_objects + ] for (related_field, related_model) in related_models: - related_objs = related_model.objects.filter( - **{related_field: obj}) + related_objs = related_model.objects.filter(**{related_field: obj}) for related_obj in related_objs: - print( - 'replacing in', - related_model.__name__, - related_field, - related_obj.id - ) + print("replacing in", related_model.__name__, related_field, related_obj.id) try: setattr(related_obj, related_field, canonical) related_obj.save() @@ -30,40 +24,41 @@ def update_related(canonical, obj): def copy_data(canonical, obj): - ''' try to get the most data possible ''' + """ try to get the most data possible """ for data_field in obj._meta.get_fields(): - if not hasattr(data_field, 'activitypub_field'): + if not hasattr(data_field, "activitypub_field"): continue data_value = getattr(obj, data_field.name) if not data_value: continue if not getattr(canonical, data_field.name): - print('setting data field', data_field.name, data_value) + print("setting data field", data_field.name, data_value) setattr(canonical, data_field.name, data_value) canonical.save() def dedupe_model(model): - ''' combine duplicate editions and update related models ''' + """ combine duplicate editions and update related models """ fields = model._meta.get_fields() - dedupe_fields = [f for f in fields if \ - hasattr(f, 'deduplication_field') and f.deduplication_field] + dedupe_fields = [ + f for f in fields if hasattr(f, "deduplication_field") and f.deduplication_field + ] for field in dedupe_fields: - dupes = model.objects.values(field.name).annotate( - Count(field.name) - ).filter(**{'%s__count__gt' % field.name: 1}) + dupes = ( + model.objects.values(field.name) + .annotate(Count(field.name)) + .filter(**{"%s__count__gt" % field.name: 1}) + ) for dupe in dupes: value = dupe[field.name] - if not value or value == '': + if not value or value == "": continue - print('----------') + print("----------") print(dupe) - objs = model.objects.filter( - **{field.name: value} - ).order_by('id') + objs = model.objects.filter(**{field.name: value}).order_by("id") canonical = objs.first() - print('keeping', canonical.remote_id) + print("keeping", canonical.remote_id) for obj in objs[1:]: print(obj.remote_id) copy_data(canonical, obj) @@ -73,11 +68,12 @@ def dedupe_model(model): class Command(BaseCommand): - ''' dedplucate allllll the book data models ''' - help = 'merges duplicate book data' + """ dedplucate allllll the book data models """ + + help = "merges duplicate book data" # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): - ''' run deudplications ''' + """ run deudplications """ dedupe_model(models.Edition) dedupe_model(models.Work) dedupe_model(models.Author) diff --git a/bookwyrm/management/commands/initdb.py b/bookwyrm/management/commands/initdb.py index 5759abfcc..6b3f3762e 100644 --- a/bookwyrm/management/commands/initdb.py +++ b/bookwyrm/management/commands/initdb.py @@ -5,51 +5,63 @@ from django.contrib.contenttypes.models import ContentType from bookwyrm.models import Connector, SiteSettings, User from bookwyrm.settings import DOMAIN + def init_groups(): - groups = ['admin', 'moderator', 'editor'] + groups = ["admin", "moderator", "editor"] for group in groups: Group.objects.create(name=group) + def init_permissions(): - permissions = [{ - 'codename': 'edit_instance_settings', - 'name': 'change the instance info', - 'groups': ['admin',] - }, { - 'codename': 'set_user_group', - 'name': 'change what group a user is in', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'control_federation', - 'name': 'control who to federate with', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'create_invites', - 'name': 'issue invitations to join', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'moderate_user', - 'name': 'deactivate or silence a user', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'moderate_post', - 'name': 'delete other users\' posts', - 'groups': ['admin', 'moderator'] - }, { - 'codename': 'edit_book', - 'name': 'edit book info', - 'groups': ['admin', 'moderator', 'editor'] - }] + permissions = [ + { + "codename": "edit_instance_settings", + "name": "change the instance info", + "groups": [ + "admin", + ], + }, + { + "codename": "set_user_group", + "name": "change what group a user is in", + "groups": ["admin", "moderator"], + }, + { + "codename": "control_federation", + "name": "control who to federate with", + "groups": ["admin", "moderator"], + }, + { + "codename": "create_invites", + "name": "issue invitations to join", + "groups": ["admin", "moderator"], + }, + { + "codename": "moderate_user", + "name": "deactivate or silence a user", + "groups": ["admin", "moderator"], + }, + { + "codename": "moderate_post", + "name": "delete other users' posts", + "groups": ["admin", "moderator"], + }, + { + "codename": "edit_book", + "name": "edit book info", + "groups": ["admin", "moderator", "editor"], + }, + ] content_type = ContentType.objects.get_for_model(User) for permission in permissions: permission_obj = Permission.objects.create( - codename=permission['codename'], - name=permission['name'], + codename=permission["codename"], + name=permission["name"], content_type=content_type, ) # add the permission to the appropriate groups - for group_name in permission['groups']: + for group_name in permission["groups"]: Group.objects.get(name=group_name).permissions.add(permission_obj) # while the groups and permissions shouldn't be changed because the code @@ -59,46 +71,48 @@ def init_permissions(): def init_connectors(): Connector.objects.create( identifier=DOMAIN, - name='Local', + name="Local", local=True, - connector_file='self_connector', - base_url='https://%s' % DOMAIN, - books_url='https://%s/book' % DOMAIN, - covers_url='https://%s/images/covers' % DOMAIN, - search_url='https://%s/search?q=' % DOMAIN, - isbn_search_url='https://%s/isbn/' % DOMAIN, + connector_file="self_connector", + base_url="https://%s" % DOMAIN, + books_url="https://%s/book" % DOMAIN, + covers_url="https://%s/images/covers" % DOMAIN, + search_url="https://%s/search?q=" % DOMAIN, + isbn_search_url="https://%s/isbn/" % DOMAIN, priority=1, ) Connector.objects.create( - identifier='bookwyrm.social', - name='BookWyrm dot Social', - connector_file='bookwyrm_connector', - base_url='https://bookwyrm.social', - books_url='https://bookwyrm.social/book', - covers_url='https://bookwyrm.social/images/covers', - search_url='https://bookwyrm.social/search?q=', - isbn_search_url='https://bookwyrm.social/isbn/', + identifier="bookwyrm.social", + name="BookWyrm dot Social", + connector_file="bookwyrm_connector", + base_url="https://bookwyrm.social", + books_url="https://bookwyrm.social/book", + covers_url="https://bookwyrm.social/images/covers", + search_url="https://bookwyrm.social/search?q=", + isbn_search_url="https://bookwyrm.social/isbn/", priority=2, ) Connector.objects.create( - identifier='openlibrary.org', - name='OpenLibrary', - connector_file='openlibrary', - base_url='https://openlibrary.org', - books_url='https://openlibrary.org', - 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:', + identifier="openlibrary.org", + name="OpenLibrary", + connector_file="openlibrary", + base_url="https://openlibrary.org", + books_url="https://openlibrary.org", + 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, ) + def init_settings(): SiteSettings.objects.create() + class Command(BaseCommand): - help = 'Initializes the database with starter data' + help = "Initializes the database with starter data" def handle(self, *args, **options): init_groups() diff --git a/bookwyrm/management/commands/remove_editions.py b/bookwyrm/management/commands/remove_editions.py index c5153f44b..6829c6d10 100644 --- a/bookwyrm/management/commands/remove_editions.py +++ b/bookwyrm/management/commands/remove_editions.py @@ -1,34 +1,42 @@ -''' PROCEED WITH CAUTION: this permanently deletes book data ''' +""" PROCEED WITH CAUTION: this permanently deletes book data """ from django.core.management.base import BaseCommand from django.db.models import Count, Q from bookwyrm import models def remove_editions(): - ''' combine duplicate editions and update related models ''' + """ combine duplicate editions and update related models """ # not in use - filters = {'%s__isnull' % r.name: True \ - for r in models.Edition._meta.related_objects} + filters = { + "%s__isnull" % r.name: True for r in models.Edition._meta.related_objects + } # no cover, no identifying fields - filters['cover'] = '' - null_fields = {'%s__isnull' % f: True for f in \ - ['isbn_10', 'isbn_13', 'oclc_number']} + filters["cover"] = "" + null_fields = { + "%s__isnull" % f: True for f in ["isbn_10", "isbn_13", "oclc_number"] + } - editions = models.Edition.objects.filter( - Q(languages=[]) | Q(languages__contains=['English']), - **filters, **null_fields - ).annotate(Count('parent_work__editions')).filter( - # mustn't be the only edition for the work - parent_work__editions__count__gt=1 + editions = ( + models.Edition.objects.filter( + Q(languages=[]) | Q(languages__contains=["English"]), + **filters, + **null_fields + ) + .annotate(Count("parent_work__editions")) + .filter( + # mustn't be the only edition for the work + parent_work__editions__count__gt=1 + ) ) print(editions.count()) editions.delete() class Command(BaseCommand): - ''' dedplucate allllll the book data models ''' - help = 'merges duplicate book data' + """ dedplucate allllll the book data models """ + + help = "merges duplicate book data" # pylint: disable=no-self-use,unused-argument def handle(self, *args, **options): - ''' run deudplications ''' + """ run deudplications """ remove_editions() diff --git a/bookwyrm/migrations/0001_initial.py b/bookwyrm/migrations/0001_initial.py index 347057e1d..a405b956f 100644 --- a/bookwyrm/migrations/0001_initial.py +++ b/bookwyrm/migrations/0001_initial.py @@ -15,199 +15,448 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0011_update_proxy_permissions'), + ("auth", "0011_update_proxy_permissions"), ] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('private_key', models.TextField(blank=True, null=True)), - ('public_key', models.TextField(blank=True, null=True)), - ('actor', models.CharField(max_length=255, unique=True)), - ('inbox', models.CharField(max_length=255, unique=True)), - ('shared_inbox', models.CharField(blank=True, max_length=255, null=True)), - ('outbox', models.CharField(max_length=255, unique=True)), - ('summary', models.TextField(blank=True, null=True)), - ('local', models.BooleanField(default=True)), - ('fedireads_user', models.BooleanField(default=True)), - ('localname', models.CharField(max_length=255, null=True, unique=True)), - ('name', models.CharField(blank=True, max_length=100, null=True)), - ('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=30, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ("private_key", models.TextField(blank=True, null=True)), + ("public_key", models.TextField(blank=True, null=True)), + ("actor", models.CharField(max_length=255, unique=True)), + ("inbox", models.CharField(max_length=255, unique=True)), + ( + "shared_inbox", + models.CharField(blank=True, max_length=255, null=True), + ), + ("outbox", models.CharField(max_length=255, unique=True)), + ("summary", models.TextField(blank=True, null=True)), + ("local", models.BooleanField(default=True)), + ("fedireads_user", models.BooleanField(default=True)), + ("localname", models.CharField(max_length=255, null=True, unique=True)), + ("name", models.CharField(blank=True, max_length=100, null=True)), + ( + "avatar", + models.ImageField(blank=True, null=True, upload_to="avatars/"), + ), ], options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, }, managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ("objects", django.contrib.auth.models.UserManager()), ], ), migrations.CreateModel( - name='Author', + name="Author", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('openlibrary_key', models.CharField(max_length=255)), - ('data', JSONField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("openlibrary_key", models.CharField(max_length=255)), + ("data", JSONField()), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='Book', + name="Book", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('openlibrary_key', models.CharField(max_length=255, unique=True)), - ('data', JSONField()), - ('cover', models.ImageField(blank=True, null=True, upload_to='covers/')), - ('added_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), - ('authors', models.ManyToManyField(to='bookwyrm.Author')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("openlibrary_key", models.CharField(max_length=255, unique=True)), + ("data", JSONField()), + ( + "cover", + models.ImageField(blank=True, null=True, upload_to="covers/"), + ), + ( + "added_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ("authors", models.ManyToManyField(to="bookwyrm.Author")), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='FederatedServer', + name="FederatedServer", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('server_name', models.CharField(max_length=255, unique=True)), - ('status', models.CharField(default='federated', max_length=255)), - ('application_type', models.CharField(max_length=255, null=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("server_name", models.CharField(max_length=255, unique=True)), + ("status", models.CharField(default="federated", max_length=255)), + ("application_type", models.CharField(max_length=255, null=True)), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='Shelf', + name="Shelf", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('name', models.CharField(max_length=100)), - ('identifier', models.CharField(max_length=100)), - ('editable', models.BooleanField(default=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("name", models.CharField(max_length=100)), + ("identifier", models.CharField(max_length=100)), + ("editable", models.BooleanField(default=True)), ], ), migrations.CreateModel( - name='Status', + name="Status", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('status_type', models.CharField(default='Note', max_length=255)), - ('activity_type', models.CharField(default='Note', max_length=255)), - ('local', models.BooleanField(default=True)), - ('privacy', models.CharField(default='public', max_length=255)), - ('sensitive', models.BooleanField(default=False)), - ('mention_books', models.ManyToManyField(related_name='mention_book', to='bookwyrm.Book')), - ('mention_users', models.ManyToManyField(related_name='mention_user', to=settings.AUTH_USER_MODEL)), - ('reply_parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Status')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("status_type", models.CharField(default="Note", max_length=255)), + ("activity_type", models.CharField(default="Note", max_length=255)), + ("local", models.BooleanField(default=True)), + ("privacy", models.CharField(default="public", max_length=255)), + ("sensitive", models.BooleanField(default=False)), + ( + "mention_books", + models.ManyToManyField( + related_name="mention_book", to="bookwyrm.Book" + ), + ), + ( + "mention_users", + models.ManyToManyField( + related_name="mention_user", to=settings.AUTH_USER_MODEL + ), + ), + ( + "reply_parent", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Status", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='UserRelationship', + name="UserRelationship", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('status', models.CharField(default='follows', max_length=100, null=True)), - ('relationship_id', models.CharField(max_length=100)), - ('user_object', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_object', to=settings.AUTH_USER_MODEL)), - ('user_subject', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_subject', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ( + "status", + models.CharField(default="follows", max_length=100, null=True), + ), + ("relationship_id", models.CharField(max_length=100)), + ( + "user_object", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="user_object", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user_subject", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="user_subject", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='ShelfBook', + name="ShelfBook", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('added_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Book')), - ('shelf', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Shelf')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ( + "added_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Book" + ), + ), + ( + "shelf", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Shelf" + ), + ), ], options={ - 'unique_together': {('book', 'shelf')}, + "unique_together": {("book", "shelf")}, }, ), migrations.AddField( - model_name='shelf', - name='books', - field=models.ManyToManyField(through='bookwyrm.ShelfBook', to='bookwyrm.Book'), + model_name="shelf", + name="books", + field=models.ManyToManyField( + through="bookwyrm.ShelfBook", to="bookwyrm.Book" + ), ), migrations.AddField( - model_name='shelf', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="shelf", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='book', - name='shelves', - field=models.ManyToManyField(through='bookwyrm.ShelfBook', to='bookwyrm.Shelf'), + model_name="book", + name="shelves", + field=models.ManyToManyField( + through="bookwyrm.ShelfBook", to="bookwyrm.Shelf" + ), ), migrations.AddField( - model_name='user', - name='federated_server', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.FederatedServer'), + model_name="user", + name="federated_server", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.FederatedServer", + ), ), migrations.AddField( - model_name='user', - name='followers', - field=models.ManyToManyField(through='bookwyrm.UserRelationship', to=settings.AUTH_USER_MODEL), + model_name="user", + name="followers", + field=models.ManyToManyField( + through="bookwyrm.UserRelationship", to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='user', - name='groups', - field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), + model_name="user", + name="groups", + field=models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), ), migrations.AddField( - model_name='user', - name='user_permissions', - field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), + model_name="user", + name="user_permissions", + field=models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), ), migrations.AlterUniqueTogether( - name='shelf', - unique_together={('user', 'identifier')}, + name="shelf", + unique_together={("user", "identifier")}, ), migrations.CreateModel( - name='Review', + name="Review", fields=[ - ('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Status')), - ('name', models.CharField(max_length=255)), - ('rating', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(5)])), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Book')), + ( + "status_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Status", + ), + ), + ("name", models.CharField(max_length=255)), + ( + "rating", + models.IntegerField( + default=0, + validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(5), + ], + ), + ), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Book" + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.status',), + bases=("bookwyrm.status",), ), ] diff --git a/bookwyrm/migrations/0002_auto_20200219_0816.py b/bookwyrm/migrations/0002_auto_20200219_0816.py index 9cb5b726d..07daad935 100644 --- a/bookwyrm/migrations/0002_auto_20200219_0816.py +++ b/bookwyrm/migrations/0002_auto_20200219_0816.py @@ -8,31 +8,59 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0001_initial'), + ("bookwyrm", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Favorite', + name="Favorite", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField(blank=True, null=True)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('status', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Status')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField(blank=True, null=True)), + ("created_date", models.DateTimeField(auto_now_add=True)), + ( + "status", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Status", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'unique_together': {('user', 'status')}, + "unique_together": {("user", "status")}, }, ), migrations.AddField( - model_name='status', - name='favorites', - field=models.ManyToManyField(related_name='user_favorites', through='bookwyrm.Favorite', to=settings.AUTH_USER_MODEL), + model_name="status", + name="favorites", + field=models.ManyToManyField( + related_name="user_favorites", + through="bookwyrm.Favorite", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='user', - name='favorites', - field=models.ManyToManyField(related_name='favorite_statuses', through='bookwyrm.Favorite', to='bookwyrm.Status'), + model_name="user", + name="favorites", + field=models.ManyToManyField( + related_name="favorite_statuses", + through="bookwyrm.Favorite", + to="bookwyrm.Status", + ), ), ] diff --git a/bookwyrm/migrations/0003_auto_20200221_0131.py b/bookwyrm/migrations/0003_auto_20200221_0131.py index e53f042b4..e3e164140 100644 --- a/bookwyrm/migrations/0003_auto_20200221_0131.py +++ b/bookwyrm/migrations/0003_auto_20200221_0131.py @@ -7,87 +7,89 @@ import django.utils.timezone class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0002_auto_20200219_0816'), + ("bookwyrm", "0002_auto_20200219_0816"), ] operations = [ migrations.RemoveField( - model_name='author', - name='content', + model_name="author", + name="content", ), migrations.RemoveField( - model_name='book', - name='content', + model_name="book", + name="content", ), migrations.RemoveField( - model_name='favorite', - name='content', + model_name="favorite", + name="content", ), migrations.RemoveField( - model_name='federatedserver', - name='content', + model_name="federatedserver", + name="content", ), migrations.RemoveField( - model_name='shelf', - name='content', + model_name="shelf", + name="content", ), migrations.RemoveField( - model_name='shelfbook', - name='content', + model_name="shelfbook", + name="content", ), migrations.RemoveField( - model_name='userrelationship', - name='content', + model_name="userrelationship", + name="content", ), migrations.AddField( - model_name='author', - name='updated_date', + model_name="author", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='book', - name='updated_date', + model_name="book", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='favorite', - name='updated_date', + model_name="favorite", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='federatedserver', - name='updated_date', + model_name="federatedserver", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='shelf', - name='updated_date', + model_name="shelf", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='shelfbook', - name='updated_date', + model_name="shelfbook", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='status', - name='updated_date', + model_name="status", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='user', - name='created_date', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + model_name="user", + name="created_date", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), preserve_default=False, ), migrations.AddField( - model_name='user', - name='updated_date', + model_name="user", + name="updated_date", field=models.DateTimeField(auto_now=True), ), migrations.AddField( - model_name='userrelationship', - name='updated_date', + model_name="userrelationship", + name="updated_date", field=models.DateTimeField(auto_now=True), ), ] diff --git a/bookwyrm/migrations/0004_tag.py b/bookwyrm/migrations/0004_tag.py index 209550008..b6210070c 100644 --- a/bookwyrm/migrations/0004_tag.py +++ b/bookwyrm/migrations/0004_tag.py @@ -8,22 +8,41 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0003_auto_20200221_0131'), + ("bookwyrm", "0003_auto_20200221_0131"), ] operations = [ migrations.CreateModel( - name='Tag', + name="Tag", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('name', models.CharField(max_length=140)), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Book')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("name", models.CharField(max_length=140)), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Book" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'unique_together': {('user', 'book', 'name')}, + "unique_together": {("user", "book", "name")}, }, ), ] diff --git a/bookwyrm/migrations/0005_auto_20200221_1645.py b/bookwyrm/migrations/0005_auto_20200221_1645.py index dbd87e924..449ce041e 100644 --- a/bookwyrm/migrations/0005_auto_20200221_1645.py +++ b/bookwyrm/migrations/0005_auto_20200221_1645.py @@ -5,27 +5,27 @@ from django.db import migrations, models def populate_identifiers(app_registry, schema_editor): db_alias = schema_editor.connection.alias - tags = app_registry.get_model('bookwyrm', 'Tag') + tags = app_registry.get_model("bookwyrm", "Tag") for tag in tags.objects.using(db_alias): - tag.identifier = re.sub(r'\W+', '-', tag.name).lower() + tag.identifier = re.sub(r"\W+", "-", tag.name).lower() tag.save() class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0004_tag'), + ("bookwyrm", "0004_tag"), ] operations = [ migrations.AddField( - model_name='tag', - name='identifier', + model_name="tag", + name="identifier", field=models.CharField(max_length=100, null=True), ), migrations.AlterField( - model_name='tag', - name='name', + model_name="tag", + name="name", field=models.CharField(max_length=100), ), migrations.RunPython(populate_identifiers), diff --git a/bookwyrm/migrations/0006_auto_20200221_1702_squashed_0064_merge_20201101_1913.py b/bookwyrm/migrations/0006_auto_20200221_1702_squashed_0064_merge_20201101_1913.py index 6a149ab59..c06fa40a0 100644 --- a/bookwyrm/migrations/0006_auto_20200221_1702_squashed_0064_merge_20201101_1913.py +++ b/bookwyrm/migrations/0006_auto_20200221_1702_squashed_0064_merge_20201101_1913.py @@ -16,1056 +16,1647 @@ import uuid class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0005_auto_20200221_1645'), + ("bookwyrm", "0005_auto_20200221_1645"), ] operations = [ migrations.AlterField( - model_name='tag', - name='identifier', + model_name="tag", + name="identifier", field=models.CharField(max_length=100), ), migrations.AddConstraint( - model_name='userrelationship', - constraint=models.UniqueConstraint(fields=('user_subject', 'user_object'), name='followers_unique'), + model_name="userrelationship", + constraint=models.UniqueConstraint( + fields=("user_subject", "user_object"), name="followers_unique" + ), ), migrations.RemoveField( - model_name='user', - name='followers', + model_name="user", + name="followers", ), migrations.AddField( - model_name='status', - name='published_date', + model_name="status", + name="published_date", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.CreateModel( - name='Edition', + name="Edition", fields=[ - ('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Book')), - ('isbn', models.CharField(max_length=255, null=True, unique=True)), - ('oclc_number', models.CharField(max_length=255, null=True, unique=True)), - ('pages', models.IntegerField(null=True)), + ( + "book_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Book", + ), + ), + ("isbn", models.CharField(max_length=255, null=True, unique=True)), + ( + "oclc_number", + models.CharField(max_length=255, null=True, unique=True), + ), + ("pages", models.IntegerField(null=True)), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.book',), + bases=("bookwyrm.book",), ), migrations.CreateModel( - name='Work', + name="Work", fields=[ - ('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Book')), - ('lccn', models.CharField(max_length=255, null=True, unique=True)), + ( + "book_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Book", + ), + ), + ("lccn", models.CharField(max_length=255, null=True, unique=True)), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.book',), + bases=("bookwyrm.book",), ), migrations.RemoveField( - model_name='author', - name='data', + model_name="author", + name="data", ), migrations.RemoveField( - model_name='book', - name='added_by', + model_name="book", + name="added_by", ), migrations.RemoveField( - model_name='book', - name='data', + model_name="book", + name="data", ), migrations.AddField( - model_name='author', - name='bio', + model_name="author", + name="bio", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='author', - name='born', + model_name="author", + name="born", field=models.DateTimeField(null=True), ), migrations.AddField( - model_name='author', - name='died', + model_name="author", + name="died", field=models.DateTimeField(null=True), ), migrations.AddField( - model_name='author', - name='first_name', + model_name="author", + name="first_name", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='author', - name='last_name', + model_name="author", + name="last_name", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='author', - name='name', - field=models.CharField(default='Unknown', max_length=255), + model_name="author", + name="name", + field=models.CharField(default="Unknown", max_length=255), preserve_default=False, ), migrations.AddField( - model_name='author', - name='wikipedia_link', + model_name="author", + name="wikipedia_link", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='book', - name='description', + model_name="book", + name="description", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='book', - name='first_published_date', + model_name="book", + name="first_published_date", field=models.DateTimeField(null=True), ), migrations.AddField( - model_name='book', - name='language', + model_name="book", + name="language", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='book', - name='last_sync_date', + model_name="book", + name="last_sync_date", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.AddField( - model_name='book', - name='librarything_key', + model_name="book", + name="librarything_key", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.AddField( - model_name='book', - name='local_edits', + model_name="book", + name="local_edits", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='book', - name='local_key', + model_name="book", + name="local_key", field=models.CharField(default=uuid.uuid4, max_length=255, unique=True), ), migrations.AddField( - model_name='book', - name='misc_identifiers', + model_name="book", + name="misc_identifiers", field=JSONField(null=True), ), migrations.AddField( - model_name='book', - name='origin', + model_name="book", + name="origin", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.AddField( - model_name='book', - name='published_date', + model_name="book", + name="published_date", field=models.DateTimeField(null=True), ), migrations.AddField( - model_name='book', - name='series', + model_name="book", + name="series", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='book', - name='series_number', + model_name="book", + name="series_number", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='book', - name='sort_title', + model_name="book", + name="sort_title", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='book', - name='subtitle', + model_name="book", + name="subtitle", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='book', - name='sync', + model_name="book", + name="sync", field=models.BooleanField(default=True), ), migrations.AddField( - model_name='book', - name='title', - field=models.CharField(default='Unknown', max_length=255), + model_name="book", + name="title", + field=models.CharField(default="Unknown", max_length=255), preserve_default=False, ), migrations.AlterField( - model_name='author', - name='openlibrary_key', + model_name="author", + name="openlibrary_key", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.AlterField( - model_name='book', - name='openlibrary_key', + model_name="book", + name="openlibrary_key", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.AddField( - model_name='book', - name='parent_work', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Work'), + model_name="book", + name="parent_work", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Work", + ), ), migrations.CreateModel( - name='Notification', + name="Notification", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('read', models.BooleanField(default=False)), - ('notification_type', models.CharField(max_length=255)), - ('related_book', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Book')), - ('related_status', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Status')), - ('related_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='related_user', to=settings.AUTH_USER_MODEL)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("read", models.BooleanField(default=False)), + ("notification_type", models.CharField(max_length=255)), + ( + "related_book", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Book", + ), + ), + ( + "related_status", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Status", + ), + ), + ( + "related_user", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="related_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.AddField( - model_name='author', - name='aliases', - field=bookwyrm.models.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="author", + name="aliases", + field=bookwyrm.models.fields.ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AddField( - model_name='user', - name='manually_approves_followers', + model_name="user", + name="manually_approves_followers", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='status', - name='remote_id', + model_name="status", + name="remote_id", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.CreateModel( - name='UserBlocks', + name="UserBlocks", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('relationship_id', models.CharField(max_length=100)), - ('user_object', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userblocks_user_object', to=settings.AUTH_USER_MODEL)), - ('user_subject', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userblocks_user_subject', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("relationship_id", models.CharField(max_length=100)), + ( + "user_object", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userblocks_user_object", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user_subject", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userblocks_user_subject", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='UserFollowRequest', + name="UserFollowRequest", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('relationship_id', models.CharField(max_length=100)), - ('user_object', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userfollowrequest_user_object', to=settings.AUTH_USER_MODEL)), - ('user_subject', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userfollowrequest_user_subject', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("relationship_id", models.CharField(max_length=100)), + ( + "user_object", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollowrequest_user_object", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user_subject", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollowrequest_user_subject", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='UserFollows', + name="UserFollows", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('relationship_id', models.CharField(max_length=100)), - ('user_object', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userfollows_user_object', to=settings.AUTH_USER_MODEL)), - ('user_subject', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userfollows_user_subject', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("relationship_id", models.CharField(max_length=100)), + ( + "user_object", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollows_user_object", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user_subject", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollows_user_subject", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.DeleteModel( - name='UserRelationship', + name="UserRelationship", ), migrations.AddField( - model_name='user', - name='blocks', - field=models.ManyToManyField(related_name='blocked_by', through='bookwyrm.UserBlocks', to=settings.AUTH_USER_MODEL), + model_name="user", + name="blocks", + field=models.ManyToManyField( + related_name="blocked_by", + through="bookwyrm.UserBlocks", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='user', - name='follow_requests', - field=models.ManyToManyField(related_name='follower_requests', through='bookwyrm.UserFollowRequest', to=settings.AUTH_USER_MODEL), + model_name="user", + name="follow_requests", + field=models.ManyToManyField( + related_name="follower_requests", + through="bookwyrm.UserFollowRequest", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='user', - name='following', - field=models.ManyToManyField(related_name='followers', through='bookwyrm.UserFollows', to=settings.AUTH_USER_MODEL), + model_name="user", + name="following", + field=models.ManyToManyField( + related_name="followers", + through="bookwyrm.UserFollows", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddConstraint( - model_name='userfollows', - constraint=models.UniqueConstraint(fields=('user_subject', 'user_object'), name='userfollows_unique'), + model_name="userfollows", + constraint=models.UniqueConstraint( + fields=("user_subject", "user_object"), name="userfollows_unique" + ), ), migrations.AddConstraint( - model_name='userfollowrequest', - constraint=models.UniqueConstraint(fields=('user_subject', 'user_object'), name='userfollowrequest_unique'), + model_name="userfollowrequest", + constraint=models.UniqueConstraint( + fields=("user_subject", "user_object"), name="userfollowrequest_unique" + ), ), migrations.AddConstraint( - model_name='userblocks', - constraint=models.UniqueConstraint(fields=('user_subject', 'user_object'), name='userblocks_unique'), + model_name="userblocks", + constraint=models.UniqueConstraint( + fields=("user_subject", "user_object"), name="userblocks_unique" + ), ), migrations.AlterField( - model_name='notification', - name='notification_type', - field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request')], max_length=255), + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("REPLY", "Reply"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ], + max_length=255, + ), ), migrations.AddConstraint( - model_name='notification', - constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST']), name='notification_type_valid'), + model_name="notification", + constraint=models.CheckConstraint( + check=models.Q( + notification_type__in=[ + "FAVORITE", + "REPLY", + "TAG", + "FOLLOW", + "FOLLOW_REQUEST", + ] + ), + name="notification_type_valid", + ), ), migrations.AddConstraint( - model_name='userblocks', - constraint=models.CheckConstraint(check=models.Q(_negated=True, user_subject=django.db.models.expressions.F('user_object')), name='userblocks_no_self'), + model_name="userblocks", + constraint=models.CheckConstraint( + check=models.Q( + _negated=True, + user_subject=django.db.models.expressions.F("user_object"), + ), + name="userblocks_no_self", + ), ), migrations.AddConstraint( - model_name='userfollowrequest', - constraint=models.CheckConstraint(check=models.Q(_negated=True, user_subject=django.db.models.expressions.F('user_object')), name='userfollowrequest_no_self'), + model_name="userfollowrequest", + constraint=models.CheckConstraint( + check=models.Q( + _negated=True, + user_subject=django.db.models.expressions.F("user_object"), + ), + name="userfollowrequest_no_self", + ), ), migrations.AddConstraint( - model_name='userfollows', - constraint=models.CheckConstraint(check=models.Q(_negated=True, user_subject=django.db.models.expressions.F('user_object')), name='userfollows_no_self'), + model_name="userfollows", + constraint=models.CheckConstraint( + check=models.Q( + _negated=True, + user_subject=django.db.models.expressions.F("user_object"), + ), + name="userfollows_no_self", + ), ), migrations.AddField( - model_name='favorite', - name='remote_id', + model_name="favorite", + name="remote_id", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.CreateModel( - name='Comment', + name="Comment", fields=[ - ('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Status')), - ('name', models.CharField(max_length=255)), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Book')), + ( + "status_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Status", + ), + ), + ("name", models.CharField(max_length=255)), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Book" + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.status',), + bases=("bookwyrm.status",), ), migrations.CreateModel( - name='Connector', + name="Connector", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('identifier', models.CharField(max_length=255, unique=True)), - ('connector_file', models.CharField(choices=[('openlibrary', 'Openlibrary'), ('bookwyrm', 'BookWyrm')], default='openlibrary', max_length=255)), - ('is_self', models.BooleanField(default=False)), - ('api_key', models.CharField(max_length=255, null=True)), - ('base_url', models.CharField(max_length=255)), - ('covers_url', models.CharField(max_length=255)), - ('search_url', models.CharField(max_length=255, null=True)), - ('key_name', models.CharField(max_length=255)), - ('politeness_delay', models.IntegerField(null=True)), - ('max_query_count', models.IntegerField(null=True)), - ('query_count', models.IntegerField(default=0)), - ('query_count_expiry', models.DateTimeField(auto_now_add=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("identifier", models.CharField(max_length=255, unique=True)), + ( + "connector_file", + models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("bookwyrm", "BookWyrm"), + ], + default="openlibrary", + max_length=255, + ), + ), + ("is_self", models.BooleanField(default=False)), + ("api_key", models.CharField(max_length=255, null=True)), + ("base_url", models.CharField(max_length=255)), + ("covers_url", models.CharField(max_length=255)), + ("search_url", models.CharField(max_length=255, null=True)), + ("key_name", models.CharField(max_length=255)), + ("politeness_delay", models.IntegerField(null=True)), + ("max_query_count", models.IntegerField(null=True)), + ("query_count", models.IntegerField(default=0)), + ("query_count_expiry", models.DateTimeField(auto_now_add=True)), ], ), migrations.RenameField( - model_name='book', - old_name='local_key', - new_name='fedireads_key', + model_name="book", + old_name="local_key", + new_name="fedireads_key", ), migrations.RenameField( - model_name='book', - old_name='origin', - new_name='source_url', + model_name="book", + old_name="origin", + new_name="source_url", ), migrations.RemoveField( - model_name='book', - name='local_edits', + model_name="book", + name="local_edits", ), migrations.AddConstraint( - model_name='connector', - constraint=models.CheckConstraint(check=models.Q(connector_file__in=bookwyrm.models.connector.ConnectorFiles), name='connector_file_valid'), + model_name="connector", + constraint=models.CheckConstraint( + check=models.Q( + connector_file__in=bookwyrm.models.connector.ConnectorFiles + ), + name="connector_file_valid", + ), ), migrations.AddField( - model_name='book', - name='connector', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Connector'), + model_name="book", + name="connector", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Connector", + ), ), migrations.AddField( - model_name='book', - name='subject_places', - field=ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="book", + name="subject_places", + field=ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AddField( - model_name='book', - name='subjects', - field=ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="book", + name="subjects", + field=ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AddField( - model_name='edition', - name='publishers', - field=ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="edition", + name="publishers", + field=ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AlterField( - model_name='connector', - name='connector_file', - field=models.CharField(choices=[('openlibrary', 'Openlibrary'), ('fedireads_connector', 'Fedireads Connector')], default='openlibrary', max_length=255), + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("fedireads_connector", "Fedireads Connector"), + ], + default="openlibrary", + max_length=255, + ), ), migrations.RemoveField( - model_name='connector', - name='is_self', + model_name="connector", + name="is_self", ), migrations.AlterField( - model_name='connector', - name='connector_file', - field=models.CharField(choices=[('openlibrary', 'Openlibrary'), ('self_connector', 'Self Connector'), ('fedireads_connector', 'Fedireads Connector')], default='openlibrary', max_length=255), + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("self_connector", "Self Connector"), + ("fedireads_connector", "Fedireads Connector"), + ], + default="openlibrary", + max_length=255, + ), ), migrations.AddField( - model_name='book', - name='sync_cover', + model_name="book", + name="sync_cover", field=models.BooleanField(default=True), ), migrations.AlterField( - model_name='author', - name='born', + model_name="author", + name="born", field=models.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='author', - name='died', + model_name="author", + name="died", field=models.DateTimeField(blank=True, null=True), ), migrations.AddField( - model_name='author', - name='fedireads_key', + model_name="author", + name="fedireads_key", field=models.CharField(default=uuid.uuid4, max_length=255, unique=True), ), migrations.AlterField( - model_name='author', - name='first_name', + model_name="author", + name="first_name", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='author', - name='last_name', + model_name="author", + name="last_name", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='author', - name='openlibrary_key', + model_name="author", + name="openlibrary_key", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='first_published_date', + model_name="book", + name="first_published_date", field=models.DateTimeField(blank=True, null=True), ), migrations.AddField( - model_name='book', - name='goodreads_key', + model_name="book", + name="goodreads_key", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='language', + model_name="book", + name="language", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='librarything_key', + model_name="book", + name="librarything_key", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='openlibrary_key', + model_name="book", + name="openlibrary_key", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='published_date', + model_name="book", + name="published_date", field=models.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='book', - name='sort_title', + model_name="book", + name="sort_title", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='subtitle', + model_name="book", + name="subtitle", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='edition', - name='isbn', + model_name="edition", + name="isbn", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='edition', - name='oclc_number', + model_name="edition", + name="oclc_number", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='edition', - name='pages', + model_name="edition", + name="pages", field=models.IntegerField(blank=True, null=True), ), migrations.AddField( - model_name='edition', - name='physical_format', + model_name="edition", + name="physical_format", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='work', - name='lccn', + model_name="work", + name="lccn", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='federatedserver', - name='application_version', + model_name="federatedserver", + name="application_version", field=models.CharField(max_length=255, null=True), ), migrations.AlterField( - model_name='book', - name='last_sync_date', + model_name="book", + name="last_sync_date", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.AlterField( - model_name='status', - name='published_date', + model_name="status", + name="published_date", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.CreateModel( - name='Boost', + name="Boost", fields=[ - ('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Status')), + ( + "status_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Status", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.status',), + bases=("bookwyrm.status",), ), migrations.RemoveConstraint( - model_name='notification', - name='notification_type_valid', + model_name="notification", + name="notification_type_valid", ), migrations.AlterField( - model_name='notification', - name='notification_type', - field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost')], max_length=255), + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("REPLY", "Reply"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("BOOST", "Boost"), + ], + max_length=255, + ), ), migrations.AddConstraint( - model_name='notification', - constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST']), name='notification_type_valid'), + model_name="notification", + constraint=models.CheckConstraint( + check=models.Q( + notification_type__in=[ + "FAVORITE", + "REPLY", + "TAG", + "FOLLOW", + "FOLLOW_REQUEST", + "BOOST", + ] + ), + name="notification_type_valid", + ), ), migrations.AddField( - model_name='boost', - name='boosted_status', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='boosters', to='bookwyrm.Status'), + model_name="boost", + name="boosted_status", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="boosters", + to="bookwyrm.Status", + ), ), migrations.RemoveField( - model_name='book', - name='language', + model_name="book", + name="language", ), migrations.RemoveField( - model_name='book', - name='parent_work', + model_name="book", + name="parent_work", ), migrations.RemoveField( - model_name='book', - name='shelves', + model_name="book", + name="shelves", ), migrations.AddField( - model_name='book', - name='languages', - field=ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="book", + name="languages", + field=ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AddField( - model_name='edition', - name='default', + model_name="edition", + name="default", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='edition', - name='parent_work', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Work'), + model_name="edition", + name="parent_work", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Work", + ), ), migrations.AddField( - model_name='edition', - name='shelves', - field=models.ManyToManyField(through='bookwyrm.ShelfBook', to='bookwyrm.Shelf'), + model_name="edition", + name="shelves", + field=models.ManyToManyField( + through="bookwyrm.ShelfBook", to="bookwyrm.Shelf" + ), ), migrations.AlterField( - model_name='comment', - name='book', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="comment", + name="book", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='notification', - name='related_book', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="notification", + name="related_book", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Edition", + ), ), migrations.AlterField( - model_name='review', - name='book', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="review", + name="book", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='shelf', - name='books', - field=models.ManyToManyField(through='bookwyrm.ShelfBook', to='bookwyrm.Edition'), + model_name="shelf", + name="books", + field=models.ManyToManyField( + through="bookwyrm.ShelfBook", to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='shelfbook', - name='book', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="shelfbook", + name="book", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='status', - name='mention_books', - field=models.ManyToManyField(related_name='mention_book', to='bookwyrm.Edition'), + model_name="status", + name="mention_books", + field=models.ManyToManyField( + related_name="mention_book", to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='tag', - name='book', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="tag", + name="book", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.RemoveField( - model_name='comment', - name='name', + model_name="comment", + name="name", ), migrations.AlterField( - model_name='review', - name='rating', - field=models.IntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)]), + model_name="review", + name="rating", + field=models.IntegerField( + blank=True, + default=None, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(5), + ], + ), ), migrations.AlterField( - model_name='review', - name='name', + model_name="review", + name="name", field=models.CharField(max_length=255, null=True), ), migrations.CreateModel( - name='Quotation', + name="Quotation", fields=[ - ('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Status')), - ('quote', models.TextField()), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition')), + ( + "status_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Status", + ), + ), + ("quote", models.TextField()), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Edition", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.status',), + bases=("bookwyrm.status",), ), migrations.CreateModel( - name='ReadThrough', + name="ReadThrough", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('pages_read', models.IntegerField(blank=True, null=True)), - ('start_date', models.DateTimeField(blank=True, null=True)), - ('finish_date', models.DateTimeField(blank=True, null=True)), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Book')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("pages_read", models.IntegerField(blank=True, null=True)), + ("start_date", models.DateTimeField(blank=True, null=True)), + ("finish_date", models.DateTimeField(blank=True, null=True)), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Book" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='ImportItem', + name="ImportItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('data', JSONField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("data", JSONField()), ], ), migrations.CreateModel( - name='ImportJob', + name="ImportJob", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(default=django.utils.timezone.now)), - ('task_id', models.CharField(max_length=100, null=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created_date", + models.DateTimeField(default=django.utils.timezone.now), + ), + ("task_id", models.CharField(max_length=100, null=True)), ], ), migrations.RemoveConstraint( - model_name='notification', - name='notification_type_valid', + model_name="notification", + name="notification_type_valid", ), migrations.AlterField( - model_name='notification', - name='notification_type', - field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost'), ('IMPORT_RESULT', 'Import Result')], max_length=255), + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("REPLY", "Reply"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("BOOST", "Boost"), + ("IMPORT_RESULT", "Import Result"), + ], + max_length=255, + ), ), migrations.AddConstraint( - model_name='notification', - constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST', 'IMPORT_RESULT']), name='notification_type_valid'), + model_name="notification", + constraint=models.CheckConstraint( + check=models.Q( + notification_type__in=[ + "FAVORITE", + "REPLY", + "TAG", + "FOLLOW", + "FOLLOW_REQUEST", + "BOOST", + "IMPORT_RESULT", + ] + ), + name="notification_type_valid", + ), ), migrations.AddField( - model_name='importjob', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="importjob", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='importitem', - name='book', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='bookwyrm.Book'), + model_name="importitem", + name="book", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="bookwyrm.Book", + ), ), migrations.AddField( - model_name='importitem', - name='job', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='bookwyrm.ImportJob'), + model_name="importitem", + name="job", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="items", + to="bookwyrm.ImportJob", + ), ), migrations.RemoveConstraint( - model_name='notification', - name='notification_type_valid', + model_name="notification", + name="notification_type_valid", ), migrations.AddField( - model_name='importitem', - name='fail_reason', + model_name="importitem", + name="fail_reason", field=models.TextField(null=True), ), migrations.AddField( - model_name='importitem', - name='index', + model_name="importitem", + name="index", field=models.IntegerField(default=1), preserve_default=False, ), migrations.AddField( - model_name='notification', - name='related_import', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.ImportJob'), + model_name="notification", + name="related_import", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.ImportJob", + ), ), migrations.AlterField( - model_name='notification', - name='notification_type', - field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost'), ('IMPORT', 'Import')], max_length=255), + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("REPLY", "Reply"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("BOOST", "Boost"), + ("IMPORT", "Import"), + ], + max_length=255, + ), ), migrations.AddConstraint( - model_name='notification', - constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST', 'IMPORT']), name='notification_type_valid'), + model_name="notification", + constraint=models.CheckConstraint( + check=models.Q( + notification_type__in=[ + "FAVORITE", + "REPLY", + "TAG", + "FOLLOW", + "FOLLOW_REQUEST", + "BOOST", + "IMPORT", + ] + ), + name="notification_type_valid", + ), ), migrations.RenameField( - model_name='edition', - old_name='isbn', - new_name='isbn_13', + model_name="edition", + old_name="isbn", + new_name="isbn_13", ), migrations.AddField( - model_name='book', - name='author_text', + model_name="book", + name="author_text", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='edition', - name='asin', + model_name="edition", + name="asin", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='edition', - name='isbn_10', + model_name="edition", + name="isbn_10", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='connector', - name='books_url', - field=models.CharField(default='https://openlibrary.org', max_length=255), + model_name="connector", + name="books_url", + field=models.CharField(default="https://openlibrary.org", max_length=255), preserve_default=False, ), migrations.AddField( - model_name='connector', - name='local', + model_name="connector", + name="local", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='connector', - name='name', + model_name="connector", + name="name", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='connector', - name='priority', + model_name="connector", + name="priority", field=models.IntegerField(default=2), ), migrations.AlterField( - model_name='connector', - name='connector_file', - field=models.CharField(choices=[('openlibrary', 'Openlibrary'), ('self_connector', 'Self Connector'), ('fedireads_connector', 'Fedireads Connector')], max_length=255), + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("self_connector", "Self Connector"), + ("fedireads_connector", "Fedireads Connector"), + ], + max_length=255, + ), ), migrations.RemoveField( - model_name='author', - name='fedireads_key', + model_name="author", + name="fedireads_key", ), migrations.RemoveField( - model_name='book', - name='fedireads_key', + model_name="book", + name="fedireads_key", ), migrations.RemoveField( - model_name='book', - name='source_url', + model_name="book", + name="source_url", ), migrations.AddField( - model_name='author', - name='last_sync_date', + model_name="author", + name="last_sync_date", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.AddField( - model_name='author', - name='sync', + model_name="author", + name="sync", field=models.BooleanField(default=True), ), migrations.AddField( - model_name='book', - name='remote_id', + model_name="book", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='author', - name='remote_id', + model_name="author", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.RemoveField( - model_name='book', - name='misc_identifiers', + model_name="book", + name="misc_identifiers", ), migrations.RemoveField( - model_name='connector', - name='key_name', + model_name="connector", + name="key_name", ), migrations.RemoveField( - model_name='user', - name='actor', + model_name="user", + name="actor", ), migrations.AddField( - model_name='connector', - name='remote_id', + model_name="connector", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='federatedserver', - name='remote_id', + model_name="federatedserver", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='notification', - name='remote_id', + model_name="notification", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='readthrough', - name='remote_id', + model_name="readthrough", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='shelf', - name='remote_id', + model_name="shelf", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='shelfbook', - name='remote_id', + model_name="shelfbook", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='tag', - name='remote_id', + model_name="tag", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='userblocks', - name='remote_id', + model_name="userblocks", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='userfollowrequest', - name='remote_id', + model_name="userfollowrequest", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='userfollows', - name='remote_id', + model_name="userfollows", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AlterField( - model_name='favorite', - name='remote_id', + model_name="favorite", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AlterField( - model_name='status', - name='remote_id', + model_name="status", + name="remote_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='user', - name='remote_id', + model_name="user", + name="remote_id", field=models.CharField(max_length=255, null=True, unique=True), ), migrations.CreateModel( - name='SiteInvite', + name="SiteInvite", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('code', models.CharField(default=bookwyrm.models.site.new_access_code, max_length=32)), - ('expiry', models.DateTimeField(blank=True, null=True)), - ('use_limit', models.IntegerField(blank=True, null=True)), - ('times_used', models.IntegerField(default=0)), - ('user', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "code", + models.CharField( + default=bookwyrm.models.site.new_access_code, max_length=32 + ), + ), + ("expiry", models.DateTimeField(blank=True, null=True)), + ("use_limit", models.IntegerField(blank=True, null=True)), + ("times_used", models.IntegerField(default=0)), + ( + "user", + models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.RemoveField( - model_name='status', - name='activity_type', + model_name="status", + name="activity_type", ), migrations.RemoveField( - model_name='status', - name='status_type', + model_name="status", + name="status_type", ), migrations.RenameField( - model_name='user', - old_name='fedireads_user', - new_name='bookwyrm_user', + model_name="user", + old_name="fedireads_user", + new_name="bookwyrm_user", ), migrations.AlterField( - model_name='connector', - name='connector_file', - field=models.CharField(choices=[('openlibrary', 'Openlibrary'), ('self_connector', 'Self Connector'), ('bookwyrm_connector', 'BookWyrm Connector')], max_length=255), + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("self_connector", "Self Connector"), + ("bookwyrm_connector", "BookWyrm Connector"), + ], + max_length=255, + ), ), migrations.AlterField( - model_name='connector', - name='connector_file', - field=models.CharField(choices=[('openlibrary', 'Openlibrary'), ('self_connector', 'Self Connector'), ('bookwyrm_connector', 'Bookwyrm Connector')], max_length=255), + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("self_connector", "Self Connector"), + ("bookwyrm_connector", "Bookwyrm Connector"), + ], + max_length=255, + ), ), migrations.CreateModel( - name='GeneratedStatus', + name="GeneratedStatus", fields=[ - ('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='bookwyrm.Status')), + ( + "status_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="bookwyrm.Status", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=('bookwyrm.status',), + bases=("bookwyrm.status",), ), migrations.CreateModel( - name='PasswordReset', + name="PasswordReset", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('code', models.CharField(default=bookwyrm.models.site.new_access_code, max_length=32)), - ('expiry', models.DateTimeField(default=bookwyrm.models.site.get_passowrd_reset_expiry)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "code", + models.CharField( + default=bookwyrm.models.site.new_access_code, max_length=32 + ), + ), + ( + "expiry", + models.DateTimeField( + default=bookwyrm.models.site.get_passowrd_reset_expiry + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AlterField( - model_name='user', - name='email', + model_name="user", + name="email", field=models.EmailField(max_length=254, unique=True), ), migrations.CreateModel( - name='SiteSettings', + name="SiteSettings", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(default='BookWyrm', max_length=100)), - ('instance_description', models.TextField(default='This instance has no description.')), - ('code_of_conduct', models.TextField(default='Add a code of conduct here.')), - ('allow_registration', models.BooleanField(default=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(default="BookWyrm", max_length=100)), + ( + "instance_description", + models.TextField(default="This instance has no description."), + ), + ( + "code_of_conduct", + models.TextField(default="Add a code of conduct here."), + ), + ("allow_registration", models.BooleanField(default=True)), ], ), migrations.AlterField( - model_name='user', - name='email', - field=models.EmailField(blank=True, max_length=254, verbose_name='email address'), + model_name="user", + name="email", + field=models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), ), migrations.AddField( - model_name='status', - name='deleted', + model_name="status", + name="deleted", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='status', - name='deleted_date', + model_name="status", + name="deleted_date", field=models.DateTimeField(), ), - django.contrib.postgres.operations.TrigramExtension( + django.contrib.postgres.operations.TrigramExtension(), + migrations.RemoveField( + model_name="userblocks", + name="relationship_id", ), migrations.RemoveField( - model_name='userblocks', - name='relationship_id', + model_name="userfollowrequest", + name="relationship_id", ), migrations.RemoveField( - model_name='userfollowrequest', - name='relationship_id', - ), - migrations.RemoveField( - model_name='userfollows', - name='relationship_id', + model_name="userfollows", + name="relationship_id", ), migrations.AlterField( - model_name='status', - name='deleted_date', + model_name="status", + name="deleted_date", field=models.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='status', - name='privacy', - field=models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="status", + name="privacy", + field=models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), migrations.AddField( - model_name='importjob', - name='include_reviews', + model_name="importjob", + name="include_reviews", field=models.BooleanField(default=True), ), migrations.AddField( - model_name='importjob', - name='privacy', - field=models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="importjob", + name="privacy", + field=models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), migrations.AlterField( - model_name='user', - name='federated_server', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.FederatedServer'), + model_name="user", + name="federated_server", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.FederatedServer", + ), ), migrations.RenameModel( - old_name='GeneratedStatus', - new_name='GeneratedNote', + old_name="GeneratedStatus", + new_name="GeneratedNote", ), migrations.AlterField( - model_name='connector', - name='api_key', + model_name="connector", + name="api_key", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='connector', - name='max_query_count', + model_name="connector", + name="max_query_count", field=models.IntegerField(blank=True, null=True), ), migrations.AlterField( - model_name='connector', - name='name', + model_name="connector", + name="name", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AlterField( - model_name='connector', - name='politeness_delay', + model_name="connector", + name="politeness_delay", field=models.IntegerField(blank=True, null=True), ), migrations.AlterField( - model_name='connector', - name='search_url', + model_name="connector", + name="search_url", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='user', - name='last_active_date', + model_name="user", + name="last_active_date", field=models.DateTimeField(auto_now=True), ), migrations.RemoveConstraint( - model_name='notification', - name='notification_type_valid', + model_name="notification", + name="notification_type_valid", ), migrations.AlterField( - model_name='notification', - name='notification_type', - field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('MENTION', 'Mention'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost'), ('IMPORT', 'Import')], max_length=255), + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("REPLY", "Reply"), + ("MENTION", "Mention"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("BOOST", "Boost"), + ("IMPORT", "Import"), + ], + max_length=255, + ), ), migrations.AddConstraint( - model_name='notification', - constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'MENTION', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST', 'IMPORT']), name='notification_type_valid'), + model_name="notification", + constraint=models.CheckConstraint( + check=models.Q( + notification_type__in=[ + "FAVORITE", + "REPLY", + "MENTION", + "TAG", + "FOLLOW", + "FOLLOW_REQUEST", + "BOOST", + "IMPORT", + ] + ), + name="notification_type_valid", + ), ), ] diff --git a/bookwyrm/migrations/0007_auto_20201103_0014.py b/bookwyrm/migrations/0007_auto_20201103_0014.py index bf0a12eb0..116c97a3e 100644 --- a/bookwyrm/migrations/0007_auto_20201103_0014.py +++ b/bookwyrm/migrations/0007_auto_20201103_0014.py @@ -8,13 +8,15 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0006_auto_20200221_1702_squashed_0064_merge_20201101_1913'), + ("bookwyrm", "0006_auto_20200221_1702_squashed_0064_merge_20201101_1913"), ] operations = [ migrations.AlterField( - model_name='siteinvite', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="siteinvite", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/bookwyrm/migrations/0008_work_default_edition.py b/bookwyrm/migrations/0008_work_default_edition.py index da1f959e8..787e3776a 100644 --- a/bookwyrm/migrations/0008_work_default_edition.py +++ b/bookwyrm/migrations/0008_work_default_edition.py @@ -6,8 +6,8 @@ import django.db.models.deletion def set_default_edition(app_registry, schema_editor): db_alias = schema_editor.connection.alias - works = app_registry.get_model('bookwyrm', 'Work').objects.using(db_alias) - editions = app_registry.get_model('bookwyrm', 'Edition').objects.using(db_alias) + works = app_registry.get_model("bookwyrm", "Work").objects.using(db_alias) + editions = app_registry.get_model("bookwyrm", "Edition").objects.using(db_alias) for work in works: ed = editions.filter(parent_work=work, default=True).first() if not ed: @@ -15,21 +15,26 @@ def set_default_edition(app_registry, schema_editor): work.default_edition = ed work.save() + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0007_auto_20201103_0014'), + ("bookwyrm", "0007_auto_20201103_0014"), ] operations = [ migrations.AddField( - model_name='work', - name='default_edition', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="work", + name="default_edition", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Edition", + ), ), migrations.RunPython(set_default_edition), migrations.RemoveField( - model_name='edition', - name='default', + model_name="edition", + name="default", ), ] diff --git a/bookwyrm/migrations/0009_shelf_privacy.py b/bookwyrm/migrations/0009_shelf_privacy.py index 8232c2edc..635661045 100644 --- a/bookwyrm/migrations/0009_shelf_privacy.py +++ b/bookwyrm/migrations/0009_shelf_privacy.py @@ -6,13 +6,22 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0008_work_default_edition'), + ("bookwyrm", "0008_work_default_edition"), ] operations = [ migrations.AddField( - model_name='shelf', - name='privacy', - field=models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="shelf", + name="privacy", + field=models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), ] diff --git a/bookwyrm/migrations/0010_importjob_retry.py b/bookwyrm/migrations/0010_importjob_retry.py index 21296cc45..b3cc371bb 100644 --- a/bookwyrm/migrations/0010_importjob_retry.py +++ b/bookwyrm/migrations/0010_importjob_retry.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0009_shelf_privacy'), + ("bookwyrm", "0009_shelf_privacy"), ] operations = [ migrations.AddField( - model_name='importjob', - name='retry', + model_name="importjob", + name="retry", field=models.BooleanField(default=False), ), ] diff --git a/bookwyrm/migrations/0011_auto_20201113_1727.py b/bookwyrm/migrations/0011_auto_20201113_1727.py index 15e853a35..f4ea55c59 100644 --- a/bookwyrm/migrations/0011_auto_20201113_1727.py +++ b/bookwyrm/migrations/0011_auto_20201113_1727.py @@ -2,9 +2,10 @@ from django.db import migrations, models + def set_origin_id(app_registry, schema_editor): db_alias = schema_editor.connection.alias - books = app_registry.get_model('bookwyrm', 'Book').objects.using(db_alias) + books = app_registry.get_model("bookwyrm", "Book").objects.using(db_alias) for book in books: book.origin_id = book.remote_id # the remote_id will be set automatically @@ -15,18 +16,18 @@ def set_origin_id(app_registry, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0010_importjob_retry'), + ("bookwyrm", "0010_importjob_retry"), ] operations = [ migrations.AddField( - model_name='author', - name='origin_id', + model_name="author", + name="origin_id", field=models.CharField(max_length=255, null=True), ), migrations.AddField( - model_name='book', - name='origin_id', + model_name="book", + name="origin_id", field=models.CharField(max_length=255, null=True), ), migrations.RunPython(set_origin_id), diff --git a/bookwyrm/migrations/0012_attachment.py b/bookwyrm/migrations/0012_attachment.py index 495538517..5188b463b 100644 --- a/bookwyrm/migrations/0012_attachment.py +++ b/bookwyrm/migrations/0012_attachment.py @@ -7,23 +7,41 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0011_auto_20201113_1727'), + ("bookwyrm", "0011_auto_20201113_1727"), ] operations = [ migrations.CreateModel( - name='Attachment', + name="Attachment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', models.CharField(max_length=255, null=True)), - ('image', models.ImageField(blank=True, null=True, upload_to='status/')), - ('caption', models.TextField(blank=True, null=True)), - ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='bookwyrm.Status')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("remote_id", models.CharField(max_length=255, null=True)), + ( + "image", + models.ImageField(blank=True, null=True, upload_to="status/"), + ), + ("caption", models.TextField(blank=True, null=True)), + ( + "status", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="attachments", + to="bookwyrm.Status", + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), ] diff --git a/bookwyrm/migrations/0012_progressupdate.py b/bookwyrm/migrations/0012_progressupdate.py index 131419712..566556b7e 100644 --- a/bookwyrm/migrations/0012_progressupdate.py +++ b/bookwyrm/migrations/0012_progressupdate.py @@ -8,24 +8,51 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0011_auto_20201113_1727'), + ("bookwyrm", "0011_auto_20201113_1727"), ] operations = [ migrations.CreateModel( - name='ProgressUpdate', + name="ProgressUpdate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', models.CharField(max_length=255, null=True)), - ('progress', models.IntegerField()), - ('mode', models.CharField(choices=[('PG', 'page'), ('PCT', 'percent')], default='PG', max_length=3)), - ('readthrough', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.ReadThrough')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("remote_id", models.CharField(max_length=255, null=True)), + ("progress", models.IntegerField()), + ( + "mode", + models.CharField( + choices=[("PG", "page"), ("PCT", "percent")], + default="PG", + max_length=3, + ), + ), + ( + "readthrough", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.ReadThrough", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), ] diff --git a/bookwyrm/migrations/0013_book_origin_id.py b/bookwyrm/migrations/0013_book_origin_id.py index 581a2406e..08cf7bee7 100644 --- a/bookwyrm/migrations/0013_book_origin_id.py +++ b/bookwyrm/migrations/0013_book_origin_id.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0012_attachment'), + ("bookwyrm", "0012_attachment"), ] operations = [ migrations.AlterField( - model_name='book', - name='origin_id', + model_name="book", + name="origin_id", field=models.CharField(blank=True, max_length=255, null=True), ), ] diff --git a/bookwyrm/migrations/0014_auto_20201128_0118.py b/bookwyrm/migrations/0014_auto_20201128_0118.py index babdd7805..2626b9652 100644 --- a/bookwyrm/migrations/0014_auto_20201128_0118.py +++ b/bookwyrm/migrations/0014_auto_20201128_0118.py @@ -6,12 +6,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0013_book_origin_id'), + ("bookwyrm", "0013_book_origin_id"), ] operations = [ migrations.RenameModel( - old_name='Attachment', - new_name='Image', + old_name="Attachment", + new_name="Image", ), ] diff --git a/bookwyrm/migrations/0014_merge_20201128_0007.py b/bookwyrm/migrations/0014_merge_20201128_0007.py index e811fa7ff..ce6bb5c03 100644 --- a/bookwyrm/migrations/0014_merge_20201128_0007.py +++ b/bookwyrm/migrations/0014_merge_20201128_0007.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0013_book_origin_id'), - ('bookwyrm', '0012_progressupdate'), + ("bookwyrm", "0013_book_origin_id"), + ("bookwyrm", "0012_progressupdate"), ] - operations = [ - ] + operations = [] diff --git a/bookwyrm/migrations/0015_auto_20201128_0349.py b/bookwyrm/migrations/0015_auto_20201128_0349.py index 52b155186..f4454c5db 100644 --- a/bookwyrm/migrations/0015_auto_20201128_0349.py +++ b/bookwyrm/migrations/0015_auto_20201128_0349.py @@ -7,13 +7,18 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0014_auto_20201128_0118'), + ("bookwyrm", "0014_auto_20201128_0118"), ] operations = [ migrations.AlterField( - model_name='image', - name='status', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='bookwyrm.Status'), + model_name="image", + name="status", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="attachments", + to="bookwyrm.Status", + ), ), ] diff --git a/bookwyrm/migrations/0015_auto_20201128_0734.py b/bookwyrm/migrations/0015_auto_20201128_0734.py index c6eb78150..efbad6109 100644 --- a/bookwyrm/migrations/0015_auto_20201128_0734.py +++ b/bookwyrm/migrations/0015_auto_20201128_0734.py @@ -6,18 +6,20 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0014_merge_20201128_0007'), + ("bookwyrm", "0014_merge_20201128_0007"), ] operations = [ migrations.RenameField( - model_name='readthrough', - old_name='pages_read', - new_name='progress', + model_name="readthrough", + old_name="pages_read", + new_name="progress", ), migrations.AddField( - model_name='readthrough', - name='progress_mode', - field=models.CharField(choices=[('PG', 'page'), ('PCT', 'percent')], default='PG', max_length=3), + model_name="readthrough", + name="progress_mode", + field=models.CharField( + choices=[("PG", "page"), ("PCT", "percent")], default="PG", max_length=3 + ), ), ] diff --git a/bookwyrm/migrations/0016_auto_20201129_0304.py b/bookwyrm/migrations/0016_auto_20201129_0304.py index 1e7159691..ef2cbe0f5 100644 --- a/bookwyrm/migrations/0016_auto_20201129_0304.py +++ b/bookwyrm/migrations/0016_auto_20201129_0304.py @@ -5,58 +5,101 @@ from django.db import migrations, models import django.db.models.deletion from django.contrib.postgres.fields import ArrayField + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0015_auto_20201128_0349'), + ("bookwyrm", "0015_auto_20201128_0349"), ] operations = [ migrations.AlterField( - model_name='book', - name='subject_places', - field=ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, null=True, size=None), + model_name="book", + name="subject_places", + field=ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + null=True, + size=None, + ), ), migrations.AlterField( - model_name='book', - name='subjects', - field=ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, null=True, size=None), + model_name="book", + name="subjects", + field=ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + null=True, + size=None, + ), ), migrations.AlterField( - model_name='edition', - name='parent_work', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='editions', to='bookwyrm.Work'), + model_name="edition", + name="parent_work", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="editions", + to="bookwyrm.Work", + ), ), migrations.AlterField( - model_name='tag', - name='name', + model_name="tag", + name="name", field=models.CharField(max_length=100, unique=True), ), migrations.AlterUniqueTogether( - name='tag', + name="tag", unique_together=set(), ), migrations.RemoveField( - model_name='tag', - name='book', + model_name="tag", + name="book", ), migrations.RemoveField( - model_name='tag', - name='user', + model_name="tag", + name="user", ), migrations.CreateModel( - name='UserTag', + name="UserTag", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', models.CharField(max_length=255, null=True)), - ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition')), - ('tag', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Tag')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ("remote_id", models.CharField(max_length=255, null=True)), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Edition", + ), + ), + ( + "tag", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Tag" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'unique_together': {('user', 'book', 'tag')}, + "unique_together": {("user", "book", "tag")}, }, ), ] diff --git a/bookwyrm/migrations/0016_auto_20201211_2026.py b/bookwyrm/migrations/0016_auto_20201211_2026.py index 46b6140c3..3793f90ba 100644 --- a/bookwyrm/migrations/0016_auto_20201211_2026.py +++ b/bookwyrm/migrations/0016_auto_20201211_2026.py @@ -6,23 +6,23 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0015_auto_20201128_0349'), + ("bookwyrm", "0015_auto_20201128_0349"), ] operations = [ migrations.AddField( - model_name='sitesettings', - name='admin_email', + model_name="sitesettings", + name="admin_email", field=models.EmailField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='sitesettings', - name='support_link', + model_name="sitesettings", + name="support_link", field=models.CharField(blank=True, max_length=255, null=True), ), migrations.AddField( - model_name='sitesettings', - name='support_title', + model_name="sitesettings", + name="support_title", field=models.CharField(blank=True, max_length=100, null=True), ), ] diff --git a/bookwyrm/migrations/0017_auto_20201130_1819.py b/bookwyrm/migrations/0017_auto_20201130_1819.py index 0775269b6..f6478e0a5 100644 --- a/bookwyrm/migrations/0017_auto_20201130_1819.py +++ b/bookwyrm/migrations/0017_auto_20201130_1819.py @@ -6,184 +6,296 @@ from django.conf import settings from django.db import migrations, models import django.db.models.deletion + def copy_rsa_keys(app_registry, schema_editor): db_alias = schema_editor.connection.alias - users = app_registry.get_model('bookwyrm', 'User') - keypair = app_registry.get_model('bookwyrm', 'KeyPair') + users = app_registry.get_model("bookwyrm", "User") + keypair = app_registry.get_model("bookwyrm", "KeyPair") for user in users.objects.using(db_alias): if user.public_key or user.private_key: user.key_pair = keypair.objects.create( - remote_id='%s/#main-key' % user.remote_id, + remote_id="%s/#main-key" % user.remote_id, private_key=user.private_key, - public_key=user.public_key + public_key=user.public_key, ) user.save() + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0016_auto_20201129_0304'), + ("bookwyrm", "0016_auto_20201129_0304"), ] operations = [ migrations.CreateModel( - name='KeyPair', + name="KeyPair", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('private_key', models.TextField(blank=True, null=True)), - ('public_key', bookwyrm.models.fields.TextField(blank=True, null=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("private_key", models.TextField(blank=True, null=True)), + ("public_key", bookwyrm.models.fields.TextField(blank=True, null=True)), ], options={ - 'abstract': False, + "abstract": False, }, bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model), ), migrations.AddField( - model_name='user', - name='followers', - field=bookwyrm.models.fields.ManyToManyField(related_name='following', through='bookwyrm.UserFollows', to=settings.AUTH_USER_MODEL), + model_name="user", + name="followers", + field=bookwyrm.models.fields.ManyToManyField( + related_name="following", + through="bookwyrm.UserFollows", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='author', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="author", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='book', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="book", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='connector', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="connector", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='favorite', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="favorite", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='federatedserver', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="federatedserver", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='image', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="image", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='notification', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="notification", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='readthrough', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="readthrough", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='shelf', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="shelf", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='shelfbook', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="shelfbook", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='status', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="status", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='tag', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="tag", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='user', - name='avatar', - field=bookwyrm.models.fields.ImageField(blank=True, null=True, upload_to='avatars/'), + model_name="user", + name="avatar", + field=bookwyrm.models.fields.ImageField( + blank=True, null=True, upload_to="avatars/" + ), ), migrations.AlterField( - model_name='user', - name='bookwyrm_user', + model_name="user", + name="bookwyrm_user", field=bookwyrm.models.fields.BooleanField(default=True), ), migrations.AlterField( - model_name='user', - name='inbox', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, unique=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="user", + name="inbox", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + unique=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='user', - name='local', + model_name="user", + name="local", field=models.BooleanField(default=False), ), migrations.AlterField( - model_name='user', - name='manually_approves_followers', + model_name="user", + name="manually_approves_followers", field=bookwyrm.models.fields.BooleanField(default=False), ), migrations.AlterField( - model_name='user', - name='name', - field=bookwyrm.models.fields.CharField(blank=True, max_length=100, null=True), + model_name="user", + name="name", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=100, null=True + ), ), migrations.AlterField( - model_name='user', - name='outbox', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, unique=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="user", + name="outbox", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + unique=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='user', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, unique=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="user", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + unique=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='user', - name='shared_inbox', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="user", + name="shared_inbox", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='user', - name='summary', + model_name="user", + name="summary", field=bookwyrm.models.fields.TextField(blank=True, null=True), ), migrations.AlterField( - model_name='user', - name='username', + model_name="user", + name="username", field=bookwyrm.models.fields.UsernameField(), ), migrations.AlterField( - model_name='userblocks', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="userblocks", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='userfollowrequest', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="userfollowrequest", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='userfollows', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="userfollows", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='usertag', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="usertag", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AddField( - model_name='user', - name='key_pair', - field=bookwyrm.models.fields.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='bookwyrm.KeyPair'), + model_name="user", + name="key_pair", + field=bookwyrm.models.fields.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="owner", + to="bookwyrm.KeyPair", + ), ), migrations.RunPython(copy_rsa_keys), ] diff --git a/bookwyrm/migrations/0017_auto_20201212_0059.py b/bookwyrm/migrations/0017_auto_20201212_0059.py index c9e3fcf4e..34d27a1f9 100644 --- a/bookwyrm/migrations/0017_auto_20201212_0059.py +++ b/bookwyrm/migrations/0017_auto_20201212_0059.py @@ -7,13 +7,15 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0016_auto_20201211_2026'), + ("bookwyrm", "0016_auto_20201211_2026"), ] operations = [ migrations.AlterField( - model_name='readthrough', - name='book', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="readthrough", + name="book", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), ] diff --git a/bookwyrm/migrations/0018_auto_20201130_1832.py b/bookwyrm/migrations/0018_auto_20201130_1832.py index 278446cf5..579b09f2f 100644 --- a/bookwyrm/migrations/0018_auto_20201130_1832.py +++ b/bookwyrm/migrations/0018_auto_20201130_1832.py @@ -6,20 +6,20 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0017_auto_20201130_1819'), + ("bookwyrm", "0017_auto_20201130_1819"), ] operations = [ migrations.RemoveField( - model_name='user', - name='following', + model_name="user", + name="following", ), migrations.RemoveField( - model_name='user', - name='private_key', + model_name="user", + name="private_key", ), migrations.RemoveField( - model_name='user', - name='public_key', + model_name="user", + name="public_key", ), ] diff --git a/bookwyrm/migrations/0019_auto_20201130_1939.py b/bookwyrm/migrations/0019_auto_20201130_1939.py index 11cf6a3b6..e5e7674a1 100644 --- a/bookwyrm/migrations/0019_auto_20201130_1939.py +++ b/bookwyrm/migrations/0019_auto_20201130_1939.py @@ -3,34 +3,36 @@ import bookwyrm.models.fields from django.db import migrations + def update_notnull(app_registry, schema_editor): db_alias = schema_editor.connection.alias - users = app_registry.get_model('bookwyrm', 'User') + users = app_registry.get_model("bookwyrm", "User") for user in users.objects.using(db_alias): if user.name and user.summary: continue if not user.summary: - user.summary = '' + user.summary = "" if not user.name: - user.name = '' + user.name = "" user.save() + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0018_auto_20201130_1832'), + ("bookwyrm", "0018_auto_20201130_1832"), ] operations = [ migrations.RunPython(update_notnull), migrations.AlterField( - model_name='user', - name='name', - field=bookwyrm.models.fields.CharField(default='', max_length=100), + model_name="user", + name="name", + field=bookwyrm.models.fields.CharField(default="", max_length=100), ), migrations.AlterField( - model_name='user', - name='summary', - field=bookwyrm.models.fields.TextField(default=''), + model_name="user", + name="summary", + field=bookwyrm.models.fields.TextField(default=""), ), ] diff --git a/bookwyrm/migrations/0020_auto_20201208_0213.py b/bookwyrm/migrations/0020_auto_20201208_0213.py index 9c5345c75..79d9e73dd 100644 --- a/bookwyrm/migrations/0020_auto_20201208_0213.py +++ b/bookwyrm/migrations/0020_auto_20201208_0213.py @@ -11,343 +11,497 @@ import django.utils.timezone class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0019_auto_20201130_1939'), + ("bookwyrm", "0019_auto_20201130_1939"), ] operations = [ migrations.AlterField( - model_name='author', - name='aliases', - field=bookwyrm.models.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="author", + name="aliases", + field=bookwyrm.models.fields.ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AlterField( - model_name='author', - name='bio', + model_name="author", + name="bio", field=bookwyrm.models.fields.TextField(blank=True, null=True), ), migrations.AlterField( - model_name='author', - name='born', + model_name="author", + name="born", field=bookwyrm.models.fields.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='author', - name='died', + model_name="author", + name="died", field=bookwyrm.models.fields.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='author', - name='name', + model_name="author", + name="name", field=bookwyrm.models.fields.CharField(max_length=255), ), migrations.AlterField( - model_name='author', - name='openlibrary_key', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="author", + name="openlibrary_key", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='author', - name='wikipedia_link', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="author", + name="wikipedia_link", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='authors', - field=bookwyrm.models.fields.ManyToManyField(to='bookwyrm.Author'), + model_name="book", + name="authors", + field=bookwyrm.models.fields.ManyToManyField(to="bookwyrm.Author"), ), migrations.AlterField( - model_name='book', - name='cover', - field=bookwyrm.models.fields.ImageField(blank=True, null=True, upload_to='covers/'), + model_name="book", + name="cover", + field=bookwyrm.models.fields.ImageField( + blank=True, null=True, upload_to="covers/" + ), ), migrations.AlterField( - model_name='book', - name='description', + model_name="book", + name="description", field=bookwyrm.models.fields.TextField(blank=True, null=True), ), migrations.AlterField( - model_name='book', - name='first_published_date', + model_name="book", + name="first_published_date", field=bookwyrm.models.fields.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='book', - name='goodreads_key', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="goodreads_key", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='languages', - field=bookwyrm.models.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="book", + name="languages", + field=bookwyrm.models.fields.ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AlterField( - model_name='book', - name='librarything_key', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="librarything_key", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='openlibrary_key', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="openlibrary_key", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='published_date', + model_name="book", + name="published_date", field=bookwyrm.models.fields.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='book', - name='series', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="series", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='series_number', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="series_number", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='sort_title', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="sort_title", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='subject_places', - field=bookwyrm.models.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, null=True, size=None), + model_name="book", + name="subject_places", + field=bookwyrm.models.fields.ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + null=True, + size=None, + ), ), migrations.AlterField( - model_name='book', - name='subjects', - field=bookwyrm.models.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, null=True, size=None), + model_name="book", + name="subjects", + field=bookwyrm.models.fields.ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + null=True, + size=None, + ), ), migrations.AlterField( - model_name='book', - name='subtitle', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="book", + name="subtitle", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='book', - name='title', + model_name="book", + name="title", field=bookwyrm.models.fields.CharField(max_length=255), ), migrations.AlterField( - model_name='boost', - name='boosted_status', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='boosters', to='bookwyrm.Status'), + model_name="boost", + name="boosted_status", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="boosters", + to="bookwyrm.Status", + ), ), migrations.AlterField( - model_name='comment', - name='book', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="comment", + name="book", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='edition', - name='asin', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="edition", + name="asin", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='edition', - name='isbn_10', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="edition", + name="isbn_10", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='edition', - name='isbn_13', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="edition", + name="isbn_13", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='edition', - name='oclc_number', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="edition", + name="oclc_number", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='edition', - name='pages', + model_name="edition", + name="pages", field=bookwyrm.models.fields.IntegerField(blank=True, null=True), ), migrations.AlterField( - model_name='edition', - name='parent_work', - field=bookwyrm.models.fields.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='editions', to='bookwyrm.Work'), + model_name="edition", + name="parent_work", + field=bookwyrm.models.fields.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="editions", + to="bookwyrm.Work", + ), ), migrations.AlterField( - model_name='edition', - name='physical_format', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="edition", + name="physical_format", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AlterField( - model_name='edition', - name='publishers', - field=bookwyrm.models.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, default=list, size=None), + model_name="edition", + name="publishers", + field=bookwyrm.models.fields.ArrayField( + base_field=models.CharField(max_length=255), + blank=True, + default=list, + size=None, + ), ), migrations.AlterField( - model_name='favorite', - name='status', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Status'), + model_name="favorite", + name="status", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Status" + ), ), migrations.AlterField( - model_name='favorite', - name='user', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="favorite", + name="user", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), migrations.AlterField( - model_name='image', - name='caption', + model_name="image", + name="caption", field=bookwyrm.models.fields.TextField(blank=True, null=True), ), migrations.AlterField( - model_name='image', - name='image', - field=bookwyrm.models.fields.ImageField(blank=True, null=True, upload_to='status/'), + model_name="image", + name="image", + field=bookwyrm.models.fields.ImageField( + blank=True, null=True, upload_to="status/" + ), ), migrations.AlterField( - model_name='quotation', - name='book', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="quotation", + name="book", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='quotation', - name='quote', + model_name="quotation", + name="quote", field=bookwyrm.models.fields.TextField(), ), migrations.AlterField( - model_name='review', - name='book', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="review", + name="book", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='review', - name='name', + model_name="review", + name="name", field=bookwyrm.models.fields.CharField(max_length=255, null=True), ), migrations.AlterField( - model_name='review', - name='rating', - field=bookwyrm.models.fields.IntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)]), + model_name="review", + name="rating", + field=bookwyrm.models.fields.IntegerField( + blank=True, + default=None, + null=True, + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(5), + ], + ), ), migrations.AlterField( - model_name='shelf', - name='name', + model_name="shelf", + name="name", field=bookwyrm.models.fields.CharField(max_length=100), ), migrations.AlterField( - model_name='shelf', - name='privacy', - field=bookwyrm.models.fields.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="shelf", + name="privacy", + field=bookwyrm.models.fields.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), migrations.AlterField( - model_name='shelf', - name='user', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="shelf", + name="user", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), migrations.AlterField( - model_name='shelfbook', - name='added_by', - field=bookwyrm.models.fields.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="shelfbook", + name="added_by", + field=bookwyrm.models.fields.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='shelfbook', - name='book', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="shelfbook", + name="book", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='shelfbook', - name='shelf', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Shelf'), + model_name="shelfbook", + name="shelf", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Shelf" + ), ), migrations.AlterField( - model_name='status', - name='content', + model_name="status", + name="content", field=bookwyrm.models.fields.TextField(blank=True, null=True), ), migrations.AlterField( - model_name='status', - name='mention_books', - field=bookwyrm.models.fields.TagField(related_name='mention_book', to='bookwyrm.Edition'), + model_name="status", + name="mention_books", + field=bookwyrm.models.fields.TagField( + related_name="mention_book", to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='status', - name='mention_users', - field=bookwyrm.models.fields.TagField(related_name='mention_user', to=settings.AUTH_USER_MODEL), + model_name="status", + name="mention_users", + field=bookwyrm.models.fields.TagField( + related_name="mention_user", to=settings.AUTH_USER_MODEL + ), ), migrations.AlterField( - model_name='status', - name='published_date', - field=bookwyrm.models.fields.DateTimeField(default=django.utils.timezone.now), + model_name="status", + name="published_date", + field=bookwyrm.models.fields.DateTimeField( + default=django.utils.timezone.now + ), ), migrations.AlterField( - model_name='status', - name='reply_parent', - field=bookwyrm.models.fields.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Status'), + model_name="status", + name="reply_parent", + field=bookwyrm.models.fields.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Status", + ), ), migrations.AlterField( - model_name='status', - name='sensitive', + model_name="status", + name="sensitive", field=bookwyrm.models.fields.BooleanField(default=False), ), migrations.AlterField( - model_name='status', - name='user', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="status", + name="user", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), migrations.AlterField( - model_name='tag', - name='name', + model_name="tag", + name="name", field=bookwyrm.models.fields.CharField(max_length=100, unique=True), ), migrations.AlterField( - model_name='userblocks', - name='user_object', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='userblocks_user_object', to=settings.AUTH_USER_MODEL), + model_name="userblocks", + name="user_object", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userblocks_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='userblocks_user_subject', to=settings.AUTH_USER_MODEL), + model_name="userblocks", + name="user_subject", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userblocks_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='userfollowrequest_user_object', to=settings.AUTH_USER_MODEL), + model_name="userfollowrequest", + name="user_object", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollowrequest_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='userfollowrequest_user_subject', to=settings.AUTH_USER_MODEL), + model_name="userfollowrequest", + name="user_subject", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollowrequest_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='userfollows_user_object', to=settings.AUTH_USER_MODEL), + model_name="userfollows", + name="user_object", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollows_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='userfollows_user_subject', to=settings.AUTH_USER_MODEL), + model_name="userfollows", + name="user_subject", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="userfollows_user_subject", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='usertag', - name='book', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="usertag", + name="book", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Edition" + ), ), migrations.AlterField( - model_name='usertag', - name='tag', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Tag'), + model_name="usertag", + name="tag", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="bookwyrm.Tag" + ), ), migrations.AlterField( - model_name='usertag', - name='user', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="usertag", + name="user", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), migrations.AlterField( - model_name='work', - name='default_edition', - field=bookwyrm.models.fields.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition'), + model_name="work", + name="default_edition", + field=bookwyrm.models.fields.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Edition", + ), ), migrations.AlterField( - model_name='work', - name='lccn', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="work", + name="lccn", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), ] diff --git a/bookwyrm/migrations/0021_merge_20201212_1737.py b/bookwyrm/migrations/0021_merge_20201212_1737.py index 4ccf8c8cc..c6b48820d 100644 --- a/bookwyrm/migrations/0021_merge_20201212_1737.py +++ b/bookwyrm/migrations/0021_merge_20201212_1737.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0020_auto_20201208_0213'), - ('bookwyrm', '0016_auto_20201211_2026'), + ("bookwyrm", "0020_auto_20201208_0213"), + ("bookwyrm", "0016_auto_20201211_2026"), ] - operations = [ - ] + operations = [] diff --git a/bookwyrm/migrations/0022_auto_20201212_1744.py b/bookwyrm/migrations/0022_auto_20201212_1744.py index 0a98597f8..2651578cc 100644 --- a/bookwyrm/migrations/0022_auto_20201212_1744.py +++ b/bookwyrm/migrations/0022_auto_20201212_1744.py @@ -5,26 +5,27 @@ from django.db import migrations def set_author_name(app_registry, schema_editor): db_alias = schema_editor.connection.alias - authors = app_registry.get_model('bookwyrm', 'Author') + authors = app_registry.get_model("bookwyrm", "Author") for author in authors.objects.using(db_alias): if not author.name: - author.name = '%s %s' % (author.first_name, author.last_name) + author.name = "%s %s" % (author.first_name, author.last_name) author.save() + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0021_merge_20201212_1737'), + ("bookwyrm", "0021_merge_20201212_1737"), ] operations = [ migrations.RunPython(set_author_name), migrations.RemoveField( - model_name='author', - name='first_name', + model_name="author", + name="first_name", ), migrations.RemoveField( - model_name='author', - name='last_name', + model_name="author", + name="last_name", ), ] diff --git a/bookwyrm/migrations/0023_auto_20201214_0511.py b/bookwyrm/migrations/0023_auto_20201214_0511.py index e811bded8..4b4a0c4a2 100644 --- a/bookwyrm/migrations/0023_auto_20201214_0511.py +++ b/bookwyrm/migrations/0023_auto_20201214_0511.py @@ -7,13 +7,22 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0022_auto_20201212_1744'), + ("bookwyrm", "0022_auto_20201212_1744"), ] operations = [ migrations.AlterField( - model_name='status', - name='privacy', - field=bookwyrm.models.fields.PrivacyField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="status", + name="privacy", + field=bookwyrm.models.fields.PrivacyField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), ] diff --git a/bookwyrm/migrations/0023_merge_20201216_0112.py b/bookwyrm/migrations/0023_merge_20201216_0112.py index e3af48496..be88546e4 100644 --- a/bookwyrm/migrations/0023_merge_20201216_0112.py +++ b/bookwyrm/migrations/0023_merge_20201216_0112.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0017_auto_20201212_0059'), - ('bookwyrm', '0022_auto_20201212_1744'), + ("bookwyrm", "0017_auto_20201212_0059"), + ("bookwyrm", "0022_auto_20201212_1744"), ] - operations = [ - ] + operations = [] diff --git a/bookwyrm/migrations/0024_merge_20201216_1721.py b/bookwyrm/migrations/0024_merge_20201216_1721.py index 41f81335e..bb944d4eb 100644 --- a/bookwyrm/migrations/0024_merge_20201216_1721.py +++ b/bookwyrm/migrations/0024_merge_20201216_1721.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0023_auto_20201214_0511'), - ('bookwyrm', '0023_merge_20201216_0112'), + ("bookwyrm", "0023_auto_20201214_0511"), + ("bookwyrm", "0023_merge_20201216_0112"), ] - operations = [ - ] + operations = [] diff --git a/bookwyrm/migrations/0025_auto_20201217_0046.py b/bookwyrm/migrations/0025_auto_20201217_0046.py index a3ffe8c13..82e1f5037 100644 --- a/bookwyrm/migrations/0025_auto_20201217_0046.py +++ b/bookwyrm/migrations/0025_auto_20201217_0046.py @@ -7,33 +7,33 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0024_merge_20201216_1721'), + ("bookwyrm", "0024_merge_20201216_1721"), ] operations = [ migrations.AlterField( - model_name='author', - name='bio', + model_name="author", + name="bio", field=bookwyrm.models.fields.HtmlField(blank=True, null=True), ), migrations.AlterField( - model_name='book', - name='description', + model_name="book", + name="description", field=bookwyrm.models.fields.HtmlField(blank=True, null=True), ), migrations.AlterField( - model_name='quotation', - name='quote', + model_name="quotation", + name="quote", field=bookwyrm.models.fields.HtmlField(), ), migrations.AlterField( - model_name='status', - name='content', + model_name="status", + name="content", field=bookwyrm.models.fields.HtmlField(blank=True, null=True), ), migrations.AlterField( - model_name='user', - name='summary', - field=bookwyrm.models.fields.HtmlField(default=''), + model_name="user", + name="summary", + field=bookwyrm.models.fields.HtmlField(default=""), ), ] diff --git a/bookwyrm/migrations/0026_status_content_warning.py b/bookwyrm/migrations/0026_status_content_warning.py index f4e494db9..5212e83a0 100644 --- a/bookwyrm/migrations/0026_status_content_warning.py +++ b/bookwyrm/migrations/0026_status_content_warning.py @@ -7,13 +7,15 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0025_auto_20201217_0046'), + ("bookwyrm", "0025_auto_20201217_0046"), ] operations = [ migrations.AddField( - model_name='status', - name='content_warning', - field=bookwyrm.models.fields.CharField(blank=True, max_length=500, null=True), + model_name="status", + name="content_warning", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=500, null=True + ), ), ] diff --git a/bookwyrm/migrations/0027_auto_20201220_2007.py b/bookwyrm/migrations/0027_auto_20201220_2007.py index a3ad4dda3..5eec5139d 100644 --- a/bookwyrm/migrations/0027_auto_20201220_2007.py +++ b/bookwyrm/migrations/0027_auto_20201220_2007.py @@ -7,18 +7,20 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0026_status_content_warning'), + ("bookwyrm", "0026_status_content_warning"), ] operations = [ migrations.AlterField( - model_name='user', - name='name', - field=bookwyrm.models.fields.CharField(blank=True, max_length=100, null=True), + model_name="user", + name="name", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=100, null=True + ), ), migrations.AlterField( - model_name='user', - name='summary', + model_name="user", + name="summary", field=bookwyrm.models.fields.HtmlField(blank=True, null=True), ), ] diff --git a/bookwyrm/migrations/0028_remove_book_author_text.py b/bookwyrm/migrations/0028_remove_book_author_text.py index 8743c910d..1f91d1c1e 100644 --- a/bookwyrm/migrations/0028_remove_book_author_text.py +++ b/bookwyrm/migrations/0028_remove_book_author_text.py @@ -6,12 +6,12 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0027_auto_20201220_2007'), + ("bookwyrm", "0027_auto_20201220_2007"), ] operations = [ migrations.RemoveField( - model_name='book', - name='author_text', + model_name="book", + name="author_text", ), ] diff --git a/bookwyrm/migrations/0029_auto_20201221_2014.py b/bookwyrm/migrations/0029_auto_20201221_2014.py index ebf27a742..7a6b71801 100644 --- a/bookwyrm/migrations/0029_auto_20201221_2014.py +++ b/bookwyrm/migrations/0029_auto_20201221_2014.py @@ -9,53 +9,65 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0028_remove_book_author_text'), + ("bookwyrm", "0028_remove_book_author_text"), ] operations = [ migrations.RemoveField( - model_name='author', - name='last_sync_date', + model_name="author", + name="last_sync_date", ), migrations.RemoveField( - model_name='author', - name='sync', + model_name="author", + name="sync", ), migrations.RemoveField( - model_name='book', - name='last_sync_date', + model_name="book", + name="last_sync_date", ), migrations.RemoveField( - model_name='book', - name='sync', + model_name="book", + name="sync", ), migrations.RemoveField( - model_name='book', - name='sync_cover', + model_name="book", + name="sync_cover", ), migrations.AddField( - model_name='author', - name='goodreads_key', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="author", + name="goodreads_key", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AddField( - model_name='author', - name='last_edited_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="author", + name="last_edited_by", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='author', - name='librarything_key', - field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + model_name="author", + name="librarything_key", + field=bookwyrm.models.fields.CharField( + blank=True, max_length=255, null=True + ), ), migrations.AddField( - model_name='book', - name='last_edited_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="book", + name="last_edited_by", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='author', - name='origin_id', + model_name="author", + name="origin_id", field=models.CharField(blank=True, max_length=255, null=True), ), ] diff --git a/bookwyrm/migrations/0030_auto_20201224_1939.py b/bookwyrm/migrations/0030_auto_20201224_1939.py index 6de5d37fb..beee20c4b 100644 --- a/bookwyrm/migrations/0030_auto_20201224_1939.py +++ b/bookwyrm/migrations/0030_auto_20201224_1939.py @@ -7,13 +7,18 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0029_auto_20201221_2014'), + ("bookwyrm", "0029_auto_20201221_2014"), ] operations = [ migrations.AlterField( - model_name='user', - name='localname', - field=models.CharField(max_length=255, null=True, unique=True, validators=[bookwyrm.models.fields.validate_localname]), + model_name="user", + name="localname", + field=models.CharField( + max_length=255, + null=True, + unique=True, + validators=[bookwyrm.models.fields.validate_localname], + ), ), ] diff --git a/bookwyrm/migrations/0031_auto_20210104_2040.py b/bookwyrm/migrations/0031_auto_20210104_2040.py index 604392d41..c6418fc9d 100644 --- a/bookwyrm/migrations/0031_auto_20210104_2040.py +++ b/bookwyrm/migrations/0031_auto_20210104_2040.py @@ -6,23 +6,23 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0030_auto_20201224_1939'), + ("bookwyrm", "0030_auto_20201224_1939"), ] operations = [ migrations.AddField( - model_name='sitesettings', - name='favicon', - field=models.ImageField(blank=True, null=True, upload_to='logos/'), + model_name="sitesettings", + name="favicon", + field=models.ImageField(blank=True, null=True, upload_to="logos/"), ), migrations.AddField( - model_name='sitesettings', - name='logo', - field=models.ImageField(blank=True, null=True, upload_to='logos/'), + model_name="sitesettings", + name="logo", + field=models.ImageField(blank=True, null=True, upload_to="logos/"), ), migrations.AddField( - model_name='sitesettings', - name='logo_small', - field=models.ImageField(blank=True, null=True, upload_to='logos/'), + model_name="sitesettings", + name="logo_small", + field=models.ImageField(blank=True, null=True, upload_to="logos/"), ), ] diff --git a/bookwyrm/migrations/0032_auto_20210104_2055.py b/bookwyrm/migrations/0032_auto_20210104_2055.py index 692cd581f..8b8012dab 100644 --- a/bookwyrm/migrations/0032_auto_20210104_2055.py +++ b/bookwyrm/migrations/0032_auto_20210104_2055.py @@ -6,18 +6,20 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0031_auto_20210104_2040'), + ("bookwyrm", "0031_auto_20210104_2040"), ] operations = [ migrations.AddField( - model_name='sitesettings', - name='instance_tagline', - field=models.CharField(default='Social Reading and Reviewing', max_length=150), + model_name="sitesettings", + name="instance_tagline", + field=models.CharField( + default="Social Reading and Reviewing", max_length=150 + ), ), migrations.AddField( - model_name='sitesettings', - name='registration_closed_text', - field=models.TextField(default='Contact an administrator to get an invite'), + model_name="sitesettings", + name="registration_closed_text", + field=models.TextField(default="Contact an administrator to get an invite"), ), ] diff --git a/bookwyrm/migrations/0033_siteinvite_created_date.py b/bookwyrm/migrations/0033_siteinvite_created_date.py index 9a3f98963..36d489ebb 100644 --- a/bookwyrm/migrations/0033_siteinvite_created_date.py +++ b/bookwyrm/migrations/0033_siteinvite_created_date.py @@ -7,14 +7,16 @@ import django.utils.timezone class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0032_auto_20210104_2055'), + ("bookwyrm", "0032_auto_20210104_2055"), ] operations = [ migrations.AddField( - model_name='siteinvite', - name='created_date', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + model_name="siteinvite", + name="created_date", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), preserve_default=False, ), ] diff --git a/bookwyrm/migrations/0034_importjob_complete.py b/bookwyrm/migrations/0034_importjob_complete.py index 141706070..6593df9fd 100644 --- a/bookwyrm/migrations/0034_importjob_complete.py +++ b/bookwyrm/migrations/0034_importjob_complete.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0033_siteinvite_created_date'), + ("bookwyrm", "0033_siteinvite_created_date"), ] operations = [ migrations.AddField( - model_name='importjob', - name='complete', + model_name="importjob", + name="complete", field=models.BooleanField(default=False), ), ] diff --git a/bookwyrm/migrations/0035_edition_edition_rank.py b/bookwyrm/migrations/0035_edition_edition_rank.py index 1a75a0974..7465c31b4 100644 --- a/bookwyrm/migrations/0035_edition_edition_rank.py +++ b/bookwyrm/migrations/0035_edition_edition_rank.py @@ -6,20 +6,21 @@ from django.db import migrations def set_rank(app_registry, schema_editor): db_alias = schema_editor.connection.alias - books = app_registry.get_model('bookwyrm', 'Edition') + books = app_registry.get_model("bookwyrm", "Edition") for book in books.objects.using(db_alias): book.save() + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0034_importjob_complete'), + ("bookwyrm", "0034_importjob_complete"), ] operations = [ migrations.AddField( - model_name='edition', - name='edition_rank', + model_name="edition", + name="edition_rank", field=bookwyrm.models.fields.IntegerField(default=0), ), migrations.RunPython(set_rank), diff --git a/bookwyrm/migrations/0036_annualgoal.py b/bookwyrm/migrations/0036_annualgoal.py index fb12833ea..fd08fb247 100644 --- a/bookwyrm/migrations/0036_annualgoal.py +++ b/bookwyrm/migrations/0036_annualgoal.py @@ -9,24 +9,57 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0035_edition_edition_rank'), + ("bookwyrm", "0035_edition_edition_rank"), ] operations = [ migrations.CreateModel( - name='AnnualGoal', + name="AnnualGoal", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('goal', models.IntegerField()), - ('year', models.IntegerField(default=2021)), - ('privacy', models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("goal", models.IntegerField()), + ("year", models.IntegerField(default=2021)), + ( + "privacy", + models.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'unique_together': {('user', 'year')}, + "unique_together": {("user", "year")}, }, ), ] diff --git a/bookwyrm/migrations/0037_auto_20210118_1954.py b/bookwyrm/migrations/0037_auto_20210118_1954.py index 97ba8808a..a0c27d457 100644 --- a/bookwyrm/migrations/0037_auto_20210118_1954.py +++ b/bookwyrm/migrations/0037_auto_20210118_1954.py @@ -2,36 +2,39 @@ from django.db import migrations, models + def empty_to_null(apps, schema_editor): User = apps.get_model("bookwyrm", "User") db_alias = schema_editor.connection.alias User.objects.using(db_alias).filter(email="").update(email=None) + def null_to_empty(apps, schema_editor): User = apps.get_model("bookwyrm", "User") db_alias = schema_editor.connection.alias User.objects.using(db_alias).filter(email=None).update(email="") + class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0036_annualgoal'), + ("bookwyrm", "0036_annualgoal"), ] operations = [ migrations.AlterModelOptions( - name='shelfbook', - options={'ordering': ('-created_date',)}, + name="shelfbook", + options={"ordering": ("-created_date",)}, ), migrations.AlterField( - model_name='user', - name='email', + model_name="user", + name="email", field=models.EmailField(max_length=254, null=True), ), migrations.RunPython(empty_to_null, null_to_empty), migrations.AlterField( - model_name='user', - name='email', + model_name="user", + name="email", field=models.EmailField(max_length=254, null=True, unique=True), ), ] diff --git a/bookwyrm/migrations/0038_auto_20210119_1534.py b/bookwyrm/migrations/0038_auto_20210119_1534.py index ac7a0d68f..14fd1ff29 100644 --- a/bookwyrm/migrations/0038_auto_20210119_1534.py +++ b/bookwyrm/migrations/0038_auto_20210119_1534.py @@ -7,13 +7,15 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0037_auto_20210118_1954'), + ("bookwyrm", "0037_auto_20210118_1954"), ] operations = [ migrations.AlterField( - model_name='annualgoal', - name='goal', - field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)]), + model_name="annualgoal", + name="goal", + field=models.IntegerField( + validators=[django.core.validators.MinValueValidator(1)] + ), ), ] diff --git a/bookwyrm/migrations/0039_merge_20210120_0753.py b/bookwyrm/migrations/0039_merge_20210120_0753.py index 1af40ee93..e698d8eaf 100644 --- a/bookwyrm/migrations/0039_merge_20210120_0753.py +++ b/bookwyrm/migrations/0039_merge_20210120_0753.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0038_auto_20210119_1534'), - ('bookwyrm', '0015_auto_20201128_0734'), + ("bookwyrm", "0038_auto_20210119_1534"), + ("bookwyrm", "0015_auto_20201128_0734"), ] - operations = [ - ] + operations = [] diff --git a/bookwyrm/migrations/0040_auto_20210122_0057.py b/bookwyrm/migrations/0040_auto_20210122_0057.py index 8e528a899..0641f5273 100644 --- a/bookwyrm/migrations/0040_auto_20210122_0057.py +++ b/bookwyrm/migrations/0040_auto_20210122_0057.py @@ -9,28 +9,40 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0039_merge_20210120_0753'), + ("bookwyrm", "0039_merge_20210120_0753"), ] operations = [ migrations.AlterField( - model_name='progressupdate', - name='progress', - field=models.IntegerField(validators=[django.core.validators.MinValueValidator(0)]), + model_name="progressupdate", + name="progress", + field=models.IntegerField( + validators=[django.core.validators.MinValueValidator(0)] + ), ), migrations.AlterField( - model_name='progressupdate', - name='readthrough', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.ReadThrough'), + model_name="progressupdate", + name="readthrough", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="bookwyrm.ReadThrough" + ), ), migrations.AlterField( - model_name='progressupdate', - name='remote_id', - field=bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id]), + model_name="progressupdate", + name="remote_id", + field=bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), ), migrations.AlterField( - model_name='readthrough', - name='progress', - field=models.IntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)]), + model_name="readthrough", + name="progress", + field=models.IntegerField( + blank=True, + null=True, + validators=[django.core.validators.MinValueValidator(0)], + ), ), ] diff --git a/bookwyrm/migrations/0041_auto_20210131_1614.py b/bookwyrm/migrations/0041_auto_20210131_1614.py index 6fcf406bd..01085dea3 100644 --- a/bookwyrm/migrations/0041_auto_20210131_1614.py +++ b/bookwyrm/migrations/0041_auto_20210131_1614.py @@ -10,56 +10,141 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0040_auto_20210122_0057'), + ("bookwyrm", "0040_auto_20210122_0057"), ] operations = [ migrations.CreateModel( - name='List', + name="List", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('name', bookwyrm.models.fields.CharField(max_length=100)), - ('description', bookwyrm.models.fields.TextField(blank=True, null=True)), - ('privacy', bookwyrm.models.fields.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)), - ('curation', bookwyrm.models.fields.CharField(choices=[('closed', 'Closed'), ('open', 'Open'), ('curated', 'Curated')], default='closed', max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("name", bookwyrm.models.fields.CharField(max_length=100)), + ( + "description", + bookwyrm.models.fields.TextField(blank=True, null=True), + ), + ( + "privacy", + bookwyrm.models.fields.CharField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), + ), + ( + "curation", + bookwyrm.models.fields.CharField( + choices=[ + ("closed", "Closed"), + ("open", "Open"), + ("curated", "Curated"), + ], + default="closed", + max_length=255, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=(bookwyrm.models.activitypub_mixin.OrderedCollectionMixin, models.Model), + bases=( + bookwyrm.models.activitypub_mixin.OrderedCollectionMixin, + models.Model, + ), ), migrations.CreateModel( - name='ListItem', + name="ListItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now=True)), - ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('notes', bookwyrm.models.fields.TextField(blank=True, null=True)), - ('approved', models.BooleanField(default=True)), - ('order', bookwyrm.models.fields.IntegerField(blank=True, null=True)), - ('added_by', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), - ('book', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition')), - ('book_list', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.List')), - ('endorsement', models.ManyToManyField(related_name='endorsers', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_date", models.DateTimeField(auto_now_add=True)), + ("updated_date", models.DateTimeField(auto_now=True)), + ( + "remote_id", + bookwyrm.models.fields.RemoteIdField( + max_length=255, + null=True, + validators=[bookwyrm.models.fields.validate_remote_id], + ), + ), + ("notes", bookwyrm.models.fields.TextField(blank=True, null=True)), + ("approved", models.BooleanField(default=True)), + ("order", bookwyrm.models.fields.IntegerField(blank=True, null=True)), + ( + "added_by", + bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "book", + bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="bookwyrm.Edition", + ), + ), + ( + "book_list", + bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="bookwyrm.List" + ), + ), + ( + "endorsement", + models.ManyToManyField( + related_name="endorsers", to=settings.AUTH_USER_MODEL + ), + ), ], options={ - 'ordering': ('-created_date',), - 'unique_together': {('book', 'book_list')}, + "ordering": ("-created_date",), + "unique_together": {("book", "book_list")}, }, bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model), ), migrations.AddField( - model_name='list', - name='books', - field=models.ManyToManyField(through='bookwyrm.ListItem', to='bookwyrm.Edition'), + model_name="list", + name="books", + field=models.ManyToManyField( + through="bookwyrm.ListItem", to="bookwyrm.Edition" + ), ), migrations.AddField( - model_name='list', - name='user', - field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="list", + name="user", + field=bookwyrm.models.fields.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/bookwyrm/migrations/0042_auto_20210201_2108.py b/bookwyrm/migrations/0042_auto_20210201_2108.py index 95a144de2..ee7201c10 100644 --- a/bookwyrm/migrations/0042_auto_20210201_2108.py +++ b/bookwyrm/migrations/0042_auto_20210201_2108.py @@ -7,22 +7,40 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0041_auto_20210131_1614'), + ("bookwyrm", "0041_auto_20210131_1614"), ] operations = [ migrations.AlterModelOptions( - name='list', - options={'ordering': ('-updated_date',)}, + name="list", + options={"ordering": ("-updated_date",)}, ), migrations.AlterField( - model_name='list', - name='privacy', - field=bookwyrm.models.fields.PrivacyField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="list", + name="privacy", + field=bookwyrm.models.fields.PrivacyField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), migrations.AlterField( - model_name='shelf', - name='privacy', - field=bookwyrm.models.fields.PrivacyField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255), + model_name="shelf", + name="privacy", + field=bookwyrm.models.fields.PrivacyField( + choices=[ + ("public", "Public"), + ("unlisted", "Unlisted"), + ("followers", "Followers"), + ("direct", "Direct"), + ], + default="public", + max_length=255, + ), ), ] diff --git a/bookwyrm/migrations/0043_auto_20210204_2223.py b/bookwyrm/migrations/0043_auto_20210204_2223.py index b9c328eaf..2e8318c55 100644 --- a/bookwyrm/migrations/0043_auto_20210204_2223.py +++ b/bookwyrm/migrations/0043_auto_20210204_2223.py @@ -6,18 +6,18 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0042_auto_20210201_2108'), + ("bookwyrm", "0042_auto_20210201_2108"), ] operations = [ migrations.RenameField( - model_name='listitem', - old_name='added_by', - new_name='user', + model_name="listitem", + old_name="added_by", + new_name="user", ), migrations.RenameField( - model_name='shelfbook', - old_name='added_by', - new_name='user', + model_name="shelfbook", + old_name="added_by", + new_name="user", ), ] diff --git a/bookwyrm/migrations/0044_auto_20210207_1924.py b/bookwyrm/migrations/0044_auto_20210207_1924.py index 7289c73d8..897e8e025 100644 --- a/bookwyrm/migrations/0044_auto_20210207_1924.py +++ b/bookwyrm/migrations/0044_auto_20210207_1924.py @@ -5,9 +5,10 @@ from django.conf import settings from django.db import migrations import django.db.models.deletion + def set_user(app_registry, schema_editor): db_alias = schema_editor.connection.alias - shelfbook = app_registry.get_model('bookwyrm', 'ShelfBook') + shelfbook = app_registry.get_model("bookwyrm", "ShelfBook") for item in shelfbook.objects.using(db_alias).filter(user__isnull=True): item.user = item.shelf.user try: @@ -19,15 +20,19 @@ def set_user(app_registry, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0043_auto_20210204_2223'), + ("bookwyrm", "0043_auto_20210204_2223"), ] operations = [ migrations.RunPython(set_user, lambda x, y: None), migrations.AlterField( - model_name='shelfbook', - name='user', - field=bookwyrm.models.fields.ForeignKey(default=2, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="shelfbook", + name="user", + field=bookwyrm.models.fields.ForeignKey( + default=2, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), preserve_default=False, ), ] diff --git a/bookwyrm/migrations/0045_auto_20210210_2114.py b/bookwyrm/migrations/0045_auto_20210210_2114.py index 87b9a3188..22f33cf47 100644 --- a/bookwyrm/migrations/0045_auto_20210210_2114.py +++ b/bookwyrm/migrations/0045_auto_20210210_2114.py @@ -8,51 +8,102 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0044_auto_20210207_1924'), + ("bookwyrm", "0044_auto_20210207_1924"), ] operations = [ migrations.RemoveConstraint( - model_name='notification', - name='notification_type_valid', + model_name="notification", + name="notification_type_valid", ), migrations.AddField( - model_name='notification', - name='related_list_item', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.ListItem'), + model_name="notification", + name="related_list_item", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="bookwyrm.ListItem", + ), ), migrations.AlterField( - model_name='notification', - name='notification_type', - field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('MENTION', 'Mention'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost'), ('IMPORT', 'Import'), ('ADD', 'Add')], max_length=255), + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("REPLY", "Reply"), + ("MENTION", "Mention"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("BOOST", "Boost"), + ("IMPORT", "Import"), + ("ADD", "Add"), + ], + max_length=255, + ), ), migrations.AlterField( - model_name='notification', - name='related_book', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.Edition'), + model_name="notification", + name="related_book", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="bookwyrm.Edition", + ), ), migrations.AlterField( - model_name='notification', - name='related_import', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.ImportJob'), + model_name="notification", + name="related_import", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="bookwyrm.ImportJob", + ), ), migrations.AlterField( - model_name='notification', - name='related_status', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.Status'), + model_name="notification", + name="related_status", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="bookwyrm.Status", + ), ), migrations.AlterField( - model_name='notification', - name='related_user', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_user', to=settings.AUTH_USER_MODEL), + model_name="notification", + name="related_user", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="related_user", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='notification', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="notification", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), migrations.AddConstraint( - model_name='notification', - constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'MENTION', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST', 'IMPORT', 'ADD']), name='notification_type_valid'), + model_name="notification", + constraint=models.CheckConstraint( + check=models.Q( + notification_type__in=[ + "FAVORITE", + "REPLY", + "MENTION", + "TAG", + "FOLLOW", + "FOLLOW_REQUEST", + "BOOST", + "IMPORT", + "ADD", + ] + ), + name="notification_type_valid", + ), ), ] diff --git a/bookwyrm/migrations/0046_sitesettings_privacy_policy.py b/bookwyrm/migrations/0046_sitesettings_privacy_policy.py index 0c49d607c..f9193764c 100644 --- a/bookwyrm/migrations/0046_sitesettings_privacy_policy.py +++ b/bookwyrm/migrations/0046_sitesettings_privacy_policy.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0045_auto_20210210_2114'), + ("bookwyrm", "0045_auto_20210210_2114"), ] operations = [ migrations.AddField( - model_name='sitesettings', - name='privacy_policy', - field=models.TextField(default='Add a privacy policy here.'), + model_name="sitesettings", + name="privacy_policy", + field=models.TextField(default="Add a privacy policy here."), ), ] diff --git a/bookwyrm/migrations/0047_connector_isbn_search_url.py b/bookwyrm/migrations/0047_connector_isbn_search_url.py index 617a89d9d..2ca802c5a 100644 --- a/bookwyrm/migrations/0047_connector_isbn_search_url.py +++ b/bookwyrm/migrations/0047_connector_isbn_search_url.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0046_sitesettings_privacy_policy'), + ("bookwyrm", "0046_sitesettings_privacy_policy"), ] operations = [ migrations.AddField( - model_name='connector', - name='isbn_search_url', + model_name="connector", + name="isbn_search_url", field=models.CharField(blank=True, max_length=255, null=True), ), ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 0aef63850..bef9debea 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -1,4 +1,4 @@ -''' bring all the models into the app namespace ''' +""" bring all the models into the app namespace """ import inspect import sys @@ -27,8 +27,12 @@ from .import_job import ImportJob, ImportItem from .site import SiteSettings, SiteInvite, PasswordReset cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) -activity_models = {c[1].activity_serializer.__name__: c[1] \ - for c in cls_members if hasattr(c[1], 'activity_serializer')} +activity_models = { + c[1].activity_serializer.__name__: c[1] + for c in cls_members + if hasattr(c[1], "activity_serializer") +} status_models = [ - c.__name__ for (_, c) in activity_models.items() if issubclass(c, Status)] + c.__name__ for (_, c) in activity_models.items() if issubclass(c, Status) +] diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 10015bf14..4ced78c28 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -1,4 +1,4 @@ -''' activitypub model functionality ''' +""" activitypub model functionality """ from base64 import b64encode from functools import reduce import json @@ -26,18 +26,19 @@ logger = logging.getLogger(__name__) # I tried to separate these classes into mutliple files but I kept getting # circular import errors so I gave up. I'm sure it could be done though! class ActivitypubMixin: - ''' add this mixin for models that are AP serializable ''' + """ add this mixin for models that are AP serializable """ + activity_serializer = lambda: {} reverse_unfurl = False def __init__(self, *args, **kwargs): - ''' collect some info on model fields ''' + """ collect some info on model fields """ self.image_fields = [] self.many_to_many_fields = [] - self.simple_fields = [] # "simple" + self.simple_fields = [] # "simple" # sort model fields by type for field in self._meta.get_fields(): - if not hasattr(field, 'field_to_activity'): + if not hasattr(field, "field_to_activity"): continue if isinstance(field, ImageField): @@ -48,33 +49,41 @@ class ActivitypubMixin: self.simple_fields.append(field) # a list of allll the serializable fields - self.activity_fields = self.image_fields + \ - self.many_to_many_fields + self.simple_fields + self.activity_fields = ( + self.image_fields + self.many_to_many_fields + self.simple_fields + ) # these are separate to avoid infinite recursion issues - self.deserialize_reverse_fields = self.deserialize_reverse_fields \ - if hasattr(self, 'deserialize_reverse_fields') else [] - self.serialize_reverse_fields = self.serialize_reverse_fields \ - if hasattr(self, 'serialize_reverse_fields') else [] + self.deserialize_reverse_fields = ( + self.deserialize_reverse_fields + if hasattr(self, "deserialize_reverse_fields") + else [] + ) + self.serialize_reverse_fields = ( + self.serialize_reverse_fields + if hasattr(self, "serialize_reverse_fields") + else [] + ) super().__init__(*args, **kwargs) - @classmethod def find_existing_by_remote_id(cls, remote_id): - ''' look up a remote id in the db ''' - return cls.find_existing({'id': remote_id}) + """ look up a remote id in the db """ + return cls.find_existing({"id": remote_id}) @classmethod def find_existing(cls, data): - ''' compare data to fields that can be used for deduplation. + """compare data to fields that can be used for deduplation. This always includes remote_id, but can also be unique identifiers - like an isbn for an edition ''' + like an isbn for an edition""" filters = [] # grabs all the data from the model to create django queryset filters for field in cls._meta.get_fields(): - if not hasattr(field, 'deduplication_field') or \ - not field.deduplication_field: + if ( + not hasattr(field, "deduplication_field") + or not field.deduplication_field + ): continue value = data.get(field.get_activitypub_field()) @@ -82,9 +91,9 @@ class ActivitypubMixin: continue filters.append({field.name: value}) - if hasattr(cls, 'origin_id') and 'id' in data: + if hasattr(cls, "origin_id") and "id" in data: # kinda janky, but this handles special case for books - filters.append({'origin_id': data['id']}) + filters.append({"origin_id": data["id"]}) if not filters: # if there are no deduplication fields, it will match the first @@ -92,45 +101,41 @@ class ActivitypubMixin: return None objects = cls.objects - if hasattr(objects, 'select_subclasses'): + if hasattr(objects, "select_subclasses"): objects = objects.select_subclasses() # an OR operation on all the match fields, sorry for the dense syntax - match = objects.filter( - reduce(operator.or_, (Q(**f) for f in filters)) - ) + match = objects.filter(reduce(operator.or_, (Q(**f) for f in filters))) # there OUGHT to be only one match return match.first() - def broadcast(self, activity, sender, software=None): - ''' send out an activity ''' + """ send out an activity """ broadcast_task.delay( sender.id, json.dumps(activity, cls=activitypub.ActivityEncoder), - self.get_recipients(software=software) + self.get_recipients(software=software), ) - def get_recipients(self, software=None): - ''' figure out which inbox urls to post to ''' + """ figure out which inbox urls to post to """ # first we have to figure out who should receive this activity - privacy = self.privacy if hasattr(self, 'privacy') else 'public' + privacy = self.privacy if hasattr(self, "privacy") else "public" # is this activity owned by a user (statuses, lists, shelves), or is it # general to the instance (like books) - user = self.user if hasattr(self, 'user') else None - user_model = apps.get_model('bookwyrm.User', require_ready=True) + user = self.user if hasattr(self, "user") else None + user_model = apps.get_model("bookwyrm.User", require_ready=True) if not user and isinstance(self, user_model): # or maybe the thing itself is a user user = self # find anyone who's tagged in a status, for example - mentions = self.recipients if hasattr(self, 'recipients') else [] + mentions = self.recipients if hasattr(self, "recipients") else [] # we always send activities to explicitly mentioned users' inboxes recipients = [u.inbox for u in mentions or []] # unless it's a dm, all the followers should receive the activity - if privacy != 'direct': + if privacy != "direct": # we will send this out to a subset of all remote users queryset = user_model.objects.filter( local=False, @@ -138,43 +143,43 @@ class ActivitypubMixin: # filter users first by whether they're using the desired software # this lets us send book updates only to other bw servers if software: - queryset = queryset.filter( - bookwyrm_user=(software == 'bookwyrm') - ) + queryset = queryset.filter(bookwyrm_user=(software == "bookwyrm")) # if there's a user, we only want to send to the user's followers if user: queryset = queryset.filter(following=user) # ideally, we will send to shared inboxes for efficiency - shared_inboxes = queryset.filter( - shared_inbox__isnull=False - ).values_list('shared_inbox', flat=True).distinct() + shared_inboxes = ( + queryset.filter(shared_inbox__isnull=False) + .values_list("shared_inbox", flat=True) + .distinct() + ) # but not everyone has a shared inbox - inboxes = queryset.filter( - shared_inbox__isnull=True - ).values_list('inbox', flat=True) + inboxes = queryset.filter(shared_inbox__isnull=True).values_list( + "inbox", flat=True + ) recipients += list(shared_inboxes) + list(inboxes) return recipients - def to_activity_dataclass(self): - ''' convert from a model to an activity ''' + """ convert from a model to an activity """ activity = generate_activity(self) return self.activity_serializer(**activity) - def to_activity(self, **kwargs): # pylint: disable=unused-argument - ''' convert from a model to a json activity ''' + def to_activity(self, **kwargs): # pylint: disable=unused-argument + """ convert from a model to a json activity """ return self.to_activity_dataclass().serialize() class ObjectMixin(ActivitypubMixin): - ''' add this mixin for object models that are AP serializable ''' + """ add this mixin for object models that are AP serializable """ + def save(self, *args, created=None, **kwargs): - ''' broadcast created/updated/deleted objects as appropriate ''' - broadcast = kwargs.get('broadcast', True) + """ broadcast created/updated/deleted objects as appropriate """ + broadcast = kwargs.get("broadcast", True) # this bonus kwarg woul cause an error in the base save method - if 'broadcast' in kwargs: - del kwargs['broadcast'] + if "broadcast" in kwargs: + del kwargs["broadcast"] created = created or not bool(self.id) # first off, we want to save normally no matter what @@ -183,7 +188,7 @@ class ObjectMixin(ActivitypubMixin): return # this will work for objects owned by a user (lists, shelves) - user = self.user if hasattr(self, 'user') else None + user = self.user if hasattr(self, "user") else None if created: # broadcast Create activities for objects owned by a local user @@ -193,10 +198,10 @@ class ObjectMixin(ActivitypubMixin): try: software = None # do we have a "pure" activitypub version of this for mastodon? - if hasattr(self, 'pure_content'): + if hasattr(self, "pure_content"): pure_activity = self.to_create_activity(user, pure=True) - self.broadcast(pure_activity, user, software='other') - software = 'bookwyrm' + self.broadcast(pure_activity, user, software="other") + software = "bookwyrm" # sends to BW only if we just did a pure version for masto activity = self.to_create_activity(user) self.broadcast(activity, user, software=software) @@ -209,39 +214,38 @@ class ObjectMixin(ActivitypubMixin): # --- updating an existing object if not user: # users don't have associated users, they ARE users - user_model = apps.get_model('bookwyrm.User', require_ready=True) + user_model = apps.get_model("bookwyrm.User", require_ready=True) if isinstance(self, user_model): user = self # book data tracks last editor - elif hasattr(self, 'last_edited_by'): + elif hasattr(self, "last_edited_by"): user = self.last_edited_by # again, if we don't know the user or they're remote, don't bother if not user or not user.local: return # is this a deletion? - if hasattr(self, 'deleted') and self.deleted: + if hasattr(self, "deleted") and self.deleted: activity = self.to_delete_activity(user) else: activity = self.to_update_activity(user) self.broadcast(activity, user) - def to_create_activity(self, user, **kwargs): - ''' returns the object wrapped in a Create activity ''' + """ returns the object wrapped in a Create activity """ activity_object = self.to_activity_dataclass(**kwargs) signature = None - create_id = self.remote_id + '/activity' - if hasattr(activity_object, 'content') and activity_object.content: + create_id = self.remote_id + "/activity" + if hasattr(activity_object, "content") and activity_object.content: signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key)) content = activity_object.content - signed_message = signer.sign(SHA256.new(content.encode('utf8'))) + signed_message = signer.sign(SHA256.new(content.encode("utf8"))) signature = activitypub.Signature( - creator='%s#main-key' % user.remote_id, + creator="%s#main-key" % user.remote_id, created=activity_object.published, - signatureValue=b64encode(signed_message).decode('utf8') + signatureValue=b64encode(signed_message).decode("utf8"), ) return activitypub.Create( @@ -253,50 +257,48 @@ class ObjectMixin(ActivitypubMixin): signature=signature, ).serialize() - def to_delete_activity(self, user): - ''' notice of deletion ''' + """ notice of deletion """ return activitypub.Delete( - id=self.remote_id + '/activity', + id=self.remote_id + "/activity", actor=user.remote_id, - to=['%s/followers' % user.remote_id], - cc=['https://www.w3.org/ns/activitystreams#Public'], + to=["%s/followers" % user.remote_id], + cc=["https://www.w3.org/ns/activitystreams#Public"], + object=self, + ).serialize() + + def to_update_activity(self, user): + """ wrapper for Updates to an activity """ + activity_id = "%s#update/%s" % (self.remote_id, uuid4()) + return activitypub.Update( + id=activity_id, + actor=user.remote_id, + to=["https://www.w3.org/ns/activitystreams#Public"], object=self, ).serialize() - def to_update_activity(self, user): - ''' wrapper for Updates to an activity ''' - activity_id = '%s#update/%s' % (self.remote_id, uuid4()) - return activitypub.Update( - id=activity_id, - actor=user.remote_id, - to=['https://www.w3.org/ns/activitystreams#Public'], - object=self - ).serialize() - - class OrderedCollectionPageMixin(ObjectMixin): - ''' just the paginator utilities, so you don't HAVE to - override ActivitypubMixin's to_activity (ie, for outbox) ''' + """just the paginator utilities, so you don't HAVE to + override ActivitypubMixin's to_activity (ie, for outbox)""" + @property def collection_remote_id(self): - ''' this can be overriden if there's a special remote id, ie outbox ''' + """ this can be overriden if there's a special remote id, ie outbox """ return self.remote_id - - def to_ordered_collection(self, queryset, \ - remote_id=None, page=False, collection_only=False, **kwargs): - ''' an ordered collection of whatevers ''' + def to_ordered_collection( + self, queryset, remote_id=None, page=False, collection_only=False, **kwargs + ): + """ an ordered collection of whatevers """ if not queryset.ordered: - raise RuntimeError('queryset must be ordered') + raise RuntimeError("queryset must be ordered") remote_id = remote_id or self.remote_id if page: - return to_ordered_collection_page( - queryset, remote_id, **kwargs) + return to_ordered_collection_page(queryset, remote_id, **kwargs) - if collection_only or not hasattr(self, 'activity_serializer'): + if collection_only or not hasattr(self, "activity_serializer"): serializer = activitypub.OrderedCollection activity = {} else: @@ -305,23 +307,24 @@ class OrderedCollectionPageMixin(ObjectMixin): activity = generate_activity(self) if remote_id: - activity['id'] = remote_id + activity["id"] = remote_id paginated = Paginator(queryset, PAGE_LENGTH) # add computed fields specific to orderd collections - activity['totalItems'] = paginated.count - activity['first'] = '%s?page=1' % remote_id - activity['last'] = '%s?page=%d' % (remote_id, paginated.num_pages) + activity["totalItems"] = paginated.count + activity["first"] = "%s?page=1" % remote_id + activity["last"] = "%s?page=%d" % (remote_id, paginated.num_pages) return serializer(**activity) class OrderedCollectionMixin(OrderedCollectionPageMixin): - ''' extends activitypub models to work as ordered collections ''' + """ extends activitypub models to work as ordered collections """ + @property def collection_queryset(self): - ''' usually an ordered collection model aggregates a different model ''' - raise NotImplementedError('Model must define collection_queryset') + """ usually an ordered collection model aggregates a different model """ + raise NotImplementedError("Model must define collection_queryset") activity_serializer = activitypub.OrderedCollection @@ -329,18 +332,20 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin): return self.to_ordered_collection(self.collection_queryset, **kwargs) def to_activity(self, **kwargs): - ''' an ordered collection of the specified model queryset ''' + """ an ordered collection of the specified model queryset """ return self.to_ordered_collection( - self.collection_queryset, **kwargs).serialize() + self.collection_queryset, **kwargs + ).serialize() class CollectionItemMixin(ActivitypubMixin): - ''' for items that are part of an (Ordered)Collection ''' + """ for items that are part of an (Ordered)Collection """ + activity_serializer = activitypub.Add object_field = collection_field = None def save(self, *args, broadcast=True, **kwargs): - ''' broadcast updated ''' + """ broadcast updated """ created = not bool(self.id) # first off, we want to save normally no matter what super().save(*args, **kwargs) @@ -353,89 +358,91 @@ class CollectionItemMixin(ActivitypubMixin): activity = self.to_add_activity() self.broadcast(activity, self.user) - def delete(self, *args, **kwargs): - ''' broadcast a remove activity ''' + """ broadcast a remove activity """ activity = self.to_remove_activity() super().delete(*args, **kwargs) self.broadcast(activity, self.user) - def to_add_activity(self): - ''' AP for shelving a book''' + """ AP for shelving a book""" object_field = getattr(self, self.object_field) collection_field = getattr(self, self.collection_field) return activitypub.Add( - id='%s#add' % self.remote_id, + id="%s#add" % self.remote_id, actor=self.user.remote_id, object=object_field, - target=collection_field.remote_id + target=collection_field.remote_id, ).serialize() def to_remove_activity(self): - ''' AP for un-shelving a book''' + """ AP for un-shelving a book""" object_field = getattr(self, self.object_field) collection_field = getattr(self, self.collection_field) return activitypub.Remove( - id='%s#remove' % self.remote_id, + id="%s#remove" % self.remote_id, actor=self.user.remote_id, object=object_field, - target=collection_field.remote_id + target=collection_field.remote_id, ).serialize() class ActivityMixin(ActivitypubMixin): - ''' add this mixin for models that are AP serializable ''' + """ add this mixin for models that are AP serializable """ + def save(self, *args, broadcast=True, **kwargs): - ''' broadcast activity ''' + """ broadcast activity """ super().save(*args, **kwargs) - user = self.user if hasattr(self, 'user') else self.user_subject + user = self.user if hasattr(self, "user") else self.user_subject if broadcast and user.local: self.broadcast(self.to_activity(), user) - def delete(self, *args, broadcast=True, **kwargs): - ''' nevermind, undo that activity ''' - user = self.user if hasattr(self, 'user') else self.user_subject + """ nevermind, undo that activity """ + user = self.user if hasattr(self, "user") else self.user_subject if broadcast and user.local: self.broadcast(self.to_undo_activity(), user) super().delete(*args, **kwargs) - def to_undo_activity(self): - ''' undo an action ''' - user = self.user if hasattr(self, 'user') else self.user_subject + """ undo an action """ + user = self.user if hasattr(self, "user") else self.user_subject return activitypub.Undo( - id='%s#undo' % self.remote_id, + id="%s#undo" % self.remote_id, actor=user.remote_id, object=self, ).serialize() def generate_activity(obj): - ''' go through the fields on an object ''' + """ go through the fields on an object """ activity = {} for field in obj.activity_fields: field.set_activity_from_field(activity, obj) - if hasattr(obj, 'serialize_reverse_fields'): + if hasattr(obj, "serialize_reverse_fields"): # for example, editions of a work - for model_field_name, activity_field_name, sort_field in \ - obj.serialize_reverse_fields: + for ( + model_field_name, + activity_field_name, + sort_field, + ) in obj.serialize_reverse_fields: related_field = getattr(obj, model_field_name) - activity[activity_field_name] = \ - unfurl_related_field(related_field, sort_field) + activity[activity_field_name] = unfurl_related_field( + related_field, sort_field + ) - if not activity.get('id'): - activity['id'] = obj.get_remote_id() + if not activity.get("id"): + activity["id"] = obj.get_remote_id() return activity def unfurl_related_field(related_field, sort_field=None): - ''' load reverse lookups (like public key owner or Status attachment ''' - if hasattr(related_field, 'all'): - return [unfurl_related_field(i) for i in related_field.order_by( - sort_field).all()] + """ load reverse lookups (like public key owner or Status attachment """ + if hasattr(related_field, "all"): + return [ + unfurl_related_field(i) for i in related_field.order_by(sort_field).all() + ] if related_field.reverse_unfurl: return related_field.field_to_activity() return related_field.remote_id @@ -443,8 +450,8 @@ def unfurl_related_field(related_field, sort_field=None): @app.task def broadcast_task(sender_id, activity, recipients): - ''' the celery task for broadcast ''' - user_model = apps.get_model('bookwyrm.User', require_ready=True) + """ the celery task for broadcast """ + user_model = apps.get_model("bookwyrm.User", require_ready=True) sender = user_model.objects.get(id=sender_id) for recipient in recipients: try: @@ -454,12 +461,12 @@ def broadcast_task(sender_id, activity, recipients): def sign_and_send(sender, data, destination): - ''' crpyto whatever and http junk ''' + """ crpyto whatever and http junk """ now = http_date() if not sender.key_pair.private_key: # this shouldn't happen. it would be bad if it happened. - raise ValueError('No private key found for sender') + raise ValueError("No private key found for sender") digest = make_digest(data) @@ -467,11 +474,11 @@ def sign_and_send(sender, data, destination): destination, data=data, headers={ - 'Date': now, - 'Digest': digest, - 'Signature': make_signature(sender, destination, now, digest), - 'Content-Type': 'application/activity+json; charset=utf-8', - 'User-Agent': USER_AGENT, + "Date": now, + "Digest": digest, + "Signature": make_signature(sender, destination, now, digest), + "Content-Type": "application/activity+json; charset=utf-8", + "User-Agent": USER_AGENT, }, ) if not response.ok: @@ -481,8 +488,9 @@ def sign_and_send(sender, data, destination): # pylint: disable=unused-argument def to_ordered_collection_page( - queryset, remote_id, id_only=False, page=1, pure=False, **kwargs): - ''' serialize and pagiante a queryset ''' + queryset, remote_id, id_only=False, page=1, pure=False, **kwargs +): + """ serialize and pagiante a queryset """ paginated = Paginator(queryset, PAGE_LENGTH) activity_page = paginated.page(page) @@ -493,14 +501,13 @@ def to_ordered_collection_page( prev_page = next_page = None if activity_page.has_next(): - next_page = '%s?page=%d' % (remote_id, activity_page.next_page_number()) + next_page = "%s?page=%d" % (remote_id, activity_page.next_page_number()) if activity_page.has_previous(): - prev_page = '%s?page=%d' % \ - (remote_id, activity_page.previous_page_number()) + prev_page = "%s?page=%d" % (remote_id, activity_page.previous_page_number()) return activitypub.OrderedCollectionPage( - id='%s?page=%s' % (remote_id, page), + id="%s?page=%s" % (remote_id, page), partOf=remote_id, orderedItems=items, next=next_page, - prev=prev_page + prev=prev_page, ) diff --git a/bookwyrm/models/attachment.py b/bookwyrm/models/attachment.py index e3450a5ad..0cd2c111f 100644 --- a/bookwyrm/models/attachment.py +++ b/bookwyrm/models/attachment.py @@ -1,4 +1,4 @@ -''' media that is posted in the app ''' +""" media that is posted in the app """ from django.db import models from bookwyrm import activitypub @@ -8,23 +8,25 @@ from . import fields class Attachment(ActivitypubMixin, BookWyrmModel): - ''' an image (or, in the future, video etc) associated with a status ''' + """ an image (or, in the future, video etc) associated with a status """ + status = models.ForeignKey( - 'Status', - on_delete=models.CASCADE, - related_name='attachments', - null=True + "Status", on_delete=models.CASCADE, related_name="attachments", null=True ) reverse_unfurl = True + class Meta: - ''' one day we'll have other types of attachments besides images ''' + """ one day we'll have other types of attachments besides images """ + abstract = True class Image(Attachment): - ''' an image attachment ''' + """ an image attachment """ + image = fields.ImageField( - upload_to='status/', null=True, blank=True, activitypub_field='url') - caption = fields.TextField(null=True, blank=True, activitypub_field='name') + upload_to="status/", null=True, blank=True, activitypub_field="url" + ) + caption = fields.TextField(null=True, blank=True, activitypub_field="name") activity_serializer = activitypub.Image diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index d0cb8d19b..4c5fe6c8f 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -1,4 +1,4 @@ -''' database schema for info about authors ''' +""" database schema for info about authors """ from django.db import models from bookwyrm import activitypub @@ -9,9 +9,11 @@ from . import fields class Author(BookDataModel): - ''' basic biographic info ''' + """ basic biographic info """ + wikipedia_link = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) # idk probably other keys would be useful here? born = fields.DateTimeField(blank=True, null=True) died = fields.DateTimeField(blank=True, null=True) @@ -22,7 +24,7 @@ class Author(BookDataModel): bio = fields.HtmlField(null=True, blank=True) def get_remote_id(self): - ''' editions and works both use "book" instead of model_name ''' - return 'https://%s/author/%s' % (DOMAIN, self.id) + """ editions and works both use "book" instead of model_name """ + return "https://%s/author/%s" % (DOMAIN, self.id) activity_serializer = activitypub.Author diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 7af487492..60e5da0ad 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -1,4 +1,4 @@ -''' base model with default fields ''' +""" base model with default fields """ from django.db import models from django.dispatch import receiver @@ -7,34 +7,36 @@ from .fields import RemoteIdField class BookWyrmModel(models.Model): - ''' shared fields ''' + """ shared fields """ + created_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now=True) - remote_id = RemoteIdField(null=True, activitypub_field='id') + remote_id = RemoteIdField(null=True, activitypub_field="id") def get_remote_id(self): - ''' generate a url that resolves to the local object ''' - base_path = 'https://%s' % DOMAIN - if hasattr(self, 'user'): - base_path = '%s%s' % (base_path, self.user.local_path) + """ generate a url that resolves to the local object """ + base_path = "https://%s" % DOMAIN + if hasattr(self, "user"): + base_path = "%s%s" % (base_path, self.user.local_path) model_name = type(self).__name__.lower() - return '%s/%s/%d' % (base_path, model_name, self.id) + return "%s/%s/%d" % (base_path, model_name, self.id) class Meta: - ''' this is just here to provide default fields for other models ''' + """ this is just here to provide default fields for other models """ + abstract = True @property def local_path(self): - ''' how to link to this object in the local app ''' - return self.get_remote_id().replace('https://%s' % DOMAIN, '') + """ how to link to this object in the local app """ + return self.get_remote_id().replace("https://%s" % DOMAIN, "") @receiver(models.signals.post_save) -#pylint: disable=unused-argument +# pylint: disable=unused-argument def execute_after_save(sender, instance, created, *args, **kwargs): - ''' set the remote_id after save (when the id is available) ''' - if not created or not hasattr(instance, 'get_remote_id'): + """ set the remote_id after save (when the id is available) """ + if not created or not hasattr(instance, "get_remote_id"): return if not instance.remote_id: instance.remote_id = instance.get_remote_id() diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 84bfbc6bd..66b539bbc 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -1,4 +1,4 @@ -''' database schema for books and shelves ''' +""" database schema for books and shelves """ import re from django.db import models @@ -11,25 +11,30 @@ from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin from .base_model import BookWyrmModel from . import fields + class BookDataModel(ObjectMixin, BookWyrmModel): - ''' fields shared between editable book data (books, works, authors) ''' + """ fields shared between editable book data (books, works, authors) """ + origin_id = models.CharField(max_length=255, null=True, blank=True) openlibrary_key = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) librarything_key = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) goodreads_key = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) - last_edited_by = models.ForeignKey( - 'User', on_delete=models.PROTECT, null=True) + last_edited_by = models.ForeignKey("User", on_delete=models.PROTECT, null=True) class Meta: - ''' can't initialize this model, that wouldn't make sense ''' + """ can't initialize this model, that wouldn't make sense """ + abstract = True def save(self, *args, **kwargs): - ''' ensure that the remote_id is within this instance ''' + """ ensure that the remote_id is within this instance """ if self.id: self.remote_id = self.get_remote_id() else: @@ -37,15 +42,15 @@ class BookDataModel(ObjectMixin, BookWyrmModel): self.remote_id = None return super().save(*args, **kwargs) - def broadcast(self, activity, sender, software='bookwyrm'): - ''' only send book data updates to other bookwyrm instances ''' + def broadcast(self, activity, sender, software="bookwyrm"): + """ only send book data updates to other bookwyrm instances """ super().broadcast(activity, sender, software=software) class Book(BookDataModel): - ''' a generic book, which can mean either an edition or a work ''' - connector = models.ForeignKey( - 'Connector', on_delete=models.PROTECT, null=True) + """ a generic book, which can mean either an edition or a work """ + + connector = models.ForeignKey("Connector", on_delete=models.PROTECT, null=True) # book/work metadata title = fields.CharField(max_length=255) @@ -63,9 +68,10 @@ class Book(BookDataModel): subject_places = fields.ArrayField( models.CharField(max_length=255), blank=True, null=True, default=list ) - authors = fields.ManyToManyField('Author') + authors = fields.ManyToManyField("Author") cover = fields.ImageField( - upload_to='covers/', blank=True, null=True, alt_field='alt_text') + upload_to="covers/", blank=True, null=True, alt_field="alt_text" + ) first_published_date = fields.DateTimeField(blank=True, null=True) published_date = fields.DateTimeField(blank=True, null=True) @@ -73,42 +79,43 @@ class Book(BookDataModel): @property def author_text(self): - ''' format a list of authors ''' - return ', '.join(a.name for a in self.authors.all()) + """ format a list of authors """ + return ", ".join(a.name for a in self.authors.all()) @property def latest_readthrough(self): - ''' most recent readthrough activity ''' - return self.readthrough_set.order_by('-updated_date').first() + """ most recent readthrough activity """ + return self.readthrough_set.order_by("-updated_date").first() @property def edition_info(self): - ''' properties of this edition, as a string ''' + """ 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' else None, + self.physical_format if hasattr(self, "physical_format") else None, + self.languages[0] + " language" + if self.languages and self.languages[0] != "English" + else None, str(self.published_date.year) if self.published_date else None, ] - return ', '.join(i for i in items if i) + return ", ".join(i for i in items if i) @property def alt_text(self): - ''' image alt test ''' - text = '%s' % self.title + """ image alt test """ + text = "%s" % self.title if self.edition_info: - text += ' (%s)' % self.edition_info + text += " (%s)" % self.edition_info return text def save(self, *args, **kwargs): - ''' can't be abstract for query reasons, but you shouldn't USE it ''' + """ can't be abstract for query reasons, but you shouldn't USE it """ if not isinstance(self, Edition) and not isinstance(self, Work): - raise ValueError('Books should be added as Editions or Works') + raise ValueError("Books should be added as Editions or Works") return super().save(*args, **kwargs) def get_remote_id(self): - ''' editions and works both use "book" instead of model_name ''' - return 'https://%s/book/%d' % (DOMAIN, self.id) + """ editions and works both use "book" instead of model_name """ + return "https://%s/book/%d" % (DOMAIN, self.id) def __repr__(self): return "<{} key={!r} title={!r}>".format( @@ -119,76 +126,82 @@ class Book(BookDataModel): class Work(OrderedCollectionPageMixin, Book): - ''' a work (an abstract concept of a book that manifests in an edition) ''' + """ a work (an abstract concept of a book that manifests in an edition) """ + # library of congress catalog control number lccn = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) # this has to be nullable but should never be null default_edition = fields.ForeignKey( - 'Edition', - on_delete=models.PROTECT, - null=True, - load_remote=False + "Edition", on_delete=models.PROTECT, null=True, load_remote=False ) def save(self, *args, **kwargs): - ''' set some fields on the edition object ''' + """ set some fields on the edition object """ # set rank for edition in self.editions.all(): edition.save() return super().save(*args, **kwargs) def get_default_edition(self): - ''' in case the default edition is not set ''' - return self.default_edition or self.editions.order_by( - '-edition_rank' - ).first() + """ in case the default edition is not set """ + return self.default_edition or self.editions.order_by("-edition_rank").first() def to_edition_list(self, **kwargs): - ''' an ordered collection of editions ''' + """ an ordered collection of editions """ return self.to_ordered_collection( - self.editions.order_by('-edition_rank').all(), - remote_id='%s/editions' % self.remote_id, + self.editions.order_by("-edition_rank").all(), + remote_id="%s/editions" % self.remote_id, **kwargs ) activity_serializer = activitypub.Work - serialize_reverse_fields = [('editions', 'editions', '-edition_rank')] - deserialize_reverse_fields = [('editions', 'editions')] + serialize_reverse_fields = [("editions", "editions", "-edition_rank")] + deserialize_reverse_fields = [("editions", "editions")] class Edition(Book): - ''' an edition of a book ''' + """ an edition of a book """ + # these identifiers only apply to editions, not works isbn_10 = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) isbn_13 = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) oclc_number = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) asin = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) + max_length=255, blank=True, null=True, deduplication_field=True + ) pages = fields.IntegerField(blank=True, null=True) physical_format = fields.CharField(max_length=255, blank=True, null=True) publishers = fields.ArrayField( models.CharField(max_length=255), blank=True, default=list ) shelves = models.ManyToManyField( - 'Shelf', + "Shelf", symmetrical=False, - through='ShelfBook', - through_fields=('book', 'shelf') + through="ShelfBook", + through_fields=("book", "shelf"), ) parent_work = fields.ForeignKey( - 'Work', on_delete=models.PROTECT, null=True, - related_name='editions', activitypub_field='work') + "Work", + on_delete=models.PROTECT, + null=True, + related_name="editions", + activitypub_field="work", + ) edition_rank = fields.IntegerField(default=0) activity_serializer = activitypub.Edition - name_field = 'title' + name_field = "title" def get_rank(self): - ''' calculate how complete the data is on this edition ''' + """ calculate how complete the data is on this edition """ if self.parent_work and self.parent_work.default_edition == self: # default edition has the highest rank return 20 @@ -204,9 +217,9 @@ class Edition(Book): return rank def save(self, *args, **kwargs): - ''' set some fields on the edition object ''' + """ set some fields on the edition object """ # calculate isbn 10/13 - if self.isbn_13 and self.isbn_13[:3] == '978' and not self.isbn_10: + if self.isbn_13 and self.isbn_13[:3] == "978" and not self.isbn_10: self.isbn_10 = isbn_13_to_10(self.isbn_13) if self.isbn_10 and not self.isbn_13: self.isbn_13 = isbn_10_to_13(self.isbn_10) @@ -218,17 +231,18 @@ class Edition(Book): def isbn_10_to_13(isbn_10): - ''' convert an isbn 10 into an isbn 13 ''' - isbn_10 = re.sub(r'[^0-9X]', '', isbn_10) + """ convert an isbn 10 into an isbn 13 """ + isbn_10 = re.sub(r"[^0-9X]", "", isbn_10) # drop the last character of the isbn 10 number (the original checkdigit) converted = isbn_10[:9] # add "978" to the front - converted = '978' + converted + converted = "978" + converted # add a check digit to the end # multiply the odd digits by 1 and the even digits by 3 and sum them try: - checksum = sum(int(i) for i in converted[::2]) + \ - sum(int(i) * 3 for i in converted[1::2]) + checksum = sum(int(i) for i in converted[::2]) + sum( + int(i) * 3 for i in converted[1::2] + ) except ValueError: return None # add the checksum mod 10 to the end @@ -239,11 +253,11 @@ def isbn_10_to_13(isbn_10): def isbn_13_to_10(isbn_13): - ''' convert isbn 13 to 10, if possible ''' - if isbn_13[:3] != '978': + """ convert isbn 13 to 10, if possible """ + if isbn_13[:3] != "978": return None - isbn_13 = re.sub(r'[^0-9X]', '', isbn_13) + isbn_13 = re.sub(r"[^0-9X]", "", isbn_13) # remove '978' and old checkdigit converted = isbn_13[3:-1] @@ -256,5 +270,5 @@ def isbn_13_to_10(isbn_13): checkdigit = checksum % 11 checkdigit = 11 - checkdigit if checkdigit == 10: - checkdigit = 'X' + checkdigit = "X" return converted + str(checkdigit) diff --git a/bookwyrm/models/connector.py b/bookwyrm/models/connector.py index c1fbf58bc..11bdbee20 100644 --- a/bookwyrm/models/connector.py +++ b/bookwyrm/models/connector.py @@ -1,21 +1,21 @@ -''' manages interfaces with external sources of book data ''' +""" manages interfaces with external sources of book data """ from django.db import models from bookwyrm.connectors.settings import CONNECTORS from .base_model import BookWyrmModel -ConnectorFiles = models.TextChoices('ConnectorFiles', CONNECTORS) +ConnectorFiles = models.TextChoices("ConnectorFiles", CONNECTORS) + + class Connector(BookWyrmModel): - ''' book data source connectors ''' + """ book data source connectors """ + identifier = models.CharField(max_length=255, unique=True) priority = models.IntegerField(default=2) name = models.CharField(max_length=255, null=True, blank=True) local = models.BooleanField(default=False) - connector_file = models.CharField( - max_length=255, - choices=ConnectorFiles.choices - ) + connector_file = models.CharField(max_length=255, choices=ConnectorFiles.choices) api_key = models.CharField(max_length=255, null=True, blank=True) base_url = models.CharField(max_length=255) @@ -24,7 +24,7 @@ class Connector(BookWyrmModel): search_url = models.CharField(max_length=255, null=True, blank=True) isbn_search_url = models.CharField(max_length=255, null=True, blank=True) - politeness_delay = models.IntegerField(null=True, blank=True) #seconds + politeness_delay = models.IntegerField(null=True, blank=True) # seconds max_query_count = models.IntegerField(null=True, blank=True) # how many queries executed in a unit of time, like a day query_count = models.IntegerField(default=0) @@ -32,11 +32,12 @@ class Connector(BookWyrmModel): query_count_expiry = models.DateTimeField(auto_now_add=True, blank=True) class Meta: - ''' check that there's code to actually use this connector ''' + """ check that there's code to actually use this connector """ + constraints = [ models.CheckConstraint( check=models.Q(connector_file__in=ConnectorFiles), - name='connector_file_valid' + name="connector_file_valid", ) ] diff --git a/bookwyrm/models/favorite.py b/bookwyrm/models/favorite.py index d34cbcba8..7b72d175f 100644 --- a/bookwyrm/models/favorite.py +++ b/bookwyrm/models/favorite.py @@ -1,4 +1,4 @@ -''' like/fav/star a status ''' +""" like/fav/star a status """ from django.apps import apps from django.db import models from django.utils import timezone @@ -9,50 +9,59 @@ from .base_model import BookWyrmModel from . import fields from .status import Status + class Favorite(ActivityMixin, BookWyrmModel): - ''' fav'ing a post ''' + """ fav'ing a post """ + user = fields.ForeignKey( - 'User', on_delete=models.PROTECT, activitypub_field='actor') + "User", on_delete=models.PROTECT, activitypub_field="actor" + ) status = fields.ForeignKey( - 'Status', on_delete=models.PROTECT, activitypub_field='object') + "Status", on_delete=models.PROTECT, activitypub_field="object" + ) activity_serializer = activitypub.Like @classmethod def ignore_activity(cls, activity): - ''' don't bother with incoming favs of unknown statuses ''' + """ don't bother with incoming favs of unknown statuses """ return not Status.objects.filter(remote_id=activity.object).exists() def save(self, *args, **kwargs): - ''' update user active time ''' + """ update user active time """ self.user.last_active_date = timezone.now() self.user.save(broadcast=False) super().save(*args, **kwargs) if self.status.user.local and self.status.user != self.user: notification_model = apps.get_model( - 'bookwyrm.Notification', require_ready=True) + "bookwyrm.Notification", require_ready=True + ) notification_model.objects.create( user=self.status.user, - notification_type='FAVORITE', + notification_type="FAVORITE", related_user=self.user, - related_status=self.status + related_status=self.status, ) def delete(self, *args, **kwargs): - ''' delete and delete notifications ''' + """ delete and delete notifications """ # check for notification if self.status.user.local: notification_model = apps.get_model( - 'bookwyrm.Notification', require_ready=True) + "bookwyrm.Notification", require_ready=True + ) notification = notification_model.objects.filter( - user=self.status.user, related_user=self.user, - related_status=self.status, notification_type='FAVORITE' + user=self.status.user, + related_user=self.user, + related_status=self.status, + notification_type="FAVORITE", ).first() if notification: notification.delete() super().delete(*args, **kwargs) class Meta: - ''' can't fav things twice ''' - unique_together = ('user', 'status') + """ can't fav things twice """ + + unique_together = ("user", "status") diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index 953cd9c8a..ce8043102 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -1,15 +1,17 @@ -''' connections to external ActivityPub servers ''' +""" connections to external ActivityPub servers """ from django.db import models from .base_model import BookWyrmModel class FederatedServer(BookWyrmModel): - ''' store which server's we federate with ''' + """ store which server's we federate with """ + server_name = models.CharField(max_length=255, unique=True) # federated, blocked, whatever else - status = models.CharField(max_length=255, default='federated') + status = models.CharField(max_length=255, default="federated") # is it mastodon, bookwyrm, etc application_type = models.CharField(max_length=255, null=True) application_version = models.CharField(max_length=255, null=True) + # TODO: blocked servers diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 4ea527eba..1ca0b377b 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -1,4 +1,4 @@ -''' activitypub-aware django model fields ''' +""" activitypub-aware django model fields """ from dataclasses import MISSING import re from uuid import uuid4 @@ -18,37 +18,43 @@ from bookwyrm.settings import DOMAIN def validate_remote_id(value): - ''' make sure the remote_id looks like a url ''' - if not value or not re.match(r'^http.?:\/\/[^\s]+$', value): + """ make sure the remote_id looks like a url """ + if not value or not re.match(r"^http.?:\/\/[^\s]+$", value): raise ValidationError( - _('%(value)s is not a valid remote_id'), - params={'value': value}, + _("%(value)s is not a valid remote_id"), + params={"value": value}, ) def validate_localname(value): - ''' make sure localnames look okay ''' - if not re.match(r'^[A-Za-z\-_\.0-9]+$', value): + """ make sure localnames look okay """ + if not re.match(r"^[A-Za-z\-_\.0-9]+$", value): raise ValidationError( - _('%(value)s is not a valid username'), - params={'value': value}, + _("%(value)s is not a valid username"), + params={"value": value}, ) def validate_username(value): - ''' make sure usernames look okay ''' - if not re.match(r'^[A-Za-z\-_\.0-9]+@[A-Za-z\-_\.0-9]+\.[a-z]{2,}$', value): + """ make sure usernames look okay """ + if not re.match(r"^[A-Za-z\-_\.0-9]+@[A-Za-z\-_\.0-9]+\.[a-z]{2,}$", value): raise ValidationError( - _('%(value)s is not a valid username'), - params={'value': value}, + _("%(value)s is not a valid username"), + params={"value": value}, ) class ActivitypubFieldMixin: - ''' make a database field serializable ''' - def __init__(self, *args, \ - activitypub_field=None, activitypub_wrapper=None, - deduplication_field=False, **kwargs): + """ make a database field serializable """ + + def __init__( + self, + *args, + activitypub_field=None, + activitypub_wrapper=None, + deduplication_field=False, + **kwargs + ): self.deduplication_field = deduplication_field if activitypub_wrapper: self.activitypub_wrapper = activitypub_field @@ -57,24 +63,22 @@ class ActivitypubFieldMixin: self.activitypub_field = activitypub_field super().__init__(*args, **kwargs) - def set_field_from_activity(self, instance, data): - ''' helper function for assinging a value to the field ''' + """ helper function for assinging a value to the field """ try: value = getattr(data, self.get_activitypub_field()) except AttributeError: # masssively hack-y workaround for boosts - if self.get_activitypub_field() != 'attributedTo': + if self.get_activitypub_field() != "attributedTo": raise - value = getattr(data, 'actor') + value = getattr(data, "actor") formatted = self.field_from_activity(value) if formatted is None or formatted is MISSING: return setattr(instance, self.name, formatted) - def set_activity_from_field(self, activity, instance): - ''' update the json object ''' + """ update the json object """ value = getattr(instance, self.name) formatted = self.field_to_activity(value) if formatted is None: @@ -82,37 +86,37 @@ class ActivitypubFieldMixin: key = self.get_activitypub_field() # TODO: surely there's a better way - if instance.__class__.__name__ == 'Boost' and key == 'attributedTo': - key = 'actor' + if instance.__class__.__name__ == "Boost" and key == "attributedTo": + key = "actor" if isinstance(activity.get(key), list): activity[key] += formatted else: activity[key] = formatted - def field_to_activity(self, value): - ''' formatter to convert a model value into activitypub ''' - if hasattr(self, 'activitypub_wrapper'): + """ formatter to convert a model value into activitypub """ + if hasattr(self, "activitypub_wrapper"): return {self.activitypub_wrapper: value} return value def field_from_activity(self, value): - ''' formatter to convert activitypub into a model value ''' - if hasattr(self, 'activitypub_wrapper'): + """ formatter to convert activitypub into a model value """ + if hasattr(self, "activitypub_wrapper"): value = value.get(self.activitypub_wrapper) return value def get_activitypub_field(self): - ''' model_field_name to activitypubFieldName ''' + """ model_field_name to activitypubFieldName """ if self.activitypub_field: return self.activitypub_field - name = self.name.split('.')[-1] - components = name.split('_') - return components[0] + ''.join(x.title() for x in components[1:]) + name = self.name.split(".")[-1] + components = name.split("_") + return components[0] + "".join(x.title() for x in components[1:]) class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): - ''' default (de)serialization for foreign key and one to one ''' + """ default (de)serialization for foreign key and one to one """ + def __init__(self, *args, load_remote=True, **kwargs): self.load_remote = load_remote super().__init__(*args, **kwargs) @@ -122,7 +126,7 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): return None related_model = self.related_model - if hasattr(value, 'id') and value.id: + if hasattr(value, "id") and value.id: if not self.load_remote: # only look in the local database return related_model.find_existing(value.serialize()) @@ -142,99 +146,98 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): class RemoteIdField(ActivitypubFieldMixin, models.CharField): - ''' a url that serves as a unique identifier ''' + """ a url that serves as a unique identifier """ + def __init__(self, *args, max_length=255, validators=None, **kwargs): validators = validators or [validate_remote_id] - super().__init__( - *args, max_length=max_length, validators=validators, - **kwargs - ) + super().__init__(*args, max_length=max_length, validators=validators, **kwargs) # for this field, the default is true. false everywhere else. - self.deduplication_field = kwargs.get('deduplication_field', True) + self.deduplication_field = kwargs.get("deduplication_field", True) class UsernameField(ActivitypubFieldMixin, models.CharField): - ''' activitypub-aware username field ''' - def __init__(self, activitypub_field='preferredUsername', **kwargs): + """ activitypub-aware username field """ + + def __init__(self, activitypub_field="preferredUsername", **kwargs): self.activitypub_field = activitypub_field # I don't totally know why pylint is mad at this, but it makes it work - super( #pylint: disable=bad-super-call - ActivitypubFieldMixin, self - ).__init__( - _('username'), + super(ActivitypubFieldMixin, self).__init__( # pylint: disable=bad-super-call + _("username"), max_length=150, unique=True, validators=[validate_username], error_messages={ - 'unique': _('A user with that username already exists.'), + "unique": _("A user with that username already exists."), }, ) def deconstruct(self): - ''' implementation of models.Field deconstruct ''' + """ implementation of models.Field deconstruct """ name, path, args, kwargs = super().deconstruct() - del kwargs['verbose_name'] - del kwargs['max_length'] - del kwargs['unique'] - del kwargs['validators'] - del kwargs['error_messages'] + del kwargs["verbose_name"] + del kwargs["max_length"] + del kwargs["unique"] + del kwargs["validators"] + del kwargs["error_messages"] return name, path, args, kwargs def field_to_activity(self, value): - return value.split('@')[0] + return value.split("@")[0] -PrivacyLevels = models.TextChoices('Privacy', [ - 'public', - 'unlisted', - 'followers', - 'direct' -]) +PrivacyLevels = models.TextChoices( + "Privacy", ["public", "unlisted", "followers", "direct"] +) + class PrivacyField(ActivitypubFieldMixin, models.CharField): - ''' this maps to two differente activitypub fields ''' - public = 'https://www.w3.org/ns/activitystreams#Public' + """ this maps to two differente activitypub fields """ + + public = "https://www.w3.org/ns/activitystreams#Public" + def __init__(self, *args, **kwargs): super().__init__( - *args, max_length=255, - choices=PrivacyLevels.choices, default='public') + *args, max_length=255, choices=PrivacyLevels.choices, default="public" + ) def set_field_from_activity(self, instance, data): to = data.to cc = data.cc if to == [self.public]: - setattr(instance, self.name, 'public') + setattr(instance, self.name, "public") elif cc == []: - setattr(instance, self.name, 'direct') + setattr(instance, self.name, "direct") elif self.public in cc: - setattr(instance, self.name, 'unlisted') + setattr(instance, self.name, "unlisted") else: - setattr(instance, self.name, 'followers') + setattr(instance, self.name, "followers") def set_activity_from_field(self, activity, instance): # explicitly to anyone mentioned (statuses only) mentions = [] - if hasattr(instance, 'mention_users'): + if hasattr(instance, "mention_users"): mentions = [u.remote_id for u in instance.mention_users.all()] # this is a link to the followers list - followers = instance.user.__class__._meta.get_field('followers')\ - .field_to_activity(instance.user.followers) - if instance.privacy == 'public': - activity['to'] = [self.public] - activity['cc'] = [followers] + mentions - elif instance.privacy == 'unlisted': - activity['to'] = [followers] - activity['cc'] = [self.public] + mentions - elif instance.privacy == 'followers': - activity['to'] = [followers] - activity['cc'] = mentions - if instance.privacy == 'direct': - activity['to'] = mentions - activity['cc'] = [] + followers = instance.user.__class__._meta.get_field( + "followers" + ).field_to_activity(instance.user.followers) + if instance.privacy == "public": + activity["to"] = [self.public] + activity["cc"] = [followers] + mentions + elif instance.privacy == "unlisted": + activity["to"] = [followers] + activity["cc"] = [self.public] + mentions + elif instance.privacy == "followers": + activity["to"] = [followers] + activity["cc"] = mentions + if instance.privacy == "direct": + activity["to"] = mentions + activity["cc"] = [] class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey): - ''' activitypub-aware foreign key field ''' + """ activitypub-aware foreign key field """ + def field_to_activity(self, value): if not value: return None @@ -242,7 +245,8 @@ class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey): class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField): - ''' activitypub-aware foreign key field ''' + """ activitypub-aware foreign key field """ + def field_to_activity(self, value): if not value: return None @@ -250,13 +254,14 @@ class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField): class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): - ''' activitypub-aware many to many field ''' + """ activitypub-aware many to many field """ + def __init__(self, *args, link_only=False, **kwargs): self.link_only = link_only super().__init__(*args, **kwargs) def set_field_from_activity(self, instance, data): - ''' helper function for assinging a value to the field ''' + """ helper function for assinging a value to the field """ value = getattr(data, self.get_activitypub_field()) formatted = self.field_from_activity(value) if formatted is None or formatted is MISSING: @@ -266,7 +271,7 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): def field_to_activity(self, value): if self.link_only: - return '%s/%s' % (value.instance.remote_id, self.name) + return "%s/%s" % (value.instance.remote_id, self.name) return [i.remote_id for i in value.all()] def field_from_activity(self, value): @@ -279,29 +284,31 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): except ValidationError: continue items.append( - activitypub.resolve_remote_id( - remote_id, model=self.related_model) + activitypub.resolve_remote_id(remote_id, model=self.related_model) ) return items class TagField(ManyToManyField): - ''' special case of many to many that uses Tags ''' + """ special case of many to many that uses Tags """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.activitypub_field = 'tag' + self.activitypub_field = "tag" def field_to_activity(self, value): tags = [] for item in value.all(): activity_type = item.__class__.__name__ - if activity_type == 'User': - activity_type = 'Mention' - tags.append(activitypub.Link( - href=item.remote_id, - name=getattr(item, item.name_field), - type=activity_type - )) + if activity_type == "User": + activity_type = "Mention" + tags.append( + activitypub.Link( + href=item.remote_id, + name=getattr(item, item.name_field), + type=activity_type, + ) + ) return tags def field_from_activity(self, value): @@ -310,38 +317,38 @@ class TagField(ManyToManyField): items = [] for link_json in value: link = activitypub.Link(**link_json) - tag_type = link.type if link.type != 'Mention' else 'Person' - if tag_type == 'Book': - tag_type = 'Edition' + tag_type = link.type if link.type != "Mention" else "Person" + if tag_type == "Book": + tag_type = "Edition" if tag_type != self.related_model.activity_serializer.type: # tags can contain multiple types continue items.append( - activitypub.resolve_remote_id( - link.href, model=self.related_model) + activitypub.resolve_remote_id(link.href, model=self.related_model) ) return items def image_serializer(value, alt): - ''' helper for serializing images ''' - if value and hasattr(value, 'url'): + """ helper for serializing images """ + if value and hasattr(value, "url"): url = value.url else: return None - url = 'https://%s%s' % (DOMAIN, url) + url = "https://%s%s" % (DOMAIN, url) return activitypub.Image(url=url, name=alt) class ImageField(ActivitypubFieldMixin, models.ImageField): - ''' activitypub-aware image field ''' + """ activitypub-aware image field """ + def __init__(self, *args, alt_field=None, **kwargs): self.alt_field = alt_field super().__init__(*args, **kwargs) # pylint: disable=arguments-differ def set_field_from_activity(self, instance, data, save=True): - ''' helper function for assinging a value to the field ''' + """ helper function for assinging a value to the field """ value = getattr(data, self.get_activitypub_field()) formatted = self.field_from_activity(value) if formatted is None or formatted is MISSING: @@ -358,16 +365,14 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): key = self.get_activitypub_field() activity[key] = formatted - def field_to_activity(self, value, alt=None): return image_serializer(value, alt) - def field_from_activity(self, value): image_slug = value # when it's an inline image (User avatar/icon, Book cover), it's a json # blob, but when it's an attached image, it's just a url - if hasattr(image_slug, 'url'): + if hasattr(image_slug, "url"): url = image_slug.url elif isinstance(image_slug, str): url = image_slug @@ -383,13 +388,14 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): if not response: return None - image_name = str(uuid4()) + '.' + url.split('.')[-1] + image_name = str(uuid4()) + "." + url.split(".")[-1] image_content = ContentFile(response.content) return [image_name, image_content] class DateTimeField(ActivitypubFieldMixin, models.DateTimeField): - ''' activitypub-aware datetime field ''' + """ activitypub-aware datetime field """ + def field_to_activity(self, value): if not value: return None @@ -405,8 +411,10 @@ class DateTimeField(ActivitypubFieldMixin, models.DateTimeField): except (ParserError, TypeError): return None + class HtmlField(ActivitypubFieldMixin, models.TextField): - ''' a text field for storing html ''' + """ a text field for storing html """ + def field_from_activity(self, value): if not value or value == MISSING: return None @@ -414,19 +422,25 @@ class HtmlField(ActivitypubFieldMixin, models.TextField): sanitizer.feed(value) return sanitizer.get_output() + class ArrayField(ActivitypubFieldMixin, DjangoArrayField): - ''' activitypub-aware array field ''' + """ activitypub-aware array field """ + def field_to_activity(self, value): return [str(i) for i in value] + class CharField(ActivitypubFieldMixin, models.CharField): - ''' activitypub-aware char field ''' + """ activitypub-aware char field """ + class TextField(ActivitypubFieldMixin, models.TextField): - ''' activitypub-aware text field ''' + """ activitypub-aware text field """ + class BooleanField(ActivitypubFieldMixin, models.BooleanField): - ''' activitypub-aware boolean field ''' + """ activitypub-aware boolean field """ + class IntegerField(ActivitypubFieldMixin, models.IntegerField): - ''' activitypub-aware boolean field ''' + """ activitypub-aware boolean field """ diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index ca05ddb08..31dccda8f 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -1,4 +1,4 @@ -''' track progress of goodreads imports ''' +""" track progress of goodreads imports """ import re import dateutil.parser @@ -14,13 +14,14 @@ from .fields import PrivacyLevels # Mapping goodreads -> bookwyrm shelf titles. GOODREADS_SHELVES = { - 'read': 'read', - 'currently-reading': 'reading', - 'to-read': 'to-read', + "read": "read", + "currently-reading": "reading", + "to-read": "to-read", } + def unquote_string(text): - ''' resolve csv quote weirdness ''' + """ resolve csv quote weirdness """ match = re.match(r'="([^"]*)"', text) if match: return match.group(1) @@ -28,63 +29,57 @@ def unquote_string(text): def construct_search_term(title, author): - ''' formulate a query for the data connector ''' + """ formulate a query for the data connector """ # Strip brackets (usually series title from search term) - title = re.sub(r'\s*\([^)]*\)\s*', '', title) + title = re.sub(r"\s*\([^)]*\)\s*", "", title) # Open library doesn't like including author initials in search term. - author = re.sub(r'(\w\.)+\s*', '', author) + author = re.sub(r"(\w\.)+\s*", "", author) - return ' '.join([title, author]) + return " ".join([title, author]) class ImportJob(models.Model): - ''' entry for a specific request for book data import ''' + """ entry for a specific request for book data import """ + user = models.ForeignKey(User, on_delete=models.CASCADE) created_date = models.DateTimeField(default=timezone.now) task_id = models.CharField(max_length=100, null=True) include_reviews = models.BooleanField(default=True) complete = models.BooleanField(default=False) privacy = models.CharField( - max_length=255, - default='public', - choices=PrivacyLevels.choices + max_length=255, default="public", choices=PrivacyLevels.choices ) retry = models.BooleanField(default=False) def save(self, *args, **kwargs): - ''' save and notify ''' + """ save and notify """ super().save(*args, **kwargs) if self.complete: notification_model = apps.get_model( - 'bookwyrm.Notification', require_ready=True) + "bookwyrm.Notification", require_ready=True + ) notification_model.objects.create( user=self.user, - notification_type='IMPORT', + notification_type="IMPORT", related_import=self, ) class ImportItem(models.Model): - ''' a single line of a csv being imported ''' - job = models.ForeignKey( - ImportJob, - on_delete=models.CASCADE, - related_name='items') + """ a single line of a csv being imported """ + + job = models.ForeignKey(ImportJob, on_delete=models.CASCADE, related_name="items") index = models.IntegerField() data = JSONField() - book = models.ForeignKey( - Book, on_delete=models.SET_NULL, null=True, blank=True) + book = models.ForeignKey(Book, on_delete=models.SET_NULL, null=True, blank=True) fail_reason = models.TextField(null=True) def resolve(self): - ''' try various ways to lookup a book ''' - self.book = ( - self.get_book_from_isbn() or - self.get_book_from_title_author() - ) + """ try various ways to lookup a book """ + self.book = self.get_book_from_isbn() or self.get_book_from_title_author() def get_book_from_isbn(self): - ''' search by isbn ''' + """ search by isbn """ search_result = connector_manager.first_search_result( self.isbn, min_confidence=0.999 ) @@ -93,13 +88,9 @@ class ImportItem(models.Model): return search_result.connector.get_or_create_book(search_result.key) return None - def get_book_from_title_author(self): - ''' search by title and author ''' - search_term = construct_search_term( - self.title, - self.author - ) + """ search by title and author """ + search_term = construct_search_term(self.title, self.author) search_result = connector_manager.first_search_result( search_term, min_confidence=0.999 ) @@ -108,84 +99,85 @@ class ImportItem(models.Model): return search_result.connector.get_or_create_book(search_result.key) return None - @property def title(self): - ''' get the book title ''' - return self.data['Title'] + """ get the book title """ + return self.data["Title"] @property def author(self): - ''' get the book title ''' - return self.data['Author'] + """ get the book title """ + return self.data["Author"] @property def isbn(self): - ''' pulls out the isbn13 field from the csv line data ''' - return unquote_string(self.data['ISBN13']) + """ pulls out the isbn13 field from the csv line data """ + return unquote_string(self.data["ISBN13"]) @property def shelf(self): - ''' the goodreads shelf field ''' - if self.data['Exclusive Shelf']: - return GOODREADS_SHELVES.get(self.data['Exclusive Shelf']) + """ the goodreads shelf field """ + if self.data["Exclusive Shelf"]: + return GOODREADS_SHELVES.get(self.data["Exclusive Shelf"]) return None @property def review(self): - ''' a user-written review, to be imported with the book data ''' - return self.data['My Review'] + """ a user-written review, to be imported with the book data """ + return self.data["My Review"] @property def rating(self): - ''' x/5 star rating for a book ''' - return int(self.data['My Rating']) + """ x/5 star rating for a book """ + return int(self.data["My Rating"]) @property def date_added(self): - ''' when the book was added to this dataset ''' - if self.data['Date Added']: - return timezone.make_aware( - dateutil.parser.parse(self.data['Date Added'])) + """ when the book was added to this dataset """ + if self.data["Date Added"]: + return timezone.make_aware(dateutil.parser.parse(self.data["Date Added"])) return None @property def date_started(self): - ''' when the book was started ''' - if "Date Started" in self.data and self.data['Date Started']: - return timezone.make_aware( - dateutil.parser.parse(self.data['Date Started'])) + """ when the book was started """ + if "Date Started" in self.data and self.data["Date Started"]: + return timezone.make_aware(dateutil.parser.parse(self.data["Date Started"])) return None @property def date_read(self): - ''' the date a book was completed ''' - if self.data['Date Read']: - return timezone.make_aware( - dateutil.parser.parse(self.data['Date Read'])) + """ the date a book was completed """ + if self.data["Date Read"]: + return timezone.make_aware(dateutil.parser.parse(self.data["Date Read"])) return None @property def reads(self): - ''' formats a read through dataset for the book in this line ''' + """ formats a read through dataset for the book in this line """ start_date = self.date_started # Goodreads special case (no 'date started' field) - if ((self.shelf == 'reading' or (self.shelf == 'read' and self.date_read)) - and self.date_added and not start_date): + if ( + (self.shelf == "reading" or (self.shelf == "read" and self.date_read)) + and self.date_added + and not start_date + ): start_date = self.date_added - if (start_date and start_date is not None and not self.date_read): + if start_date and start_date is not None and not self.date_read: return [ReadThrough(start_date=start_date)] if self.date_read: - return [ReadThrough( - start_date=start_date, - finish_date=self.date_read, - )] + return [ + ReadThrough( + start_date=start_date, + finish_date=self.date_read, + ) + ] return [] def __repr__(self): - return "<{!r}Item {!r}>".format(self.data['import_source'], self.data['Title']) + return "<{!r}Item {!r}>".format(self.data["import_source"], self.data["Title"]) def __str__(self): - return "{} by {}".format(self.data['Title'], self.data['Author']) + return "{} by {}".format(self.data["Title"], self.data["Author"]) diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index 1b14c2aa5..a05325f3f 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -1,4 +1,4 @@ -''' make a list of books!! ''' +""" make a list of books!! """ from django.apps import apps from django.db import models @@ -9,86 +9,89 @@ from .base_model import BookWyrmModel from . import fields -CurationType = models.TextChoices('Curation', [ - 'closed', - 'open', - 'curated', -]) +CurationType = models.TextChoices( + "Curation", + [ + "closed", + "open", + "curated", + ], +) + class List(OrderedCollectionMixin, BookWyrmModel): - ''' a list of books ''' + """ a list of books """ + name = fields.CharField(max_length=100) user = fields.ForeignKey( - 'User', on_delete=models.PROTECT, activitypub_field='owner') - description = fields.TextField( - blank=True, null=True, activitypub_field='summary') + "User", on_delete=models.PROTECT, activitypub_field="owner" + ) + description = fields.TextField(blank=True, null=True, activitypub_field="summary") privacy = fields.PrivacyField() curation = fields.CharField( - max_length=255, - default='closed', - choices=CurationType.choices + max_length=255, default="closed", choices=CurationType.choices ) books = models.ManyToManyField( - 'Edition', + "Edition", symmetrical=False, - through='ListItem', - through_fields=('book_list', 'book'), + through="ListItem", + through_fields=("book_list", "book"), ) activity_serializer = activitypub.BookList def get_remote_id(self): - ''' don't want the user to be in there in this case ''' - return 'https://%s/list/%d' % (DOMAIN, self.id) + """ don't want the user to be in there in this case """ + return "https://%s/list/%d" % (DOMAIN, self.id) @property def collection_queryset(self): - ''' list of books for this shelf, overrides OrderedCollectionMixin ''' - return self.books.filter( - listitem__approved=True - ).all().order_by('listitem') + """ list of books for this shelf, overrides OrderedCollectionMixin """ + return self.books.filter(listitem__approved=True).all().order_by("listitem") class Meta: - ''' default sorting ''' - ordering = ('-updated_date',) + """ default sorting """ + + ordering = ("-updated_date",) class ListItem(CollectionItemMixin, BookWyrmModel): - ''' ok ''' + """ ok """ + book = fields.ForeignKey( - 'Edition', on_delete=models.PROTECT, activitypub_field='object') + "Edition", on_delete=models.PROTECT, activitypub_field="object" + ) book_list = fields.ForeignKey( - 'List', on_delete=models.CASCADE, activitypub_field='target') + "List", on_delete=models.CASCADE, activitypub_field="target" + ) user = fields.ForeignKey( - 'User', - on_delete=models.PROTECT, - activitypub_field='actor' + "User", on_delete=models.PROTECT, activitypub_field="actor" ) notes = fields.TextField(blank=True, null=True) approved = models.BooleanField(default=True) order = fields.IntegerField(blank=True, null=True) - endorsement = models.ManyToManyField('User', related_name='endorsers') + endorsement = models.ManyToManyField("User", related_name="endorsers") activity_serializer = activitypub.Add - object_field = 'book' - collection_field = 'book_list' + object_field = "book" + collection_field = "book_list" def save(self, *args, **kwargs): - ''' create a notification too ''' + """ create a notification too """ created = not bool(self.id) super().save(*args, **kwargs) list_owner = self.book_list.user # create a notification if somoene ELSE added to a local user's list if created and list_owner.local and list_owner != self.user: - model = apps.get_model('bookwyrm.Notification', require_ready=True) + model = apps.get_model("bookwyrm.Notification", require_ready=True) model.objects.create( user=list_owner, related_user=self.user, related_list_item=self, - notification_type='ADD', + notification_type="ADD", ) - class Meta: - ''' an opinionated constraint! you can't put a book on a list twice ''' - unique_together = ('book', 'book_list') - ordering = ('-created_date',) + """ an opinionated constraint! you can't put a book on a list twice """ + + unique_together = ("book", "book_list") + ordering = ("-created_date",) diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 0470b3258..190181657 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -1,47 +1,50 @@ -''' alert a user to activity ''' +""" alert a user to activity """ from django.db import models from .base_model import BookWyrmModel NotificationType = models.TextChoices( - 'NotificationType', - 'FAVORITE REPLY MENTION TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT ADD') + "NotificationType", + "FAVORITE REPLY MENTION TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT ADD", +) + class Notification(BookWyrmModel): - ''' you've been tagged, liked, followed, etc ''' - user = models.ForeignKey('User', on_delete=models.CASCADE) - related_book = models.ForeignKey( - 'Edition', on_delete=models.CASCADE, null=True) + """ you've been tagged, liked, followed, etc """ + + user = models.ForeignKey("User", on_delete=models.CASCADE) + related_book = models.ForeignKey("Edition", on_delete=models.CASCADE, null=True) related_user = models.ForeignKey( - 'User', - on_delete=models.CASCADE, null=True, related_name='related_user') - related_status = models.ForeignKey( - 'Status', on_delete=models.CASCADE, null=True) - related_import = models.ForeignKey( - 'ImportJob', on_delete=models.CASCADE, null=True) + "User", on_delete=models.CASCADE, null=True, related_name="related_user" + ) + related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True) + related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) related_list_item = models.ForeignKey( - 'ListItem', on_delete=models.CASCADE, null=True) + "ListItem", on_delete=models.CASCADE, null=True + ) read = models.BooleanField(default=False) notification_type = models.CharField( - max_length=255, choices=NotificationType.choices) + max_length=255, choices=NotificationType.choices + ) def save(self, *args, **kwargs): - ''' save, but don't make dupes ''' + """ save, but don't make dupes """ # there's probably a better way to do this if self.__class__.objects.filter( - user=self.user, - related_book=self.related_book, - related_user=self.related_user, - related_status=self.related_status, - related_import=self.related_import, - related_list_item=self.related_list_item, - notification_type=self.notification_type, - ).exists(): + user=self.user, + related_book=self.related_book, + related_user=self.related_user, + related_status=self.related_status, + related_import=self.related_import, + related_list_item=self.related_list_item, + notification_type=self.notification_type, + ).exists(): return super().save(*args, **kwargs) class Meta: - ''' checks if notifcation is in enum list for valid types ''' + """ checks if notifcation is in enum list for valid types """ + constraints = [ models.CheckConstraint( check=models.Q(notification_type__in=NotificationType.values), diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index 2bec3a818..3445573c4 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -1,35 +1,32 @@ -''' progress in a book ''' +""" progress in a book """ from django.db import models from django.utils import timezone from django.core import validators from .base_model import BookWyrmModel + class ProgressMode(models.TextChoices): - PAGE = 'PG', 'page' - PERCENT = 'PCT', 'percent' + PAGE = "PG", "page" + PERCENT = "PCT", "percent" + class ReadThrough(BookWyrmModel): - ''' Store a read through a book in the database. ''' - user = models.ForeignKey('User', on_delete=models.PROTECT) - book = models.ForeignKey('Edition', on_delete=models.PROTECT) + """ Store a read through a book in the database. """ + + user = models.ForeignKey("User", on_delete=models.PROTECT) + book = models.ForeignKey("Edition", on_delete=models.PROTECT) progress = models.IntegerField( - validators=[validators.MinValueValidator(0)], - null=True, - blank=True) + validators=[validators.MinValueValidator(0)], null=True, blank=True + ) progress_mode = models.CharField( - max_length=3, - choices=ProgressMode.choices, - default=ProgressMode.PAGE) - start_date = models.DateTimeField( - blank=True, - null=True) - finish_date = models.DateTimeField( - blank=True, - null=True) + max_length=3, choices=ProgressMode.choices, default=ProgressMode.PAGE + ) + start_date = models.DateTimeField(blank=True, null=True) + finish_date = models.DateTimeField(blank=True, null=True) def save(self, *args, **kwargs): - ''' update user active time ''' + """ update user active time """ self.user.last_active_date = timezone.now() self.user.save(broadcast=False) super().save(*args, **kwargs) @@ -37,22 +34,22 @@ class ReadThrough(BookWyrmModel): def create_update(self): if self.progress: return self.progressupdate_set.create( - user=self.user, - progress=self.progress, - mode=self.progress_mode) + user=self.user, progress=self.progress, mode=self.progress_mode + ) + class ProgressUpdate(BookWyrmModel): - ''' Store progress through a book in the database. ''' - user = models.ForeignKey('User', on_delete=models.PROTECT) - readthrough = models.ForeignKey('ReadThrough', on_delete=models.CASCADE) + """ Store progress through a book in the database. """ + + user = models.ForeignKey("User", on_delete=models.PROTECT) + readthrough = models.ForeignKey("ReadThrough", on_delete=models.CASCADE) progress = models.IntegerField(validators=[validators.MinValueValidator(0)]) mode = models.CharField( - max_length=3, - choices=ProgressMode.choices, - default=ProgressMode.PAGE) + max_length=3, choices=ProgressMode.choices, default=ProgressMode.PAGE + ) def save(self, *args, **kwargs): - ''' update user active time ''' + """ update user active time """ self.user.last_active_date = timezone.now() self.user.save(broadcast=False) super().save(*args, **kwargs) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 3b0e85d41..df99d2165 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,4 +1,4 @@ -''' defines relationships between users ''' +""" defines relationships between users """ from django.apps import apps from django.db import models, transaction, IntegrityError from django.db.models import Q @@ -11,71 +11,74 @@ from . import fields class UserRelationship(BookWyrmModel): - ''' many-to-many through table for followers ''' + """ many-to-many through table for followers """ + user_subject = fields.ForeignKey( - 'User', + "User", on_delete=models.PROTECT, - related_name='%(class)s_user_subject', - activitypub_field='actor', + related_name="%(class)s_user_subject", + activitypub_field="actor", ) user_object = fields.ForeignKey( - 'User', + "User", on_delete=models.PROTECT, - related_name='%(class)s_user_object', - activitypub_field='object', + related_name="%(class)s_user_object", + activitypub_field="object", ) @property def privacy(self): - ''' all relationships are handled directly with the participants ''' - return 'direct' + """ all relationships are handled directly with the participants """ + return "direct" @property def recipients(self): - ''' the remote user needs to recieve direct broadcasts ''' + """ the remote user needs to recieve direct broadcasts """ return [u for u in [self.user_subject, self.user_object] if not u.local] class Meta: - ''' relationships should be unique ''' + """ relationships should be unique """ + abstract = True constraints = [ models.UniqueConstraint( - fields=['user_subject', 'user_object'], - name='%(class)s_unique' + fields=["user_subject", "user_object"], name="%(class)s_unique" ), models.CheckConstraint( - check=~models.Q(user_subject=models.F('user_object')), - name='%(class)s_no_self' - ) + check=~models.Q(user_subject=models.F("user_object")), + name="%(class)s_no_self", + ), ] - def get_remote_id(self, status=None):# pylint: disable=arguments-differ - ''' use shelf identifier in remote_id ''' - status = status or 'follows' + def get_remote_id(self, status=None): # pylint: disable=arguments-differ + """ use shelf identifier in remote_id """ + status = status or "follows" base_path = self.user_subject.remote_id - return '%s#%s/%d' % (base_path, status, self.id) + return "%s#%s/%d" % (base_path, status, self.id) class UserFollows(ActivityMixin, UserRelationship): - ''' Following a user ''' - status = 'follows' + """ Following a user """ + + status = "follows" def to_activity(self): - ''' overrides default to manually set serializer ''' + """ overrides default to manually set serializer """ return activitypub.Follow(**generate_activity(self)) def save(self, *args, **kwargs): - ''' really really don't let a user follow someone who blocked them ''' + """ really really don't let a user follow someone who blocked them """ # blocking in either direction is a no-go if UserBlocks.objects.filter( - Q( - user_subject=self.user_subject, - user_object=self.user_object, - ) | Q( - user_subject=self.user_object, - user_object=self.user_subject, - ) - ).exists(): + Q( + user_subject=self.user_subject, + user_object=self.user_object, + ) + | Q( + user_subject=self.user_object, + user_object=self.user_subject, + ) + ).exists(): raise IntegrityError() # don't broadcast this type of relationship -- accepts and requests # are handled by the UserFollowRequest model @@ -83,7 +86,7 @@ class UserFollows(ActivityMixin, UserRelationship): @classmethod def from_request(cls, follow_request): - ''' converts a follow request into a follow relationship ''' + """ converts a follow request into a follow relationship """ return cls.objects.create( user_subject=follow_request.user_subject, user_object=follow_request.user_object, @@ -92,28 +95,30 @@ class UserFollows(ActivityMixin, UserRelationship): class UserFollowRequest(ActivitypubMixin, UserRelationship): - ''' following a user requires manual or automatic confirmation ''' - status = 'follow_request' + """ following a user requires manual or automatic confirmation """ + + status = "follow_request" activity_serializer = activitypub.Follow def save(self, *args, broadcast=True, **kwargs): - ''' make sure the follow or block relationship doesn't already exist ''' + """ make sure the follow or block relationship doesn't already exist """ # don't create a request if a follow already exists if UserFollows.objects.filter( - user_subject=self.user_subject, - user_object=self.user_object, - ).exists(): + user_subject=self.user_subject, + user_object=self.user_object, + ).exists(): raise IntegrityError() # blocking in either direction is a no-go if UserBlocks.objects.filter( - Q( - user_subject=self.user_subject, - user_object=self.user_object, - ) | Q( - user_subject=self.user_object, - user_object=self.user_subject, - ) - ).exists(): + Q( + user_subject=self.user_subject, + user_object=self.user_object, + ) + | Q( + user_subject=self.user_object, + user_object=self.user_subject, + ) + ).exists(): raise IntegrityError() super().save(*args, **kwargs) @@ -125,39 +130,35 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): if not manually_approves: self.accept() - model = apps.get_model('bookwyrm.Notification', require_ready=True) - notification_type = 'FOLLOW_REQUEST' if \ - manually_approves else 'FOLLOW' + model = apps.get_model("bookwyrm.Notification", require_ready=True) + notification_type = "FOLLOW_REQUEST" if manually_approves else "FOLLOW" model.objects.create( user=self.user_object, related_user=self.user_subject, notification_type=notification_type, ) - def accept(self): - ''' turn this request into the real deal''' + """ turn this request into the real deal""" user = self.user_object if not self.user_subject.local: activity = activitypub.Accept( - id=self.get_remote_id(status='accepts'), + id=self.get_remote_id(status="accepts"), actor=self.user_object.remote_id, - object=self.to_activity() + object=self.to_activity(), ).serialize() self.broadcast(activity, user) with transaction.atomic(): UserFollows.from_request(self) self.delete() - - def reject(self): - ''' generate a Reject for this follow request ''' + """ generate a Reject for this follow request """ if self.user_object.local: activity = activitypub.Reject( - id=self.get_remote_id(status='rejects'), + id=self.get_remote_id(status="rejects"), actor=self.user_object.remote_id, - object=self.to_activity() + object=self.to_activity(), ).serialize() self.broadcast(activity, self.user_object) @@ -165,19 +166,20 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): class UserBlocks(ActivityMixin, UserRelationship): - ''' prevent another user from following you and seeing your posts ''' - status = 'blocks' + """ prevent another user from following you and seeing your posts """ + + status = "blocks" activity_serializer = activitypub.Block def save(self, *args, **kwargs): - ''' remove follow or follow request rels after a block is created ''' + """ remove follow or follow request rels after a block is created """ super().save(*args, **kwargs) UserFollows.objects.filter( - Q(user_subject=self.user_subject, user_object=self.user_object) | \ - Q(user_subject=self.user_object, user_object=self.user_subject) + Q(user_subject=self.user_subject, user_object=self.user_object) + | Q(user_subject=self.user_object, user_object=self.user_subject) ).delete() UserFollowRequest.objects.filter( - Q(user_subject=self.user_subject, user_object=self.user_object) | \ - Q(user_subject=self.user_object, user_object=self.user_subject) + Q(user_subject=self.user_subject, user_object=self.user_object) + | Q(user_subject=self.user_object, user_object=self.user_subject) ).delete() diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index dfb8b9b31..965541a29 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -1,4 +1,4 @@ -''' puttin' books on shelves ''' +""" puttin' books on shelves """ import re from django.db import models @@ -9,61 +9,68 @@ from . import fields class Shelf(OrderedCollectionMixin, BookWyrmModel): - ''' a list of books owned by a user ''' + """ a list of books owned by a user """ + name = fields.CharField(max_length=100) identifier = models.CharField(max_length=100) user = fields.ForeignKey( - 'User', on_delete=models.PROTECT, activitypub_field='owner') + "User", on_delete=models.PROTECT, activitypub_field="owner" + ) editable = models.BooleanField(default=True) privacy = fields.PrivacyField() books = models.ManyToManyField( - 'Edition', + "Edition", symmetrical=False, - through='ShelfBook', - through_fields=('shelf', 'book') + through="ShelfBook", + through_fields=("shelf", "book"), ) activity_serializer = activitypub.Shelf def save(self, *args, **kwargs): - ''' set the identifier ''' + """ set the identifier """ super().save(*args, **kwargs) if not self.identifier: - slug = re.sub(r'[^\w]', '', self.name).lower() - self.identifier = '%s-%d' % (slug, self.id) + slug = re.sub(r"[^\w]", "", self.name).lower() + self.identifier = "%s-%d" % (slug, self.id) super().save(*args, **kwargs) @property def collection_queryset(self): - ''' list of books for this shelf, overrides OrderedCollectionMixin ''' - return self.books.all().order_by('shelfbook') + """ list of books for this shelf, overrides OrderedCollectionMixin """ + return self.books.all().order_by("shelfbook") def get_remote_id(self): - ''' shelf identifier instead of id ''' + """ shelf identifier instead of id """ base_path = self.user.remote_id - return '%s/shelf/%s' % (base_path, self.identifier) + return "%s/shelf/%s" % (base_path, self.identifier) class Meta: - ''' user/shelf unqiueness ''' - unique_together = ('user', 'identifier') + """ user/shelf unqiueness """ + + unique_together = ("user", "identifier") class ShelfBook(CollectionItemMixin, BookWyrmModel): - ''' many to many join table for books and shelves ''' + """ many to many join table for books and shelves """ + book = fields.ForeignKey( - 'Edition', on_delete=models.PROTECT, activitypub_field='object') + "Edition", on_delete=models.PROTECT, activitypub_field="object" + ) shelf = fields.ForeignKey( - 'Shelf', on_delete=models.PROTECT, activitypub_field='target') + "Shelf", on_delete=models.PROTECT, activitypub_field="target" + ) user = fields.ForeignKey( - 'User', on_delete=models.PROTECT, activitypub_field='actor') + "User", on_delete=models.PROTECT, activitypub_field="actor" + ) activity_serializer = activitypub.Add - object_field = 'book' - collection_field = 'shelf' - + object_field = "book" + collection_field = "shelf" class Meta: - ''' an opinionated constraint! - you can't put a book on shelf twice ''' - unique_together = ('book', 'shelf') - ordering = ('-created_date',) + """an opinionated constraint! + you can't put a book on shelf twice""" + + unique_together = ("book", "shelf") + ordering = ("-created_date",) diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index d39718b30..7fde6781e 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -1,4 +1,4 @@ -''' the particulars for this instance of BookWyrm ''' +""" the particulars for this instance of BookWyrm """ import base64 import datetime @@ -9,36 +9,31 @@ from django.utils import timezone from bookwyrm.settings import DOMAIN from .user import User + class SiteSettings(models.Model): - ''' customized settings for this instance ''' - name = models.CharField(default='BookWyrm', max_length=100) + """ customized settings for this instance """ + + name = models.CharField(default="BookWyrm", max_length=100) instance_tagline = models.CharField( - max_length=150, default='Social Reading and Reviewing') - instance_description = models.TextField( - default='This instance has no description.') + max_length=150, default="Social Reading and Reviewing" + ) + instance_description = models.TextField(default="This instance has no description.") registration_closed_text = models.TextField( - default='Contact an administrator to get an invite') - code_of_conduct = models.TextField( - default='Add a code of conduct here.') - privacy_policy = models.TextField( - default='Add a privacy policy here.') + default="Contact an administrator to get an invite" + ) + code_of_conduct = models.TextField(default="Add a code of conduct here.") + privacy_policy = models.TextField(default="Add a privacy policy here.") allow_registration = models.BooleanField(default=True) - logo = models.ImageField( - upload_to='logos/', null=True, blank=True - ) - logo_small = models.ImageField( - upload_to='logos/', null=True, blank=True - ) - favicon = models.ImageField( - upload_to='logos/', null=True, blank=True - ) + logo = models.ImageField(upload_to="logos/", null=True, blank=True) + logo_small = models.ImageField(upload_to="logos/", null=True, blank=True) + favicon = models.ImageField(upload_to="logos/", null=True, blank=True) support_link = models.CharField(max_length=255, null=True, blank=True) support_title = models.CharField(max_length=100, null=True, blank=True) admin_email = models.EmailField(max_length=255, null=True, blank=True) @classmethod def get(cls): - ''' gets the site settings db entry or defaults ''' + """ gets the site settings db entry or defaults """ try: return cls.objects.get(id=1) except cls.DoesNotExist: @@ -46,12 +41,15 @@ class SiteSettings(models.Model): default_settings.save() return default_settings + def new_access_code(): - ''' the identifier for a user invite ''' - return base64.b32encode(Random.get_random_bytes(5)).decode('ascii') + """ the identifier for a user invite """ + return base64.b32encode(Random.get_random_bytes(5)).decode("ascii") + class SiteInvite(models.Model): - ''' gives someone access to create an account on the instance ''' + """ gives someone access to create an account on the instance """ + created_date = models.DateTimeField(auto_now_add=True) code = models.CharField(max_length=32, default=new_access_code) expiry = models.DateTimeField(blank=True, null=True) @@ -60,34 +58,35 @@ class SiteInvite(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) def valid(self): - ''' make sure it hasn't expired or been used ''' - return ( - (self.expiry is None or self.expiry > timezone.now()) and - (self.use_limit is None or self.times_used < self.use_limit)) + """ make sure it hasn't expired or been used """ + return (self.expiry is None or self.expiry > timezone.now()) and ( + self.use_limit is None or self.times_used < self.use_limit + ) @property def link(self): - ''' formats the invite link ''' - return 'https://{}/invite/{}'.format(DOMAIN, self.code) + """ formats the invite link """ + return "https://{}/invite/{}".format(DOMAIN, self.code) def get_passowrd_reset_expiry(): - ''' give people a limited time to use the link ''' + """ give people a limited time to use the link """ now = timezone.now() return now + datetime.timedelta(days=1) class PasswordReset(models.Model): - ''' gives someone access to create an account on the instance ''' + """ gives someone access to create an account on the instance """ + code = models.CharField(max_length=32, default=new_access_code) expiry = models.DateTimeField(default=get_passowrd_reset_expiry) user = models.OneToOneField(User, on_delete=models.CASCADE) def valid(self): - ''' make sure it hasn't expired or been used ''' + """ make sure it hasn't expired or been used """ return self.expiry > timezone.now() @property def link(self): - ''' formats the invite link ''' - return 'https://{}/password-reset/{}'.format(DOMAIN, self.code) + """ formats the invite link """ + return "https://{}/password-reset/{}".format(DOMAIN, self.code) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index ba9727f58..c18afd1bf 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -1,4 +1,4 @@ -''' models for storing different kinds of Activities ''' +""" models for storing different kinds of Activities """ from dataclasses import MISSING import re @@ -17,76 +17,81 @@ from . import fields class Status(OrderedCollectionPageMixin, BookWyrmModel): - ''' any post, like a reply to a review, etc ''' + """ any post, like a reply to a review, etc """ + user = fields.ForeignKey( - 'User', on_delete=models.PROTECT, activitypub_field='attributedTo') + "User", on_delete=models.PROTECT, activitypub_field="attributedTo" + ) content = fields.HtmlField(blank=True, null=True) - mention_users = fields.TagField('User', related_name='mention_user') - mention_books = fields.TagField('Edition', related_name='mention_book') + mention_users = fields.TagField("User", related_name="mention_user") + mention_books = fields.TagField("Edition", related_name="mention_book") local = models.BooleanField(default=True) content_warning = fields.CharField( - max_length=500, blank=True, null=True, activitypub_field='summary') + max_length=500, blank=True, null=True, activitypub_field="summary" + ) privacy = fields.PrivacyField(max_length=255) sensitive = fields.BooleanField(default=False) # created date is different than publish date because of federated posts published_date = fields.DateTimeField( - default=timezone.now, activitypub_field='published') + default=timezone.now, activitypub_field="published" + ) deleted = models.BooleanField(default=False) deleted_date = models.DateTimeField(blank=True, null=True) favorites = models.ManyToManyField( - 'User', + "User", symmetrical=False, - through='Favorite', - through_fields=('status', 'user'), - related_name='user_favorites' + through="Favorite", + through_fields=("status", "user"), + related_name="user_favorites", ) reply_parent = fields.ForeignKey( - 'self', + "self", null=True, on_delete=models.PROTECT, - activitypub_field='inReplyTo', + activitypub_field="inReplyTo", ) objects = InheritanceManager() activity_serializer = activitypub.Note - serialize_reverse_fields = [('attachments', 'attachment', 'id')] - deserialize_reverse_fields = [('attachments', 'attachment')] - + serialize_reverse_fields = [("attachments", "attachment", "id")] + deserialize_reverse_fields = [("attachments", "attachment")] def save(self, *args, **kwargs): - ''' save and notify ''' + """ save and notify """ super().save(*args, **kwargs) - notification_model = apps.get_model( - 'bookwyrm.Notification', require_ready=True) + notification_model = apps.get_model("bookwyrm.Notification", require_ready=True) if self.deleted: notification_model.objects.filter(related_status=self).delete() - if self.reply_parent and self.reply_parent.user != self.user and \ - self.reply_parent.user.local: + if ( + self.reply_parent + and self.reply_parent.user != self.user + and self.reply_parent.user.local + ): notification_model.objects.create( user=self.reply_parent.user, - notification_type='REPLY', + notification_type="REPLY", related_user=self.user, related_status=self, ) for mention_user in self.mention_users.all(): # avoid double-notifying about this status - if not mention_user.local or \ - (self.reply_parent and \ - mention_user == self.reply_parent.user): + if not mention_user.local or ( + self.reply_parent and mention_user == self.reply_parent.user + ): continue notification_model.objects.create( user=mention_user, - notification_type='MENTION', + notification_type="MENTION", related_user=self.user, related_status=self, ) - def delete(self, *args, **kwargs):#pylint: disable=unused-argument - ''' "delete" a status ''' - if hasattr(self, 'boosted_status'): + def delete(self, *args, **kwargs): # pylint: disable=unused-argument + """ "delete" a status """ + if hasattr(self, "boosted_status"): # okay but if it's a boost really delete it super().delete(*args, **kwargs) return @@ -96,141 +101,154 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): @property def recipients(self): - ''' tagged users who definitely need to get this status in broadcast ''' + """ tagged users who definitely need to get this status in broadcast """ mentions = [u for u in self.mention_users.all() if not u.local] - if hasattr(self, 'reply_parent') and self.reply_parent \ - and not self.reply_parent.user.local: + if ( + hasattr(self, "reply_parent") + and self.reply_parent + and not self.reply_parent.user.local + ): mentions.append(self.reply_parent.user) return list(set(mentions)) @classmethod def ignore_activity(cls, activity): - ''' keep notes if they are replies to existing statuses ''' - if activity.type == 'Announce': + """ keep notes if they are replies to existing statuses """ + if activity.type == "Announce": # keep it if the booster or the boosted are local boosted = activitypub.resolve_remote_id(activity.object, save=False) return cls.ignore_activity(boosted.to_activity_dataclass()) # keep if it if it's a custom type - if activity.type != 'Note': + if activity.type != "Note": return False - if cls.objects.filter( - remote_id=activity.inReplyTo).exists(): + if cls.objects.filter(remote_id=activity.inReplyTo).exists(): return False # keep notes if they mention local users if activity.tag == MISSING or activity.tag is None: return True - tags = [l['href'] for l in activity.tag if l['type'] == 'Mention'] - user_model = apps.get_model('bookwyrm.User', require_ready=True) + tags = [l["href"] for l in activity.tag if l["type"] == "Mention"] + user_model = apps.get_model("bookwyrm.User", require_ready=True) for tag in tags: - if user_model.objects.filter( - remote_id=tag, local=True).exists(): + if user_model.objects.filter(remote_id=tag, local=True).exists(): # we found a mention of a known use boost return False return True @classmethod def replies(cls, status): - ''' load all replies to a status. idk if there's a better way - to write this so it's just a property ''' - return cls.objects.filter( - reply_parent=status - ).select_subclasses().order_by('published_date') + """load all replies to a status. idk if there's a better way + to write this so it's just a property""" + return ( + cls.objects.filter(reply_parent=status) + .select_subclasses() + .order_by("published_date") + ) @property def status_type(self): - ''' expose the type of status for the ui using activity type ''' + """ expose the type of status for the ui using activity type """ return self.activity_serializer.__name__ @property def boostable(self): - ''' you can't boost dms ''' - return self.privacy in ['unlisted', 'public'] + """ you can't boost dms """ + return self.privacy in ["unlisted", "public"] def to_replies(self, **kwargs): - ''' helper function for loading AP serialized replies to a status ''' + """ helper function for loading AP serialized replies to a status """ return self.to_ordered_collection( self.replies(self), - remote_id='%s/replies' % self.remote_id, + remote_id="%s/replies" % self.remote_id, collection_only=True, **kwargs ).serialize() - def to_activity_dataclass(self, pure=False):# pylint: disable=arguments-differ - ''' return tombstone if the status is deleted ''' + def to_activity_dataclass(self, pure=False): # pylint: disable=arguments-differ + """ return tombstone if the status is deleted """ if self.deleted: return activitypub.Tombstone( id=self.remote_id, url=self.remote_id, deleted=self.deleted_date.isoformat(), - published=self.deleted_date.isoformat() + published=self.deleted_date.isoformat(), ) activity = ActivitypubMixin.to_activity_dataclass(self) activity.replies = self.to_replies() # "pure" serialization for non-bookwyrm instances - if pure and hasattr(self, 'pure_content'): + if pure and hasattr(self, "pure_content"): activity.content = self.pure_content - if hasattr(activity, 'name'): + if hasattr(activity, "name"): activity.name = self.pure_name activity.type = self.pure_type activity.attachment = [ - image_serializer(b.cover, b.alt_text) \ - for b in self.mention_books.all()[:4] if b.cover] - if hasattr(self, 'book') and self.book.cover: + image_serializer(b.cover, b.alt_text) + for b in self.mention_books.all()[:4] + if b.cover + ] + if hasattr(self, "book") and self.book.cover: activity.attachment.append( image_serializer(self.book.cover, self.book.alt_text) ) return activity - def to_activity(self, pure=False):# pylint: disable=arguments-differ - ''' json serialized activitypub class ''' + def to_activity(self, pure=False): # pylint: disable=arguments-differ + """ json serialized activitypub class """ return self.to_activity_dataclass(pure=pure).serialize() class GeneratedNote(Status): - ''' these are app-generated messages about user activity ''' + """ these are app-generated messages about user activity """ + @property def pure_content(self): - ''' indicate the book in question for mastodon (or w/e) users ''' + """ indicate the book in question for mastodon (or w/e) users """ message = self.content - books = ', '.join( - '"%s"' % (book.remote_id, book.title) \ + books = ", ".join( + '"%s"' % (book.remote_id, book.title) for book in self.mention_books.all() ) - return '%s %s %s' % (self.user.display_name, message, books) + return "%s %s %s" % (self.user.display_name, message, books) activity_serializer = activitypub.GeneratedNote - pure_type = 'Note' + pure_type = "Note" class Comment(Status): - ''' like a review but without a rating and transient ''' + """ like a review but without a rating and transient """ + book = fields.ForeignKey( - 'Edition', on_delete=models.PROTECT, activitypub_field='inReplyToBook') + "Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook" + ) @property def pure_content(self): - ''' indicate the book in question for mastodon (or w/e) users ''' - return '%s

(comment on "%s")

' % \ - (self.content, self.book.remote_id, self.book.title) + """ indicate the book in question for mastodon (or w/e) users """ + return '%s

(comment on "%s")

' % ( + self.content, + self.book.remote_id, + self.book.title, + ) activity_serializer = activitypub.Comment - pure_type = 'Note' + pure_type = "Note" class Quotation(Status): - ''' like a review but without a rating and transient ''' + """ like a review but without a rating and transient """ + quote = fields.HtmlField() book = fields.ForeignKey( - 'Edition', on_delete=models.PROTECT, activitypub_field='inReplyToBook') + "Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook" + ) @property def pure_content(self): - ''' indicate the book in question for mastodon (or w/e) users ''' - quote = re.sub(r'^

', '

"', self.quote) - quote = re.sub(r'

$', '"

', quote) + """ indicate the book in question for mastodon (or w/e) users """ + quote = re.sub(r"^

", '

"', self.quote) + quote = re.sub(r"

$", '"

', quote) return '%s

-- "%s"

%s' % ( quote, self.book.remote_id, @@ -239,90 +257,86 @@ class Quotation(Status): ) activity_serializer = activitypub.Quotation - pure_type = 'Note' + pure_type = "Note" class Review(Status): - ''' a book review ''' + """ a book review """ + name = fields.CharField(max_length=255, null=True) book = fields.ForeignKey( - 'Edition', on_delete=models.PROTECT, activitypub_field='inReplyToBook') + "Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook" + ) rating = fields.IntegerField( default=None, null=True, blank=True, - validators=[MinValueValidator(1), MaxValueValidator(5)] + validators=[MinValueValidator(1), MaxValueValidator(5)], ) @property def pure_name(self): - ''' clarify review names for mastodon serialization ''' + """ clarify review names for mastodon serialization """ if self.rating: - #pylint: disable=bad-string-format-type + # pylint: disable=bad-string-format-type return 'Review of "%s" (%d stars): %s' % ( self.book.title, self.rating, - self.name + self.name, ) - return 'Review of "%s": %s' % ( - self.book.title, - self.name - ) + return 'Review of "%s": %s' % (self.book.title, self.name) @property def pure_content(self): - ''' indicate the book in question for mastodon (or w/e) users ''' + """ indicate the book in question for mastodon (or w/e) users """ return self.content activity_serializer = activitypub.Review - pure_type = 'Article' + pure_type = "Article" class Boost(ActivityMixin, Status): - ''' boost'ing a post ''' + """ boost'ing a post """ + boosted_status = fields.ForeignKey( - 'Status', + "Status", on_delete=models.PROTECT, - related_name='boosters', - activitypub_field='object', + related_name="boosters", + activitypub_field="object", ) activity_serializer = activitypub.Announce def save(self, *args, **kwargs): - ''' save and notify ''' + """ save and notify """ super().save(*args, **kwargs) if not self.boosted_status.user.local: return - notification_model = apps.get_model( - 'bookwyrm.Notification', require_ready=True) + notification_model = apps.get_model("bookwyrm.Notification", require_ready=True) notification_model.objects.create( user=self.boosted_status.user, related_status=self.boosted_status, related_user=self.user, - notification_type='BOOST', + notification_type="BOOST", ) def delete(self, *args, **kwargs): - ''' delete and un-notify ''' - notification_model = apps.get_model( - 'bookwyrm.Notification', require_ready=True) + """ delete and un-notify """ + notification_model = apps.get_model("bookwyrm.Notification", require_ready=True) notification_model.objects.filter( user=self.boosted_status.user, related_status=self.boosted_status, related_user=self.user, - notification_type='BOOST', + notification_type="BOOST", ).delete() super().delete(*args, **kwargs) - def __init__(self, *args, **kwargs): - ''' the user field is "actor" here instead of "attributedTo" ''' + """ the user field is "actor" here instead of "attributedTo" """ super().__init__(*args, **kwargs) - reserve_fields = ['user', 'boosted_status'] - self.simple_fields = [f for f in self.simple_fields if \ - f.name in reserve_fields] + reserve_fields = ["user", "boosted_status"] + self.simple_fields = [f for f in self.simple_fields if f.name in reserve_fields] self.activity_fields = self.simple_fields self.many_to_many_fields = [] self.image_fields = [] diff --git a/bookwyrm/models/tag.py b/bookwyrm/models/tag.py index 83359170a..2c45b8f91 100644 --- a/bookwyrm/models/tag.py +++ b/bookwyrm/models/tag.py @@ -1,4 +1,4 @@ -''' models for storing different kinds of Activities ''' +""" models for storing different kinds of Activities """ import urllib.parse from django.apps import apps @@ -12,28 +12,30 @@ from . import fields class Tag(OrderedCollectionMixin, BookWyrmModel): - ''' freeform tags for books ''' + """ freeform tags for books """ + name = fields.CharField(max_length=100, unique=True) identifier = models.CharField(max_length=100) @property def books(self): - ''' count of books associated with this tag ''' - edition_model = apps.get_model('bookwyrm.Edition', require_ready=True) - return edition_model.objects.filter( - usertag__tag__identifier=self.identifier - ).order_by('-created_date').distinct() + """ count of books associated with this tag """ + edition_model = apps.get_model("bookwyrm.Edition", require_ready=True) + return ( + edition_model.objects.filter(usertag__tag__identifier=self.identifier) + .order_by("-created_date") + .distinct() + ) collection_queryset = books def get_remote_id(self): - ''' tag should use identifier not id in remote_id ''' - base_path = 'https://%s' % DOMAIN - return '%s/tag/%s' % (base_path, self.identifier) - + """ tag should use identifier not id in remote_id """ + base_path = "https://%s" % DOMAIN + return "%s/tag/%s" % (base_path, self.identifier) def save(self, *args, **kwargs): - ''' create a url-safe lookup key for the tag ''' + """ create a url-safe lookup key for the tag """ if not self.id: # add identifiers to new tags self.identifier = urllib.parse.quote_plus(self.name) @@ -41,18 +43,21 @@ class Tag(OrderedCollectionMixin, BookWyrmModel): class UserTag(CollectionItemMixin, BookWyrmModel): - ''' an instance of a tag on a book by a user ''' + """ an instance of a tag on a book by a user """ + user = fields.ForeignKey( - 'User', on_delete=models.PROTECT, activitypub_field='actor') + "User", on_delete=models.PROTECT, activitypub_field="actor" + ) book = fields.ForeignKey( - 'Edition', on_delete=models.PROTECT, activitypub_field='object') - tag = fields.ForeignKey( - 'Tag', on_delete=models.PROTECT, activitypub_field='target') + "Edition", on_delete=models.PROTECT, activitypub_field="object" + ) + tag = fields.ForeignKey("Tag", on_delete=models.PROTECT, activitypub_field="target") activity_serializer = activitypub.Add - object_field = 'book' - collection_field = 'tag' + object_field = "book" + collection_field = "tag" class Meta: - ''' unqiueness constraint ''' - unique_together = ('user', 'book', 'tag') + """ unqiueness constraint """ + + unique_together = ("user", "book", "tag") diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index bbeb10ccb..440b65d3d 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -1,4 +1,4 @@ -''' database schema for user data ''' +""" database schema for user data """ import re from urllib.parse import urlparse @@ -23,25 +23,28 @@ from . import fields, Review class User(OrderedCollectionPageMixin, AbstractUser): - ''' a user who wants to read books ''' + """ a user who wants to read books """ + username = fields.UsernameField() email = models.EmailField(unique=True, null=True) key_pair = fields.OneToOneField( - 'KeyPair', + "KeyPair", on_delete=models.CASCADE, - blank=True, null=True, - activitypub_field='publicKey', - related_name='owner' + blank=True, + null=True, + activitypub_field="publicKey", + related_name="owner", ) inbox = fields.RemoteIdField(unique=True) shared_inbox = fields.RemoteIdField( - activitypub_field='sharedInbox', - activitypub_wrapper='endpoints', + activitypub_field="sharedInbox", + activitypub_wrapper="endpoints", deduplication_field=False, - null=True) + null=True, + ) federated_server = models.ForeignKey( - 'FederatedServer', + "FederatedServer", on_delete=models.PROTECT, null=True, blank=True, @@ -59,54 +62,58 @@ class User(OrderedCollectionPageMixin, AbstractUser): # name is your display name, which you can change at will name = fields.CharField(max_length=100, null=True, blank=True) avatar = fields.ImageField( - upload_to='avatars/', blank=True, null=True, - activitypub_field='icon', alt_field='alt_text') + upload_to="avatars/", + blank=True, + null=True, + activitypub_field="icon", + alt_field="alt_text", + ) followers = fields.ManyToManyField( - 'self', + "self", link_only=True, symmetrical=False, - through='UserFollows', - through_fields=('user_object', 'user_subject'), - related_name='following' + through="UserFollows", + through_fields=("user_object", "user_subject"), + related_name="following", ) follow_requests = models.ManyToManyField( - 'self', + "self", symmetrical=False, - through='UserFollowRequest', - through_fields=('user_subject', 'user_object'), - related_name='follower_requests' + through="UserFollowRequest", + through_fields=("user_subject", "user_object"), + related_name="follower_requests", ) blocks = models.ManyToManyField( - 'self', + "self", symmetrical=False, - through='UserBlocks', - through_fields=('user_subject', 'user_object'), - related_name='blocked_by' + through="UserBlocks", + through_fields=("user_subject", "user_object"), + related_name="blocked_by", ) favorites = models.ManyToManyField( - 'Status', + "Status", symmetrical=False, - through='Favorite', - through_fields=('user', 'status'), - related_name='favorite_statuses' + through="Favorite", + through_fields=("user", "status"), + related_name="favorite_statuses", ) - remote_id = fields.RemoteIdField( - null=True, unique=True, activitypub_field='id') + remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id") created_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now=True) last_active_date = models.DateTimeField(auto_now=True) manually_approves_followers = fields.BooleanField(default=False) - name_field = 'username' + name_field = "username" + @property def alt_text(self): - ''' alt text with username ''' - return 'avatar for %s' % (self.localname or self.username) + """ alt text with username """ + return "avatar for %s" % (self.localname or self.username) @property def display_name(self): - ''' show the cleanest version of the user's name possible ''' - if self.name and self.name != '': + """ show the cleanest version of the user's name possible """ + if self.name and self.name != "": return self.name return self.localname or self.username @@ -114,78 +121,82 @@ class User(OrderedCollectionPageMixin, AbstractUser): @classmethod def viewer_aware_objects(cls, viewer): - ''' the user queryset filtered for the context of the logged in user ''' + """ the user queryset filtered for the context of the logged in user """ queryset = cls.objects.filter(is_active=True) if viewer.is_authenticated: - queryset = queryset.exclude( - blocks=viewer - ) + queryset = queryset.exclude(blocks=viewer) return queryset def to_outbox(self, filter_type=None, **kwargs): - ''' an ordered collection of statuses ''' + """ an ordered collection of statuses """ if filter_type: filter_class = apps.get_model( - 'bookwyrm.%s' % filter_type, require_ready=True) + "bookwyrm.%s" % filter_type, require_ready=True + ) if not issubclass(filter_class, Status): raise TypeError( - 'filter_status_class must be a subclass of models.Status') + "filter_status_class must be a subclass of models.Status" + ) queryset = filter_class.objects else: queryset = Status.objects - queryset = queryset.filter( - user=self, - deleted=False, - privacy__in=['public', 'unlisted'], - ).select_subclasses().order_by('-published_date') - return self.to_ordered_collection(queryset, \ - collection_only=True, remote_id=self.outbox, **kwargs).serialize() + queryset = ( + queryset.filter( + user=self, + deleted=False, + privacy__in=["public", "unlisted"], + ) + .select_subclasses() + .order_by("-published_date") + ) + return self.to_ordered_collection( + queryset, collection_only=True, remote_id=self.outbox, **kwargs + ).serialize() def to_following_activity(self, **kwargs): - ''' activitypub following list ''' - remote_id = '%s/following' % self.remote_id + """ activitypub following list """ + remote_id = "%s/following" % self.remote_id return self.to_ordered_collection( - self.following.order_by('-updated_date').all(), + self.following.order_by("-updated_date").all(), remote_id=remote_id, id_only=True, **kwargs ) def to_followers_activity(self, **kwargs): - ''' activitypub followers list ''' - remote_id = '%s/followers' % self.remote_id + """ activitypub followers list """ + remote_id = "%s/followers" % self.remote_id return self.to_ordered_collection( - self.followers.order_by('-updated_date').all(), + self.followers.order_by("-updated_date").all(), remote_id=remote_id, id_only=True, **kwargs ) def to_activity(self): - ''' override default AP serializer to add context object - idk if this is the best way to go about this ''' + """override default AP serializer to add context object + idk if this is the best way to go about this""" activity_object = super().to_activity() - activity_object['@context'] = [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', + activity_object["@context"] = [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", { - 'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers', - 'schema': 'http://schema.org#', - 'PropertyValue': 'schema:PropertyValue', - 'value': 'schema:value', - } + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + }, ] return activity_object - def save(self, *args, **kwargs): - ''' populate fields for new local users ''' + """ populate fields for new local users """ created = not bool(self.id) if not self.local and not re.match(regex.full_username, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) - self.username = '%s@%s' % (self.username, actor_parts.netloc) + self.username = "%s@%s" % (self.username, actor_parts.netloc) super().save(*args, **kwargs) # this user already exists, no need to populate fields @@ -200,114 +211,120 @@ class User(OrderedCollectionPageMixin, AbstractUser): return # populate fields for local users - self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname) - self.inbox = '%s/inbox' % self.remote_id - self.shared_inbox = 'https://%s/inbox' % DOMAIN - self.outbox = '%s/outbox' % self.remote_id + self.remote_id = "https://%s/user/%s" % (DOMAIN, self.localname) + self.inbox = "%s/inbox" % self.remote_id + self.shared_inbox = "https://%s/inbox" % DOMAIN + self.outbox = "%s/outbox" % self.remote_id # an id needs to be set before we can proceed with related models super().save(*args, **kwargs) # make users editors by default try: - self.groups.add(Group.objects.get(name='editor')) + self.groups.add(Group.objects.get(name="editor")) except Group.DoesNotExist: # this should only happen in tests pass # create keys and shelves for new local users self.key_pair = KeyPair.objects.create( - remote_id='%s/#main-key' % self.remote_id) + remote_id="%s/#main-key" % self.remote_id + ) self.save(broadcast=False) - shelves = [{ - 'name': 'To Read', - 'identifier': 'to-read', - }, { - 'name': 'Currently Reading', - 'identifier': 'reading', - }, { - 'name': 'Read', - 'identifier': 'read', - }] + shelves = [ + { + "name": "To Read", + "identifier": "to-read", + }, + { + "name": "Currently Reading", + "identifier": "reading", + }, + { + "name": "Read", + "identifier": "read", + }, + ] for shelf in shelves: Shelf( - name=shelf['name'], - identifier=shelf['identifier'], + name=shelf["name"], + identifier=shelf["identifier"], user=self, - editable=False + editable=False, ).save(broadcast=False) @property def local_path(self): - ''' this model doesn't inherit bookwyrm model, so here we are ''' - return '/user/%s' % (self.localname or self.username) + """ this model doesn't inherit bookwyrm model, so here we are """ + return "/user/%s" % (self.localname or self.username) class KeyPair(ActivitypubMixin, BookWyrmModel): - ''' public and private keys for a user ''' + """ public and private keys for a user """ + private_key = models.TextField(blank=True, null=True) public_key = fields.TextField( - blank=True, null=True, activitypub_field='publicKeyPem') + blank=True, null=True, activitypub_field="publicKeyPem" + ) activity_serializer = activitypub.PublicKey - serialize_reverse_fields = [('owner', 'owner', 'id')] + serialize_reverse_fields = [("owner", "owner", "id")] def get_remote_id(self): # self.owner is set by the OneToOneField on User - return '%s/#main-key' % self.owner.remote_id + return "%s/#main-key" % self.owner.remote_id def save(self, *args, **kwargs): - ''' create a key pair ''' + """ create a key pair """ # no broadcasting happening here - if 'broadcast' in kwargs: - del kwargs['broadcast'] + if "broadcast" in kwargs: + del kwargs["broadcast"] if not self.public_key: self.private_key, self.public_key = create_key_pair() return super().save(*args, **kwargs) def to_activity(self): - ''' override default AP serializer to add context object - idk if this is the best way to go about this ''' + """override default AP serializer to add context object + idk if this is the best way to go about this""" activity_object = super().to_activity() - del activity_object['@context'] - del activity_object['type'] + del activity_object["@context"] + del activity_object["type"] return activity_object class AnnualGoal(BookWyrmModel): - ''' set a goal for how many books you read in a year ''' - user = models.ForeignKey('User', on_delete=models.PROTECT) - goal = models.IntegerField( - validators=[MinValueValidator(1)] - ) + """ set a goal for how many books you read in a year """ + + user = models.ForeignKey("User", on_delete=models.PROTECT) + goal = models.IntegerField(validators=[MinValueValidator(1)]) year = models.IntegerField(default=timezone.now().year) privacy = models.CharField( - max_length=255, - default='public', - choices=fields.PrivacyLevels.choices + max_length=255, default="public", choices=fields.PrivacyLevels.choices ) class Meta: - ''' unqiueness constraint ''' - unique_together = ('user', 'year') + """ unqiueness constraint """ + + unique_together = ("user", "year") def get_remote_id(self): - ''' put the year in the path ''' - return '%s/goal/%d' % (self.user.remote_id, self.year) + """ put the year in the path """ + return "%s/goal/%d" % (self.user.remote_id, self.year) @property def books(self): - ''' the books you've read this year ''' - return self.user.readthrough_set.filter( - finish_date__year__gte=self.year - ).order_by('-finish_date').all() - + """ the books you've read this year """ + return ( + self.user.readthrough_set.filter(finish_date__year__gte=self.year) + .order_by("-finish_date") + .all() + ) @property def ratings(self): - ''' ratings for books read this year ''' + """ ratings for books read this year """ book_ids = [r.book.id for r in self.books] reviews = Review.objects.filter( user=self.user, @@ -315,55 +332,50 @@ class AnnualGoal(BookWyrmModel): ) return {r.book.id: r.rating for r in reviews} - @property def progress_percent(self): - ''' how close to your goal, in percent form ''' + """ how close to your goal, in percent form """ return int(float(self.book_count / self.goal) * 100) - @property def book_count(self): - ''' how many books you've read this year ''' + """ how many books you've read this year """ return self.user.readthrough_set.filter( - finish_date__year__gte=self.year).count() + finish_date__year__gte=self.year + ).count() @app.task def set_remote_server(user_id): - ''' figure out the user's remote server in the background ''' + """ figure out the user's remote server in the background """ user = User.objects.get(id=user_id) actor_parts = urlparse(user.remote_id) - user.federated_server = \ - get_or_create_remote_server(actor_parts.netloc) + user.federated_server = get_or_create_remote_server(actor_parts.netloc) user.save(broadcast=False) if user.bookwyrm_user: get_remote_reviews.delay(user.outbox) def get_or_create_remote_server(domain): - ''' get info on a remote server ''' + """ get info on a remote server """ try: - return FederatedServer.objects.get( - server_name=domain - ) + return FederatedServer.objects.get(server_name=domain) except FederatedServer.DoesNotExist: pass try: - data = get_data('https://%s/.well-known/nodeinfo' % domain) + data = get_data("https://%s/.well-known/nodeinfo" % domain) try: - nodeinfo_url = data.get('links')[0].get('href') + nodeinfo_url = data.get("links")[0].get("href") except (TypeError, KeyError): raise ConnectorException() data = get_data(nodeinfo_url) - application_type = data.get('software', {}).get('name') - application_version = data.get('software', {}).get('version') + application_type = data.get("software", {}).get("name") + application_version = data.get("software", {}).get("version") except ConnectorException: application_type = application_version = None - server = FederatedServer.objects.create( server_name=domain, application_type=application_type, @@ -374,12 +386,12 @@ def get_or_create_remote_server(domain): @app.task def get_remote_reviews(outbox): - ''' ingest reviews by a new remote bookwyrm user ''' - outbox_page = outbox + '?page=true&type=Review' + """ ingest reviews by a new remote bookwyrm user """ + outbox_page = outbox + "?page=true&type=Review" data = get_data(outbox_page) # TODO: pagination? - for activity in data['orderedItems']: - if not activity['type'] == 'Review': + for activity in data["orderedItems"]: + if not activity["type"] == "Review": continue activitypub.Review(**activity).to_model() diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py index be7fb56fd..2a630f838 100644 --- a/bookwyrm/sanitize_html.py +++ b/bookwyrm/sanitize_html.py @@ -1,56 +1,63 @@ -''' html parser to clean up incoming text from unknown sources ''' +""" html parser to clean up incoming text from unknown sources """ from html.parser import HTMLParser -class InputHtmlParser(HTMLParser):#pylint: disable=abstract-method - ''' Removes any html that isn't allowed_tagsed from a block ''' + +class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method + """ Removes any html that isn't allowed_tagsed from a block """ def __init__(self): HTMLParser.__init__(self) self.allowed_tags = [ - 'p', 'blockquote', 'br', - 'b', 'i', 'strong', 'em', 'pre', - 'a', 'span', 'ul', 'ol', 'li' + "p", + "blockquote", + "br", + "b", + "i", + "strong", + "em", + "pre", + "a", + "span", + "ul", + "ol", + "li", ] self.tag_stack = [] self.output = [] # if the html appears invalid, we just won't allow any at all self.allow_html = True - def handle_starttag(self, tag, attrs): - ''' check if the tag is valid ''' + """ check if the tag is valid """ if self.allow_html and tag in self.allowed_tags: - self.output.append(('tag', self.get_starttag_text())) + self.output.append(("tag", self.get_starttag_text())) self.tag_stack.append(tag) else: - self.output.append(('data', '')) - + self.output.append(("data", "")) def handle_endtag(self, tag): - ''' keep the close tag ''' + """ keep the close tag """ if not self.allow_html or tag not in self.allowed_tags: - self.output.append(('data', '')) + self.output.append(("data", "")) return if not self.tag_stack or self.tag_stack[-1] != tag: # the end tag doesn't match the most recent start tag self.allow_html = False - self.output.append(('data', '')) + self.output.append(("data", "")) return self.tag_stack = self.tag_stack[:-1] - self.output.append(('tag', '' % tag)) - + self.output.append(("tag", "" % tag)) def handle_data(self, data): - ''' extract the answer, if we're in an answer tag ''' - self.output.append(('data', data)) - + """ extract the answer, if we're in an answer tag """ + self.output.append(("data", data)) def get_output(self): - ''' convert the output from a list of tuples to a string ''' + """ convert the output from a list of tuples to a string """ if self.tag_stack: self.allow_html = False if not self.allow_html: - return ''.join(v for (k, v) in self.output if k == 'data') - return ''.join(v for (k, v) in self.output) + return "".join(v for (k, v) in self.output if k == "data") + return "".join(v for (k, v) in self.output) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 45fdbd9da..bcff58287 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -1,4 +1,4 @@ -''' bookwyrm settings and configuration ''' +""" bookwyrm settings and configuration """ import os from environs import Env @@ -7,129 +7,129 @@ from django.utils.translation import gettext_lazy as _ env = Env() -DOMAIN = env('DOMAIN') -VERSION = '0.0.1' +DOMAIN = env("DOMAIN") +VERSION = "0.0.1" -PAGE_LENGTH = env('PAGE_LENGTH', 15) +PAGE_LENGTH = env("PAGE_LENGTH", 15) # celery -CELERY_BROKER = env('CELERY_BROKER') -CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND') -CELERY_ACCEPT_CONTENT = ['application/json'] -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' +CELERY_BROKER = env("CELERY_BROKER") +CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND") +CELERY_ACCEPT_CONTENT = ["application/json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_SERIALIZER = "json" # email -EMAIL_HOST = env('EMAIL_HOST') -EMAIL_PORT = env('EMAIL_PORT', 587) -EMAIL_HOST_USER = env('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD') -EMAIL_USE_TLS = env('EMAIL_USE_TLS', True) +EMAIL_HOST = env("EMAIL_HOST") +EMAIL_PORT = env("EMAIL_PORT", 587) +EMAIL_HOST_USER = env("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") +EMAIL_USE_TLS = env("EMAIL_USE_TLS", True) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale'),] +LOCALE_PATHS = [ + os.path.join(BASE_DIR, "locale"), +] # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = env('SECRET_KEY') +SECRET_KEY = env("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env.bool('DEBUG', True) +DEBUG = env.bool("DEBUG", True) -ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', ['*']) -OL_URL = env('OL_URL') +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", ["*"]) +OL_URL = env("OL_URL") # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.humanize', - 'django_rename_app', - 'bookwyrm', - 'celery', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.humanize", + "django_rename_app", + "bookwyrm", + "celery", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'bookwyrm.urls' +ROOT_URLCONF = "bookwyrm.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': ['templates'], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'bookwyrm.context_processors.site_settings', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": ["templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "bookwyrm.context_processors.site_settings", ], }, }, ] -WSGI_APPLICATION = 'bookwyrm.wsgi.application' +WSGI_APPLICATION = "bookwyrm.wsgi.application" # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases -BOOKWYRM_DATABASE_BACKEND = env('BOOKWYRM_DATABASE_BACKEND', 'postgres') +BOOKWYRM_DATABASE_BACKEND = env("BOOKWYRM_DATABASE_BACKEND", "postgres") BOOKWYRM_DBS = { - 'postgres': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': env('POSTGRES_DB', 'fedireads'), - 'USER': env('POSTGRES_USER', 'fedireads'), - 'PASSWORD': env('POSTGRES_PASSWORD', 'fedireads'), - 'HOST': env('POSTGRES_HOST', ''), - 'PORT': 5432 + "postgres": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": env("POSTGRES_DB", "fedireads"), + "USER": env("POSTGRES_USER", "fedireads"), + "PASSWORD": env("POSTGRES_PASSWORD", "fedireads"), + "HOST": env("POSTGRES_HOST", ""), + "PORT": 5432, }, } -DATABASES = { - 'default': BOOKWYRM_DBS[BOOKWYRM_DATABASE_BACKEND] -} +DATABASES = {"default": BOOKWYRM_DBS[BOOKWYRM_DATABASE_BACKEND]} -LOGIN_URL = '/login/' -AUTH_USER_MODEL = 'bookwyrm.User' +LOGIN_URL = "/login/" +AUTH_USER_MODEL = "bookwyrm.User" # Password validation # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -137,17 +137,17 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" LANGUAGES = [ - ('en-us', _('English')), - ('de-de', _('German')), - ('es', _('Spanish')), - ('fr-fr', _('French')), - ('zh-cn', _('Simplified Chinese')), + ("en-us", _("English")), + ("de-de", _("German")), + ("es", _("Spanish")), + ("fr-fr", _("French")), + ("zh-cn", _("Simplified Chinese")), ] -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -160,10 +160,13 @@ USE_TZ = True # https://docs.djangoproject.com/en/2.0/howto/static-files/ PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, env('STATIC_ROOT', 'static')) -MEDIA_URL = '/images/' -MEDIA_ROOT = os.path.join(BASE_DIR, env('MEDIA_ROOT', 'images')) +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static")) +MEDIA_URL = "/images/" +MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images")) USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % ( - requests.utils.default_user_agent(), VERSION, DOMAIN) + requests.utils.default_user_agent(), + VERSION, + DOMAIN, +) diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index ff2816640..80cbfdc79 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -1,4 +1,4 @@ -''' signs activitypub activities ''' +""" signs activitypub activities """ import hashlib from urllib.parse import urlparse import datetime @@ -6,54 +6,56 @@ from base64 import b64encode, b64decode from Crypto import Random from Crypto.PublicKey import RSA -from Crypto.Signature import pkcs1_15 #pylint: disable=no-name-in-module +from Crypto.Signature import pkcs1_15 # pylint: disable=no-name-in-module from Crypto.Hash import SHA256 MAX_SIGNATURE_AGE = 300 + def create_key_pair(): - ''' a new public/private key pair, used for creating new users ''' + """ a new public/private key pair, used for creating new users """ random_generator = Random.new().read key = RSA.generate(1024, random_generator) - private_key = key.export_key().decode('utf8') - public_key = key.publickey().export_key().decode('utf8') + private_key = key.export_key().decode("utf8") + public_key = key.publickey().export_key().decode("utf8") return private_key, public_key def make_signature(sender, destination, date, digest): - ''' uses a private key to sign an outgoing message ''' + """ uses a private key to sign an outgoing message """ inbox_parts = urlparse(destination) signature_headers = [ - '(request-target): post %s' % inbox_parts.path, - 'host: %s' % inbox_parts.netloc, - 'date: %s' % date, - 'digest: %s' % digest, + "(request-target): post %s" % inbox_parts.path, + "host: %s" % inbox_parts.netloc, + "date: %s" % date, + "digest: %s" % digest, ] - message_to_sign = '\n'.join(signature_headers) + message_to_sign = "\n".join(signature_headers) signer = pkcs1_15.new(RSA.import_key(sender.key_pair.private_key)) - signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8'))) + signed_message = signer.sign(SHA256.new(message_to_sign.encode("utf8"))) signature = { - 'keyId': '%s#main-key' % sender.remote_id, - 'algorithm': 'rsa-sha256', - 'headers': '(request-target) host date digest', - 'signature': b64encode(signed_message).decode('utf8'), + "keyId": "%s#main-key" % sender.remote_id, + "algorithm": "rsa-sha256", + "headers": "(request-target) host date digest", + "signature": b64encode(signed_message).decode("utf8"), } - return ','.join('%s="%s"' % (k, v) for (k, v) in signature.items()) + return ",".join('%s="%s"' % (k, v) for (k, v) in signature.items()) def make_digest(data): - ''' creates a message digest for signing ''' - return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\ - .digest()).decode('utf-8') + """ creates a message digest for signing """ + return "SHA-256=" + b64encode(hashlib.sha256(data.encode("utf-8")).digest()).decode( + "utf-8" + ) def verify_digest(request): - ''' checks if a digest is syntactically valid and matches the message ''' - algorithm, digest = request.headers['digest'].split('=', 1) - if algorithm == 'SHA-256': + """ checks if a digest is syntactically valid and matches the message """ + algorithm, digest = request.headers["digest"].split("=", 1) + if algorithm == "SHA-256": hash_function = hashlib.sha256 - elif algorithm == 'SHA-512': + elif algorithm == "SHA-512": hash_function = hashlib.sha512 else: raise ValueError("Unsupported hash function: {}".format(algorithm)) @@ -62,8 +64,10 @@ def verify_digest(request): if b64decode(digest) != expected: raise ValueError("Invalid HTTP Digest header") + class Signature: - ''' read and validate incoming signatures ''' + """ read and validate incoming signatures """ + def __init__(self, key_id, headers, signature): self.key_id = key_id self.headers = headers @@ -71,42 +75,39 @@ class Signature: @classmethod def parse(cls, request): - ''' extract and parse a signature from an http request ''' + """ extract and parse a signature from an http request """ signature_dict = {} - for pair in request.headers['Signature'].split(','): - k, v = pair.split('=', 1) - v = v.replace('"', '') + for pair in request.headers["Signature"].split(","): + k, v = pair.split("=", 1) + v = v.replace('"', "") signature_dict[k] = v try: - key_id = signature_dict['keyId'] - headers = signature_dict['headers'] - signature = b64decode(signature_dict['signature']) + key_id = signature_dict["keyId"] + headers = signature_dict["headers"] + signature = b64decode(signature_dict["signature"]) except KeyError: - raise ValueError('Invalid auth header') + raise ValueError("Invalid auth header") return cls(key_id, headers, signature) def verify(self, public_key, request): - ''' verify rsa signature ''' - if http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE: - raise ValueError( - "Request too old: %s" % (request.headers['date'],)) + """ verify rsa signature """ + if http_date_age(request.headers["date"]) > MAX_SIGNATURE_AGE: + raise ValueError("Request too old: %s" % (request.headers["date"],)) public_key = RSA.import_key(public_key) comparison_string = [] - for signed_header_name in self.headers.split(' '): - if signed_header_name == '(request-target)': - comparison_string.append( - '(request-target): post %s' % request.path) + for signed_header_name in self.headers.split(" "): + if signed_header_name == "(request-target)": + comparison_string.append("(request-target): post %s" % request.path) else: - if signed_header_name == 'digest': + if signed_header_name == "digest": verify_digest(request) - comparison_string.append('%s: %s' % ( - signed_header_name, - request.headers[signed_header_name] - )) - comparison_string = '\n'.join(comparison_string) + comparison_string.append( + "%s: %s" % (signed_header_name, request.headers[signed_header_name]) + ) + comparison_string = "\n".join(comparison_string) signer = pkcs1_15.new(public_key) digest = SHA256.new() @@ -117,7 +118,7 @@ class Signature: def http_date_age(datestr): - ''' age of a signature in seconds ''' - parsed = datetime.datetime.strptime(datestr, '%a, %d %b %Y %H:%M:%S GMT') + """ age of a signature in seconds """ + parsed = datetime.datetime.strptime(datestr, "%a, %d %b %Y %H:%M:%S GMT") delta = datetime.datetime.utcnow() - parsed return delta.total_seconds() diff --git a/bookwyrm/status.py b/bookwyrm/status.py index 4dc4991d0..7f0757410 100644 --- a/bookwyrm/status.py +++ b/bookwyrm/status.py @@ -1,4 +1,4 @@ -''' Handle user activity ''' +""" Handle user activity """ from django.db import transaction from django.utils import timezone @@ -7,14 +7,14 @@ from bookwyrm.sanitize_html import InputHtmlParser def delete_status(status): - ''' replace the status with a tombstone ''' + """ replace the status with a tombstone """ status.deleted = True status.deleted_date = timezone.now() status.save() -def create_generated_note(user, content, mention_books=None, privacy='public'): - ''' a note created by the app about user activity ''' +def create_generated_note(user, content, mention_books=None, privacy="public"): + """ a note created by the app about user activity """ # sanitize input html parser = InputHtmlParser() parser.feed(content) @@ -22,11 +22,7 @@ def create_generated_note(user, content, mention_books=None, privacy='public'): with transaction.atomic(): # create but don't save - status = models.GeneratedNote( - user=user, - content=content, - privacy=privacy - ) + status = models.GeneratedNote(user=user, content=content, privacy=privacy) # we have to save it to set the related fields, but hold off on telling # folks about it because it is not ready status.save(broadcast=False) diff --git a/bookwyrm/tasks.py b/bookwyrm/tasks.py index fc0b9739b..23765f09b 100644 --- a/bookwyrm/tasks.py +++ b/bookwyrm/tasks.py @@ -1,12 +1,12 @@ -''' background tasks ''' +""" background tasks """ import os from celery import Celery from bookwyrm import settings # set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celerywyrm.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celerywyrm.settings") app = Celery( - 'tasks', + "tasks", broker=settings.CELERY_BROKER, ) diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index 67354ac65..77be0ca39 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -1,4 +1,4 @@ -''' template filters ''' +""" template filters """ from uuid import uuid4 from datetime import datetime @@ -13,66 +13,78 @@ from bookwyrm.views.status import to_markdown register = template.Library() -@register.filter(name='dict_key') + +@register.filter(name="dict_key") def dict_key(d, k): - ''' Returns the given key from a dictionary. ''' + """ Returns the given key from a dictionary. """ return d.get(k) or 0 -@register.filter(name='rating') +@register.filter(name="rating") def get_rating(book, user): - ''' get the overall rating of a book ''' + """ get the overall rating of a book """ queryset = views.helpers.privacy_filter( - user, models.Review.objects.filter(book=book)) - return queryset.aggregate(Avg('rating'))['rating__avg'] + user, models.Review.objects.filter(book=book) + ) + return queryset.aggregate(Avg("rating"))["rating__avg"] -@register.filter(name='user_rating') +@register.filter(name="user_rating") def get_user_rating(book, user): - ''' get a user's rating of a book ''' - rating = models.Review.objects.filter( - user=user, - book=book, - rating__isnull=False, - ).order_by('-published_date').first() + """ get a user's rating of a book """ + rating = ( + models.Review.objects.filter( + user=user, + book=book, + rating__isnull=False, + ) + .order_by("-published_date") + .first() + ) if rating: return rating.rating return 0 -@register.filter(name='username') +@register.filter(name="username") def get_user_identifier(user): - ''' use localname for local users, username for remote ''' + """ use localname for local users, username for remote """ return user.localname if user.localname else user.username -@register.filter(name='notification_count') +@register.filter(name="notification_count") def get_notification_count(user): - ''' how many UNREAD notifications are there ''' + """ how many UNREAD notifications are there """ return user.notification_set.filter(read=False).count() -@register.filter(name='replies') +@register.filter(name="replies") def get_replies(status): - ''' get all direct replies to a status ''' - #TODO: this limit could cause problems - return models.Status.objects.filter( - reply_parent=status, - deleted=False, - ).select_subclasses().all()[:10] + """ get all direct replies to a status """ + # TODO: this limit could cause problems + return ( + models.Status.objects.filter( + reply_parent=status, + deleted=False, + ) + .select_subclasses() + .all()[:10] + ) -@register.filter(name='parent') +@register.filter(name="parent") def get_parent(status): - ''' get the reply parent for a status ''' - return models.Status.objects.filter( - id=status.reply_parent_id - ).select_subclasses().get() + """ get the reply parent for a status """ + return ( + models.Status.objects.filter(id=status.reply_parent_id) + .select_subclasses() + .get() + ) -@register.filter(name='liked') +@register.filter(name="liked") def get_user_liked(user, status): - ''' did the given user fav a status? ''' + """ did the given user fav a status? """ try: models.Favorite.objects.get(user=user, status=status) return True @@ -80,15 +92,15 @@ def get_user_liked(user, status): return False -@register.filter(name='boosted') +@register.filter(name="boosted") def get_user_boosted(user, status): - ''' did the given user fav a status? ''' - return user.id in status.boosters.all().values_list('user', flat=True) + """ did the given user fav a status? """ + return user.id in status.boosters.all().values_list("user", flat=True) -@register.filter(name='follow_request_exists') +@register.filter(name="follow_request_exists") def follow_request_exists(user, requester): - ''' see if there is a pending follow request for a user ''' + """ see if there is a pending follow request for a user """ try: models.UserFollowRequest.objects.filter( user_subject=requester, @@ -99,129 +111,139 @@ def follow_request_exists(user, requester): return False -@register.filter(name='boosted_status') +@register.filter(name="boosted_status") def get_boosted(boost): - ''' load a boosted status. have to do this or it wont get foregin keys ''' - return models.Status.objects.select_subclasses().filter( - id=boost.boosted_status.id - ).get() + """ load a boosted status. have to do this or it wont get foregin keys """ + return ( + models.Status.objects.select_subclasses() + .filter(id=boost.boosted_status.id) + .get() + ) -@register.filter(name='book_description') +@register.filter(name="book_description") def get_book_description(book): - ''' use the work's text if the book doesn't have it ''' + """ use the work's text if the book doesn't have it """ return book.description or book.parent_work.description -@register.filter(name='uuid') +@register.filter(name="uuid") def get_uuid(identifier): - ''' for avoiding clashing ids when there are many forms ''' - return '%s%s' % (identifier, uuid4()) + """ for avoiding clashing ids when there are many forms """ + return "%s%s" % (identifier, uuid4()) -@register.filter(name='post_date') +@register.filter(name="post_date") def time_since(date): - ''' concise time ago function ''' + """ concise time ago function """ if not isinstance(date, datetime): - return '' + return "" now = timezone.now() delta = now - date if date < (now - relativedelta(weeks=1)): - formatter = '%b %-d' + formatter = "%b %-d" if date.year != now.year: - formatter += ' %Y' + formatter += " %Y" return date.strftime(formatter) delta = relativedelta(now, date) if delta.days: - return '%dd' % delta.days + return "%dd" % delta.days if delta.hours: - return '%dh' % delta.hours + return "%dh" % delta.hours if delta.minutes: - return '%dm' % delta.minutes - return '%ds' % delta.seconds + return "%dm" % delta.minutes + return "%ds" % delta.seconds -@register.filter(name='to_markdown') +@register.filter(name="to_markdown") def get_markdown(content): - ''' convert markdown to html ''' + """ convert markdown to html """ if content: return to_markdown(content) return None -@register.filter(name='mentions') -def get_mentions(status, user): - ''' people to @ in a reply: the parent and all mentions ''' - mentions = set([status.user] + list(status.mention_users.all())) - return ' '.join( - '@' + get_user_identifier(m) for m in mentions if not m == user) + ' ' -@register.filter(name='status_preview_name') +@register.filter(name="mentions") +def get_mentions(status, user): + """ people to @ in a reply: the parent and all mentions """ + mentions = set([status.user] + list(status.mention_users.all())) + return ( + " ".join("@" + get_user_identifier(m) for m in mentions if not m == user) + " " + ) + + +@register.filter(name="status_preview_name") def get_status_preview_name(obj): - ''' text snippet with book context for a status ''' + """ text snippet with book context for a status """ name = obj.__class__.__name__.lower() - if name == 'review': - return '%s of %s' % (name, obj.book.title) - if name == 'comment': - return '%s on %s' % (name, obj.book.title) - if name == 'quotation': - return '%s from %s' % (name, obj.book.title) + if name == "review": + return "%s of %s" % (name, obj.book.title) + if name == "comment": + return "%s on %s" % (name, obj.book.title) + if name == "quotation": + return "%s from %s" % (name, obj.book.title) return name -@register.filter(name='next_shelf') + +@register.filter(name="next_shelf") def get_next_shelf(current_shelf): - ''' shelf you'd use to update reading progress ''' - if current_shelf == 'to-read': - return 'reading' - if current_shelf == 'reading': - return 'read' - if current_shelf == 'read': - return 'read' - return 'to-read' + """ shelf you'd use to update reading progress """ + if current_shelf == "to-read": + return "reading" + if current_shelf == "reading": + return "read" + if current_shelf == "read": + return "read" + return "to-read" + @register.simple_tag(takes_context=False) def related_status(notification): - ''' for notifications ''' + """ for notifications """ if not notification.related_status: return None - if hasattr(notification.related_status, 'quotation'): + if hasattr(notification.related_status, "quotation"): return notification.related_status.quotation - if hasattr(notification.related_status, 'review'): + if hasattr(notification.related_status, "review"): return notification.related_status.review - if hasattr(notification.related_status, 'comment'): + if hasattr(notification.related_status, "comment"): return notification.related_status.comment return notification.related_status + @register.simple_tag(takes_context=True) def active_shelf(context, book): - ''' check what shelf a user has a book on, if any ''' + """ check what shelf a user has a book on, if any """ shelf = models.ShelfBook.objects.filter( - shelf__user=context['request'].user, - book__in=book.parent_work.editions.all() + shelf__user=context["request"].user, book__in=book.parent_work.editions.all() ).first() - return shelf if shelf else {'book': book} + return shelf if shelf else {"book": book} @register.simple_tag(takes_context=False) def latest_read_through(book, user): - ''' the most recent read activity ''' - return models.ReadThrough.objects.filter( - user=user, - book=book - ).order_by('-start_date').first() + """ the most recent read activity """ + return ( + models.ReadThrough.objects.filter(user=user, book=book) + .order_by("-start_date") + .first() + ) @register.simple_tag(takes_context=False) def active_read_through(book, user): - ''' the most recent read activity ''' - return models.ReadThrough.objects.filter( - user=user, - book=book, - finish_date__isnull=True - ).order_by('-start_date').first() + """ the most recent read activity """ + return ( + models.ReadThrough.objects.filter( + user=user, book=book, finish_date__isnull=True + ) + .order_by("-start_date") + .first() + ) @register.simple_tag(takes_context=False) def comparison_bool(str1, str2): - ''' idk why I need to write a tag for this, it reutrns a bool ''' + """ idk why I need to write a tag for this, it reutrns a bool """ return str1 == str2 diff --git a/bookwyrm/tests/activitypub/test_author.py b/bookwyrm/tests/activitypub/test_author.py index fd31f105e..e65f86b76 100644 --- a/bookwyrm/tests/activitypub/test_author.py +++ b/bookwyrm/tests/activitypub/test_author.py @@ -7,19 +7,18 @@ from bookwyrm import models class Author(TestCase): def setUp(self): self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', + title="Example Edition", + remote_id="https://example.com/book/1", ) self.author = models.Author.objects.create( - name='Author fullname', - aliases=['One', 'Two'], - bio='bio bio bio', + name="Author fullname", + aliases=["One", "Two"], + bio="bio bio bio", ) - def test_serialize_model(self): activity = self.author.to_activity() - self.assertEqual(activity['id'], self.author.remote_id) - self.assertIsInstance(activity['aliases'], list) - self.assertEqual(activity['aliases'], ['One', 'Two']) - self.assertEqual(activity['name'], 'Author fullname') + self.assertEqual(activity["id"], self.author.remote_id) + self.assertIsInstance(activity["aliases"], list) + self.assertEqual(activity["aliases"], ["One", "Two"]) + self.assertEqual(activity["name"], "Author fullname") diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index de108eaeb..b3e282619 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -1,4 +1,4 @@ -''' tests the base functionality for activitypub dataclasses ''' +""" tests the base functionality for activitypub dataclasses """ from io import BytesIO import json import pathlib @@ -10,231 +10,229 @@ from PIL import Image import responses from bookwyrm import activitypub -from bookwyrm.activitypub.base_activity import ActivityObject, \ - resolve_remote_id, set_related_field +from bookwyrm.activitypub.base_activity import ( + ActivityObject, + resolve_remote_id, + set_related_field, +) from bookwyrm.activitypub import ActivitySerializerError from bookwyrm import models + class BaseActivity(TestCase): - ''' the super class for model-linked activitypub dataclasses ''' + """ the super class for model-linked activitypub dataclasses """ + def setUp(self): - ''' we're probably going to re-use this so why copy/paste ''' + """ we're probably going to re-use this so why copy/paste """ self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') - self.user.remote_id = 'http://example.com/a/b' + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + self.user.remote_id = "http://example.com/a/b" self.user.save(broadcast=False) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json' - ) + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") self.userdata = json.loads(datafile.read_bytes()) # don't try to load the user icon - del self.userdata['icon'] + del self.userdata["icon"] image_file = pathlib.Path(__file__).parent.joinpath( - '../../static/images/default_avi.jpg') + "../../static/images/default_avi.jpg" + ) image = Image.open(image_file) output = BytesIO() image.save(output, format=image.format) self.image_data = output.getvalue() def test_init(self): - ''' simple successfuly init ''' - instance = ActivityObject(id='a', type='b') - self.assertTrue(hasattr(instance, 'id')) - self.assertTrue(hasattr(instance, 'type')) + """ simple successfuly init """ + instance = ActivityObject(id="a", type="b") + self.assertTrue(hasattr(instance, "id")) + self.assertTrue(hasattr(instance, "type")) def test_init_missing(self): - ''' init with missing required params ''' + """ init with missing required params """ with self.assertRaises(ActivitySerializerError): ActivityObject() def test_init_extra_fields(self): - ''' init ignoring additional fields ''' - instance = ActivityObject(id='a', type='b', fish='c') - self.assertTrue(hasattr(instance, 'id')) - self.assertTrue(hasattr(instance, 'type')) + """ init ignoring additional fields """ + instance = ActivityObject(id="a", type="b", fish="c") + self.assertTrue(hasattr(instance, "id")) + self.assertTrue(hasattr(instance, "type")) def test_init_default_field(self): - ''' replace an existing required field with a default field ''' + """ replace an existing required field with a default field """ + @dataclass(init=False) class TestClass(ActivityObject): - ''' test class with default field ''' - type: str = 'TestObject' + """ test class with default field """ - instance = TestClass(id='a') - self.assertEqual(instance.id, 'a') - self.assertEqual(instance.type, 'TestObject') + type: str = "TestObject" + + instance = TestClass(id="a") + self.assertEqual(instance.id, "a") + self.assertEqual(instance.type, "TestObject") def test_serialize(self): - ''' simple function for converting dataclass to dict ''' - instance = ActivityObject(id='a', type='b') + """ simple function for converting dataclass to dict """ + instance = ActivityObject(id="a", type="b") serialized = instance.serialize() self.assertIsInstance(serialized, dict) - self.assertEqual(serialized['id'], 'a') - self.assertEqual(serialized['type'], 'b') + self.assertEqual(serialized["id"], "a") + self.assertEqual(serialized["type"], "b") @responses.activate def test_resolve_remote_id(self): - ''' look up or load remote data ''' + """ look up or load remote data """ # existing item - result = resolve_remote_id('http://example.com/a/b', model=models.User) + result = resolve_remote_id("http://example.com/a/b", model=models.User) self.assertEqual(result, self.user) # remote item responses.add( responses.GET, - 'https://example.com/user/mouse', + "https://example.com/user/mouse", json=self.userdata, - status=200) + status=200, + ) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): result = resolve_remote_id( - 'https://example.com/user/mouse', model=models.User) + "https://example.com/user/mouse", model=models.User + ) self.assertIsInstance(result, models.User) - self.assertEqual(result.remote_id, 'https://example.com/user/mouse') - self.assertEqual(result.name, 'MOUSE?? MOUSE!!') + self.assertEqual(result.remote_id, "https://example.com/user/mouse") + self.assertEqual(result.name, "MOUSE?? MOUSE!!") def test_to_model_invalid_model(self): - ''' catch mismatch between activity type and model type ''' - instance = ActivityObject(id='a', type='b') + """ catch mismatch between activity type and model type """ + instance = ActivityObject(id="a", type="b") with self.assertRaises(ActivitySerializerError): instance.to_model(model=models.User) - @responses.activate def test_to_model_image(self): - ''' update an image field ''' + """ update an image field """ activity = activitypub.Person( id=self.user.remote_id, - name='New Name', - preferredUsername='mouse', - inbox='http://www.com/', - outbox='http://www.com/', - followers='', - summary='', - publicKey={ - 'id': 'hi', - 'owner': self.user.remote_id, - 'publicKeyPem': 'hi'}, + name="New Name", + preferredUsername="mouse", + inbox="http://www.com/", + outbox="http://www.com/", + followers="", + summary="", + publicKey={"id": "hi", "owner": self.user.remote_id, "publicKeyPem": "hi"}, endpoints={}, - icon={ - 'type': 'Image', - 'url': 'http://www.example.com/image.jpg' - } + icon={"type": "Image", "url": "http://www.example.com/image.jpg"}, ) responses.add( responses.GET, - 'http://www.example.com/image.jpg', + "http://www.example.com/image.jpg", body=self.image_data, - status=200) + status=200, + ) self.assertIsNone(self.user.avatar.name) with self.assertRaises(ValueError): - self.user.avatar.file #pylint: disable=pointless-statement + self.user.avatar.file # pylint: disable=pointless-statement # this would trigger a broadcast because it's a local user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): activity.to_model(model=models.User, instance=self.user) self.assertIsNotNone(self.user.avatar.file) - self.assertEqual(self.user.name, 'New Name') - self.assertEqual(self.user.key_pair.public_key, 'hi') + self.assertEqual(self.user.name, "New Name") + self.assertEqual(self.user.key_pair.public_key, "hi") def test_to_model_many_to_many(self): - ''' annoying that these all need special handling ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ annoying that these all need special handling """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( - content='test status', + content="test status", user=self.user, ) book = models.Edition.objects.create( - title='Test Edition', remote_id='http://book.com/book') + title="Test Edition", remote_id="http://book.com/book" + ) update_data = activitypub.Note( id=status.remote_id, content=status.content, attributedTo=self.user.remote_id, - published='hi', + published="hi", to=[], cc=[], tag=[ + {"type": "Mention", "name": "gerald", "href": "http://example.com/a/b"}, { - 'type': 'Mention', - 'name': 'gerald', - 'href': 'http://example.com/a/b' + "type": "Edition", + "name": "gerald j. books", + "href": "http://book.com/book", }, - { - 'type': 'Edition', - 'name': 'gerald j. books', - 'href': 'http://book.com/book' - }, - ] + ], ) update_data.to_model(model=models.Status, instance=status) self.assertEqual(status.mention_users.first(), self.user) self.assertEqual(status.mention_books.first(), book) - @responses.activate def test_to_model_one_to_many(self): - ''' these are reversed relationships, where the secondary object - keys the primary object but not vice versa ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """these are reversed relationships, where the secondary object + keys the primary object but not vice versa""" + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( - content='test status', + content="test status", user=self.user, ) update_data = activitypub.Note( id=status.remote_id, content=status.content, attributedTo=self.user.remote_id, - published='hi', + published="hi", to=[], cc=[], - attachment=[{ - 'url': 'http://www.example.com/image.jpg', - 'name': 'alt text', - 'type': 'Image', - }], + attachment=[ + { + "url": "http://www.example.com/image.jpg", + "name": "alt text", + "type": "Image", + } + ], ) responses.add( responses.GET, - 'http://www.example.com/image.jpg', + "http://www.example.com/image.jpg", body=self.image_data, - status=200) + status=200, + ) # sets the celery task call to the function call - with patch( - 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - with patch('bookwyrm.models.status.Status.ignore_activity') \ - as discarder: + with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"): + with patch("bookwyrm.models.status.Status.ignore_activity") as discarder: discarder.return_value = False update_data.to_model(model=models.Status, instance=status) self.assertIsNone(status.attachments.first()) - @responses.activate def test_set_related_field(self): - ''' celery task to add back-references to created objects ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ celery task to add back-references to created objects """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( - content='test status', + content="test status", user=self.user, ) data = { - 'url': 'http://www.example.com/image.jpg', - 'name': 'alt text', - 'type': 'Image', + "url": "http://www.example.com/image.jpg", + "name": "alt text", + "type": "Image", } responses.add( responses.GET, - 'http://www.example.com/image.jpg', + "http://www.example.com/image.jpg", body=self.image_data, - status=200) - set_related_field( - 'Image', 'Status', 'status', status.remote_id, data) + status=200, + ) + set_related_field("Image", "Status", "status", status.remote_id, data) self.assertIsInstance(status.attachments.first(), models.Image) self.assertIsNotNone(status.attachments.first().image) diff --git a/bookwyrm/tests/activitypub/test_person.py b/bookwyrm/tests/activitypub/test_person.py index 062402813..67aaf891e 100644 --- a/bookwyrm/tests/activitypub/test_person.py +++ b/bookwyrm/tests/activitypub/test_person.py @@ -9,23 +9,19 @@ from bookwyrm import activitypub, models class Person(TestCase): def setUp(self): - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json' - ) + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") self.user_data = json.loads(datafile.read_bytes()) - def test_load_user_data(self): activity = activitypub.Person(**self.user_data) - self.assertEqual(activity.id, 'https://example.com/user/mouse') - self.assertEqual(activity.preferredUsername, 'mouse') - self.assertEqual(activity.type, 'Person') - + self.assertEqual(activity.id, "https://example.com/user/mouse") + self.assertEqual(activity.preferredUsername, "mouse") + self.assertEqual(activity.type, "Person") def test_user_to_model(self): activity = activitypub.Person(**self.user_data) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): user = activity.to_model(model=models.User) - self.assertEqual(user.username, 'mouse@example.com') - self.assertEqual(user.remote_id, 'https://example.com/user/mouse') + self.assertEqual(user.username, "mouse@example.com") + self.assertEqual(user.remote_id, "https://example.com/user/mouse") self.assertFalse(user.local) diff --git a/bookwyrm/tests/activitypub/test_quotation.py b/bookwyrm/tests/activitypub/test_quotation.py index 1cd1f05d4..1f429dd25 100644 --- a/bookwyrm/tests/activitypub/test_quotation.py +++ b/bookwyrm/tests/activitypub/test_quotation.py @@ -1,4 +1,4 @@ -''' quotation activty object serializer class ''' +""" quotation activty object serializer class """ import json import pathlib from unittest.mock import patch @@ -8,43 +8,40 @@ from bookwyrm import activitypub, models class Quotation(TestCase): - ''' we have hecka ways to create statuses ''' + """ we have hecka ways to create statuses """ + def setUp(self): - ''' model objects we'll need ''' - with patch('bookwyrm.models.user.set_remote_server.delay'): + """ model objects we'll need """ + with patch("bookwyrm.models.user.set_remote_server.delay"): self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', + "mouse", + "mouse@mouse.mouse", + "mouseword", local=False, - inbox='https://example.com/user/mouse/inbox', - outbox='https://example.com/user/mouse/outbox', - remote_id='https://example.com/user/mouse', + inbox="https://example.com/user/mouse/inbox", + outbox="https://example.com/user/mouse/outbox", + remote_id="https://example.com/user/mouse", ) self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - ) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_quotation.json' + title="Example Edition", + remote_id="https://example.com/book/1", ) + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_quotation.json") self.status_data = json.loads(datafile.read_bytes()) - def test_quotation_activity(self): - ''' create a Quoteation ap object from json ''' + """ create a Quoteation ap object from json """ quotation = activitypub.Quotation(**self.status_data) - self.assertEqual(quotation.type, 'Quotation') - self.assertEqual( - quotation.id, 'https://example.com/user/mouse/quotation/13') - self.assertEqual(quotation.content, 'commentary') - self.assertEqual(quotation.quote, 'quote body') - self.assertEqual(quotation.inReplyToBook, 'https://example.com/book/1') - self.assertEqual( - quotation.published, '2020-05-10T02:38:31.150343+00:00') - + self.assertEqual(quotation.type, "Quotation") + self.assertEqual(quotation.id, "https://example.com/user/mouse/quotation/13") + self.assertEqual(quotation.content, "commentary") + self.assertEqual(quotation.quote, "quote body") + self.assertEqual(quotation.inReplyToBook, "https://example.com/book/1") + self.assertEqual(quotation.published, "2020-05-10T02:38:31.150343+00:00") def test_activity_to_model(self): - ''' create a model instance from an activity object ''' + """ create a model instance from an activity object """ activity = activitypub.Quotation(**self.status_data) quotation = activity.to_model(model=models.Quotation) diff --git a/bookwyrm/tests/connectors/test_abstract_connector.py b/bookwyrm/tests/connectors/test_abstract_connector.py index 1b3821040..9aa78f6a2 100644 --- a/bookwyrm/tests/connectors/test_abstract_connector.py +++ b/bookwyrm/tests/connectors/test_abstract_connector.py @@ -1,4 +1,4 @@ -''' testing book data connectors ''' +""" testing book data connectors """ from unittest.mock import patch from django.test import TestCase import responses @@ -10,109 +10,115 @@ from bookwyrm.settings import DOMAIN class AbstractConnector(TestCase): - ''' generic code for connecting to outside data sources ''' + """ generic code for connecting to outside data sources """ + def setUp(self): - ''' we need an example connector ''' + """ we need an example connector """ self.connector_info = models.Connector.objects.create( - identifier='example.com', - connector_file='openlibrary', - base_url='https://example.com', - books_url='https://example.com/books', - covers_url='https://example.com/covers', - search_url='https://example.com/search?q=', + identifier="example.com", + connector_file="openlibrary", + base_url="https://example.com", + books_url="https://example.com/books", + covers_url="https://example.com/covers", + search_url="https://example.com/search?q=", ) work_data = { - 'id': 'abc1', - 'title': 'Test work', - 'type': 'work', - 'openlibraryKey': 'OL1234W', + "id": "abc1", + "title": "Test work", + "type": "work", + "openlibraryKey": "OL1234W", } self.work_data = work_data edition_data = { - 'id': 'abc2', - 'title': 'Test edition', - 'type': 'edition', - 'openlibraryKey': 'OL1234M', + "id": "abc2", + "title": "Test edition", + "type": "edition", + "openlibraryKey": "OL1234M", } self.edition_data = edition_data class TestConnector(abstract_connector.AbstractConnector): - ''' nothing added here ''' + """ nothing added here """ + def format_search_result(self, search_result): return search_result + def parse_search_data(self, data): return data + def format_isbn_search_result(self, search_result): return search_result + def parse_isbn_search_data(self, data): return data + def is_work_data(self, data): - return data['type'] == 'work' + return data["type"] == "work" + def get_edition_from_work_data(self, data): return edition_data + def get_work_from_edition_data(self, data): return work_data + def get_authors_from_data(self, data): return [] + def expand_book_data(self, book): pass - self.connector = TestConnector('example.com') + + self.connector = TestConnector("example.com") self.connector.book_mappings = [ - Mapping('id'), - Mapping('title'), - Mapping('openlibraryKey'), + Mapping("id"), + Mapping("title"), + Mapping("openlibraryKey"), ] self.book = models.Edition.objects.create( - title='Test Book', remote_id='https://example.com/book/1234', - openlibrary_key='OL1234M') - + title="Test Book", + remote_id="https://example.com/book/1234", + openlibrary_key="OL1234M", + ) def test_abstract_connector_init(self): - ''' barebones connector for search with defaults ''' + """ barebones connector for search with defaults """ self.assertIsInstance(self.connector.book_mappings, list) - def test_is_available(self): - ''' this isn't used.... ''' + """ this isn't used.... """ self.assertTrue(self.connector.is_available()) self.connector.max_query_count = 1 self.connector.connector.query_count = 2 self.assertFalse(self.connector.is_available()) - def test_get_or_create_book_existing(self): - ''' find an existing book by remote/origin id ''' + """ find an existing book by remote/origin id """ self.assertEqual(models.Book.objects.count(), 1) self.assertEqual( - self.book.remote_id, 'https://%s/book/%d' % (DOMAIN, self.book.id)) - self.assertEqual( - self.book.origin_id, 'https://example.com/book/1234') + self.book.remote_id, "https://%s/book/%d" % (DOMAIN, self.book.id) + ) + self.assertEqual(self.book.origin_id, "https://example.com/book/1234") # dedupe by origin id - result = self.connector.get_or_create_book( - 'https://example.com/book/1234') + result = self.connector.get_or_create_book("https://example.com/book/1234") self.assertEqual(models.Book.objects.count(), 1) self.assertEqual(result, self.book) # dedupe by remote id result = self.connector.get_or_create_book( - 'https://%s/book/%d' % (DOMAIN, self.book.id)) + "https://%s/book/%d" % (DOMAIN, self.book.id) + ) self.assertEqual(models.Book.objects.count(), 1) self.assertEqual(result, self.book) @responses.activate def test_get_or_create_book_deduped(self): - ''' load remote data and deduplicate ''' + """ load remote data and deduplicate """ responses.add( - responses.GET, - 'https://example.com/book/abcd', - json=self.edition_data + responses.GET, "https://example.com/book/abcd", json=self.edition_data ) - with patch( - 'bookwyrm.connectors.abstract_connector.load_more_data.delay'): - result = self.connector.get_or_create_book( - 'https://example.com/book/abcd') + with patch("bookwyrm.connectors.abstract_connector.load_more_data.delay"): + result = self.connector.get_or_create_book("https://example.com/book/abcd") self.assertEqual(result, self.book) self.assertEqual(models.Edition.objects.count(), 1) self.assertEqual(models.Edition.objects.count(), 1) diff --git a/bookwyrm/tests/connectors/test_abstract_minimal_connector.py b/bookwyrm/tests/connectors/test_abstract_minimal_connector.py index 9b939067b..957d32334 100644 --- a/bookwyrm/tests/connectors/test_abstract_minimal_connector.py +++ b/bookwyrm/tests/connectors/test_abstract_minimal_connector.py @@ -1,4 +1,4 @@ -''' testing book data connectors ''' +""" testing book data connectors """ from django.test import TestCase import responses @@ -8,99 +8,101 @@ from bookwyrm.connectors.abstract_connector import Mapping, SearchResult class AbstractConnector(TestCase): - ''' generic code for connecting to outside data sources ''' + """ generic code for connecting to outside data sources """ + def setUp(self): - ''' we need an example connector ''' + """ we need an example connector """ self.connector_info = models.Connector.objects.create( - identifier='example.com', - connector_file='openlibrary', - base_url='https://example.com', - books_url='https://example.com/books', - covers_url='https://example.com/covers', - search_url='https://example.com/search?q=', - isbn_search_url='https://example.com/isbn', + identifier="example.com", + connector_file="openlibrary", + base_url="https://example.com", + books_url="https://example.com/books", + covers_url="https://example.com/covers", + search_url="https://example.com/search?q=", + isbn_search_url="https://example.com/isbn", ) class TestConnector(abstract_connector.AbstractMinimalConnector): - ''' nothing added here ''' + """ nothing added here """ + def format_search_result(self, search_result): return search_result + def get_or_create_book(self, remote_id): pass + def parse_search_data(self, data): return data + def format_isbn_search_result(self, search_result): return search_result + def parse_isbn_search_data(self, data): return data - self.test_connector = TestConnector('example.com') + self.test_connector = TestConnector("example.com") def test_abstract_minimal_connector_init(self): - ''' barebones connector for search with defaults ''' + """ barebones connector for search with defaults """ connector = self.test_connector self.assertEqual(connector.connector, self.connector_info) - self.assertEqual(connector.base_url, 'https://example.com') - self.assertEqual(connector.books_url, 'https://example.com/books') - self.assertEqual(connector.covers_url, 'https://example.com/covers') - self.assertEqual(connector.search_url, 'https://example.com/search?q=') - self.assertEqual(connector.isbn_search_url, 'https://example.com/isbn') + self.assertEqual(connector.base_url, "https://example.com") + self.assertEqual(connector.books_url, "https://example.com/books") + self.assertEqual(connector.covers_url, "https://example.com/covers") + self.assertEqual(connector.search_url, "https://example.com/search?q=") + self.assertEqual(connector.isbn_search_url, "https://example.com/isbn") self.assertIsNone(connector.name) - self.assertEqual(connector.identifier, 'example.com') + self.assertEqual(connector.identifier, "example.com") self.assertIsNone(connector.max_query_count) self.assertFalse(connector.local) - @responses.activate def test_search(self): - ''' makes an http request to the outside service ''' + """ makes an http request to the outside service """ responses.add( responses.GET, - 'https://example.com/search?q=a%20book%20title', - json=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'], - status=200) - results = self.test_connector.search('a book title') + "https://example.com/search?q=a%20book%20title", + json=["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"], + status=200, + ) + results = self.test_connector.search("a book title") self.assertEqual(len(results), 10) - self.assertEqual(results[0], 'a') - self.assertEqual(results[1], 'b') - self.assertEqual(results[2], 'c') - + self.assertEqual(results[0], "a") + self.assertEqual(results[1], "b") + self.assertEqual(results[2], "c") def test_search_result(self): - ''' a class that stores info about a search result ''' + """ a class that stores info about a search result """ result = SearchResult( - title='Title', - key='https://example.com/book/1', - author='Author Name', - year='1850', + title="Title", + key="https://example.com/book/1", + author="Author Name", + year="1850", connector=self.test_connector, ) # there's really not much to test here, it's just a dataclass self.assertEqual(result.confidence, 1) - self.assertEqual(result.title, 'Title') - + self.assertEqual(result.title, "Title") def test_create_mapping(self): - ''' maps remote fields for book data to bookwyrm activitypub fields ''' - mapping = Mapping('isbn') - self.assertEqual(mapping.local_field, 'isbn') - self.assertEqual(mapping.remote_field, 'isbn') - self.assertEqual(mapping.formatter('bb'), 'bb') - + """ maps remote fields for book data to bookwyrm activitypub fields """ + mapping = Mapping("isbn") + self.assertEqual(mapping.local_field, "isbn") + self.assertEqual(mapping.remote_field, "isbn") + self.assertEqual(mapping.formatter("bb"), "bb") def test_create_mapping_with_remote(self): - ''' the remote field is different than the local field ''' - mapping = Mapping('isbn', remote_field='isbn13') - self.assertEqual(mapping.local_field, 'isbn') - self.assertEqual(mapping.remote_field, 'isbn13') - self.assertEqual(mapping.formatter('bb'), 'bb') - + """ the remote field is different than the local field """ + mapping = Mapping("isbn", remote_field="isbn13") + self.assertEqual(mapping.local_field, "isbn") + self.assertEqual(mapping.remote_field, "isbn13") + self.assertEqual(mapping.formatter("bb"), "bb") def test_create_mapping_with_formatter(self): - ''' a function is provided to modify the data ''' - formatter = lambda x: 'aa' + x - mapping = Mapping('isbn', formatter=formatter) - self.assertEqual(mapping.local_field, 'isbn') - self.assertEqual(mapping.remote_field, 'isbn') + """ a function is provided to modify the data """ + formatter = lambda x: "aa" + x + mapping = Mapping("isbn", formatter=formatter) + self.assertEqual(mapping.local_field, "isbn") + self.assertEqual(mapping.remote_field, "isbn") self.assertEqual(mapping.formatter, formatter) - self.assertEqual(mapping.formatter('bb'), 'aabb') + self.assertEqual(mapping.formatter("bb"), "aabb") diff --git a/bookwyrm/tests/connectors/test_bookwyrm_connector.py b/bookwyrm/tests/connectors/test_bookwyrm_connector.py index 6b00b0e3a..1fc71688d 100644 --- a/bookwyrm/tests/connectors/test_bookwyrm_connector.py +++ b/bookwyrm/tests/connectors/test_bookwyrm_connector.py @@ -1,4 +1,4 @@ -''' testing book data connectors ''' +""" testing book data connectors """ import json import pathlib from django.test import TestCase @@ -9,39 +9,36 @@ from bookwyrm.connectors.abstract_connector import SearchResult class BookWyrmConnector(TestCase): - ''' this connector doesn't do much, just search ''' - def setUp(self): - ''' create the connector ''' - models.Connector.objects.create( - identifier='example.com', - connector_file='bookwyrm_connector', - base_url='https://example.com', - books_url='https://example.com', - covers_url='https://example.com/images/covers', - search_url='https://example.com/search?q=', - ) - self.connector = Connector('example.com') + """ this connector doesn't do much, just search """ - work_file = pathlib.Path(__file__).parent.joinpath( - '../data/bw_work.json') - edition_file = pathlib.Path(__file__).parent.joinpath( - '../data/bw_edition.json') + def setUp(self): + """ create the connector """ + models.Connector.objects.create( + identifier="example.com", + connector_file="bookwyrm_connector", + base_url="https://example.com", + books_url="https://example.com", + covers_url="https://example.com/images/covers", + search_url="https://example.com/search?q=", + ) + self.connector = Connector("example.com") + + work_file = pathlib.Path(__file__).parent.joinpath("../data/bw_work.json") + edition_file = pathlib.Path(__file__).parent.joinpath("../data/bw_edition.json") self.work_data = json.loads(work_file.read_bytes()) self.edition_data = json.loads(edition_file.read_bytes()) - def test_format_search_result(self): - ''' create a SearchResult object from search response json ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/bw_search.json') + """ create a SearchResult object from search response json """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_search.json") search_data = json.loads(datafile.read_bytes()) results = self.connector.parse_search_data(search_data) self.assertIsInstance(results, list) result = self.connector.format_search_result(results[0]) self.assertIsInstance(result, SearchResult) - self.assertEqual(result.title, 'Jonathan Strange and Mr Norrell') - self.assertEqual(result.key, 'https://example.com/book/122') - self.assertEqual(result.author, 'Susanna Clarke') + self.assertEqual(result.title, "Jonathan Strange and Mr Norrell") + self.assertEqual(result.key, "https://example.com/book/122") + self.assertEqual(result.author, "Susanna Clarke") self.assertEqual(result.year, 2017) self.assertEqual(result.connector, self.connector) diff --git a/bookwyrm/tests/connectors/test_connector_manager.py b/bookwyrm/tests/connectors/test_connector_manager.py index 783b5a276..4410e011c 100644 --- a/bookwyrm/tests/connectors/test_connector_manager.py +++ b/bookwyrm/tests/connectors/test_connector_manager.py @@ -1,54 +1,49 @@ -''' interface between the app and various connectors ''' +""" interface between the app and various connectors """ from django.test import TestCase from bookwyrm import models from bookwyrm.connectors import connector_manager -from bookwyrm.connectors.bookwyrm_connector \ - import Connector as BookWyrmConnector -from bookwyrm.connectors.self_connector \ - import Connector as SelfConnector +from bookwyrm.connectors.bookwyrm_connector import Connector as BookWyrmConnector +from bookwyrm.connectors.self_connector import Connector as SelfConnector class ConnectorManager(TestCase): - ''' interface between the app and various connectors ''' + """ interface between the app and various connectors """ + def setUp(self): - ''' we'll need some books and a connector info entry ''' - self.work = models.Work.objects.create( - title='Example Work' - ) + """ we'll need some books and a connector info entry """ + self.work = models.Work.objects.create(title="Example Work") self.edition = models.Edition.objects.create( - title='Example Edition', - parent_work=self.work + title="Example Edition", parent_work=self.work ) self.work.default_edition = self.edition self.work.save() self.connector = models.Connector.objects.create( - identifier='test_connector', + identifier="test_connector", priority=1, local=True, - connector_file='self_connector', - base_url='http://test.com/', - books_url='http://test.com/', - covers_url='http://test.com/', + connector_file="self_connector", + base_url="http://test.com/", + books_url="http://test.com/", + covers_url="http://test.com/", ) - def test_get_or_create_connector(self): - ''' loads a connector if the data source is known or creates one ''' - remote_id = 'https://example.com/object/1' + """ loads a connector if the data source is known or creates one """ + remote_id = "https://example.com/object/1" connector = connector_manager.get_or_create_connector(remote_id) self.assertIsInstance(connector, BookWyrmConnector) - self.assertEqual(connector.identifier, 'example.com') - self.assertEqual(connector.base_url, 'https://example.com') + self.assertEqual(connector.identifier, "example.com") + self.assertEqual(connector.base_url, "https://example.com") same_connector = connector_manager.get_or_create_connector(remote_id) self.assertEqual(connector.identifier, same_connector.identifier) def test_get_connectors(self): - ''' load all connectors ''' - remote_id = 'https://example.com/object/1' + """ load all connectors """ + remote_id = "https://example.com/object/1" connector_manager.get_or_create_connector(remote_id) connectors = list(connector_manager.get_connectors()) self.assertEqual(len(connectors), 2) @@ -56,28 +51,28 @@ class ConnectorManager(TestCase): self.assertIsInstance(connectors[1], BookWyrmConnector) def test_search(self): - ''' search all connectors ''' - results = connector_manager.search('Example') + """ search all connectors """ + results = connector_manager.search("Example") self.assertEqual(len(results), 1) - self.assertIsInstance(results[0]['connector'], SelfConnector) - self.assertEqual(len(results[0]['results']), 1) - self.assertEqual(results[0]['results'][0].title, 'Example Edition') + self.assertIsInstance(results[0]["connector"], SelfConnector) + self.assertEqual(len(results[0]["results"]), 1) + self.assertEqual(results[0]["results"][0].title, "Example Edition") def test_local_search(self): - ''' search only the local database ''' - results = connector_manager.local_search('Example') + """ search only the local database """ + results = connector_manager.local_search("Example") self.assertEqual(len(results), 1) - self.assertEqual(results[0].title, 'Example Edition') + self.assertEqual(results[0].title, "Example Edition") def test_first_search_result(self): - ''' only get one search result ''' - result = connector_manager.first_search_result('Example') - self.assertEqual(result.title, 'Example Edition') - no_result = connector_manager.first_search_result('dkjfhg') + """ only get one search result """ + result = connector_manager.first_search_result("Example") + self.assertEqual(result.title, "Example Edition") + no_result = connector_manager.first_search_result("dkjfhg") self.assertIsNone(no_result) def test_load_connector(self): - ''' load a connector object from the database entry ''' + """ load a connector object from the database entry """ connector = connector_manager.load_connector(self.connector) self.assertIsInstance(connector, SelfConnector) - self.assertEqual(connector.identifier, 'test_connector') + self.assertEqual(connector.identifier, "test_connector") diff --git a/bookwyrm/tests/connectors/test_openlibrary_connector.py b/bookwyrm/tests/connectors/test_openlibrary_connector.py index a174300a9..bb018830e 100644 --- a/bookwyrm/tests/connectors/test_openlibrary_connector.py +++ b/bookwyrm/tests/connectors/test_openlibrary_connector.py @@ -1,4 +1,4 @@ -''' testing book data connectors ''' +""" testing book data connectors """ import json import pathlib from unittest.mock import patch @@ -9,253 +9,231 @@ import responses from bookwyrm import models from bookwyrm.connectors.openlibrary import Connector from bookwyrm.connectors.openlibrary import get_languages, get_description -from bookwyrm.connectors.openlibrary import pick_default_edition, \ - get_openlibrary_key +from bookwyrm.connectors.openlibrary import pick_default_edition, get_openlibrary_key from bookwyrm.connectors.abstract_connector import SearchResult from bookwyrm.connectors.connector_manager import ConnectorException class Openlibrary(TestCase): - ''' test loading data from openlibrary.org ''' - def setUp(self): - ''' creates the connector we'll use ''' - models.Connector.objects.create( - identifier='openlibrary.org', - name='OpenLibrary', - connector_file='openlibrary', - base_url='https://openlibrary.org', - books_url='https://openlibrary.org', - covers_url='https://covers.openlibrary.org', - search_url='https://openlibrary.org/search?q=', - isbn_search_url='https://openlibrary.org/isbn', - ) - self.connector = Connector('openlibrary.org') + """ test loading data from openlibrary.org """ - work_file = pathlib.Path(__file__).parent.joinpath( - '../data/ol_work.json') - edition_file = pathlib.Path(__file__).parent.joinpath( - '../data/ol_edition.json') + def setUp(self): + """ creates the connector we'll use """ + models.Connector.objects.create( + identifier="openlibrary.org", + name="OpenLibrary", + connector_file="openlibrary", + base_url="https://openlibrary.org", + books_url="https://openlibrary.org", + covers_url="https://covers.openlibrary.org", + search_url="https://openlibrary.org/search?q=", + isbn_search_url="https://openlibrary.org/isbn", + ) + self.connector = Connector("openlibrary.org") + + work_file = pathlib.Path(__file__).parent.joinpath("../data/ol_work.json") + edition_file = pathlib.Path(__file__).parent.joinpath("../data/ol_edition.json") edition_list_file = pathlib.Path(__file__).parent.joinpath( - '../data/ol_edition_list.json') + "../data/ol_edition_list.json" + ) self.work_data = json.loads(work_file.read_bytes()) self.edition_data = json.loads(edition_file.read_bytes()) self.edition_list_data = json.loads(edition_list_file.read_bytes()) - def test_get_remote_id_from_data(self): - ''' format the remote id from the data ''' - data = {'key': '/work/OL1234W'} + """ format the remote id from the data """ + data = {"key": "/work/OL1234W"} result = self.connector.get_remote_id_from_data(data) - self.assertEqual(result, 'https://openlibrary.org/work/OL1234W') + self.assertEqual(result, "https://openlibrary.org/work/OL1234W") # error handlding with self.assertRaises(ConnectorException): self.connector.get_remote_id_from_data({}) - def test_is_work_data(self): - ''' detect if the loaded json is a work ''' + """ detect if the loaded json is a work """ self.assertEqual(self.connector.is_work_data(self.work_data), True) self.assertEqual(self.connector.is_work_data(self.edition_data), False) - @responses.activate def test_get_edition_from_work_data(self): - ''' loads a list of editions ''' - data = {'key': '/work/OL1234W'} + """ loads a list of editions """ + data = {"key": "/work/OL1234W"} responses.add( responses.GET, - 'https://openlibrary.org/work/OL1234W/editions', - json={'entries': []}, - status=200) - with patch('bookwyrm.connectors.openlibrary.pick_default_edition') \ - as pick_edition: - pick_edition.return_value = 'hi' + "https://openlibrary.org/work/OL1234W/editions", + json={"entries": []}, + status=200, + ) + with patch( + "bookwyrm.connectors.openlibrary.pick_default_edition" + ) as pick_edition: + pick_edition.return_value = "hi" result = self.connector.get_edition_from_work_data(data) - self.assertEqual(result, 'hi') - + self.assertEqual(result, "hi") @responses.activate def test_get_work_from_edition_data(self): - ''' loads a list of editions ''' - data = {'works': [{'key': '/work/OL1234W'}]} + """ loads a list of editions """ + data = {"works": [{"key": "/work/OL1234W"}]} responses.add( responses.GET, - 'https://openlibrary.org/work/OL1234W', - json={'hi': 'there'}, - status=200) + "https://openlibrary.org/work/OL1234W", + json={"hi": "there"}, + status=200, + ) result = self.connector.get_work_from_edition_data(data) - self.assertEqual(result, {'hi': 'there'}) - + self.assertEqual(result, {"hi": "there"}) @responses.activate def test_get_authors_from_data(self): - ''' find authors in data ''' + """ find authors in data """ responses.add( responses.GET, - 'https://openlibrary.org/authors/OL382982A', + "https://openlibrary.org/authors/OL382982A", json={ "name": "George Elliott", "personal_name": "George Elliott", "last_modified": { "type": "/type/datetime", - "value": "2008-08-31 10:09:33.413686" - }, + "value": "2008-08-31 10:09:33.413686", + }, "key": "/authors/OL453734A", - "type": { - "key": "/type/author" - }, + "type": {"key": "/type/author"}, "id": 1259965, - "revision": 2 + "revision": 2, }, - status=200) + status=200, + ) results = self.connector.get_authors_from_data(self.work_data) result = list(results)[0] self.assertIsInstance(result, models.Author) - self.assertEqual(result.name, 'George Elliott') - self.assertEqual(result.openlibrary_key, 'OL453734A') - + self.assertEqual(result.name, "George Elliott") + self.assertEqual(result.openlibrary_key, "OL453734A") def test_get_cover_url(self): - ''' formats a url that should contain the cover image ''' - blob = ['image'] + """ formats a url that should contain the cover image """ + blob = ["image"] result = self.connector.get_cover_url(blob) - self.assertEqual( - result, 'https://covers.openlibrary.org/b/id/image-L.jpg') + self.assertEqual(result, "https://covers.openlibrary.org/b/id/image-L.jpg") def test_parse_search_result(self): - ''' extract the results from the search json response ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ol_search.json') + """ extract the results from the search json response """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_search.json") search_data = json.loads(datafile.read_bytes()) result = self.connector.parse_search_data(search_data) self.assertIsInstance(result, list) self.assertEqual(len(result), 2) - def test_format_search_result(self): - ''' translate json from openlibrary into SearchResult ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ol_search.json') + """ translate json from openlibrary into SearchResult """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_search.json") search_data = json.loads(datafile.read_bytes()) results = self.connector.parse_search_data(search_data) self.assertIsInstance(results, list) result = self.connector.format_search_result(results[0]) self.assertIsInstance(result, SearchResult) - self.assertEqual(result.title, 'This Is How You Lose the Time War') - self.assertEqual( - result.key, 'https://openlibrary.org/works/OL20639540W') - self.assertEqual(result.author, 'Amal El-Mohtar, Max Gladstone') + self.assertEqual(result.title, "This Is How You Lose the Time War") + self.assertEqual(result.key, "https://openlibrary.org/works/OL20639540W") + self.assertEqual(result.author, "Amal El-Mohtar, Max Gladstone") self.assertEqual(result.year, 2019) self.assertEqual(result.connector, self.connector) - def test_parse_isbn_search_result(self): - ''' extract the results from the search json response ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ol_isbn_search.json') + """ extract the results from the search json response """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_isbn_search.json") search_data = json.loads(datafile.read_bytes()) result = self.connector.parse_isbn_search_data(search_data) self.assertIsInstance(result, list) self.assertEqual(len(result), 1) - def test_format_isbn_search_result(self): - ''' translate json from openlibrary into SearchResult ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ol_isbn_search.json') + """ translate json from openlibrary into SearchResult """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_isbn_search.json") search_data = json.loads(datafile.read_bytes()) results = self.connector.parse_isbn_search_data(search_data) self.assertIsInstance(results, list) result = self.connector.format_isbn_search_result(results[0]) self.assertIsInstance(result, SearchResult) - self.assertEqual(result.title, 'Les ombres errantes') - self.assertEqual( - result.key, 'https://openlibrary.org/books/OL16262504M') - self.assertEqual(result.author, 'Pascal Quignard') - self.assertEqual(result.year, '2002') + self.assertEqual(result.title, "Les ombres errantes") + self.assertEqual(result.key, "https://openlibrary.org/books/OL16262504M") + self.assertEqual(result.author, "Pascal Quignard") + self.assertEqual(result.year, "2002") self.assertEqual(result.connector, self.connector) - @responses.activate def test_load_edition_data(self): - ''' format url from key and make request ''' - key = 'OL1234W' + """ format url from key and make request """ + key = "OL1234W" responses.add( responses.GET, - 'https://openlibrary.org/works/OL1234W/editions', - json={'hi': 'there'} + "https://openlibrary.org/works/OL1234W/editions", + json={"hi": "there"}, ) result = self.connector.load_edition_data(key) - self.assertEqual(result, {'hi': 'there'}) - + self.assertEqual(result, {"hi": "there"}) @responses.activate def test_expand_book_data(self): - ''' given a book, get more editions ''' - work = models.Work.objects.create( - title='Test Work', openlibrary_key='OL1234W') - edition = models.Edition.objects.create( - title='Test Edition', parent_work=work) + """ given a book, get more editions """ + work = models.Work.objects.create(title="Test Work", openlibrary_key="OL1234W") + edition = models.Edition.objects.create(title="Test Edition", parent_work=work) responses.add( responses.GET, - 'https://openlibrary.org/works/OL1234W/editions', - json={'entries': []}, + "https://openlibrary.org/works/OL1234W/editions", + json={"entries": []}, ) with patch( - 'bookwyrm.connectors.abstract_connector.AbstractConnector.' \ - 'create_edition_from_data'): + "bookwyrm.connectors.abstract_connector.AbstractConnector." + "create_edition_from_data" + ): self.connector.expand_book_data(edition) self.connector.expand_book_data(work) - def test_get_description(self): - ''' should do some cleanup on the description data ''' - description = get_description(self.work_data['description']) - expected = 'First in the Old Kingdom/Abhorsen series.' + """ should do some cleanup on the description data """ + description = get_description(self.work_data["description"]) + expected = "First in the Old Kingdom/Abhorsen series." self.assertEqual(description, expected) - def test_get_openlibrary_key(self): - ''' extracts the uuid ''' - key = get_openlibrary_key('/books/OL27320736M') - self.assertEqual(key, 'OL27320736M') - + """ extracts the uuid """ + key = get_openlibrary_key("/books/OL27320736M") + self.assertEqual(key, "OL27320736M") def test_get_languages(self): - ''' looks up languages from a list ''' - languages = get_languages(self.edition_data['languages']) - self.assertEqual(languages, ['English']) - + """ looks up languages from a list """ + languages = get_languages(self.edition_data["languages"]) + self.assertEqual(languages, ["English"]) def test_pick_default_edition(self): - ''' detect if the loaded json is an edition ''' - edition = pick_default_edition(self.edition_list_data['entries']) - self.assertEqual(edition['key'], '/books/OL9788823M') - + """ detect if the loaded json is an edition """ + edition = pick_default_edition(self.edition_list_data["entries"]) + self.assertEqual(edition["key"], "/books/OL9788823M") @responses.activate def test_create_edition_from_data(self): - ''' okay but can it actually create an edition with proper metadata ''' - work = models.Work.objects.create(title='Hello') + """ okay but can it actually create an edition with proper metadata """ + work = models.Work.objects.create(title="Hello") responses.add( responses.GET, - 'https://openlibrary.org/authors/OL382982A', - json={'hi': 'there'}, - status=200) - with patch('bookwyrm.connectors.openlibrary.Connector.' \ - 'get_authors_from_data') as mock: + "https://openlibrary.org/authors/OL382982A", + json={"hi": "there"}, + status=200, + ) + with patch( + "bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data" + ) as mock: mock.return_value = [] - result = self.connector.create_edition_from_data( - work, self.edition_data) + result = self.connector.create_edition_from_data(work, self.edition_data) self.assertEqual(result.parent_work, work) - self.assertEqual(result.title, 'Sabriel') - self.assertEqual(result.isbn_10, '0060273224') + self.assertEqual(result.title, "Sabriel") + self.assertEqual(result.isbn_10, "0060273224") self.assertIsNotNone(result.description) - self.assertEqual(result.languages[0], 'English') - self.assertEqual(result.publishers[0], 'Harper Trophy') + self.assertEqual(result.languages[0], "English") + self.assertEqual(result.publishers[0], "Harper Trophy") self.assertEqual(result.pages, 491) - self.assertEqual(result.subjects[0], 'Fantasy.') - self.assertEqual(result.physical_format, 'Hardcover') + self.assertEqual(result.subjects[0], "Fantasy.") + self.assertEqual(result.physical_format, "Hardcover") diff --git a/bookwyrm/tests/connectors/test_self_connector.py b/bookwyrm/tests/connectors/test_self_connector.py index 0fc789556..9925f5943 100644 --- a/bookwyrm/tests/connectors/test_self_connector.py +++ b/bookwyrm/tests/connectors/test_self_connector.py @@ -1,4 +1,4 @@ -''' testing book data connectors ''' +""" testing book data connectors """ import datetime from django.test import TestCase from django.utils import timezone @@ -9,100 +9,98 @@ from bookwyrm.settings import DOMAIN class SelfConnector(TestCase): - ''' just uses local data ''' + """ just uses local data """ + def setUp(self): - ''' creating the connector ''' + """ creating the connector """ models.Connector.objects.create( identifier=DOMAIN, - name='Local', + name="Local", local=True, - connector_file='self_connector', - base_url='https://%s' % DOMAIN, - books_url='https://%s/book' % DOMAIN, - covers_url='https://%s/images/covers' % DOMAIN, - search_url='https://%s/search?q=' % DOMAIN, + connector_file="self_connector", + base_url="https://%s" % DOMAIN, + books_url="https://%s/book" % DOMAIN, + covers_url="https://%s/images/covers" % DOMAIN, + search_url="https://%s/search?q=" % DOMAIN, priority=1, ) self.connector = Connector(DOMAIN) - def test_format_search_result(self): - ''' create a SearchResult ''' - author = models.Author.objects.create(name='Anonymous') + """ create a SearchResult """ + author = models.Author.objects.create(name="Anonymous") edition = models.Edition.objects.create( - title='Edition of Example Work', + title="Edition of Example Work", published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc), ) edition.authors.add(author) - result = self.connector.search('Edition of Example')[0] - self.assertEqual(result.title, 'Edition of Example Work') + result = self.connector.search("Edition of Example")[0] + self.assertEqual(result.title, "Edition of Example Work") self.assertEqual(result.key, edition.remote_id) - self.assertEqual(result.author, 'Anonymous') + self.assertEqual(result.author, "Anonymous") self.assertEqual(result.year, 1980) self.assertEqual(result.connector, self.connector) - def test_search_rank(self): - ''' prioritize certain results ''' - author = models.Author.objects.create(name='Anonymous') + """ prioritize certain results """ + author = models.Author.objects.create(name="Anonymous") edition = models.Edition.objects.create( - title='Edition of Example Work', + title="Edition of Example Work", published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc), - parent_work=models.Work.objects.create(title='') + parent_work=models.Work.objects.create(title=""), ) # author text is rank C edition.authors.add(author) # series is rank D models.Edition.objects.create( - title='Another Edition', - series='Anonymous', - parent_work=models.Work.objects.create(title='') + title="Another Edition", + series="Anonymous", + parent_work=models.Work.objects.create(title=""), ) # subtitle is rank B models.Edition.objects.create( - title='More Editions', - subtitle='The Anonymous Edition', - parent_work=models.Work.objects.create(title='') + title="More Editions", + subtitle="The Anonymous Edition", + parent_work=models.Work.objects.create(title=""), ) # title is rank A - models.Edition.objects.create(title='Anonymous') + models.Edition.objects.create(title="Anonymous") # doesn't rank in this search edition = models.Edition.objects.create( - title='An Edition', - parent_work=models.Work.objects.create(title='') + title="An Edition", parent_work=models.Work.objects.create(title="") ) - results = self.connector.search('Anonymous') + results = self.connector.search("Anonymous") self.assertEqual(len(results), 3) - self.assertEqual(results[0].title, 'Anonymous') - self.assertEqual(results[1].title, 'More Editions') - self.assertEqual(results[2].title, 'Edition of Example Work') - + self.assertEqual(results[0].title, "Anonymous") + self.assertEqual(results[1].title, "More Editions") + self.assertEqual(results[2].title, "Edition of Example Work") def test_search_multiple_editions(self): - ''' it should get rid of duplicate editions for the same work ''' - work = models.Work.objects.create(title='Work Title') + """ it should get rid of duplicate editions for the same work """ + work = models.Work.objects.create(title="Work Title") edition_1 = models.Edition.objects.create( - title='Edition 1 Title', parent_work=work) + title="Edition 1 Title", parent_work=work + ) edition_2 = models.Edition.objects.create( - title='Edition 2 Title', parent_work=work) - edition_3 = models.Edition.objects.create( - title='Fish', parent_work=work) + title="Edition 2 Title", parent_work=work + ) + edition_3 = models.Edition.objects.create(title="Fish", parent_work=work) work.default_edition = edition_2 work.save() # pick the best edition - results = self.connector.search('Edition 1 Title') + results = self.connector.search("Edition 1 Title") self.assertEqual(len(results), 1) self.assertEqual(results[0].key, edition_1.remote_id) # pick the default edition when no match is best - results = self.connector.search('Edition Title') + results = self.connector.search("Edition Title") self.assertEqual(len(results), 1) self.assertEqual(results[0].key, edition_2.remote_id) # only matches one edition, so no deduplication takes place - results = self.connector.search('Fish') + results = self.connector.search("Fish") self.assertEqual(len(results), 1) self.assertEqual(results[0].key, edition_3.remote_id) diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 11b944d9a..930f3a533 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -1,4 +1,4 @@ -''' testing model activitypub utilities ''' +""" testing model activitypub utilities """ from unittest.mock import patch from collections import namedtuple from dataclasses import dataclass @@ -12,238 +12,249 @@ from bookwyrm.models import base_model from bookwyrm.models.activitypub_mixin import ActivitypubMixin from bookwyrm.models.activitypub_mixin import ActivityMixin, ObjectMixin + class ActivitypubMixins(TestCase): - ''' functionality shared across models ''' + """ functionality shared across models """ + def setUp(self): - ''' shared data ''' + """ shared data """ self.local_user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse') - self.local_user.remote_id = 'http://example.com/a/b' + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) + self.local_user.remote_id = "http://example.com/a/b" self.local_user.save(broadcast=False) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', + "rat", + "rat@rat.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) self.object_mock = { - 'to': 'to field', 'cc': 'cc field', - 'content': 'hi', 'id': 'bip', 'type': 'Test', - 'published': '2020-12-04T17:52:22.623807+00:00', + "to": "to field", + "cc": "cc field", + "content": "hi", + "id": "bip", + "type": "Test", + "published": "2020-12-04T17:52:22.623807+00:00", } - # ActivitypubMixin def test_to_activity(self): - ''' model to ActivityPub json ''' + """ model to ActivityPub json """ + @dataclass(init=False) class TestActivity(ActivityObject): - ''' real simple mock ''' - type: str = 'Test' + """ real simple mock """ + + type: str = "Test" class TestModel(ActivitypubMixin, base_model.BookWyrmModel): - ''' real simple mock model because BookWyrmModel is abstract ''' + """ real simple mock model because BookWyrmModel is abstract """ instance = TestModel() - instance.remote_id = 'https://www.example.com/test' + instance.remote_id = "https://www.example.com/test" instance.activity_serializer = TestActivity activity = instance.to_activity() self.assertIsInstance(activity, dict) - self.assertEqual(activity['id'], 'https://www.example.com/test') - self.assertEqual(activity['type'], 'Test') - + self.assertEqual(activity["id"], "https://www.example.com/test") + self.assertEqual(activity["type"], "Test") def test_find_existing_by_remote_id(self): - ''' attempt to match a remote id to an object in the db ''' + """ attempt to match a remote id to an object in the db """ # uses a different remote id scheme # this isn't really part of this test directly but it's helpful to state book = models.Edition.objects.create( - title='Test Edition', remote_id='http://book.com/book') + title="Test Edition", remote_id="http://book.com/book" + ) - self.assertEqual(book.origin_id, 'http://book.com/book') - self.assertNotEqual(book.remote_id, 'http://book.com/book') + self.assertEqual(book.origin_id, "http://book.com/book") + self.assertNotEqual(book.remote_id, "http://book.com/book") # uses subclasses - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Comment.objects.create( - user=self.local_user, content='test status', book=book, \ - remote_id='https://comment.net') + user=self.local_user, + content="test status", + book=book, + remote_id="https://comment.net", + ) - result = models.User.find_existing_by_remote_id('hi') + result = models.User.find_existing_by_remote_id("hi") self.assertIsNone(result) - result = models.User.find_existing_by_remote_id( - 'http://example.com/a/b') + result = models.User.find_existing_by_remote_id("http://example.com/a/b") self.assertEqual(result, self.local_user) # test using origin id - result = models.Edition.find_existing_by_remote_id( - 'http://book.com/book') + result = models.Edition.find_existing_by_remote_id("http://book.com/book") self.assertEqual(result, book) # test subclass match - result = models.Status.find_existing_by_remote_id( - 'https://comment.net') - + result = models.Status.find_existing_by_remote_id("https://comment.net") def test_find_existing(self): - ''' match a blob of data to a model ''' + """ match a blob of data to a model """ book = models.Edition.objects.create( - title='Test edition', - openlibrary_key='OL1234', + title="Test edition", + openlibrary_key="OL1234", ) - result = models.Edition.find_existing( - {'openlibraryKey': 'OL1234'}) + result = models.Edition.find_existing({"openlibraryKey": "OL1234"}) self.assertEqual(result, book) - def test_get_recipients_public_object(self): - ''' determines the recipients for an object's broadcast ''' - MockSelf = namedtuple('Self', ('privacy')) - mock_self = MockSelf('public') + """ determines the recipients for an object's broadcast """ + MockSelf = namedtuple("Self", ("privacy")) + mock_self = MockSelf("public") recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_no_followers(self): - ''' determines the recipients for a user's object broadcast ''' - MockSelf = namedtuple('Self', ('privacy', 'user')) - mock_self = MockSelf('public', self.local_user) + """ determines the recipients for a user's object broadcast """ + MockSelf = namedtuple("Self", ("privacy", "user")) + mock_self = MockSelf("public", self.local_user) recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 0) - def test_get_recipients_public_user_object(self): - ''' determines the recipients for a user's object broadcast ''' - MockSelf = namedtuple('Self', ('privacy', 'user')) - mock_self = MockSelf('public', self.local_user) + """ determines the recipients for a user's object broadcast """ + MockSelf = namedtuple("Self", ("privacy", "user")) + mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - def test_get_recipients_public_user_object_with_mention(self): - ''' determines the recipients for a user's object broadcast ''' - MockSelf = namedtuple('Self', ('privacy', 'user')) - mock_self = MockSelf('public', self.local_user) + """ determines the recipients for a user's object broadcast """ + MockSelf = namedtuple("Self", ("privacy", "user")) + mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( - 'nutria', 'nutria@nutria.com', 'nutriaword', + "nutria", + "nutria@nutria.com", + "nutriaword", local=False, - remote_id='https://example.com/users/nutria', - inbox='https://example.com/users/nutria/inbox', - outbox='https://example.com/users/nutria/outbox', + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", ) - MockSelf = namedtuple('Self', ('privacy', 'user', 'recipients')) - mock_self = MockSelf('public', self.local_user, [another_remote_user]) + MockSelf = namedtuple("Self", ("privacy", "user", "recipients")) + mock_self = MockSelf("public", self.local_user, [another_remote_user]) recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 2) self.assertEqual(recipients[0], another_remote_user.inbox) self.assertEqual(recipients[1], self.remote_user.inbox) - def test_get_recipients_direct(self): - ''' determines the recipients for a user's object broadcast ''' - MockSelf = namedtuple('Self', ('privacy', 'user')) - mock_self = MockSelf('public', self.local_user) + """ determines the recipients for a user's object broadcast """ + MockSelf = namedtuple("Self", ("privacy", "user")) + mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( - 'nutria', 'nutria@nutria.com', 'nutriaword', + "nutria", + "nutria@nutria.com", + "nutriaword", local=False, - remote_id='https://example.com/users/nutria', - inbox='https://example.com/users/nutria/inbox', - outbox='https://example.com/users/nutria/outbox', + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", ) - MockSelf = namedtuple('Self', ('privacy', 'user', 'recipients')) - mock_self = MockSelf('direct', self.local_user, [another_remote_user]) + MockSelf = namedtuple("Self", ("privacy", "user", "recipients")) + mock_self = MockSelf("direct", self.local_user, [another_remote_user]) recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], another_remote_user.inbox) - def test_get_recipients_combine_inboxes(self): - ''' should combine users with the same shared_inbox ''' - self.remote_user.shared_inbox = 'http://example.com/inbox' + """ should combine users with the same shared_inbox """ + self.remote_user.shared_inbox = "http://example.com/inbox" self.remote_user.save(broadcast=False) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( - 'nutria', 'nutria@nutria.com', 'nutriaword', + "nutria", + "nutria@nutria.com", + "nutriaword", local=False, - remote_id='https://example.com/users/nutria', - inbox='https://example.com/users/nutria/inbox', - shared_inbox='http://example.com/inbox', - outbox='https://example.com/users/nutria/outbox', + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + shared_inbox="http://example.com/inbox", + outbox="https://example.com/users/nutria/outbox", ) - MockSelf = namedtuple('Self', ('privacy', 'user')) - mock_self = MockSelf('public', self.local_user) + MockSelf = namedtuple("Self", ("privacy", "user")) + mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) self.local_user.followers.add(another_remote_user) recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 1) - self.assertEqual(recipients[0], 'http://example.com/inbox') - + self.assertEqual(recipients[0], "http://example.com/inbox") def test_get_recipients_software(self): - ''' should differentiate between bookwyrm and other remote users ''' - with patch('bookwyrm.models.user.set_remote_server.delay'): + """ should differentiate between bookwyrm and other remote users """ + with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( - 'nutria', 'nutria@nutria.com', 'nutriaword', + "nutria", + "nutria@nutria.com", + "nutriaword", local=False, - remote_id='https://example.com/users/nutria', - inbox='https://example.com/users/nutria/inbox', - outbox='https://example.com/users/nutria/outbox', + remote_id="https://example.com/users/nutria", + inbox="https://example.com/users/nutria/inbox", + outbox="https://example.com/users/nutria/outbox", bookwyrm_user=False, ) - MockSelf = namedtuple('Self', ('privacy', 'user')) - mock_self = MockSelf('public', self.local_user) + MockSelf = namedtuple("Self", ("privacy", "user")) + mock_self = MockSelf("public", self.local_user) self.local_user.followers.add(self.remote_user) self.local_user.followers.add(another_remote_user) recipients = ActivitypubMixin.get_recipients(mock_self) self.assertEqual(len(recipients), 2) - recipients = ActivitypubMixin.get_recipients( - mock_self, software='bookwyrm') + recipients = ActivitypubMixin.get_recipients(mock_self, software="bookwyrm") self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], self.remote_user.inbox) - recipients = ActivitypubMixin.get_recipients( - mock_self, software='other') + recipients = ActivitypubMixin.get_recipients(mock_self, software="other") self.assertEqual(len(recipients), 1) self.assertEqual(recipients[0], another_remote_user.inbox) - # ObjectMixin def test_object_save_create(self): - ''' should save uneventufully when broadcast is disabled ''' + """ should save uneventufully when broadcast is disabled """ + class Success(Exception): - ''' this means we got to the right method ''' + """ this means we got to the right method """ class ObjectModel(ObjectMixin, base_model.BookWyrmModel): - ''' real simple mock model because BookWyrmModel is abstract ''' - user = models.fields.ForeignKey('User', on_delete=db.models.CASCADE) + """ real simple mock model because BookWyrmModel is abstract """ + + user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE) + def save(self, *args, **kwargs): - with patch('django.db.models.Model.save'): + with patch("django.db.models.Model.save"): super().save(*args, **kwargs) - def broadcast(self, activity, sender, **kwargs):#pylint: disable=arguments-differ - ''' do something ''' + + def broadcast( + self, activity, sender, **kwargs + ): # pylint: disable=arguments-differ + """ do something """ raise Success() - def to_create_activity(self, user):#pylint: disable=arguments-differ + + def to_create_activity(self, user): # pylint: disable=arguments-differ return {} with self.assertRaises(Success): @@ -253,20 +264,24 @@ class ActivitypubMixins(TestCase): ObjectModel(user=self.local_user).save(broadcast=False) ObjectModel(user=None).save() - def test_object_save_update(self): - ''' should save uneventufully when broadcast is disabled ''' + """ should save uneventufully when broadcast is disabled """ + class Success(Exception): - ''' this means we got to the right method ''' + """ this means we got to the right method """ class UpdateObjectModel(ObjectMixin, base_model.BookWyrmModel): - ''' real simple mock model because BookWyrmModel is abstract ''' - user = models.fields.ForeignKey('User', on_delete=db.models.CASCADE) + """ real simple mock model because BookWyrmModel is abstract """ + + user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE) last_edited_by = models.fields.ForeignKey( - 'User', on_delete=db.models.CASCADE) + "User", on_delete=db.models.CASCADE + ) + def save(self, *args, **kwargs): - with patch('django.db.models.Model.save'): + with patch("django.db.models.Model.save"): super().save(*args, **kwargs) + def to_update_activity(self, user): raise Success() @@ -275,87 +290,71 @@ class ActivitypubMixins(TestCase): with self.assertRaises(Success): UpdateObjectModel(id=1, last_edited_by=self.local_user).save() - def test_object_save_delete(self): - ''' should create delete activities when objects are deleted by flag ''' + """ should create delete activities when objects are deleted by flag """ + class ActivitySuccess(Exception): - ''' this means we got to the right method ''' + """ this means we got to the right method """ class DeletableObjectModel(ObjectMixin, base_model.BookWyrmModel): - ''' real simple mock model because BookWyrmModel is abstract ''' - user = models.fields.ForeignKey('User', on_delete=db.models.CASCADE) + """ real simple mock model because BookWyrmModel is abstract """ + + user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE) deleted = models.fields.BooleanField() + def save(self, *args, **kwargs): - with patch('django.db.models.Model.save'): + with patch("django.db.models.Model.save"): super().save(*args, **kwargs) + def to_delete_activity(self, user): raise ActivitySuccess() with self.assertRaises(ActivitySuccess): - DeletableObjectModel( - id=1, user=self.local_user, deleted=True).save() - + DeletableObjectModel(id=1, user=self.local_user, deleted=True).save() def test_to_delete_activity(self): - ''' wrapper for Delete activity ''' - MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) + """ wrapper for Delete activity """ + MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( - 'https://example.com/status/1', - lambda *args: self.object_mock + "https://example.com/status/1", lambda *args: self.object_mock ) - activity = ObjectMixin.to_delete_activity( - mock_self, self.local_user) + activity = ObjectMixin.to_delete_activity(mock_self, self.local_user) + self.assertEqual(activity["id"], "https://example.com/status/1/activity") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["to"], ["%s/followers" % self.local_user.remote_id]) self.assertEqual( - activity['id'], - 'https://example.com/status/1/activity' + activity["cc"], ["https://www.w3.org/ns/activitystreams#Public"] ) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['type'], 'Delete') - self.assertEqual( - activity['to'], - ['%s/followers' % self.local_user.remote_id]) - self.assertEqual( - activity['cc'], - ['https://www.w3.org/ns/activitystreams#Public']) - def test_to_update_activity(self): - ''' ditto above but for Update ''' - MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) + """ ditto above but for Update """ + MockSelf = namedtuple("Self", ("remote_id", "to_activity")) mock_self = MockSelf( - 'https://example.com/status/1', - lambda *args: self.object_mock + "https://example.com/status/1", lambda *args: self.object_mock ) - activity = ObjectMixin.to_update_activity( - mock_self, self.local_user) + activity = ObjectMixin.to_update_activity(mock_self, self.local_user) self.assertIsNotNone( - re.match( - r'^https:\/\/example\.com\/status\/1#update\/.*', - activity['id'] - ) + re.match(r"^https:\/\/example\.com\/status\/1#update\/.*", activity["id"]) ) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['type'], 'Update') + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["type"], "Update") self.assertEqual( - activity['to'], - ['https://www.w3.org/ns/activitystreams#Public']) - self.assertIsInstance(activity['object'], dict) - + activity["to"], ["https://www.w3.org/ns/activitystreams#Public"] + ) + self.assertIsInstance(activity["object"], dict) # Activity mixin def test_to_undo_activity(self): - ''' and again, for Undo ''' - MockSelf = namedtuple('Self', ('remote_id', 'to_activity', 'user')) + """ and again, for Undo """ + MockSelf = namedtuple("Self", ("remote_id", "to_activity", "user")) mock_self = MockSelf( - 'https://example.com/status/1', + "https://example.com/status/1", lambda *args: self.object_mock, self.local_user, ) activity = ActivityMixin.to_undo_activity(mock_self) - self.assertEqual( - activity['id'], - 'https://example.com/status/1#undo' - ) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['type'], 'Undo') - self.assertIsInstance(activity['object'], dict) + self.assertEqual(activity["id"], "https://example.com/status/1#undo") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["type"], "Undo") + self.assertIsInstance(activity["object"], dict) diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index ab388efe0..4479d1560 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -1,42 +1,41 @@ -''' testing models ''' +""" testing models """ from django.test import TestCase from bookwyrm import models from bookwyrm.models import base_model from bookwyrm.settings import DOMAIN + class BaseModel(TestCase): - ''' functionality shared across models ''' + """ functionality shared across models """ + def test_remote_id(self): - ''' these should be generated ''' + """ these should be generated """ instance = base_model.BookWyrmModel() instance.id = 1 expected = instance.get_remote_id() - self.assertEqual(expected, 'https://%s/bookwyrmmodel/1' % DOMAIN) + self.assertEqual(expected, "https://%s/bookwyrmmodel/1" % DOMAIN) def test_remote_id_with_user(self): - ''' format of remote id when there's a user object ''' + """ format of remote id when there's a user object """ user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse') + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) instance = base_model.BookWyrmModel() instance.user = user instance.id = 1 expected = instance.get_remote_id() - self.assertEqual( - expected, - 'https://%s/user/mouse/bookwyrmmodel/1' % DOMAIN) + self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN) def test_execute_after_save(self): - ''' this function sets remote ids after creation ''' + """ this function sets remote ids after creation """ # using Work because it BookWrymModel is abstract and this requires save # Work is a relatively not-fancy model. - instance = models.Work.objects.create(title='work title') + instance = models.Work.objects.create(title="work title") instance.remote_id = None base_model.execute_after_save(None, instance, True) self.assertEqual( - instance.remote_id, - 'https://%s/book/%d' % (DOMAIN, instance.id) + instance.remote_id, "https://%s/book/%d" % (DOMAIN, instance.id) ) # shouldn't set remote_id if it's not created diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index b4a099d05..14ab0c572 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ from dateutil.parser import parse from django.test import TestCase from django.utils import timezone @@ -8,88 +8,80 @@ from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10 class Book(TestCase): - ''' not too much going on in the books model but here we are ''' + """ not too much going on in the books model but here we are """ + def setUp(self): - ''' we'll need some books ''' + """ we'll need some books """ self.work = models.Work.objects.create( - title='Example Work', - remote_id='https://example.com/book/1' + title="Example Work", remote_id="https://example.com/book/1" ) self.first_edition = models.Edition.objects.create( - title='Example Edition', + title="Example Edition", parent_work=self.work, ) self.second_edition = models.Edition.objects.create( - title='Another Example Edition', + title="Another Example Edition", parent_work=self.work, ) def test_remote_id(self): - ''' fanciness with remote/origin ids ''' - remote_id = 'https://%s/book/%d' % (settings.DOMAIN, self.work.id) + """ fanciness with remote/origin ids """ + remote_id = "https://%s/book/%d" % (settings.DOMAIN, self.work.id) self.assertEqual(self.work.get_remote_id(), remote_id) self.assertEqual(self.work.remote_id, remote_id) def test_create_book(self): - ''' you shouldn't be able to create Books (only editions and works) ''' - self.assertRaises( - ValueError, - models.Book.objects.create, - title='Invalid Book' - ) + """ you shouldn't be able to create Books (only editions and works) """ + self.assertRaises(ValueError, models.Book.objects.create, title="Invalid Book") def test_isbn_10_to_13(self): - ''' checksums and so on ''' - isbn_10 = '178816167X' + """ checksums and so on """ + isbn_10 = "178816167X" isbn_13 = isbn_10_to_13(isbn_10) - self.assertEqual(isbn_13, '9781788161671') + self.assertEqual(isbn_13, "9781788161671") - isbn_10 = '1-788-16167-X' + isbn_10 = "1-788-16167-X" isbn_13 = isbn_10_to_13(isbn_10) - self.assertEqual(isbn_13, '9781788161671') - + self.assertEqual(isbn_13, "9781788161671") def test_isbn_13_to_10(self): - ''' checksums and so on ''' - isbn_13 = '9781788161671' + """ checksums and so on """ + isbn_13 = "9781788161671" isbn_10 = isbn_13_to_10(isbn_13) - self.assertEqual(isbn_10, '178816167X') + self.assertEqual(isbn_10, "178816167X") - isbn_13 = '978-1788-16167-1' + isbn_13 = "978-1788-16167-1" isbn_10 = isbn_13_to_10(isbn_13) - self.assertEqual(isbn_10, '178816167X') - + self.assertEqual(isbn_10, "178816167X") def test_get_edition_info(self): - ''' text slug about an edition ''' - book = models.Edition.objects.create(title='Test Edition') - self.assertEqual(book.edition_info, '') + """ text slug about an edition """ + book = models.Edition.objects.create(title="Test Edition") + self.assertEqual(book.edition_info, "") - book.physical_format = 'worm' + book.physical_format = "worm" book.save() - self.assertEqual(book.edition_info, 'worm') + self.assertEqual(book.edition_info, "worm") - book.languages = ['English'] + book.languages = ["English"] book.save() - self.assertEqual(book.edition_info, 'worm') + self.assertEqual(book.edition_info, "worm") - book.languages = ['Glorbish', 'English'] + book.languages = ["Glorbish", "English"] book.save() - self.assertEqual(book.edition_info, 'worm, Glorbish language') + self.assertEqual(book.edition_info, "worm, Glorbish language") - book.published_date = timezone.make_aware(parse('2020')) + book.published_date = timezone.make_aware(parse("2020")) book.save() - self.assertEqual(book.edition_info, 'worm, Glorbish language, 2020') - self.assertEqual( - book.alt_text, 'Test Edition (worm, Glorbish language, 2020)') - + self.assertEqual(book.edition_info, "worm, Glorbish language, 2020") + self.assertEqual(book.alt_text, "Test Edition (worm, Glorbish language, 2020)") def test_get_rank(self): - ''' sets the data quality index for the book ''' + """ sets the data quality index for the book """ # basic rank self.assertEqual(self.first_edition.edition_rank, 0) - self.first_edition.description = 'hi' + self.first_edition.description = "hi" self.first_edition.save() self.assertEqual(self.first_edition.edition_rank, 1) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 24c0fb021..522d16f94 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ from io import BytesIO from collections import namedtuple from dataclasses import dataclass @@ -23,408 +23,388 @@ from bookwyrm.models import fields, User, Status from bookwyrm.models.base_model import BookWyrmModel from bookwyrm.models.activitypub_mixin import ActivitypubMixin -#pylint: disable=too-many-public-methods +# pylint: disable=too-many-public-methods class ActivitypubFields(TestCase): - ''' overwrites standard model feilds to work with activitypub ''' + """ overwrites standard model feilds to work with activitypub """ + def test_validate_remote_id(self): - ''' should look like a url ''' - self.assertIsNone(fields.validate_remote_id('http://www.example.com')) - self.assertIsNone(fields.validate_remote_id('https://www.example.com')) - self.assertIsNone(fields.validate_remote_id('http://exle.com/dlg-23/x')) + """ should look like a url """ + self.assertIsNone(fields.validate_remote_id("http://www.example.com")) + self.assertIsNone(fields.validate_remote_id("https://www.example.com")) + self.assertIsNone(fields.validate_remote_id("http://exle.com/dlg-23/x")) self.assertRaises( - ValidationError, fields.validate_remote_id, - 'http:/example.com/dlfjg-23/x') + ValidationError, fields.validate_remote_id, "http:/example.com/dlfjg-23/x" + ) self.assertRaises( - ValidationError, fields.validate_remote_id, - 'www.example.com/dlfjg-23/x') + ValidationError, fields.validate_remote_id, "www.example.com/dlfjg-23/x" + ) self.assertRaises( - ValidationError, fields.validate_remote_id, - 'http://www.example.com/dlfjg 23/x') + ValidationError, + fields.validate_remote_id, + "http://www.example.com/dlfjg 23/x", + ) def test_activitypub_field_mixin(self): - ''' generic mixin with super basic to and from functionality ''' + """ generic mixin with super basic to and from functionality """ instance = fields.ActivitypubFieldMixin() - self.assertEqual(instance.field_to_activity('fish'), 'fish') - self.assertEqual(instance.field_from_activity('fish'), 'fish') + self.assertEqual(instance.field_to_activity("fish"), "fish") + self.assertEqual(instance.field_from_activity("fish"), "fish") self.assertFalse(instance.deduplication_field) instance = fields.ActivitypubFieldMixin( - activitypub_wrapper='endpoints', activitypub_field='outbox' + activitypub_wrapper="endpoints", activitypub_field="outbox" ) - self.assertEqual( - instance.field_to_activity('fish'), - {'outbox': 'fish'} - ) - self.assertEqual( - instance.field_from_activity({'outbox': 'fish'}), - 'fish' - ) - self.assertEqual(instance.get_activitypub_field(), 'endpoints') + self.assertEqual(instance.field_to_activity("fish"), {"outbox": "fish"}) + self.assertEqual(instance.field_from_activity({"outbox": "fish"}), "fish") + self.assertEqual(instance.get_activitypub_field(), "endpoints") instance = fields.ActivitypubFieldMixin() - instance.name = 'snake_case_name' - self.assertEqual(instance.get_activitypub_field(), 'snakeCaseName') + instance.name = "snake_case_name" + self.assertEqual(instance.get_activitypub_field(), "snakeCaseName") def test_set_field_from_activity(self): - ''' setter from entire json blob ''' + """ setter from entire json blob """ + @dataclass class TestModel: - ''' real simple mock ''' + """ real simple mock """ + field_name: str - mock_model = TestModel(field_name='bip') - TestActivity = namedtuple('test', ('fieldName', 'unrelated')) - data = TestActivity(fieldName='hi', unrelated='bfkjh') + mock_model = TestModel(field_name="bip") + TestActivity = namedtuple("test", ("fieldName", "unrelated")) + data = TestActivity(fieldName="hi", unrelated="bfkjh") instance = fields.ActivitypubFieldMixin() - instance.name = 'field_name' + instance.name = "field_name" instance.set_field_from_activity(mock_model, data) - self.assertEqual(mock_model.field_name, 'hi') + self.assertEqual(mock_model.field_name, "hi") def test_set_activity_from_field(self): - ''' set json field given entire model ''' + """ set json field given entire model """ + @dataclass class TestModel: - ''' real simple mock ''' + """ real simple mock """ + field_name: str unrelated: str - mock_model = TestModel(field_name='bip', unrelated='field') + + mock_model = TestModel(field_name="bip", unrelated="field") instance = fields.ActivitypubFieldMixin() - instance.name = 'field_name' + instance.name = "field_name" data = {} instance.set_activity_from_field(data, mock_model) - self.assertEqual(data['fieldName'], 'bip') + self.assertEqual(data["fieldName"], "bip") def test_remote_id_field(self): - ''' just sets some defaults on charfield ''' + """ just sets some defaults on charfield """ instance = fields.RemoteIdField() self.assertEqual(instance.max_length, 255) self.assertTrue(instance.deduplication_field) with self.assertRaises(ValidationError): - instance.run_validators('http://www.example.com/dlfjg 23/x') + instance.run_validators("http://www.example.com/dlfjg 23/x") def test_username_field(self): - ''' again, just setting defaults on username field ''' + """ again, just setting defaults on username field """ instance = fields.UsernameField() - self.assertEqual(instance.activitypub_field, 'preferredUsername') + self.assertEqual(instance.activitypub_field, "preferredUsername") self.assertEqual(instance.max_length, 150) self.assertEqual(instance.unique, True) with self.assertRaises(ValidationError): - instance.run_validators('mouse') - instance.run_validators('mouseexample.com') - instance.run_validators('mouse@example.c') - instance.run_validators('@example.com') - instance.run_validators('mouse@examplecom') - instance.run_validators('one two@fish.aaaa') - instance.run_validators('a*&@exampke.com') - instance.run_validators('trailingwhite@example.com ') - self.assertIsNone(instance.run_validators('mouse@example.com')) - self.assertIsNone(instance.run_validators('mo-2use@ex3ample.com')) - self.assertIsNone(instance.run_validators('aksdhf@sdkjf-df.cm')) - - self.assertEqual(instance.field_to_activity('test@example.com'), 'test') + instance.run_validators("mouse") + instance.run_validators("mouseexample.com") + instance.run_validators("mouse@example.c") + instance.run_validators("@example.com") + instance.run_validators("mouse@examplecom") + instance.run_validators("one two@fish.aaaa") + instance.run_validators("a*&@exampke.com") + instance.run_validators("trailingwhite@example.com ") + self.assertIsNone(instance.run_validators("mouse@example.com")) + self.assertIsNone(instance.run_validators("mo-2use@ex3ample.com")) + self.assertIsNone(instance.run_validators("aksdhf@sdkjf-df.cm")) + self.assertEqual(instance.field_to_activity("test@example.com"), "test") def test_privacy_field_defaults(self): - ''' post privacy field's many default values ''' + """ post privacy field's many default values """ instance = fields.PrivacyField() self.assertEqual(instance.max_length, 255) self.assertEqual( [c[0] for c in instance.choices], - ['public', 'unlisted', 'followers', 'direct']) - self.assertEqual(instance.default, 'public') + ["public", "unlisted", "followers", "direct"], + ) + self.assertEqual(instance.default, "public") self.assertEqual( - instance.public, 'https://www.w3.org/ns/activitystreams#Public') + instance.public, "https://www.w3.org/ns/activitystreams#Public" + ) def test_privacy_field_set_field_from_activity(self): - ''' translate between to/cc fields and privacy ''' + """ translate between to/cc fields and privacy """ + @dataclass(init=False) class TestActivity(ActivityObject): - ''' real simple mock ''' + """ real simple mock """ + to: List[str] cc: List[str] - id: str = 'http://hi.com' - type: str = 'Test' + id: str = "http://hi.com" + type: str = "Test" class TestPrivacyModel(ActivitypubMixin, BookWyrmModel): - ''' real simple mock model because BookWyrmModel is abstract ''' + """ real simple mock model because BookWyrmModel is abstract """ + privacy_field = fields.PrivacyField() mention_users = fields.TagField(User) user = fields.ForeignKey(User, on_delete=models.CASCADE) - public = 'https://www.w3.org/ns/activitystreams#Public' + public = "https://www.w3.org/ns/activitystreams#Public" data = TestActivity( to=[public], - cc=['bleh'], + cc=["bleh"], ) - model_instance = TestPrivacyModel(privacy_field='direct') - self.assertEqual(model_instance.privacy_field, 'direct') + model_instance = TestPrivacyModel(privacy_field="direct") + self.assertEqual(model_instance.privacy_field, "direct") instance = fields.PrivacyField() - instance.name = 'privacy_field' + instance.name = "privacy_field" instance.set_field_from_activity(model_instance, data) - self.assertEqual(model_instance.privacy_field, 'public') + self.assertEqual(model_instance.privacy_field, "public") - data.to = ['bleh'] + data.to = ["bleh"] data.cc = [] instance.set_field_from_activity(model_instance, data) - self.assertEqual(model_instance.privacy_field, 'direct') + self.assertEqual(model_instance.privacy_field, "direct") - data.to = ['bleh'] - data.cc = [public, 'waah'] + data.to = ["bleh"] + data.cc = [public, "waah"] instance.set_field_from_activity(model_instance, data) - self.assertEqual(model_instance.privacy_field, 'unlisted') + self.assertEqual(model_instance.privacy_field, "unlisted") - - @patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast') + @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") def test_privacy_field_set_activity_from_field(self, _): - ''' translate between to/cc fields and privacy ''' + """ translate between to/cc fields and privacy """ user = User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', - local=True, localname='rat') - public = 'https://www.w3.org/ns/activitystreams#Public' - followers = '%s/followers' % user.remote_id + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) + public = "https://www.w3.org/ns/activitystreams#Public" + followers = "%s/followers" % user.remote_id instance = fields.PrivacyField() - instance.name = 'privacy_field' + instance.name = "privacy_field" - model_instance = Status.objects.create(user=user, content='hi') + model_instance = Status.objects.create(user=user, content="hi") activity = {} instance.set_activity_from_field(activity, model_instance) - self.assertEqual(activity['to'], [public]) - self.assertEqual(activity['cc'], [followers]) + self.assertEqual(activity["to"], [public]) + self.assertEqual(activity["cc"], [followers]) model_instance = Status.objects.create( - user=user, content='hi', privacy='unlisted') + user=user, content="hi", privacy="unlisted" + ) activity = {} instance.set_activity_from_field(activity, model_instance) - self.assertEqual(activity['to'], [followers]) - self.assertEqual(activity['cc'], [public]) + self.assertEqual(activity["to"], [followers]) + self.assertEqual(activity["cc"], [public]) model_instance = Status.objects.create( - user=user, content='hi', privacy='followers') + user=user, content="hi", privacy="followers" + ) activity = {} instance.set_activity_from_field(activity, model_instance) - self.assertEqual(activity['to'], [followers]) - self.assertEqual(activity['cc'], []) + self.assertEqual(activity["to"], [followers]) + self.assertEqual(activity["cc"], []) model_instance = Status.objects.create( user=user, - content='hi', - privacy='direct', + content="hi", + privacy="direct", ) model_instance.mention_users.set([user]) activity = {} instance.set_activity_from_field(activity, model_instance) - self.assertEqual(activity['to'], [user.remote_id]) - self.assertEqual(activity['cc'], []) - + self.assertEqual(activity["to"], [user.remote_id]) + self.assertEqual(activity["cc"], []) def test_foreign_key(self): - ''' should be able to format a related model ''' - instance = fields.ForeignKey('User', on_delete=models.CASCADE) - Serializable = namedtuple('Serializable', ('to_activity', 'remote_id')) - item = Serializable(lambda: {'a': 'b'}, 'https://e.b/c') + """ should be able to format a related model """ + instance = fields.ForeignKey("User", on_delete=models.CASCADE) + Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) + item = Serializable(lambda: {"a": "b"}, "https://e.b/c") # returns the remote_id field of the related object - self.assertEqual(instance.field_to_activity(item), 'https://e.b/c') - + self.assertEqual(instance.field_to_activity(item), "https://e.b/c") @responses.activate def test_foreign_key_from_activity_str(self): - ''' create a new object from a foreign key ''' + """ create a new object from a foreign key """ instance = fields.ForeignKey(User, on_delete=models.CASCADE) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") userdata = json.loads(datafile.read_bytes()) # don't try to load the user icon - del userdata['icon'] + del userdata["icon"] # it shouldn't match with this unrelated user: unrelated_user = User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', - local=True, localname='rat') + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) # test receiving an unknown remote id and loading data responses.add( - responses.GET, - 'https://example.com/user/mouse', - json=userdata, - status=200) - with patch('bookwyrm.models.user.set_remote_server.delay'): - value = instance.field_from_activity( - 'https://example.com/user/mouse') + responses.GET, "https://example.com/user/mouse", json=userdata, status=200 + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + value = instance.field_from_activity("https://example.com/user/mouse") self.assertIsInstance(value, User) self.assertNotEqual(value, unrelated_user) - self.assertEqual(value.remote_id, 'https://example.com/user/mouse') - self.assertEqual(value.name, 'MOUSE?? MOUSE!!') - + self.assertEqual(value.remote_id, "https://example.com/user/mouse") + self.assertEqual(value.name, "MOUSE?? MOUSE!!") def test_foreign_key_from_activity_dict(self): - ''' test recieving activity json ''' + """ test recieving activity json """ instance = fields.ForeignKey(User, on_delete=models.CASCADE) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") userdata = json.loads(datafile.read_bytes()) # don't try to load the user icon - del userdata['icon'] + del userdata["icon"] # it shouldn't match with this unrelated user: unrelated_user = User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', - local=True, localname='rat') - with patch('bookwyrm.models.user.set_remote_server.delay'): + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertIsInstance(value, User) self.assertNotEqual(value, unrelated_user) - self.assertEqual(value.remote_id, 'https://example.com/user/mouse') - self.assertEqual(value.name, 'MOUSE?? MOUSE!!') + self.assertEqual(value.remote_id, "https://example.com/user/mouse") + self.assertEqual(value.name, "MOUSE?? MOUSE!!") # et cetera but we're not testing serializing user json - def test_foreign_key_from_activity_dict_existing(self): - ''' test receiving a dict of an existing object in the db ''' + """ test receiving a dict of an existing object in the db """ instance = fields.ForeignKey(User, on_delete=models.CASCADE) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json' - ) + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") userdata = json.loads(datafile.read_bytes()) user = User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') - user.remote_id = 'https://example.com/user/mouse' + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + user.remote_id = "https://example.com/user/mouse" user.save(broadcast=False) User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', - local=True, localname='rat') + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) - with patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast'): + with patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast"): value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertEqual(value, user) - def test_foreign_key_from_activity_str_existing(self): - ''' test receiving a remote id of an existing object in the db ''' + """ test receiving a remote id of an existing object in the db """ instance = fields.ForeignKey(User, on_delete=models.CASCADE) user = User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', - local=True, localname='rat') + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) value = instance.field_from_activity(user.remote_id) self.assertEqual(value, user) - def test_one_to_one_field(self): - ''' a gussied up foreign key ''' - instance = fields.OneToOneField('User', on_delete=models.CASCADE) - Serializable = namedtuple('Serializable', ('to_activity', 'remote_id')) - item = Serializable(lambda: {'a': 'b'}, 'https://e.b/c') - self.assertEqual(instance.field_to_activity(item), {'a': 'b'}) + """ a gussied up foreign key """ + instance = fields.OneToOneField("User", on_delete=models.CASCADE) + Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) + item = Serializable(lambda: {"a": "b"}, "https://e.b/c") + self.assertEqual(instance.field_to_activity(item), {"a": "b"}) def test_many_to_many_field(self): - ''' lists! ''' - instance = fields.ManyToManyField('User') + """ lists! """ + instance = fields.ManyToManyField("User") - Serializable = namedtuple('Serializable', ('to_activity', 'remote_id')) - Queryset = namedtuple('Queryset', ('all', 'instance')) - item = Serializable(lambda: {'a': 'b'}, 'https://e.b/c') - another_item = Serializable(lambda: {}, 'example.com') + Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) + Queryset = namedtuple("Queryset", ("all", "instance")) + item = Serializable(lambda: {"a": "b"}, "https://e.b/c") + another_item = Serializable(lambda: {}, "example.com") items = Queryset(lambda: [item], another_item) - self.assertEqual(instance.field_to_activity(items), ['https://e.b/c']) + self.assertEqual(instance.field_to_activity(items), ["https://e.b/c"]) - instance = fields.ManyToManyField('User', link_only=True) - instance.name = 'snake_case' - self.assertEqual( - instance.field_to_activity(items), - 'example.com/snake_case' - ) + instance = fields.ManyToManyField("User", link_only=True) + instance.name = "snake_case" + self.assertEqual(instance.field_to_activity(items), "example.com/snake_case") @responses.activate def test_many_to_many_field_from_activity(self): - ''' resolve related fields for a list, takes a list of remote ids ''' + """ resolve related fields for a list, takes a list of remote ids """ instance = fields.ManyToManyField(User) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json' - ) + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") userdata = json.loads(datafile.read_bytes()) # don't try to load the user icon - del userdata['icon'] + del userdata["icon"] # test receiving an unknown remote id and loading data responses.add( - responses.GET, - 'https://example.com/user/mouse', - json=userdata, - status=200) - with patch('bookwyrm.models.user.set_remote_server.delay'): + responses.GET, "https://example.com/user/mouse", json=userdata, status=200 + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): value = instance.field_from_activity( - ['https://example.com/user/mouse', 'bleh'] + ["https://example.com/user/mouse", "bleh"] ) self.assertIsInstance(value, list) self.assertEqual(len(value), 1) self.assertIsInstance(value[0], User) def test_tag_field(self): - ''' a special type of many to many field ''' - instance = fields.TagField('User') + """ a special type of many to many field """ + instance = fields.TagField("User") Serializable = namedtuple( - 'Serializable', - ('to_activity', 'remote_id', 'name_field', 'name') + "Serializable", ("to_activity", "remote_id", "name_field", "name") ) - Queryset = namedtuple('Queryset', ('all', 'instance')) - item = Serializable( - lambda: {'a': 'b'}, 'https://e.b/c', 'name', 'Name') - another_item = Serializable( - lambda: {}, 'example.com', '', '') + Queryset = namedtuple("Queryset", ("all", "instance")) + item = Serializable(lambda: {"a": "b"}, "https://e.b/c", "name", "Name") + another_item = Serializable(lambda: {}, "example.com", "", "") items = Queryset(lambda: [item], another_item) result = instance.field_to_activity(items) self.assertIsInstance(result, list) self.assertEqual(len(result), 1) - self.assertEqual(result[0].href, 'https://e.b/c') - self.assertEqual(result[0].name, 'Name') - self.assertEqual(result[0].type, 'Serializable') - + self.assertEqual(result[0].href, "https://e.b/c") + self.assertEqual(result[0].name, "Name") + self.assertEqual(result[0].type, "Serializable") def test_tag_field_from_activity(self): - ''' loadin' a list of items from Links ''' + """ loadin' a list of items from Links """ # TODO - @responses.activate - @patch('bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast') + @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") def test_image_field(self, _): - ''' storing images ''' + """ storing images """ user = User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) image_file = pathlib.Path(__file__).parent.joinpath( - '../../static/images/default_avi.jpg') + "../../static/images/default_avi.jpg" + ) image = Image.open(image_file) output = BytesIO() image.save(output, format=image.format) - user.avatar.save( - 'test.jpg', - ContentFile(output.getvalue()) - ) + user.avatar.save("test.jpg", ContentFile(output.getvalue())) - output = fields.image_serializer(user.avatar, alt='alt text') + output = fields.image_serializer(user.avatar, alt="alt text") self.assertIsNotNone( re.match( - r'.*\.jpg', + r".*\.jpg", output.url, ) ) - self.assertEqual(output.name, 'alt text') - self.assertEqual(output.type, 'Image') + self.assertEqual(output.name, "alt text") + self.assertEqual(output.type, "Image") instance = fields.ImageField() @@ -433,36 +413,30 @@ class ActivitypubFields(TestCase): responses.add( responses.GET, - 'http://www.example.com/image.jpg', + "http://www.example.com/image.jpg", body=user.avatar.file.read(), - status=200) - loaded_image = instance.field_from_activity( - 'http://www.example.com/image.jpg') + status=200, + ) + loaded_image = instance.field_from_activity("http://www.example.com/image.jpg") self.assertIsInstance(loaded_image, list) self.assertIsInstance(loaded_image[1], ContentFile) - def test_datetime_field(self): - ''' this one is pretty simple, it just has to use isoformat ''' + """ this one is pretty simple, it just has to use isoformat """ instance = fields.DateTimeField() now = timezone.now() self.assertEqual(instance.field_to_activity(now), now.isoformat()) - self.assertEqual( - instance.field_from_activity(now.isoformat()), now - ) - self.assertEqual(instance.field_from_activity('bip'), None) - + self.assertEqual(instance.field_from_activity(now.isoformat()), now) + self.assertEqual(instance.field_from_activity("bip"), None) def test_array_field(self): - ''' idk why it makes them strings but probably for a good reason ''' + """ idk why it makes them strings but probably for a good reason """ instance = fields.ArrayField(fields.IntegerField) - self.assertEqual(instance.field_to_activity([0, 1]), ['0', '1']) - + self.assertEqual(instance.field_to_activity([0, 1]), ["0", "1"]) def test_html_field(self): - ''' sanitizes html, the sanitizer has its own tests ''' + """ sanitizes html, the sanitizer has its own tests """ instance = fields.HtmlField() self.assertEqual( - instance.field_from_activity('

hi

'), - '

hi

' + instance.field_from_activity("

hi

"), "

hi

" ) diff --git a/bookwyrm/tests/models/test_import_model.py b/bookwyrm/tests/models/test_import_model.py index 7ec4e7003..38c3b1ed3 100644 --- a/bookwyrm/tests/models/test_import_model.py +++ b/bookwyrm/tests/models/test_import_model.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ import datetime import json import pathlib @@ -14,165 +14,166 @@ from bookwyrm.connectors.abstract_connector import SearchResult class ImportJob(TestCase): - ''' this is a fancy one!!! ''' + """ this is a fancy one!!! """ + def setUp(self): - ''' data is from a goodreads export of The Raven Tower ''' + """ data is from a goodreads export of The Raven Tower """ read_data = { - 'Book Id': 39395857, - 'Title': 'The Raven Tower', - 'Author': 'Ann Leckie', - 'Author l-f': 'Leckie, Ann', - 'Additional Authors': '', - 'ISBN': '="0356506991"', - 'ISBN13': '="9780356506999"', - 'My Rating': 0, - 'Average Rating': 4.06, - 'Publisher': 'Orbit', - 'Binding': 'Hardcover', - 'Number of Pages': 416, - 'Year Published': 2019, - 'Original Publication Year': 2019, - 'Date Read': '2019/04/12', - 'Date Added': '2019/04/09', - 'Bookshelves': '', - 'Bookshelves with positions': '', - 'Exclusive Shelf': 'read', - 'My Review': '', - 'Spoiler': '', - 'Private Notes': '', - 'Read Count': 1, - 'Recommended For': '', - 'Recommended By': '', - 'Owned Copies': 0, - 'Original Purchase Date': '', - 'Original Purchase Location': '', - 'Condition': '', - 'Condition Description': '', - 'BCID': '' + "Book Id": 39395857, + "Title": "The Raven Tower", + "Author": "Ann Leckie", + "Author l-f": "Leckie, Ann", + "Additional Authors": "", + "ISBN": '="0356506991"', + "ISBN13": '="9780356506999"', + "My Rating": 0, + "Average Rating": 4.06, + "Publisher": "Orbit", + "Binding": "Hardcover", + "Number of Pages": 416, + "Year Published": 2019, + "Original Publication Year": 2019, + "Date Read": "2019/04/12", + "Date Added": "2019/04/09", + "Bookshelves": "", + "Bookshelves with positions": "", + "Exclusive Shelf": "read", + "My Review": "", + "Spoiler": "", + "Private Notes": "", + "Read Count": 1, + "Recommended For": "", + "Recommended By": "", + "Owned Copies": 0, + "Original Purchase Date": "", + "Original Purchase Location": "", + "Condition": "", + "Condition Description": "", + "BCID": "", } currently_reading_data = read_data.copy() - currently_reading_data['Exclusive Shelf'] = 'currently-reading' - currently_reading_data['Date Read'] = '' + currently_reading_data["Exclusive Shelf"] = "currently-reading" + currently_reading_data["Date Read"] = "" unknown_read_data = currently_reading_data.copy() - unknown_read_data['Exclusive Shelf'] = 'read' - unknown_read_data['Date Read'] = '' + unknown_read_data["Exclusive Shelf"] = "read" + unknown_read_data["Date Read"] = "" user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) job = models.ImportJob.objects.create(user=user) self.item_1 = models.ImportItem.objects.create( - job=job, index=1, data=currently_reading_data) - self.item_2 = models.ImportItem.objects.create( - job=job, index=2, data=read_data) + job=job, index=1, data=currently_reading_data + ) + self.item_2 = models.ImportItem.objects.create(job=job, index=2, data=read_data) self.item_3 = models.ImportItem.objects.create( - job=job, index=3, data=unknown_read_data) - + job=job, index=3, data=unknown_read_data + ) def test_isbn(self): - ''' it unquotes the isbn13 field from data ''' - expected = '9780356506999' + """ it unquotes the isbn13 field from data """ + expected = "9780356506999" item = models.ImportItem.objects.get(index=1) self.assertEqual(item.isbn, expected) - def test_shelf(self): - ''' converts to the local shelf typology ''' - expected = 'reading' + """ converts to the local shelf typology """ + expected = "reading" self.assertEqual(self.item_1.shelf, expected) - def test_date_added(self): - ''' converts to the local shelf typology ''' + """ converts to the local shelf typology """ expected = datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc) item = models.ImportItem.objects.get(index=1) self.assertEqual(item.date_added, expected) - def test_date_read(self): - ''' converts to the local shelf typology ''' + """ converts to the local shelf typology """ expected = datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc) item = models.ImportItem.objects.get(index=2) self.assertEqual(item.date_read, expected) - def test_currently_reading_reads(self): - ''' infer currently reading dates where available ''' - expected = [models.ReadThrough( - start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc) - )] + """ infer currently reading dates where available """ + expected = [ + models.ReadThrough( + start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc) + ) + ] actual = models.ImportItem.objects.get(index=1) self.assertEqual(actual.reads[0].start_date, expected[0].start_date) self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date) def test_read_reads(self): - ''' infer read dates where available ''' + """ infer read dates where available """ actual = self.item_2 self.assertEqual( actual.reads[0].start_date, - datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)) + datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc), + ) self.assertEqual( actual.reads[0].finish_date, - datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc)) + datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc), + ) def test_unread_reads(self): - ''' handle books with no read dates ''' + """ handle books with no read dates """ expected = [] actual = models.ImportItem.objects.get(index=3) self.assertEqual(actual.reads, expected) - @responses.activate def test_get_book_from_isbn(self): - ''' search and load books by isbn (9780356506999) ''' + """ search and load books by isbn (9780356506999) """ connector_info = models.Connector.objects.create( - identifier='openlibrary.org', - name='OpenLibrary', - connector_file='openlibrary', - base_url='https://openlibrary.org', - books_url='https://openlibrary.org', - covers_url='https://covers.openlibrary.org', - search_url='https://openlibrary.org/search?q=', + identifier="openlibrary.org", + name="OpenLibrary", + connector_file="openlibrary", + base_url="https://openlibrary.org", + books_url="https://openlibrary.org", + covers_url="https://covers.openlibrary.org", + search_url="https://openlibrary.org/search?q=", priority=3, ) connector = connector_manager.load_connector(connector_info) result = SearchResult( - title='Test Result', - key='https://openlibrary.org/works/OL1234W', - author='An Author', - year='1980', + title="Test Result", + key="https://openlibrary.org/works/OL1234W", + author="An Author", + year="1980", connector=connector, ) - - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ol_edition.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_edition.json") bookdata = json.loads(datafile.read_bytes()) responses.add( responses.GET, - 'https://openlibrary.org/works/OL1234W', + "https://openlibrary.org/works/OL1234W", json=bookdata, - status=200) + status=200, + ) responses.add( responses.GET, - 'https://openlibrary.org/works/OL15832982W', + "https://openlibrary.org/works/OL15832982W", json=bookdata, - status=200) + status=200, + ) responses.add( responses.GET, - 'https://openlibrary.org/authors/OL382982A', - json={'name': 'test author'}, - status=200) + "https://openlibrary.org/authors/OL382982A", + json={"name": "test author"}, + status=200, + ) - with patch( - 'bookwyrm.connectors.abstract_connector.load_more_data.delay'): + with patch("bookwyrm.connectors.abstract_connector.load_more_data.delay"): with patch( - 'bookwyrm.connectors.connector_manager.first_search_result' - ) as search: + "bookwyrm.connectors.connector_manager.first_search_result" + ) as search: search.return_value = result - with patch('bookwyrm.connectors.openlibrary.Connector.' \ - 'get_authors_from_data'): + with patch( + "bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data" + ): book = self.item_1.get_book_from_isbn() - self.assertEqual(book.title, 'Sabriel') + self.assertEqual(book.title, "Sabriel") diff --git a/bookwyrm/tests/models/test_list.py b/bookwyrm/tests/models/test_list.py index d41b81c76..6bc4b796b 100644 --- a/bookwyrm/tests/models/test_list.py +++ b/bookwyrm/tests/models/test_list.py @@ -1,43 +1,41 @@ -''' testing models ''' +""" testing models """ from unittest.mock import patch from django.test import TestCase from bookwyrm import models, settings -@patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') +@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class List(TestCase): - ''' some activitypub oddness ahead ''' + """ some activitypub oddness ahead """ + def setUp(self): - ''' look, a list ''' + """ look, a list """ self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - self.list = models.List.objects.create( - name='Test List', user=self.user) + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + self.list = models.List.objects.create(name="Test List", user=self.user) def test_remote_id(self, _): - ''' shelves use custom remote ids ''' - expected_id = 'https://%s/list/%d' % \ - (settings.DOMAIN, self.list.id) + """ shelves use custom remote ids """ + expected_id = "https://%s/list/%d" % (settings.DOMAIN, self.list.id) self.assertEqual(self.list.get_remote_id(), expected_id) - def test_to_activity(self, _): - ''' jsonify it ''' + """ jsonify it """ activity_json = self.list.to_activity() self.assertIsInstance(activity_json, dict) - self.assertEqual(activity_json['id'], self.list.remote_id) - self.assertEqual(activity_json['totalItems'], 0) - self.assertEqual(activity_json['type'], 'BookList') - self.assertEqual(activity_json['name'], 'Test List') - self.assertEqual(activity_json['owner'], self.user.remote_id) + self.assertEqual(activity_json["id"], self.list.remote_id) + self.assertEqual(activity_json["totalItems"], 0) + self.assertEqual(activity_json["type"], "BookList") + self.assertEqual(activity_json["name"], "Test List") + self.assertEqual(activity_json["owner"], self.user.remote_id) def test_list_item(self, _): - ''' a list entry ''' - work = models.Work.objects.create(title='hello') - book = models.Edition.objects.create(title='hi', parent_work=work) + """ a list entry """ + work = models.Work.objects.create(title="hello") + book = models.Edition.objects.create(title="hi", parent_work=work) item = models.ListItem.objects.create( book_list=self.list, book=book, @@ -47,11 +45,11 @@ class List(TestCase): self.assertTrue(item.approved) add_activity = item.to_add_activity() - self.assertEqual(add_activity['actor'], self.user.remote_id) - self.assertEqual(add_activity['object']['id'], book.remote_id) - self.assertEqual(add_activity['target'], self.list.remote_id) + self.assertEqual(add_activity["actor"], self.user.remote_id) + self.assertEqual(add_activity["object"]["id"], book.remote_id) + self.assertEqual(add_activity["target"], self.list.remote_id) remove_activity = item.to_remove_activity() - self.assertEqual(remove_activity['actor'], self.user.remote_id) - self.assertEqual(remove_activity['object']['id'], book.remote_id) - self.assertEqual(remove_activity['target'], self.list.remote_id) + self.assertEqual(remove_activity["actor"], self.user.remote_id) + self.assertEqual(remove_activity["object"]["id"], book.remote_id) + self.assertEqual(remove_activity["target"], self.list.remote_id) diff --git a/bookwyrm/tests/models/test_readthrough_model.py b/bookwyrm/tests/models/test_readthrough_model.py index 3fcdf1e4a..f69e87790 100644 --- a/bookwyrm/tests/models/test_readthrough_model.py +++ b/bookwyrm/tests/models/test_readthrough_model.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ from django.test import TestCase from django.core.exceptions import ValidationError @@ -6,39 +6,36 @@ from bookwyrm import models, settings class ReadThrough(TestCase): - ''' some activitypub oddness ahead ''' - def setUp(self): - ''' look, a shelf ''' - self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') + """ some activitypub oddness ahead """ - self.work = models.Work.objects.create( - title='Example Work' + def setUp(self): + """ look, a shelf """ + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" ) + self.work = models.Work.objects.create(title="Example Work") + self.edition = models.Edition.objects.create( - title='Example Edition', - parent_work=self.work + title="Example Edition", parent_work=self.work ) self.work.default_edition = self.edition self.work.save() self.readthrough = models.ReadThrough.objects.create( - user=self.user, - book=self.edition) + user=self.user, book=self.edition + ) def test_progress_update(self): - ''' Test progress updates ''' - self.readthrough.create_update() # No-op, no progress yet + """ Test progress updates """ + self.readthrough.create_update() # No-op, no progress yet self.readthrough.progress = 10 self.readthrough.create_update() self.readthrough.progress = 20 self.readthrough.progress_mode = models.ProgressMode.PERCENT self.readthrough.create_update() - updates = self.readthrough.progressupdate_set \ - .order_by('created_date').all() + updates = self.readthrough.progressupdate_set.order_by("created_date").all() self.assertEqual(len(updates), 2) self.assertEqual(updates[0].progress, 10) self.assertEqual(updates[0].mode, models.ProgressMode.PAGE) diff --git a/bookwyrm/tests/models/test_relationship_models.py b/bookwyrm/tests/models/test_relationship_models.py index 0ef534502..0e842b218 100644 --- a/bookwyrm/tests/models/test_relationship_models.py +++ b/bookwyrm/tests/models/test_relationship_models.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ from unittest.mock import patch from django.test import TestCase @@ -6,87 +6,79 @@ from bookwyrm import models class Relationship(TestCase): - ''' following, blocking, stuff like that ''' + """ following, blocking, stuff like that """ + def setUp(self): - ''' we need some users for this ''' - with patch('bookwyrm.models.user.set_remote_server.delay'): + """ we need some users for this """ + with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', + "rat", + "rat@rat.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) self.local_user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse') - self.local_user.remote_id = 'http://local.com/user/mouse' + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) + self.local_user.remote_id = "http://local.com/user/mouse" self.local_user.save(broadcast=False) - def test_user_follows_from_request(self): - ''' convert a follow request into a follow ''' + """ convert a follow request into a follow """ real_broadcast = models.UserFollowRequest.broadcast + def mock_broadcast(_, activity, user): - ''' introspect what's being sent out ''' + """ introspect what's being sent out """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Follow') + self.assertEqual(activity["type"], "Follow") models.UserFollowRequest.broadcast = mock_broadcast request = models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user + user_subject=self.local_user, user_object=self.remote_user ) self.assertEqual( - request.remote_id, - 'http://local.com/user/mouse#follows/%d' % request.id + request.remote_id, "http://local.com/user/mouse#follows/%d" % request.id ) - self.assertEqual(request.status, 'follow_request') + self.assertEqual(request.status, "follow_request") rel = models.UserFollows.from_request(request) self.assertEqual( - rel.remote_id, - 'http://local.com/user/mouse#follows/%d' % request.id + rel.remote_id, "http://local.com/user/mouse#follows/%d" % request.id ) - self.assertEqual(rel.status, 'follows') + self.assertEqual(rel.status, "follows") self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, self.remote_user) models.UserFollowRequest.broadcast = real_broadcast - - def test_user_follows_from_request_custom_remote_id(self): - ''' store a specific remote id for a relationship provided by remote ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ store a specific remote id for a relationship provided by remote """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): request = models.UserFollowRequest.objects.create( user_subject=self.local_user, user_object=self.remote_user, - remote_id='http://antoher.server/sdkfhskdjf/23' + remote_id="http://antoher.server/sdkfhskdjf/23", ) - self.assertEqual( - request.remote_id, - 'http://antoher.server/sdkfhskdjf/23' - ) - self.assertEqual(request.status, 'follow_request') + self.assertEqual(request.remote_id, "http://antoher.server/sdkfhskdjf/23") + self.assertEqual(request.status, "follow_request") rel = models.UserFollows.from_request(request) - self.assertEqual( - rel.remote_id, - 'http://antoher.server/sdkfhskdjf/23' - ) - self.assertEqual(rel.status, 'follows') + self.assertEqual(rel.remote_id, "http://antoher.server/sdkfhskdjf/23") + self.assertEqual(rel.status, "follows") self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, self.remote_user) - def test_follow_request_activity(self): - ''' accept a request and make it a relationship ''' + """ accept a request and make it a relationship """ real_broadcast = models.UserFollowRequest.broadcast + def mock_broadcast(_, activity, user): self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object'], self.remote_user.remote_id) - self.assertEqual(activity['type'], 'Follow') + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"], self.remote_user.remote_id) + self.assertEqual(activity["type"], "Follow") models.UserFollowRequest.broadcast = mock_broadcast models.UserFollowRequest.objects.create( @@ -95,15 +87,15 @@ class Relationship(TestCase): ) models.UserFollowRequest.broadcast = real_broadcast - def test_follow_request_accept(self): - ''' accept a request and make it a relationship ''' + """ accept a request and make it a relationship """ real_broadcast = models.UserFollowRequest.broadcast + def mock_broadcast(_, activity, user): self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Accept') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object']['id'], 'https://www.hi.com/') + self.assertEqual(activity["type"], "Accept") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], "https://www.hi.com/") self.local_user.manually_approves_followers = True self.local_user.save(broadcast=False) @@ -111,7 +103,7 @@ class Relationship(TestCase): request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user, - remote_id='https://www.hi.com/' + remote_id="https://www.hi.com/", ) request.accept() @@ -122,16 +114,15 @@ class Relationship(TestCase): self.assertEqual(rel.user_object, self.local_user) models.UserFollowRequest.broadcast = real_broadcast - def test_follow_request_reject(self): - ''' accept a request and make it a relationship ''' + """ accept a request and make it a relationship """ real_broadcast = models.UserFollowRequest.broadcast + def mock_reject(_, activity, user): self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Reject') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual( - activity['object']['id'], request.remote_id) + self.assertEqual(activity["type"], "Reject") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], request.remote_id) models.UserFollowRequest.broadcast = mock_reject self.local_user.manually_approves_followers = True diff --git a/bookwyrm/tests/models/test_shelf_model.py b/bookwyrm/tests/models/test_shelf_model.py index c997326e7..3bbb9890d 100644 --- a/bookwyrm/tests/models/test_shelf_model.py +++ b/bookwyrm/tests/models/test_shelf_model.py @@ -1,116 +1,120 @@ -''' testing models ''' +""" testing models """ from django.test import TestCase from bookwyrm import models, settings -#pylint: disable=unused-argument +# pylint: disable=unused-argument class Shelf(TestCase): - ''' some activitypub oddness ahead ''' + """ some activitypub oddness ahead """ + def setUp(self): - ''' look, a shelf ''' + """ look, a shelf """ self.local_user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') - work = models.Work.objects.create(title='Test Work') - self.book = models.Edition.objects.create( - title='test book', - parent_work=work) + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create(title="test book", parent_work=work) def test_remote_id(self): - ''' shelves use custom remote ids ''' + """ shelves use custom remote ids """ real_broadcast = models.Shelf.broadcast + def broadcast_mock(_, activity, user, **kwargs): - ''' nah ''' + """ nah """ + models.Shelf.broadcast = broadcast_mock shelf = models.Shelf.objects.create( - name='Test Shelf', identifier='test-shelf', - user=self.local_user) - expected_id = 'https://%s/user/mouse/shelf/test-shelf' % settings.DOMAIN + name="Test Shelf", identifier="test-shelf", user=self.local_user + ) + expected_id = "https://%s/user/mouse/shelf/test-shelf" % settings.DOMAIN self.assertEqual(shelf.get_remote_id(), expected_id) models.Shelf.broadcast = real_broadcast - def test_to_activity(self): - ''' jsonify it ''' + """ jsonify it """ real_broadcast = models.Shelf.broadcast + def empty_mock(_, activity, user, **kwargs): - ''' nah ''' + """ nah """ + models.Shelf.broadcast = empty_mock shelf = models.Shelf.objects.create( - name='Test Shelf', identifier='test-shelf', - user=self.local_user) + name="Test Shelf", identifier="test-shelf", user=self.local_user + ) activity_json = shelf.to_activity() self.assertIsInstance(activity_json, dict) - self.assertEqual(activity_json['id'], shelf.remote_id) - self.assertEqual(activity_json['totalItems'], 0) - self.assertEqual(activity_json['type'], 'Shelf') - self.assertEqual(activity_json['name'], 'Test Shelf') - self.assertEqual(activity_json['owner'], self.local_user.remote_id) + self.assertEqual(activity_json["id"], shelf.remote_id) + self.assertEqual(activity_json["totalItems"], 0) + self.assertEqual(activity_json["type"], "Shelf") + self.assertEqual(activity_json["name"], "Test Shelf") + self.assertEqual(activity_json["owner"], self.local_user.remote_id) models.Shelf.broadcast = real_broadcast - def test_create_update_shelf(self): - ''' create and broadcast shelf creation ''' + """ create and broadcast shelf creation """ real_broadcast = models.Shelf.broadcast + def create_mock(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Create') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object']['name'], 'Test Shelf') + self.assertEqual(activity["type"], "Create") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["name"], "Test Shelf") + models.Shelf.broadcast = create_mock shelf = models.Shelf.objects.create( - name='Test Shelf', identifier='test-shelf', user=self.local_user) + name="Test Shelf", identifier="test-shelf", user=self.local_user + ) def update_mock(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Update') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object']['name'], 'arthur russel') + self.assertEqual(activity["type"], "Update") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["name"], "arthur russel") + models.Shelf.broadcast = update_mock - shelf.name = 'arthur russel' + shelf.name = "arthur russel" shelf.save() - self.assertEqual(shelf.name, 'arthur russel') + self.assertEqual(shelf.name, "arthur russel") models.Shelf.broadcast = real_broadcast - def test_shelve(self): - ''' create and broadcast shelf creation ''' + """ create and broadcast shelf creation """ real_broadcast = models.Shelf.broadcast real_shelfbook_broadcast = models.ShelfBook.broadcast def add_mock(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Add') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object']['id'], self.book.remote_id) - self.assertEqual(activity['target'], shelf.remote_id) + self.assertEqual(activity["type"], "Add") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], self.book.remote_id) + self.assertEqual(activity["target"], shelf.remote_id) def remove_mock(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Remove') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object']['id'], self.book.remote_id) - self.assertEqual(activity['target'], shelf.remote_id) + self.assertEqual(activity["type"], "Remove") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], self.book.remote_id) + self.assertEqual(activity["target"], shelf.remote_id) def empty_mock(_, activity, user, **kwargs): - ''' nah ''' + """ nah """ models.Shelf.broadcast = empty_mock shelf = models.Shelf.objects.create( - name='Test Shelf', identifier='test-shelf', user=self.local_user) + name="Test Shelf", identifier="test-shelf", user=self.local_user + ) models.ShelfBook.broadcast = add_mock shelf_book = models.ShelfBook.objects.create( - shelf=shelf, - user=self.local_user, - book=self.book) + shelf=shelf, user=self.local_user, book=self.book + ) self.assertEqual(shelf.books.first(), self.book) models.ShelfBook.broadcast = remove_mock diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 29be5c072..21982c200 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ from unittest.mock import patch from io import BytesIO import pathlib @@ -12,44 +12,45 @@ from django.utils import timezone from bookwyrm import models, settings -@patch('bookwyrm.models.Status.broadcast') +@patch("bookwyrm.models.Status.broadcast") class Status(TestCase): - ''' lotta types of statuses ''' + """ lotta types of statuses """ + def setUp(self): - ''' useful things for creating a status ''' + """ useful things for creating a status """ self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') - self.book = models.Edition.objects.create(title='Test Edition') + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + self.book = models.Edition.objects.create(title="Test Edition") image_file = pathlib.Path(__file__).parent.joinpath( - '../../static/images/default_avi.jpg') + "../../static/images/default_avi.jpg" + ) image = Image.open(image_file) output = BytesIO() - with patch('bookwyrm.models.Status.broadcast'): + with patch("bookwyrm.models.Status.broadcast"): image.save(output, format=image.format) - self.book.cover.save( - 'test.jpg', - ContentFile(output.getvalue()) - ) + self.book.cover.save("test.jpg", ContentFile(output.getvalue())) def test_status_generated_fields(self, _): - ''' setting remote id ''' - status = models.Status.objects.create(content='bleh', user=self.user) - expected_id = 'https://%s/user/mouse/status/%d' % \ - (settings.DOMAIN, status.id) + """ setting remote id """ + status = models.Status.objects.create(content="bleh", user=self.user) + expected_id = "https://%s/user/mouse/status/%d" % (settings.DOMAIN, status.id) self.assertEqual(status.remote_id, expected_id) - self.assertEqual(status.privacy, 'public') + self.assertEqual(status.privacy, "public") def test_replies(self, _): - ''' get a list of replies ''' - parent = models.Status.objects.create(content='hi', user=self.user) + """ get a list of replies """ + parent = models.Status.objects.create(content="hi", user=self.user) child = models.Status.objects.create( - content='hello', reply_parent=parent, user=self.user) + content="hello", reply_parent=parent, user=self.user + ) models.Review.objects.create( - content='hey', reply_parent=parent, user=self.user, book=self.book) + content="hey", reply_parent=parent, user=self.user, book=self.book + ) models.Status.objects.create( - content='hi hello', reply_parent=child, user=self.user) + content="hi hello", reply_parent=child, user=self.user + ) replies = models.Status.replies(parent) self.assertEqual(replies.count(), 2) @@ -58,199 +59,228 @@ class Status(TestCase): self.assertIsInstance(replies.last(), models.Review) def test_status_type(self, _): - ''' class name ''' - self.assertEqual(models.Status().status_type, 'Note') - self.assertEqual(models.Review().status_type, 'Review') - self.assertEqual(models.Quotation().status_type, 'Quotation') - self.assertEqual(models.Comment().status_type, 'Comment') - self.assertEqual(models.Boost().status_type, 'Announce') + """ class name """ + self.assertEqual(models.Status().status_type, "Note") + self.assertEqual(models.Review().status_type, "Review") + self.assertEqual(models.Quotation().status_type, "Quotation") + self.assertEqual(models.Comment().status_type, "Comment") + self.assertEqual(models.Boost().status_type, "Announce") def test_boostable(self, _): - ''' can a status be boosted, based on privacy ''' - self.assertTrue(models.Status(privacy='public').boostable) - self.assertTrue(models.Status(privacy='unlisted').boostable) - self.assertFalse(models.Status(privacy='followers').boostable) - self.assertFalse(models.Status(privacy='direct').boostable) + """ can a status be boosted, based on privacy """ + self.assertTrue(models.Status(privacy="public").boostable) + self.assertTrue(models.Status(privacy="unlisted").boostable) + self.assertFalse(models.Status(privacy="followers").boostable) + self.assertFalse(models.Status(privacy="direct").boostable) def test_to_replies(self, _): - ''' activitypub replies collection ''' - parent = models.Status.objects.create(content='hi', user=self.user) + """ activitypub replies collection """ + parent = models.Status.objects.create(content="hi", user=self.user) child = models.Status.objects.create( - content='hello', reply_parent=parent, user=self.user) + content="hello", reply_parent=parent, user=self.user + ) models.Review.objects.create( - content='hey', reply_parent=parent, user=self.user, book=self.book) + content="hey", reply_parent=parent, user=self.user, book=self.book + ) models.Status.objects.create( - content='hi hello', reply_parent=child, user=self.user) + content="hi hello", reply_parent=child, user=self.user + ) replies = parent.to_replies() - self.assertEqual(replies['id'], '%s/replies' % parent.remote_id) - self.assertEqual(replies['totalItems'], 2) + self.assertEqual(replies["id"], "%s/replies" % parent.remote_id) + self.assertEqual(replies["totalItems"], 2) def test_status_to_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' - status = models.Status.objects.create( - content='test content', user=self.user) + """ subclass of the base model version with a "pure" serializer """ + status = models.Status.objects.create(content="test content", user=self.user) activity = status.to_activity() - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Note') - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['sensitive'], False) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Note") + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["sensitive"], False) def test_status_to_activity_tombstone(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Status.objects.create( - content='test content', user=self.user, - deleted=True, deleted_date=timezone.now()) + content="test content", + user=self.user, + deleted=True, + deleted_date=timezone.now(), + ) activity = status.to_activity() - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Tombstone') - self.assertFalse(hasattr(activity, 'content')) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Tombstone") + self.assertFalse(hasattr(activity, "content")) def test_status_to_pure_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' - status = models.Status.objects.create( - content='test content', user=self.user) + """ subclass of the base model version with a "pure" serializer """ + status = models.Status.objects.create(content="test content", user=self.user) activity = status.to_activity(pure=True) - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Note') - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['sensitive'], False) - self.assertEqual(activity['attachment'], []) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Note") + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["sensitive"], False) + self.assertEqual(activity["attachment"], []) def test_generated_note_to_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.GeneratedNote.objects.create( - content='test content', user=self.user) + content="test content", user=self.user + ) status.mention_books.set([self.book]) status.mention_users.set([self.user]) activity = status.to_activity() - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'GeneratedNote') - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['sensitive'], False) - self.assertEqual(len(activity['tag']), 2) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "GeneratedNote") + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["sensitive"], False) + self.assertEqual(len(activity["tag"]), 2) def test_generated_note_to_pure_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.GeneratedNote.objects.create( - content='test content', user=self.user) + content="test content", user=self.user + ) status.mention_books.set([self.book]) status.mention_users.set([self.user]) activity = status.to_activity(pure=True) - self.assertEqual(activity['id'], status.remote_id) + self.assertEqual(activity["id"], status.remote_id) self.assertEqual( - activity['content'], - 'mouse test content "Test Edition"' % \ - self.book.remote_id) - self.assertEqual(len(activity['tag']), 2) - self.assertEqual(activity['type'], 'Note') - self.assertEqual(activity['sensitive'], False) - self.assertIsInstance(activity['attachment'], list) - self.assertEqual(activity['attachment'][0].type, 'Image') - self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \ - (settings.DOMAIN, self.book.cover.url)) + activity["content"], + 'mouse test content "Test Edition"' % self.book.remote_id, + ) + self.assertEqual(len(activity["tag"]), 2) + self.assertEqual(activity["type"], "Note") + self.assertEqual(activity["sensitive"], False) + self.assertIsInstance(activity["attachment"], list) + self.assertEqual(activity["attachment"][0].type, "Image") self.assertEqual( - activity['attachment'][0].name, 'Test Edition') + activity["attachment"][0].url, + "https://%s%s" % (settings.DOMAIN, self.book.cover.url), + ) + self.assertEqual(activity["attachment"][0].name, "Test Edition") def test_comment_to_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Comment.objects.create( - content='test content', user=self.user, book=self.book) + content="test content", user=self.user, book=self.book + ) activity = status.to_activity() - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Comment') - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['inReplyToBook'], self.book.remote_id) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Comment") + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["inReplyToBook"], self.book.remote_id) def test_comment_to_pure_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Comment.objects.create( - content='test content', user=self.user, book=self.book) + content="test content", user=self.user, book=self.book + ) activity = status.to_activity(pure=True) - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Note') + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Note") self.assertEqual( - activity['content'], - 'test content

(comment on "Test Edition")

' % - self.book.remote_id) - self.assertEqual(activity['attachment'][0].type, 'Image') - self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \ - (settings.DOMAIN, self.book.cover.url)) + activity["content"], + 'test content

(comment on "Test Edition")

' + % self.book.remote_id, + ) + self.assertEqual(activity["attachment"][0].type, "Image") self.assertEqual( - activity['attachment'][0].name, 'Test Edition') + activity["attachment"][0].url, + "https://%s%s" % (settings.DOMAIN, self.book.cover.url), + ) + self.assertEqual(activity["attachment"][0].name, "Test Edition") def test_quotation_to_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Quotation.objects.create( - quote='a sickening sense', content='test content', - user=self.user, book=self.book) + quote="a sickening sense", + content="test content", + user=self.user, + book=self.book, + ) activity = status.to_activity() - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Quotation') - self.assertEqual(activity['quote'], 'a sickening sense') - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['inReplyToBook'], self.book.remote_id) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Quotation") + self.assertEqual(activity["quote"], "a sickening sense") + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["inReplyToBook"], self.book.remote_id) def test_quotation_to_pure_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Quotation.objects.create( - quote='a sickening sense', content='test content', - user=self.user, book=self.book) + quote="a sickening sense", + content="test content", + user=self.user, + book=self.book, + ) activity = status.to_activity(pure=True) - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Note') + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Note") self.assertEqual( - activity['content'], - 'a sickening sense

-- "Test Edition"

' \ - 'test content' % self.book.remote_id) - self.assertEqual(activity['attachment'][0].type, 'Image') - self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \ - (settings.DOMAIN, self.book.cover.url)) + activity["content"], + 'a sickening sense

-- "Test Edition"

' + "test content" % self.book.remote_id, + ) + self.assertEqual(activity["attachment"][0].type, "Image") self.assertEqual( - activity['attachment'][0].name, 'Test Edition') + activity["attachment"][0].url, + "https://%s%s" % (settings.DOMAIN, self.book.cover.url), + ) + self.assertEqual(activity["attachment"][0].name, "Test Edition") def test_review_to_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Review.objects.create( - name='Review name', content='test content', rating=3, - user=self.user, book=self.book) + name="Review name", + content="test content", + rating=3, + user=self.user, + book=self.book, + ) activity = status.to_activity() - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Review') - self.assertEqual(activity['rating'], 3) - self.assertEqual(activity['name'], 'Review name') - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['inReplyToBook'], self.book.remote_id) + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Review") + self.assertEqual(activity["rating"], 3) + self.assertEqual(activity["name"], "Review name") + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["inReplyToBook"], self.book.remote_id) def test_review_to_pure_activity(self, _): - ''' subclass of the base model version with a "pure" serializer ''' + """ subclass of the base model version with a "pure" serializer """ status = models.Review.objects.create( - name='Review name', content='test content', rating=3, - user=self.user, book=self.book) + name="Review name", + content="test content", + rating=3, + user=self.user, + book=self.book, + ) activity = status.to_activity(pure=True) - self.assertEqual(activity['id'], status.remote_id) - self.assertEqual(activity['type'], 'Article') + self.assertEqual(activity["id"], status.remote_id) + self.assertEqual(activity["type"], "Article") self.assertEqual( - activity['name'], 'Review of "%s" (3 stars): Review name' \ - % self.book.title) - self.assertEqual(activity['content'], 'test content') - self.assertEqual(activity['attachment'][0].type, 'Image') - self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \ - (settings.DOMAIN, self.book.cover.url)) + activity["name"], 'Review of "%s" (3 stars): Review name' % self.book.title + ) + self.assertEqual(activity["content"], "test content") + self.assertEqual(activity["attachment"][0].type, "Image") self.assertEqual( - activity['attachment'][0].name, 'Test Edition') + activity["attachment"][0].url, + "https://%s%s" % (settings.DOMAIN, self.book.cover.url), + ) + self.assertEqual(activity["attachment"][0].name, "Test Edition") def test_favorite(self, _): - ''' fav a status ''' + """ fav a status """ real_broadcast = models.Favorite.broadcast + def fav_broadcast_mock(_, activity, user): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.user.remote_id) - self.assertEqual(activity['type'], 'Like') + self.assertEqual(activity["type"], "Like") + models.Favorite.broadcast = fav_broadcast_mock - status = models.Status.objects.create( - content='test content', user=self.user) + status = models.Status.objects.create(content="test content", user=self.user) fav = models.Favorite.objects.create(status=status, user=self.user) # can't fav a status twice @@ -258,50 +288,47 @@ class Status(TestCase): models.Favorite.objects.create(status=status, user=self.user) activity = fav.to_activity() - self.assertEqual(activity['type'], 'Like') - self.assertEqual(activity['actor'], self.user.remote_id) - self.assertEqual(activity['object'], status.remote_id) + self.assertEqual(activity["type"], "Like") + self.assertEqual(activity["actor"], self.user.remote_id) + self.assertEqual(activity["object"], status.remote_id) models.Favorite.broadcast = real_broadcast def test_boost(self, _): - ''' boosting, this one's a bit fussy ''' - status = models.Status.objects.create( - content='test content', user=self.user) - boost = models.Boost.objects.create( - boosted_status=status, user=self.user) + """ boosting, this one's a bit fussy """ + status = models.Status.objects.create(content="test content", user=self.user) + boost = models.Boost.objects.create(boosted_status=status, user=self.user) activity = boost.to_activity() - self.assertEqual(activity['actor'], self.user.remote_id) - self.assertEqual(activity['object'], status.remote_id) - self.assertEqual(activity['type'], 'Announce') + self.assertEqual(activity["actor"], self.user.remote_id) + self.assertEqual(activity["object"], status.remote_id) + self.assertEqual(activity["type"], "Announce") self.assertEqual(activity, boost.to_activity(pure=True)) def test_notification(self, _): - ''' a simple model ''' + """ a simple model """ notification = models.Notification.objects.create( - user=self.user, notification_type='FAVORITE') + user=self.user, notification_type="FAVORITE" + ) self.assertFalse(notification.read) with self.assertRaises(IntegrityError): models.Notification.objects.create( - user=self.user, notification_type='GLORB') - + user=self.user, notification_type="GLORB" + ) def test_create_broadcast(self, broadcast_mock): - ''' should send out two verions of a status on create ''' - models.Comment.objects.create( - content='hi', user=self.user, book=self.book) + """ should send out two verions of a status on create """ + models.Comment.objects.create(content="hi", user=self.user, book=self.book) self.assertEqual(broadcast_mock.call_count, 2) pure_call = broadcast_mock.call_args_list[0] bw_call = broadcast_mock.call_args_list[1] - self.assertEqual(pure_call[1]['software'], 'other') + self.assertEqual(pure_call[1]["software"], "other") args = pure_call[0][0] - self.assertEqual(args['type'], 'Create') - self.assertEqual(args['object']['type'], 'Note') - self.assertTrue('content' in args['object']) + self.assertEqual(args["type"], "Create") + self.assertEqual(args["object"]["type"], "Note") + self.assertTrue("content" in args["object"]) - - self.assertEqual(bw_call[1]['software'], 'bookwyrm') + self.assertEqual(bw_call[1]["software"], "bookwyrm") args = bw_call[0][0] - self.assertEqual(args['type'], 'Create') - self.assertEqual(args['object']['type'], 'Comment') + self.assertEqual(args["type"], "Create") + self.assertEqual(args["object"]["type"], "Comment") diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index ed3ad41a5..618e4f781 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -1,4 +1,4 @@ -''' testing models ''' +""" testing models """ from unittest.mock import patch from django.test import TestCase import responses @@ -11,75 +11,84 @@ from bookwyrm.settings import DOMAIN class User(TestCase): def setUp(self): self.user = models.User.objects.create_user( - 'mouse@%s' % DOMAIN, 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse', name='hi', bookwyrm_user=False) + "mouse@%s" % DOMAIN, + "mouse@mouse.mouse", + "mouseword", + local=True, + localname="mouse", + name="hi", + bookwyrm_user=False, + ) def test_computed_fields(self): - ''' username instead of id here ''' - expected_id = 'https://%s/user/mouse' % DOMAIN + """ username instead of id here """ + expected_id = "https://%s/user/mouse" % DOMAIN self.assertEqual(self.user.remote_id, expected_id) - self.assertEqual(self.user.username, 'mouse@%s' % DOMAIN) - self.assertEqual(self.user.localname, 'mouse') - self.assertEqual(self.user.shared_inbox, 'https://%s/inbox' % DOMAIN) - self.assertEqual(self.user.inbox, '%s/inbox' % expected_id) - self.assertEqual(self.user.outbox, '%s/outbox' % expected_id) + self.assertEqual(self.user.username, "mouse@%s" % DOMAIN) + self.assertEqual(self.user.localname, "mouse") + self.assertEqual(self.user.shared_inbox, "https://%s/inbox" % DOMAIN) + self.assertEqual(self.user.inbox, "%s/inbox" % expected_id) + self.assertEqual(self.user.outbox, "%s/outbox" % expected_id) self.assertIsNotNone(self.user.key_pair.private_key) self.assertIsNotNone(self.user.key_pair.public_key) def test_remote_user(self): - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): user = models.User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', local=False, - remote_id='https://example.com/dfjkg', bookwyrm_user=False) - self.assertEqual(user.username, 'rat@example.com') - + "rat", + "rat@rat.rat", + "ratword", + local=False, + remote_id="https://example.com/dfjkg", + bookwyrm_user=False, + ) + self.assertEqual(user.username, "rat@example.com") def test_user_shelves(self): shelves = models.Shelf.objects.filter(user=self.user).all() self.assertEqual(len(shelves), 3) names = [s.name for s in shelves] - self.assertTrue('To Read' in names) - self.assertTrue('Currently Reading' in names) - self.assertTrue('Read' in names) + self.assertTrue("To Read" in names) + self.assertTrue("Currently Reading" in names) + self.assertTrue("Read" in names) ids = [s.identifier for s in shelves] - self.assertTrue('to-read' in ids) - self.assertTrue('reading' in ids) - self.assertTrue('read' in ids) - + self.assertTrue("to-read" in ids) + self.assertTrue("reading" in ids) + self.assertTrue("read" in ids) def test_activitypub_serialize(self): activity = self.user.to_activity() - self.assertEqual(activity['id'], self.user.remote_id) - self.assertEqual(activity['@context'], [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { - 'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers', - 'schema': 'http://schema.org#', - 'PropertyValue': 'schema:PropertyValue', - 'value': 'schema:value', - } - ]) - self.assertEqual(activity['preferredUsername'], self.user.localname) - self.assertEqual(activity['name'], self.user.name) - self.assertEqual(activity['inbox'], self.user.inbox) - self.assertEqual(activity['outbox'], self.user.outbox) - self.assertEqual(activity['bookwyrmUser'], False) - self.assertEqual(activity['discoverable'], True) - self.assertEqual(activity['type'], 'Person') + self.assertEqual(activity["id"], self.user.remote_id) + self.assertEqual( + activity["@context"], + [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + }, + ], + ) + self.assertEqual(activity["preferredUsername"], self.user.localname) + self.assertEqual(activity["name"], self.user.name) + self.assertEqual(activity["inbox"], self.user.inbox) + self.assertEqual(activity["outbox"], self.user.outbox) + self.assertEqual(activity["bookwyrmUser"], False) + self.assertEqual(activity["discoverable"], True) + self.assertEqual(activity["type"], "Person") def test_activitypub_outbox(self): activity = self.user.to_outbox() - self.assertEqual(activity['type'], 'OrderedCollection') - self.assertEqual(activity['id'], self.user.outbox) - self.assertEqual(activity['totalItems'], 0) - + self.assertEqual(activity["type"], "OrderedCollection") + self.assertEqual(activity["id"], self.user.outbox) + self.assertEqual(activity["totalItems"], 0) def test_set_remote_server(self): server = models.FederatedServer.objects.create( - server_name=DOMAIN, - application_type='test type', - application_version=3 + server_name=DOMAIN, application_type="test type", application_version=3 ) models.user.set_remote_server(self.user.id) @@ -91,26 +100,24 @@ class User(TestCase): def test_get_or_create_remote_server(self): responses.add( responses.GET, - 'https://%s/.well-known/nodeinfo' % DOMAIN, - json={'links': [{'href': 'http://www.example.com'}, {}]} + "https://%s/.well-known/nodeinfo" % DOMAIN, + json={"links": [{"href": "http://www.example.com"}, {}]}, ) responses.add( responses.GET, - 'http://www.example.com', - json={'software': {'name': 'hi', 'version': '2'}}, + "http://www.example.com", + json={"software": {"name": "hi", "version": "2"}}, ) server = models.user.get_or_create_remote_server(DOMAIN) self.assertEqual(server.server_name, DOMAIN) - self.assertEqual(server.application_type, 'hi') - self.assertEqual(server.application_version, '2') + self.assertEqual(server.application_type, "hi") + self.assertEqual(server.application_version, "2") @responses.activate def test_get_or_create_remote_server_no_wellknown(self): responses.add( - responses.GET, - 'https://%s/.well-known/nodeinfo' % DOMAIN, - status=404 + responses.GET, "https://%s/.well-known/nodeinfo" % DOMAIN, status=404 ) server = models.user.get_or_create_remote_server(DOMAIN) @@ -122,14 +129,10 @@ class User(TestCase): def test_get_or_create_remote_server_no_links(self): responses.add( responses.GET, - 'https://%s/.well-known/nodeinfo' % DOMAIN, - json={'links': [{'href': 'http://www.example.com'}, {}]} - ) - responses.add( - responses.GET, - 'http://www.example.com', - status=404 + "https://%s/.well-known/nodeinfo" % DOMAIN, + json={"links": [{"href": "http://www.example.com"}, {}]}, ) + responses.add(responses.GET, "http://www.example.com", status=404) server = models.user.get_or_create_remote_server(DOMAIN) self.assertEqual(server.server_name, DOMAIN) @@ -140,14 +143,10 @@ class User(TestCase): def test_get_or_create_remote_server_unknown_format(self): responses.add( responses.GET, - 'https://%s/.well-known/nodeinfo' % DOMAIN, - json={'links': [{'href': 'http://www.example.com'}, {}]} - ) - responses.add( - responses.GET, - 'http://www.example.com', - json={'fish': 'salmon'} + "https://%s/.well-known/nodeinfo" % DOMAIN, + json={"links": [{"href": "http://www.example.com"}, {}]}, ) + responses.add(responses.GET, "http://www.example.com", json={"fish": "salmon"}) server = models.user.get_or_create_remote_server(DOMAIN) self.assertEqual(server.server_name, DOMAIN) diff --git a/bookwyrm/tests/test_goodreads_import.py b/bookwyrm/tests/test_goodreads_import.py index aee9afe49..080ccd15b 100644 --- a/bookwyrm/tests/test_goodreads_import.py +++ b/bookwyrm/tests/test_goodreads_import.py @@ -1,4 +1,4 @@ -''' testing import ''' +""" testing import """ from collections import namedtuple import csv import pathlib @@ -12,125 +12,117 @@ from bookwyrm.goodreads_import import GoodreadsImporter from bookwyrm import importer from bookwyrm.settings import DOMAIN + class GoodreadsImport(TestCase): - ''' importing from goodreads csv ''' + """ importing from goodreads csv """ + def setUp(self): self.importer = GoodreadsImporter() - ''' use a test csv ''' - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/goodreads.csv') - self.csv = open(datafile, 'r', encoding=self.importer.encoding) + """ use a test csv """ + datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + self.csv = open(datafile, "r", encoding=self.importer.encoding) self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'password', local=True) + "mouse", "mouse@mouse.mouse", "password", local=True + ) models.Connector.objects.create( identifier=DOMAIN, - name='Local', + name="Local", local=True, - connector_file='self_connector', - base_url='https://%s' % DOMAIN, - books_url='https://%s/book' % DOMAIN, - covers_url='https://%s/images/covers' % DOMAIN, - search_url='https://%s/search?q=' % DOMAIN, + connector_file="self_connector", + base_url="https://%s" % DOMAIN, + books_url="https://%s/book" % DOMAIN, + covers_url="https://%s/images/covers" % DOMAIN, + search_url="https://%s/search?q=" % DOMAIN, priority=1, ) - work = models.Work.objects.create(title='Test Work') + work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, ) - def test_create_job(self): - ''' creates the import job entry and checks csv ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'public') + """ creates the import job entry and checks csv """ + import_job = self.importer.create_job(self.user, self.csv, False, "public") self.assertEqual(import_job.user, self.user) self.assertEqual(import_job.include_reviews, False) - self.assertEqual(import_job.privacy, 'public') + self.assertEqual(import_job.privacy, "public") import_items = models.ImportItem.objects.filter(job=import_job).all() self.assertEqual(len(import_items), 3) self.assertEqual(import_items[0].index, 0) - self.assertEqual(import_items[0].data['Book Id'], '42036538') + self.assertEqual(import_items[0].data["Book Id"], "42036538") self.assertEqual(import_items[1].index, 1) - self.assertEqual(import_items[1].data['Book Id'], '52691223') + self.assertEqual(import_items[1].data["Book Id"], "52691223") self.assertEqual(import_items[2].index, 2) - self.assertEqual(import_items[2].data['Book Id'], '28694510') - + self.assertEqual(import_items[2].data["Book Id"], "28694510") def test_create_retry_job(self): - ''' trying again with items that didn't import ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'unlisted') - import_items = models.ImportItem.objects.filter( - job=import_job - ).all()[:2] + """ trying again with items that didn't import """ + import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") + import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] - retry = self.importer.create_retry_job( - self.user, import_job, import_items) + retry = self.importer.create_retry_job(self.user, import_job, import_items) self.assertNotEqual(import_job, retry) self.assertEqual(retry.user, self.user) self.assertEqual(retry.include_reviews, False) - self.assertEqual(retry.privacy, 'unlisted') + self.assertEqual(retry.privacy, "unlisted") retry_items = models.ImportItem.objects.filter(job=retry).all() self.assertEqual(len(retry_items), 2) self.assertEqual(retry_items[0].index, 0) - self.assertEqual(retry_items[0].data['Book Id'], '42036538') + self.assertEqual(retry_items[0].data["Book Id"], "42036538") self.assertEqual(retry_items[1].index, 1) - self.assertEqual(retry_items[1].data['Book Id'], '52691223') - + self.assertEqual(retry_items[1].data["Book Id"], "52691223") def test_start_import(self): - ''' begin loading books ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'unlisted') - MockTask = namedtuple('Task', ('id')) + """ begin loading books """ + import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") + MockTask = namedtuple("Task", ("id")) mock_task = MockTask(7) - with patch('bookwyrm.importer.import_data.delay') as start: + with patch("bookwyrm.importer.import_data.delay") as start: start.return_value = mock_task self.importer.start_import(import_job) import_job.refresh_from_db() - self.assertEqual(import_job.task_id, '7') - + self.assertEqual(import_job.task_id, "7") @responses.activate def test_import_data(self): - ''' resolve entry ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'unlisted') - book = models.Edition.objects.create(title='Test Book') + """ resolve entry """ + import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") + book = models.Edition.objects.create(title="Test Book") with patch( - 'bookwyrm.models.import_job.ImportItem.get_book_from_isbn' - ) as resolve: + "bookwyrm.models.import_job.ImportItem.get_book_from_isbn" + ) as resolve: resolve.return_value = book - with patch('bookwyrm.importer.handle_imported_book'): + with patch("bookwyrm.importer.handle_imported_book"): importer.import_data(self.importer.service, import_job.id) import_item = models.ImportItem.objects.get(job=import_job, index=0) self.assertEqual(import_item.book.id, book.id) - def test_handle_imported_book(self): - ''' goodreads import added a book, this adds related connections ''' - shelf = self.user.shelf_set.filter(identifier='read').first() + """ goodreads import added a book, this adds related connections """ + shelf = self.user.shelf_set.filter(identifier="read").first() self.assertIsNone(shelf.books.first()) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/goodreads.csv') - csv_file = open(datafile, 'r') + datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + csv_file = open(datafile, "r") for index, entry in enumerate(list(csv.DictReader(csv_file))): entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=index, data=entry, book=self.book) + job_id=import_job.id, index=index, data=entry, book=self.book + ) break - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -145,31 +137,30 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.finish_date.month, 10) self.assertEqual(readthrough.finish_date.day, 25) - def test_handle_imported_book_already_shelved(self): - ''' goodreads import added a book, this adds related connections ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - shelf = self.user.shelf_set.filter(identifier='to-read').first() - models.ShelfBook.objects.create( - shelf=shelf, user=self.user, book=self.book) + """ goodreads import added a book, this adds related connections """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + shelf = self.user.shelf_set.filter(identifier="to-read").first() + models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/goodreads.csv') - csv_file = open(datafile, 'r') + datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + csv_file = open(datafile, "r") for index, entry in enumerate(list(csv.DictReader(csv_file))): entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=index, data=entry, book=self.book) + job_id=import_job.id, index=index, data=entry, book=self.book + ) break - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) - self.assertIsNone( - self.user.shelf_set.get(identifier='read').books.first()) + self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first()) readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) self.assertEqual(readthrough.start_date.year, 2020) @@ -179,24 +170,26 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.finish_date.month, 10) self.assertEqual(readthrough.finish_date.day, 25) - def test_handle_import_twice(self): - ''' re-importing books ''' - shelf = self.user.shelf_set.filter(identifier='read').first() + """ re-importing books """ + shelf = self.user.shelf_set.filter(identifier="read").first() import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/goodreads.csv') - csv_file = open(datafile, 'r') + datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + csv_file = open(datafile, "r") for index, entry in enumerate(list(csv.DictReader(csv_file))): entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=index, data=entry, book=self.book) + job_id=import_job.id, index=index, data=entry, book=self.book + ) break - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -211,42 +204,44 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.finish_date.month, 10) self.assertEqual(readthrough.finish_date.day, 25) - def test_handle_imported_book_review(self): - ''' goodreads review import ''' + """ goodreads review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/goodreads.csv') - csv_file = open(datafile, 'r') + datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + csv_file = open(datafile, "r") entry = list(csv.DictReader(csv_file))[2] entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=0, data=entry, book=self.book) + job_id=import_job.id, index=0, data=entry, book=self.book + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, True, 'unlisted') + self.importer.service, self.user, import_item, True, "unlisted" + ) review = models.Review.objects.get(book=self.book, user=self.user) - self.assertEqual(review.content, 'mixed feelings') + self.assertEqual(review.content, "mixed feelings") self.assertEqual(review.rating, 2) self.assertEqual(review.published_date.year, 2019) self.assertEqual(review.published_date.month, 7) self.assertEqual(review.published_date.day, 8) - self.assertEqual(review.privacy, 'unlisted') - + self.assertEqual(review.privacy, "unlisted") def test_handle_imported_book_reviews_disabled(self): - ''' goodreads review import ''' + """ goodreads review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/goodreads.csv') - csv_file = open(datafile, 'r') + datafile = pathlib.Path(__file__).parent.joinpath("data/goodreads.csv") + csv_file = open(datafile, "r") entry = list(csv.DictReader(csv_file))[2] entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=0, data=entry, book=self.book) + job_id=import_job.id, index=0, data=entry, book=self.book + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'unlisted') - self.assertFalse(models.Review.objects.filter( - book=self.book, user=self.user - ).exists()) + self.importer.service, self.user, import_item, False, "unlisted" + ) + self.assertFalse( + models.Review.objects.filter(book=self.book, user=self.user).exists() + ) diff --git a/bookwyrm/tests/test_librarything_import.py b/bookwyrm/tests/test_librarything_import.py index 2623a5047..a8e4cfe4f 100644 --- a/bookwyrm/tests/test_librarything_import.py +++ b/bookwyrm/tests/test_librarything_import.py @@ -1,4 +1,4 @@ -''' testing import ''' +""" testing import """ from collections import namedtuple import csv import pathlib @@ -11,114 +11,110 @@ from bookwyrm import models, importer from bookwyrm.librarything_import import LibrarythingImporter from bookwyrm.settings import DOMAIN + class LibrarythingImport(TestCase): - ''' importing from librarything tsv ''' + """ importing from librarything tsv """ + def setUp(self): self.importer = LibrarythingImporter() - ''' use a test tsv ''' - datafile = pathlib.Path(__file__).parent.joinpath( - 'data/librarything.tsv') + """ use a test tsv """ + datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") # Librarything generates latin encoded exports... - self.csv = open(datafile, 'r', encoding=self.importer.encoding) + self.csv = open(datafile, "r", encoding=self.importer.encoding) self.user = models.User.objects.create_user( - 'mmai', 'mmai@mmai.mmai', 'password', local=True) + "mmai", "mmai@mmai.mmai", "password", local=True + ) models.Connector.objects.create( identifier=DOMAIN, - name='Local', + name="Local", local=True, - connector_file='self_connector', - base_url='https://%s' % DOMAIN, - books_url='https://%s/book' % DOMAIN, - covers_url='https://%s/images/covers' % DOMAIN, - search_url='https://%s/search?q=' % DOMAIN, + connector_file="self_connector", + base_url="https://%s" % DOMAIN, + books_url="https://%s/book" % DOMAIN, + covers_url="https://%s/images/covers" % DOMAIN, + search_url="https://%s/search?q=" % DOMAIN, priority=1, ) - work = models.Work.objects.create(title='Test Work') + work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, ) - def test_create_job(self): - ''' creates the import job entry and checks csv ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'public') + """ creates the import job entry and checks csv """ + import_job = self.importer.create_job(self.user, self.csv, False, "public") self.assertEqual(import_job.user, self.user) self.assertEqual(import_job.include_reviews, False) - self.assertEqual(import_job.privacy, 'public') + self.assertEqual(import_job.privacy, "public") import_items = models.ImportItem.objects.filter(job=import_job).all() self.assertEqual(len(import_items), 3) self.assertEqual(import_items[0].index, 0) - self.assertEqual(import_items[0].data['Book Id'], '5498194') + self.assertEqual(import_items[0].data["Book Id"], "5498194") self.assertEqual(import_items[1].index, 1) - self.assertEqual(import_items[1].data['Book Id'], '5015319') + self.assertEqual(import_items[1].data["Book Id"], "5015319") self.assertEqual(import_items[2].index, 2) - self.assertEqual(import_items[2].data['Book Id'], '5015399') - + self.assertEqual(import_items[2].data["Book Id"], "5015399") def test_create_retry_job(self): - ''' trying again with items that didn't import ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'unlisted') - import_items = models.ImportItem.objects.filter( - job=import_job - ).all()[:2] + """ trying again with items that didn't import """ + import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") + import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] - retry = self.importer.create_retry_job( - self.user, import_job, import_items) + retry = self.importer.create_retry_job(self.user, import_job, import_items) self.assertNotEqual(import_job, retry) self.assertEqual(retry.user, self.user) self.assertEqual(retry.include_reviews, False) - self.assertEqual(retry.privacy, 'unlisted') + self.assertEqual(retry.privacy, "unlisted") retry_items = models.ImportItem.objects.filter(job=retry).all() self.assertEqual(len(retry_items), 2) self.assertEqual(retry_items[0].index, 0) - self.assertEqual(import_items[0].data['Book Id'], '5498194') + self.assertEqual(import_items[0].data["Book Id"], "5498194") self.assertEqual(retry_items[1].index, 1) - self.assertEqual(retry_items[1].data['Book Id'], '5015319') - + self.assertEqual(retry_items[1].data["Book Id"], "5015319") @responses.activate def test_import_data(self): - ''' resolve entry ''' - import_job = self.importer.create_job( - self.user, self.csv, False, 'unlisted') - book = models.Edition.objects.create(title='Test Book') + """ resolve entry """ + import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") + book = models.Edition.objects.create(title="Test Book") with patch( - 'bookwyrm.models.import_job.ImportItem.get_book_from_isbn' - ) as resolve: + "bookwyrm.models.import_job.ImportItem.get_book_from_isbn" + ) as resolve: resolve.return_value = book - with patch('bookwyrm.importer.handle_imported_book'): + with patch("bookwyrm.importer.handle_imported_book"): importer.import_data(self.importer.service, import_job.id) import_item = models.ImportItem.objects.get(job=import_job, index=0) self.assertEqual(import_item.book.id, book.id) - def test_handle_imported_book(self): - ''' librarything import added a book, this adds related connections ''' - shelf = self.user.shelf_set.filter(identifier='read').first() + """ librarything import added a book, this adds related connections """ + shelf = self.user.shelf_set.filter(identifier="read").first() self.assertIsNone(shelf.books.first()) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/librarything.tsv') - csv_file = open(datafile, 'r', encoding=self.importer.encoding) - for index, entry in enumerate(list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))): + datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + csv_file = open(datafile, "r", encoding=self.importer.encoding) + for index, entry in enumerate( + list(csv.DictReader(csv_file, delimiter=self.importer.delimiter)) + ): entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=index, data=entry, book=self.book) + job_id=import_job.id, index=index, data=entry, book=self.book + ) break - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -133,31 +129,32 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.finish_date.month, 5) self.assertEqual(readthrough.finish_date.day, 8) - def test_handle_imported_book_already_shelved(self): - ''' librarything import added a book, this adds related connections ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - shelf = self.user.shelf_set.filter(identifier='to-read').first() - models.ShelfBook.objects.create( - shelf=shelf, user=self.user, book=self.book) + """ librarything import added a book, this adds related connections """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + shelf = self.user.shelf_set.filter(identifier="to-read").first() + models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/librarything.tsv') - csv_file = open(datafile, 'r', encoding=self.importer.encoding) - for index, entry in enumerate(list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))): + datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + csv_file = open(datafile, "r", encoding=self.importer.encoding) + for index, entry in enumerate( + list(csv.DictReader(csv_file, delimiter=self.importer.delimiter)) + ): entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=index, data=entry, book=self.book) + job_id=import_job.id, index=index, data=entry, book=self.book + ) break - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) - self.assertIsNone( - self.user.shelf_set.get(identifier='read').books.first()) + self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first()) readthrough = models.ReadThrough.objects.get(user=self.user) self.assertEqual(readthrough.book, self.book) self.assertEqual(readthrough.start_date.year, 2007) @@ -167,24 +164,28 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.finish_date.month, 5) self.assertEqual(readthrough.finish_date.day, 8) - def test_handle_import_twice(self): - ''' re-importing books ''' - shelf = self.user.shelf_set.filter(identifier='read').first() + """ re-importing books """ + shelf = self.user.shelf_set.filter(identifier="read").first() import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/librarything.tsv') - csv_file = open(datafile, 'r', encoding=self.importer.encoding) - for index, entry in enumerate(list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))): + datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + csv_file = open(datafile, "r", encoding=self.importer.encoding) + for index, entry in enumerate( + list(csv.DictReader(csv_file, delimiter=self.importer.delimiter)) + ): entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=index, data=entry, book=self.book) + job_id=import_job.id, index=index, data=entry, book=self.book + ) break - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'public') + self.importer.service, self.user, import_item, False, "public" + ) shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) @@ -199,42 +200,44 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.finish_date.month, 5) self.assertEqual(readthrough.finish_date.day, 8) - def test_handle_imported_book_review(self): - ''' librarything review import ''' + """ librarything review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/librarything.tsv') - csv_file = open(datafile, 'r', encoding=self.importer.encoding) + datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + csv_file = open(datafile, "r", encoding=self.importer.encoding) entry = list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))[0] entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=0, data=entry, book=self.book) + job_id=import_job.id, index=0, data=entry, book=self.book + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, True, 'unlisted') + self.importer.service, self.user, import_item, True, "unlisted" + ) review = models.Review.objects.get(book=self.book, user=self.user) - self.assertEqual(review.content, 'chef d\'oeuvre') + self.assertEqual(review.content, "chef d'oeuvre") self.assertEqual(review.rating, 5) self.assertEqual(review.published_date.year, 2007) self.assertEqual(review.published_date.month, 5) self.assertEqual(review.published_date.day, 8) - self.assertEqual(review.privacy, 'unlisted') - + self.assertEqual(review.privacy, "unlisted") def test_handle_imported_book_reviews_disabled(self): - ''' librarything review import ''' + """ librarything review import """ import_job = models.ImportJob.objects.create(user=self.user) - datafile = pathlib.Path(__file__).parent.joinpath('data/librarything.tsv') - csv_file = open(datafile, 'r', encoding=self.importer.encoding) + datafile = pathlib.Path(__file__).parent.joinpath("data/librarything.tsv") + csv_file = open(datafile, "r", encoding=self.importer.encoding) entry = list(csv.DictReader(csv_file, delimiter=self.importer.delimiter))[2] entry = self.importer.parse_fields(entry) import_item = models.ImportItem.objects.create( - job_id=import_job.id, index=0, data=entry, book=self.book) + job_id=import_job.id, index=0, data=entry, book=self.book + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): importer.handle_imported_book( - self.importer.service, self.user, import_item, False, 'unlisted') - self.assertFalse(models.Review.objects.filter( - book=self.book, user=self.user - ).exists()) + self.importer.service, self.user, import_item, False, "unlisted" + ) + self.assertFalse( + models.Review.objects.filter(book=self.book, user=self.user).exists() + ) diff --git a/bookwyrm/tests/test_sanitize_html.py b/bookwyrm/tests/test_sanitize_html.py index 58d94311c..2b3d0378d 100644 --- a/bookwyrm/tests/test_sanitize_html.py +++ b/bookwyrm/tests/test_sanitize_html.py @@ -1,28 +1,30 @@ -''' make sure only valid html gets to the app ''' +""" make sure only valid html gets to the app """ from django.test import TestCase from bookwyrm.sanitize_html import InputHtmlParser + class Sanitizer(TestCase): - ''' sanitizer tests ''' + """ sanitizer tests """ + def test_no_html(self): - ''' just text ''' - input_text = 'no html ' + """ just text """ + input_text = "no html " parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() self.assertEqual(input_text, output) def test_valid_html(self): - ''' leave the html untouched ''' - input_text = 'yes html' + """ leave the html untouched """ + input_text = "yes html" parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() self.assertEqual(input_text, output) def test_valid_html_attrs(self): - ''' and don't remove attributes ''' + """ and don't remove attributes """ input_text = 'yes html' parser = InputHtmlParser() parser.feed(input_text) @@ -30,23 +32,23 @@ class Sanitizer(TestCase): self.assertEqual(input_text, output) def test_invalid_html(self): - ''' remove all html when the html is malformed ''' - input_text = 'yes html' + """ remove all html when the html is malformed """ + input_text = "yes html" parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() - self.assertEqual('yes html', output) + self.assertEqual("yes html", output) - input_text = 'yes html ' + input_text = "yes html " parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() - self.assertEqual('yes html ', output) + self.assertEqual("yes html ", output) def test_disallowed_html(self): - ''' remove disallowed html but keep allowed html ''' - input_text = '
yes html
' + """ remove disallowed html but keep allowed html """ + input_text = "
yes html
" parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() - self.assertEqual(' yes html', output) + self.assertEqual(" yes html", output) diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index f6de11e1e..d9cc411c0 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -1,4 +1,4 @@ -''' getting and verifying signatures ''' +""" getting and verifying signatures """ import time from collections import namedtuple from urllib.parse import urlsplit @@ -18,141 +18,125 @@ from bookwyrm.activitypub import Follow from bookwyrm.settings import DOMAIN from bookwyrm.signatures import create_key_pair, make_signature, make_digest + def get_follow_activity(follower, followee): - ''' generates a test activity ''' + """ generates a test activity """ return Follow( - id='https://test.com/user/follow/id', + id="https://test.com/user/follow/id", actor=follower.remote_id, object=followee.remote_id, ).serialize() -KeyPair = namedtuple('KeyPair', ('private_key', 'public_key')) -Sender = namedtuple('Sender', ('remote_id', 'key_pair')) + +KeyPair = namedtuple("KeyPair", ("private_key", "public_key")) +Sender = namedtuple("Sender", ("remote_id", "key_pair")) + class Signature(TestCase): - ''' signature test ''' + """ signature test """ + def setUp(self): - ''' create users and test data ''' + """ create users and test data """ self.mouse = models.User.objects.create_user( - 'mouse@%s' % DOMAIN, 'mouse@example.com', '', - local=True, localname='mouse') + "mouse@%s" % DOMAIN, "mouse@example.com", "", local=True, localname="mouse" + ) self.rat = models.User.objects.create_user( - 'rat@%s' % DOMAIN, 'rat@example.com', '', - local=True, localname='rat') + "rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat" + ) self.cat = models.User.objects.create_user( - 'cat@%s' % DOMAIN, 'cat@example.com', '', - local=True, localname='cat') + "cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat" + ) private_key, public_key = create_key_pair() self.fake_remote = Sender( - 'http://localhost/user/remote', - KeyPair(private_key, public_key) + "http://localhost/user/remote", KeyPair(private_key, public_key) ) models.SiteSettings.objects.create() def send(self, signature, now, data, digest): - ''' test request ''' + """ test request """ c = Client() return c.post( urlsplit(self.rat.inbox).path, data=data, - content_type='application/json', + content_type="application/json", **{ - 'HTTP_DATE': now, - 'HTTP_SIGNATURE': signature, - 'HTTP_DIGEST': digest, - 'HTTP_CONTENT_TYPE': 'application/activity+json; charset=utf-8', - 'HTTP_HOST': DOMAIN, + "HTTP_DATE": now, + "HTTP_SIGNATURE": signature, + "HTTP_DIGEST": digest, + "HTTP_CONTENT_TYPE": "application/activity+json; charset=utf-8", + "HTTP_HOST": DOMAIN, } ) - def send_test_request(#pylint: disable=too-many-arguments - self, - sender, - signer=None, - send_data=None, - digest=None, - date=None): - ''' sends a follow request to the "rat" user ''' + def send_test_request( # pylint: disable=too-many-arguments + self, sender, signer=None, send_data=None, digest=None, date=None + ): + """ sends a follow request to the "rat" user """ now = date or http_date() data = json.dumps(get_follow_activity(sender, self.rat)) digest = digest or make_digest(data) - signature = make_signature( - signer or sender, self.rat.inbox, now, digest) - with patch('bookwyrm.views.inbox.activity_task.delay'): - with patch('bookwyrm.models.user.set_remote_server.delay'): + signature = make_signature(signer or sender, self.rat.inbox, now, digest) + with patch("bookwyrm.views.inbox.activity_task.delay"): + with patch("bookwyrm.models.user.set_remote_server.delay"): return self.send(signature, now, send_data or data, digest) def test_correct_signature(self): - ''' this one should just work ''' + """ this one should just work """ response = self.send_test_request(sender=self.mouse) self.assertEqual(response.status_code, 200) def test_wrong_signature(self): - ''' Messages must be signed by the right actor. - (cat cannot sign messages on behalf of mouse) ''' + """Messages must be signed by the right actor. + (cat cannot sign messages on behalf of mouse)""" response = self.send_test_request(sender=self.mouse, signer=self.cat) self.assertEqual(response.status_code, 401) @responses.activate def test_remote_signer(self): - ''' signtures for remote users ''' - datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json') + """ signtures for remote users """ + datafile = pathlib.Path(__file__).parent.joinpath("data/ap_user.json") data = json.loads(datafile.read_bytes()) - data['id'] = self.fake_remote.remote_id - data['publicKey']['publicKeyPem'] = self.fake_remote.key_pair.public_key - del data['icon'] # Avoid having to return an avatar. + data["id"] = self.fake_remote.remote_id + data["publicKey"]["publicKeyPem"] = self.fake_remote.key_pair.public_key + del data["icon"] # Avoid having to return an avatar. + responses.add(responses.GET, self.fake_remote.remote_id, json=data, status=200) + responses.add( + responses.GET, "https://localhost/.well-known/nodeinfo", status=404 + ) responses.add( responses.GET, - self.fake_remote.remote_id, - json=data, - status=200) - responses.add( - responses.GET, - 'https://localhost/.well-known/nodeinfo', - status=404) - responses.add( - responses.GET, - 'https://example.com/user/mouse/outbox?page=true', - json={'orderedItems': []}, - status=200 + "https://example.com/user/mouse/outbox?page=true", + json={"orderedItems": []}, + status=200, ) - with patch('bookwyrm.models.user.get_remote_reviews.delay'): + with patch("bookwyrm.models.user.get_remote_reviews.delay"): response = self.send_test_request(sender=self.fake_remote) self.assertEqual(response.status_code, 200) @responses.activate def test_key_needs_refresh(self): - ''' an out of date key should be updated and the new key work ''' - datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json') + """ an out of date key should be updated and the new key work """ + datafile = pathlib.Path(__file__).parent.joinpath("data/ap_user.json") data = json.loads(datafile.read_bytes()) - data['id'] = self.fake_remote.remote_id - data['publicKey']['publicKeyPem'] = self.fake_remote.key_pair.public_key - del data['icon'] # Avoid having to return an avatar. + data["id"] = self.fake_remote.remote_id + data["publicKey"]["publicKeyPem"] = self.fake_remote.key_pair.public_key + del data["icon"] # Avoid having to return an avatar. + responses.add(responses.GET, self.fake_remote.remote_id, json=data, status=200) responses.add( - responses.GET, - self.fake_remote.remote_id, - json=data, - status=200) - responses.add( - responses.GET, - 'https://localhost/.well-known/nodeinfo', - status=404) + responses.GET, "https://localhost/.well-known/nodeinfo", status=404 + ) # Second and subsequent fetches get a different key: key_pair = KeyPair(*create_key_pair()) new_sender = Sender(self.fake_remote.remote_id, key_pair) - data['publicKey']['publicKeyPem'] = key_pair.public_key - responses.add( - responses.GET, - self.fake_remote.remote_id, - json=data, - status=200) + data["publicKey"]["publicKeyPem"] = key_pair.public_key + responses.add(responses.GET, self.fake_remote.remote_id, json=data, status=200) - with patch('bookwyrm.models.user.get_remote_reviews.delay'): + with patch("bookwyrm.models.user.get_remote_reviews.delay"): # Key correct: response = self.send_test_request(sender=self.fake_remote) self.assertEqual(response.status_code, 200) @@ -169,43 +153,42 @@ class Signature(TestCase): response = self.send_test_request(sender=self.fake_remote) self.assertEqual(response.status_code, 401) - @responses.activate def test_nonexistent_signer(self): - ''' fail when unable to look up signer ''' + """ fail when unable to look up signer """ responses.add( responses.GET, self.fake_remote.remote_id, - json={'error': 'not found'}, - status=404) + json={"error": "not found"}, + status=404, + ) response = self.send_test_request(sender=self.fake_remote) self.assertEqual(response.status_code, 401) @pytest.mark.integration def test_changed_data(self): - '''Message data must match the digest header.''' - with patch('bookwyrm.activitypub.resolve_remote_id'): + """Message data must match the digest header.""" + with patch("bookwyrm.activitypub.resolve_remote_id"): response = self.send_test_request( - self.mouse, - send_data=get_follow_activity(self.mouse, self.cat)) + self.mouse, send_data=get_follow_activity(self.mouse, self.cat) + ) self.assertEqual(response.status_code, 401) @pytest.mark.integration def test_invalid_digest(self): - ''' signature digest must be valid ''' - with patch('bookwyrm.activitypub.resolve_remote_id'): + """ signature digest must be valid """ + with patch("bookwyrm.activitypub.resolve_remote_id"): response = self.send_test_request( - self.mouse, - digest='SHA-256=AAAAAAAAAAAAAAAAAA') + self.mouse, digest="SHA-256=AAAAAAAAAAAAAAAAAA" + ) self.assertEqual(response.status_code, 401) @pytest.mark.integration def test_old_message(self): - '''Old messages should be rejected to prevent replay attacks.''' - with patch('bookwyrm.activitypub.resolve_remote_id'): + """Old messages should be rejected to prevent replay attacks.""" + with patch("bookwyrm.activitypub.resolve_remote_id"): response = self.send_test_request( - self.mouse, - date=http_date(time.time() - 301) + self.mouse, date=http_date(time.time() - 301) ) self.assertEqual(response.status_code, 401) diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 45c993449..6bdbd04c0 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -1,4 +1,4 @@ -''' style fixes and lookups for templates ''' +""" style fixes and lookups for templates """ import re from unittest.mock import patch @@ -11,82 +11,85 @@ from bookwyrm.templatetags import bookwyrm_tags class TemplateTags(TestCase): - ''' lotta different things here ''' - def setUp(self): - ''' create some filler objects ''' - self.user = models.User.objects.create_user( - 'mouse@example.com', 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse') - with patch('bookwyrm.models.user.set_remote_server.delay'): - self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.rat', 'ratword', - remote_id='http://example.com/rat', local=False) - self.book = models.Edition.objects.create(title='Test Book') + """ lotta different things here """ + def setUp(self): + """ create some filler objects """ + self.user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.mouse", + "mouseword", + local=True, + localname="mouse", + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.rat", + "ratword", + remote_id="http://example.com/rat", + local=False, + ) + self.book = models.Edition.objects.create(title="Test Book") def test_dict_key(self): - ''' just getting a value out of a dict ''' - test_dict = {'a': 1, 'b': 3} - self.assertEqual( - bookwyrm_tags.dict_key(test_dict, 'a'), 1) - self.assertEqual( - bookwyrm_tags.dict_key(test_dict, 'c'), 0) - + """ just getting a value out of a dict """ + test_dict = {"a": 1, "b": 3} + self.assertEqual(bookwyrm_tags.dict_key(test_dict, "a"), 1) + self.assertEqual(bookwyrm_tags.dict_key(test_dict, "c"), 0) def test_get_user_rating(self): - ''' get a user's most recent rating of a book ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.Review.objects.create( - user=self.user, book=self.book, rating=3) - self.assertEqual( - bookwyrm_tags.get_user_rating(self.book, self.user), 3) - + """ get a user's most recent rating of a book """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.Review.objects.create(user=self.user, book=self.book, rating=3) + self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3) def test_get_user_rating_doesnt_exist(self): - ''' there is no rating available ''' - self.assertEqual( - bookwyrm_tags.get_user_rating(self.book, self.user), 0) - + """ there is no rating available """ + self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0) def test_get_user_identifer_local(self): - ''' fall back to the simplest uid available ''' + """ fall back to the simplest uid available """ self.assertNotEqual(self.user.username, self.user.localname) - self.assertEqual( - bookwyrm_tags.get_user_identifier(self.user), 'mouse') + self.assertEqual(bookwyrm_tags.get_user_identifier(self.user), "mouse") def test_get_user_identifer_remote(self): - ''' for a remote user, should be their full username ''' + """ for a remote user, should be their full username """ self.assertEqual( - bookwyrm_tags.get_user_identifier(self.remote_user), - 'rat@example.com') + bookwyrm_tags.get_user_identifier(self.remote_user), "rat@example.com" + ) def test_get_notification_count(self): - ''' just countin' ''' + """ just countin' """ self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 0) - models.Notification.objects.create( - user=self.user, notification_type='FAVORITE') - models.Notification.objects.create( - user=self.user, notification_type='MENTION') + models.Notification.objects.create(user=self.user, notification_type="FAVORITE") + models.Notification.objects.create(user=self.user, notification_type="MENTION") models.Notification.objects.create( - user=self.remote_user, notification_type='FOLLOW') + user=self.remote_user, notification_type="FOLLOW" + ) self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 2) - def test_get_replies(self): - ''' direct replies to a status ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ direct replies to a status """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Review.objects.create( - user=self.user, book=self.book, content='hi') + user=self.user, book=self.book, content="hi" + ) first_child = models.Status.objects.create( - reply_parent=parent, user=self.user, content='hi') + reply_parent=parent, user=self.user, content="hi" + ) second_child = models.Status.objects.create( - reply_parent=parent, user=self.user, content='hi') + reply_parent=parent, user=self.user, content="hi" + ) third_child = models.Status.objects.create( - reply_parent=parent, user=self.user, - deleted=True, deleted_date=timezone.now()) + reply_parent=parent, + user=self.user, + deleted=True, + deleted_date=timezone.now(), + ) replies = bookwyrm_tags.get_replies(parent) self.assertEqual(len(replies), 2) @@ -94,181 +97,162 @@ class TemplateTags(TestCase): self.assertTrue(second_child in replies) self.assertFalse(third_child in replies) - def test_get_parent(self): - ''' get the reply parent of a status ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ get the reply parent of a status """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Review.objects.create( - user=self.user, book=self.book, content='hi') + user=self.user, book=self.book, content="hi" + ) child = models.Status.objects.create( - reply_parent=parent, user=self.user, content='hi') + reply_parent=parent, user=self.user, content="hi" + ) result = bookwyrm_tags.get_parent(child) self.assertEqual(result, parent) self.assertIsInstance(result, models.Review) - def test_get_user_liked(self): - ''' did a user like a status ''' - status = models.Review.objects.create( - user=self.remote_user, book=self.book) + """ did a user like a status """ + status = models.Review.objects.create(user=self.remote_user, book=self.book) self.assertFalse(bookwyrm_tags.get_user_liked(self.user, status)) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.Favorite.objects.create( - user=self.user, - status=status - ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.Favorite.objects.create(user=self.user, status=status) self.assertTrue(bookwyrm_tags.get_user_liked(self.user, status)) - def test_get_user_boosted(self): - ''' did a user boost a status ''' - status = models.Review.objects.create( - user=self.remote_user, book=self.book) + """ did a user boost a status """ + status = models.Review.objects.create(user=self.remote_user, book=self.book) self.assertFalse(bookwyrm_tags.get_user_boosted(self.user, status)) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.Boost.objects.create( - user=self.user, - boosted_status=status - ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.Boost.objects.create(user=self.user, boosted_status=status) self.assertTrue(bookwyrm_tags.get_user_boosted(self.user, status)) - def test_follow_request_exists(self): - ''' does a user want to follow ''' + """ does a user want to follow """ self.assertFalse( - bookwyrm_tags.follow_request_exists(self.user, self.remote_user)) + bookwyrm_tags.follow_request_exists(self.user, self.remote_user) + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.UserFollowRequest.objects.create( - user_subject=self.user, - user_object=self.remote_user) + user_subject=self.user, user_object=self.remote_user + ) self.assertFalse( - bookwyrm_tags.follow_request_exists(self.user, self.remote_user)) + bookwyrm_tags.follow_request_exists(self.user, self.remote_user) + ) self.assertTrue( - bookwyrm_tags.follow_request_exists(self.remote_user, self.user)) - + bookwyrm_tags.follow_request_exists(self.remote_user, self.user) + ) def test_get_boosted(self): - ''' load a boosted status ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Review.objects.create( - user=self.remote_user, book=self.book) - boost = models.Boost.objects.create( - user=self.user, - boosted_status=status - ) + """ load a boosted status """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Review.objects.create(user=self.remote_user, book=self.book) + boost = models.Boost.objects.create(user=self.user, boosted_status=status) boosted = bookwyrm_tags.get_boosted(boost) self.assertIsInstance(boosted, models.Review) self.assertEqual(boosted, status) - def test_get_book_description(self): - ''' grab it from the edition or the parent ''' - work = models.Work.objects.create(title='Test Work') + """ grab it from the edition or the parent """ + work = models.Work.objects.create(title="Test Work") self.book.parent_work = work self.book.save() self.assertIsNone(bookwyrm_tags.get_book_description(self.book)) - work.description = 'hi' + work.description = "hi" work.save() - self.assertEqual(bookwyrm_tags.get_book_description(self.book), 'hi') + self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hi") - self.book.description = 'hello' + self.book.description = "hello" self.book.save() - self.assertEqual(bookwyrm_tags.get_book_description(self.book), 'hello') - + self.assertEqual(bookwyrm_tags.get_book_description(self.book), "hello") def test_get_uuid(self): - ''' uuid functionality ''' - uuid = bookwyrm_tags.get_uuid('hi') - self.assertTrue(re.match(r'hi[A-Za-z0-9\-]', uuid)) - + """ uuid functionality """ + uuid = bookwyrm_tags.get_uuid("hi") + self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid)) def test_time_since(self): - ''' ultraconcise timestamps ''' - self.assertEqual(bookwyrm_tags.time_since('bleh'), '') + """ ultraconcise timestamps """ + self.assertEqual(bookwyrm_tags.time_since("bleh"), "") now = timezone.now() - self.assertEqual(bookwyrm_tags.time_since(now), '0s') + self.assertEqual(bookwyrm_tags.time_since(now), "0s") seconds_ago = now - relativedelta(seconds=4) - self.assertEqual(bookwyrm_tags.time_since(seconds_ago), '4s') + self.assertEqual(bookwyrm_tags.time_since(seconds_ago), "4s") minutes_ago = now - relativedelta(minutes=8) - self.assertEqual(bookwyrm_tags.time_since(minutes_ago), '8m') + self.assertEqual(bookwyrm_tags.time_since(minutes_ago), "8m") hours_ago = now - relativedelta(hours=9) - self.assertEqual(bookwyrm_tags.time_since(hours_ago), '9h') + self.assertEqual(bookwyrm_tags.time_since(hours_ago), "9h") days_ago = now - relativedelta(days=3) - self.assertEqual(bookwyrm_tags.time_since(days_ago), '3d') + self.assertEqual(bookwyrm_tags.time_since(days_ago), "3d") # I am not going to figure out how to mock dates tonight. months_ago = now - relativedelta(months=5) - self.assertTrue(re.match( - r'[A-Z][a-z]{2} \d?\d', - bookwyrm_tags.time_since(months_ago) - )) + self.assertTrue( + re.match(r"[A-Z][a-z]{2} \d?\d", bookwyrm_tags.time_since(months_ago)) + ) years_ago = now - relativedelta(years=10) - self.assertTrue(re.match( - r'[A-Z][a-z]{2} \d?\d \d{4}', - bookwyrm_tags.time_since(years_ago) - )) - + self.assertTrue( + re.match(r"[A-Z][a-z]{2} \d?\d \d{4}", bookwyrm_tags.time_since(years_ago)) + ) def test_get_markdown(self): - ''' mardown format data ''' - result = bookwyrm_tags.get_markdown('_hi_') - self.assertEqual(result, '

hi

') - - result = bookwyrm_tags.get_markdown('_hi_') - self.assertEqual(result, '

hi

') + """ mardown format data """ + result = bookwyrm_tags.get_markdown("_hi_") + self.assertEqual(result, "

hi

") + result = bookwyrm_tags.get_markdown("_hi_") + self.assertEqual(result, "

hi

") def test_get_mentions(self): - ''' list of people mentioned ''' - status = models.Status.objects.create( - content='hi', user=self.remote_user) + """ list of people mentioned """ + status = models.Status.objects.create(content="hi", user=self.remote_user) result = bookwyrm_tags.get_mentions(status, self.user) - self.assertEqual(result, '@rat@example.com ') - + self.assertEqual(result, "@rat@example.com ") def test_get_status_preview_name(self): - ''' status context string ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create(content='hi', user=self.user) + """ status context string """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(content="hi", user=self.user) result = bookwyrm_tags.get_status_preview_name(status) - self.assertEqual(result, 'status') + self.assertEqual(result, "status") status = models.Review.objects.create( - content='hi', user=self.user, book=self.book) + content="hi", user=self.user, book=self.book + ) result = bookwyrm_tags.get_status_preview_name(status) - self.assertEqual(result, 'review of Test Book') + self.assertEqual(result, "review of Test Book") status = models.Comment.objects.create( - content='hi', user=self.user, book=self.book) + content="hi", user=self.user, book=self.book + ) result = bookwyrm_tags.get_status_preview_name(status) - self.assertEqual(result, 'comment on Test Book') + self.assertEqual(result, "comment on Test Book") status = models.Quotation.objects.create( - content='hi', user=self.user, book=self.book) + content="hi", user=self.user, book=self.book + ) result = bookwyrm_tags.get_status_preview_name(status) - self.assertEqual(result, 'quotation from Test Book') - + self.assertEqual(result, "quotation from Test Book") def test_related_status(self): - ''' gets the subclass model for a notification status ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create(content='hi', user=self.user) + """ gets the subclass model for a notification status """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(content="hi", user=self.user) notification = models.Notification.objects.create( - user=self.user, notification_type='MENTION', - related_status=status) + user=self.user, notification_type="MENTION", related_status=status + ) result = bookwyrm_tags.related_status(notification) self.assertIsInstance(result, models.Status) diff --git a/bookwyrm/tests/views/test_authentication.py b/bookwyrm/tests/views/test_authentication.py index dc52719c9..f6d318615 100644 --- a/bookwyrm/tests/views/test_authentication.py +++ b/bookwyrm/tests/views/test_authentication.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import AnonymousUser @@ -14,21 +14,26 @@ from bookwyrm.settings import DOMAIN # pylint: disable=too-many-public-methods class AuthenticationViews(TestCase): - ''' login and password management ''' + """ login and password management """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.com", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False self.settings = models.SiteSettings.objects.create(id=1) def test_login_get(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ login = views.Login.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.anonymous_user result = login(request) @@ -38,135 +43,117 @@ class AuthenticationViews(TestCase): request.user = self.local_user result = login(request) - self.assertEqual(result.url, '/') + self.assertEqual(result.url, "/") self.assertEqual(result.status_code, 302) - def test_register(self): - ''' create a user ''' + """ create a user """ view = views.Register.as_view() self.assertEqual(models.User.objects.count(), 1) request = self.factory.post( - 'register/', + "register/", { - 'localname': 'nutria-user.user_nutria', - 'password': 'mouseword', - 'email': 'aa@bb.cccc' - }) - with patch('bookwyrm.views.authentication.login'): + "localname": "nutria-user.user_nutria", + "password": "mouseword", + "email": "aa@bb.cccc", + }, + ) + with patch("bookwyrm.views.authentication.login"): response = view(request) self.assertEqual(models.User.objects.count(), 2) self.assertEqual(response.status_code, 302) nutria = models.User.objects.last() - self.assertEqual(nutria.username, 'nutria-user.user_nutria@%s' % DOMAIN) - self.assertEqual(nutria.localname, 'nutria-user.user_nutria') + self.assertEqual(nutria.username, "nutria-user.user_nutria@%s" % DOMAIN) + self.assertEqual(nutria.localname, "nutria-user.user_nutria") self.assertEqual(nutria.local, True) def test_register_trailing_space(self): - ''' django handles this so weirdly ''' + """ django handles this so weirdly """ view = views.Register.as_view() request = self.factory.post( - 'register/', - { - 'localname': 'nutria ', - 'password': 'mouseword', - 'email': 'aa@bb.ccc' - }) - with patch('bookwyrm.views.authentication.login'): + "register/", + {"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"}, + ) + with patch("bookwyrm.views.authentication.login"): response = view(request) self.assertEqual(models.User.objects.count(), 2) self.assertEqual(response.status_code, 302) nutria = models.User.objects.last() - self.assertEqual(nutria.username, 'nutria@%s' % DOMAIN) - self.assertEqual(nutria.localname, 'nutria') + self.assertEqual(nutria.username, "nutria@%s" % DOMAIN) + self.assertEqual(nutria.localname, "nutria") self.assertEqual(nutria.local, True) def test_register_invalid_email(self): - ''' gotta have an email ''' + """ gotta have an email """ view = views.Register.as_view() self.assertEqual(models.User.objects.count(), 1) request = self.factory.post( - 'register/', - { - 'localname': 'nutria', - 'password': 'mouseword', - 'email': 'aa' - }) + "register/", {"localname": "nutria", "password": "mouseword", "email": "aa"} + ) response = view(request) self.assertEqual(models.User.objects.count(), 1) response.render() def test_register_invalid_username(self): - ''' gotta have an email ''' + """ gotta have an email """ view = views.Register.as_view() self.assertEqual(models.User.objects.count(), 1) request = self.factory.post( - 'register/', - { - 'localname': 'nut@ria', - 'password': 'mouseword', - 'email': 'aa@bb.ccc' - }) + "register/", + {"localname": "nut@ria", "password": "mouseword", "email": "aa@bb.ccc"}, + ) response = view(request) self.assertEqual(models.User.objects.count(), 1) response.render() request = self.factory.post( - 'register/', - { - 'localname': 'nutr ia', - 'password': 'mouseword', - 'email': 'aa@bb.ccc' - }) + "register/", + {"localname": "nutr ia", "password": "mouseword", "email": "aa@bb.ccc"}, + ) response = view(request) self.assertEqual(models.User.objects.count(), 1) response.render() request = self.factory.post( - 'register/', - { - 'localname': 'nut@ria', - 'password': 'mouseword', - 'email': 'aa@bb.ccc' - }) + "register/", + {"localname": "nut@ria", "password": "mouseword", "email": "aa@bb.ccc"}, + ) response = view(request) self.assertEqual(models.User.objects.count(), 1) response.render() - def test_register_closed_instance(self): - ''' you can't just register ''' + """ you can't just register """ view = views.Register.as_view() self.settings.allow_registration = False self.settings.save() request = self.factory.post( - 'register/', - { - 'localname': 'nutria ', - 'password': 'mouseword', - 'email': 'aa@bb.ccc' - }) + "register/", + {"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"}, + ) with self.assertRaises(PermissionDenied): view(request) def test_register_invite(self): - ''' you can't just register ''' + """ you can't just register """ view = views.Register.as_view() self.settings.allow_registration = False self.settings.save() models.SiteInvite.objects.create( - code='testcode', user=self.local_user, use_limit=1) + code="testcode", user=self.local_user, use_limit=1 + ) self.assertEqual(models.SiteInvite.objects.get().times_used, 0) request = self.factory.post( - 'register/', + "register/", { - 'localname': 'nutria', - 'password': 'mouseword', - 'email': 'aa@bb.ccc', - 'invite_code': 'testcode' - }) - with patch('bookwyrm.views.authentication.login'): + "localname": "nutria", + "password": "mouseword", + "email": "aa@bb.ccc", + "invite_code": "testcode", + }, + ) + with patch("bookwyrm.views.authentication.login"): response = view(request) self.assertEqual(models.User.objects.count(), 2) self.assertEqual(response.status_code, 302) @@ -174,26 +161,28 @@ class AuthenticationViews(TestCase): # invite already used to max capacity request = self.factory.post( - 'register/', + "register/", { - 'localname': 'nutria2', - 'password': 'mouseword', - 'email': 'aa@bb.ccc', - 'invite_code': 'testcode' - }) + "localname": "nutria2", + "password": "mouseword", + "email": "aa@bb.ccc", + "invite_code": "testcode", + }, + ) with self.assertRaises(PermissionDenied): response = view(request) self.assertEqual(models.User.objects.count(), 2) # bad invite code request = self.factory.post( - 'register/', + "register/", { - 'localname': 'nutria3', - 'password': 'mouseword', - 'email': 'aa@bb.ccc', - 'invite_code': 'dkfkdjgdfkjgkdfj' - }) + "localname": "nutria3", + "password": "mouseword", + "email": "aa@bb.ccc", + "invite_code": "dkfkdjgdfkjgkdfj", + }, + ) with self.assertRaises(Http404): response = view(request) self.assertEqual(models.User.objects.count(), 2) diff --git a/bookwyrm/tests/views/test_author.py b/bookwyrm/tests/views/test_author.py index 3c1a68bb2..bb047b7c1 100644 --- a/bookwyrm/tests/views/test_author.py +++ b/bookwyrm/tests/views/test_author.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType @@ -12,37 +12,41 @@ from bookwyrm.activitypub import ActivitypubResponse class AuthorViews(TestCase): - ''' author views''' + """ author views""" + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.group = Group.objects.create(name='editor') + self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( - name='edit_book', - codename='edit_book', - content_type=ContentType.objects.get_for_model(models.User)).id + name="edit_book", + codename="edit_book", + content_type=ContentType.objects.get_for_model(models.User), + ).id ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, ) models.SiteSettings.objects.create() - def test_author_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Author.as_view() - author = models.Author.objects.create(name='Jessica') - request = self.factory.get('') - with patch('bookwyrm.views.author.is_api_request') as is_api: + author = models.Author.objects.create(name="Jessica") + request = self.factory.get("") + with patch("bookwyrm.views.author.is_api_request") as is_api: is_api.return_value = False result = view(request, author.id) self.assertIsInstance(result, TemplateResponse) @@ -50,19 +54,18 @@ class AuthorViews(TestCase): self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200) - request = self.factory.get('') - with patch('bookwyrm.views.author.is_api_request') as is_api: + request = self.factory.get("") + with patch("bookwyrm.views.author.is_api_request") as is_api: is_api.return_value = True result = view(request, author.id) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_edit_author_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.EditAuthor.as_view() - author = models.Author.objects.create(name='Test Author') - request = self.factory.get('') + author = models.Author.objects.create(name="Test Author") + request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True @@ -72,52 +75,51 @@ class AuthorViews(TestCase): self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200) - def test_edit_author(self): - ''' edit an author ''' + """ edit an author """ view = views.EditAuthor.as_view() - author = models.Author.objects.create(name='Test Author') + author = models.Author.objects.create(name="Test Author") self.local_user.groups.add(self.group) form = forms.AuthorForm(instance=author) - form.data['name'] = 'New Name' - form.data['last_edited_by'] = self.local_user.id - request = self.factory.post('', form.data) + form.data["name"] = "New Name" + form.data["last_edited_by"] = self.local_user.id + request = self.factory.post("", form.data) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, author.id) author.refresh_from_db() - self.assertEqual(author.name, 'New Name') + self.assertEqual(author.name, "New Name") self.assertEqual(author.last_edited_by, self.local_user) def test_edit_author_non_editor(self): - ''' edit an author with invalid post data''' + """ edit an author with invalid post data""" view = views.EditAuthor.as_view() - author = models.Author.objects.create(name='Test Author') + author = models.Author.objects.create(name="Test Author") form = forms.AuthorForm(instance=author) - form.data['name'] = 'New Name' - form.data['last_edited_by'] = self.local_user.id - request = self.factory.post('', form.data) + form.data["name"] = "New Name" + form.data["last_edited_by"] = self.local_user.id + request = self.factory.post("", form.data) request.user = self.local_user with self.assertRaises(PermissionDenied): view(request, author.id) author.refresh_from_db() - self.assertEqual(author.name, 'Test Author') + self.assertEqual(author.name, "Test Author") def test_edit_author_invalid_form(self): - ''' edit an author with invalid post data''' + """ edit an author with invalid post data""" view = views.EditAuthor.as_view() - author = models.Author.objects.create(name='Test Author') + author = models.Author.objects.create(name="Test Author") self.local_user.groups.add(self.group) form = forms.AuthorForm(instance=author) - form.data['name'] = '' - form.data['last_edited_by'] = self.local_user.id - request = self.factory.post('', form.data) + form.data["name"] = "" + form.data["last_edited_by"] = self.local_user.id + request = self.factory.post("", form.data) request.user = self.local_user resp = view(request, author.id) author.refresh_from_db() - self.assertEqual(author.name, 'Test Author') + self.assertEqual(author.name, "Test Author") resp.render() self.assertEqual(resp.status_code, 200) diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index 315dc2247..60920e38b 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase @@ -8,28 +8,34 @@ from bookwyrm import models, views class BlockViews(TestCase): - ''' view user and edit profile ''' + """ view user and edit profile """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') - with patch('bookwyrm.models.user.set_remote_server.delay'): + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', + "rat", + "rat@rat.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) models.SiteSettings.objects.create() - def test_block_get(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Block.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) @@ -37,19 +43,19 @@ class BlockViews(TestCase): self.assertEqual(result.status_code, 200) def test_block_post(self): - ''' create a "block" database entry from an activity ''' + """ create a "block" database entry from an activity """ view = views.Block.as_view() self.local_user.followers.add(self.remote_user) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user) + user_subject=self.local_user, user_object=self.remote_user + ) self.assertTrue(models.UserFollows.objects.exists()) self.assertTrue(models.UserFollowRequest.objects.exists()) - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.remote_user.id) block = models.UserBlocks.objects.get() self.assertEqual(block.user_subject, self.local_user) @@ -59,12 +65,12 @@ class BlockViews(TestCase): self.assertFalse(models.UserFollowRequest.objects.exists()) def test_unblock(self): - ''' undo a block ''' + """ undo a block """ self.local_user.blocks.add(self.remote_user) - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.block.unblock(request, self.remote_user.id) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_book.py b/bookwyrm/tests/views/test_book.py index b3360200d..eb8c89b98 100644 --- a/bookwyrm/tests/views/test_book.py +++ b/bookwyrm/tests/views/test_book.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType @@ -11,55 +11,58 @@ from bookwyrm.activitypub import ActivitypubResponse class BookViews(TestCase): - ''' books books books ''' + """ books books books """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.group = Group.objects.create(name='editor') + self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( - name='edit_book', - codename='edit_book', - content_type=ContentType.objects.get_for_model(models.User)).id + name="edit_book", + codename="edit_book", + content_type=ContentType.objects.get_for_model(models.User), + ).id ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, ) models.SiteSettings.objects.create() - def test_book_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Book.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.books.is_api_request') as is_api: + with patch("bookwyrm.views.books.is_api_request") as is_api: is_api.return_value = False result = view(request, self.book.id) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - request = self.factory.get('') - with patch('bookwyrm.views.books.is_api_request') as is_api: + request = self.factory.get("") + with patch("bookwyrm.views.books.is_api_request") as is_api: is_api.return_value = True result = view(request, self.book.id) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_edit_book_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.EditBook.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True result = view(request, self.book.id) @@ -67,66 +70,57 @@ class BookViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_edit_book(self): - ''' lets a user edit a book ''' + """ lets a user edit a book """ view = views.EditBook.as_view() self.local_user.groups.add(self.group) form = forms.EditionForm(instance=self.book) - form.data['title'] = 'New Title' - form.data['last_edited_by'] = self.local_user.id - request = self.factory.post('', form.data) + form.data["title"] = "New Title" + form.data["last_edited_by"] = self.local_user.id + request = self.factory.post("", form.data) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.book.id) self.book.refresh_from_db() - self.assertEqual(self.book.title, 'New Title') - + self.assertEqual(self.book.title, "New Title") def test_switch_edition(self): - ''' updates user's relationships to a book ''' - work = models.Work.objects.create(title='test work') - edition1 = models.Edition.objects.create( - title='first ed', parent_work=work) - edition2 = models.Edition.objects.create( - title='second ed', parent_work=work) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - shelf = models.Shelf.objects.create( - name='Test Shelf', user=self.local_user) + """ updates user's relationships to a book """ + work = models.Work.objects.create(title="test work") + edition1 = models.Edition.objects.create(title="first ed", parent_work=work) + edition2 = models.Edition.objects.create(title="second ed", parent_work=work) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user) models.ShelfBook.objects.create( book=edition1, user=self.local_user, shelf=shelf, ) - models.ReadThrough.objects.create( - user=self.local_user, book=edition1) + models.ReadThrough.objects.create(user=self.local_user, book=edition1) self.assertEqual(models.ShelfBook.objects.get().book, edition1) self.assertEqual(models.ReadThrough.objects.get().book, edition1) - request = self.factory.post('', { - 'edition': edition2.id - }) + request = self.factory.post("", {"edition": edition2.id}) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.switch_edition(request) self.assertEqual(models.ShelfBook.objects.get().book, edition2) self.assertEqual(models.ReadThrough.objects.get().book, edition2) - def test_editions_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Editions.as_view() - request = self.factory.get('') - with patch('bookwyrm.views.books.is_api_request') as is_api: + request = self.factory.get("") + with patch("bookwyrm.views.books.is_api_request") as is_api: is_api.return_value = False result = view(request, self.work.id) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - request = self.factory.get('') - with patch('bookwyrm.views.books.is_api_request') as is_api: + request = self.factory.get("") + with patch("bookwyrm.views.books.is_api_request") as is_api: is_api.return_value = True result = view(request, self.work.id) self.assertIsInstance(result, ActivitypubResponse) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 70cf41f6e..d11d7415f 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -8,20 +8,24 @@ from bookwyrm import views class FederationViews(TestCase): - ''' every response to a get request, html or json ''' + """ every response to a get request, html or json """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() - def test_federation_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Federation.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True result = view(request) diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index 93af9944b..c54be0061 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase @@ -10,95 +10,93 @@ from bookwyrm.activitypub import ActivitypubResponse class FeedMessageViews(TestCase): - ''' dms ''' + """ dms """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.book = models.Edition.objects.create( - parent_work=models.Work.objects.create(title='hi'), - title='Example Edition', - remote_id='https://example.com/book/1', + parent_work=models.Work.objects.create(title="hi"), + title="Example Edition", + remote_id="https://example.com/book/1", ) models.SiteSettings.objects.create() - def test_feed(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Feed.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - result = view(request, 'local') + result = view(request, "local") self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_status_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Status.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - content='hi', user=self.local_user) - request = self.factory.get('') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(content="hi", user=self.local_user) + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.feed.is_api_request') as is_api: + with patch("bookwyrm.views.feed.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'mouse', status.id) + result = view(request, "mouse", status.id) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.feed.is_api_request') as is_api: + with patch("bookwyrm.views.feed.is_api_request") as is_api: is_api.return_value = True - result = view(request, 'mouse', status.id) + result = view(request, "mouse", status.id) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_replies_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Replies.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - content='hi', user=self.local_user) - request = self.factory.get('') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(content="hi", user=self.local_user) + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.feed.is_api_request') as is_api: + with patch("bookwyrm.views.feed.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'mouse', status.id) + result = view(request, "mouse", status.id) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.feed.is_api_request') as is_api: + with patch("bookwyrm.views.feed.is_api_request") as is_api: is_api.return_value = True - result = view(request, 'mouse', status.id) + result = view(request, "mouse", status.id) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_direct_messages_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.DirectMessage.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_get_suggested_book(self): - ''' gets books the ~*~ algorithm ~*~ thinks you want to post about ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ gets books the ~*~ algorithm ~*~ thinks you want to post about """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( book=self.book, user=self.local_user, - shelf=self.local_user.shelf_set.get(identifier='reading') + shelf=self.local_user.shelf_set.get(identifier="reading"), ) suggestions = views.feed.get_suggested_books(self.local_user) - self.assertEqual(suggestions[0]['name'], 'Currently Reading') - self.assertEqual(suggestions[0]['books'][0], self.book) + self.assertEqual(suggestions[0]["name"], "Currently Reading") + self.assertEqual(suggestions[0]["books"][0], self.book) diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 62543d2de..67ac0f0b8 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType @@ -9,148 +9,147 @@ from bookwyrm import models, views class BookViews(TestCase): - ''' books books books ''' + """ books books books """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - with patch('bookwyrm.models.user.set_remote_server'): + with patch("bookwyrm.models.user.set_remote_server"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@email.com', 'ratword', + "rat", + "rat@email.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) - self.group = Group.objects.create(name='editor') + self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( - name='edit_book', - codename='edit_book', - content_type=ContentType.objects.get_for_model(models.User)).id + name="edit_book", + codename="edit_book", + content_type=ContentType.objects.get_for_model(models.User), + ).id ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, ) def test_handle_follow_remote(self): - ''' send a follow request ''' - request = self.factory.post('', {'user': self.remote_user.username}) + """ send a follow request """ + request = self.factory.post("", {"user": self.remote_user.username}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.follow(request) rel = models.UserFollowRequest.objects.get() self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, self.remote_user) - self.assertEqual(rel.status, 'follow_request') - + self.assertEqual(rel.status, "follow_request") def test_handle_follow_local_manually_approves(self): - ''' send a follow request ''' + """ send a follow request """ rat = models.User.objects.create_user( - 'rat@local.com', 'rat@rat.com', 'ratword', - local=True, localname='rat', - remote_id='https://example.com/users/rat', + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", manually_approves_followers=True, ) - request = self.factory.post('', {'user': rat}) + request = self.factory.post("", {"user": rat}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.follow(request) rel = models.UserFollowRequest.objects.get() self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, rat) - self.assertEqual(rel.status, 'follow_request') - + self.assertEqual(rel.status, "follow_request") def test_handle_follow_local(self): - ''' send a follow request ''' + """ send a follow request """ rat = models.User.objects.create_user( - 'rat@local.com', 'rat@rat.com', 'ratword', - local=True, localname='rat', - remote_id='https://example.com/users/rat', + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", ) - request = self.factory.post('', {'user': rat}) + request = self.factory.post("", {"user": rat}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.follow(request) rel = models.UserFollows.objects.get() self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, rat) - self.assertEqual(rel.status, 'follows') - + self.assertEqual(rel.status, "follows") def test_handle_unfollow(self): - ''' send an unfollow ''' - request = self.factory.post('', {'user': self.remote_user.username}) + """ send an unfollow """ + request = self.factory.post("", {"user": self.remote_user.username}) request.user = self.local_user self.remote_user.followers.add(self.local_user) self.assertEqual(self.remote_user.followers.count(), 1) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ - as mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: views.unfollow(request) self.assertEqual(mock.call_count, 1) self.assertEqual(self.remote_user.followers.count(), 0) - def test_handle_accept(self): - ''' accept a follow request ''' + """ accept a follow request """ self.local_user.manually_approves_followers = True self.local_user.save(broadcast=False) - request = self.factory.post('', {'user': self.remote_user.username}) + request = self.factory.post("", {"user": self.remote_user.username}) request.user = self.local_user rel = models.UserFollowRequest.objects.create( - user_subject=self.remote_user, - user_object=self.local_user + user_subject=self.remote_user, user_object=self.local_user ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.accept_follow_request(request) # request should be deleted - self.assertEqual( - models.UserFollowRequest.objects.filter(id=rel.id).count(), 0 - ) + self.assertEqual(models.UserFollowRequest.objects.filter(id=rel.id).count(), 0) # follow relationship should exist self.assertEqual(self.local_user.followers.first(), self.remote_user) - def test_handle_reject(self): - ''' reject a follow request ''' + """ reject a follow request """ self.local_user.manually_approves_followers = True self.local_user.save(broadcast=False) - request = self.factory.post('', {'user': self.remote_user.username}) + request = self.factory.post("", {"user": self.remote_user.username}) request.user = self.local_user rel = models.UserFollowRequest.objects.create( - user_subject=self.remote_user, - user_object=self.local_user + user_subject=self.remote_user, user_object=self.local_user ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.delete_follow_request(request) # request should be deleted - self.assertEqual( - models.UserFollowRequest.objects.filter(id=rel.id).count(), 0 - ) + self.assertEqual(models.UserFollowRequest.objects.filter(id=rel.id).count(), 0) # follow relationship should not exist - self.assertEqual( - models.UserFollows.objects.filter(id=rel.id).count(), 0 - ) + self.assertEqual(models.UserFollows.objects.filter(id=rel.id).count(), 0) diff --git a/bookwyrm/tests/views/test_goal.py b/bookwyrm/tests/views/test_goal.py index 0d534112a..990bb5c2b 100644 --- a/bookwyrm/tests/views/test_goal.py +++ b/bookwyrm/tests/views/test_goal.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.utils import timezone @@ -11,60 +11,65 @@ from bookwyrm import models, views class GoalViews(TestCase): - ''' viewing and creating statuses ''' + """ viewing and creating statuses """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) self.rat = models.User.objects.create_user( - 'rat@local.com', 'rat@rat.com', 'ratword', - local=True, localname='rat', - remote_id='https://example.com/users/rat', + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", ) self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', + title="Example Edition", + remote_id="https://example.com/book/1", ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create() - def test_goal_page_no_goal(self): - ''' view a reading goal page for another's unset goal ''' + """ view a reading goal page for another's unset goal """ view = views.Goal.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.rat result = view(request, self.local_user.localname, 2020) self.assertEqual(result.status_code, 404) def test_goal_page_no_goal_self(self): - ''' view a reading goal page for your own unset goal ''' + """ view a reading goal page for your own unset goal """ view = views.Goal.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request, self.local_user.localname, 2020) result.render() self.assertIsInstance(result, TemplateResponse) - def test_goal_page_anonymous(self): - ''' can't view it without login ''' + """ can't view it without login """ view = views.Goal.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.anonymous_user result = view(request, self.local_user.localname, 2020) self.assertEqual(result.status_code, 302) def test_goal_page_public(self): - ''' view a user's public goal ''' + """ view a user's public goal """ models.ReadThrough.objects.create( finish_date=timezone.now(), user=self.local_user, @@ -75,9 +80,10 @@ class GoalViews(TestCase): user=self.local_user, year=timezone.now().year, goal=128937123, - privacy='public') + privacy="public", + ) view = views.Goal.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.rat result = view(request, self.local_user.localname, timezone.now().year) @@ -85,40 +91,40 @@ class GoalViews(TestCase): self.assertIsInstance(result, TemplateResponse) def test_goal_page_private(self): - ''' view a user's private goal ''' + """ view a user's private goal """ models.AnnualGoal.objects.create( - user=self.local_user, - year=2020, - goal=15, - privacy='followers') + user=self.local_user, year=2020, goal=15, privacy="followers" + ) view = views.Goal.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.rat result = view(request, self.local_user.localname, 2020) self.assertEqual(result.status_code, 404) - def test_create_goal(self): - ''' create a new goal ''' + """ create a new goal """ view = views.Goal.as_view() - request = self.factory.post('', { - 'user': self.local_user.id, - 'goal': 10, - 'year': 2020, - 'privacy': 'unlisted', - 'post-status': True - }) + request = self.factory.post( + "", + { + "user": self.local_user.id, + "goal": 10, + "year": 2020, + "privacy": "unlisted", + "post-status": True, + }, + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.local_user.localname, 2020) goal = models.AnnualGoal.objects.get() self.assertEqual(goal.user, self.local_user) self.assertEqual(goal.goal, 10) self.assertEqual(goal.year, 2020) - self.assertEqual(goal.privacy, 'unlisted') + self.assertEqual(goal.privacy, "unlisted") status = models.GeneratedNote.objects.get() self.assertEqual(status.user, self.local_user) - self.assertEqual(status.privacy, 'unlisted') + self.assertEqual(status.privacy, "unlisted") diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index eff083072..bb4cf69c4 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ import json from unittest.mock import patch import pathlib @@ -9,127 +9,129 @@ import responses from bookwyrm import models, views from bookwyrm.settings import USER_AGENT + class ViewsHelpers(TestCase): - ''' viewing and creating statuses ''' + """ viewing and creating statuses """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Test Book', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=self.work, ) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', + "rat", + "rat@rat.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json' - ) + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") self.userdata = json.loads(datafile.read_bytes()) - del self.userdata['icon'] - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + del self.userdata["icon"] + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.shelf = models.Shelf.objects.create( - name='Test Shelf', - identifier='test-shelf', - user=self.local_user + name="Test Shelf", identifier="test-shelf", user=self.local_user ) - def test_get_edition(self): - ''' given an edition or a work, returns an edition ''' - self.assertEqual( - views.helpers.get_edition(self.book.id), self.book) - self.assertEqual( - views.helpers.get_edition(self.work.id), self.book) + """ given an edition or a work, returns an edition """ + self.assertEqual(views.helpers.get_edition(self.book.id), self.book) + self.assertEqual(views.helpers.get_edition(self.work.id), self.book) def test_get_user_from_username(self): - ''' works for either localname or username ''' + """ works for either localname or username """ self.assertEqual( - views.helpers.get_user_from_username( - self.local_user, 'mouse'), self.local_user) + views.helpers.get_user_from_username(self.local_user, "mouse"), + self.local_user, + ) self.assertEqual( - views.helpers.get_user_from_username( - self.local_user, 'mouse@local.com'), self.local_user) + views.helpers.get_user_from_username(self.local_user, "mouse@local.com"), + self.local_user, + ) with self.assertRaises(models.User.DoesNotExist): - views.helpers.get_user_from_username( - self.local_user, 'mojfse@example.com') - + views.helpers.get_user_from_username(self.local_user, "mojfse@example.com") def test_is_api_request(self): - ''' should it return html or json ''' - request = self.factory.get('/path') - request.headers = {'Accept': 'application/json'} + """ should it return html or json """ + request = self.factory.get("/path") + request.headers = {"Accept": "application/json"} self.assertTrue(views.helpers.is_api_request(request)) - request = self.factory.get('/path.json') - request.headers = {'Accept': 'Praise'} + request = self.factory.get("/path.json") + request.headers = {"Accept": "Praise"} self.assertTrue(views.helpers.is_api_request(request)) - request = self.factory.get('/path') - request.headers = {'Accept': 'Praise'} + request = self.factory.get("/path") + request.headers = {"Accept": "Praise"} self.assertFalse(views.helpers.is_api_request(request)) - def test_get_activity_feed(self): - ''' loads statuses ''' + """ loads statuses """ rat = models.User.objects.create_user( - 'rat', 'rat@rat.rat', 'password', local=True) + "rat", "rat@rat.rat", "password", local=True + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): public_status = models.Comment.objects.create( - content='public status', book=self.book, user=self.local_user) + content="public status", book=self.book, user=self.local_user + ) direct_status = models.Status.objects.create( - content='direct', user=self.local_user, privacy='direct') + content="direct", user=self.local_user, privacy="direct" + ) - rat_public = models.Status.objects.create( - content='blah blah', user=rat) + rat_public = models.Status.objects.create(content="blah blah", user=rat) rat_unlisted = models.Status.objects.create( - content='blah blah', user=rat, privacy='unlisted') + content="blah blah", user=rat, privacy="unlisted" + ) remote_status = models.Status.objects.create( - content='blah blah', user=self.remote_user) + content="blah blah", user=self.remote_user + ) followers_status = models.Status.objects.create( - content='blah', user=rat, privacy='followers') + content="blah", user=rat, privacy="followers" + ) rat_mention = models.Status.objects.create( - content='blah blah blah', user=rat, privacy='followers') + content="blah blah blah", user=rat, privacy="followers" + ) rat_mention.mention_users.set([self.local_user]) statuses = views.helpers.get_activity_feed( self.local_user, - privacy=['public', 'unlisted', 'followers'], + privacy=["public", "unlisted", "followers"], following_only=True, - queryset=models.Comment.objects + queryset=models.Comment.objects, ) self.assertEqual(len(statuses), 1) self.assertEqual(statuses[0], public_status) statuses = views.helpers.get_activity_feed( - self.local_user, - privacy=['public', 'followers'], - local_only=True + self.local_user, privacy=["public", "followers"], local_only=True ) self.assertEqual(len(statuses), 2) self.assertEqual(statuses[1], public_status) self.assertEqual(statuses[0], rat_public) - statuses = views.helpers.get_activity_feed( - self.local_user, privacy=['direct']) + statuses = views.helpers.get_activity_feed(self.local_user, privacy=["direct"]) self.assertEqual(len(statuses), 1) self.assertEqual(statuses[0], direct_status) statuses = views.helpers.get_activity_feed( self.local_user, - privacy=['public', 'followers'], + privacy=["public", "followers"], ) self.assertEqual(len(statuses), 3) self.assertEqual(statuses[2], public_status) @@ -138,8 +140,8 @@ class ViewsHelpers(TestCase): statuses = views.helpers.get_activity_feed( self.local_user, - privacy=['public', 'unlisted', 'followers'], - following_only=True + privacy=["public", "unlisted", "followers"], + following_only=True, ) self.assertEqual(len(statuses), 2) self.assertEqual(statuses[1], public_status) @@ -148,8 +150,8 @@ class ViewsHelpers(TestCase): rat.followers.add(self.local_user) statuses = views.helpers.get_activity_feed( self.local_user, - privacy=['public', 'unlisted', 'followers'], - following_only=True + privacy=["public", "unlisted", "followers"], + following_only=True, ) self.assertEqual(len(statuses), 5) self.assertEqual(statuses[4], public_status) @@ -158,187 +160,187 @@ class ViewsHelpers(TestCase): self.assertEqual(statuses[1], followers_status) self.assertEqual(statuses[0], rat_mention) - def test_get_activity_feed_blocks(self): - ''' feed generation with blocked users ''' + """ feed generation with blocked users """ rat = models.User.objects.create_user( - 'rat', 'rat@rat.rat', 'password', local=True) + "rat", "rat@rat.rat", "password", local=True + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): public_status = models.Comment.objects.create( - content='public status', book=self.book, user=self.local_user) - rat_public = models.Status.objects.create( - content='blah blah', user=rat) + content="public status", book=self.book, user=self.local_user + ) + rat_public = models.Status.objects.create(content="blah blah", user=rat) statuses = views.helpers.get_activity_feed( - self.local_user, privacy=['public']) + self.local_user, privacy=["public"] + ) self.assertEqual(len(statuses), 2) # block relationship rat.blocks.add(self.local_user) - statuses = views.helpers.get_activity_feed( - self.local_user, privacy=['public']) + statuses = views.helpers.get_activity_feed(self.local_user, privacy=["public"]) self.assertEqual(len(statuses), 1) self.assertEqual(statuses[0], public_status) - statuses = views.helpers.get_activity_feed( - rat, privacy=['public']) + statuses = views.helpers.get_activity_feed(rat, privacy=["public"]) self.assertEqual(len(statuses), 1) self.assertEqual(statuses[0], rat_public) - - def test_is_bookwyrm_request(self): - ''' checks if a request came from a bookwyrm instance ''' - request = self.factory.get('', {'q': 'Test Book'}) + """ checks if a request came from a bookwyrm instance """ + request = self.factory.get("", {"q": "Test Book"}) self.assertFalse(views.helpers.is_bookwyrm_request(request)) request = self.factory.get( - '', {'q': 'Test Book'}, - HTTP_USER_AGENT=\ - "http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)" + "", + {"q": "Test Book"}, + HTTP_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"}, HTTP_USER_AGENT=USER_AGENT) self.assertTrue(views.helpers.is_bookwyrm_request(request)) - def test_existing_user(self): - ''' simple database lookup by username ''' - result = views.helpers.handle_remote_webfinger('@mouse@local.com') + """ simple database lookup by username """ + result = views.helpers.handle_remote_webfinger("@mouse@local.com") self.assertEqual(result, self.local_user) - result = views.helpers.handle_remote_webfinger('mouse@local.com') + result = views.helpers.handle_remote_webfinger("mouse@local.com") self.assertEqual(result, self.local_user) - @responses.activate def test_load_user(self): - ''' find a remote user using webfinger ''' - username = 'mouse@example.com' + """ find a remote user using webfinger """ + username = "mouse@example.com" wellknown = { "subject": "acct:mouse@example.com", - "links": [{ - "rel": "self", - "type": "application/activity+json", - "href": "https://example.com/user/mouse" - }] + "links": [ + { + "rel": "self", + "type": "application/activity+json", + "href": "https://example.com/user/mouse", + } + ], } responses.add( responses.GET, - 'https://example.com/.well-known/webfinger?resource=acct:%s' \ - % username, + "https://example.com/.well-known/webfinger?resource=acct:%s" % username, json=wellknown, - status=200) + status=200, + ) responses.add( responses.GET, - 'https://example.com/user/mouse', + "https://example.com/user/mouse", json=self.userdata, - status=200) - with patch('bookwyrm.models.user.set_remote_server.delay'): - result = views.helpers.handle_remote_webfinger('@mouse@example.com') + status=200, + ) + with patch("bookwyrm.models.user.set_remote_server.delay"): + result = views.helpers.handle_remote_webfinger("@mouse@example.com") self.assertIsInstance(result, models.User) - self.assertEqual(result.username, 'mouse@example.com') - + self.assertEqual(result.username, "mouse@example.com") def test_handle_reading_status_to_read(self): - ''' posts shelve activities ''' - shelf = self.local_user.shelf_set.get(identifier='to-read') - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ posts shelve activities """ + shelf = self.local_user.shelf_set.get(identifier="to-read") + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( - self.local_user, shelf, self.book, 'public') + self.local_user, shelf, self.book, "public" + ) status = models.GeneratedNote.objects.get() self.assertEqual(status.user, self.local_user) self.assertEqual(status.mention_books.first(), self.book) - self.assertEqual(status.content, 'wants to read') + self.assertEqual(status.content, "wants to read") def test_handle_reading_status_reading(self): - ''' posts shelve activities ''' - shelf = self.local_user.shelf_set.get(identifier='reading') - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ posts shelve activities """ + shelf = self.local_user.shelf_set.get(identifier="reading") + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( - self.local_user, shelf, self.book, 'public') + self.local_user, shelf, self.book, "public" + ) status = models.GeneratedNote.objects.get() self.assertEqual(status.user, self.local_user) self.assertEqual(status.mention_books.first(), self.book) - self.assertEqual(status.content, 'started reading') + self.assertEqual(status.content, "started reading") def test_handle_reading_status_read(self): - ''' posts shelve activities ''' - shelf = self.local_user.shelf_set.get(identifier='read') - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ posts shelve activities """ + shelf = self.local_user.shelf_set.get(identifier="read") + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( - self.local_user, shelf, self.book, 'public') + self.local_user, shelf, self.book, "public" + ) status = models.GeneratedNote.objects.get() self.assertEqual(status.user, self.local_user) self.assertEqual(status.mention_books.first(), self.book) - self.assertEqual(status.content, 'finished reading') + self.assertEqual(status.content, "finished reading") def test_handle_reading_status_other(self): - ''' posts shelve activities ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ posts shelve activities """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( - self.local_user, self.shelf, self.book, 'public') + self.local_user, self.shelf, self.book, "public" + ) self.assertFalse(models.GeneratedNote.objects.exists()) def test_object_visible_to_user(self): - ''' does a user have permission to view an object ''' + """ does a user have permission to view an object """ obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='public') - self.assertTrue( - views.helpers.object_visible_to_user(self.local_user, obj)) + content="hi", user=self.remote_user, privacy="public" + ) + self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Shelf.objects.create( - name='test', user=self.remote_user, privacy='unlisted') - self.assertTrue( - views.helpers.object_visible_to_user(self.local_user, obj)) + name="test", user=self.remote_user, privacy="unlisted" + ) + self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='followers') - self.assertFalse( - views.helpers.object_visible_to_user(self.local_user, obj)) + content="hi", user=self.remote_user, privacy="followers" + ) + self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='direct') - self.assertFalse( - views.helpers.object_visible_to_user(self.local_user, obj)) + content="hi", user=self.remote_user, privacy="direct" + ) + self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='direct') + content="hi", user=self.remote_user, privacy="direct" + ) obj.mention_users.add(self.local_user) - self.assertTrue( - views.helpers.object_visible_to_user(self.local_user, obj)) + self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) def test_object_visible_to_user_follower(self): - ''' what you can see if you follow a user ''' + """ what you can see if you follow a user """ self.remote_user.followers.add(self.local_user) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='followers') - self.assertTrue( - views.helpers.object_visible_to_user(self.local_user, obj)) + content="hi", user=self.remote_user, privacy="followers" + ) + self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='direct') - self.assertFalse( - views.helpers.object_visible_to_user(self.local_user, obj)) + content="hi", user=self.remote_user, privacy="direct" + ) + self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='direct') + content="hi", user=self.remote_user, privacy="direct" + ) obj.mention_users.add(self.local_user) - self.assertTrue( - views.helpers.object_visible_to_user(self.local_user, obj)) + self.assertTrue(views.helpers.object_visible_to_user(self.local_user, obj)) def test_object_visible_to_user_blocked(self): - ''' you can't see it if they block you ''' + """ you can't see it if they block you """ self.remote_user.blocks.add(self.local_user) obj = models.Status.objects.create( - content='hi', user=self.remote_user, privacy='public') - self.assertFalse( - views.helpers.object_visible_to_user(self.local_user, obj)) + content="hi", user=self.remote_user, privacy="public" + ) + self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) obj = models.Shelf.objects.create( - name='test', user=self.remote_user, privacy='unlisted') - self.assertFalse( - views.helpers.object_visible_to_user(self.local_user, obj)) + name="test", user=self.remote_user, privacy="unlisted" + ) + self.assertFalse(views.helpers.object_visible_to_user(self.local_user, obj)) diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index ba8f2457b..b98b2516a 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase @@ -9,34 +9,37 @@ from bookwyrm import views class ImportViews(TestCase): - ''' goodreads import views ''' + """ goodreads import views """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() - def test_import_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Import.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_import_status(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.ImportStatus.as_view() import_job = models.ImportJob.objects.create(user=self.local_user) - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.tasks.app.AsyncResult') as async_result: + with patch("bookwyrm.tasks.app.AsyncResult") as async_result: async_result.return_value = [] result = view(request, import_job.id) self.assertIsInstance(result, TemplateResponse) diff --git a/bookwyrm/tests/views/test_inbox.py b/bookwyrm/tests/views/test_inbox.py index 4202979b3..cab508928 100644 --- a/bookwyrm/tests/views/test_inbox.py +++ b/bookwyrm/tests/views/test_inbox.py @@ -1,4 +1,4 @@ -''' tests incoming activities''' +""" tests incoming activities""" from datetime import datetime import json import pathlib @@ -11,101 +11,100 @@ import responses from bookwyrm import models, views -#pylint: disable=too-many-public-methods +# pylint: disable=too-many-public-methods class Inbox(TestCase): - ''' readthrough tests ''' + """ readthrough tests """ + def setUp(self): - ''' basic user and book data ''' + """ basic user and book data """ self.client = Client() self.local_user = models.User.objects.create_user( - 'mouse@example.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse') - self.local_user.remote_id = 'https://example.com/user/mouse' + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) + self.local_user.remote_id = "https://example.com/user/mouse" self.local_user.save(broadcast=False) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', + "rat", + "rat@rat.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.status = models.Status.objects.create( user=self.local_user, - content='Test status', - remote_id='https://example.com/status/1', + content="Test status", + remote_id="https://example.com/status/1", ) self.create_json = { - 'id': 'hi', - 'type': 'Create', - 'actor': 'hi', - "to": [ - "https://www.w3.org/ns/activitystreams#public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], - 'object': {} + "id": "hi", + "type": "Create", + "actor": "hi", + "to": ["https://www.w3.org/ns/activitystreams#public"], + "cc": ["https://example.com/user/mouse/followers"], + "object": {}, } models.SiteSettings.objects.create() - def test_inbox_invalid_get(self): - ''' shouldn't try to handle if the user is not found ''' - result = self.client.get( - '/inbox', content_type="application/json" - ) + """ shouldn't try to handle if the user is not found """ + result = self.client.get("/inbox", content_type="application/json") self.assertIsInstance(result, HttpResponseNotAllowed) def test_inbox_invalid_user(self): - ''' shouldn't try to handle if the user is not found ''' + """ shouldn't try to handle if the user is not found """ result = self.client.post( - '/user/bleh/inbox', + "/user/bleh/inbox", '{"type": "Test", "object": "exists"}', - content_type="application/json" + content_type="application/json", ) self.assertIsInstance(result, HttpResponseNotFound) def test_inbox_invalid_bad_signature(self): - ''' bad request for invalid signature ''' - with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + """ bad request for invalid signature """ + with patch("bookwyrm.views.inbox.has_valid_signature") as mock_valid: mock_valid.return_value = False result = self.client.post( - '/user/mouse/inbox', + "/user/mouse/inbox", '{"type": "Announce", "object": "exists"}', - content_type="application/json" + content_type="application/json", ) self.assertEqual(result.status_code, 401) def test_inbox_invalid_bad_signature_delete(self): - ''' invalid signature for Delete is okay though ''' - with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + """ invalid signature for Delete is okay though """ + with patch("bookwyrm.views.inbox.has_valid_signature") as mock_valid: mock_valid.return_value = False result = self.client.post( - '/user/mouse/inbox', + "/user/mouse/inbox", '{"type": "Delete", "object": "exists"}', - content_type="application/json" + content_type="application/json", ) self.assertEqual(result.status_code, 200) def test_inbox_unknown_type(self): - ''' never heard of that activity type, don't have a handler for it ''' - with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + """ never heard of that activity type, don't have a handler for it """ + with patch("bookwyrm.views.inbox.has_valid_signature") as mock_valid: result = self.client.post( - '/inbox', + "/inbox", '{"type": "Fish", "object": "exists"}', - content_type="application/json" + content_type="application/json", ) mock_valid.return_value = True self.assertIsInstance(result, HttpResponseNotFound) - def test_inbox_success(self): - ''' a known type, for which we start a task ''' + """ a known type, for which we start a task """ activity = self.create_json - activity['object'] = { + activity["object"] = { "id": "https://example.com/list/22", "type": "BookList", "totalItems": 1, @@ -113,47 +112,41 @@ class Inbox(TestCase): "last": "https://example.com/list/22?page=1", "name": "Test List", "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], "summary": "summary text", "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" + "@context": "https://www.w3.org/ns/activitystreams", } - with patch('bookwyrm.views.inbox.has_valid_signature') as mock_valid: + with patch("bookwyrm.views.inbox.has_valid_signature") as mock_valid: mock_valid.return_value = True - with patch('bookwyrm.views.inbox.activity_task.delay'): + with patch("bookwyrm.views.inbox.activity_task.delay"): result = self.client.post( - '/inbox', - json.dumps(activity), - content_type="application/json" + "/inbox", json.dumps(activity), content_type="application/json" ) self.assertEqual(result.status_code, 200) - def test_handle_create_status(self): - ''' the "it justs works" mode ''' + """ the "it justs works" mode """ self.assertEqual(models.Status.objects.count(), 1) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_quotation.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_quotation.json") status_data = json.loads(datafile.read_bytes()) models.Edition.objects.create( - title='Test Book', remote_id='https://example.com/book/1') + title="Test Book", remote_id="https://example.com/book/1" + ) activity = self.create_json - activity['object'] = status_data + activity["object"] = status_data views.inbox.activity_task(activity) status = models.Quotation.objects.get() self.assertEqual( - status.remote_id, 'https://example.com/user/mouse/quotation/13') - self.assertEqual(status.quote, 'quote body') - self.assertEqual(status.content, 'commentary') + status.remote_id, "https://example.com/user/mouse/quotation/13" + ) + self.assertEqual(status.quote, "quote body") + self.assertEqual(status.content, "commentary") self.assertEqual(status.user, self.local_user) self.assertEqual(models.Status.objects.count(), 2) @@ -161,56 +154,50 @@ class Inbox(TestCase): views.inbox.activity_task(activity) self.assertEqual(models.Status.objects.count(), 2) - def test_handle_create_status_remote_note_with_mention(self): - ''' should only create it under the right circumstances ''' + """ should only create it under the right circumstances """ self.assertEqual(models.Status.objects.count(), 1) self.assertFalse( - models.Notification.objects.filter(user=self.local_user).exists()) + models.Notification.objects.filter(user=self.local_user).exists() + ) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_note.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_note.json") status_data = json.loads(datafile.read_bytes()) activity = self.create_json - activity['object'] = status_data + activity["object"] = status_data views.inbox.activity_task(activity) status = models.Status.objects.last() - self.assertEqual(status.content, 'test content in note') + self.assertEqual(status.content, "test content in note") self.assertEqual(status.mention_users.first(), self.local_user) self.assertTrue( - models.Notification.objects.filter(user=self.local_user).exists()) - self.assertEqual( - models.Notification.objects.get().notification_type, 'MENTION') + models.Notification.objects.filter(user=self.local_user).exists() + ) + self.assertEqual(models.Notification.objects.get().notification_type, "MENTION") def test_handle_create_status_remote_note_with_reply(self): - ''' should only create it under the right circumstances ''' + """ should only create it under the right circumstances """ self.assertEqual(models.Status.objects.count(), 1) - self.assertFalse( - models.Notification.objects.filter(user=self.local_user)) + self.assertFalse(models.Notification.objects.filter(user=self.local_user)) - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_note.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_note.json") status_data = json.loads(datafile.read_bytes()) - del status_data['tag'] - status_data['inReplyTo'] = self.status.remote_id + del status_data["tag"] + status_data["inReplyTo"] = self.status.remote_id activity = self.create_json - activity['object'] = status_data + activity["object"] = status_data views.inbox.activity_task(activity) status = models.Status.objects.last() - self.assertEqual(status.content, 'test content in note') + self.assertEqual(status.content, "test content in note") self.assertEqual(status.reply_parent, self.status) - self.assertTrue( - models.Notification.objects.filter(user=self.local_user)) - self.assertEqual( - models.Notification.objects.get().notification_type, 'REPLY') - + self.assertTrue(models.Notification.objects.filter(user=self.local_user)) + self.assertEqual(models.Notification.objects.get().notification_type, "REPLY") def test_handle_create_list(self): - ''' a new list ''' + """ a new list """ activity = self.create_json - activity['object'] = { + activity["object"] = { "id": "https://example.com/list/22", "type": "BookList", "totalItems": 1, @@ -218,44 +205,38 @@ class Inbox(TestCase): "last": "https://example.com/list/22?page=1", "name": "Test List", "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], "summary": "summary text", "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" + "@context": "https://www.w3.org/ns/activitystreams", } views.inbox.activity_task(activity) book_list = models.List.objects.get() - self.assertEqual(book_list.name, 'Test List') - self.assertEqual(book_list.curation, 'curated') - self.assertEqual(book_list.description, 'summary text') - self.assertEqual(book_list.remote_id, 'https://example.com/list/22') - + self.assertEqual(book_list.name, "Test List") + self.assertEqual(book_list.curation, "curated") + self.assertEqual(book_list.description, "summary text") + self.assertEqual(book_list.remote_id, "https://example.com/list/22") def test_handle_follow_x(self): - ''' remote user wants to follow local user ''' + """ remote user wants to follow local user """ activity = { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://example.com/users/rat/follows/123", "type": "Follow", "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" + "object": "https://example.com/user/mouse", } self.assertFalse(models.UserFollowRequest.objects.exists()) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ - as mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: views.inbox.activity_task(activity) self.assertEqual(mock.call_count, 1) # notification created notification = models.Notification.objects.get() self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.notification_type, 'FOLLOW') + self.assertEqual(notification.notification_type, "FOLLOW") # the request should have been deleted self.assertFalse(models.UserFollowRequest.objects.exists()) @@ -264,27 +245,26 @@ class Inbox(TestCase): follow = models.UserFollows.objects.get(user_object=self.local_user) self.assertEqual(follow.user_subject, self.remote_user) - def test_handle_follow_manually_approved(self): - ''' needs approval before following ''' + """ needs approval before following """ activity = { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://example.com/users/rat/follows/123", "type": "Follow", "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" + "object": "https://example.com/user/mouse", } self.local_user.manually_approves_followers = True self.local_user.save(broadcast=False) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.inbox.activity_task(activity) # notification created notification = models.Notification.objects.get() self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.notification_type, 'FOLLOW_REQUEST') + self.assertEqual(notification.notification_type, "FOLLOW_REQUEST") # the request should exist request = models.UserFollowRequest.objects.get() @@ -295,38 +275,36 @@ class Inbox(TestCase): follow = models.UserFollows.objects.all() self.assertEqual(list(follow), []) - def test_handle_unfollow(self): - ''' remove a relationship ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ remove a relationship """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): rel = models.UserFollows.objects.create( - user_subject=self.remote_user, user_object=self.local_user) + user_subject=self.remote_user, user_object=self.local_user + ) activity = { "type": "Undo", "id": "bleh", "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": ["https://example.com/user/mouse/followers"], - 'actor': self.remote_user.remote_id, + "actor": self.remote_user.remote_id, "@context": "https://www.w3.org/ns/activitystreams", "object": { "id": rel.remote_id, "type": "Follow", "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - } + "object": "https://example.com/user/mouse", + }, } self.assertEqual(self.remote_user, self.local_user.followers.first()) views.inbox.activity_task(activity) self.assertIsNone(self.local_user.followers.first()) - def test_handle_follow_accept(self): - ''' a remote user approved a follow request from local ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ a remote user approved a follow request from local """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): rel = models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user + user_subject=self.local_user, user_object=self.remote_user ) activity = { "@context": "https://www.w3.org/ns/activitystreams", @@ -337,8 +315,8 @@ class Inbox(TestCase): "id": rel.remote_id, "type": "Follow", "actor": "https://example.com/user/mouse", - "object": "https://example.com/users/rat" - } + "object": "https://example.com/users/rat", + }, } self.assertEqual(models.UserFollowRequest.objects.count(), 1) @@ -353,13 +331,11 @@ class Inbox(TestCase): self.assertEqual(follows.count(), 1) self.assertEqual(follows.first(), self.local_user) - def test_handle_follow_reject(self): - ''' turn down a follow request ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ turn down a follow request """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): rel = models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user + user_subject=self.local_user, user_object=self.remote_user ) activity = { "@context": "https://www.w3.org/ns/activitystreams", @@ -370,8 +346,8 @@ class Inbox(TestCase): "id": rel.remote_id, "type": "Follow", "actor": "https://example.com/user/mouse", - "object": "https://example.com/users/rat" - } + "object": "https://example.com/users/rat", + }, } self.assertEqual(models.UserFollowRequest.objects.count(), 1) @@ -382,18 +358,19 @@ class Inbox(TestCase): self.assertFalse(models.UserFollowRequest.objects.exists()) self.assertFalse(self.remote_user.followers.exists()) - def test_handle_update_list(self): - ''' a new list ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ a new list """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): book_list = models.List.objects.create( - name='hi', remote_id='https://example.com/list/22', - user=self.local_user) + name="hi", remote_id="https://example.com/list/22", user=self.local_user + ) activity = { - 'type': 'Update', - 'to': [], 'cc': [], 'actor': 'hi', - 'id': 'sdkjf', - 'object': { + "type": "Update", + "to": [], + "cc": [], + "actor": "hi", + "id": "sdkjf", + "object": { "id": "https://example.com/list/22", "type": "BookList", "totalItems": 1, @@ -401,38 +378,33 @@ class Inbox(TestCase): "last": "https://example.com/list/22?page=1", "name": "Test List", "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], "summary": "summary text", "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" - } + "@context": "https://www.w3.org/ns/activitystreams", + }, } views.inbox.activity_task(activity) book_list.refresh_from_db() - self.assertEqual(book_list.name, 'Test List') - self.assertEqual(book_list.curation, 'curated') - self.assertEqual(book_list.description, 'summary text') - self.assertEqual(book_list.remote_id, 'https://example.com/list/22') - + self.assertEqual(book_list.name, "Test List") + self.assertEqual(book_list.curation, "curated") + self.assertEqual(book_list.description, "summary text") + self.assertEqual(book_list.remote_id, "https://example.com/list/22") def test_handle_delete_status(self): - ''' remove a status ''' + """ remove a status """ self.status.user = self.remote_user self.status.save(broadcast=False) self.assertFalse(self.status.deleted) activity = { - 'type': 'Delete', + "type": "Delete", "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": ["https://example.com/user/mouse/followers"], - 'id': '%s/activity' % self.status.remote_id, - 'actor': self.remote_user.remote_id, - 'object': {'id': self.status.remote_id, 'type': 'Tombstone'}, + "id": "%s/activity" % self.status.remote_id, + "actor": self.remote_user.remote_id, + "object": {"id": self.status.remote_id, "type": "Tombstone"}, } views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone @@ -440,30 +412,28 @@ class Inbox(TestCase): self.assertTrue(status.deleted) self.assertIsInstance(status.deleted_date, datetime) - def test_handle_delete_status_notifications(self): - ''' remove a status with related notifications ''' + """ remove a status with related notifications """ self.status.user = self.remote_user self.status.save(broadcast=False) models.Notification.objects.create( related_status=self.status, user=self.local_user, - notification_type='MENTION' + notification_type="MENTION", ) # this one is innocent, don't delete it notif = models.Notification.objects.create( - user=self.local_user, - notification_type='MENTION' + user=self.local_user, notification_type="MENTION" ) self.assertFalse(self.status.deleted) self.assertEqual(models.Notification.objects.count(), 2) activity = { - 'type': 'Delete', + "type": "Delete", "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": ["https://example.com/user/mouse/followers"], - 'id': '%s/activity' % self.status.remote_id, - 'actor': self.remote_user.remote_id, - 'object': {'id': self.status.remote_id, 'type': 'Tombstone'}, + "id": "%s/activity" % self.status.remote_id, + "actor": self.remote_user.remote_id, + "object": {"id": self.status.remote_id, "type": "Tombstone"}, } views.inbox.activity_task(activity) # deletion doens't remove the status, it turns it into a tombstone @@ -475,34 +445,33 @@ class Inbox(TestCase): self.assertEqual(models.Notification.objects.count(), 1) self.assertEqual(models.Notification.objects.get(), notif) - def test_handle_favorite(self): - ''' fav a status ''' + """ fav a status """ activity = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': 'https://example.com/fav/1', - 'actor': 'https://example.com/users/rat', - 'type': 'Like', - 'published': 'Mon, 25 May 2020 19:31:20 GMT', - 'object': self.status.remote_id, + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/fav/1", + "actor": "https://example.com/users/rat", + "type": "Like", + "published": "Mon, 25 May 2020 19:31:20 GMT", + "object": self.status.remote_id, } views.inbox.activity_task(activity) - fav = models.Favorite.objects.get(remote_id='https://example.com/fav/1') + fav = models.Favorite.objects.get(remote_id="https://example.com/fav/1") self.assertEqual(fav.status, self.status) - self.assertEqual(fav.remote_id, 'https://example.com/fav/1') + self.assertEqual(fav.remote_id, "https://example.com/fav/1") self.assertEqual(fav.user, self.remote_user) def test_ignore_favorite(self): - ''' don't try to save an unknown status ''' + """ don't try to save an unknown status """ activity = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': 'https://example.com/fav/1', - 'actor': 'https://example.com/users/rat', - 'type': 'Like', - 'published': 'Mon, 25 May 2020 19:31:20 GMT', - 'object': 'https://unknown.status/not-found', + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/fav/1", + "actor": "https://example.com/users/rat", + "type": "Like", + "published": "Mon, 25 May 2020 19:31:20 GMT", + "object": "https://unknown.status/not-found", } views.inbox.activity_task(activity) @@ -510,43 +479,42 @@ class Inbox(TestCase): self.assertFalse(models.Favorite.objects.exists()) def test_handle_unfavorite(self): - ''' fav a status ''' + """ fav a status """ activity = { - 'id': 'https://example.com/fav/1#undo', - 'type': 'Undo', + "id": "https://example.com/fav/1#undo", + "type": "Undo", "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": ["https://example.com/user/mouse/followers"], - 'actor': self.remote_user.remote_id, - 'object': { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': 'https://example.com/fav/1', - 'actor': 'https://example.com/users/rat', - 'type': 'Like', - 'published': 'Mon, 25 May 2020 19:31:20 GMT', - 'object': self.status.remote_id, - } + "actor": self.remote_user.remote_id, + "object": { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.com/fav/1", + "actor": "https://example.com/users/rat", + "type": "Like", + "published": "Mon, 25 May 2020 19:31:20 GMT", + "object": self.status.remote_id, + }, } models.Favorite.objects.create( status=self.status, user=self.remote_user, - remote_id='https://example.com/fav/1') + remote_id="https://example.com/fav/1", + ) self.assertEqual(models.Favorite.objects.count(), 1) views.inbox.activity_task(activity) self.assertEqual(models.Favorite.objects.count(), 0) - def test_handle_boost(self): - ''' boost a status ''' + """ boost a status """ self.assertEqual(models.Notification.objects.count(), 0) activity = { - 'type': 'Announce', - 'id': '%s/boost' % self.status.remote_id, - 'actor': self.remote_user.remote_id, - 'object': self.status.remote_id, + "type": "Announce", + "id": "%s/boost" % self.status.remote_id, + "actor": self.remote_user.remote_id, + "object": self.status.remote_id, } - with patch('bookwyrm.models.status.Status.ignore_activity') \ - as discarder: + with patch("bookwyrm.models.status.Status.ignore_activity") as discarder: discarder.return_value = False views.inbox.activity_task(activity) boost = models.Boost.objects.get() @@ -555,59 +523,56 @@ class Inbox(TestCase): self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_status, self.status) - @responses.activate def test_handle_discarded_boost(self): - ''' test a boost of a mastodon status that will be discarded ''' + """ test a boost of a mastodon status that will be discarded """ status = models.Status( - content='hi', + content="hi", user=self.remote_user, ) status.save(broadcast=False) activity = { - 'type': 'Announce', - 'id': 'http://www.faraway.com/boost/12', - 'actor': self.remote_user.remote_id, - 'object': status.remote_id, + "type": "Announce", + "id": "http://www.faraway.com/boost/12", + "actor": self.remote_user.remote_id, + "object": status.remote_id, } responses.add( - responses.GET, - status.remote_id, - json=status.to_activity(), - status=200) + responses.GET, status.remote_id, json=status.to_activity(), status=200 + ) views.inbox.activity_task(activity) self.assertEqual(models.Boost.objects.count(), 0) - def test_handle_unboost(self): - ''' undo a boost ''' + """ undo a boost """ boost = models.Boost.objects.create( - boosted_status=self.status, user=self.remote_user) + boosted_status=self.status, user=self.remote_user + ) activity = { - 'type': 'Undo', - 'actor': 'hi', - 'id': 'bleh', + "type": "Undo", + "actor": "hi", + "id": "bleh", "to": ["https://www.w3.org/ns/activitystreams#public"], "cc": ["https://example.com/user/mouse/followers"], - 'object': { - 'type': 'Announce', - 'id': boost.remote_id, - 'actor': self.remote_user.remote_id, - 'object': self.status.remote_id, - } + "object": { + "type": "Announce", + "id": boost.remote_id, + "actor": self.remote_user.remote_id, + "object": self.status.remote_id, + }, } views.inbox.activity_task(activity) - def test_handle_add_book_to_shelf(self): - ''' shelving a book ''' - work = models.Work.objects.create(title='work title') + """ shelving a book """ + work = models.Work.objects.create(title="work title") book = models.Edition.objects.create( - title='Test', remote_id='https://bookwyrm.social/book/37292', - parent_work=work) - shelf = models.Shelf.objects.create( - user=self.remote_user, name='Test Shelf') - shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read' + title="Test", + remote_id="https://bookwyrm.social/book/37292", + parent_work=work, + ) + shelf = models.Shelf.objects.create(user=self.remote_user, name="Test Shelf") + shelf.remote_id = "https://bookwyrm.social/user/mouse/shelf/to-read" shelf.save() activity = { @@ -621,23 +586,24 @@ class Inbox(TestCase): "id": "https://bookwyrm.social/book/37292", }, "target": "https://bookwyrm.social/user/mouse/shelf/to-read", - "@context": "https://www.w3.org/ns/activitystreams" + "@context": "https://www.w3.org/ns/activitystreams", } views.inbox.activity_task(activity) self.assertEqual(shelf.books.first(), book) - @responses.activate def test_handle_add_book_to_list(self): - ''' listing a book ''' - work = models.Work.objects.create(title='work title') + """ listing a book """ + work = models.Work.objects.create(title="work title") book = models.Edition.objects.create( - title='Test', remote_id='https://bookwyrm.social/book/37292', - parent_work=work) + title="Test", + remote_id="https://bookwyrm.social/book/37292", + parent_work=work, + ) responses.add( responses.GET, - 'https://bookwyrm.social/user/mouse/list/to-read', + "https://bookwyrm.social/user/mouse/list/to-read", json={ "id": "https://example.com/list/22", "type": "BookList", @@ -646,16 +612,12 @@ class Inbox(TestCase): "last": "https://example.com/list/22?page=1", "name": "Test List", "owner": "https://example.com/user/mouse", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.com/user/mouse/followers" - ], + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], "summary": "summary text", "curation": "curated", - "@context": "https://www.w3.org/ns/activitystreams" - } + "@context": "https://www.w3.org/ns/activitystreams", + }, ) activity = { @@ -669,26 +631,27 @@ class Inbox(TestCase): "id": "https://bookwyrm.social/book/37292", }, "target": "https://bookwyrm.social/user/mouse/list/to-read", - "@context": "https://www.w3.org/ns/activitystreams" + "@context": "https://www.w3.org/ns/activitystreams", } views.inbox.activity_task(activity) booklist = models.List.objects.get() - self.assertEqual(booklist.name, 'Test List') + self.assertEqual(booklist.name, "Test List") self.assertEqual(booklist.books.first(), book) - @responses.activate def test_handle_tag_book(self): - ''' listing a book ''' - work = models.Work.objects.create(title='work title') + """ listing a book """ + work = models.Work.objects.create(title="work title") book = models.Edition.objects.create( - title='Test', remote_id='https://bookwyrm.social/book/37292', - parent_work=work) + title="Test", + remote_id="https://bookwyrm.social/book/37292", + parent_work=work, + ) responses.add( responses.GET, - 'https://www.example.com/tag/cool-tag', + "https://www.example.com/tag/cool-tag", json={ "id": "https://1b1a78582461.ngrok.io/tag/tag", "type": "OrderedCollection", @@ -696,8 +659,8 @@ class Inbox(TestCase): "first": "https://1b1a78582461.ngrok.io/tag/tag?page=1", "last": "https://1b1a78582461.ngrok.io/tag/tag?page=1", "name": "cool tag", - "@context": "https://www.w3.org/ns/activitystreams" - } + "@context": "https://www.w3.org/ns/activitystreams", + }, ) activity = { @@ -711,95 +674,101 @@ class Inbox(TestCase): "id": "https://bookwyrm.social/book/37292", }, "target": "https://www.example.com/tag/cool-tag", - "@context": "https://www.w3.org/ns/activitystreams" + "@context": "https://www.w3.org/ns/activitystreams", } views.inbox.activity_task(activity) tag = models.Tag.objects.get() self.assertFalse(models.List.objects.exists()) - self.assertEqual(tag.name, 'cool tag') + self.assertEqual(tag.name, "cool tag") self.assertEqual(tag.books.first(), book) - def test_handle_update_user(self): - ''' update an existing user ''' + """ update an existing user """ # we only do this with remote users self.local_user.local = False self.local_user.save() - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json') + datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") userdata = json.loads(datafile.read_bytes()) - del userdata['icon'] + del userdata["icon"] self.assertIsNone(self.local_user.name) - views.inbox.activity_task({ - 'type': 'Update', - 'to': [], 'cc': [], 'actor': 'hi', - 'id': 'sdkjf', - 'object': userdata - }) + views.inbox.activity_task( + { + "type": "Update", + "to": [], + "cc": [], + "actor": "hi", + "id": "sdkjf", + "object": userdata, + } + ) user = models.User.objects.get(id=self.local_user.id) - self.assertEqual(user.name, 'MOUSE?? MOUSE!!') - self.assertEqual(user.username, 'mouse@example.com') - self.assertEqual(user.localname, 'mouse') - + self.assertEqual(user.name, "MOUSE?? MOUSE!!") + self.assertEqual(user.username, "mouse@example.com") + self.assertEqual(user.localname, "mouse") def test_handle_update_edition(self): - ''' update an existing edition ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/bw_edition.json') + """ update an existing edition """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_edition.json") bookdata = json.loads(datafile.read_bytes()) models.Work.objects.create( - title='Test Work', remote_id='https://bookwyrm.social/book/5988') + title="Test Work", remote_id="https://bookwyrm.social/book/5988" + ) book = models.Edition.objects.create( - title='Test Book', remote_id='https://bookwyrm.social/book/5989') + title="Test Book", remote_id="https://bookwyrm.social/book/5989" + ) - del bookdata['authors'] - self.assertEqual(book.title, 'Test Book') + del bookdata["authors"] + self.assertEqual(book.title, "Test Book") - with patch( - 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - views.inbox.activity_task({ - 'type': 'Update', - 'to': [], 'cc': [], 'actor': 'hi', - 'id': 'sdkjf', - 'object': bookdata - }) + with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"): + views.inbox.activity_task( + { + "type": "Update", + "to": [], + "cc": [], + "actor": "hi", + "id": "sdkjf", + "object": bookdata, + } + ) book = models.Edition.objects.get(id=book.id) - self.assertEqual(book.title, 'Piranesi') - + self.assertEqual(book.title, "Piranesi") def test_handle_update_work(self): - ''' update an existing edition ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/bw_work.json') + """ update an existing edition """ + datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_work.json") bookdata = json.loads(datafile.read_bytes()) book = models.Work.objects.create( - title='Test Book', remote_id='https://bookwyrm.social/book/5988') + title="Test Book", remote_id="https://bookwyrm.social/book/5988" + ) - del bookdata['authors'] - self.assertEqual(book.title, 'Test Book') - with patch( - 'bookwyrm.activitypub.base_activity.set_related_field.delay'): - views.inbox.activity_task({ - 'type': 'Update', - 'to': [], 'cc': [], 'actor': 'hi', - 'id': 'sdkjf', - 'object': bookdata - }) + del bookdata["authors"] + self.assertEqual(book.title, "Test Book") + with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"): + views.inbox.activity_task( + { + "type": "Update", + "to": [], + "cc": [], + "actor": "hi", + "id": "sdkjf", + "object": bookdata, + } + ) book = models.Work.objects.get(id=book.id) - self.assertEqual(book.title, 'Piranesi') - + self.assertEqual(book.title, "Piranesi") def test_handle_blocks(self): - ''' create a "block" database entry from an activity ''' + """ create a "block" database entry from an activity """ self.local_user.followers.add(self.remote_user) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user) + user_subject=self.local_user, user_object=self.remote_user + ) self.assertTrue(models.UserFollows.objects.exists()) self.assertTrue(models.UserFollowRequest.objects.exists()) @@ -808,42 +777,41 @@ class Inbox(TestCase): "id": "https://example.com/9e1f41ac-9ddd-4159", "type": "Block", "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" + "object": "https://example.com/user/mouse", } views.inbox.activity_task(activity) block = models.UserBlocks.objects.get() self.assertEqual(block.user_subject, self.remote_user) self.assertEqual(block.user_object, self.local_user) - self.assertEqual( - block.remote_id, 'https://example.com/9e1f41ac-9ddd-4159') + self.assertEqual(block.remote_id, "https://example.com/9e1f41ac-9ddd-4159") self.assertFalse(models.UserFollows.objects.exists()) self.assertFalse(models.UserFollowRequest.objects.exists()) - def test_handle_unblock(self): - ''' unblock a user ''' + """ unblock a user """ self.remote_user.blocks.add(self.local_user) block = models.UserBlocks.objects.get() - block.remote_id = 'https://example.com/9e1f41ac-9ddd-4159' + block.remote_id = "https://example.com/9e1f41ac-9ddd-4159" block.save() self.assertEqual(block.user_subject, self.remote_user) self.assertEqual(block.user_object, self.local_user) activity = { - 'type': 'Undo', - 'actor': 'hi', - 'id': 'bleh', + "type": "Undo", + "actor": "hi", + "id": "bleh", "to": ["https://www.w3.org/ns/activitystreams#public"], "cc": ["https://example.com/user/mouse/followers"], - 'object': { + "object": { "@context": "https://www.w3.org/ns/activitystreams", "id": "https://example.com/9e1f41ac-9ddd-4159", "type": "Block", "actor": "https://example.com/users/rat", - "object": "https://example.com/user/mouse" - }} + "object": "https://example.com/user/mouse", + }, + } views.inbox.activity_task(activity) self.assertFalse(models.UserBlocks.objects.exists()) diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index c6d39f29a..857f7061f 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.test import TestCase from django.test.client import RequestFactory @@ -7,40 +7,44 @@ from bookwyrm import models, views class InteractionViews(TestCase): - ''' viewing and creating statuses ''' + """ viewing and creating statuses """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - with patch('bookwyrm.models.user.set_remote_server'): + with patch("bookwyrm.models.user.set_remote_server"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@email.com', 'ratword', + "rat", + "rat@email.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) - work = models.Work.objects.create(title='Test Work') + work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, ) - def test_handle_favorite(self): - ''' create and broadcast faving a status ''' + """ create and broadcast faving a status """ view = views.Favorite.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.remote_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - user=self.local_user, content='hi') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) fav = models.Favorite.objects.get() @@ -48,106 +52,100 @@ class InteractionViews(TestCase): self.assertEqual(fav.user, self.remote_user) notification = models.Notification.objects.get() - self.assertEqual(notification.notification_type, 'FAVORITE') + self.assertEqual(notification.notification_type, "FAVORITE") self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_user, self.remote_user) - def test_handle_unfavorite(self): - ''' unfav a status ''' + """ unfav a status """ view = views.Unfavorite.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.remote_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - user=self.local_user, content='hi') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(user=self.local_user, content="hi") views.Favorite.as_view()(request, status.id) self.assertEqual(models.Favorite.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, status.id) self.assertEqual(models.Favorite.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0) - def test_handle_boost(self): - ''' boost a status ''' + """ boost a status """ view = views.Boost.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.remote_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - user=self.local_user, content='hi') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) boost = models.Boost.objects.get() self.assertEqual(boost.boosted_status, status) self.assertEqual(boost.user, self.remote_user) - self.assertEqual(boost.privacy, 'public') + self.assertEqual(boost.privacy, "public") notification = models.Notification.objects.get() - self.assertEqual(notification.notification_type, 'BOOST') + self.assertEqual(notification.notification_type, "BOOST") self.assertEqual(notification.user, self.local_user) self.assertEqual(notification.related_user, self.remote_user) self.assertEqual(notification.related_status, status) def test_handle_boost_unlisted(self): - ''' boost a status ''' + """ boost a status """ view = views.Boost.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( - user=self.local_user, content='hi', privacy='unlisted') + user=self.local_user, content="hi", privacy="unlisted" + ) view(request, status.id) boost = models.Boost.objects.get() - self.assertEqual(boost.privacy, 'unlisted') + self.assertEqual(boost.privacy, "unlisted") def test_handle_boost_private(self): - ''' boost a status ''' + """ boost a status """ view = views.Boost.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( - user=self.local_user, content='hi', privacy='followers') + user=self.local_user, content="hi", privacy="followers" + ) view(request, status.id) self.assertFalse(models.Boost.objects.exists()) def test_handle_boost_twice(self): - ''' boost a status ''' + """ boost a status """ view = views.Boost.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - user=self.local_user, content='hi') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(user=self.local_user, content="hi") view(request, status.id) view(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) - def test_handle_unboost(self): - ''' undo a boost ''' + """ undo a boost """ view = views.Unboost.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - user=self.local_user, content='hi') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(user=self.local_user, content="hi") views.Boost.as_view()(request, status.id) self.assertEqual(models.Boost.objects.count(), 1) self.assertEqual(models.Notification.objects.count(), 1) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ - as mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) self.assertEqual(mock.call_count, 1) self.assertEqual(models.Boost.objects.count(), 0) diff --git a/bookwyrm/tests/views/test_invite.py b/bookwyrm/tests/views/test_invite.py index e93e7209b..cd2276c0d 100644 --- a/bookwyrm/tests/views/test_invite.py +++ b/bookwyrm/tests/views/test_invite.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import AnonymousUser @@ -11,36 +11,39 @@ from bookwyrm import views class InviteViews(TestCase): - ''' every response to a get request, html or json ''' + """ every response to a get request, html or json """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() - def test_invite_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Invite.as_view() - models.SiteInvite.objects.create(code='hi', user=self.local_user) - request = self.factory.get('') + models.SiteInvite.objects.create(code="hi", user=self.local_user) + request = self.factory.get("") request.user = AnonymousUser # why?? this is annoying. request.user.is_authenticated = False - with patch('bookwyrm.models.site.SiteInvite.valid') as invite: + with patch("bookwyrm.models.site.SiteInvite.valid") as invite: invite.return_value = True - result = view(request, 'hi') + result = view(request, "hi") self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_manage_invites(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.ManageInvites.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user request.user.is_superuser = True result = view(request) diff --git a/bookwyrm/tests/views/test_isbn.py b/bookwyrm/tests/views/test_isbn.py index 1966702b4..c7ae1f39f 100644 --- a/bookwyrm/tests/views/test_isbn.py +++ b/bookwyrm/tests/views/test_isbn.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ import json from unittest.mock import patch @@ -13,42 +13,41 @@ from bookwyrm.settings import DOMAIN class IsbnViews(TestCase): - ''' tag views''' + """ tag views""" + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Test Book', - isbn_13='1234567890123', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Test Book", + isbn_13="1234567890123", + remote_id="https://example.com/book/1", + parent_work=self.work, ) models.Connector.objects.create( - identifier='self', - connector_file='self_connector', - local=True + identifier="self", connector_file="self_connector", local=True ) models.SiteSettings.objects.create() - def test_isbn_json_response(self): - ''' searches local data only and returns book data in json format ''' + """ searches local data only and returns book data in json format """ view = views.Isbn.as_view() - request = self.factory.get('') - with patch('bookwyrm.views.isbn.is_api_request') as is_api: + request = self.factory.get("") + with patch("bookwyrm.views.isbn.is_api_request") as is_api: is_api.return_value = True - response = view(request, isbn='1234567890123') + response = view(request, isbn="1234567890123") self.assertIsInstance(response, JsonResponse) data = json.loads(response.content) self.assertEqual(len(data), 1) - self.assertEqual(data[0]['title'], 'Test Book') - self.assertEqual( - data[0]['key'], 'https://%s/book/%d' % (DOMAIN, self.book.id)) - + self.assertEqual(data[0]["title"], "Test Book") + self.assertEqual(data[0]["key"], "https://%s/book/%d" % (DOMAIN, self.book.id)) diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 5e0e50cff..910e4a851 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from django.contrib.auth.models import AnonymousUser from django.template.response import TemplateResponse from django.test import TestCase @@ -9,22 +9,26 @@ from bookwyrm import views class LandingViews(TestCase): - ''' pages you land on without really trying ''' + """ pages you land on without really trying """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create() - def test_home_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Home.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertEqual(result.status_code, 200) @@ -36,21 +40,19 @@ class LandingViews(TestCase): self.assertEqual(result.status_code, 200) result.render() - def test_about_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.About.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_discover(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Discover.as_view() - request = self.factory.get('') + request = self.factory.get("") result = view(request) self.assertIsInstance(result, TemplateResponse) diff --git a/bookwyrm/tests/views/test_list.py b/bookwyrm/tests/views/test_list.py index e41d9806c..cc895ad17 100644 --- a/bookwyrm/tests/views/test_list.py +++ b/bookwyrm/tests/views/test_list.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import AnonymousUser @@ -9,44 +9,52 @@ from django.test.client import RequestFactory from bookwyrm import models, views from bookwyrm.activitypub import ActivitypubResponse -#pylint: disable=unused-argument +# pylint: disable=unused-argument class ListViews(TestCase): - ''' tag views''' + """ tag views""" + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) self.rat = models.User.objects.create_user( - 'rat@local.com', 'rat@rat.com', 'ratword', - local=True, localname='rat', - remote_id='https://example.com/users/rat', + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", ) - work = models.Work.objects.create(title='Work') + work = models.Work.objects.create(title="Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', + title="Example Edition", + remote_id="https://example.com/book/1", parent_work=work, ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.list = models.List.objects.create( - name='Test List', user=self.local_user) + name="Test List", user=self.local_user + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create() - def test_lists_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Lists.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.List.objects.create(name='Public list', user=self.local_user) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.List.objects.create(name="Public list", user=self.local_user) models.List.objects.create( - name='Private list', privacy='direct', user=self.local_user) - request = self.factory.get('') + name="Private list", privacy="direct", user=self.local_user + ) + request = self.factory.get("") request.user = self.local_user result = view(request) @@ -61,42 +69,45 @@ class ListViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_lists_create(self): - ''' create list view ''' + """ create list view """ real_broadcast = models.List.broadcast + def mock_broadcast(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Create') - self.assertEqual(activity['actor'], self.local_user.remote_id) + self.assertEqual(activity["type"], "Create") + self.assertEqual(activity["actor"], self.local_user.remote_id) + models.List.broadcast = mock_broadcast view = views.Lists.as_view() - request = self.factory.post('', { - 'name': 'A list', - 'description': 'wow', - 'privacy': 'unlisted', - 'curation': 'open', - 'user': self.local_user.id, - }) + request = self.factory.post( + "", + { + "name": "A list", + "description": "wow", + "privacy": "unlisted", + "curation": "open", + "user": self.local_user.id, + }, + ) request.user = self.local_user result = view(request) self.assertEqual(result.status_code, 302) - new_list = models.List.objects.filter(name='A list').get() - self.assertEqual(new_list.description, 'wow') - self.assertEqual(new_list.privacy, 'unlisted') - self.assertEqual(new_list.curation, 'open') + new_list = models.List.objects.filter(name="A list").get() + self.assertEqual(new_list.description, "wow") + self.assertEqual(new_list.privacy, "unlisted") + self.assertEqual(new_list.curation, "open") models.List.broadcast = real_broadcast - def test_list_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.List.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.list.is_api_request') as is_api: + with patch("bookwyrm.views.list.is_api_request") as is_api: is_api.return_value = False result = view(request, self.list.id) self.assertIsInstance(result, TemplateResponse) @@ -104,68 +115,72 @@ class ListViews(TestCase): self.assertEqual(result.status_code, 200) request.user = self.anonymous_user - with patch('bookwyrm.views.list.is_api_request') as is_api: + with patch("bookwyrm.views.list.is_api_request") as is_api: is_api.return_value = False result = view(request, self.list.id) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.list.is_api_request') as is_api: + with patch("bookwyrm.views.list.is_api_request") as is_api: is_api.return_value = True result = view(request, self.list.id) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - request = self.factory.get('/?page=1') + request = self.factory.get("/?page=1") request.user = self.local_user - with patch('bookwyrm.views.list.is_api_request') as is_api: + with patch("bookwyrm.views.list.is_api_request") as is_api: is_api.return_value = True result = view(request, self.list.id) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_list_edit(self): - ''' edit a list ''' + """ edit a list """ real_broadcast = models.List.broadcast + def mock_broadcast(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Update') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['object']['id'], self.list.remote_id) + self.assertEqual(activity["type"], "Update") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], self.list.remote_id) + models.List.broadcast = mock_broadcast view = views.List.as_view() - request = self.factory.post('', { - 'name': 'New Name', - 'description': 'wow', - 'privacy': 'direct', - 'curation': 'curated', - 'user': self.local_user.id, - }) + request = self.factory.post( + "", + { + "name": "New Name", + "description": "wow", + "privacy": "direct", + "curation": "curated", + "user": self.local_user.id, + }, + ) request.user = self.local_user result = view(request, self.list.id) self.assertEqual(result.status_code, 302) self.list.refresh_from_db() - self.assertEqual(self.list.name, 'New Name') - self.assertEqual(self.list.description, 'wow') - self.assertEqual(self.list.privacy, 'direct') - self.assertEqual(self.list.curation, 'curated') + self.assertEqual(self.list.name, "New Name") + self.assertEqual(self.list.description, "wow") + self.assertEqual(self.list.privacy, "direct") + self.assertEqual(self.list.curation, "curated") models.List.broadcast = real_broadcast - def test_curate_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Curate.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - models.List.objects.create(name='Public list', user=self.local_user) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.List.objects.create(name="Public list", user=self.local_user) models.List.objects.create( - name='Private list', privacy='direct', user=self.local_user) - request = self.factory.get('') + name="Private list", privacy="direct", user=self.local_user + ) + request = self.factory.get("") request.user = self.local_user result = view(request, self.list.id) @@ -177,31 +192,35 @@ class ListViews(TestCase): result = view(request, self.list.id) self.assertEqual(result.status_code, 302) - def test_curate_approve(self): - ''' approve a pending item ''' + """ approve a pending item """ real_broadcast = models.List.broadcast + def mock_broadcast(_, activity, user, **kwargs): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Add') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['target'], self.list.remote_id) + self.assertEqual(activity["type"], "Add") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["target"], self.list.remote_id) + models.ListItem.broadcast = mock_broadcast view = views.Curate.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): pending = models.ListItem.objects.create( book_list=self.list, user=self.local_user, book=self.book, - approved=False + approved=False, ) - request = self.factory.post('', { - 'item': pending.id, - 'approved': 'true', - }) + request = self.factory.post( + "", + { + "item": pending.id, + "approved": "true", + }, + ) request.user = self.local_user view(request, self.list.id) @@ -211,43 +230,49 @@ class ListViews(TestCase): self.assertTrue(pending.approved) models.ListItem.broadcast = real_broadcast - def test_curate_reject(self): - ''' approve a pending item ''' + """ approve a pending item """ view = views.Curate.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): pending = models.ListItem.objects.create( book_list=self.list, user=self.local_user, book=self.book, - approved=False + approved=False, ) - request = self.factory.post('', { - 'item': pending.id, - 'approved': 'false', - }) + request = self.factory.post( + "", + { + "item": pending.id, + "approved": "false", + }, + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, self.list.id) self.assertFalse(self.list.books.exists()) self.assertFalse(models.ListItem.objects.exists()) - def test_add_book(self): - ''' put a book on a list ''' + """ put a book on a list """ real_broadcast = models.List.broadcast + def mock_broadcast(_, activity, user): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Add') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['target'], self.list.remote_id) + self.assertEqual(activity["type"], "Add") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["target"], self.list.remote_id) + models.ListItem.broadcast = mock_broadcast - request = self.factory.post('', { - 'book': self.book.id, - }) + request = self.factory.post( + "", + { + "book": self.book.id, + }, + ) request.user = self.local_user views.list.add_book(request, self.list.id) @@ -257,22 +282,26 @@ class ListViews(TestCase): self.assertTrue(item.approved) models.ListItem.broadcast = real_broadcast - def test_add_book_outsider(self): - ''' put a book on a list ''' + """ put a book on a list """ real_broadcast = models.List.broadcast + def mock_broadcast(_, activity, user): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.rat.remote_id) - self.assertEqual(activity['type'], 'Add') - self.assertEqual(activity['actor'], self.rat.remote_id) - self.assertEqual(activity['target'], self.list.remote_id) + self.assertEqual(activity["type"], "Add") + self.assertEqual(activity["actor"], self.rat.remote_id) + self.assertEqual(activity["target"], self.list.remote_id) + models.ListItem.broadcast = mock_broadcast - self.list.curation = 'open' + self.list.curation = "open" self.list.save(broadcast=False) - request = self.factory.post('', { - 'book': self.book.id, - }) + request = self.factory.post( + "", + { + "book": self.book.id, + }, + ) request.user = self.rat views.list.add_book(request, self.list.id) @@ -282,23 +311,27 @@ class ListViews(TestCase): self.assertTrue(item.approved) models.ListItem.broadcast = real_broadcast - def test_add_book_pending(self): - ''' put a book on a list awaiting approval ''' + """ put a book on a list awaiting approval """ real_broadcast = models.List.broadcast + def mock_broadcast(_, activity, user): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.rat.remote_id) - self.assertEqual(activity['type'], 'Add') - self.assertEqual(activity['actor'], self.rat.remote_id) - self.assertEqual(activity['target'], self.list.remote_id) - self.assertEqual(activity['object']['id'], self.book.remote_id) + self.assertEqual(activity["type"], "Add") + self.assertEqual(activity["actor"], self.rat.remote_id) + self.assertEqual(activity["target"], self.list.remote_id) + self.assertEqual(activity["object"]["id"], self.book.remote_id) + models.ListItem.broadcast = mock_broadcast - self.list.curation = 'curated' + self.list.curation = "curated" self.list.save(broadcast=False) - request = self.factory.post('', { - 'book': self.book.id, - }) + request = self.factory.post( + "", + { + "book": self.book.id, + }, + ) request.user = self.rat views.list.add_book(request, self.list.id) @@ -308,23 +341,27 @@ class ListViews(TestCase): self.assertFalse(item.approved) models.ListItem.broadcast = real_broadcast - def test_add_book_self_curated(self): - ''' put a book on a list automatically approved ''' + """ put a book on a list automatically approved """ real_broadcast = models.ListItem.broadcast + def mock_broadcast(_, activity, user): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Add') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['target'], self.list.remote_id) + self.assertEqual(activity["type"], "Add") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["target"], self.list.remote_id) + models.ListItem.broadcast = mock_broadcast - self.list.curation = 'curated' + self.list.curation = "curated" self.list.save(broadcast=False) - request = self.factory.post('', { - 'book': self.book.id, - }) + request = self.factory.post( + "", + { + "book": self.book.id, + }, + ) request.user = self.local_user views.list.add_book(request, self.list.id) @@ -334,12 +371,11 @@ class ListViews(TestCase): self.assertTrue(item.approved) models.ListItem.broadcast = real_broadcast - def test_remove_book(self): - ''' take an item off a list ''' + """ take an item off a list """ real_broadcast = models.ListItem.broadcast - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): item = models.ListItem.objects.create( book_list=self.list, user=self.local_user, @@ -348,15 +384,19 @@ class ListViews(TestCase): self.assertTrue(self.list.listitem_set.exists()) def mock_broadcast(_, activity, user): - ''' ok ''' + """ ok """ self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity['type'], 'Remove') - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['target'], self.list.remote_id) + self.assertEqual(activity["type"], "Remove") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["target"], self.list.remote_id) + models.ListItem.broadcast = mock_broadcast - request = self.factory.post('', { - 'item': item.id, - }) + request = self.factory.post( + "", + { + "item": item.id, + }, + ) request.user = self.local_user views.list.remove_book(request, self.list.id) @@ -364,19 +404,21 @@ class ListViews(TestCase): self.assertFalse(self.list.listitem_set.exists()) models.ListItem.broadcast = real_broadcast - def test_remove_book_unauthorized(self): - ''' take an item off a list ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ take an item off a list """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): item = models.ListItem.objects.create( book_list=self.list, user=self.local_user, book=self.book, ) self.assertTrue(self.list.listitem_set.exists()) - request = self.factory.post('', { - 'item': item.id, - }) + request = self.factory.post( + "", + { + "item": item.id, + }, + ) request.user = self.rat views.list.remove_book(request, self.list.id) diff --git a/bookwyrm/tests/views/test_notifications.py b/bookwyrm/tests/views/test_notifications.py index 555133597..6d92485ef 100644 --- a/bookwyrm/tests/views/test_notifications.py +++ b/bookwyrm/tests/views/test_notifications.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -8,19 +8,24 @@ from bookwyrm import views class NotificationViews(TestCase): - ''' notifications ''' + """ notifications """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_notifications_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Notifications.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) @@ -28,14 +33,16 @@ class NotificationViews(TestCase): self.assertEqual(result.status_code, 200) def test_clear_notifications(self): - ''' erase notifications ''' + """ erase notifications """ models.Notification.objects.create( - user=self.local_user, notification_type='FAVORITE') + user=self.local_user, notification_type="FAVORITE" + ) models.Notification.objects.create( - user=self.local_user, notification_type='MENTION', read=True) + user=self.local_user, notification_type="MENTION", read=True + ) self.assertEqual(models.Notification.objects.count(), 2) view = views.Notifications.as_view() - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user result = view(request) self.assertEqual(result.status_code, 302) diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index 7986dea61..5934eb7c7 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -1,4 +1,4 @@ -''' sending out activities ''' +""" sending out activities """ from unittest.mock import patch import json @@ -12,118 +12,127 @@ from bookwyrm.settings import USER_AGENT # pylint: disable=too-many-public-methods class OutboxView(TestCase): - ''' sends out activities ''' + """ sends out activities """ + def setUp(self): - ''' we'll need some data ''' + """ we'll need some data """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - work = models.Work.objects.create(title='Test Work') + work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, ) - def test_outbox(self): - ''' returns user's statuses ''' - request = self.factory.get('') - result = views.Outbox.as_view()(request, 'mouse') + """ returns user's statuses """ + request = self.factory.get("") + result = views.Outbox.as_view()(request, "mouse") self.assertIsInstance(result, JsonResponse) def test_outbox_bad_method(self): - ''' can't POST to outbox ''' - request = self.factory.post('') - result = views.Outbox.as_view()(request, 'mouse') + """ can't POST to outbox """ + request = self.factory.post("") + result = views.Outbox.as_view()(request, "mouse") self.assertEqual(result.status_code, 405) def test_outbox_unknown_user(self): - ''' should 404 for unknown and remote users ''' - request = self.factory.post('') - result = views.Outbox.as_view()(request, 'beepboop') + """ should 404 for unknown and remote users """ + request = self.factory.post("") + result = views.Outbox.as_view()(request, "beepboop") self.assertEqual(result.status_code, 405) - result = views.Outbox.as_view()(request, 'rat') + result = views.Outbox.as_view()(request, "rat") self.assertEqual(result.status_code, 405) def test_outbox_privacy(self): - ''' don't show dms et cetera in outbox ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ don't show dms et cetera in outbox """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Status.objects.create( - content='PRIVATE!!', user=self.local_user, privacy='direct') + content="PRIVATE!!", user=self.local_user, privacy="direct" + ) models.Status.objects.create( - content='bffs ONLY', user=self.local_user, privacy='followers') + content="bffs ONLY", user=self.local_user, privacy="followers" + ) models.Status.objects.create( - content='unlisted status', user=self.local_user, - privacy='unlisted') + content="unlisted status", user=self.local_user, privacy="unlisted" + ) models.Status.objects.create( - content='look at this', user=self.local_user, privacy='public') + content="look at this", user=self.local_user, privacy="public" + ) - request = self.factory.get('') - result = views.Outbox.as_view()(request, 'mouse') + request = self.factory.get("") + result = views.Outbox.as_view()(request, "mouse") self.assertIsInstance(result, JsonResponse) data = json.loads(result.content) - self.assertEqual(data['type'], 'OrderedCollection') - self.assertEqual(data['totalItems'], 2) + self.assertEqual(data["type"], "OrderedCollection") + self.assertEqual(data["totalItems"], 2) def test_outbox_filter(self): - ''' if we only care about reviews, only get reviews ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ if we only care about reviews, only get reviews """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( - content='look at this', name='hi', rating=1, - book=self.book, user=self.local_user) - models.Status.objects.create( - content='look at this', user=self.local_user) + content="look at this", + name="hi", + rating=1, + book=self.book, + user=self.local_user, + ) + models.Status.objects.create(content="look at this", user=self.local_user) - request = self.factory.get('', {'type': 'bleh'}) - result = views.Outbox.as_view()(request, 'mouse') + request = self.factory.get("", {"type": "bleh"}) + result = views.Outbox.as_view()(request, "mouse") self.assertIsInstance(result, JsonResponse) data = json.loads(result.content) - self.assertEqual(data['type'], 'OrderedCollection') - self.assertEqual(data['totalItems'], 2) + self.assertEqual(data["type"], "OrderedCollection") + self.assertEqual(data["totalItems"], 2) - request = self.factory.get('', {'type': 'Review'}) - result = views.Outbox.as_view()(request, 'mouse') + request = self.factory.get("", {"type": "Review"}) + result = views.Outbox.as_view()(request, "mouse") self.assertIsInstance(result, JsonResponse) data = json.loads(result.content) - self.assertEqual(data['type'], 'OrderedCollection') - self.assertEqual(data['totalItems'], 1) + self.assertEqual(data["type"], "OrderedCollection") + self.assertEqual(data["totalItems"], 1) def test_outbox_bookwyrm_request_true(self): - ''' should differentiate between bookwyrm and outside requests ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ should differentiate between bookwyrm and outside requests """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( - name='hi', - content='look at this', + name="hi", + content="look at this", user=self.local_user, book=self.book, - privacy='public', + privacy="public", ) - request = self.factory.get('', {'page': 1}, HTTP_USER_AGENT=USER_AGENT) - result = views.Outbox.as_view()(request, 'mouse') + request = self.factory.get("", {"page": 1}, HTTP_USER_AGENT=USER_AGENT) + result = views.Outbox.as_view()(request, "mouse") data = json.loads(result.content) - self.assertEqual(len(data['orderedItems']), 1) - self.assertEqual(data['orderedItems'][0]['type'], 'Review') + self.assertEqual(len(data["orderedItems"]), 1) + self.assertEqual(data["orderedItems"][0]["type"], "Review") def test_outbox_bookwyrm_request_false(self): - ''' should differentiate between bookwyrm and outside requests ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ should differentiate between bookwyrm and outside requests """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.Review.objects.create( - name='hi', - content='look at this', + name="hi", + content="look at this", user=self.local_user, book=self.book, - privacy='public', + privacy="public", ) - request = self.factory.get('', {'page': 1}) - result = views.Outbox.as_view()(request, 'mouse') + request = self.factory.get("", {"page": 1}) + result = views.Outbox.as_view()(request, "mouse") data = json.loads(result.content) - self.assertEqual(len(data['orderedItems']), 1) - self.assertEqual(data['orderedItems'][0]['type'], 'Article') + self.assertEqual(len(data["orderedItems"]), 1) + self.assertEqual(data["orderedItems"][0]["type"], "Article") diff --git a/bookwyrm/tests/views/test_password.py b/bookwyrm/tests/views/test_password.py index 9fc37fdb9..f67f5538f 100644 --- a/bookwyrm/tests/views/test_password.py +++ b/bookwyrm/tests/views/test_password.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import AnonymousUser @@ -10,22 +10,26 @@ from bookwyrm import models, views class PasswordViews(TestCase): - ''' view user and edit profile ''' + """ view user and edit profile """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.com", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create(id=1) - def test_password_reset_request(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.PasswordResetRequest.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) @@ -33,76 +37,63 @@ class PasswordViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_password_reset_request_post(self): - ''' send 'em an email ''' - request = self.factory.post('', {'email': 'aa@bb.ccc'}) + """ send 'em an email """ + request = self.factory.post("", {"email": "aa@bb.ccc"}) view = views.PasswordResetRequest.as_view() resp = view(request) self.assertEqual(resp.status_code, 302) - request = self.factory.post('', {'email': 'mouse@mouse.com'}) - with patch('bookwyrm.emailing.send_email.delay'): + request = self.factory.post("", {"email": "mouse@mouse.com"}) + with patch("bookwyrm.emailing.send_email.delay"): resp = view(request) resp.render() - self.assertEqual( - models.PasswordReset.objects.get().user, self.local_user) + self.assertEqual(models.PasswordReset.objects.get().user, self.local_user) def test_password_reset(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.PasswordReset.as_view() code = models.PasswordReset.objects.create(user=self.local_user) - request = self.factory.get('') + request = self.factory.get("") request.user = self.anonymous_user result = view(request, code.code) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_password_reset_post(self): - ''' reset from code ''' + """ reset from code """ view = views.PasswordReset.as_view() code = models.PasswordReset.objects.create(user=self.local_user) - request = self.factory.post('', { - 'password': 'hi', - 'confirm-password': 'hi' - }) - with patch('bookwyrm.views.password.login'): + request = self.factory.post("", {"password": "hi", "confirm-password": "hi"}) + with patch("bookwyrm.views.password.login"): resp = view(request, code.code) self.assertEqual(resp.status_code, 302) self.assertFalse(models.PasswordReset.objects.exists()) def test_password_reset_wrong_code(self): - ''' reset from code ''' + """ reset from code """ view = views.PasswordReset.as_view() models.PasswordReset.objects.create(user=self.local_user) - request = self.factory.post('', { - 'password': 'hi', - 'confirm-password': 'hi' - }) - resp = view(request, 'jhgdkfjgdf') + request = self.factory.post("", {"password": "hi", "confirm-password": "hi"}) + resp = view(request, "jhgdkfjgdf") resp.render() self.assertTrue(models.PasswordReset.objects.exists()) def test_password_reset_mismatch(self): - ''' reset from code ''' + """ reset from code """ view = views.PasswordReset.as_view() code = models.PasswordReset.objects.create(user=self.local_user) - request = self.factory.post('', { - 'password': 'hi', - 'confirm-password': 'hihi' - }) + request = self.factory.post("", {"password": "hi", "confirm-password": "hihi"}) resp = view(request, code.code) resp.render() self.assertTrue(models.PasswordReset.objects.exists()) - def test_password_change_get(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.ChangePassword.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) @@ -110,28 +101,21 @@ class PasswordViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_password_change(self): - ''' change password ''' + """ change password """ view = views.ChangePassword.as_view() password_hash = self.local_user.password - request = self.factory.post('', { - 'password': 'hi', - 'confirm-password': 'hi' - }) + request = self.factory.post("", {"password": "hi", "confirm-password": "hi"}) request.user = self.local_user - with patch('bookwyrm.views.password.login'): + with patch("bookwyrm.views.password.login"): view(request) self.assertNotEqual(self.local_user.password, password_hash) def test_password_change_mismatch(self): - ''' change password ''' + """ change password """ view = views.ChangePassword.as_view() password_hash = self.local_user.password - request = self.factory.post('', { - 'password': 'hi', - 'confirm-password': 'hihi' - }) + request = self.factory.post("", {"password": "hi", "confirm-password": "hihi"}) request.user = self.local_user view(request) self.assertEqual(self.local_user.password, password_hash) diff --git a/bookwyrm/tests/views/test_reading.py b/bookwyrm/tests/views/test_reading.py index 2bb052ae2..96d1f1f4c 100644 --- a/bookwyrm/tests/views/test_reading.py +++ b/bookwyrm/tests/views/test_reading.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch import dateutil from django.test import TestCase @@ -7,45 +7,54 @@ from django.utils import timezone from bookwyrm import models, views + class ReadingViews(TestCase): - ''' viewing and creating statuses ''' + """ viewing and creating statuses """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Test Book', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=self.work, ) - with patch('bookwyrm.models.user.set_remote_server.delay'): + with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', + "rat", + "rat@rat.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) - def test_start_reading(self): - ''' begin a book ''' - shelf = self.local_user.shelf_set.get(identifier='reading') + """ begin a book """ + shelf = self.local_user.shelf_set.get(identifier="reading") self.assertFalse(shelf.books.exists()) self.assertFalse(models.Status.objects.exists()) - request = self.factory.post('', { - 'post-status': True, - 'privacy': 'followers', - 'start_date': '2020-01-05', - }) + request = self.factory.post( + "", + { + "post-status": True, + "privacy": "followers", + "start_date": "2020-01-05", + }, + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.start_reading(request, self.book.id) self.assertEqual(shelf.books.get(), self.book) @@ -53,7 +62,7 @@ class ReadingViews(TestCase): status = models.GeneratedNote.objects.get() self.assertEqual(status.user, self.local_user) self.assertEqual(status.mention_books.get(), self.book) - self.assertEqual(status.privacy, 'followers') + self.assertEqual(status.privacy, "followers") readthrough = models.ReadThrough.objects.get() self.assertIsNotNone(readthrough.start_date) @@ -61,45 +70,47 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_start_reading_reshelf(self): - ''' begin a book ''' - to_read_shelf = self.local_user.shelf_set.get(identifier='to-read') - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ begin a book """ + to_read_shelf = self.local_user.shelf_set.get(identifier="to-read") + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( - shelf=to_read_shelf, book=self.book, user=self.local_user) - shelf = self.local_user.shelf_set.get(identifier='reading') + shelf=to_read_shelf, book=self.book, user=self.local_user + ) + shelf = self.local_user.shelf_set.get(identifier="reading") self.assertEqual(to_read_shelf.books.get(), self.book) self.assertFalse(shelf.books.exists()) self.assertFalse(models.Status.objects.exists()) - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.start_reading(request, self.book.id) self.assertFalse(to_read_shelf.books.exists()) self.assertEqual(shelf.books.get(), self.book) def test_finish_reading(self): - ''' begin a book ''' - shelf = self.local_user.shelf_set.get(identifier='read') + """ begin a book """ + shelf = self.local_user.shelf_set.get(identifier="read") self.assertFalse(shelf.books.exists()) self.assertFalse(models.Status.objects.exists()) readthrough = models.ReadThrough.objects.create( - user=self.local_user, - start_date=timezone.now(), - book=self.book) + user=self.local_user, start_date=timezone.now(), book=self.book + ) - request = self.factory.post('', { - 'post-status': True, - 'privacy': 'followers', - 'finish_date': '2020-01-07', - 'id': readthrough.id, - }) + request = self.factory.post( + "", + { + "post-status": True, + "privacy": "followers", + "finish_date": "2020-01-07", + "id": readthrough.id, + }, + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.finish_reading(request, self.book.id) self.assertEqual(shelf.books.get(), self.book) @@ -107,7 +118,7 @@ class ReadingViews(TestCase): status = models.GeneratedNote.objects.get() self.assertEqual(status.user, self.local_user) self.assertEqual(status.mention_books.get(), self.book) - self.assertEqual(status.privacy, 'followers') + self.assertEqual(status.privacy, "followers") readthrough = models.ReadThrough.objects.get() self.assertIsNotNone(readthrough.start_date) @@ -115,19 +126,21 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_edit_readthrough(self): - ''' adding dates to an ongoing readthrough ''' - start = timezone.make_aware(dateutil.parser.parse('2021-01-03')) + """ adding dates to an ongoing readthrough """ + start = timezone.make_aware(dateutil.parser.parse("2021-01-03")) readthrough = models.ReadThrough.objects.create( - book=self.book, user=self.local_user, start_date=start) + book=self.book, user=self.local_user, start_date=start + ) request = self.factory.post( - '', { - 'start_date': '2017-01-01', - 'finish_date': '2018-03-07', - 'book': '', - 'id': readthrough.id, - }) + "", + { + "start_date": "2017-01-01", + "finish_date": "2018-03-07", + "book": "", + "id": readthrough.id, + }, + ) request.user = self.local_user views.edit_readthrough(request) @@ -140,33 +153,34 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.finish_date.day, 7) self.assertEqual(readthrough.book, self.book) - def test_delete_readthrough(self): - ''' remove a readthrough ''' + """ remove a readthrough """ readthrough = models.ReadThrough.objects.create( - book=self.book, user=self.local_user) - models.ReadThrough.objects.create( - book=self.book, user=self.local_user) + book=self.book, user=self.local_user + ) + models.ReadThrough.objects.create(book=self.book, user=self.local_user) request = self.factory.post( - '', { - 'id': readthrough.id, - }) + "", + { + "id": readthrough.id, + }, + ) request.user = self.local_user views.delete_readthrough(request) - self.assertFalse( - models.ReadThrough.objects.filter(id=readthrough.id).exists()) - + self.assertFalse(models.ReadThrough.objects.filter(id=readthrough.id).exists()) def test_create_readthrough(self): - ''' adding new read dates ''' + """ adding new read dates """ request = self.factory.post( - '', { - 'start_date': '2017-01-01', - 'finish_date': '2018-03-07', - 'book': self.book.id, - 'id': '', - }) + "", + { + "start_date": "2017-01-01", + "finish_date": "2018-03-07", + "book": self.book.id, + "id": "", + }, + ) request.user = self.local_user views.create_readthrough(request) diff --git a/bookwyrm/tests/views/test_readthrough.py b/bookwyrm/tests/views/test_readthrough.py index 41e38a1d6..5399f673c 100644 --- a/bookwyrm/tests/views/test_readthrough.py +++ b/bookwyrm/tests/views/test_readthrough.py @@ -1,4 +1,4 @@ -''' tests updating reading progress ''' +""" tests updating reading progress """ from datetime import datetime from unittest.mock import patch from django.test import TestCase, Client @@ -6,63 +6,68 @@ from django.utils import timezone from bookwyrm import models -@patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') + +@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class ReadThrough(TestCase): - ''' readthrough tests ''' + """ readthrough tests """ + def setUp(self): - ''' basic user and book data ''' + """ basic user and book data """ self.client = Client() - self.work = models.Work.objects.create( - title='Example Work' - ) + self.work = models.Work.objects.create(title="Example Work") self.edition = models.Edition.objects.create( - title='Example Edition', - parent_work=self.work + title="Example Edition", parent_work=self.work ) self.work.default_edition = self.edition self.work.save() self.user = models.User.objects.create_user( - 'cinco', 'cinco@example.com', 'seissiete', - local=True, localname='cinco') + "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" + ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.client.force_login(self.user) def test_create_basic_readthrough(self, delay_mock): """A basic readthrough doesn't create a progress update""" self.assertEqual(self.edition.readthrough_set.count(), 0) - self.client.post('/start-reading/{}'.format(self.edition.id), { - 'start_date': '2020-11-27', - }) + self.client.post( + "/start-reading/{}".format(self.edition.id), + { + "start_date": "2020-11-27", + }, + ) readthroughs = self.edition.readthrough_set.all() self.assertEqual(len(readthroughs), 1) self.assertEqual(readthroughs[0].progressupdate_set.count(), 0) self.assertEqual( - readthroughs[0].start_date, - datetime(2020, 11, 27, tzinfo=timezone.utc)) + readthroughs[0].start_date, datetime(2020, 11, 27, tzinfo=timezone.utc) + ) self.assertEqual(readthroughs[0].progress, None) self.assertEqual(readthroughs[0].finish_date, None) self.assertEqual(delay_mock.call_count, 1) def test_create_progress_readthrough(self, delay_mock): - ''' a readthrough with progress ''' + """ a readthrough with progress """ self.assertEqual(self.edition.readthrough_set.count(), 0) - self.client.post('/start-reading/{}'.format(self.edition.id), { - 'start_date': '2020-11-27', - 'progress': 50, - }) + self.client.post( + "/start-reading/{}".format(self.edition.id), + { + "start_date": "2020-11-27", + "progress": 50, + }, + ) readthroughs = self.edition.readthrough_set.all() self.assertEqual(len(readthroughs), 1) self.assertEqual( - readthroughs[0].start_date, - datetime(2020, 11, 27, tzinfo=timezone.utc)) + readthroughs[0].start_date, datetime(2020, 11, 27, tzinfo=timezone.utc) + ) self.assertEqual(readthroughs[0].progress, 50) self.assertEqual(readthroughs[0].finish_date, None) @@ -73,13 +78,17 @@ class ReadThrough(TestCase): self.assertEqual(delay_mock.call_count, 1) # Update progress - self.client.post('/edit-readthrough', { - 'id': readthroughs[0].id, - 'progress': 100, - }) + self.client.post( + "/edit-readthrough", + { + "id": readthroughs[0].id, + "progress": 100, + }, + ) - progress_updates = readthroughs[0].progressupdate_set\ - .order_by('updated_date').all() + progress_updates = ( + readthroughs[0].progressupdate_set.order_by("updated_date").all() + ) self.assertEqual(len(progress_updates), 2) self.assertEqual(progress_updates[1].mode, models.ProgressMode.PAGE) self.assertEqual(progress_updates[1].progress, 100) @@ -87,9 +96,12 @@ class ReadThrough(TestCase): # Edit doesn't publish anything self.assertEqual(delay_mock.call_count, 1) - self.client.post('/delete-readthrough', { - 'id': readthroughs[0].id, - }) + self.client.post( + "/delete-readthrough", + { + "id": readthroughs[0].id, + }, + ) readthroughs = self.edition.readthrough_set.all() updates = self.user.progressupdate_set.all() diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index 3d5bec49a..c7fef2a94 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -1,4 +1,4 @@ -''' testing import ''' +""" testing import """ from unittest.mock import patch from django.test import RequestFactory, TestCase @@ -6,41 +6,51 @@ from django.test import RequestFactory, TestCase from bookwyrm import models from bookwyrm.views import rss_feed + class RssFeedView(TestCase): - ''' rss feed behaves as expected ''' + """ rss feed behaves as expected """ + def setUp(self): - ''' test data ''' + """ test data """ self.site = models.SiteSettings.objects.create() self.user = models.User.objects.create_user( - 'rss_user', 'rss@test.rss', 'password', local=True) - - work = models.Work.objects.create(title='Test Work') - self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work + "rss_user", "rss@test.rss", "password", local=True ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + work = models.Work.objects.create(title="Test Work") + self.book = models.Edition.objects.create( + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, + ) + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.review = models.Review.objects.create( - name='Review name', content='test content', rating=3, - user=self.user, book=self.book) + name="Review name", + content="test content", + rating=3, + user=self.user, + book=self.book, + ) self.quote = models.Quotation.objects.create( - quote='a sickening sense', content='test content', - user=self.user, book=self.book) + quote="a sickening sense", + content="test content", + user=self.user, + book=self.book, + ) self.generatednote = models.GeneratedNote.objects.create( - content='test content', user=self.user) + content="test content", user=self.user + ) self.factory = RequestFactory() - def test_rss_feed(self): - ''' load an rss feed ''' + """ load an rss feed """ view = rss_feed.RssFeed() - request = self.factory.get('/user/rss_user/rss') + request = self.factory.get("/user/rss_user/rss") request.user = self.user with patch("bookwyrm.models.SiteSettings.objects.get") as site: site.return_value = self.site diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py index 5d7109e71..78c7a1037 100644 --- a/bookwyrm/tests/views/test_search.py +++ b/bookwyrm/tests/views/test_search.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ import json from unittest.mock import patch @@ -13,103 +13,107 @@ from bookwyrm.settings import DOMAIN class ShelfViews(TestCase): - ''' tag views''' + """ tag views""" + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Test Book', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=self.work, ) models.Connector.objects.create( - identifier='self', - connector_file='self_connector', - local=True + identifier="self", connector_file="self_connector", local=True ) models.SiteSettings.objects.create() - def test_search_json_response(self): - ''' searches local data only and returns book data in json format ''' + """ searches local data only and returns book data in json format """ view = views.Search.as_view() # we need a connector for this, sorry - request = self.factory.get('', {'q': 'Test Book'}) - with patch('bookwyrm.views.search.is_api_request') as is_api: + request = self.factory.get("", {"q": "Test Book"}) + with patch("bookwyrm.views.search.is_api_request") as is_api: is_api.return_value = True response = view(request) self.assertIsInstance(response, JsonResponse) data = json.loads(response.content) self.assertEqual(len(data), 1) - self.assertEqual(data[0]['title'], 'Test Book') - self.assertEqual( - data[0]['key'], 'https://%s/book/%d' % (DOMAIN, self.book.id)) - + self.assertEqual(data[0]["title"], "Test Book") + self.assertEqual(data[0]["key"], "https://%s/book/%d" % (DOMAIN, self.book.id)) def test_search_html_response(self): - ''' searches remote connectors ''' + """ searches remote connectors """ view = views.Search.as_view() + class TestConnector(abstract_connector.AbstractMinimalConnector): - ''' nothing added here ''' + """ nothing added here """ + def format_search_result(self, search_result): pass + def get_or_create_book(self, remote_id): pass + def parse_search_data(self, data): pass + def format_isbn_search_result(self, search_result): return search_result + def parse_isbn_search_data(self, data): return data + models.Connector.objects.create( - identifier='example.com', - connector_file='openlibrary', - base_url='https://example.com', - books_url='https://example.com/books', - covers_url='https://example.com/covers', - search_url='https://example.com/search?q=', + identifier="example.com", + connector_file="openlibrary", + base_url="https://example.com", + books_url="https://example.com/books", + covers_url="https://example.com/covers", + search_url="https://example.com/search?q=", ) - connector = TestConnector('example.com') + connector = TestConnector("example.com") search_result = abstract_connector.SearchResult( - key='http://www.example.com/book/1', - title='Gideon the Ninth', - author='Tamsyn Muir', - year='2019', - connector=connector + key="http://www.example.com/book/1", + title="Gideon the Ninth", + author="Tamsyn Muir", + year="2019", + connector=connector, ) - request = self.factory.get('', {'q': 'Test Book'}) + request = self.factory.get("", {"q": "Test Book"}) request.user = self.local_user - with patch('bookwyrm.views.search.is_api_request') as is_api: + with patch("bookwyrm.views.search.is_api_request") as is_api: is_api.return_value = False - with patch( - 'bookwyrm.connectors.connector_manager.search') as manager: + with patch("bookwyrm.connectors.connector_manager.search") as manager: manager.return_value = [search_result] response = view(request) self.assertIsInstance(response, TemplateResponse) response.render() self.assertEqual( - response.context_data['book_results'][0].title, 'Gideon the Ninth') - + response.context_data["book_results"][0].title, "Gideon the Ninth" + ) def test_search_html_response_users(self): - ''' searches remote connectors ''' + """ searches remote connectors """ view = views.Search.as_view() - request = self.factory.get('', {'q': 'mouse'}) + request = self.factory.get("", {"q": "mouse"}) request.user = self.local_user - with patch('bookwyrm.views.search.is_api_request') as is_api: + with patch("bookwyrm.views.search.is_api_request") as is_api: is_api.return_value = False - with patch('bookwyrm.connectors.connector_manager.search'): + with patch("bookwyrm.connectors.connector_manager.search"): response = view(request) self.assertIsInstance(response, TemplateResponse) response.render() - self.assertEqual( - response.context_data['user_results'][0], self.local_user) + self.assertEqual(response.context_data["user_results"][0], self.local_user) diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index 4fa63689a..a308fe56a 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase @@ -8,195 +8,172 @@ from bookwyrm import models, views from bookwyrm.activitypub import ActivitypubResponse -@patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') +@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class ShelfViews(TestCase): - ''' tag views''' + """ tag views""" + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, ) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.shelf = models.Shelf.objects.create( - name='Test Shelf', - identifier='test-shelf', - user=self.local_user + name="Test Shelf", identifier="test-shelf", user=self.local_user ) models.SiteSettings.objects.create() - def test_shelf_page(self, _): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Shelf.as_view() shelf = self.local_user.shelf_set.first() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.shelf.is_api_request') as is_api: + with patch("bookwyrm.views.shelf.is_api_request") as is_api: is_api.return_value = False result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.shelf.is_api_request') as is_api: + with patch("bookwyrm.views.shelf.is_api_request") as is_api: is_api.return_value = True - result = view( - request, self.local_user.username, shelf.identifier) + result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - request = self.factory.get('/?page=1') + request = self.factory.get("/?page=1") request.user = self.local_user - with patch('bookwyrm.views.shelf.is_api_request') as is_api: + with patch("bookwyrm.views.shelf.is_api_request") as is_api: is_api.return_value = True - result = view( - request, self.local_user.username, shelf.identifier) + result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_edit_shelf_privacy(self, _): - ''' set name or privacy on shelf ''' + """ set name or privacy on shelf """ view = views.Shelf.as_view() - shelf = self.local_user.shelf_set.get(identifier='to-read') - self.assertEqual(shelf.privacy, 'public') + shelf = self.local_user.shelf_set.get(identifier="to-read") + self.assertEqual(shelf.privacy, "public") request = self.factory.post( - '', { - 'privacy': 'unlisted', - 'user': self.local_user.id, - 'name': 'To Read', - }) + "", + { + "privacy": "unlisted", + "user": self.local_user.id, + "name": "To Read", + }, + ) request.user = self.local_user view(request, self.local_user.username, shelf.identifier) shelf.refresh_from_db() - self.assertEqual(shelf.privacy, 'unlisted') - + self.assertEqual(shelf.privacy, "unlisted") def test_edit_shelf_name(self, _): - ''' change the name of an editable shelf ''' + """ change the name of an editable shelf """ view = views.Shelf.as_view() - shelf = models.Shelf.objects.create( - name='Test Shelf', user=self.local_user) - self.assertEqual(shelf.privacy, 'public') + shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user) + self.assertEqual(shelf.privacy, "public") request = self.factory.post( - '', { - 'privacy': 'public', - 'user': self.local_user.id, - 'name': 'cool name' - }) + "", {"privacy": "public", "user": self.local_user.id, "name": "cool name"} + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, request.user.username, shelf.identifier) shelf.refresh_from_db() - self.assertEqual(shelf.name, 'cool name') - self.assertEqual(shelf.identifier, 'testshelf-%d' % shelf.id) - + self.assertEqual(shelf.name, "cool name") + self.assertEqual(shelf.identifier, "testshelf-%d" % shelf.id) def test_edit_shelf_name_not_editable(self, _): - ''' can't change the name of an non-editable shelf ''' + """ can't change the name of an non-editable shelf """ view = views.Shelf.as_view() - shelf = self.local_user.shelf_set.get(identifier='to-read') - self.assertEqual(shelf.privacy, 'public') + shelf = self.local_user.shelf_set.get(identifier="to-read") + self.assertEqual(shelf.privacy, "public") request = self.factory.post( - '', { - 'privacy': 'public', - 'user': self.local_user.id, - 'name': 'cool name' - }) + "", {"privacy": "public", "user": self.local_user.id, "name": "cool name"} + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request, request.user.username, shelf.identifier) - self.assertEqual(shelf.name, 'To Read') - + self.assertEqual(shelf.name, "To Read") def test_handle_shelve(self, _): - ''' shelve a book ''' - request = self.factory.post('', { - 'book': self.book.id, - 'shelf': self.shelf.identifier - }) + """ shelve a book """ + request = self.factory.post( + "", {"book": self.book.id, "shelf": self.shelf.identifier} + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.shelve(request) # make sure the book is on the shelf self.assertEqual(self.shelf.books.get(), self.book) - def test_handle_shelve_to_read(self, _): - ''' special behavior for the to-read shelf ''' - shelf = models.Shelf.objects.get(identifier='to-read') - request = self.factory.post('', { - 'book': self.book.id, - 'shelf': shelf.identifier - }) + """ special behavior for the to-read shelf """ + shelf = models.Shelf.objects.get(identifier="to-read") + request = self.factory.post( + "", {"book": self.book.id, "shelf": shelf.identifier} + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.shelve(request) # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) - def test_handle_shelve_reading(self, _): - ''' special behavior for the reading shelf ''' - shelf = models.Shelf.objects.get(identifier='reading') - request = self.factory.post('', { - 'book': self.book.id, - 'shelf': shelf.identifier - }) + """ special behavior for the reading shelf """ + shelf = models.Shelf.objects.get(identifier="reading") + request = self.factory.post( + "", {"book": self.book.id, "shelf": shelf.identifier} + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.shelve(request) # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) - def test_handle_shelve_read(self, _): - ''' special behavior for the read shelf ''' - shelf = models.Shelf.objects.get(identifier='read') - request = self.factory.post('', { - 'book': self.book.id, - 'shelf': shelf.identifier - }) + """ special behavior for the read shelf """ + shelf = models.Shelf.objects.get(identifier="read") + request = self.factory.post( + "", {"book": self.book.id, "shelf": shelf.identifier} + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.shelve(request) # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) - def test_handle_unshelve(self, _): - ''' remove a book from a shelf ''' - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + """ remove a book from a shelf """ + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( - book=self.book, - user=self.local_user, - shelf=self.shelf + book=self.book, user=self.local_user, shelf=self.shelf ) self.shelf.save() self.assertEqual(self.shelf.books.count(), 1) - request = self.factory.post('', { - 'book': self.book.id, - 'shelf': self.shelf.id - }) + request = self.factory.post("", {"book": self.book.id, "shelf": self.shelf.id}) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.unshelve(request) self.assertEqual(self.shelf.books.count(), 0) diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 4594a2031..7fdc0e060 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ import json from unittest.mock import patch from django.test import TestCase @@ -9,239 +9,247 @@ from bookwyrm.settings import DOMAIN class StatusViews(TestCase): - ''' viewing and creating statuses ''' + """ viewing and creating statuses """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - with patch('bookwyrm.models.user.set_remote_server'): + with patch("bookwyrm.models.user.set_remote_server"): self.remote_user = models.User.objects.create_user( - 'rat', 'rat@email.com', 'ratword', + "rat", + "rat@email.com", + "ratword", local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', + remote_id="https://example.com/users/rat", + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", ) - work = models.Work.objects.create(title='Test Work') + work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=work, ) - def test_handle_status(self): - ''' create a status ''' + """ create a status """ view = views.CreateStatus.as_view() - form = forms.CommentForm({ - 'content': 'hi', - 'user': self.local_user.id, - 'book': self.book.id, - 'privacy': 'public', - }) - request = self.factory.post('', form.data) + form = forms.CommentForm( + { + "content": "hi", + "user": self.local_user.id, + "book": self.book.id, + "privacy": "public", + } + ) + request = self.factory.post("", form.data) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - view(request, 'comment') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, "comment") status = models.Comment.objects.get() - self.assertEqual(status.content, '

hi

') + self.assertEqual(status.content, "

hi

") self.assertEqual(status.user, self.local_user) self.assertEqual(status.book, self.book) def test_handle_status_reply(self): - ''' create a status in reply to an existing status ''' + """ create a status in reply to an existing status """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'password', local=True) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + "rat", "rat@rat.com", "password", local=True + ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): parent = models.Status.objects.create( - content='parent status', user=self.local_user) - form = forms.ReplyForm({ - 'content': 'hi', - 'user': user.id, - 'reply_parent': parent.id, - 'privacy': 'public', - }) - request = self.factory.post('', form.data) + content="parent status", user=self.local_user + ) + form = forms.ReplyForm( + { + "content": "hi", + "user": user.id, + "reply_parent": parent.id, + "privacy": "public", + } + ) + request = self.factory.post("", form.data) request.user = user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - view(request, 'reply') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, "reply") status = models.Status.objects.get(user=user) - self.assertEqual(status.content, '

hi

') + self.assertEqual(status.content, "

hi

") self.assertEqual(status.user, user) - self.assertEqual( - models.Notification.objects.get().user, self.local_user) + self.assertEqual(models.Notification.objects.get().user, self.local_user) def test_handle_status_mentions(self): - ''' @mention a user in a post ''' + """ @mention a user in a post """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( - 'rat@%s' % DOMAIN, 'rat@rat.com', 'password', - local=True, localname='rat') - form = forms.CommentForm({ - 'content': 'hi @rat', - 'user': self.local_user.id, - 'book': self.book.id, - 'privacy': 'public', - }) - request = self.factory.post('', form.data) + "rat@%s" % DOMAIN, "rat@rat.com", "password", local=True, localname="rat" + ) + form = forms.CommentForm( + { + "content": "hi @rat", + "user": self.local_user.id, + "book": self.book.id, + "privacy": "public", + } + ) + request = self.factory.post("", form.data) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - view(request, 'comment') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, "comment") status = models.Status.objects.get() self.assertEqual(list(status.mention_users.all()), [user]) self.assertEqual(models.Notification.objects.get().user, user) self.assertEqual( - status.content, - '

hi @rat

' % user.remote_id) + status.content, '

hi @rat

' % user.remote_id + ) def test_handle_status_reply_with_mentions(self): - ''' reply to a post with an @mention'ed user ''' + """ reply to a post with an @mention'ed user """ view = views.CreateStatus.as_view() user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'password', - local=True, localname='rat') - form = forms.CommentForm({ - 'content': 'hi @rat@example.com', - 'user': self.local_user.id, - 'book': self.book.id, - 'privacy': 'public', - }) - request = self.factory.post('', form.data) + "rat", "rat@rat.com", "password", local=True, localname="rat" + ) + form = forms.CommentForm( + { + "content": "hi @rat@example.com", + "user": self.local_user.id, + "book": self.book.id, + "privacy": "public", + } + ) + request = self.factory.post("", form.data) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - view(request, 'comment') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, "comment") status = models.Status.objects.get() - form = forms.ReplyForm({ - 'content': 'right', - 'user': user.id, - 'privacy': 'public', - 'reply_parent': status.id - }) - request = self.factory.post('', form.data) + form = forms.ReplyForm( + { + "content": "right", + "user": user.id, + "privacy": "public", + "reply_parent": status.id, + } + ) + request = self.factory.post("", form.data) request.user = user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - view(request, 'reply') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + view(request, "reply") reply = models.Status.replies(status).first() - self.assertEqual(reply.content, '

right

') + self.assertEqual(reply.content, "

right

") self.assertEqual(reply.user, user) # the mentioned user in the parent post is only included if @'ed self.assertFalse(self.remote_user in reply.mention_users.all()) self.assertTrue(self.local_user in reply.mention_users.all()) def test_find_mentions(self): - ''' detect and look up @ mentions of users ''' + """ detect and look up @ mentions of users """ user = models.User.objects.create_user( - 'nutria@%s' % DOMAIN, 'nutria@nutria.com', 'password', - local=True, localname='nutria') - self.assertEqual(user.username, 'nutria@%s' % DOMAIN) + "nutria@%s" % DOMAIN, + "nutria@nutria.com", + "password", + local=True, + localname="nutria", + ) + self.assertEqual(user.username, "nutria@%s" % DOMAIN) self.assertEqual( - list(views.status.find_mentions('@nutria'))[0], - ('@nutria', user) + list(views.status.find_mentions("@nutria"))[0], ("@nutria", user) ) self.assertEqual( - list(views.status.find_mentions('leading text @nutria'))[0], - ('@nutria', user) + list(views.status.find_mentions("leading text @nutria"))[0], + ("@nutria", user), ) self.assertEqual( - list(views.status.find_mentions( - 'leading @nutria trailing text'))[0], - ('@nutria', user) + list(views.status.find_mentions("leading @nutria trailing text"))[0], + ("@nutria", user), ) self.assertEqual( - list(views.status.find_mentions( - '@rat@example.com'))[0], - ('@rat@example.com', self.remote_user) + list(views.status.find_mentions("@rat@example.com"))[0], + ("@rat@example.com", self.remote_user), ) - multiple = list(views.status.find_mentions( - '@nutria and @rat@example.com')) - self.assertEqual(multiple[0], ('@nutria', user)) - self.assertEqual(multiple[1], ('@rat@example.com', self.remote_user)) + multiple = list(views.status.find_mentions("@nutria and @rat@example.com")) + self.assertEqual(multiple[0], ("@nutria", user)) + self.assertEqual(multiple[1], ("@rat@example.com", self.remote_user)) - with patch('bookwyrm.views.status.handle_remote_webfinger') as rw: + with patch("bookwyrm.views.status.handle_remote_webfinger") as rw: rw.return_value = self.local_user self.assertEqual( - list(views.status.find_mentions('@beep@beep.com'))[0], - ('@beep@beep.com', self.local_user) + list(views.status.find_mentions("@beep@beep.com"))[0], + ("@beep@beep.com", self.local_user), ) - with patch('bookwyrm.views.status.handle_remote_webfinger') as rw: + with patch("bookwyrm.views.status.handle_remote_webfinger") as rw: rw.return_value = None - self.assertEqual(list(views.status.find_mentions( - '@beep@beep.com')), []) + self.assertEqual(list(views.status.find_mentions("@beep@beep.com")), []) self.assertEqual( - list(views.status.find_mentions('@nutria@%s' % DOMAIN))[0], - ('@nutria@%s' % DOMAIN, user) + list(views.status.find_mentions("@nutria@%s" % DOMAIN))[0], + ("@nutria@%s" % DOMAIN, user), ) def test_format_links(self): - ''' find and format urls into a tags ''' - url = 'http://www.fish.com/' + """ find and format urls into a tags """ + url = "http://www.fish.com/" + self.assertEqual( + views.status.format_links(url), 'www.fish.com/' % url + ) + self.assertEqual( + views.status.format_links("(%s)" % url), + '(www.fish.com/)' % url, + ) + url = "https://archive.org/details/dli.granth.72113/page/n25/mode/2up" self.assertEqual( views.status.format_links(url), - 'www.fish.com/' % url) - self.assertEqual( - views.status.format_links('(%s)' % url), - '(www.fish.com/)' % url) - url = 'https://archive.org/details/dli.granth.72113/page/n25/mode/2up' + '' + "archive.org/details/dli.granth.72113/page/n25/mode/2up" % url, + ) + url = "https://openlibrary.org/search" "?q=arkady+strugatsky&mode=everything" self.assertEqual( views.status.format_links(url), - '' \ - 'archive.org/details/dli.granth.72113/page/n25/mode/2up' \ - % url) - url = 'https://openlibrary.org/search' \ - '?q=arkady+strugatsky&mode=everything' - self.assertEqual( - views.status.format_links(url), - 'openlibrary.org/search' \ - '?q=arkady+strugatsky&mode=everything' % url) - + 'openlibrary.org/search' + "?q=arkady+strugatsky&mode=everything" % url, + ) def test_to_markdown(self): - ''' this is mostly handled in other places, but nonetheless ''' - text = '_hi_ and http://fish.com is rad' + """ this is mostly handled in other places, but nonetheless """ + text = "_hi_ and http://fish.com is rad" result = views.status.to_markdown(text) self.assertEqual( result, - '

hi and fish.com ' \ - 'is rad

') - + '

hi and fish.com ' "is rad

", + ) def test_to_markdown_link(self): - ''' this is mostly handled in other places, but nonetheless ''' - text = '[hi](http://fish.com) is rad' + """ this is mostly handled in other places, but nonetheless """ + text = "[hi](http://fish.com) is rad" result = views.status.to_markdown(text) - self.assertEqual( - result, - '

hi ' \ - 'is rad

') - + self.assertEqual(result, '

hi ' "is rad

") def test_handle_delete_status(self): - ''' marks a status as deleted ''' + """ marks a status as deleted """ view = views.DeleteStatus.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - status = models.Status.objects.create( - user=self.local_user, content='hi') + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Status.objects.create(user=self.local_user, content="hi") self.assertFalse(status.deleted) - request = self.factory.post('') + request = self.factory.post("") request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ - as mock: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: view(request, status.id) activity = json.loads(mock.call_args_list[0][0][1]) - self.assertEqual(activity['type'], 'Delete') - self.assertEqual(activity['object']['type'], 'Tombstone') + self.assertEqual(activity["type"], "Delete") + self.assertEqual(activity["object"]["type"], "Tombstone") status.refresh_from_db() self.assertTrue(status.deleted) diff --git a/bookwyrm/tests/views/test_tag.py b/bookwyrm/tests/views/test_tag.py index ef809b466..6ad6ab254 100644 --- a/bookwyrm/tests/views/test_tag.py +++ b/bookwyrm/tests/views/test_tag.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ from unittest.mock import patch from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType @@ -11,107 +11,109 @@ from bookwyrm.activitypub import ActivitypubResponse class TagViews(TestCase): - ''' tag views''' + """ tag views""" + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.com', 'mouseword', - local=True, localname='mouse', - remote_id='https://example.com/users/mouse', + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", ) - self.group = Group.objects.create(name='editor') + self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( - name='edit_book', - codename='edit_book', - content_type=ContentType.objects.get_for_model(models.User)).id + name="edit_book", + codename="edit_book", + content_type=ContentType.objects.get_for_model(models.User), + ).id ) - self.work = models.Work.objects.create(title='Test Work') + self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=self.work + title="Example Edition", + remote_id="https://example.com/book/1", + parent_work=self.work, ) models.SiteSettings.objects.create() - def test_tag_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Tag.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - tag = models.Tag.objects.create(name='hi there') - models.UserTag.objects.create( - tag=tag, user=self.local_user, book=self.book) - request = self.factory.get('') - with patch('bookwyrm.views.tag.is_api_request') as is_api: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + tag = models.Tag.objects.create(name="hi there") + models.UserTag.objects.create(tag=tag, user=self.local_user, book=self.book) + request = self.factory.get("") + with patch("bookwyrm.views.tag.is_api_request") as is_api: is_api.return_value = False result = view(request, tag.identifier) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - request = self.factory.get('') - with patch('bookwyrm.views.tag.is_api_request') as is_api: + request = self.factory.get("") + with patch("bookwyrm.views.tag.is_api_request") as is_api: is_api.return_value = True result = view(request, tag.identifier) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_tag_page_activitypub_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Tag.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - tag = models.Tag.objects.create(name='hi there') - models.UserTag.objects.create( - tag=tag, user=self.local_user, book=self.book) - request = self.factory.get('', {'page': 1}) - with patch('bookwyrm.views.tag.is_api_request') as is_api: + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + tag = models.Tag.objects.create(name="hi there") + models.UserTag.objects.create(tag=tag, user=self.local_user, book=self.book) + request = self.factory.get("", {"page": 1}) + with patch("bookwyrm.views.tag.is_api_request") as is_api: is_api.return_value = True result = view(request, tag.identifier) self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_tag(self): - ''' add a tag to a book ''' + """ add a tag to a book """ view = views.AddTag.as_view() request = self.factory.post( - '', { - 'name': 'A Tag!?', - 'book': self.book.id, - }) + "", + { + "name": "A Tag!?", + "book": self.book.id, + }, + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request) tag = models.Tag.objects.get() user_tag = models.UserTag.objects.get() - self.assertEqual(tag.name, 'A Tag!?') - self.assertEqual(tag.identifier, 'A+Tag%21%3F') + self.assertEqual(tag.name, "A Tag!?") + self.assertEqual(tag.identifier, "A+Tag%21%3F") self.assertEqual(user_tag.user, self.local_user) self.assertEqual(user_tag.book, self.book) - def test_untag(self): - ''' remove a tag from a book ''' + """ remove a tag from a book """ view = views.RemoveTag.as_view() - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): - tag = models.Tag.objects.create(name='A Tag!?') - models.UserTag.objects.create( - user=self.local_user, book=self.book, tag=tag) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + tag = models.Tag.objects.create(name="A Tag!?") + models.UserTag.objects.create(user=self.local_user, book=self.book, tag=tag) request = self.factory.post( - '', { - 'user': self.local_user.id, - 'book': self.book.id, - 'name': tag.name, - }) + "", + { + "user": self.local_user.id, + "book": self.book.id, + "name": tag.name, + }, + ) request.user = self.local_user - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): view(request) - self.assertTrue(models.Tag.objects.filter(name='A Tag!?').exists()) + self.assertTrue(models.Tag.objects.filter(name="A Tag!?").exists()) self.assertFalse(models.UserTag.objects.exists()) diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py index f08c17e71..596ea8bf7 100644 --- a/bookwyrm/tests/views/test_user.py +++ b/bookwyrm/tests/views/test_user.py @@ -1,4 +1,4 @@ -''' test for app action functionality ''' +""" test for app action functionality """ import pathlib from unittest.mock import patch from PIL import Image @@ -15,180 +15,177 @@ from bookwyrm.activitypub import ActivitypubResponse class UserViews(TestCase): - ''' view user and edit profile ''' + """ view user and edit profile """ + def setUp(self): - ''' we need basic test data and mocks ''' + """ we need basic test data and mocks """ self.factory = RequestFactory() self.local_user = models.User.objects.create_user( - 'mouse@local.com', 'mouse@mouse.mouse', 'password', - local=True, localname='mouse') + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.rat = models.User.objects.create_user( - 'rat@local.com', 'rat@rat.rat', 'password', - local=True, localname='rat') + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) models.SiteSettings.objects.create() self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - def test_user_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.User.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) request.user = self.anonymous_user - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = True - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_user_page_blocked(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.User.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user self.rat.blocks.add(self.local_user) - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'rat') + result = view(request, "rat") self.assertEqual(result.status_code, 404) - def test_followers_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Followers.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = True - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_followers_page_blocked(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Followers.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user self.rat.blocks.add(self.local_user) - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'rat') + result = view(request, "rat") self.assertEqual(result.status_code, 404) - def test_following_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Following.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = True - result = view(request, 'mouse') + result = view(request, "mouse") self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_following_page_blocked(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.Following.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user self.rat.blocks.add(self.local_user) - with patch('bookwyrm.views.user.is_api_request') as is_api: + with patch("bookwyrm.views.user.is_api_request") as is_api: is_api.return_value = False - result = view(request, 'rat') + result = view(request, "rat") self.assertEqual(result.status_code, 404) - def test_edit_user_page(self): - ''' there are so many views, this just makes sure it LOADS ''' + """ there are so many views, this just makes sure it LOADS """ view = views.EditUser.as_view() - request = self.factory.get('') + request = self.factory.get("") request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) - def test_edit_user(self): - ''' use a form to update a user ''' + """ use a form to update a user """ view = views.EditUser.as_view() form = forms.EditUserForm(instance=self.local_user) - form.data['name'] = 'New Name' - form.data['email'] = 'wow@email.com' - request = self.factory.post('', form.data) + form.data["name"] = "New Name" + form.data["email"] = "wow@email.com" + request = self.factory.post("", form.data) request.user = self.local_user self.assertIsNone(self.local_user.name) - with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ - as delay_mock: + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.delay" + ) as delay_mock: view(request) self.assertEqual(delay_mock.call_count, 1) - self.assertEqual(self.local_user.name, 'New Name') - self.assertEqual(self.local_user.email, 'wow@email.com') + self.assertEqual(self.local_user.name, "New Name") + self.assertEqual(self.local_user.email, "wow@email.com") + # idk how to mock the upload form, got tired of triyng to make it work + # def test_edit_user_avatar(self): + # ''' use a form to update a user ''' + # view = views.EditUser.as_view() + # form = forms.EditUserForm(instance=self.local_user) + # form.data['name'] = 'New Name' + # form.data['email'] = 'wow@email.com' + # image_file = pathlib.Path(__file__).parent.joinpath( + # '../../static/images/no_cover.jpg') + # image = Image.open(image_file) + # form.files['avatar'] = SimpleUploadedFile( + # image_file, open(image_file), content_type='image/jpeg') + # request = self.factory.post('', form.data, form.files) + # request.user = self.local_user -# idk how to mock the upload form, got tired of triyng to make it work -# def test_edit_user_avatar(self): -# ''' use a form to update a user ''' -# view = views.EditUser.as_view() -# form = forms.EditUserForm(instance=self.local_user) -# form.data['name'] = 'New Name' -# form.data['email'] = 'wow@email.com' -# image_file = pathlib.Path(__file__).parent.joinpath( -# '../../static/images/no_cover.jpg') -# image = Image.open(image_file) -# form.files['avatar'] = SimpleUploadedFile( -# image_file, open(image_file), content_type='image/jpeg') -# request = self.factory.post('', form.data, form.files) -# request.user = self.local_user - -# with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ -# as delay_mock: -# view(request) -# self.assertEqual(delay_mock.call_count, 1) -# self.assertEqual(self.local_user.name, 'New Name') -# self.assertEqual(self.local_user.email, 'wow@email.com') -# self.assertIsNotNone(self.local_user.avatar) -# self.assertEqual(self.local_user.avatar.size, (120, 120)) - + # with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \ + # as delay_mock: + # view(request) + # self.assertEqual(delay_mock.call_count, 1) + # self.assertEqual(self.local_user.name, 'New Name') + # self.assertEqual(self.local_user.email, 'wow@email.com') + # self.assertIsNotNone(self.local_user.avatar) + # self.assertEqual(self.local_user.avatar.size, (120, 120)) def test_crop_avatar(self): - ''' reduce that image size ''' + """ reduce that image size """ image_file = pathlib.Path(__file__).parent.joinpath( - '../../static/images/no_cover.jpg') + "../../static/images/no_cover.jpg" + ) image = Image.open(image_file) result = views.user.crop_avatar(image) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 1c3da3016..8aaf29516 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -1,4 +1,4 @@ -''' url routing for the app and api ''' +""" url routing for the app and api """ from django.conf.urls.static import static from django.contrib import admin from django.urls import path, re_path @@ -7,170 +7,157 @@ from django.urls import path, re_path from bookwyrm import settings, views, wellknown from bookwyrm.utils import regex -user_path = r'^user/(?P%s)' % regex.username -local_user_path = r'^user/(?P%s)' % regex.localname +user_path = r"^user/(?P%s)" % regex.username +local_user_path = r"^user/(?P%s)" % regex.localname -status_types = [ - 'status', - 'review', - 'comment', - 'quotation', - 'boost', - 'generatednote' -] -status_path = r'%s/(%s)/(?P\d+)' % \ - (user_path, '|'.join(status_types)) +status_types = ["status", "review", "comment", "quotation", "boost", "generatednote"] +status_path = r"%s/(%s)/(?P\d+)" % (user_path, "|".join(status_types)) -book_path = r'^book/(?P\d+)' +book_path = r"^book/(?P\d+)" -handler404 = 'bookwyrm.views.not_found_page' -handler500 = 'bookwyrm.views.server_error_page' +handler404 = "bookwyrm.views.not_found_page" +handler500 = "bookwyrm.views.server_error_page" urlpatterns = [ - path('admin/', admin.site.urls), - + path("admin/", admin.site.urls), # federation endpoints - re_path(r'^inbox/?$', views.Inbox.as_view()), - re_path(r'%s/inbox/?$' % local_user_path, views.Inbox.as_view()), - re_path(r'%s/outbox/?$' % local_user_path, views.Outbox.as_view()), - re_path(r'^.well-known/webfinger/?$', wellknown.webfinger), - re_path(r'^.well-known/nodeinfo/?$', wellknown.nodeinfo_pointer), - re_path(r'^nodeinfo/2\.0/?$', wellknown.nodeinfo), - re_path(r'^api/v1/instance/?$', wellknown.instance_info), - re_path(r'^api/v1/instance/peers/?$', wellknown.peers), - + re_path(r"^inbox/?$", views.Inbox.as_view()), + re_path(r"%s/inbox/?$" % local_user_path, views.Inbox.as_view()), + re_path(r"%s/outbox/?$" % local_user_path, views.Outbox.as_view()), + re_path(r"^.well-known/webfinger/?$", wellknown.webfinger), + re_path(r"^.well-known/nodeinfo/?$", wellknown.nodeinfo_pointer), + re_path(r"^nodeinfo/2\.0/?$", wellknown.nodeinfo), + re_path(r"^api/v1/instance/?$", wellknown.instance_info), + re_path(r"^api/v1/instance/peers/?$", wellknown.peers), # polling updates - re_path('^api/updates/notifications/?$', views.Updates.as_view()), - + re_path("^api/updates/notifications/?$", views.Updates.as_view()), # authentication - re_path(r'^login/?$', views.Login.as_view()), - re_path(r'^register/?$', views.Register.as_view()), - re_path(r'^logout/?$', views.Logout.as_view()), - re_path(r'^password-reset/?$', views.PasswordResetRequest.as_view()), - re_path(r'^password-reset/(?P[A-Za-z0-9]+)/?$', - views.PasswordReset.as_view()), - + re_path(r"^login/?$", views.Login.as_view()), + re_path(r"^register/?$", views.Register.as_view()), + re_path(r"^logout/?$", views.Logout.as_view()), + re_path(r"^password-reset/?$", views.PasswordResetRequest.as_view()), + re_path( + r"^password-reset/(?P[A-Za-z0-9]+)/?$", views.PasswordReset.as_view() + ), # admin - re_path(r'^settings/site-settings', - views.Site.as_view(), name='settings-site'), - re_path(r'^settings/federation', - views.Federation.as_view(), name='settings-federation'), - re_path(r'^settings/invites/?$', - views.ManageInvites.as_view(), name='settings-invites'), - re_path(r'^invite/(?P[A-Za-z0-9]+)/?$', views.Invite.as_view()), - + re_path(r"^settings/site-settings", views.Site.as_view(), name="settings-site"), + re_path( + r"^settings/federation", views.Federation.as_view(), name="settings-federation" + ), + re_path( + r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" + ), + re_path(r"^invite/(?P[A-Za-z0-9]+)/?$", views.Invite.as_view()), # landing pages - re_path(r'^about/?$', views.About.as_view()), - path('', views.Home.as_view()), - re_path(r'^discover/?$', views.Discover.as_view()), - re_path(r'^notifications/?$', views.Notifications.as_view()), - + re_path(r"^about/?$", views.About.as_view()), + path("", views.Home.as_view()), + re_path(r"^discover/?$", views.Discover.as_view()), + re_path(r"^notifications/?$", views.Notifications.as_view()), # feeds - re_path(r'^(?Phome|local|federated)/?$', views.Feed.as_view()), - re_path(r'^direct-messages/?$', views.DirectMessage.as_view()), - re_path(r'^direct-messages/(?P%s)?$' % regex.username, - views.DirectMessage.as_view()), - + re_path(r"^(?Phome|local|federated)/?$", views.Feed.as_view()), + re_path(r"^direct-messages/?$", views.DirectMessage.as_view()), + re_path( + r"^direct-messages/(?P%s)?$" % regex.username, + views.DirectMessage.as_view(), + ), # search - re_path(r'^search/?$', views.Search.as_view()), - + re_path(r"^search/?$", views.Search.as_view()), # imports - re_path(r'^import/?$', views.Import.as_view()), - re_path(r'^import/(\d+)/?$', views.ImportStatus.as_view()), - + re_path(r"^import/?$", views.Import.as_view()), + re_path(r"^import/(\d+)/?$", views.ImportStatus.as_view()), # users - re_path(r'%s/?$' % user_path, views.User.as_view(), name='user-feed'), - re_path(r'%s\.json$' % user_path, views.User.as_view()), - re_path(r'%s/rss' % user_path, views.rss_feed.RssFeed(), name='user-rss'), - re_path(r'%s/followers(.json)?/?$' % user_path, - views.Followers.as_view(), name='user-followers'), - re_path(r'%s/following(.json)?/?$' % user_path, - views.Following.as_view(), name='user-following'), - re_path(r'%s/shelves/?$' % user_path, - views.user_shelves_page, name='user-shelves'), - re_path(r'%s/lists/?$' % user_path, - views.UserLists.as_view(), name='user-lists'), - re_path(r'%s/goal/(?P\d{4})/?$' % user_path, - views.Goal.as_view(), name='user-goal'), - - + re_path(r"%s/?$" % user_path, views.User.as_view(), name="user-feed"), + re_path(r"%s\.json$" % user_path, views.User.as_view()), + re_path(r"%s/rss" % user_path, views.rss_feed.RssFeed(), name="user-rss"), + re_path( + r"%s/followers(.json)?/?$" % user_path, + views.Followers.as_view(), + name="user-followers", + ), + re_path( + r"%s/following(.json)?/?$" % user_path, + views.Following.as_view(), + name="user-following", + ), + re_path(r"%s/shelves/?$" % user_path, views.user_shelves_page, name="user-shelves"), + re_path(r"%s/lists/?$" % user_path, views.UserLists.as_view(), name="user-lists"), + re_path( + r"%s/goal/(?P\d{4})/?$" % user_path, + views.Goal.as_view(), + name="user-goal", + ), # lists - re_path(r'^list/?$', views.Lists.as_view(), name='lists'), - re_path(r'^list/(?P\d+)(.json)?/?$', - views.List.as_view(), name='list'), - re_path(r'^list/(?P\d+)/add/?$', - views.list.add_book, name='list-add-book'), - re_path(r'^list/(?P\d+)/remove/?$', - views.list.remove_book, name='list-remove-book'), - re_path(r'^list/(?P\d+)/curate/?$', - views.Curate.as_view(), name='list-curate'), - + re_path(r"^list/?$", views.Lists.as_view(), name="lists"), + re_path(r"^list/(?P\d+)(.json)?/?$", views.List.as_view(), name="list"), + re_path( + r"^list/(?P\d+)/add/?$", views.list.add_book, name="list-add-book" + ), + re_path( + r"^list/(?P\d+)/remove/?$", + views.list.remove_book, + name="list-remove-book", + ), + re_path( + r"^list/(?P\d+)/curate/?$", views.Curate.as_view(), name="list-curate" + ), # preferences - re_path(r'^preferences/profile/?$', - views.EditUser.as_view(), name='prefs-profile'), - re_path(r'^preferences/password/?$', views.ChangePassword.as_view()), - re_path(r'^preferences/block/?$', views.Block.as_view()), - re_path(r'^block/(?P\d+)/?$', views.Block.as_view()), - re_path(r'^unblock/(?P\d+)/?$', views.unblock), - + re_path(r"^preferences/profile/?$", views.EditUser.as_view(), name="prefs-profile"), + re_path(r"^preferences/password/?$", views.ChangePassword.as_view()), + re_path(r"^preferences/block/?$", views.Block.as_view()), + re_path(r"^block/(?P\d+)/?$", views.Block.as_view()), + re_path(r"^unblock/(?P\d+)/?$", views.unblock), # statuses - re_path(r'%s(.json)?/?$' % status_path, views.Status.as_view()), - re_path(r'%s/activity/?$' % status_path, views.Status.as_view()), - re_path(r'%s/replies(.json)?/?$' % status_path, views.Replies.as_view()), - re_path(r'^post/(?P\w+)/?$', views.CreateStatus.as_view()), - re_path(r'^delete-status/(?P\d+)/?$', - views.DeleteStatus.as_view()), - + re_path(r"%s(.json)?/?$" % status_path, views.Status.as_view()), + re_path(r"%s/activity/?$" % status_path, views.Status.as_view()), + re_path(r"%s/replies(.json)?/?$" % status_path, views.Replies.as_view()), + re_path(r"^post/(?P\w+)/?$", views.CreateStatus.as_view()), + re_path(r"^delete-status/(?P\d+)/?$", views.DeleteStatus.as_view()), # interact - re_path(r'^favorite/(?P\d+)/?$', views.Favorite.as_view()), - re_path(r'^unfavorite/(?P\d+)/?$', views.Unfavorite.as_view()), - re_path(r'^boost/(?P\d+)/?$', views.Boost.as_view()), - re_path(r'^unboost/(?P\d+)/?$', views.Unboost.as_view()), - + re_path(r"^favorite/(?P\d+)/?$", views.Favorite.as_view()), + re_path(r"^unfavorite/(?P\d+)/?$", views.Unfavorite.as_view()), + re_path(r"^boost/(?P\d+)/?$", views.Boost.as_view()), + re_path(r"^unboost/(?P\d+)/?$", views.Unboost.as_view()), # books - re_path(r'%s(.json)?/?$' % book_path, views.Book.as_view()), - re_path(r'%s/edit/?$' % book_path, views.EditBook.as_view()), - re_path(r'%s/editions(.json)?/?$' % book_path, views.Editions.as_view()), - re_path(r'^upload-cover/(?P\d+)/?$', views.upload_cover), - re_path(r'^add-description/(?P\d+)/?$', views.add_description), - re_path(r'^resolve-book/?$', views.resolve_book), - re_path(r'^switch-edition/?$', views.switch_edition), - + re_path(r"%s(.json)?/?$" % book_path, views.Book.as_view()), + re_path(r"%s/edit/?$" % book_path, views.EditBook.as_view()), + re_path(r"%s/editions(.json)?/?$" % book_path, views.Editions.as_view()), + re_path(r"^upload-cover/(?P\d+)/?$", views.upload_cover), + re_path(r"^add-description/(?P\d+)/?$", views.add_description), + re_path(r"^resolve-book/?$", views.resolve_book), + re_path(r"^switch-edition/?$", views.switch_edition), # isbn - re_path(r'^isbn/(?P\d+)(.json)?/?$', views.Isbn.as_view()), - + re_path(r"^isbn/(?P\d+)(.json)?/?$", views.Isbn.as_view()), # author - re_path(r'^author/(?P\d+)(.json)?/?$', views.Author.as_view()), - re_path(r'^author/(?P\d+)/edit/?$', views.EditAuthor.as_view()), - + re_path(r"^author/(?P\d+)(.json)?/?$", views.Author.as_view()), + re_path(r"^author/(?P\d+)/edit/?$", views.EditAuthor.as_view()), # tags - re_path(r'^tag/(?P.+)\.json/?$', views.Tag.as_view()), - re_path(r'^tag/(?P.+)/?$', views.Tag.as_view()), - re_path(r'^tag/?$', views.AddTag.as_view()), - re_path(r'^untag/?$', views.RemoveTag.as_view()), - + re_path(r"^tag/(?P.+)\.json/?$", views.Tag.as_view()), + re_path(r"^tag/(?P.+)/?$", views.Tag.as_view()), + re_path(r"^tag/?$", views.AddTag.as_view()), + re_path(r"^untag/?$", views.RemoveTag.as_view()), # shelf - re_path(r'^%s/shelf/(?P[\w-]+)(.json)?/?$' % \ - user_path, views.Shelf.as_view(), name='shelf'), - re_path(r'^%s/shelf/(?P[\w-]+)(.json)?/?$' % \ - local_user_path, views.Shelf.as_view()), - re_path(r'^create-shelf/?$', views.create_shelf, name='shelf-create'), - re_path(r'^delete-shelf/(?P\d+)?$', views.delete_shelf), - re_path(r'^shelve/?$', views.shelve), - re_path(r'^unshelve/?$', views.unshelve), - + re_path( + r"^%s/shelf/(?P[\w-]+)(.json)?/?$" % user_path, + views.Shelf.as_view(), + name="shelf", + ), + re_path( + r"^%s/shelf/(?P[\w-]+)(.json)?/?$" % local_user_path, + views.Shelf.as_view(), + ), + re_path(r"^create-shelf/?$", views.create_shelf, name="shelf-create"), + re_path(r"^delete-shelf/(?P\d+)?$", views.delete_shelf), + re_path(r"^shelve/?$", views.shelve), + re_path(r"^unshelve/?$", views.unshelve), # reading progress - re_path(r'^edit-readthrough/?$', views.edit_readthrough), - re_path(r'^delete-readthrough/?$', views.delete_readthrough), - re_path(r'^create-readthrough/?$', views.create_readthrough), - re_path(r'^delete-progressupdate/?$', views.delete_progressupdate), - - re_path(r'^start-reading/(?P\d+)/?$', views.start_reading), - re_path(r'^finish-reading/(?P\d+)/?$', views.finish_reading), - + re_path(r"^edit-readthrough/?$", views.edit_readthrough), + re_path(r"^delete-readthrough/?$", views.delete_readthrough), + re_path(r"^create-readthrough/?$", views.create_readthrough), + re_path(r"^delete-progressupdate/?$", views.delete_progressupdate), + re_path(r"^start-reading/(?P\d+)/?$", views.start_reading), + re_path(r"^finish-reading/(?P\d+)/?$", views.finish_reading), # following - re_path(r'^follow/?$', views.follow), - re_path(r'^unfollow/?$', views.unfollow), - re_path(r'^accept-follow-request/?$', views.accept_follow_request), - re_path(r'^delete-follow-request/?$', views.delete_follow_request), - + re_path(r"^follow/?$", views.follow), + re_path(r"^unfollow/?$", views.unfollow), + re_path(r"^accept-follow-request/?$", views.accept_follow_request), + re_path(r"^delete-follow-request/?$", views.delete_follow_request), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/bookwyrm/utils/regex.py b/bookwyrm/utils/regex.py index c818bc415..6389c35d6 100644 --- a/bookwyrm/utils/regex.py +++ b/bookwyrm/utils/regex.py @@ -1,10 +1,10 @@ -''' defining regexes for regularly used concepts ''' +""" defining regexes for regularly used concepts """ -domain = r'[\w_\-\.]+\.[a-z]{2,}' -localname = r'@?[a-zA-Z_\-\.0-9]+' -strict_localname = r'@[a-zA-Z_\-\.0-9]+' -username = r'%s(@%s)?' % (localname, domain) -strict_username = r'\B%s(@%s)?\b' % (strict_localname, domain) -full_username = r'%s@%s\b' % (localname, domain) +domain = r"[\w_\-\.]+\.[a-z]{2,}" +localname = r"@?[a-zA-Z_\-\.0-9]+" +strict_localname = r"@[a-zA-Z_\-\.0-9]+" +username = r"%s(@%s)?" % (localname, domain) +strict_username = r"\B%s(@%s)?\b" % (strict_localname, domain) +full_username = r"%s@%s\b" % (localname, domain) # should match (BookWyrm/1.0.0; or (BookWyrm/99.1.2; -bookwyrm_user_agent = r'\(BookWyrm/[0-9]+\.[0-9]+\.[0-9]+;' +bookwyrm_user_agent = r"\(BookWyrm/[0-9]+\.[0-9]+\.[0-9]+;" diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index dd601b28b..48da8ec1a 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -1,4 +1,4 @@ -''' make sure all our nice views are available ''' +""" make sure all our nice views are available """ from .authentication import Login, Register, Logout from .author import Author, EditAuthor from .block import Block, unblock diff --git a/bookwyrm/views/authentication.py b/bookwyrm/views/authentication.py index 13c9ce49e..a91475567 100644 --- a/bookwyrm/views/authentication.py +++ b/bookwyrm/views/authentication.py @@ -1,4 +1,4 @@ -''' class views for login/register views ''' +""" class views for login/register views """ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied @@ -14,60 +14,59 @@ from bookwyrm.settings import DOMAIN # pylint: disable= no-self-use -@method_decorator(csrf_exempt, name='dispatch') +@method_decorator(csrf_exempt, name="dispatch") class Login(View): - ''' authenticate an existing user ''' + """ authenticate an existing user """ + def get(self, request): - ''' login page ''' + """ login page """ if request.user.is_authenticated: - return redirect('/') + return redirect("/") # sene user to the login page data = { - 'login_form': forms.LoginForm(), - 'register_form': forms.RegisterForm(), + "login_form": forms.LoginForm(), + "register_form": forms.RegisterForm(), } - return TemplateResponse(request, 'login.html', data) + return TemplateResponse(request, "login.html", data) def post(self, request): - ''' authentication action ''' + """ authentication action """ if request.user.is_authenticated: - return redirect('/') + return redirect("/") login_form = forms.LoginForm(request.POST) - localname = login_form.data['localname'] - if '@' in localname: # looks like an email address to me + localname = login_form.data["localname"] + if "@" in localname: # looks like an email address to me email = localname try: username = models.User.objects.get(email=email) - except models.User.DoesNotExist: # maybe it's a full username? + except models.User.DoesNotExist: # maybe it's a full username? username = localname else: - username = '%s@%s' % (localname, DOMAIN) - password = login_form.data['password'] + username = "%s@%s" % (localname, DOMAIN) + password = login_form.data["password"] user = authenticate(request, username=username, password=password) if user is not None: # successful login login(request, user) user.last_active_date = timezone.now() user.save(broadcast=False) - return redirect(request.GET.get('next', '/')) + return redirect(request.GET.get("next", "/")) # login errors - login_form.non_field_errors = 'Username or password are incorrect' + login_form.non_field_errors = "Username or password are incorrect" register_form = forms.RegisterForm() - data = { - 'login_form': login_form, - 'register_form': register_form - } - return TemplateResponse(request, 'login.html', data) + data = {"login_form": login_form, "register_form": register_form} + return TemplateResponse(request, "login.html", data) class Register(View): - ''' register a user ''' + """ register a user """ + def post(self, request): - ''' join the server ''' + """ join the server """ if not models.SiteSettings.get().allow_registration: - invite_code = request.POST.get('invite_code') + invite_code = request.POST.get("invite_code") if not invite_code: raise PermissionDenied @@ -83,42 +82,43 @@ class Register(View): if not form.is_valid(): errors = True - localname = form.data['localname'].strip() - email = form.data['email'] - password = form.data['password'] + localname = form.data["localname"].strip() + email = form.data["email"] + password = form.data["password"] # check localname and email uniqueness if models.User.objects.filter(localname=localname).first(): - form.errors['localname'] = [ - 'User with this username already exists'] + form.errors["localname"] = ["User with this username already exists"] errors = True if errors: data = { - 'login_form': forms.LoginForm(), - 'register_form': form, - 'invite': invite, - 'valid': invite.valid() if invite else True, + "login_form": forms.LoginForm(), + "register_form": form, + "invite": invite, + "valid": invite.valid() if invite else True, } if invite: - return TemplateResponse(request, 'invite.html', data) - return TemplateResponse(request, 'login.html', data) + return TemplateResponse(request, "invite.html", data) + return TemplateResponse(request, "login.html", data) - username = '%s@%s' % (localname, DOMAIN) + username = "%s@%s" % (localname, DOMAIN) user = models.User.objects.create_user( - username, email, password, localname=localname, local=True) + username, email, password, localname=localname, local=True + ) if invite: invite.times_used += 1 invite.save() login(request, user) - return redirect('/') + return redirect("/") -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Logout(View): - ''' log out ''' + """ log out """ + def get(self, request): - ''' done with this place! outa here! ''' + """ done with this place! outa here! """ logout(request) - return redirect('/') + return redirect("/") diff --git a/bookwyrm/views/author.py b/bookwyrm/views/author.py index 356c95ffd..50a3588de 100644 --- a/bookwyrm/views/author.py +++ b/bookwyrm/views/author.py @@ -1,4 +1,4 @@ -''' the good people stuff! the authors! ''' +""" the good people stuff! the authors! """ from django.contrib.auth.decorators import login_required, permission_required from django.db.models import Q from django.shortcuts import get_object_or_404, redirect @@ -13,49 +13,46 @@ from .helpers import is_api_request # pylint: disable= no-self-use class Author(View): - ''' this person wrote a book ''' + """ this person wrote a book """ + def get(self, request, author_id): - ''' landing page for an author ''' + """ landing page for an author """ author = get_object_or_404(models.Author, id=author_id) if is_api_request(request): return ActivitypubResponse(author.to_activity()) books = models.Work.objects.filter( - Q(authors=author) | Q(editions__authors=author)).distinct() + Q(authors=author) | Q(editions__authors=author) + ).distinct() data = { - 'author': author, - 'books': [b.get_default_edition() for b in books], + "author": author, + "books": [b.get_default_edition() for b in books], } - return TemplateResponse(request, 'author.html', data) + return TemplateResponse(request, "author.html", data) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") @method_decorator( - permission_required('bookwyrm.edit_book', raise_exception=True), - name='dispatch') + permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch" +) class EditAuthor(View): - ''' edit author info ''' + """ edit author info """ + def get(self, request, author_id): - ''' info about a book ''' + """ info about a book """ author = get_object_or_404(models.Author, id=author_id) - data = { - 'author': author, - 'form': forms.AuthorForm(instance=author) - } - return TemplateResponse(request, 'edit_author.html', data) + data = {"author": author, "form": forms.AuthorForm(instance=author)} + return TemplateResponse(request, "edit_author.html", data) def post(self, request, author_id): - ''' edit a author cool ''' + """ edit a author cool """ author = get_object_or_404(models.Author, id=author_id) form = forms.AuthorForm(request.POST, request.FILES, instance=author) if not form.is_valid(): - data = { - 'author': author, - 'form': form - } - return TemplateResponse(request, 'edit_author.html', data) + data = {"author": author, "form": form} + return TemplateResponse(request, "edit_author.html", data) author = form.save() - return redirect('/author/%s' % author.id) + return redirect("/author/%s" % author.id) diff --git a/bookwyrm/views/block.py b/bookwyrm/views/block.py index 90e5033b5..6d6a8a58c 100644 --- a/bookwyrm/views/block.py +++ b/bookwyrm/views/block.py @@ -1,4 +1,4 @@ -''' views for actions you can take in the application ''' +""" views for actions you can take in the application """ from django.contrib.auth.decorators import login_required from django.http import HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect @@ -10,25 +10,27 @@ from django.views.decorators.http import require_POST from bookwyrm import models # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Block(View): - ''' blocking users ''' + """ blocking users """ + def get(self, request): - ''' list of blocked users? ''' - return TemplateResponse(request, 'preferences/blocks.html') + """ list of blocked users? """ + return TemplateResponse(request, "preferences/blocks.html") def post(self, request, user_id): - ''' block a user ''' + """ block a user """ to_block = get_object_or_404(models.User, id=user_id) models.UserBlocks.objects.create( - user_subject=request.user, user_object=to_block) - return redirect('/preferences/block') + user_subject=request.user, user_object=to_block + ) + return redirect("/preferences/block") @require_POST @login_required def unblock(request, user_id): - ''' undo a block ''' + """ undo a block """ to_unblock = get_object_or_404(models.User, id=user_id) try: block = models.UserBlocks.objects.get( @@ -38,4 +40,4 @@ def unblock(request, user_id): except models.UserBlocks.DoesNotExist: return HttpResponseNotFound() block.delete() - return redirect('/preferences/block') + return redirect("/preferences/block") diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py index cf246446c..46df04830 100644 --- a/bookwyrm/views/books.py +++ b/bookwyrm/views/books.py @@ -1,4 +1,4 @@ -''' the good stuff! the books! ''' +""" the good stuff! the books! """ from django.core.paginator import Paginator from django.contrib.auth.decorators import login_required, permission_required from django.db import transaction @@ -20,11 +20,12 @@ from .helpers import privacy_filter # pylint: disable= no-self-use class Book(View): - ''' a book! this is the stuff ''' + """ a book! this is the stuff """ + def get(self, request, book_id): - ''' info about a book ''' + """ info about a book """ try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 @@ -50,30 +51,28 @@ class Book(View): reviews = get_activity_feed(request.user, queryset=reviews) # the reviews to show - paginated = Paginator(reviews.exclude( - Q(content__isnull=True) | Q(content='') - ), PAGE_LENGTH) + paginated = Paginator( + reviews.exclude(Q(content__isnull=True) | Q(content="")), PAGE_LENGTH + ) reviews_page = paginated.page(page) user_tags = readthroughs = user_shelves = other_edition_shelves = [] if request.user.is_authenticated: user_tags = models.UserTag.objects.filter( book=book, user=request.user - ).values_list('tag__identifier', flat=True) + ).values_list("tag__identifier", flat=True) readthroughs = models.ReadThrough.objects.filter( user=request.user, book=book, - ).order_by('start_date') + ).order_by("start_date") for readthrough in readthroughs: - readthrough.progress_updates = \ - readthrough.progressupdate_set.all() \ - .order_by('-updated_date') + readthrough.progress_updates = ( + readthrough.progressupdate_set.all().order_by("-updated_date") + ) - user_shelves = models.ShelfBook.objects.filter( - user=request.user, book=book - ) + user_shelves = models.ShelfBook.objects.filter(user=request.user, book=book) other_edition_shelves = models.ShelfBook.objects.filter( ~Q(book=book), @@ -82,129 +81,124 @@ class Book(View): ) data = { - 'book': book, - 'reviews': reviews_page, - 'review_count': reviews.count(), - 'ratings': reviews.filter(Q(content__isnull=True) | Q(content='')), - 'rating': reviews.aggregate(Avg('rating'))['rating__avg'], - 'tags': models.UserTag.objects.filter(book=book), - 'lists': privacy_filter( + "book": book, + "reviews": reviews_page, + "review_count": reviews.count(), + "ratings": reviews.filter(Q(content__isnull=True) | Q(content="")), + "rating": reviews.aggregate(Avg("rating"))["rating__avg"], + "tags": models.UserTag.objects.filter(book=book), + "lists": privacy_filter( request.user, book.list_set.filter(listitem__approved=True) ), - 'user_tags': user_tags, - 'user_shelves': user_shelves, - 'other_edition_shelves': other_edition_shelves, - 'readthroughs': readthroughs, - 'path': '/book/%s' % book_id, + "user_tags": user_tags, + "user_shelves": user_shelves, + "other_edition_shelves": other_edition_shelves, + "readthroughs": readthroughs, + "path": "/book/%s" % book_id, } - return TemplateResponse(request, 'book.html', data) + return TemplateResponse(request, "book.html", data) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") @method_decorator( - permission_required('bookwyrm.edit_book', raise_exception=True), - name='dispatch') + permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch" +) class EditBook(View): - ''' edit a book ''' + """ edit a book """ + def get(self, request, book_id): - ''' info about a book ''' + """ info about a book """ book = get_edition(book_id) if not book.description: book.description = book.parent_work.description - data = { - 'book': book, - 'form': forms.EditionForm(instance=book) - } - return TemplateResponse(request, 'edit_book.html', data) + data = {"book": book, "form": forms.EditionForm(instance=book)} + return TemplateResponse(request, "edit_book.html", data) def post(self, request, book_id): - ''' edit a book cool ''' + """ edit a book cool """ book = get_object_or_404(models.Edition, id=book_id) form = forms.EditionForm(request.POST, request.FILES, instance=book) if not form.is_valid(): - data = { - 'book': book, - 'form': form - } - return TemplateResponse(request, 'edit_book.html', data) + data = {"book": book, "form": form} + return TemplateResponse(request, "edit_book.html", data) book = form.save() - return redirect('/book/%s' % book.id) + return redirect("/book/%s" % book.id) class Editions(View): - ''' list of editions ''' + """ list of editions """ + def get(self, request, book_id): - ''' list of editions of a book ''' + """ list of editions of a book """ work = get_object_or_404(models.Work, id=book_id) if is_api_request(request): return ActivitypubResponse(work.to_edition_list(**request.GET)) data = { - 'editions': work.editions.order_by('-edition_rank').all(), - 'work': work, + "editions": work.editions.order_by("-edition_rank").all(), + "work": work, } - return TemplateResponse(request, 'editions.html', data) + return TemplateResponse(request, "editions.html", data) @login_required @require_POST def upload_cover(request, book_id): - ''' upload a new cover ''' + """ upload a new cover """ book = get_object_or_404(models.Edition, id=book_id) form = forms.CoverForm(request.POST, request.FILES, instance=book) if not form.is_valid(): - return redirect('/book/%d' % book.id) + return redirect("/book/%d" % book.id) book.last_edited_by = request.user - book.cover = form.files['cover'] + book.cover = form.files["cover"] book.save() - return redirect('/book/%s' % book.id) + return redirect("/book/%s" % book.id) @login_required @require_POST -@permission_required('bookwyrm.edit_book', raise_exception=True) +@permission_required("bookwyrm.edit_book", raise_exception=True) def add_description(request, book_id): - ''' upload a new cover ''' - if not request.method == 'POST': - return redirect('/') + """ upload a new cover """ + if not request.method == "POST": + return redirect("/") book = get_object_or_404(models.Edition, id=book_id) - description = request.POST.get('description') + description = request.POST.get("description") book.description = description book.last_edited_by = request.user book.save() - return redirect('/book/%s' % book.id) + return redirect("/book/%s" % book.id) @require_POST def resolve_book(request): - ''' figure out the local path to a book from a remote_id ''' - remote_id = request.POST.get('remote_id') + """ figure out the local path to a book from a remote_id """ + remote_id = request.POST.get("remote_id") connector = connector_manager.get_or_create_connector(remote_id) book = connector.get_or_create_book(remote_id) - return redirect('/book/%d' % book.id) + return redirect("/book/%d" % book.id) @login_required @require_POST @transaction.atomic def switch_edition(request): - ''' switch your copy of a book to a different edition ''' - edition_id = request.POST.get('edition') + """ switch your copy of a book to a different edition """ + edition_id = request.POST.get("edition") new_edition = get_object_or_404(models.Edition, id=edition_id) shelfbooks = models.ShelfBook.objects.filter( - book__parent_work=new_edition.parent_work, - shelf__user=request.user + book__parent_work=new_edition.parent_work, shelf__user=request.user ) for shelfbook in shelfbooks.all(): with transaction.atomic(): @@ -212,16 +206,15 @@ def switch_edition(request): created_date=shelfbook.created_date, user=shelfbook.user, shelf=shelfbook.shelf, - book=new_edition + book=new_edition, ) shelfbook.delete() readthroughs = models.ReadThrough.objects.filter( - book__parent_work=new_edition.parent_work, - user=request.user + book__parent_work=new_edition.parent_work, user=request.user ) for readthrough in readthroughs.all(): readthrough.book = new_edition readthrough.save() - return redirect('/book/%d' % new_edition.id) + return redirect("/book/%d" % new_edition.id) diff --git a/bookwyrm/views/error.py b/bookwyrm/views/error.py index 82999d6ed..9bd94ca32 100644 --- a/bookwyrm/views/error.py +++ b/bookwyrm/views/error.py @@ -1,11 +1,12 @@ -''' something has gone amiss ''' +""" something has gone amiss """ from django.template.response import TemplateResponse + def server_error_page(request): - ''' 500 errors ''' - return TemplateResponse(request, 'error.html', status=500) + """ 500 errors """ + return TemplateResponse(request, "error.html", status=500) def not_found_page(request, _): - ''' 404s ''' - return TemplateResponse(request, 'notfound.html', status=404) + """ 404s """ + return TemplateResponse(request, "notfound.html", status=404) diff --git a/bookwyrm/views/federation.py b/bookwyrm/views/federation.py index 62ae076cd..e9c0466e4 100644 --- a/bookwyrm/views/federation.py +++ b/bookwyrm/views/federation.py @@ -1,4 +1,4 @@ -''' manage federated servers ''' +""" manage federated servers """ from django.contrib.auth.decorators import login_required, permission_required from django.template.response import TemplateResponse from django.utils.decorators import method_decorator @@ -8,14 +8,16 @@ from bookwyrm import models # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") @method_decorator( - permission_required('bookwyrm.control_federation', raise_exception=True), - name='dispatch') + permission_required("bookwyrm.control_federation", raise_exception=True), + name="dispatch", +) class Federation(View): - ''' what servers do we federate with ''' + """ what servers do we federate with """ + def get(self, request): - ''' edit form ''' + """ edit form """ servers = models.FederatedServer.objects.all() - data = {'servers': servers} - return TemplateResponse(request, 'settings/federation.html', data) + data = {"servers": servers} + return TemplateResponse(request, "settings/federation.html", data) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index f7e93e9a3..d08c9a42c 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -1,4 +1,4 @@ -''' non-interactive pages ''' +""" non-interactive pages """ from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import Q @@ -17,48 +17,54 @@ from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Feed(View): - ''' activity stream ''' + """ activity stream """ + def get(self, request, tab): - ''' user's homepage with activity feed ''' + """ user's homepage with activity feed """ try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 - if tab == 'home': + if tab == "home": + activities = get_activity_feed(request.user, following_only=True) + tab_title = _("Home") + elif tab == "local": activities = get_activity_feed( - request.user, following_only=True) - tab_title = _('Home') - elif tab == 'local': - activities = get_activity_feed( - request.user, privacy=['public', 'followers'], local_only=True) - tab_title = _('Local') + request.user, privacy=["public", "followers"], local_only=True + ) + tab_title = _("Local") else: activities = get_activity_feed( - request.user, privacy=['public', 'followers']) - tab_title = _('Federated') + request.user, privacy=["public", "followers"] + ) + tab_title = _("Federated") paginated = Paginator(activities, PAGE_LENGTH) - data = {**feed_page_data(request.user), **{ - 'user': request.user, - 'activities': paginated.page(page), - 'tab': tab, - 'tab_title': tab_title, - 'goal_form': forms.GoalForm(), - 'path': '/%s' % tab, - }} - return TemplateResponse(request, 'feed/feed.html', data) + data = { + **feed_page_data(request.user), + **{ + "user": request.user, + "activities": paginated.page(page), + "tab": tab, + "tab_title": tab_title, + "goal_form": forms.GoalForm(), + "path": "/%s" % tab, + }, + } + return TemplateResponse(request, "feed/feed.html", data) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class DirectMessage(View): - ''' dm view ''' + """ dm view """ + def get(self, request, username=None): - ''' like a feed but for dms only ''' + """ like a feed but for dms only """ try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 @@ -74,27 +80,33 @@ class DirectMessage(View): queryset = queryset.filter(Q(user=user) | Q(mention_users=user)) activities = get_activity_feed( - request.user, privacy=['direct'], queryset=queryset) + request.user, privacy=["direct"], queryset=queryset + ) paginated = Paginator(activities, PAGE_LENGTH) activity_page = paginated.page(page) - data = {**feed_page_data(request.user), **{ - 'user': request.user, - 'partner': user, - 'activities': activity_page, - 'path': '/direct-messages', - }} - return TemplateResponse(request, 'feed/direct_messages.html', data) + data = { + **feed_page_data(request.user), + **{ + "user": request.user, + "partner": user, + "activities": activity_page, + "path": "/direct-messages", + }, + } + return TemplateResponse(request, "feed/direct_messages.html", data) class Status(View): - ''' get posting ''' + """ get posting """ + def get(self, request, username, status_id): - ''' display a particular status (and replies, etc) ''' + """ display a particular status (and replies, etc) """ try: user = get_user_from_username(request.user, username) status = models.Status.objects.select_subclasses().get( - id=status_id, deleted=False) + id=status_id, deleted=False + ) except ValueError: return HttpResponseNotFound() @@ -108,18 +120,23 @@ class Status(View): if is_api_request(request): return ActivitypubResponse( - status.to_activity(pure=not is_bookwyrm_request(request))) + status.to_activity(pure=not is_bookwyrm_request(request)) + ) - data = {**feed_page_data(request.user), **{ - 'status': status, - }} - return TemplateResponse(request, 'feed/status.html', data) + data = { + **feed_page_data(request.user), + **{ + "status": status, + }, + } + return TemplateResponse(request, "feed/status.html", data) class Replies(View): - ''' replies page (a json view of status) ''' + """ replies page (a json view of status) """ + def get(self, request, username, status_id): - ''' ordered collection of replies to a status ''' + """ ordered collection of replies to a status """ # the html view is the same as Status if not is_api_request(request): status_view = Status.as_view() @@ -134,41 +151,39 @@ class Replies(View): def feed_page_data(user): - ''' info we need for every feed page ''' + """ info we need for every feed page """ if not user.is_authenticated: return {} - goal = models.AnnualGoal.objects.filter( - user=user, year=timezone.now().year - ).first() + goal = models.AnnualGoal.objects.filter(user=user, year=timezone.now().year).first() return { - 'suggested_books': get_suggested_books(user), - 'goal': goal, - 'goal_form': forms.GoalForm(), + "suggested_books": get_suggested_books(user), + "goal": goal, + "goal_form": forms.GoalForm(), } + def get_suggested_books(user, max_books=5): - ''' helper to get a user's recent books ''' + """ helper to get a user's recent books """ book_count = 0 - preset_shelves = [ - ('reading', max_books), ('read', 2), ('to-read', max_books) - ] + preset_shelves = [("reading", max_books), ("read", 2), ("to-read", max_books)] suggested_books = [] for (preset, shelf_max) in preset_shelves: - limit = shelf_max if shelf_max < (max_books - book_count) \ - else max_books - book_count + limit = ( + shelf_max + if shelf_max < (max_books - book_count) + else max_books - book_count + ) shelf = user.shelf_set.get(identifier=preset) - shelf_books = shelf.shelfbook_set.order_by( - '-updated_date' - ).all()[:limit] + shelf_books = shelf.shelfbook_set.order_by("-updated_date").all()[:limit] if not shelf_books: continue shelf_preview = { - 'name': shelf.name, - 'identifier': shelf.identifier, - 'books': [s.book for s in shelf_books] + "name": shelf.name, + "identifier": shelf.identifier, + "books": [s.book for s in shelf_books], } suggested_books.append(shelf_preview) - book_count += len(shelf_preview['books']) + book_count += len(shelf_preview["books"]) return suggested_books diff --git a/bookwyrm/views/follow.py b/bookwyrm/views/follow.py index 4c69890c9..515bf3251 100644 --- a/bookwyrm/views/follow.py +++ b/bookwyrm/views/follow.py @@ -1,4 +1,4 @@ -''' views for actions you can take in the application ''' +""" views for actions you can take in the application """ from django.contrib.auth.decorators import login_required from django.db import IntegrityError from django.http import HttpResponseBadRequest @@ -8,11 +8,12 @@ from django.views.decorators.http import require_POST from bookwyrm import models from .helpers import get_user_from_username + @login_required @require_POST def follow(request): - ''' follow another user, here or abroad ''' - username = request.POST['user'] + """ follow another user, here or abroad """ + username = request.POST["user"] try: to_follow = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -32,16 +33,15 @@ def follow(request): @login_required @require_POST def unfollow(request): - ''' unfollow a user ''' - username = request.POST['user'] + """ unfollow a user """ + username = request.POST["user"] try: to_unfollow = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() models.UserFollows.objects.get( - user_subject=request.user, - user_object=to_unfollow + user_subject=request.user, user_object=to_unfollow ).delete() return redirect(to_unfollow.local_path) @@ -49,8 +49,8 @@ def unfollow(request): @login_required @require_POST def accept_follow_request(request): - ''' a user accepts a follow request ''' - username = request.POST['user'] + """ a user accepts a follow request """ + username = request.POST["user"] try: requester = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -58,8 +58,7 @@ def accept_follow_request(request): try: follow_request = models.UserFollowRequest.objects.get( - user_subject=requester, - user_object=request.user + user_subject=requester, user_object=request.user ) except models.UserFollowRequest.DoesNotExist: # Request already dealt with. @@ -72,8 +71,8 @@ def accept_follow_request(request): @login_required @require_POST def delete_follow_request(request): - ''' a user rejects a follow request ''' - username = request.POST['user'] + """ a user rejects a follow request """ + username = request.POST["user"] try: requester = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -81,11 +80,10 @@ def delete_follow_request(request): try: follow_request = models.UserFollowRequest.objects.get( - user_subject=requester, - user_object=request.user + user_subject=requester, user_object=request.user ) except models.UserFollowRequest.DoesNotExist: return HttpResponseBadRequest() follow_request.delete() - return redirect('/user/%s' % request.user.localname) + return redirect("/user/%s" % request.user.localname) diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py index 7da9e4343..4e3a9534c 100644 --- a/bookwyrm/views/goal.py +++ b/bookwyrm/views/goal.py @@ -1,4 +1,4 @@ -''' non-interactive pages ''' +""" non-interactive pages """ from django.contrib.auth.decorators import login_required from django.http import HttpResponseNotFound from django.shortcuts import redirect @@ -13,16 +13,15 @@ from .helpers import get_user_from_username, object_visible_to_user # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Goal(View): - ''' track books for the year ''' + """ track books for the year """ + def get(self, request, username, year): - ''' reading goal page ''' + """ reading goal page """ user = get_user_from_username(request.user, username) year = int(year) - goal = models.AnnualGoal.objects.filter( - year=year, user=user - ).first() + goal = models.AnnualGoal.objects.filter(year=year, user=user).first() if not goal and user != request.user: return HttpResponseNotFound() @@ -30,42 +29,39 @@ class Goal(View): return HttpResponseNotFound() data = { - 'goal_form': forms.GoalForm(instance=goal), - 'goal': goal, - 'user': user, - 'year': year, - 'is_self': request.user == user, + "goal_form": forms.GoalForm(instance=goal), + "goal": goal, + "user": user, + "year": year, + "is_self": request.user == user, } - return TemplateResponse(request, 'goal.html', data) - + return TemplateResponse(request, "goal.html", data) def post(self, request, username, year): - ''' update or create an annual goal ''' + """ update or create an annual goal """ user = get_user_from_username(request.user, username) if user != request.user: return HttpResponseNotFound() year = int(year) - goal = models.AnnualGoal.objects.filter( - year=year, user=request.user - ).first() + goal = models.AnnualGoal.objects.filter(year=year, user=request.user).first() form = forms.GoalForm(request.POST, instance=goal) if not form.is_valid(): data = { - 'goal_form': form, - 'goal': goal, - 'year': year, + "goal_form": form, + "goal": goal, + "year": year, } - return TemplateResponse(request, 'goal.html', data) + return TemplateResponse(request, "goal.html", data) goal = form.save() - if request.POST.get('post-status'): + if request.POST.get("post-status"): # create status, if appropraite - template = get_template('snippets/generated_status/goal.html') + template = get_template("snippets/generated_status/goal.html") create_generated_note( request.user, - template.render({'goal': goal, 'user': request.user}).strip(), - privacy=goal.privacy + template.render({"goal": goal, "user": request.user}).strip(), + privacy=goal.privacy, ) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 64f0fc267..20332cbda 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -1,4 +1,4 @@ -''' helper functions used in various views ''' +""" helper functions used in various views """ import re from requests import HTTPError from django.core.exceptions import FieldError @@ -11,7 +11,7 @@ from bookwyrm.utils import regex def get_user_from_username(viewer, username): - ''' helper function to resolve a localname or a username to a user ''' + """ helper function to resolve a localname or a username to a user """ # raises DoesNotExist if user is now found try: return models.User.viewer_aware_objects(viewer).get(localname=username) @@ -20,22 +20,20 @@ def get_user_from_username(viewer, username): def is_api_request(request): - ''' check whether a request is asking for html or data ''' - return 'json' in request.headers.get('Accept') or \ - request.path[-5:] == '.json' + """ check whether a request is asking for html or data """ + return "json" in request.headers.get("Accept") or request.path[-5:] == ".json" def is_bookwyrm_request(request): - ''' check if the request is coming from another bookwyrm instance ''' - user_agent = request.headers.get('User-Agent') - if user_agent is None or \ - re.search(regex.bookwyrm_user_agent, user_agent) is None: + """ check if the request is coming from another bookwyrm instance """ + user_agent = request.headers.get("User-Agent") + if user_agent is None or re.search(regex.bookwyrm_user_agent, user_agent) is None: return False return True def object_visible_to_user(viewer, obj): - ''' is a user authorized to view an object? ''' + """ is a user authorized to view an object? """ if not obj: return False @@ -44,37 +42,32 @@ def object_visible_to_user(viewer, obj): return False # you can see your own posts and any public or unlisted posts - if viewer == obj.user or obj.privacy in ['public', 'unlisted']: + if viewer == obj.user or obj.privacy in ["public", "unlisted"]: return True # you can see the followers only posts of people you follow - if obj.privacy == 'followers' and \ - obj.user.followers.filter(id=viewer.id).first(): + if obj.privacy == "followers" and obj.user.followers.filter(id=viewer.id).first(): return True # you can see dms you are tagged in if isinstance(obj, models.Status): - if obj.privacy == 'direct' and \ - obj.mention_users.filter(id=viewer.id).first(): + if obj.privacy == "direct" and obj.mention_users.filter(id=viewer.id).first(): return True return False def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): - ''' filter objects that have "user" and "privacy" fields ''' - privacy_levels = privacy_levels or \ - ['public', 'unlisted', 'followers', 'direct'] + """ filter objects that have "user" and "privacy" fields """ + privacy_levels = privacy_levels or ["public", "unlisted", "followers", "direct"] # exclude blocks from both directions if not viewer.is_anonymous: blocked = models.User.objects.filter(id__in=viewer.blocks.all()).all() - queryset = queryset.exclude( - Q(user__in=blocked) | Q(user__blocks=viewer)) + queryset = queryset.exclude(Q(user__in=blocked) | Q(user__blocks=viewer)) # you can't see followers only or direct messages if you're not logged in if viewer.is_anonymous: - privacy_levels = [p for p in privacy_levels if \ - not p in ['followers', 'direct']] + privacy_levels = [p for p in privacy_levels if not p in ["followers", "direct"]] # filter to only privided privacy levels queryset = queryset.filter(privacy__in=privacy_levels) @@ -82,53 +75,48 @@ def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): # only include statuses the user follows if following_only: queryset = queryset.exclude( - ~Q(# remove everythign except - Q(user__in=viewer.following.all()) | # user following - Q(user=viewer) |# is self - Q(mention_users=viewer)# mentions user + ~Q( # remove everythign except + Q(user__in=viewer.following.all()) + | Q(user=viewer) # user following + | Q(mention_users=viewer) # is self # mentions user ), ) # exclude followers-only statuses the user doesn't follow - elif 'followers' in privacy_levels: + elif "followers" in privacy_levels: queryset = queryset.exclude( - ~Q(# user isn't following and it isn't their own status + ~Q( # user isn't following and it isn't their own status Q(user__in=viewer.following.all()) | Q(user=viewer) ), - privacy='followers' # and the status is followers only + privacy="followers", # and the status is followers only ) # exclude direct messages not intended for the user - if 'direct' in privacy_levels: + if "direct" in privacy_levels: try: queryset = queryset.exclude( - ~Q( - Q(user=viewer) | Q(mention_users=viewer) - ), privacy='direct' + ~Q(Q(user=viewer) | Q(mention_users=viewer)), privacy="direct" ) except FieldError: - queryset = queryset.exclude( - ~Q(user=viewer), privacy='direct' - ) + queryset = queryset.exclude(~Q(user=viewer), privacy="direct") return queryset def get_activity_feed( - user, privacy=None, local_only=False, following_only=False, - queryset=None): - ''' get a filtered queryset of statuses ''' + user, privacy=None, local_only=False, following_only=False, queryset=None +): + """ get a filtered queryset of statuses """ if queryset is None: queryset = models.Status.objects.select_subclasses() # exclude deleted - queryset = queryset.exclude(deleted=True).order_by('-published_date') + queryset = queryset.exclude(deleted=True).order_by("-published_date") # apply privacy filters - queryset = privacy_filter( - user, queryset, privacy, following_only=following_only) + queryset = privacy_filter(user, queryset, privacy, following_only=following_only) # only show dms if we only want dms - if privacy == ['direct']: + if privacy == ["direct"]: # dms are direct statuses not related to books queryset = queryset.filter( review__isnull=True, @@ -143,7 +131,7 @@ def get_activity_feed( comment__isnull=True, quotation__isnull=True, generatednote__isnull=True, - privacy='direct' + privacy="direct", ) except FieldError: # if we're looking at a subtype of Status (like Review) @@ -163,36 +151,35 @@ def get_activity_feed( def handle_remote_webfinger(query): - ''' webfingerin' other servers ''' + """ webfingerin' other servers """ user = None # usernames could be @user@domain or user@domain if not query: return None - if query[0] == '@': + if query[0] == "@": query = query[1:] try: - domain = query.split('@')[1] + domain = query.split("@")[1] except IndexError: return None try: user = models.User.objects.get(username=query) except models.User.DoesNotExist: - url = 'https://%s/.well-known/webfinger?resource=acct:%s' % \ - (domain, query) + url = "https://%s/.well-known/webfinger?resource=acct:%s" % (domain, query) try: data = get_data(url) except (ConnectorException, HTTPError): return None - for link in data.get('links'): - if link.get('rel') == 'self': + for link in data.get("links"): + if link.get("rel") == "self": try: user = activitypub.resolve_remote_id( - link['href'], model=models.User + link["href"], model=models.User ) except KeyError: return None @@ -200,7 +187,7 @@ def handle_remote_webfinger(query): def get_edition(book_id): - ''' look up a book in the db and return an edition ''' + """ look up a book in the db and return an edition """ book = models.Book.objects.select_subclasses().get(id=book_id) if isinstance(book, models.Work): book = book.get_default_edition() @@ -208,29 +195,24 @@ def get_edition(book_id): def handle_reading_status(user, shelf, book, privacy): - ''' post about a user reading a book ''' + """ post about a user reading a book """ # tell the world about this cool thing that happened try: message = { - 'to-read': 'wants to read', - 'reading': 'started reading', - 'read': 'finished reading' + "to-read": "wants to read", + "reading": "started reading", + "read": "finished reading", }[shelf.identifier] except KeyError: # it's a non-standard shelf, don't worry about it return - status = create_generated_note( - user, - message, - mention_books=[book], - privacy=privacy - ) + status = create_generated_note(user, message, mention_books=[book], privacy=privacy) status.save() def is_blocked(viewer, user): - ''' is this viewer blocked by the user? ''' + """ is this viewer blocked by the user? """ if viewer.is_authenticated and viewer in user.blocks.all(): return True return False diff --git a/bookwyrm/views/import_data.py b/bookwyrm/views/import_data.py index cf33163ab..8f9ea27fd 100644 --- a/bookwyrm/views/import_data.py +++ b/bookwyrm/views/import_data.py @@ -1,4 +1,4 @@ -''' import books from another app ''' +""" import books from another app """ from io import TextIOWrapper from django.contrib.auth.decorators import login_required @@ -13,27 +13,33 @@ from bookwyrm import forms, goodreads_import, librarything_import, models from bookwyrm.tasks import app # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Import(View): - ''' import view ''' + """ import view """ + def get(self, request): - ''' load import page ''' - return TemplateResponse(request, 'import.html', { - 'import_form': forms.ImportForm(), - 'jobs': models.ImportJob. - objects.filter(user=request.user).order_by('-created_date'), - }) + """ load import page """ + return TemplateResponse( + request, + "import.html", + { + "import_form": forms.ImportForm(), + "jobs": models.ImportJob.objects.filter(user=request.user).order_by( + "-created_date" + ), + }, + ) def post(self, request): - ''' ingest a goodreads csv ''' + """ ingest a goodreads csv """ form = forms.ImportForm(request.POST, request.FILES) if form.is_valid(): - include_reviews = request.POST.get('include_reviews') == 'on' - privacy = request.POST.get('privacy') - source = request.POST.get('source') + include_reviews = request.POST.get("include_reviews") == "on" + privacy = request.POST.get("privacy") + source = request.POST.get("source") importer = None - if source == 'LibraryThing': + if source == "LibraryThing": importer = librarything_import.LibrarythingImporter() else: # Default : GoodReads @@ -43,44 +49,44 @@ class Import(View): job = importer.create_job( request.user, TextIOWrapper( - request.FILES['csv_file'], - encoding=importer.encoding), + request.FILES["csv_file"], encoding=importer.encoding + ), include_reviews, privacy, ) except (UnicodeDecodeError, ValueError): - return HttpResponseBadRequest('Not a valid csv file') + return HttpResponseBadRequest("Not a valid csv file") importer.start_import(job) - return redirect('/import/%d' % job.id) + return redirect("/import/%d" % job.id) return HttpResponseBadRequest() -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class ImportStatus(View): - ''' status of an existing import ''' + """ status of an existing import """ + def get(self, request, job_id): - ''' status of an import job ''' + """ status of an import job """ job = models.ImportJob.objects.get(id=job_id) if job.user != request.user: raise PermissionDenied task = app.AsyncResult(job.task_id) - items = job.items.order_by('index').all() + items = job.items.order_by("index").all() failed_items = [i for i in items if i.fail_reason] items = [i for i in items if not i.fail_reason] - return TemplateResponse(request, 'import_status.html', { - 'job': job, - 'items': items, - 'failed_items': failed_items, - 'task': task - }) + return TemplateResponse( + request, + "import_status.html", + {"job": job, "items": items, "failed_items": failed_items, "task": task}, + ) def post(self, request, job_id): - ''' retry lines from an import ''' + """ retry lines from an import """ job = get_object_or_404(models.ImportJob, id=job_id) items = [] - for item in request.POST.getlist('import_item'): + for item in request.POST.getlist("import_item"): items.append(get_object_or_404(models.ImportItem, id=item)) job = goodreads_import.create_retry_job( @@ -89,4 +95,4 @@ class ImportStatus(View): items, ) goodreads_import.start_import(job) - return redirect('/import/%d' % job.id) + return redirect("/import/%d" % job.id) diff --git a/bookwyrm/views/inbox.py b/bookwyrm/views/inbox.py index 46385093c..34bd2e1cc 100644 --- a/bookwyrm/views/inbox.py +++ b/bookwyrm/views/inbox.py @@ -1,4 +1,4 @@ -''' incoming activities ''' +""" incoming activities """ import json from urllib.parse import urldefrag @@ -14,12 +14,13 @@ from bookwyrm.tasks import app from bookwyrm.signatures import Signature -@method_decorator(csrf_exempt, name='dispatch') +@method_decorator(csrf_exempt, name="dispatch") # pylint: disable=no-self-use class Inbox(View): - ''' requests sent by outside servers''' + """ requests sent by outside servers""" + def post(self, request, username=None): - ''' only works as POST request ''' + """ only works as POST request """ # make sure the user's inbox even exists if username: try: @@ -33,14 +34,16 @@ class Inbox(View): except json.decoder.JSONDecodeError: return HttpResponseBadRequest() - if not 'object' in activity_json or \ - not 'type' in activity_json or \ - not activity_json['type'] in activitypub.activity_objects: + if ( + not "object" in activity_json + or not "type" in activity_json + or not activity_json["type"] in activitypub.activity_objects + ): return HttpResponseNotFound() # verify the signature if not has_valid_signature(request, activity_json): - if activity_json['type'] == 'Delete': + if activity_json["type"] == "Delete": # Pretend that unauth'd deletes succeed. Auth may be failing # because the resource or owner of the resource might have # been deleted. @@ -53,7 +56,7 @@ class Inbox(View): @app.task def activity_task(activity_json): - ''' do something with this json we think is legit ''' + """ do something with this json we think is legit """ # lets see if the activitypub module can make sense of this json try: activity = activitypub.parse(activity_json) @@ -70,16 +73,15 @@ def activity_task(activity_json): def has_valid_signature(request, activity): - ''' verify incoming signature ''' + """ verify incoming signature """ try: signature = Signature.parse(request) key_actor = urldefrag(signature.key_id).url - if key_actor != activity.get('actor'): + if key_actor != activity.get("actor"): raise ValueError("Wrong actor created signature.") - remote_user = activitypub.resolve_remote_id( - key_actor, model=models.User) + remote_user = activitypub.resolve_remote_id(key_actor, model=models.User) if not remote_user: return False @@ -91,7 +93,7 @@ def has_valid_signature(request, activity): remote_user.remote_id, model=models.User, refresh=True ) if remote_user.key_pair.public_key == old_key: - raise # Key unchanged. + raise # Key unchanged. signature.verify(remote_user.key_pair.public_key, request) except (ValueError, requests.exceptions.HTTPError): return False diff --git a/bookwyrm/views/interaction.py b/bookwyrm/views/interaction.py index a7fcc231f..e337f2ef6 100644 --- a/bookwyrm/views/interaction.py +++ b/bookwyrm/views/interaction.py @@ -1,4 +1,4 @@ -''' boosts and favs ''' +""" boosts and favs """ from django.db import IntegrityError from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest, HttpResponseNotFound @@ -10,75 +10,74 @@ from bookwyrm import models # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Favorite(View): - ''' like a status ''' + """ like a status """ + def post(self, request, status_id): - ''' create a like ''' + """ create a like """ status = models.Status.objects.get(id=status_id) try: - models.Favorite.objects.create( - status=status, - user=request.user - ) + models.Favorite.objects.create(status=status, user=request.user) except IntegrityError: # you already fav'ed that return HttpResponseBadRequest() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Unfavorite(View): - ''' take back a fav ''' + """ take back a fav """ + def post(self, request, status_id): - ''' unlike a status ''' + """ unlike a status """ status = models.Status.objects.get(id=status_id) try: - favorite = models.Favorite.objects.get( - status=status, - user=request.user - ) + favorite = models.Favorite.objects.get(status=status, user=request.user) except models.Favorite.DoesNotExist: # can't find that status, idk return HttpResponseNotFound() favorite.delete() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Boost(View): - ''' boost a status ''' + """ boost a status """ + def post(self, request, status_id): - ''' boost a status ''' + """ boost a status """ status = models.Status.objects.get(id=status_id) # is it boostable? if not status.boostable: return HttpResponseBadRequest() if models.Boost.objects.filter( - boosted_status=status, user=request.user).exists(): + boosted_status=status, user=request.user + ).exists(): # you already boosted that. - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) models.Boost.objects.create( boosted_status=status, privacy=status.privacy, user=request.user, ) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Unboost(View): - ''' boost a status ''' + """ boost a status """ + def post(self, request, status_id): - ''' boost a status ''' + """ boost a status """ status = models.Status.objects.get(id=status_id) boost = models.Boost.objects.filter( boosted_status=status, user=request.user ).first() boost.delete() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index 750a5c2b9..1f1ccaf11 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -1,4 +1,4 @@ -''' invites when registration is closed ''' +""" invites when registration is closed """ from django.contrib.auth.decorators import login_required, permission_required from django.core.paginator import Paginator from django.http import HttpResponseBadRequest @@ -12,31 +12,36 @@ from bookwyrm.settings import PAGE_LENGTH # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") @method_decorator( - permission_required('bookwyrm.create_invites', raise_exception=True), - name='dispatch') + permission_required("bookwyrm.create_invites", raise_exception=True), + name="dispatch", +) class ManageInvites(View): - ''' create invites ''' + """ create invites """ + def get(self, request): - ''' invite management page ''' + """ invite management page """ try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 - paginated = Paginator(models.SiteInvite.objects.filter( - user=request.user - ).order_by('-created_date'), PAGE_LENGTH) + paginated = Paginator( + models.SiteInvite.objects.filter(user=request.user).order_by( + "-created_date" + ), + PAGE_LENGTH, + ) data = { - 'invites': paginated.page(page), - 'form': forms.CreateInviteForm(), + "invites": paginated.page(page), + "form": forms.CreateInviteForm(), } - return TemplateResponse(request, 'settings/manage_invites.html', data) + return TemplateResponse(request, "settings/manage_invites.html", data) def post(self, request): - ''' creates an invite database entry ''' + """ creates an invite database entry """ form = forms.CreateInviteForm(request.POST) if not form.is_valid(): return HttpResponseBadRequest("ERRORS : %s" % (form.errors,)) @@ -45,29 +50,30 @@ class ManageInvites(View): invite.user = request.user invite.save() - paginated = Paginator(models.SiteInvite.objects.filter( - user=request.user - ).order_by('-created_date'), PAGE_LENGTH) - data = { - 'invites': paginated.page(1), - 'form': form - } - return TemplateResponse(request, 'settings/manage_invites.html', data) + paginated = Paginator( + models.SiteInvite.objects.filter(user=request.user).order_by( + "-created_date" + ), + PAGE_LENGTH, + ) + data = {"invites": paginated.page(1), "form": form} + return TemplateResponse(request, "settings/manage_invites.html", data) class Invite(View): - ''' use an invite to register ''' + """ use an invite to register """ + def get(self, request, code): - ''' endpoint for using an invites ''' + """ endpoint for using an invites """ if request.user.is_authenticated: - return redirect('/') + return redirect("/") invite = get_object_or_404(models.SiteInvite, code=code) data = { - 'register_form': forms.RegisterForm(), - 'invite': invite, - 'valid': invite.valid() if invite else True, + "register_form": forms.RegisterForm(), + "invite": invite, + "valid": invite.valid() if invite else True, } - return TemplateResponse(request, 'invite.html', data) + return TemplateResponse(request, "invite.html", data) # post handling is in views.authentication.Register diff --git a/bookwyrm/views/isbn.py b/bookwyrm/views/isbn.py index e5539ba3a..b7ba02dd8 100644 --- a/bookwyrm/views/isbn.py +++ b/bookwyrm/views/isbn.py @@ -1,4 +1,4 @@ -''' isbn search view ''' +""" isbn search view """ from django.http import HttpResponseNotFound from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect @@ -13,17 +13,18 @@ from .helpers import is_api_request # pylint: disable= no-self-use class Isbn(View): - ''' search a book by isbn ''' + """ search a book by isbn """ + def get(self, request, isbn): - ''' info about a book ''' + """ info about a book """ book_results = connector_manager.isbn_local_search(isbn) if is_api_request(request): return JsonResponse([r.json() for r in book_results], safe=False) data = { - 'title': 'ISBN Search Results', - 'results': book_results, - 'query': isbn, + "title": "ISBN Search Results", + "results": book_results, + "query": isbn, } - return TemplateResponse(request, 'isbn_search_results.html', data) + return TemplateResponse(request, "isbn_search_results.html", data) diff --git a/bookwyrm/views/landing.py b/bookwyrm/views/landing.py index 94b27b8fc..2c4a51478 100644 --- a/bookwyrm/views/landing.py +++ b/bookwyrm/views/landing.py @@ -1,4 +1,4 @@ -''' non-interactive pages ''' +""" non-interactive pages """ from django.db.models import Max from django.template.response import TemplateResponse from django.views import View @@ -9,38 +9,44 @@ from .feed import Feed # pylint: disable= no-self-use class About(View): - ''' create invites ''' + """ create invites """ + def get(self, request): - ''' more information about the instance ''' - return TemplateResponse(request, 'discover/about.html') + """ more information about the instance """ + return TemplateResponse(request, "discover/about.html") + class Home(View): - ''' discover page or home feed depending on auth ''' + """ discover page or home feed depending on auth """ + def get(self, request): - ''' this is the same as the feed on the home tab ''' + """ this is the same as the feed on the home tab """ if request.user.is_authenticated: feed_view = Feed.as_view() - return feed_view(request, 'home') + return feed_view(request, "home") discover_view = Discover.as_view() return discover_view(request) + class Discover(View): - ''' preview of recently reviewed books ''' + """ preview of recently reviewed books """ + def get(self, request): - ''' tiled book activity page ''' - books = models.Edition.objects.filter( - review__published_date__isnull=False, - review__deleted=False, - review__user__local=True, - review__privacy__in=['public', 'unlisted'], - ).exclude( - cover__exact='' - ).annotate( - Max('review__published_date') - ).order_by('-review__published_date__max')[:6] + """ tiled book activity page """ + books = ( + models.Edition.objects.filter( + review__published_date__isnull=False, + review__deleted=False, + review__user__local=True, + review__privacy__in=["public", "unlisted"], + ) + .exclude(cover__exact="") + .annotate(Max("review__published_date")) + .order_by("-review__published_date__max")[:6] + ) data = { - 'register_form': forms.RegisterForm(), - 'books': list(set(books)), + "register_form": forms.RegisterForm(), + "books": list(set(books)), } - return TemplateResponse(request, 'discover/discover.html', data) + return TemplateResponse(request, "discover/discover.html", data) diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index b1741b117..ba3200d1e 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -1,4 +1,4 @@ -''' book list views''' +""" book list views""" from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db import IntegrityError @@ -18,51 +18,57 @@ from .helpers import get_user_from_username # pylint: disable=no-self-use class Lists(View): - ''' book list page ''' + """ book list page """ + def get(self, request): - ''' display a book list ''' + """ display a book list """ try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 user = request.user if request.user.is_authenticated else None # hide lists with no approved books - lists = models.List.objects.filter( - ~Q(user=user), - ).annotate( - item_count=Count('listitem', filter=Q(listitem__approved=True)) - ).filter( - item_count__gt=0 - ).distinct().all() + lists = ( + models.List.objects.filter( + ~Q(user=user), + ) + .annotate(item_count=Count("listitem", filter=Q(listitem__approved=True))) + .filter(item_count__gt=0) + .distinct() + .all() + ) lists = privacy_filter( - request.user, lists, privacy_levels=['public', 'followers']) + request.user, lists, privacy_levels=["public", "followers"] + ) paginated = Paginator(lists, 12) data = { - 'lists': paginated.page(page), - 'list_form': forms.ListForm(), - 'path': '/list', + "lists": paginated.page(page), + "list_form": forms.ListForm(), + "path": "/list", } - return TemplateResponse(request, 'lists/lists.html', data) + return TemplateResponse(request, "lists/lists.html", data) - @method_decorator(login_required, name='dispatch') + @method_decorator(login_required, name="dispatch") # pylint: disable=unused-argument def post(self, request): - ''' create a book_list ''' + """ create a book_list """ form = forms.ListForm(request.POST) if not form.is_valid(): - return redirect('lists') + return redirect("lists") book_list = form.save() return redirect(book_list.local_path) + class UserLists(View): - ''' a user's book list page ''' + """ a user's book list page """ + def get(self, request, username): - ''' display a book list ''' + """ display a book list """ try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 user = get_user_from_username(request.user, username) @@ -71,19 +77,20 @@ class UserLists(View): paginated = Paginator(lists, 12) data = { - 'user': user, - 'is_self': request.user.id == user.id, - 'lists': paginated.page(page), - 'list_form': forms.ListForm(), - 'path': user.local_path + '/lists', + "user": user, + "is_self": request.user.id == user.id, + "lists": paginated.page(page), + "list_form": forms.ListForm(), + "path": user.local_path + "/lists", } - return TemplateResponse(request, 'user/lists.html', data) + return TemplateResponse(request, "user/lists.html", data) class List(View): - ''' book list page ''' + """ book list page """ + def get(self, request, list_id): - ''' display a book list ''' + """ display a book list """ book_list = get_object_or_404(models.List, id=list_id) if not object_visible_to_user(request.user, book_list): return HttpResponseNotFound() @@ -91,7 +98,7 @@ class List(View): if is_api_request(request): return ActivitypubResponse(book_list.to_activity(**request.GET)) - query = request.GET.get('q') + query = request.GET.get("q") suggestions = None if query and request.user.is_authenticated: # search for books @@ -104,89 +111,85 @@ class List(View): suggestions = [s.book for s in suggestions[:5]] if len(suggestions) < 5: suggestions += [ - s.default_edition for s in \ - models.Work.objects.filter( - ~Q(editions__in=book_list.books.all()), - ).order_by('-updated_date') - ][:5 - len(suggestions)] - + s.default_edition + for s in models.Work.objects.filter( + ~Q(editions__in=book_list.books.all()), + ).order_by("-updated_date") + ][: 5 - len(suggestions)] data = { - 'list': book_list, - 'items': book_list.listitem_set.filter(approved=True), - 'pending_count': book_list.listitem_set.filter( - approved=False).count(), - 'suggested_books': suggestions, - 'list_form': forms.ListForm(instance=book_list), - 'query': query or '' + "list": book_list, + "items": book_list.listitem_set.filter(approved=True), + "pending_count": book_list.listitem_set.filter(approved=False).count(), + "suggested_books": suggestions, + "list_form": forms.ListForm(instance=book_list), + "query": query or "", } - return TemplateResponse(request, 'lists/list.html', data) + return TemplateResponse(request, "lists/list.html", data) - - @method_decorator(login_required, name='dispatch') + @method_decorator(login_required, name="dispatch") # pylint: disable=unused-argument def post(self, request, list_id): - ''' edit a list ''' + """ edit a list """ book_list = get_object_or_404(models.List, id=list_id) form = forms.ListForm(request.POST, instance=book_list) if not form.is_valid(): - return redirect('list', book_list.id) + return redirect("list", book_list.id) book_list = form.save() return redirect(book_list.local_path) class Curate(View): - ''' approve or discard list suggestsions ''' - @method_decorator(login_required, name='dispatch') + """ approve or discard list suggestsions """ + + @method_decorator(login_required, name="dispatch") def get(self, request, list_id): - ''' display a pending list ''' + """ display a pending list """ book_list = get_object_or_404(models.List, id=list_id) if not book_list.user == request.user: # only the creater can curate the list return HttpResponseNotFound() data = { - 'list': book_list, - 'pending': book_list.listitem_set.filter(approved=False), - 'list_form': forms.ListForm(instance=book_list), + "list": book_list, + "pending": book_list.listitem_set.filter(approved=False), + "list_form": forms.ListForm(instance=book_list), } - return TemplateResponse(request, 'lists/curate.html', data) + return TemplateResponse(request, "lists/curate.html", data) - - @method_decorator(login_required, name='dispatch') + @method_decorator(login_required, name="dispatch") # pylint: disable=unused-argument def post(self, request, list_id): - ''' edit a book_list ''' + """ edit a book_list """ book_list = get_object_or_404(models.List, id=list_id) - suggestion = get_object_or_404( - models.ListItem, id=request.POST.get('item')) - approved = request.POST.get('approved') == 'true' + suggestion = get_object_or_404(models.ListItem, id=request.POST.get("item")) + approved = request.POST.get("approved") == "true" if approved: suggestion.approved = True suggestion.save() else: suggestion.delete() - return redirect('list-curate', book_list.id) + return redirect("list-curate", book_list.id) @require_POST def add_book(request, list_id): - ''' put a book on a list ''' + """ put a book on a list """ book_list = get_object_or_404(models.List, id=list_id) if not object_visible_to_user(request.user, book_list): return HttpResponseNotFound() - book = get_object_or_404(models.Edition, id=request.POST.get('book')) + book = get_object_or_404(models.Edition, id=request.POST.get("book")) # do you have permission to add to the list? try: - if request.user == book_list.user or book_list.curation == 'open': + if request.user == book_list.user or book_list.curation == "open": # go ahead and add it models.ListItem.objects.create( book=book, book_list=book_list, user=request.user, ) - elif book_list.curation == 'curated': + elif book_list.curation == "curated": # make a pending entry models.ListItem.objects.create( approved=False, @@ -201,17 +204,17 @@ def add_book(request, list_id): # if the book is already on the list, don't flip out pass - return redirect('list', list_id) + return redirect("list", list_id) @require_POST def remove_book(request, list_id): - ''' put a book on a list ''' + """ put a book on a list """ book_list = get_object_or_404(models.List, id=list_id) - item = get_object_or_404(models.ListItem, id=request.POST.get('item')) + item = get_object_or_404(models.ListItem, id=request.POST.get("item")) if not book_list.user == request.user and not item.user == request.user: return HttpResponseNotFound() item.delete() - return redirect('list', list_id) + return redirect("list", list_id) diff --git a/bookwyrm/views/notifications.py b/bookwyrm/views/notifications.py index 684154e4c..7a62ec01e 100644 --- a/bookwyrm/views/notifications.py +++ b/bookwyrm/views/notifications.py @@ -1,4 +1,4 @@ -''' non-interactive pages ''' +""" non-interactive pages """ from django.contrib.auth.decorators import login_required from django.template.response import TemplateResponse from django.utils.decorators import method_decorator @@ -7,22 +7,22 @@ from django.views import View # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Notifications(View): - ''' notifications view ''' + """ notifications view """ + def get(self, request): - ''' people are interacting with you, get hyped ''' - notifications = request.user.notification_set.all() \ - .order_by('-created_date') + """ people are interacting with you, get hyped """ + notifications = request.user.notification_set.all().order_by("-created_date") unread = [n.id for n in notifications.filter(read=False)] data = { - 'notifications': notifications, - 'unread': unread, + "notifications": notifications, + "unread": unread, } notifications.update(read=True) - return TemplateResponse(request, 'notifications.html', data) + return TemplateResponse(request, "notifications.html", data) def post(self, request): - ''' permanently delete notification for user ''' + """ permanently delete notification for user """ request.user.notification_set.filter(read=True).delete() - return redirect('/notifications') + return redirect("/notifications") diff --git a/bookwyrm/views/outbox.py b/bookwyrm/views/outbox.py index 5df9d1999..ec6f5cd39 100644 --- a/bookwyrm/views/outbox.py +++ b/bookwyrm/views/outbox.py @@ -1,4 +1,4 @@ -''' the good stuff! the books! ''' +""" the good stuff! the books! """ from django.http import JsonResponse from django.shortcuts import get_object_or_404 from django.views import View @@ -9,11 +9,12 @@ from .helpers import is_bookwyrm_request # pylint: disable= no-self-use class Outbox(View): - ''' outbox ''' + """ outbox """ + def get(self, request, username): - ''' outbox for the requested user ''' + """ outbox for the requested user """ user = get_object_or_404(models.User, localname=username) - filter_type = request.GET.get('type') + filter_type = request.GET.get("type") if filter_type not in models.status_models: filter_type = None @@ -23,5 +24,5 @@ class Outbox(View): filter_type=filter_type, pure=not is_bookwyrm_request(request) ), - encoder=activitypub.ActivityEncoder + encoder=activitypub.ActivityEncoder, ) diff --git a/bookwyrm/views/password.py b/bookwyrm/views/password.py index 792da2d78..e853d16bf 100644 --- a/bookwyrm/views/password.py +++ b/bookwyrm/views/password.py @@ -1,4 +1,4 @@ -''' class views for password management ''' +""" class views for password management """ from django.contrib.auth import login from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied @@ -13,21 +13,22 @@ from bookwyrm.emailing import password_reset_email # pylint: disable= no-self-use class PasswordResetRequest(View): - ''' forgot password flow ''' + """ forgot password flow """ + def get(self, request): - ''' password reset page ''' + """ password reset page """ return TemplateResponse( request, - 'password_reset_request.html', + "password_reset_request.html", ) def post(self, request): - ''' create a password reset token ''' - email = request.POST.get('email') + """ create a password reset token """ + email = request.POST.get("email") try: user = models.User.objects.get(email=email) except models.User.DoesNotExist: - return redirect('/password-reset') + return redirect("/password-reset") # remove any existing password reset cods for this user models.PasswordReset.objects.filter(user=user).all().delete() @@ -35,16 +36,17 @@ class PasswordResetRequest(View): # create a new reset code code = models.PasswordReset.objects.create(user=user) password_reset_email(code) - data = {'message': 'Password reset link sent to %s' % email} - return TemplateResponse(request, 'password_reset_request.html', data) + data = {"message": "Password reset link sent to %s" % email} + return TemplateResponse(request, "password_reset_request.html", data) class PasswordReset(View): - ''' set new password ''' + """ set new password """ + def get(self, request, code): - ''' endpoint for sending invites ''' + """ endpoint for sending invites """ if request.user.is_authenticated: - return redirect('/') + return redirect("/") try: reset_code = models.PasswordReset.objects.get(code=code) if not reset_code.valid(): @@ -52,50 +54,48 @@ class PasswordReset(View): except models.PasswordReset.DoesNotExist: raise PermissionDenied - return TemplateResponse(request, 'password_reset.html') + return TemplateResponse(request, "password_reset.html") def post(self, request, code): - ''' allow a user to change their password through an emailed token ''' + """ allow a user to change their password through an emailed token """ try: - reset_code = models.PasswordReset.objects.get( - code=code - ) + reset_code = models.PasswordReset.objects.get(code=code) except models.PasswordReset.DoesNotExist: - data = {'errors': ['Invalid password reset link']} - return TemplateResponse(request, 'password_reset.html', data) + data = {"errors": ["Invalid password reset link"]} + return TemplateResponse(request, "password_reset.html", data) user = reset_code.user - new_password = request.POST.get('password') - confirm_password = request.POST.get('confirm-password') + new_password = request.POST.get("password") + confirm_password = request.POST.get("confirm-password") if new_password != confirm_password: - data = {'errors': ['Passwords do not match']} - return TemplateResponse(request, 'password_reset.html', data) + data = {"errors": ["Passwords do not match"]} + return TemplateResponse(request, "password_reset.html", data) user.set_password(new_password) user.save(broadcast=False) login(request, user) reset_code.delete() - return redirect('/') + return redirect("/") -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class ChangePassword(View): - ''' change password as logged in user ''' + """ change password as logged in user """ + def get(self, request): - ''' change password page ''' - data = {'user': request.user} - return TemplateResponse( - request, 'preferences/change_password.html', data) + """ change password page """ + data = {"user": request.user} + return TemplateResponse(request, "preferences/change_password.html", data) def post(self, request): - ''' allow a user to change their password ''' - new_password = request.POST.get('password') - confirm_password = request.POST.get('confirm-password') + """ allow a user to change their password """ + new_password = request.POST.get("password") + confirm_password = request.POST.get("confirm-password") if new_password != confirm_password: - return redirect('preferences/password') + return redirect("preferences/password") request.user.set_password(new_password) request.user.save(broadcast=False) diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index 6b2bf242e..fb63f2a3e 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -1,4 +1,4 @@ -''' the good stuff! the books! ''' +""" the good stuff! the books! """ import dateutil.parser from dateutil.parser import ParserError @@ -17,12 +17,9 @@ from .shelf import handle_unshelve @login_required @require_POST def start_reading(request, book_id): - ''' begin reading a book ''' + """ begin reading a book """ book = get_edition(book_id) - shelf = models.Shelf.objects.filter( - identifier='reading', - user=request.user - ).first() + shelf = models.Shelf.objects.filter(identifier="reading", user=request.user).first() # create a readthrough readthrough = update_readthrough(request, book=book) @@ -33,36 +30,29 @@ def start_reading(request, book_id): readthrough.create_update() # shelve the book - if request.POST.get('reshelve', True): + if request.POST.get("reshelve", True): try: - current_shelf = models.Shelf.objects.get( - user=request.user, - edition=book - ) + current_shelf = models.Shelf.objects.get(user=request.user, edition=book) handle_unshelve(request.user, book, current_shelf) except models.Shelf.DoesNotExist: # this just means it isn't currently on the user's shelves pass - models.ShelfBook.objects.create( - book=book, shelf=shelf, user=request.user) + models.ShelfBook.objects.create(book=book, shelf=shelf, user=request.user) # post about it (if you want) - if request.POST.get('post-status'): - privacy = request.POST.get('privacy') + if request.POST.get("post-status"): + privacy = request.POST.get("privacy") handle_reading_status(request.user, shelf, book, privacy) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) @login_required @require_POST def finish_reading(request, book_id): - ''' a user completed a book, yay ''' + """ a user completed a book, yay """ book = get_edition(book_id) - shelf = models.Shelf.objects.filter( - identifier='read', - user=request.user - ).first() + shelf = models.Shelf.objects.filter(identifier="read", user=request.user).first() # update or create a readthrough readthrough = update_readthrough(request, book=book) @@ -70,31 +60,27 @@ def finish_reading(request, book_id): readthrough.save() # shelve the book - if request.POST.get('reshelve', True): + if request.POST.get("reshelve", True): try: - current_shelf = models.Shelf.objects.get( - user=request.user, - edition=book - ) + current_shelf = models.Shelf.objects.get(user=request.user, edition=book) handle_unshelve(request.user, book, current_shelf) except models.Shelf.DoesNotExist: # this just means it isn't currently on the user's shelves pass - models.ShelfBook.objects.create( - book=book, shelf=shelf, user=request.user) + models.ShelfBook.objects.create(book=book, shelf=shelf, user=request.user) # post about it (if you want) - if request.POST.get('post-status'): - privacy = request.POST.get('privacy') + if request.POST.get("post-status"): + privacy = request.POST.get("privacy") handle_reading_status(request.user, shelf, book, privacy) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) @login_required @require_POST def edit_readthrough(request): - ''' can't use the form because the dates are too finnicky ''' + """ can't use the form because the dates are too finnicky """ readthrough = update_readthrough(request, create=False) if not readthrough: return HttpResponseNotFound() @@ -108,40 +94,39 @@ def edit_readthrough(request): # use default now for date field readthrough.create_update() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) @login_required @require_POST def delete_readthrough(request): - ''' remove a readthrough ''' - readthrough = get_object_or_404( - models.ReadThrough, id=request.POST.get('id')) + """ remove a readthrough """ + readthrough = get_object_or_404(models.ReadThrough, id=request.POST.get("id")) # don't let people edit other people's data if request.user != readthrough.user: return HttpResponseBadRequest() readthrough.delete() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) @login_required @require_POST def create_readthrough(request): - ''' can't use the form because the dates are too finnicky ''' - book = get_object_or_404(models.Edition, id=request.POST.get('book')) + """ can't use the form because the dates are too finnicky """ + book = get_object_or_404(models.Edition, id=request.POST.get("book")) readthrough = update_readthrough(request, create=True, book=book) if not readthrough: return redirect(book.local_path) readthrough.save() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) def update_readthrough(request, book=None, create=True): - ''' updates but does not save dates on a readthrough ''' + """ updates but does not save dates on a readthrough """ try: - read_id = request.POST.get('id') + read_id = request.POST.get("id") if not read_id: raise models.ReadThrough.DoesNotExist readthrough = models.ReadThrough.objects.get(id=read_id) @@ -153,7 +138,7 @@ def update_readthrough(request, book=None, create=True): book=book, ) - start_date = request.POST.get('start_date') + start_date = request.POST.get("start_date") if start_date: try: start_date = timezone.make_aware(dateutil.parser.parse(start_date)) @@ -161,16 +146,15 @@ def update_readthrough(request, book=None, create=True): except ParserError: pass - finish_date = request.POST.get('finish_date') + finish_date = request.POST.get("finish_date") if finish_date: try: - finish_date = timezone.make_aware( - dateutil.parser.parse(finish_date)) + finish_date = timezone.make_aware(dateutil.parser.parse(finish_date)) readthrough.finish_date = finish_date except ParserError: pass - progress = request.POST.get('progress') + progress = request.POST.get("progress") if progress: try: progress = int(progress) @@ -178,7 +162,7 @@ def update_readthrough(request, book=None, create=True): except ValueError: pass - progress_mode = request.POST.get('progress_mode') + progress_mode = request.POST.get("progress_mode") if progress_mode: try: progress_mode = models.ProgressMode(progress_mode) @@ -191,15 +175,16 @@ def update_readthrough(request, book=None, create=True): return readthrough + @login_required @require_POST def delete_progressupdate(request): - ''' remove a progress update ''' - update = get_object_or_404(models.ProgressUpdate, id=request.POST.get('id')) + """ remove a progress update """ + update = get_object_or_404(models.ProgressUpdate, id=request.POST.get("id")) # don't let people edit other people's data if request.user != update.user: return HttpResponseBadRequest() update.delete() - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) diff --git a/bookwyrm/views/rss_feed.py b/bookwyrm/views/rss_feed.py index d24b636ea..57821af4e 100644 --- a/bookwyrm/views/rss_feed.py +++ b/bookwyrm/views/rss_feed.py @@ -1,38 +1,35 @@ -''' serialize user's posts in rss feed ''' +""" serialize user's posts in rss feed """ from django.contrib.syndication.views import Feed from .helpers import get_activity_feed, get_user_from_username # pylint: disable=no-self-use, unused-argument class RssFeed(Feed): - ''' serialize user's posts in rss feed ''' - description_template = 'snippets/rss_content.html' - title_template = 'snippets/rss_title.html' + """ serialize user's posts in rss feed """ + + description_template = "snippets/rss_content.html" + title_template = "snippets/rss_title.html" def get_object(self, request, username): - ''' the user who's posts get serialized ''' + """ the user who's posts get serialized """ return get_user_from_username(request.user, username) - def link(self, obj): - ''' link to the user's profile ''' + """ link to the user's profile """ return obj.local_path - def title(self, obj): - ''' title of the rss feed entry ''' - return f'Status updates from {obj.display_name}' - + """ title of the rss feed entry """ + return f"Status updates from {obj.display_name}" def items(self, obj): - ''' the user's activity feed ''' + """ the user's activity feed """ return get_activity_feed( obj, - privacy=['public', 'unlisted'], - queryset=obj.status_set.select_subclasses() + privacy=["public", "unlisted"], + queryset=obj.status_set.select_subclasses(), ) - def item_link(self, item): - ''' link to the status ''' + """ link to the status """ return item.local_path diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index b44c49f81..80969d31b 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -1,4 +1,4 @@ -''' search views''' +""" search views""" import re from django.contrib.postgres.search import TrigramSimilarity @@ -16,51 +16,63 @@ from .helpers import handle_remote_webfinger # pylint: disable= no-self-use class Search(View): - ''' search users or books ''' + """ search users or books """ + def get(self, request): - ''' that search bar up top ''' - query = request.GET.get('q') - min_confidence = request.GET.get('min_confidence', 0.1) + """ that search bar up top """ + query = request.GET.get("q") + min_confidence = request.GET.get("min_confidence", 0.1) if is_api_request(request): # only return local book results via json so we don't cascade book_results = connector_manager.local_search( - query, min_confidence=min_confidence) + query, min_confidence=min_confidence + ) return JsonResponse([r.json() for r in book_results], safe=False) # use webfinger for mastodon style account@domain.com username - if re.match(r'\B%s' % regex.full_username, query): + if re.match(r"\B%s" % regex.full_username, query): handle_remote_webfinger(query) # do a user search - user_results = models.User.viewer_aware_objects(request.user).annotate( - similarity=Greatest( - TrigramSimilarity('username', query), - TrigramSimilarity('localname', query), + user_results = ( + models.User.viewer_aware_objects(request.user) + .annotate( + similarity=Greatest( + TrigramSimilarity("username", query), + TrigramSimilarity("localname", query), + ) ) - ).filter( - similarity__gt=0.5, - ).order_by('-similarity')[:10] + .filter( + similarity__gt=0.5, + ) + .order_by("-similarity")[:10] + ) # any relevent lists? - list_results = privacy_filter( - request.user, models.List.objects, - privacy_levels=['public', 'followers'] - ).annotate( - similarity=Greatest( - TrigramSimilarity('name', query), - TrigramSimilarity('description', query), + list_results = ( + privacy_filter( + request.user, + models.List.objects, + privacy_levels=["public", "followers"], ) - ).filter( - similarity__gt=0.1, - ).order_by('-similarity')[:10] + .annotate( + similarity=Greatest( + TrigramSimilarity("name", query), + TrigramSimilarity("description", query), + ) + ) + .filter( + similarity__gt=0.1, + ) + .order_by("-similarity")[:10] + ) - book_results = connector_manager.search( - query, min_confidence=min_confidence) + book_results = connector_manager.search(query, min_confidence=min_confidence) data = { - 'book_results': book_results, - 'user_results': user_results, - 'list_results': list_results, - 'query': query, + "book_results": book_results, + "user_results": user_results, + "list_results": list_results, + "query": query, } - return TemplateResponse(request, 'search_results.html', data) + return TemplateResponse(request, "search_results.html", data) diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 867c7d91c..6256eac7f 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -1,4 +1,4 @@ -''' shelf views''' +""" shelf views""" from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect @@ -15,9 +15,10 @@ from .helpers import handle_reading_status # pylint: disable= no-self-use class Shelf(View): - ''' shelf page ''' + """ shelf page """ + def get(self, request, username, shelf_identifier): - ''' display a shelf ''' + """ display a shelf """ try: user = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -34,38 +35,40 @@ class Shelf(View): if not is_self: follower = user.followers.filter(id=request.user.id).exists() # make sure the user has permission to view the shelf - if shelf.privacy == 'direct' or \ - (shelf.privacy == 'followers' and not follower): + if shelf.privacy == "direct" or ( + shelf.privacy == "followers" and not follower + ): return HttpResponseNotFound() # only show other shelves that should be visible if follower: - shelves = shelves.filter(privacy__in=['public', 'followers']) + shelves = shelves.filter(privacy__in=["public", "followers"]) else: - shelves = shelves.filter(privacy='public') - + shelves = shelves.filter(privacy="public") if is_api_request(request): return ActivitypubResponse(shelf.to_activity(**request.GET)) - books = models.ShelfBook.objects.filter( - user=user, shelf=shelf - ).order_by('-updated_date').all() + books = ( + models.ShelfBook.objects.filter(user=user, shelf=shelf) + .order_by("-updated_date") + .all() + ) data = { - 'user': user, - 'is_self': is_self, - 'shelves': shelves.all(), - 'shelf': shelf, - 'books': [b.book for b in books], + "user": user, + "is_self": is_self, + "shelves": shelves.all(), + "shelf": shelf, + "books": [b.book for b in books], } - return TemplateResponse(request, 'user/shelf.html', data) + return TemplateResponse(request, "user/shelf.html", data) - @method_decorator(login_required, name='dispatch') + @method_decorator(login_required, name="dispatch") # pylint: disable=unused-argument def post(self, request, username, shelf_identifier): - ''' edit a shelf ''' + """ edit a shelf """ try: shelf = request.user.shelf_set.get(identifier=shelf_identifier) except models.Shelf.DoesNotExist: @@ -73,7 +76,7 @@ class Shelf(View): if request.user != shelf.user: return HttpResponseBadRequest() - if not shelf.editable and request.POST.get('name') != shelf.name: + if not shelf.editable and request.POST.get("name") != shelf.name: return HttpResponseBadRequest() form = forms.ShelfForm(request.POST, instance=shelf) @@ -84,88 +87,76 @@ class Shelf(View): def user_shelves_page(request, username): - ''' default shelf ''' + """ default shelf """ return Shelf.as_view()(request, username, None) @login_required @require_POST def create_shelf(request): - ''' user generated shelves ''' + """ user generated shelves """ form = forms.ShelfForm(request.POST) if not form.is_valid(): - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) shelf = form.save() - return redirect('/user/%s/shelf/%s' % \ - (request.user.localname, shelf.identifier)) + return redirect("/user/%s/shelf/%s" % (request.user.localname, shelf.identifier)) @login_required @require_POST def delete_shelf(request, shelf_id): - ''' user generated shelves ''' + """ user generated shelves """ shelf = get_object_or_404(models.Shelf, id=shelf_id) if request.user != shelf.user or not shelf.editable: return HttpResponseBadRequest() shelf.delete() - return redirect('/user/%s/shelves' % request.user.localname) + return redirect("/user/%s/shelves" % request.user.localname) @login_required @require_POST def shelve(request): - ''' put a on a user's shelf ''' - book = get_edition(request.POST.get('book')) + """ put a on a user's shelf """ + book = get_edition(request.POST.get("book")) desired_shelf = models.Shelf.objects.filter( - identifier=request.POST.get('shelf'), - user=request.user + identifier=request.POST.get("shelf"), user=request.user ).first() if not desired_shelf: return HttpResponseNotFound() - if request.POST.get('reshelve', True): + if request.POST.get("reshelve", True): try: - current_shelf = models.Shelf.objects.get( - user=request.user, - edition=book - ) + current_shelf = models.Shelf.objects.get(user=request.user, edition=book) handle_unshelve(request.user, book, current_shelf) except models.Shelf.DoesNotExist: # this just means it isn't currently on the user's shelves pass - models.ShelfBook.objects.create( - book=book, shelf=desired_shelf, user=request.user) + models.ShelfBook.objects.create(book=book, shelf=desired_shelf, user=request.user) # post about "want to read" shelves - if desired_shelf.identifier == 'to-read' and \ - request.POST.get('post-status'): - privacy = request.POST.get('privacy') or desired_shelf.privacy - handle_reading_status( - request.user, - desired_shelf, - book, - privacy=privacy - ) + if desired_shelf.identifier == "to-read" and request.POST.get("post-status"): + privacy = request.POST.get("privacy") or desired_shelf.privacy + handle_reading_status(request.user, desired_shelf, book, privacy=privacy) - return redirect('/') + return redirect("/") @login_required @require_POST def unshelve(request): - ''' put a on a user's shelf ''' - book = models.Edition.objects.get(id=request.POST['book']) - current_shelf = models.Shelf.objects.get(id=request.POST['shelf']) + """ put a on a user's shelf """ + book = models.Edition.objects.get(id=request.POST["book"]) + current_shelf = models.Shelf.objects.get(id=request.POST["shelf"]) handle_unshelve(request.user, book, current_shelf) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) -#pylint: disable=unused-argument +# pylint: disable=unused-argument def handle_unshelve(user, book, shelf): - ''' unshelve a book ''' + """ unshelve a book """ row = models.ShelfBook.objects.get(book=book, shelf=shelf) row.delete() diff --git a/bookwyrm/views/site.py b/bookwyrm/views/site.py index e60354a32..c40a9e760 100644 --- a/bookwyrm/views/site.py +++ b/bookwyrm/views/site.py @@ -1,4 +1,4 @@ -''' manage site settings ''' +""" manage site settings """ from django.contrib.auth.decorators import login_required, permission_required from django.shortcuts import redirect from django.template.response import TemplateResponse @@ -9,26 +9,27 @@ from bookwyrm import forms, models # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") @method_decorator( - permission_required( - 'bookwyrm.edit_instance_settings', raise_exception=True), - name='dispatch') + permission_required("bookwyrm.edit_instance_settings", raise_exception=True), + name="dispatch", +) class Site(View): - ''' manage things like the instance name ''' + """ manage things like the instance name """ + def get(self, request): - ''' edit form ''' + """ edit form """ site = models.SiteSettings.objects.get() - data = {'site_form': forms.SiteForm(instance=site)} - return TemplateResponse(request, 'settings/site.html', data) + data = {"site_form": forms.SiteForm(instance=site)} + return TemplateResponse(request, "settings/site.html", data) def post(self, request): - ''' edit the site settings ''' + """ edit the site settings """ site = models.SiteSettings.objects.get() form = forms.SiteForm(request.POST, instance=site) if not form.is_valid(): - data = {'site_form': form} - return TemplateResponse(request, 'settings/site.html', data) + data = {"site_form": form} + return TemplateResponse(request, "settings/site.html", data) form.save() - return redirect('settings-site') + return redirect("settings-site") diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index db924ce8b..9a626371c 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -1,4 +1,4 @@ -''' what are we here for if not for posting ''' +""" what are we here for if not for posting """ import re from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest @@ -16,19 +16,20 @@ from .helpers import handle_remote_webfinger # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class CreateStatus(View): - ''' the view for *posting* ''' + """ the view for *posting* """ + def post(self, request, status_type): - ''' create status of whatever type ''' + """ create status of whatever type """ status_type = status_type[0].upper() + status_type[1:] try: - form = getattr(forms, '%sForm' % status_type)(request.POST) + form = getattr(forms, "%sForm" % status_type)(request.POST) except AttributeError: return HttpResponseBadRequest() if not form.is_valid(): - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) status = form.save(commit=False) if not status.sensitive and status.content_warning: @@ -44,10 +45,10 @@ class CreateStatus(View): # turn the mention into a link content = re.sub( - r'%s([^@]|$)' % mention_text, - r'%s\g<1>' % \ - (mention_user.remote_id, mention_text), - content) + r"%s([^@]|$)" % mention_text, + r'%s\g<1>' % (mention_user.remote_id, mention_text), + content, + ) # add reply parent to mentions if status.reply_parent: status.mention_users.add(status.reply_parent.user) @@ -59,17 +60,18 @@ class CreateStatus(View): if not isinstance(status, models.GeneratedNote): status.content = to_markdown(content) # do apply formatting to quotes - if hasattr(status, 'quote'): + if hasattr(status, "quote"): status.quote = to_markdown(status.quote) status.save(created=True) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) class DeleteStatus(View): - ''' tombstone that bad boy ''' + """ tombstone that bad boy """ + def post(self, request, status_id): - ''' delete and tombstone a status ''' + """ delete and tombstone a status """ status = get_object_or_404(models.Status, id=status_id) # don't let people delete other people's statuses @@ -78,16 +80,17 @@ class DeleteStatus(View): # perform deletion delete_status(status) - return redirect(request.headers.get('Referer', '/')) + return redirect(request.headers.get("Referer", "/")) + def find_mentions(content): - ''' detect @mentions in raw status content ''' + """ detect @mentions in raw status content """ for match in re.finditer(regex.strict_username, content): - username = match.group().strip().split('@')[1:] + username = match.group().strip().split("@")[1:] if len(username) == 1: # this looks like a local user (@user), fill in the domain username.append(DOMAIN) - username = '@'.join(username) + username = "@".join(username) mention_user = handle_remote_webfinger(username) if not mention_user: @@ -97,15 +100,16 @@ def find_mentions(content): def format_links(content): - ''' detect and format links ''' + """ detect and format links """ return re.sub( - r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % \ - regex.domain, + r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % regex.domain, r'\g<1>\g<3>', - content) + content, + ) + def to_markdown(content): - ''' catch links and convert to markdown ''' + """ catch links and convert to markdown """ content = markdown(content) content = format_links(content) # sanitize resulting html diff --git a/bookwyrm/views/tag.py b/bookwyrm/views/tag.py index e106e8dce..a6bdf05a2 100644 --- a/bookwyrm/views/tag.py +++ b/bookwyrm/views/tag.py @@ -1,4 +1,4 @@ -''' tagging views''' +""" tagging views""" from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse @@ -12,34 +12,35 @@ from .helpers import is_api_request # pylint: disable= no-self-use class Tag(View): - ''' tag page ''' + """ tag page """ + def get(self, request, tag_id): - ''' see books related to a tag ''' + """ see books related to a tag """ tag_obj = get_object_or_404(models.Tag, identifier=tag_id) if is_api_request(request): - return ActivitypubResponse( - tag_obj.to_activity(**request.GET)) + return ActivitypubResponse(tag_obj.to_activity(**request.GET)) books = models.Edition.objects.filter( usertag__tag__identifier=tag_id ).distinct() data = { - 'books': books, - 'tag': tag_obj, + "books": books, + "tag": tag_obj, } - return TemplateResponse(request, 'tag.html', data) + return TemplateResponse(request, "tag.html", data) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class AddTag(View): - ''' add a tag to a book ''' + """ add a tag to a book """ + def post(self, request): - ''' tag a book ''' + """ tag a book """ # I'm not using a form here because sometimes "name" is sent as a hidden # field which doesn't validate - name = request.POST.get('name') - book_id = request.POST.get('book') + name = request.POST.get("name") + book_id = request.POST.get("book") book = get_object_or_404(models.Edition, id=book_id) tag_obj, _ = models.Tag.objects.get_or_create( name=name, @@ -50,21 +51,23 @@ class AddTag(View): tag=tag_obj, ) - return redirect('/book/%s' % book_id) + return redirect("/book/%s" % book_id) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class RemoveTag(View): - ''' remove a user's tag from a book ''' + """ remove a user's tag from a book """ + def post(self, request): - ''' untag a book ''' - name = request.POST.get('name') + """ untag a book """ + name = request.POST.get("name") tag_obj = get_object_or_404(models.Tag, name=name) - book_id = request.POST.get('book') + book_id = request.POST.get("book") book = get_object_or_404(models.Edition, id=book_id) user_tag = get_object_or_404( - models.UserTag, tag=tag_obj, book=book, user=request.user) + models.UserTag, tag=tag_obj, book=book, user=request.user + ) user_tag.delete() - return redirect('/book/%s' % book_id) + return redirect("/book/%s" % book_id) diff --git a/bookwyrm/views/updates.py b/bookwyrm/views/updates.py index 233e51917..83b680c0b 100644 --- a/bookwyrm/views/updates.py +++ b/bookwyrm/views/updates.py @@ -1,17 +1,20 @@ -''' endpoints for getting updates about activity ''' +""" endpoints for getting updates about activity """ from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.utils.decorators import method_decorator from django.views import View # pylint: disable= no-self-use -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class Updates(View): - ''' so the app can poll ''' + """ so the app can poll """ + def get(self, request): - ''' any notifications waiting? ''' - return JsonResponse({ - 'notifications': request.user.notification_set.filter( - read=False - ).count(), - }) + """ any notifications waiting? """ + return JsonResponse( + { + "notifications": request.user.notification_set.filter( + read=False + ).count(), + } + ) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index f3a088c13..469a82d38 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -1,4 +1,4 @@ -''' non-interactive pages ''' +""" non-interactive pages """ from io import BytesIO from uuid import uuid4 from PIL import Image @@ -22,9 +22,10 @@ from .helpers import is_blocked, object_visible_to_user # pylint: disable= no-self-use class User(View): - ''' user profile page ''' + """ user profile page """ + def get(self, request, username): - ''' profile page for a user ''' + """ profile page for a user """ try: user = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -40,7 +41,7 @@ class User(View): # otherwise we're at a UI view try: - page = int(request.GET.get('page', 1)) + page = int(request.GET.get("page", 1)) except ValueError: page = 1 @@ -52,19 +53,21 @@ class User(View): if not is_self: follower = user.followers.filter(id=request.user.id).exists() if follower: - shelves = shelves.filter(privacy__in=['public', 'followers']) + shelves = shelves.filter(privacy__in=["public", "followers"]) else: - shelves = shelves.filter(privacy='public') + shelves = shelves.filter(privacy="public") for user_shelf in shelves.all(): if not user_shelf.books.count(): continue - shelf_preview.append({ - 'name': user_shelf.name, - 'local_path': user_shelf.local_path, - 'books': user_shelf.books.all()[:3], - 'size': user_shelf.books.count(), - }) + shelf_preview.append( + { + "name": user_shelf.name, + "local_path": user_shelf.local_path, + "books": user_shelf.books.all()[:3], + "size": user_shelf.books.count(), + } + ) if len(shelf_preview) > 2: break @@ -75,24 +78,27 @@ class User(View): ) paginated = Paginator(activities, PAGE_LENGTH) goal = models.AnnualGoal.objects.filter( - user=user, year=timezone.now().year).first() + user=user, year=timezone.now().year + ).first() if not object_visible_to_user(request.user, goal): goal = None data = { - 'user': user, - 'is_self': is_self, - 'shelves': shelf_preview, - 'shelf_count': shelves.count(), - 'activities': paginated.page(page), - 'goal': goal, + "user": user, + "is_self": is_self, + "shelves": shelf_preview, + "shelf_count": shelves.count(), + "activities": paginated.page(page), + "goal": goal, } - return TemplateResponse(request, 'user/user.html', data) + return TemplateResponse(request, "user/user.html", data) + class Followers(View): - ''' list of followers view ''' + """ list of followers view """ + def get(self, request, username): - ''' list of followers ''' + """ list of followers """ try: user = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -103,20 +109,21 @@ class Followers(View): return HttpResponseNotFound() if is_api_request(request): - return ActivitypubResponse( - user.to_followers_activity(**request.GET)) + return ActivitypubResponse(user.to_followers_activity(**request.GET)) data = { - 'user': user, - 'is_self': request.user.id == user.id, - 'followers': user.followers.all(), + "user": user, + "is_self": request.user.id == user.id, + "followers": user.followers.all(), } - return TemplateResponse(request, 'user/followers.html', data) + return TemplateResponse(request, "user/followers.html", data) + class Following(View): - ''' list of following view ''' + """ list of following view """ + def get(self, request, username): - ''' list of followers ''' + """ list of followers """ try: user = get_user_from_username(request.user, username) except models.User.DoesNotExist: @@ -127,46 +134,45 @@ class Following(View): return HttpResponseNotFound() if is_api_request(request): - return ActivitypubResponse( - user.to_following_activity(**request.GET)) + return ActivitypubResponse(user.to_following_activity(**request.GET)) data = { - 'user': user, - 'is_self': request.user.id == user.id, - 'following': user.following.all(), + "user": user, + "is_self": request.user.id == user.id, + "following": user.following.all(), } - return TemplateResponse(request, 'user/following.html', data) + return TemplateResponse(request, "user/following.html", data) -@method_decorator(login_required, name='dispatch') +@method_decorator(login_required, name="dispatch") class EditUser(View): - ''' edit user view ''' + """ edit user view """ + def get(self, request): - ''' edit profile page for a user ''' + """ edit profile page for a user """ data = { - 'form': forms.EditUserForm(instance=request.user), - 'user': request.user, + "form": forms.EditUserForm(instance=request.user), + "user": request.user, } - return TemplateResponse(request, 'preferences/edit_user.html', data) + return TemplateResponse(request, "preferences/edit_user.html", data) def post(self, request): - ''' les get fancy with images ''' - form = forms.EditUserForm( - request.POST, request.FILES, instance=request.user) + """ les get fancy with images """ + form = forms.EditUserForm(request.POST, request.FILES, instance=request.user) if not form.is_valid(): - data = {'form': form, 'user': request.user} - return TemplateResponse(request, 'preferences/edit_user.html', data) + data = {"form": form, "user": request.user} + return TemplateResponse(request, "preferences/edit_user.html", data) user = form.save(commit=False) - if 'avatar' in form.files: + if "avatar" in form.files: # crop and resize avatar upload - image = Image.open(form.files['avatar']) + image = Image.open(form.files["avatar"]) image = crop_avatar(image) # set the name to a hash - extension = form.files['avatar'].name.split('.')[-1] - filename = '%s.%s' % (uuid4(), extension) + extension = form.files["avatar"].name.split(".")[-1] + filename = "%s.%s" % (uuid4(), extension) user.avatar.save(filename, image) user.save() @@ -174,22 +180,27 @@ class EditUser(View): def crop_avatar(image): - ''' reduce the size and make an avatar square ''' + """ reduce the size and make an avatar square """ target_size = 120 width, height = image.size - thumbnail_scale = height / (width / target_size) if height > width \ + thumbnail_scale = ( + height / (width / target_size) + if height > width else width / (height / target_size) + ) image.thumbnail([thumbnail_scale, thumbnail_scale]) width, height = image.size width_diff = width - target_size height_diff = height - target_size - cropped = image.crop(( - int(width_diff / 2), - int(height_diff / 2), - int(width - (width_diff / 2)), - int(height - (height_diff / 2)) - )) + cropped = image.crop( + ( + int(width_diff / 2), + int(height_diff / 2), + int(width - (width_diff / 2)), + int(height - (height_diff / 2)), + ) + ) output = BytesIO() cropped.save(output, format=image.format) return ContentFile(output.getvalue()) diff --git a/bookwyrm/wellknown.py b/bookwyrm/wellknown.py index 1f6d4ccfb..eb0148082 100644 --- a/bookwyrm/wellknown.py +++ b/bookwyrm/wellknown.py @@ -1,4 +1,4 @@ -''' responds to various requests to /.well-know ''' +""" responds to various requests to /.well-know """ from dateutil.relativedelta import relativedelta from django.http import HttpResponseNotFound @@ -10,50 +10,54 @@ from bookwyrm.settings import DOMAIN, VERSION def webfinger(request): - ''' allow other servers to ask about a user ''' - if request.method != 'GET': + """ allow other servers to ask about a user """ + if request.method != "GET": return HttpResponseNotFound() - resource = request.GET.get('resource') - if not resource and not resource.startswith('acct:'): + resource = request.GET.get("resource") + if not resource and not resource.startswith("acct:"): return HttpResponseNotFound() - username = resource.replace('acct:', '') + username = resource.replace("acct:", "") try: user = models.User.objects.get(username=username) except models.User.DoesNotExist: - return HttpResponseNotFound('No account found') + return HttpResponseNotFound("No account found") - return JsonResponse({ - 'subject': 'acct:%s' % (user.username), - 'links': [ - { - 'rel': 'self', - 'type': 'application/activity+json', - 'href': user.remote_id - } - ] - }) + return JsonResponse( + { + "subject": "acct:%s" % (user.username), + "links": [ + { + "rel": "self", + "type": "application/activity+json", + "href": user.remote_id, + } + ], + } + ) def nodeinfo_pointer(request): - ''' direct servers to nodeinfo ''' - if request.method != 'GET': + """ direct servers to nodeinfo """ + if request.method != "GET": return HttpResponseNotFound() - return JsonResponse({ - 'links': [ - { - 'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0', - 'href': 'https://%s/nodeinfo/2.0' % DOMAIN - } - ] - }) + return JsonResponse( + { + "links": [ + { + "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0", + "href": "https://%s/nodeinfo/2.0" % DOMAIN, + } + ] + } + ) def nodeinfo(request): - ''' basic info about the server ''' - if request.method != 'GET': + """ basic info about the server """ + if request.method != "GET": return HttpResponseNotFound() status_count = models.Status.objects.filter(user__local=True).count() @@ -61,70 +65,65 @@ def nodeinfo(request): month_ago = timezone.now() - relativedelta(months=1) last_month_count = models.User.objects.filter( - local=True, - last_active_date__gt=month_ago + local=True, last_active_date__gt=month_ago ).count() six_months_ago = timezone.now() - relativedelta(months=6) six_month_count = models.User.objects.filter( - local=True, - last_active_date__gt=six_months_ago + local=True, last_active_date__gt=six_months_ago ).count() site = models.SiteSettings.get() - return JsonResponse({ - 'version': '2.0', - 'software': { - 'name': 'bookwyrm', - 'version': VERSION - }, - 'protocols': [ - 'activitypub' - ], - 'usage': { - 'users': { - 'total': user_count, - 'activeMonth': last_month_count, - 'activeHalfyear': six_month_count, + return JsonResponse( + { + "version": "2.0", + "software": {"name": "bookwyrm", "version": VERSION}, + "protocols": ["activitypub"], + "usage": { + "users": { + "total": user_count, + "activeMonth": last_month_count, + "activeHalfyear": six_month_count, + }, + "localPosts": status_count, }, - 'localPosts': status_count, - }, - 'openRegistrations': site.allow_registration, - }) + "openRegistrations": site.allow_registration, + } + ) def instance_info(request): - ''' let's talk about your cool unique instance ''' - if request.method != 'GET': + """ let's talk about your cool unique instance """ + if request.method != "GET": return HttpResponseNotFound() user_count = models.User.objects.filter(local=True).count() status_count = models.Status.objects.filter(user__local=True).count() site = models.SiteSettings.get() - return JsonResponse({ - 'uri': DOMAIN, - 'title': site.name, - 'short_description': '', - 'description': site.instance_description, - 'version': '0.0.1', - 'stats': { - 'user_count': user_count, - 'status_count': status_count, - }, - 'thumbnail': 'https://%s/static/images/logo.png' % DOMAIN, - 'languages': [ - 'en' - ], - 'registrations': site.allow_registration, - 'approval_required': False, - }) + return JsonResponse( + { + "uri": DOMAIN, + "title": site.name, + "short_description": "", + "description": site.instance_description, + "version": "0.0.1", + "stats": { + "user_count": user_count, + "status_count": status_count, + }, + "thumbnail": "https://%s/static/images/logo.png" % DOMAIN, + "languages": ["en"], + "registrations": site.allow_registration, + "approval_required": False, + } + ) def peers(request): - ''' list of federated servers this instance connects with ''' - if request.method != 'GET': + """ list of federated servers this instance connects with """ + if request.method != "GET": return HttpResponseNotFound() - names = models.FederatedServer.objects.values_list('server_name', flat=True) + names = models.FederatedServer.objects.values_list("server_name", flat=True) return JsonResponse(list(names), safe=False) diff --git a/celerywyrm/__init__.py b/celerywyrm/__init__.py index 1c8fa15d1..fe0c87ff1 100644 --- a/celerywyrm/__init__.py +++ b/celerywyrm/__init__.py @@ -1,8 +1,8 @@ -''' we need this file to initialize celery ''' +""" we need this file to initialize celery """ from __future__ import absolute_import, unicode_literals # This will make sure the app is always imported when # Django starts so that shared_task will use this app. from .celery import app as celery_app -__all__ = ('celery_app',) +__all__ = ("celery_app",) diff --git a/celerywyrm/asgi.py b/celerywyrm/asgi.py index c03a6ec6b..0f0b00219 100644 --- a/celerywyrm/asgi.py +++ b/celerywyrm/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celerywyrm.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celerywyrm.settings") application = get_asgi_application() diff --git a/celerywyrm/celery.py b/celerywyrm/celery.py index 2937ef0fc..4af8e281d 100644 --- a/celerywyrm/celery.py +++ b/celerywyrm/celery.py @@ -1,4 +1,4 @@ -''' configures celery for task management ''' +""" configures celery for task management """ from __future__ import absolute_import, unicode_literals import os @@ -7,23 +7,22 @@ from . import settings # set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celerywyrm.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celerywyrm.settings") -app = Celery('celerywyrm') +app = Celery("celerywyrm") # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. -app.config_from_object('django.conf:settings', namespace='CELERY') +app.config_from_object("django.conf:settings", namespace="CELERY") # Load task modules from all registered Django app configs. app.autodiscover_tasks() -app.autodiscover_tasks(['bookwyrm'], related_name='activitypub.base_activity') -app.autodiscover_tasks(['bookwyrm'], related_name='broadcast') -app.autodiscover_tasks( - ['bookwyrm'], related_name='connectors.abstract_connector') -app.autodiscover_tasks(['bookwyrm'], related_name='emailing') -app.autodiscover_tasks(['bookwyrm'], related_name='goodreads_import') -app.autodiscover_tasks(['bookwyrm'], related_name='models.user') -app.autodiscover_tasks(['bookwyrm'], related_name='views.inbox') +app.autodiscover_tasks(["bookwyrm"], related_name="activitypub.base_activity") +app.autodiscover_tasks(["bookwyrm"], related_name="broadcast") +app.autodiscover_tasks(["bookwyrm"], related_name="connectors.abstract_connector") +app.autodiscover_tasks(["bookwyrm"], related_name="emailing") +app.autodiscover_tasks(["bookwyrm"], related_name="goodreads_import") +app.autodiscover_tasks(["bookwyrm"], related_name="models.user") +app.autodiscover_tasks(["bookwyrm"], related_name="views.inbox") diff --git a/celerywyrm/settings.py b/celerywyrm/settings.py index 92986d8e4..7591163b1 100644 --- a/celerywyrm/settings.py +++ b/celerywyrm/settings.py @@ -16,31 +16,31 @@ from environs import Env env = Env() # emailing -EMAIL_HOST = env('EMAIL_HOST') -EMAIL_PORT = env('EMAIL_PORT') -EMAIL_HOST_USER = env('EMAIL_HOST_USER') -EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD') -EMAIL_USE_TLS = env('EMAIL_USE_TLS') +EMAIL_HOST = env("EMAIL_HOST") +EMAIL_PORT = env("EMAIL_PORT") +EMAIL_HOST_USER = env("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") +EMAIL_USE_TLS = env("EMAIL_USE_TLS") # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # celery/rebbitmq -CELERY_BROKER_URL = env('CELERY_BROKER') -CELERY_ACCEPT_CONTENT = ['json'] -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_BACKEND = 'redis' +CELERY_BROKER_URL = env("CELERY_BROKER") +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_BACKEND = "redis" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '0a^0gpwjc1ap+lb$dinin=efc@e&_0%102$o3(>9e7lndiaw' +SECRET_KEY = "0a^0gpwjc1ap+lb$dinin=efc@e&_0%102$o3(>9e7lndiaw" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = env.bool('DEBUG', True) +DEBUG = env.bool("DEBUG", True) ALLOWED_HOSTS = [] @@ -49,71 +49,69 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'celerywyrm', - 'bookwyrm', - 'celery', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "celerywyrm", + "bookwyrm", + "celery", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'celerywyrm.urls' +ROOT_URLCONF = "celerywyrm.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'celerywyrm.wsgi.application' +WSGI_APPLICATION = "celerywyrm.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -BOOKWYRM_DATABASE_BACKEND = env('BOOKWYRM_DATABASE_BACKEND', 'postgres') +BOOKWYRM_DATABASE_BACKEND = env("BOOKWYRM_DATABASE_BACKEND", "postgres") BOOKWYRM_DBS = { - 'postgres': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': env('POSTGRES_DB', 'fedireads'), - 'USER': env('POSTGRES_USER', 'fedireads'), - 'PASSWORD': env('POSTGRES_PASSWORD', 'fedireads'), - 'HOST': env('POSTGRES_HOST', ''), - 'PORT': 5432 + "postgres": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": env("POSTGRES_DB", "fedireads"), + "USER": env("POSTGRES_USER", "fedireads"), + "PASSWORD": env("POSTGRES_PASSWORD", "fedireads"), + "HOST": env("POSTGRES_HOST", ""), + "PORT": 5432, + }, + "sqlite": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "fedireads.db"), }, - 'sqlite': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'fedireads.db') - } } -DATABASES = { - 'default': BOOKWYRM_DBS[BOOKWYRM_DATABASE_BACKEND] -} +DATABASES = {"default": BOOKWYRM_DBS[BOOKWYRM_DATABASE_BACKEND]} # Password validation @@ -121,16 +119,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -138,9 +136,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = False @@ -152,7 +150,7 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, env('STATIC_ROOT', 'static')) -MEDIA_URL = '/images/' -MEDIA_ROOT = os.path.join(BASE_DIR, env('MEDIA_ROOT', 'images')) +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static")) +MEDIA_URL = "/images/" +MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images")) diff --git a/celerywyrm/urls.py b/celerywyrm/urls.py index 21c8dee86..394c0ef0d 100644 --- a/celerywyrm/urls.py +++ b/celerywyrm/urls.py @@ -20,5 +20,5 @@ from django.urls import path from celerywyrm import settings urlpatterns = [ - path('admin/', admin.site.urls), + path("admin/", admin.site.urls), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/celerywyrm/wsgi.py b/celerywyrm/wsgi.py index 6de8d633f..7ccf36a9f 100644 --- a/celerywyrm/wsgi.py +++ b/celerywyrm/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celerywyrm.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celerywyrm.settings") application = get_wsgi_application()