Merge pull request #1004 from bookwyrm-social/black-update

New version of black, new whitespace
This commit is contained in:
Mouse Reeve 2021-04-26 09:42:23 -07:00 committed by GitHub
commit 7449f34a61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
152 changed files with 1289 additions and 1289 deletions

View file

@ -27,5 +27,5 @@ activity_objects = {c[0]: c[1] for c in cls_members if hasattr(c[1], "to_model")
def parse(activity_json): 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) return naive_parse(activity_objects, activity_json)

View file

@ -10,11 +10,11 @@ from bookwyrm.tasks import app
class ActivitySerializerError(ValueError): class ActivitySerializerError(ValueError):
""" routine problems serializing activitypub json """ """routine problems serializing activitypub json"""
class ActivityEncoder(JSONEncoder): class ActivityEncoder(JSONEncoder):
""" used to convert an Activity object into json """ """used to convert an Activity object into json"""
def default(self, o): def default(self, o):
return o.__dict__ return o.__dict__
@ -22,7 +22,7 @@ class ActivityEncoder(JSONEncoder):
@dataclass @dataclass
class Link: class Link:
""" for tagging a book in a status """ """for tagging a book in a status"""
href: str href: str
name: str name: str
@ -31,14 +31,14 @@ class Link:
@dataclass @dataclass
class Mention(Link): class Mention(Link):
""" a subtype of Link for mentioning an actor """ """a subtype of Link for mentioning an actor"""
type: str = "Mention" type: str = "Mention"
@dataclass @dataclass
class Signature: class Signature:
""" public key block """ """public key block"""
creator: str creator: str
created: str created: str
@ -47,7 +47,7 @@ class Signature:
def naive_parse(activity_objects, activity_json, serializer=None): def naive_parse(activity_objects, activity_json, serializer=None):
""" this navigates circular import issues """ """this navigates circular import issues"""
if not serializer: if not serializer:
if activity_json.get("publicKeyPem"): if activity_json.get("publicKeyPem"):
# ugh # ugh
@ -67,7 +67,7 @@ def naive_parse(activity_objects, activity_json, serializer=None):
@dataclass(init=False) @dataclass(init=False)
class ActivityObject: class ActivityObject:
""" actor activitypub json """ """actor activitypub json"""
id: str id: str
type: str type: str
@ -106,7 +106,7 @@ class ActivityObject:
setattr(self, field.name, value) setattr(self, field.name, value)
def to_model(self, model=None, instance=None, allow_create=True, save=True): 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) model = model or get_model_from_type(self.type)
# only reject statuses if we're potentially creating them # only reject statuses if we're potentially creating them
@ -181,7 +181,7 @@ class ActivityObject:
return instance return instance
def serialize(self): def serialize(self):
""" convert to dictionary with context attr """ """convert to dictionary with context attr"""
data = self.__dict__.copy() data = self.__dict__.copy()
# recursively serialize # recursively serialize
for (k, v) in data.items(): for (k, v) in data.items():
@ -200,7 +200,7 @@ class ActivityObject:
def set_related_field( def set_related_field(
model_name, origin_model_name, related_field_name, related_remote_id, data model_name, origin_model_name, related_field_name, related_remote_id, data
): ):
""" load reverse related fields (editions, attachments) without blocking """ """load reverse related fields (editions, attachments) without blocking"""
model = apps.get_model("bookwyrm.%s" % model_name, require_ready=True) model = apps.get_model("bookwyrm.%s" % model_name, require_ready=True)
origin_model = apps.get_model("bookwyrm.%s" % origin_model_name, require_ready=True) origin_model = apps.get_model("bookwyrm.%s" % origin_model_name, require_ready=True)
@ -236,7 +236,7 @@ def set_related_field(
def get_model_from_type(activity_type): 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() models = apps.get_models()
model = [ model = [
m m
@ -255,7 +255,7 @@ def get_model_from_type(activity_type):
def resolve_remote_id( def resolve_remote_id(
remote_id, model=None, refresh=False, save=True, get_activity=False remote_id, model=None, refresh=False, save=True, get_activity=False
): ):
""" take a remote_id and return an instance, creating if necessary """ """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 if model: # a bonus check we can do if we already know the model
result = model.find_existing_by_remote_id(remote_id) result = model.find_existing_by_remote_id(remote_id)
if result and not refresh: if result and not refresh:

View file

@ -8,7 +8,7 @@ from .image import Document
@dataclass(init=False) @dataclass(init=False)
class Book(ActivityObject): class Book(ActivityObject):
""" serializes an edition or work, abstract """ """serializes an edition or work, abstract"""
title: str title: str
lastEditedBy: str = None lastEditedBy: str = None
@ -35,7 +35,7 @@ class Book(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class Edition(Book): class Edition(Book):
""" Edition instance of a book object """ """Edition instance of a book object"""
work: str work: str
isbn10: str = "" isbn10: str = ""
@ -52,7 +52,7 @@ class Edition(Book):
@dataclass(init=False) @dataclass(init=False)
class Work(Book): class Work(Book):
""" work instance of a book object """ """work instance of a book object"""
lccn: str = "" lccn: str = ""
defaultEdition: str = "" defaultEdition: str = ""
@ -62,7 +62,7 @@ class Work(Book):
@dataclass(init=False) @dataclass(init=False)
class Author(ActivityObject): class Author(ActivityObject):
""" author of a book """ """author of a book"""
name: str name: str
lastEditedBy: str = None lastEditedBy: str = None

View file

@ -5,7 +5,7 @@ from .base_activity import ActivityObject
@dataclass(init=False) @dataclass(init=False)
class Document(ActivityObject): class Document(ActivityObject):
""" a document """ """a document"""
url: str url: str
name: str = "" name: str = ""
@ -15,6 +15,6 @@ class Document(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class Image(Document): class Image(Document):
""" an image """ """an image"""
type: str = "Image" type: str = "Image"

View file

@ -9,19 +9,19 @@ from .image import Document
@dataclass(init=False) @dataclass(init=False)
class Tombstone(ActivityObject): class Tombstone(ActivityObject):
""" the placeholder for a deleted status """ """the placeholder for a deleted status"""
type: str = "Tombstone" type: str = "Tombstone"
def to_model(self, *args, **kwargs): # pylint: disable=unused-argument def to_model(self, *args, **kwargs): # pylint: disable=unused-argument
""" this should never really get serialized, just searched for """ """this should never really get serialized, just searched for"""
model = apps.get_model("bookwyrm.Status") model = apps.get_model("bookwyrm.Status")
return model.find_existing_by_remote_id(self.id) return model.find_existing_by_remote_id(self.id)
@dataclass(init=False) @dataclass(init=False)
class Note(ActivityObject): class Note(ActivityObject):
""" Note activity """ """Note activity"""
published: str published: str
attributedTo: str attributedTo: str
@ -39,7 +39,7 @@ class Note(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class Article(Note): 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 name: str
type: str = "Article" type: str = "Article"
@ -47,14 +47,14 @@ class Article(Note):
@dataclass(init=False) @dataclass(init=False)
class GeneratedNote(Note): class GeneratedNote(Note):
""" just a re-typed note """ """just a re-typed note"""
type: str = "GeneratedNote" type: str = "GeneratedNote"
@dataclass(init=False) @dataclass(init=False)
class Comment(Note): class Comment(Note):
""" like a note but with a book """ """like a note but with a book"""
inReplyToBook: str inReplyToBook: str
type: str = "Comment" type: str = "Comment"
@ -62,7 +62,7 @@ class Comment(Note):
@dataclass(init=False) @dataclass(init=False)
class Quotation(Comment): class Quotation(Comment):
""" a quote and commentary on a book """ """a quote and commentary on a book"""
quote: str quote: str
type: str = "Quotation" type: str = "Quotation"
@ -70,7 +70,7 @@ class Quotation(Comment):
@dataclass(init=False) @dataclass(init=False)
class Review(Comment): class Review(Comment):
""" a full book review """ """a full book review"""
name: str = None name: str = None
rating: int = None rating: int = None
@ -79,7 +79,7 @@ class Review(Comment):
@dataclass(init=False) @dataclass(init=False)
class Rating(Comment): class Rating(Comment):
""" just a star rating """ """just a star rating"""
rating: int rating: int
content: str = None content: str = None

View file

@ -7,7 +7,7 @@ from .base_activity import ActivityObject
@dataclass(init=False) @dataclass(init=False)
class OrderedCollection(ActivityObject): class OrderedCollection(ActivityObject):
""" structure of an ordered collection activity """ """structure of an ordered collection activity"""
totalItems: int totalItems: int
first: str first: str
@ -19,7 +19,7 @@ class OrderedCollection(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class OrderedCollectionPrivate(OrderedCollection): class OrderedCollectionPrivate(OrderedCollection):
""" an ordered collection with privacy settings """ """an ordered collection with privacy settings"""
to: List[str] = field(default_factory=lambda: []) to: List[str] = field(default_factory=lambda: [])
cc: List[str] = field(default_factory=lambda: []) cc: List[str] = field(default_factory=lambda: [])
@ -27,14 +27,14 @@ class OrderedCollectionPrivate(OrderedCollection):
@dataclass(init=False) @dataclass(init=False)
class Shelf(OrderedCollectionPrivate): class Shelf(OrderedCollectionPrivate):
""" structure of an ordered collection activity """ """structure of an ordered collection activity"""
type: str = "Shelf" type: str = "Shelf"
@dataclass(init=False) @dataclass(init=False)
class BookList(OrderedCollectionPrivate): class BookList(OrderedCollectionPrivate):
""" structure of an ordered collection activity """ """structure of an ordered collection activity"""
summary: str = None summary: str = None
curation: str = "closed" curation: str = "closed"
@ -43,7 +43,7 @@ class BookList(OrderedCollectionPrivate):
@dataclass(init=False) @dataclass(init=False)
class OrderedCollectionPage(ActivityObject): class OrderedCollectionPage(ActivityObject):
""" structure of an ordered collection activity """ """structure of an ordered collection activity"""
partOf: str partOf: str
orderedItems: List orderedItems: List
@ -54,7 +54,7 @@ class OrderedCollectionPage(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class CollectionItem(ActivityObject): class CollectionItem(ActivityObject):
""" an item in a collection """ """an item in a collection"""
actor: str actor: str
type: str = "CollectionItem" type: str = "CollectionItem"
@ -62,7 +62,7 @@ class CollectionItem(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class ListItem(CollectionItem): class ListItem(CollectionItem):
""" a book on a list """ """a book on a list"""
book: str book: str
notes: str = None notes: str = None
@ -73,7 +73,7 @@ class ListItem(CollectionItem):
@dataclass(init=False) @dataclass(init=False)
class ShelfItem(CollectionItem): class ShelfItem(CollectionItem):
""" a book on a list """ """a book on a list"""
book: str book: str
type: str = "ShelfItem" type: str = "ShelfItem"

View file

@ -8,7 +8,7 @@ from .image import Image
@dataclass(init=False) @dataclass(init=False)
class PublicKey(ActivityObject): class PublicKey(ActivityObject):
""" public key block """ """public key block"""
owner: str owner: str
publicKeyPem: str publicKeyPem: str
@ -17,7 +17,7 @@ class PublicKey(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class Person(ActivityObject): class Person(ActivityObject):
""" actor activitypub json """ """actor activitypub json"""
preferredUsername: str preferredUsername: str
inbox: str inbox: str

View file

@ -9,13 +9,13 @@ from .ordered_collection import CollectionItem
@dataclass(init=False) @dataclass(init=False)
class Verb(ActivityObject): class Verb(ActivityObject):
"""generic fields for activities """ """generic fields for activities"""
actor: str actor: str
object: ActivityObject object: ActivityObject
def action(self): def action(self):
""" usually we just want to update and save """ """usually we just want to update and save"""
# self.object may return None if the object is invalid in an expected way # self.object may return None if the object is invalid in an expected way
# ie, Question type # ie, Question type
if self.object: if self.object:
@ -24,7 +24,7 @@ class Verb(ActivityObject):
@dataclass(init=False) @dataclass(init=False)
class Create(Verb): class Create(Verb):
""" Create activity """ """Create activity"""
to: List[str] to: List[str]
cc: List[str] = field(default_factory=lambda: []) cc: List[str] = field(default_factory=lambda: [])
@ -34,14 +34,14 @@ class Create(Verb):
@dataclass(init=False) @dataclass(init=False)
class Delete(Verb): class Delete(Verb):
""" Create activity """ """Create activity"""
to: List[str] to: List[str]
cc: List[str] = field(default_factory=lambda: []) cc: List[str] = field(default_factory=lambda: [])
type: str = "Delete" type: str = "Delete"
def action(self): def action(self):
""" find and delete the activity object """ """find and delete the activity object"""
if not self.object: if not self.object:
return return
@ -59,25 +59,25 @@ class Delete(Verb):
@dataclass(init=False) @dataclass(init=False)
class Update(Verb): class Update(Verb):
""" Update activity """ """Update activity"""
to: List[str] to: List[str]
type: str = "Update" type: str = "Update"
def action(self): def action(self):
""" update a model instance from the dataclass """ """update a model instance from the dataclass"""
if self.object: if self.object:
self.object.to_model(allow_create=False) self.object.to_model(allow_create=False)
@dataclass(init=False) @dataclass(init=False)
class Undo(Verb): class Undo(Verb):
""" Undo an activity """ """Undo an activity"""
type: str = "Undo" type: str = "Undo"
def action(self): def action(self):
""" find and remove the activity object """ """find and remove the activity object"""
if isinstance(self.object, str): if isinstance(self.object, str):
# it may be that sometihng should be done with these, but idk what # it may be that sometihng should be done with these, but idk what
# this seems just to be coming from pleroma # this seems just to be coming from pleroma
@ -103,64 +103,64 @@ class Undo(Verb):
@dataclass(init=False) @dataclass(init=False)
class Follow(Verb): class Follow(Verb):
""" Follow activity """ """Follow activity"""
object: str object: str
type: str = "Follow" type: str = "Follow"
def action(self): def action(self):
""" relationship save """ """relationship save"""
self.to_model() self.to_model()
@dataclass(init=False) @dataclass(init=False)
class Block(Verb): class Block(Verb):
""" Block activity """ """Block activity"""
object: str object: str
type: str = "Block" type: str = "Block"
def action(self): def action(self):
""" relationship save """ """relationship save"""
self.to_model() self.to_model()
@dataclass(init=False) @dataclass(init=False)
class Accept(Verb): class Accept(Verb):
""" Accept activity """ """Accept activity"""
object: Follow object: Follow
type: str = "Accept" type: str = "Accept"
def action(self): 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 = self.object.to_model(save=False, allow_create=False)
obj.accept() obj.accept()
@dataclass(init=False) @dataclass(init=False)
class Reject(Verb): class Reject(Verb):
""" Reject activity """ """Reject activity"""
object: Follow object: Follow
type: str = "Reject" type: str = "Reject"
def action(self): 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 = self.object.to_model(save=False, allow_create=False)
obj.reject() obj.reject()
@dataclass(init=False) @dataclass(init=False)
class Add(Verb): class Add(Verb):
"""Add activity """ """Add activity"""
target: ActivityObject target: ActivityObject
object: CollectionItem object: CollectionItem
type: str = "Add" type: str = "Add"
def action(self): def action(self):
""" figure out the target to assign the item to a collection """ """figure out the target to assign the item to a collection"""
target = resolve_remote_id(self.target) target = resolve_remote_id(self.target)
item = self.object.to_model(save=False) item = self.object.to_model(save=False)
setattr(item, item.collection_field, target) setattr(item, item.collection_field, target)
@ -169,12 +169,12 @@ class Add(Verb):
@dataclass(init=False) @dataclass(init=False)
class Remove(Add): class Remove(Add):
"""Remove activity """ """Remove activity"""
type: str = "Remove" type: str = "Remove"
def action(self): 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 = self.object.to_model(save=False, allow_create=False)
if obj: if obj:
obj.delete() obj.delete()
@ -182,19 +182,19 @@ class Remove(Add):
@dataclass(init=False) @dataclass(init=False)
class Like(Verb): class Like(Verb):
""" a user faving an object """ """a user faving an object"""
object: str object: str
type: str = "Like" type: str = "Like"
def action(self): def action(self):
""" like """ """like"""
self.to_model() self.to_model()
@dataclass(init=False) @dataclass(init=False)
class Announce(Verb): class Announce(Verb):
""" boosting a status """ """boosting a status"""
published: str published: str
to: List[str] = field(default_factory=lambda: []) to: List[str] = field(default_factory=lambda: [])
@ -203,5 +203,5 @@ class Announce(Verb):
type: str = "Announce" type: str = "Announce"
def action(self): def action(self):
""" boost """ """boost"""
self.to_model() self.to_model()

View file

@ -8,22 +8,22 @@ from bookwyrm.views.helpers import privacy_filter
class ActivityStream(RedisStore): class ActivityStream(RedisStore):
""" a category of activity stream (like home, local, federated) """ """a category of activity stream (like home, local, federated)"""
def stream_id(self, user): def stream_id(self, user):
""" the redis key for this user's instance of this stream """ """the redis key for this user's instance of this stream"""
return "{}-{}".format(user.id, self.key) return "{}-{}".format(user.id, self.key)
def unread_id(self, user): def unread_id(self, user):
""" the redis key for this user's unread count for this stream """ """the redis key for this user's unread count for this stream"""
return "{}-unread".format(self.stream_id(user)) return "{}-unread".format(self.stream_id(user))
def get_rank(self, obj): # pylint: disable=no-self-use def get_rank(self, obj): # pylint: disable=no-self-use
""" statuses are sorted by date published """ """statuses are sorted by date published"""
return obj.published_date.timestamp() return obj.published_date.timestamp()
def add_status(self, status): def add_status(self, status):
""" add a status to users' feeds """ """add a status to users' feeds"""
# the pipeline contains all the add-to-stream activities # the pipeline contains all the add-to-stream activities
pipeline = self.add_object_to_related_stores(status, execute=False) pipeline = self.add_object_to_related_stores(status, execute=False)
@ -35,19 +35,19 @@ class ActivityStream(RedisStore):
pipeline.execute() pipeline.execute()
def add_user_statuses(self, viewer, user): def add_user_statuses(self, viewer, user):
""" add a user's statuses to another user's feed """ """add a user's statuses to another user's feed"""
# only add the statuses that the viewer should be able to see (ie, not dms) # only add the statuses that the viewer should be able to see (ie, not dms)
statuses = privacy_filter(viewer, user.status_set.all()) statuses = privacy_filter(viewer, user.status_set.all())
self.bulk_add_objects_to_store(statuses, self.stream_id(viewer)) self.bulk_add_objects_to_store(statuses, self.stream_id(viewer))
def remove_user_statuses(self, viewer, user): def remove_user_statuses(self, viewer, user):
""" remove a user's status from another user's feed """ """remove a user's status from another user's feed"""
# remove all so that followers only statuses are removed # remove all so that followers only statuses are removed
statuses = user.status_set.all() statuses = user.status_set.all()
self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer)) self.bulk_remove_objects_from_store(statuses, self.stream_id(viewer))
def get_activity_stream(self, user): def get_activity_stream(self, user):
""" load the statuses to be displayed """ """load the statuses to be displayed"""
# clear unreads for this feed # clear unreads for this feed
r.set(self.unread_id(user), 0) r.set(self.unread_id(user), 0)
@ -59,15 +59,15 @@ class ActivityStream(RedisStore):
) )
def get_unread_count(self, user): def get_unread_count(self, user):
""" get the unread status count for this user's feed """ """get the unread status count for this user's feed"""
return int(r.get(self.unread_id(user)) or 0) return int(r.get(self.unread_id(user)) or 0)
def populate_streams(self, user): def populate_streams(self, user):
""" go from zero to a timeline """ """go from zero to a timeline"""
self.populate_store(self.stream_id(user)) self.populate_store(self.stream_id(user))
def get_audience(self, status): # pylint: disable=no-self-use def get_audience(self, status): # pylint: disable=no-self-use
""" given a status, what users should see it """ """given a status, what users should see it"""
# direct messages don't appeard in feeds, direct comments/reviews/etc do # direct messages don't appeard in feeds, direct comments/reviews/etc do
if status.privacy == "direct" and status.status_type == "Note": if status.privacy == "direct" and status.status_type == "Note":
return [] return []
@ -98,7 +98,7 @@ class ActivityStream(RedisStore):
return [self.stream_id(u) for u in self.get_audience(obj)] return [self.stream_id(u) for u in self.get_audience(obj)]
def get_statuses_for_user(self, user): # pylint: disable=no-self-use def get_statuses_for_user(self, user): # pylint: disable=no-self-use
""" given a user, what statuses should they see on this stream """ """given a user, what statuses should they see on this stream"""
return privacy_filter( return privacy_filter(
user, user,
models.Status.objects.select_subclasses(), models.Status.objects.select_subclasses(),
@ -111,7 +111,7 @@ class ActivityStream(RedisStore):
class HomeStream(ActivityStream): class HomeStream(ActivityStream):
""" users you follow """ """users you follow"""
key = "home" key = "home"
@ -134,7 +134,7 @@ class HomeStream(ActivityStream):
class LocalStream(ActivityStream): class LocalStream(ActivityStream):
""" users you follow """ """users you follow"""
key = "local" key = "local"
@ -154,7 +154,7 @@ class LocalStream(ActivityStream):
class FederatedStream(ActivityStream): class FederatedStream(ActivityStream):
""" users you follow """ """users you follow"""
key = "federated" key = "federated"
@ -182,7 +182,7 @@ streams = {
@receiver(signals.post_save) @receiver(signals.post_save)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def add_status_on_create(sender, instance, created, *args, **kwargs): def add_status_on_create(sender, instance, created, *args, **kwargs):
""" add newly created statuses to activity feeds """ """add newly created statuses to activity feeds"""
# we're only interested in new statuses # we're only interested in new statuses
if not issubclass(sender, models.Status): if not issubclass(sender, models.Status):
return return
@ -203,7 +203,7 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
@receiver(signals.post_delete, sender=models.Boost) @receiver(signals.post_delete, sender=models.Boost)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def remove_boost_on_delete(sender, instance, *args, **kwargs): def remove_boost_on_delete(sender, instance, *args, **kwargs):
""" boosts are deleted """ """boosts are deleted"""
# we're only interested in new statuses # we're only interested in new statuses
for stream in streams.values(): for stream in streams.values():
stream.remove_object_from_related_stores(instance) stream.remove_object_from_related_stores(instance)
@ -212,7 +212,7 @@ def remove_boost_on_delete(sender, instance, *args, **kwargs):
@receiver(signals.post_save, sender=models.UserFollows) @receiver(signals.post_save, sender=models.UserFollows)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def add_statuses_on_follow(sender, instance, created, *args, **kwargs): def add_statuses_on_follow(sender, instance, created, *args, **kwargs):
""" add a newly followed user's statuses to feeds """ """add a newly followed user's statuses to feeds"""
if not created or not instance.user_subject.local: if not created or not instance.user_subject.local:
return return
HomeStream().add_user_statuses(instance.user_subject, instance.user_object) HomeStream().add_user_statuses(instance.user_subject, instance.user_object)
@ -221,7 +221,7 @@ def add_statuses_on_follow(sender, instance, created, *args, **kwargs):
@receiver(signals.post_delete, sender=models.UserFollows) @receiver(signals.post_delete, sender=models.UserFollows)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def remove_statuses_on_unfollow(sender, instance, *args, **kwargs): def remove_statuses_on_unfollow(sender, instance, *args, **kwargs):
""" remove statuses from a feed on unfollow """ """remove statuses from a feed on unfollow"""
if not instance.user_subject.local: if not instance.user_subject.local:
return return
HomeStream().remove_user_statuses(instance.user_subject, instance.user_object) HomeStream().remove_user_statuses(instance.user_subject, instance.user_object)
@ -230,7 +230,7 @@ def remove_statuses_on_unfollow(sender, instance, *args, **kwargs):
@receiver(signals.post_save, sender=models.UserBlocks) @receiver(signals.post_save, sender=models.UserBlocks)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def remove_statuses_on_block(sender, instance, *args, **kwargs): def remove_statuses_on_block(sender, instance, *args, **kwargs):
""" remove statuses from all feeds on block """ """remove statuses from all feeds on block"""
# blocks apply ot all feeds # blocks apply ot all feeds
if instance.user_subject.local: if instance.user_subject.local:
for stream in streams.values(): for stream in streams.values():
@ -245,7 +245,7 @@ def remove_statuses_on_block(sender, instance, *args, **kwargs):
@receiver(signals.post_delete, sender=models.UserBlocks) @receiver(signals.post_delete, sender=models.UserBlocks)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def add_statuses_on_unblock(sender, instance, *args, **kwargs): def add_statuses_on_unblock(sender, instance, *args, **kwargs):
""" remove statuses from all feeds on block """ """remove statuses from all feeds on block"""
public_streams = [LocalStream(), FederatedStream()] public_streams = [LocalStream(), FederatedStream()]
# add statuses back to streams with statuses from anyone # add statuses back to streams with statuses from anyone
if instance.user_subject.local: if instance.user_subject.local:
@ -261,7 +261,7 @@ def add_statuses_on_unblock(sender, instance, *args, **kwargs):
@receiver(signals.post_save, sender=models.User) @receiver(signals.post_save, sender=models.User)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def populate_streams_on_account_create(sender, instance, created, *args, **kwargs): def populate_streams_on_account_create(sender, instance, created, *args, **kwargs):
""" build a user's feeds when they join """ """build a user's feeds when they join"""
if not created or not instance.local: if not created or not instance.local:
return return

View file

@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
class AbstractMinimalConnector(ABC): class AbstractMinimalConnector(ABC):
""" just the bare bones, for other bookwyrm instances """ """just the bare bones, for other bookwyrm instances"""
def __init__(self, identifier): def __init__(self, identifier):
# load connector settings # load connector settings
@ -39,7 +39,7 @@ class AbstractMinimalConnector(ABC):
setattr(self, field, getattr(info, field)) setattr(self, field, getattr(info, field))
def search(self, query, min_confidence=None): def search(self, query, min_confidence=None):
""" free text search """ """free text search"""
params = {} params = {}
if min_confidence: if min_confidence:
params["min_confidence"] = min_confidence params["min_confidence"] = min_confidence
@ -55,7 +55,7 @@ class AbstractMinimalConnector(ABC):
return results return results
def isbn_search(self, query): def isbn_search(self, query):
""" isbn search """ """isbn search"""
params = {} params = {}
data = get_data( data = get_data(
"%s%s" % (self.isbn_search_url, query), "%s%s" % (self.isbn_search_url, query),
@ -70,27 +70,27 @@ class AbstractMinimalConnector(ABC):
@abstractmethod @abstractmethod
def get_or_create_book(self, remote_id): 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 @abstractmethod
def parse_search_data(self, data): 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 @abstractmethod
def format_search_result(self, search_result): def format_search_result(self, search_result):
""" create a SearchResult obj from json """ """create a SearchResult obj from json"""
@abstractmethod @abstractmethod
def parse_isbn_search_data(self, data): 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 @abstractmethod
def format_isbn_search_result(self, search_result): def format_isbn_search_result(self, search_result):
""" create a SearchResult obj from json """ """create a SearchResult obj from json"""
class AbstractConnector(AbstractMinimalConnector): class AbstractConnector(AbstractMinimalConnector):
""" generic book data connector """ """generic book data connector"""
def __init__(self, identifier): def __init__(self, identifier):
super().__init__(identifier) super().__init__(identifier)
@ -99,14 +99,14 @@ class AbstractConnector(AbstractMinimalConnector):
self.book_mappings = [] self.book_mappings = []
def is_available(self): 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.max_query_count is not None:
if self.connector.query_count >= self.max_query_count: if self.connector.query_count >= self.max_query_count:
return False return False
return True return True
def get_or_create_book(self, remote_id): 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 # first, check if we have the origin_id saved
existing = models.Edition.find_existing_by_remote_id( existing = models.Edition.find_existing_by_remote_id(
remote_id remote_id
@ -151,7 +151,7 @@ class AbstractConnector(AbstractMinimalConnector):
return edition return edition
def create_edition_from_data(self, work, edition_data): 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 = 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_activity = activitypub.Edition(**mapped_data)
@ -171,7 +171,7 @@ class AbstractConnector(AbstractMinimalConnector):
return edition return edition
def get_or_create_author(self, remote_id): def get_or_create_author(self, remote_id):
""" load that author """ """load that author"""
existing = models.Author.find_existing_by_remote_id(remote_id) existing = models.Author.find_existing_by_remote_id(remote_id)
if existing: if existing:
return existing return existing
@ -189,23 +189,23 @@ class AbstractConnector(AbstractMinimalConnector):
@abstractmethod @abstractmethod
def is_work_data(self, data): def is_work_data(self, data):
""" differentiate works and editions """ """differentiate works and editions"""
@abstractmethod @abstractmethod
def get_edition_from_work_data(self, data): def get_edition_from_work_data(self, data):
""" every work needs at least one edition """ """every work needs at least one edition"""
@abstractmethod @abstractmethod
def get_work_from_edition_data(self, data): def get_work_from_edition_data(self, data):
""" every edition needs a work """ """every edition needs a work"""
@abstractmethod @abstractmethod
def get_authors_from_data(self, data): def get_authors_from_data(self, data):
""" load author data """ """load author data"""
@abstractmethod @abstractmethod
def expand_book_data(self, book): def expand_book_data(self, book):
""" get more info on a book """ """get more info on a book"""
def dict_from_mappings(data, mappings): def dict_from_mappings(data, mappings):
@ -218,7 +218,7 @@ def dict_from_mappings(data, mappings):
def get_data(url, params=None): def get_data(url, params=None):
""" wrapper for request.get """ """wrapper for request.get"""
# check if the url is blocked # check if the url is blocked
if models.FederatedServer.is_blocked(url): if models.FederatedServer.is_blocked(url):
raise ConnectorException( raise ConnectorException(
@ -250,7 +250,7 @@ def get_data(url, params=None):
def get_image(url): def get_image(url):
""" wrapper for requesting an image """ """wrapper for requesting an image"""
try: try:
resp = requests.get( resp = requests.get(
url, url,
@ -268,7 +268,7 @@ def get_image(url):
@dataclass @dataclass
class SearchResult: class SearchResult:
""" standardized search result object """ """standardized search result object"""
title: str title: str
key: str key: str
@ -284,14 +284,14 @@ class SearchResult:
) )
def json(self): def json(self):
""" serialize a connector for json response """ """serialize a connector for json response"""
serialized = asdict(self) serialized = asdict(self)
del serialized["connector"] del serialized["connector"]
return serialized return serialized
class Mapping: 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): def __init__(self, local_field, remote_field=None, formatter=None):
noop = lambda x: x noop = lambda x: x
@ -301,7 +301,7 @@ class Mapping:
self.formatter = formatter or noop self.formatter = formatter or noop
def get_value(self, data): 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) value = data.get(self.remote_field)
if not value: if not value:
return None return None

View file

@ -4,7 +4,7 @@ from .abstract_connector import AbstractMinimalConnector, SearchResult
class Connector(AbstractMinimalConnector): class Connector(AbstractMinimalConnector):
""" this is basically just for search """ """this is basically just for search"""
def get_or_create_book(self, remote_id): def get_or_create_book(self, remote_id):
edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) edition = activitypub.resolve_remote_id(remote_id, model=models.Edition)

View file

@ -16,11 +16,11 @@ logger = logging.getLogger(__name__)
class ConnectorException(HTTPError): 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): def search(query, min_confidence=0.1):
""" find books based on arbitary keywords """ """find books based on arbitary keywords"""
if not query: if not query:
return [] return []
results = [] results = []
@ -68,19 +68,19 @@ def search(query, min_confidence=0.1):
def local_search(query, min_confidence=0.1, raw=False): 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)) connector = load_connector(models.Connector.objects.get(local=True))
return connector.search(query, min_confidence=min_confidence, raw=raw) return connector.search(query, min_confidence=min_confidence, raw=raw)
def isbn_local_search(query, raw=False): 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)) connector = load_connector(models.Connector.objects.get(local=True))
return connector.isbn_search(query, raw=raw) return connector.isbn_search(query, raw=raw)
def first_search_result(query, min_confidence=0.1): 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(): for connector in get_connectors():
result = connector.search(query, min_confidence=min_confidence) result = connector.search(query, min_confidence=min_confidence)
if result: if result:
@ -89,13 +89,13 @@ def first_search_result(query, min_confidence=0.1):
def get_connectors(): def get_connectors():
""" load all connectors """ """load all connectors"""
for info in models.Connector.objects.order_by("priority").all(): for info in models.Connector.objects.order_by("priority").all():
yield load_connector(info) yield load_connector(info)
def get_or_create_connector(remote_id): def get_or_create_connector(remote_id):
""" get the connector related to the object's server """ """get the connector related to the object's server"""
url = urlparse(remote_id) url = urlparse(remote_id)
identifier = url.netloc identifier = url.netloc
if not identifier: if not identifier:
@ -119,7 +119,7 @@ def get_or_create_connector(remote_id):
@app.task @app.task
def load_more_data(connector_id, book_id): 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_info = models.Connector.objects.get(id=connector_id)
connector = load_connector(connector_info) connector = load_connector(connector_info)
book = models.Book.objects.select_subclasses().get(id=book_id) book = models.Book.objects.select_subclasses().get(id=book_id)
@ -127,7 +127,7 @@ def load_more_data(connector_id, book_id):
def load_connector(connector_info): def load_connector(connector_info):
""" instantiate the connector class """ """instantiate the connector class"""
connector = importlib.import_module( connector = importlib.import_module(
"bookwyrm.connectors.%s" % connector_info.connector_file "bookwyrm.connectors.%s" % connector_info.connector_file
) )
@ -137,6 +137,6 @@ def load_connector(connector_info):
@receiver(signals.post_save, sender="bookwyrm.FederatedServer") @receiver(signals.post_save, sender="bookwyrm.FederatedServer")
# pylint: disable=unused-argument # pylint: disable=unused-argument
def create_connector(sender, instance, created, *args, **kwargs): def create_connector(sender, instance, created, *args, **kwargs):
""" create a connector to an external bookwyrm server """ """create a connector to an external bookwyrm server"""
if instance.application_type == "bookwyrm": if instance.application_type == "bookwyrm":
get_or_create_connector("https://{:s}".format(instance.server_name)) get_or_create_connector("https://{:s}".format(instance.server_name))

View file

@ -9,7 +9,7 @@ from .openlibrary_languages import languages
class Connector(AbstractConnector): class Connector(AbstractConnector):
""" instantiate a connector for OL """ """instantiate a connector for OL"""
def __init__(self, identifier): def __init__(self, identifier):
super().__init__(identifier) super().__init__(identifier)
@ -59,7 +59,7 @@ class Connector(AbstractConnector):
] ]
def get_remote_id_from_data(self, data): 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: try:
key = data["key"] key = data["key"]
except KeyError: except KeyError:
@ -87,7 +87,7 @@ class Connector(AbstractConnector):
return get_data(url) return get_data(url)
def get_authors_from_data(self, data): def get_authors_from_data(self, data):
""" parse author json and load or create authors """ """parse author json and load or create authors"""
for author_blob in data.get("authors", []): for author_blob in data.get("authors", []):
author_blob = author_blob.get("author", author_blob) author_blob = author_blob.get("author", author_blob)
# this id is "/authors/OL1234567A" # this id is "/authors/OL1234567A"
@ -99,7 +99,7 @@ class Connector(AbstractConnector):
yield author yield author
def get_cover_url(self, cover_blob, size="L"): def get_cover_url(self, cover_blob, size="L"):
""" ask openlibrary for the cover """ """ask openlibrary for the cover"""
if not cover_blob: if not cover_blob:
return None return None
cover_id = cover_blob[0] cover_id = cover_blob[0]
@ -141,7 +141,7 @@ class Connector(AbstractConnector):
) )
def load_edition_data(self, olkey): def load_edition_data(self, olkey):
""" query openlibrary for editions of a work """ """query openlibrary for editions of a work"""
url = "%s/works/%s/editions" % (self.books_url, olkey) url = "%s/works/%s/editions" % (self.books_url, olkey)
return get_data(url) return get_data(url)
@ -166,7 +166,7 @@ class Connector(AbstractConnector):
def ignore_edition(edition_data): 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 # an isbn, we love to see it
if edition_data.get("isbn_13") or edition_data.get("isbn_10"): if edition_data.get("isbn_13") or edition_data.get("isbn_10"):
return False return False
@ -185,19 +185,19 @@ def ignore_edition(edition_data):
def get_description(description_blob): 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): if isinstance(description_blob, dict):
return description_blob.get("value") return description_blob.get("value")
return description_blob return description_blob
def get_openlibrary_key(key): def get_openlibrary_key(key):
""" convert /books/OL27320736M into OL27320736M """ """convert /books/OL27320736M into OL27320736M"""
return key.split("/")[-1] return key.split("/")[-1]
def get_languages(language_blob): def get_languages(language_blob):
""" /language/eng -> English """ """/language/eng -> English"""
langs = [] langs = []
for lang in language_blob: for lang in language_blob:
langs.append(languages.get(lang.get("key", ""), None)) langs.append(languages.get(lang.get("key", ""), None))
@ -205,7 +205,7 @@ def get_languages(language_blob):
def pick_default_edition(options): def pick_default_edition(options):
""" favor physical copies with covers in english """ """favor physical copies with covers in english"""
if not options: if not options:
return None return None
if len(options) == 1: if len(options) == 1:

View file

@ -10,11 +10,11 @@ from .abstract_connector import AbstractConnector, SearchResult
class Connector(AbstractConnector): class Connector(AbstractConnector):
""" instantiate a connector """ """instantiate a connector"""
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def search(self, query, min_confidence=0.1, raw=False): def search(self, query, min_confidence=0.1, raw=False):
""" search your local database """ """search your local database"""
if not query: if not query:
return [] return []
# first, try searching unqiue identifiers # first, try searching unqiue identifiers
@ -35,7 +35,7 @@ class Connector(AbstractConnector):
return search_results return search_results
def isbn_search(self, query, raw=False): def isbn_search(self, query, raw=False):
""" search your local database """ """search your local database"""
if not query: if not query:
return [] return []
@ -87,11 +87,11 @@ class Connector(AbstractConnector):
return None return None
def parse_isbn_search_data(self, data): 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 return data
def parse_search_data(self, 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 return data
def expand_book_data(self, book): def expand_book_data(self, book):
@ -99,7 +99,7 @@ class Connector(AbstractConnector):
def search_identifiers(query): def search_identifiers(query):
""" tries remote_id, isbn; defined as dedupe fields on the model """ """tries remote_id, isbn; defined as dedupe fields on the model"""
filters = [ filters = [
{f.name: query} {f.name: query}
for f in models.Edition._meta.get_fields() for f in models.Edition._meta.get_fields()
@ -115,7 +115,7 @@ def search_identifiers(query):
def search_title_author(query, min_confidence): def search_title_author(query, min_confidence):
""" searches for title and author """ """searches for title and author"""
vector = ( vector = (
SearchVector("title", weight="A") SearchVector("title", weight="A")
+ SearchVector("subtitle", weight="B") + SearchVector("subtitle", weight="B")

View file

@ -3,5 +3,5 @@ from bookwyrm import models
def site_settings(request): # pylint: disable=unused-argument def site_settings(request): # pylint: disable=unused-argument
""" include the custom info about the site """ """include the custom info about the site"""
return {"site": models.SiteSettings.objects.get()} return {"site": models.SiteSettings.objects.get()}

View file

@ -8,7 +8,7 @@ from bookwyrm.settings import DOMAIN
def email_data(): def email_data():
""" fields every email needs """ """fields every email needs"""
site = models.SiteSettings.objects.get() site = models.SiteSettings.objects.get()
if site.logo_small: if site.logo_small:
logo_path = "/images/{}".format(site.logo_small.url) logo_path = "/images/{}".format(site.logo_small.url)
@ -24,14 +24,14 @@ def email_data():
def invite_email(invite_request): def invite_email(invite_request):
""" send out an invite code """ """send out an invite code"""
data = email_data() data = email_data()
data["invite_link"] = invite_request.invite.link data["invite_link"] = invite_request.invite.link
send_email.delay(invite_request.email, *format_email("invite", data)) send_email.delay(invite_request.email, *format_email("invite", data))
def password_reset_email(reset_code): def password_reset_email(reset_code):
""" generate a password reset email """ """generate a password reset email"""
data = email_data() data = email_data()
data["reset_link"] = reset_code.link data["reset_link"] = reset_code.link
data["user"] = reset_code.user.display_name data["user"] = reset_code.user.display_name
@ -39,7 +39,7 @@ def password_reset_email(reset_code):
def format_email(email_name, data): def format_email(email_name, data):
""" render the email templates """ """render the email templates"""
subject = ( subject = (
get_template("email/{}/subject.html".format(email_name)).render(data).strip() get_template("email/{}/subject.html".format(email_name)).render(data).strip()
) )
@ -58,7 +58,7 @@ def format_email(email_name, data):
@app.task @app.task
def send_email(recipient, subject, html_content, text_content): def send_email(recipient, subject, html_content, text_content):
""" use a task to send the email """ """use a task to send the email"""
email = EmailMultiAlternatives( email = EmailMultiAlternatives(
subject, text_content, settings.DEFAULT_FROM_EMAIL, [recipient] subject, text_content, settings.DEFAULT_FROM_EMAIL, [recipient]
) )

View file

@ -12,7 +12,7 @@ from bookwyrm import models
class CustomForm(ModelForm): class CustomForm(ModelForm):
""" add css classes to the forms """ """add css classes to the forms"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
css_classes = defaultdict(lambda: "") css_classes = defaultdict(lambda: "")
@ -198,7 +198,7 @@ class ImportForm(forms.Form):
class ExpiryWidget(widgets.Select): class ExpiryWidget(widgets.Select):
def value_from_datadict(self, data, files, name): 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) selected_string = super().value_from_datadict(data, files, name)
if selected_string == "day": if selected_string == "day":
@ -217,7 +217,7 @@ class ExpiryWidget(widgets.Select):
class InviteRequestForm(CustomForm): class InviteRequestForm(CustomForm):
def clean(self): def clean(self):
""" make sure the email isn't in use by a registered user """ """make sure the email isn't in use by a registered user"""
cleaned_data = super().clean() cleaned_data = super().clean()
email = cleaned_data.get("email") email = cleaned_data.get("email")
if email and models.User.objects.filter(email=email).exists(): if email and models.User.objects.filter(email=email).exists():

View file

@ -9,7 +9,7 @@ class GoodreadsImporter(Importer):
service = "GoodReads" service = "GoodReads"
def parse_fields(self, entry): def parse_fields(self, entry):
""" handle the specific fields in goodreads csvs """ """handle the specific fields in goodreads csvs"""
entry.update({"import_source": self.service}) entry.update({"import_source": self.service})
# add missing 'Date Started' field # add missing 'Date Started' field
entry.update({"Date Started": None}) entry.update({"Date Started": None})

View file

@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
class Importer: class Importer:
""" Generic class for csv data import from an outside service """ """Generic class for csv data import from an outside service"""
service = "Unknown" service = "Unknown"
delimiter = "," delimiter = ","
@ -18,7 +18,7 @@ class Importer:
mandatory_fields = ["Title", "Author"] mandatory_fields = ["Title", "Author"]
def create_job(self, user, csv_file, include_reviews, privacy): 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( job = ImportJob.objects.create(
user=user, include_reviews=include_reviews, privacy=privacy user=user, include_reviews=include_reviews, privacy=privacy
) )
@ -32,16 +32,16 @@ class Importer:
return job return job
def save_item(self, job, index, data): # pylint: disable=no-self-use def save_item(self, job, index, data): # pylint: disable=no-self-use
""" creates and saves an import item """ """creates and saves an import item"""
ImportItem(job=job, index=index, data=data).save() ImportItem(job=job, index=index, data=data).save()
def parse_fields(self, entry): def parse_fields(self, entry):
""" updates csv data with additional info """ """updates csv data with additional info"""
entry.update({"import_source": self.service}) entry.update({"import_source": self.service})
return entry return entry
def create_retry_job(self, user, original_job, items): 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( job = ImportJob.objects.create(
user=user, user=user,
include_reviews=original_job.include_reviews, include_reviews=original_job.include_reviews,
@ -53,7 +53,7 @@ class Importer:
return job return job
def start_import(self, job): def start_import(self, job):
""" initalizes a csv import job """ """initalizes a csv import job"""
result = import_data.delay(self.service, job.id) result = import_data.delay(self.service, job.id)
job.task_id = result.id job.task_id = result.id
job.save() job.save()
@ -61,7 +61,7 @@ class Importer:
@app.task @app.task
def import_data(source, job_id): 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) job = ImportJob.objects.get(id=job_id)
try: try:
for item in job.items.all(): for item in job.items.all():
@ -89,7 +89,7 @@ def import_data(source, job_id):
def handle_imported_book(source, user, item, include_reviews, privacy): 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): if isinstance(item.book, models.Work):
item.book = item.book.default_edition item.book = item.book.default_edition
if not item.book: if not item.book:

View file

@ -6,7 +6,7 @@ from . import Importer
class LibrarythingImporter(Importer): class LibrarythingImporter(Importer):
""" csv downloads from librarything """ """csv downloads from librarything"""
service = "LibraryThing" service = "LibraryThing"
delimiter = "\t" delimiter = "\t"
@ -15,7 +15,7 @@ class LibrarythingImporter(Importer):
mandatory_fields = ["Title", "Primary Author"] mandatory_fields = ["Title", "Primary Author"]
def parse_fields(self, entry): def parse_fields(self, entry):
""" custom parsing for librarything """ """custom parsing for librarything"""
data = {} data = {}
data["import_source"] = self.service data["import_source"] = self.service
data["Book Id"] = entry["Book Id"] data["Book Id"] = entry["Book Id"]

View file

@ -6,7 +6,7 @@ from bookwyrm import models
def update_related(canonical, obj): 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 # move related models to canonical
related_models = [ 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
@ -24,7 +24,7 @@ def update_related(canonical, obj):
def copy_data(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(): for data_field in obj._meta.get_fields():
if not hasattr(data_field, "activitypub_field"): if not hasattr(data_field, "activitypub_field"):
continue continue
@ -38,7 +38,7 @@ def copy_data(canonical, obj):
def dedupe_model(model): def dedupe_model(model):
""" combine duplicate editions and update related models """ """combine duplicate editions and update related models"""
fields = model._meta.get_fields() fields = model._meta.get_fields()
dedupe_fields = [ dedupe_fields = [
f for f in fields if hasattr(f, "deduplication_field") and f.deduplication_field f for f in fields if hasattr(f, "deduplication_field") and f.deduplication_field
@ -68,12 +68,12 @@ def dedupe_model(model):
class Command(BaseCommand): class Command(BaseCommand):
""" dedplucate allllll the book data models """ """dedplucate allllll the book data models"""
help = "merges duplicate book data" help = "merges duplicate book data"
# pylint: disable=no-self-use,unused-argument # pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options): def handle(self, *args, **options):
""" run deudplications """ """run deudplications"""
dedupe_model(models.Edition) dedupe_model(models.Edition)
dedupe_model(models.Work) dedupe_model(models.Work)
dedupe_model(models.Author) dedupe_model(models.Author)

View file

@ -10,15 +10,15 @@ r = redis.Redis(
def erase_streams(): def erase_streams():
""" throw the whole redis away """ """throw the whole redis away"""
r.flushall() r.flushall()
class Command(BaseCommand): class Command(BaseCommand):
""" delete activity streams for all users """ """delete activity streams for all users"""
help = "Delete all the user streams" help = "Delete all the user streams"
# pylint: disable=no-self-use,unused-argument # pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options): def handle(self, *args, **options):
""" flush all, baby """ """flush all, baby"""
erase_streams() erase_streams()

View file

@ -108,7 +108,7 @@ def init_connectors():
def init_federated_servers(): def init_federated_servers():
""" big no to nazis """ """big no to nazis"""
built_in_blocks = ["gab.ai", "gab.com"] built_in_blocks = ["gab.ai", "gab.com"]
for server in built_in_blocks: for server in built_in_blocks:
FederatedServer.objects.create( FederatedServer.objects.create(

View file

@ -10,7 +10,7 @@ r = redis.Redis(
def populate_streams(): def populate_streams():
""" build all the streams for all the users """ """build all the streams for all the users"""
users = models.User.objects.filter( users = models.User.objects.filter(
local=True, local=True,
is_active=True, is_active=True,
@ -21,10 +21,10 @@ def populate_streams():
class Command(BaseCommand): class Command(BaseCommand):
""" start all over with user streams """ """start all over with user streams"""
help = "Populate streams for all users" help = "Populate streams for all users"
# pylint: disable=no-self-use,unused-argument # pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options): def handle(self, *args, **options):
""" run feed builder """ """run feed builder"""
populate_streams() populate_streams()

View file

@ -5,7 +5,7 @@ from bookwyrm import models
def remove_editions(): def remove_editions():
""" combine duplicate editions and update related models """ """combine duplicate editions and update related models"""
# not in use # not in use
filters = { filters = {
"%s__isnull" % r.name: True for r in models.Edition._meta.related_objects "%s__isnull" % r.name: True for r in models.Edition._meta.related_objects
@ -33,10 +33,10 @@ def remove_editions():
class Command(BaseCommand): class Command(BaseCommand):
""" dedplucate allllll the book data models """ """dedplucate allllll the book data models"""
help = "merges duplicate book data" help = "merges duplicate book data"
# pylint: disable=no-self-use,unused-argument # pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options): def handle(self, *args, **options):
""" run deudplications """ """run deudplications"""
remove_editions() remove_editions()

View file

@ -8,7 +8,7 @@ from psycopg2.extras import execute_values
def convert_review_rating(app_registry, schema_editor): def convert_review_rating(app_registry, schema_editor):
""" take rating type Reviews and convert them to ReviewRatings """ """take rating type Reviews and convert them to ReviewRatings"""
db_alias = schema_editor.connection.alias db_alias = schema_editor.connection.alias
reviews = ( reviews = (
@ -29,7 +29,7 @@ VALUES %s""",
def unconvert_review_rating(app_registry, schema_editor): def unconvert_review_rating(app_registry, schema_editor):
""" undo the conversion from ratings back to reviews""" """undo the conversion from ratings back to reviews"""
# All we need to do to revert this is drop the table, which Django will do # All we need to do to revert this is drop the table, which Django will do
# on its own, as long as we have a valid reverse function. So, this is a # on its own, as long as we have a valid reverse function. So, this is a
# no-op function so Django will do its thing # no-op function so Django will do its thing

View file

@ -31,18 +31,18 @@ PropertyField = namedtuple("PropertyField", ("set_activity_from_field"))
def set_activity_from_property_field(activity, obj, field): def set_activity_from_property_field(activity, obj, field):
""" assign a model property value to the activity json """ """assign a model property value to the activity json"""
activity[field[1]] = getattr(obj, field[0]) activity[field[1]] = getattr(obj, field[0])
class ActivitypubMixin: class ActivitypubMixin:
""" add this mixin for models that are AP serializable """ """add this mixin for models that are AP serializable"""
activity_serializer = lambda: {} activity_serializer = lambda: {}
reverse_unfurl = False reverse_unfurl = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" collect some info on model fields """ """collect some info on model fields"""
self.image_fields = [] self.image_fields = []
self.many_to_many_fields = [] self.many_to_many_fields = []
self.simple_fields = [] # "simple" self.simple_fields = [] # "simple"
@ -85,7 +85,7 @@ class ActivitypubMixin:
@classmethod @classmethod
def find_existing_by_remote_id(cls, remote_id): def find_existing_by_remote_id(cls, remote_id):
""" look up a remote id in the db """ """look up a remote id in the db"""
return cls.find_existing({"id": remote_id}) return cls.find_existing({"id": remote_id})
@classmethod @classmethod
@ -126,7 +126,7 @@ class ActivitypubMixin:
return match.first() return match.first()
def broadcast(self, activity, sender, software=None): def broadcast(self, activity, sender, software=None):
""" send out an activity """ """send out an activity"""
broadcast_task.delay( broadcast_task.delay(
sender.id, sender.id,
json.dumps(activity, cls=activitypub.ActivityEncoder), json.dumps(activity, cls=activitypub.ActivityEncoder),
@ -134,7 +134,7 @@ class ActivitypubMixin:
) )
def get_recipients(self, software=None): 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 # 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 # is this activity owned by a user (statuses, lists, shelves), or is it
@ -182,20 +182,20 @@ class ActivitypubMixin:
return list(set(recipients)) return list(set(recipients))
def to_activity_dataclass(self): def to_activity_dataclass(self):
""" convert from a model to an activity """ """convert from a model to an activity"""
activity = generate_activity(self) activity = generate_activity(self)
return self.activity_serializer(**activity) return self.activity_serializer(**activity)
def to_activity(self, **kwargs): # pylint: disable=unused-argument def to_activity(self, **kwargs): # pylint: disable=unused-argument
""" convert from a model to a json activity """ """convert from a model to a json activity"""
return self.to_activity_dataclass().serialize() return self.to_activity_dataclass().serialize()
class ObjectMixin(ActivitypubMixin): 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): def save(self, *args, created=None, **kwargs):
""" broadcast created/updated/deleted objects as appropriate """ """broadcast created/updated/deleted objects as appropriate"""
broadcast = kwargs.get("broadcast", True) broadcast = kwargs.get("broadcast", True)
# this bonus kwarg would cause an error in the base save method # this bonus kwarg would cause an error in the base save method
if "broadcast" in kwargs: if "broadcast" in kwargs:
@ -254,7 +254,7 @@ class ObjectMixin(ActivitypubMixin):
self.broadcast(activity, user) self.broadcast(activity, user)
def to_create_activity(self, user, **kwargs): 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) activity_object = self.to_activity_dataclass(**kwargs)
signature = None signature = None
@ -280,7 +280,7 @@ class ObjectMixin(ActivitypubMixin):
).serialize() ).serialize()
def to_delete_activity(self, user): def to_delete_activity(self, user):
""" notice of deletion """ """notice of deletion"""
return activitypub.Delete( return activitypub.Delete(
id=self.remote_id + "/activity", id=self.remote_id + "/activity",
actor=user.remote_id, actor=user.remote_id,
@ -290,7 +290,7 @@ class ObjectMixin(ActivitypubMixin):
).serialize() ).serialize()
def to_update_activity(self, user): def to_update_activity(self, user):
""" wrapper for Updates to an activity """ """wrapper for Updates to an activity"""
activity_id = "%s#update/%s" % (self.remote_id, uuid4()) activity_id = "%s#update/%s" % (self.remote_id, uuid4())
return activitypub.Update( return activitypub.Update(
id=activity_id, id=activity_id,
@ -306,13 +306,13 @@ class OrderedCollectionPageMixin(ObjectMixin):
@property @property
def collection_remote_id(self): 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 return self.remote_id
def to_ordered_collection( def to_ordered_collection(
self, queryset, remote_id=None, page=False, collection_only=False, **kwargs self, queryset, remote_id=None, page=False, collection_only=False, **kwargs
): ):
""" an ordered collection of whatevers """ """an ordered collection of whatevers"""
if not queryset.ordered: if not queryset.ordered:
raise RuntimeError("queryset must be ordered") raise RuntimeError("queryset must be ordered")
@ -341,11 +341,11 @@ class OrderedCollectionPageMixin(ObjectMixin):
class OrderedCollectionMixin(OrderedCollectionPageMixin): class OrderedCollectionMixin(OrderedCollectionPageMixin):
""" extends activitypub models to work as ordered collections """ """extends activitypub models to work as ordered collections"""
@property @property
def collection_queryset(self): def collection_queryset(self):
""" usually an ordered collection model aggregates a different model """ """usually an ordered collection model aggregates a different model"""
raise NotImplementedError("Model must define collection_queryset") raise NotImplementedError("Model must define collection_queryset")
activity_serializer = activitypub.OrderedCollection activity_serializer = activitypub.OrderedCollection
@ -354,24 +354,24 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin):
return self.to_ordered_collection(self.collection_queryset, **kwargs) return self.to_ordered_collection(self.collection_queryset, **kwargs)
def to_activity(self, **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( return self.to_ordered_collection(
self.collection_queryset, **kwargs self.collection_queryset, **kwargs
).serialize() ).serialize()
class CollectionItemMixin(ActivitypubMixin): class CollectionItemMixin(ActivitypubMixin):
""" for items that are part of an (Ordered)Collection """ """for items that are part of an (Ordered)Collection"""
activity_serializer = activitypub.CollectionItem activity_serializer = activitypub.CollectionItem
def broadcast(self, activity, sender, software="bookwyrm"): def broadcast(self, activity, sender, software="bookwyrm"):
""" only send book collection updates to other bookwyrm instances """ """only send book collection updates to other bookwyrm instances"""
super().broadcast(activity, sender, software=software) super().broadcast(activity, sender, software=software)
@property @property
def privacy(self): def privacy(self):
""" inherit the privacy of the list, or direct if pending """ """inherit the privacy of the list, or direct if pending"""
collection_field = getattr(self, self.collection_field) collection_field = getattr(self, self.collection_field)
if self.approved: if self.approved:
return collection_field.privacy return collection_field.privacy
@ -379,7 +379,7 @@ class CollectionItemMixin(ActivitypubMixin):
@property @property
def recipients(self): def recipients(self):
""" the owner of the list is a direct recipient """ """the owner of the list is a direct recipient"""
collection_field = getattr(self, self.collection_field) collection_field = getattr(self, self.collection_field)
if collection_field.user.local: if collection_field.user.local:
# don't broadcast to yourself # don't broadcast to yourself
@ -387,7 +387,7 @@ class CollectionItemMixin(ActivitypubMixin):
return [collection_field.user] return [collection_field.user]
def save(self, *args, broadcast=True, **kwargs): def save(self, *args, broadcast=True, **kwargs):
""" broadcast updated """ """broadcast updated"""
# first off, we want to save normally no matter what # first off, we want to save normally no matter what
super().save(*args, **kwargs) super().save(*args, **kwargs)
@ -400,14 +400,14 @@ class CollectionItemMixin(ActivitypubMixin):
self.broadcast(activity, self.user) self.broadcast(activity, self.user)
def delete(self, *args, broadcast=True, **kwargs): def delete(self, *args, broadcast=True, **kwargs):
""" broadcast a remove activity """ """broadcast a remove activity"""
activity = self.to_remove_activity(self.user) activity = self.to_remove_activity(self.user)
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
if self.user.local and broadcast: if self.user.local and broadcast:
self.broadcast(activity, self.user) self.broadcast(activity, self.user)
def to_add_activity(self, user): def to_add_activity(self, user):
""" AP for shelving a book""" """AP for shelving a book"""
collection_field = getattr(self, self.collection_field) collection_field = getattr(self, self.collection_field)
return activitypub.Add( return activitypub.Add(
id="{:s}#add".format(collection_field.remote_id), id="{:s}#add".format(collection_field.remote_id),
@ -417,7 +417,7 @@ class CollectionItemMixin(ActivitypubMixin):
).serialize() ).serialize()
def to_remove_activity(self, user): def to_remove_activity(self, user):
""" AP for un-shelving a book""" """AP for un-shelving a book"""
collection_field = getattr(self, self.collection_field) collection_field = getattr(self, self.collection_field)
return activitypub.Remove( return activitypub.Remove(
id="{:s}#remove".format(collection_field.remote_id), id="{:s}#remove".format(collection_field.remote_id),
@ -428,24 +428,24 @@ class CollectionItemMixin(ActivitypubMixin):
class ActivityMixin(ActivitypubMixin): 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): def save(self, *args, broadcast=True, **kwargs):
""" broadcast activity """ """broadcast activity"""
super().save(*args, **kwargs) 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: if broadcast and user.local:
self.broadcast(self.to_activity(), user) self.broadcast(self.to_activity(), user)
def delete(self, *args, broadcast=True, **kwargs): def delete(self, *args, broadcast=True, **kwargs):
""" nevermind, undo that activity """ """nevermind, undo that activity"""
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: if broadcast and user.local:
self.broadcast(self.to_undo_activity(), user) self.broadcast(self.to_undo_activity(), user)
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
def to_undo_activity(self): def to_undo_activity(self):
""" undo an action """ """undo an action"""
user = self.user if hasattr(self, "user") else self.user_subject user = self.user if hasattr(self, "user") else self.user_subject
return activitypub.Undo( return activitypub.Undo(
id="%s#undo" % self.remote_id, id="%s#undo" % self.remote_id,
@ -455,7 +455,7 @@ class ActivityMixin(ActivitypubMixin):
def generate_activity(obj): def generate_activity(obj):
""" go through the fields on an object """ """go through the fields on an object"""
activity = {} activity = {}
for field in obj.activity_fields: for field in obj.activity_fields:
field.set_activity_from_field(activity, obj) field.set_activity_from_field(activity, obj)
@ -478,7 +478,7 @@ def generate_activity(obj):
def unfurl_related_field(related_field, sort_field=None): def unfurl_related_field(related_field, sort_field=None):
""" load reverse lookups (like public key owner or Status attachment """ """load reverse lookups (like public key owner or Status attachment"""
if sort_field and hasattr(related_field, "all"): if sort_field and hasattr(related_field, "all"):
return [ return [
unfurl_related_field(i) for i in related_field.order_by(sort_field).all() unfurl_related_field(i) for i in related_field.order_by(sort_field).all()
@ -494,7 +494,7 @@ def unfurl_related_field(related_field, sort_field=None):
@app.task @app.task
def broadcast_task(sender_id, activity, recipients): def broadcast_task(sender_id, activity, recipients):
""" the celery task for broadcast """ """the celery task for broadcast"""
user_model = apps.get_model("bookwyrm.User", require_ready=True) user_model = apps.get_model("bookwyrm.User", require_ready=True)
sender = user_model.objects.get(id=sender_id) sender = user_model.objects.get(id=sender_id)
for recipient in recipients: for recipient in recipients:
@ -505,7 +505,7 @@ def broadcast_task(sender_id, activity, recipients):
def sign_and_send(sender, data, destination): def sign_and_send(sender, data, destination):
""" crpyto whatever and http junk """ """crpyto whatever and http junk"""
now = http_date() now = http_date()
if not sender.key_pair.private_key: if not sender.key_pair.private_key:
@ -534,7 +534,7 @@ def sign_and_send(sender, data, destination):
def to_ordered_collection_page( def to_ordered_collection_page(
queryset, remote_id, id_only=False, page=1, pure=False, **kwargs queryset, remote_id, id_only=False, page=1, pure=False, **kwargs
): ):
""" serialize and pagiante a queryset """ """serialize and pagiante a queryset"""
paginated = Paginator(queryset, PAGE_LENGTH) paginated = Paginator(queryset, PAGE_LENGTH)
activity_page = paginated.get_page(page) activity_page = paginated.get_page(page)

View file

@ -8,7 +8,7 @@ from . import fields
class Attachment(ActivitypubMixin, BookWyrmModel): 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 = models.ForeignKey(
"Status", on_delete=models.CASCADE, related_name="attachments", null=True "Status", on_delete=models.CASCADE, related_name="attachments", null=True
@ -16,13 +16,13 @@ class Attachment(ActivitypubMixin, BookWyrmModel):
reverse_unfurl = True reverse_unfurl = True
class Meta: 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 abstract = True
class Image(Attachment): class Image(Attachment):
""" an image attachment """ """an image attachment"""
image = fields.ImageField( image = fields.ImageField(
upload_to="status/", upload_to="status/",

View file

@ -9,7 +9,7 @@ from . import fields
class Author(BookDataModel): class Author(BookDataModel):
""" basic biographic info """ """basic biographic info"""
wikipedia_link = fields.CharField( wikipedia_link = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True max_length=255, blank=True, null=True, deduplication_field=True
@ -24,7 +24,7 @@ class Author(BookDataModel):
bio = fields.HtmlField(null=True, blank=True) bio = fields.HtmlField(null=True, blank=True)
def get_remote_id(self): def get_remote_id(self):
""" editions and works both use "book" instead of model_name """ """editions and works both use "book" instead of model_name"""
return "https://%s/author/%s" % (DOMAIN, self.id) return "https://%s/author/%s" % (DOMAIN, self.id)
activity_serializer = activitypub.Author activity_serializer = activitypub.Author

View file

@ -7,14 +7,14 @@ from .fields import RemoteIdField
class BookWyrmModel(models.Model): class BookWyrmModel(models.Model):
""" shared fields """ """shared fields"""
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=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): def get_remote_id(self):
""" generate a url that resolves to the local object """ """generate a url that resolves to the local object"""
base_path = "https://%s" % DOMAIN base_path = "https://%s" % DOMAIN
if hasattr(self, "user"): if hasattr(self, "user"):
base_path = "%s%s" % (base_path, self.user.local_path) base_path = "%s%s" % (base_path, self.user.local_path)
@ -22,17 +22,17 @@ class BookWyrmModel(models.Model):
return "%s/%s/%d" % (base_path, model_name, self.id) return "%s/%s/%d" % (base_path, model_name, self.id)
class Meta: 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 abstract = True
@property @property
def local_path(self): def local_path(self):
""" how to link to this object in the local app """ """how to link to this object in the local app"""
return self.get_remote_id().replace("https://%s" % DOMAIN, "") return self.get_remote_id().replace("https://%s" % DOMAIN, "")
def visible_to_user(self, viewer): def visible_to_user(self, viewer):
""" is a user authorized to view an object? """ """is a user authorized to view an object?"""
# make sure this is an object with privacy owned by a user # make sure this is an object with privacy owned by a user
if not hasattr(self, "user") or not hasattr(self, "privacy"): if not hasattr(self, "user") or not hasattr(self, "privacy"):
return None return None
@ -65,7 +65,7 @@ class BookWyrmModel(models.Model):
@receiver(models.signals.post_save) @receiver(models.signals.post_save)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def set_remote_id(sender, instance, created, *args, **kwargs): def set_remote_id(sender, instance, created, *args, **kwargs):
""" set the remote_id after save (when the id is available) """ """set the remote_id after save (when the id is available)"""
if not created or not hasattr(instance, "get_remote_id"): if not created or not hasattr(instance, "get_remote_id"):
return return
if not instance.remote_id: if not instance.remote_id:

View file

@ -13,7 +13,7 @@ from . import fields
class BookDataModel(ObjectMixin, BookWyrmModel): 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) origin_id = models.CharField(max_length=255, null=True, blank=True)
openlibrary_key = fields.CharField( openlibrary_key = fields.CharField(
@ -33,12 +33,12 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
) )
class Meta: class Meta:
""" can't initialize this model, that wouldn't make sense """ """can't initialize this model, that wouldn't make sense"""
abstract = True abstract = True
def save(self, *args, **kwargs): 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: if self.id:
self.remote_id = self.get_remote_id() self.remote_id = self.get_remote_id()
else: else:
@ -47,12 +47,12 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def broadcast(self, activity, sender, software="bookwyrm"): def broadcast(self, activity, sender, software="bookwyrm"):
""" only send book data updates to other bookwyrm instances """ """only send book data updates to other bookwyrm instances"""
super().broadcast(activity, sender, software=software) super().broadcast(activity, sender, software=software)
class Book(BookDataModel): class Book(BookDataModel):
""" a generic book, which can mean either an edition or a work """ """a generic book, which can mean either an edition or a work"""
connector = models.ForeignKey("Connector", on_delete=models.PROTECT, null=True) connector = models.ForeignKey("Connector", on_delete=models.PROTECT, null=True)
@ -83,17 +83,17 @@ class Book(BookDataModel):
@property @property
def author_text(self): def author_text(self):
""" format a list of authors """ """format a list of authors"""
return ", ".join(a.name for a in self.authors.all()) return ", ".join(a.name for a in self.authors.all())
@property @property
def latest_readthrough(self): def latest_readthrough(self):
""" most recent readthrough activity """ """most recent readthrough activity"""
return self.readthrough_set.order_by("-updated_date").first() return self.readthrough_set.order_by("-updated_date").first()
@property @property
def edition_info(self): def edition_info(self):
""" properties of this edition, as a string """ """properties of this edition, as a string"""
items = [ items = [
self.physical_format if hasattr(self, "physical_format") else None, self.physical_format if hasattr(self, "physical_format") else None,
self.languages[0] + " language" self.languages[0] + " language"
@ -106,20 +106,20 @@ class Book(BookDataModel):
@property @property
def alt_text(self): def alt_text(self):
""" image alt test """ """image alt test"""
text = "%s" % self.title text = "%s" % self.title
if self.edition_info: if self.edition_info:
text += " (%s)" % self.edition_info text += " (%s)" % self.edition_info
return text return text
def save(self, *args, **kwargs): 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): 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) return super().save(*args, **kwargs)
def get_remote_id(self): def get_remote_id(self):
""" editions and works both use "book" instead of model_name """ """editions and works both use "book" instead of model_name"""
return "https://%s/book/%d" % (DOMAIN, self.id) return "https://%s/book/%d" % (DOMAIN, self.id)
def __repr__(self): def __repr__(self):
@ -131,7 +131,7 @@ class Book(BookDataModel):
class Work(OrderedCollectionPageMixin, Book): 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 # library of congress catalog control number
lccn = fields.CharField( lccn = fields.CharField(
@ -143,19 +143,19 @@ class Work(OrderedCollectionPageMixin, Book):
) )
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" set some fields on the edition object """ """set some fields on the edition object"""
# set rank # set rank
for edition in self.editions.all(): for edition in self.editions.all():
edition.save() edition.save()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def get_default_edition(self): def get_default_edition(self):
""" in case the default edition is not set """ """in case the default edition is not set"""
return self.default_edition or self.editions.order_by("-edition_rank").first() return self.default_edition or self.editions.order_by("-edition_rank").first()
@transaction.atomic() @transaction.atomic()
def reset_default_edition(self): def reset_default_edition(self):
""" sets a new default edition based on computed rank """ """sets a new default edition based on computed rank"""
self.default_edition = None self.default_edition = None
# editions are re-ranked implicitly # editions are re-ranked implicitly
self.save() self.save()
@ -163,11 +163,11 @@ class Work(OrderedCollectionPageMixin, Book):
self.save() self.save()
def to_edition_list(self, **kwargs): def to_edition_list(self, **kwargs):
""" an ordered collection of editions """ """an ordered collection of editions"""
return self.to_ordered_collection( return self.to_ordered_collection(
self.editions.order_by("-edition_rank").all(), self.editions.order_by("-edition_rank").all(),
remote_id="%s/editions" % self.remote_id, remote_id="%s/editions" % self.remote_id,
**kwargs **kwargs,
) )
activity_serializer = activitypub.Work activity_serializer = activitypub.Work
@ -176,7 +176,7 @@ class Work(OrderedCollectionPageMixin, Book):
class Edition(Book): class Edition(Book):
""" an edition of a book """ """an edition of a book"""
# these identifiers only apply to editions, not works # these identifiers only apply to editions, not works
isbn_10 = fields.CharField( isbn_10 = fields.CharField(
@ -215,7 +215,7 @@ class Edition(Book):
name_field = "title" name_field = "title"
def get_rank(self, ignore_default=False): def get_rank(self, ignore_default=False):
""" calculate how complete the data is on this edition """ """calculate how complete the data is on this edition"""
if ( if (
not ignore_default not ignore_default
and self.parent_work and self.parent_work
@ -235,7 +235,7 @@ class Edition(Book):
return rank return rank
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" set some fields on the edition object """ """set some fields on the edition object"""
# calculate isbn 10/13 # 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) self.isbn_10 = isbn_13_to_10(self.isbn_13)
@ -249,7 +249,7 @@ class Edition(Book):
def isbn_10_to_13(isbn_10): def isbn_10_to_13(isbn_10):
""" convert an isbn 10 into an isbn 13 """ """convert an isbn 10 into an isbn 13"""
isbn_10 = re.sub(r"[^0-9X]", "", isbn_10) isbn_10 = re.sub(r"[^0-9X]", "", isbn_10)
# drop the last character of the isbn 10 number (the original checkdigit) # drop the last character of the isbn 10 number (the original checkdigit)
converted = isbn_10[:9] converted = isbn_10[:9]
@ -271,7 +271,7 @@ def isbn_10_to_13(isbn_10):
def isbn_13_to_10(isbn_13): def isbn_13_to_10(isbn_13):
""" convert isbn 13 to 10, if possible """ """convert isbn 13 to 10, if possible"""
if isbn_13[:3] != "978": if isbn_13[:3] != "978":
return None return None

View file

@ -9,7 +9,7 @@ ConnectorFiles = models.TextChoices("ConnectorFiles", CONNECTORS)
class Connector(BookWyrmModel): class Connector(BookWyrmModel):
""" book data source connectors """ """book data source connectors"""
identifier = models.CharField(max_length=255, unique=True) identifier = models.CharField(max_length=255, unique=True)
priority = models.IntegerField(default=2) priority = models.IntegerField(default=2)
@ -32,7 +32,7 @@ class Connector(BookWyrmModel):
query_count_expiry = models.DateTimeField(auto_now_add=True, blank=True) query_count_expiry = models.DateTimeField(auto_now_add=True, blank=True)
class Meta: class Meta:
""" check that there's code to actually use this connector """ """check that there's code to actually use this connector"""
constraints = [ constraints = [
models.CheckConstraint( models.CheckConstraint(

View file

@ -11,7 +11,7 @@ from .status import Status
class Favorite(ActivityMixin, BookWyrmModel): class Favorite(ActivityMixin, BookWyrmModel):
""" fav'ing a post """ """fav'ing a post"""
user = fields.ForeignKey( user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="actor" "User", on_delete=models.PROTECT, activitypub_field="actor"
@ -24,11 +24,11 @@ class Favorite(ActivityMixin, BookWyrmModel):
@classmethod @classmethod
def ignore_activity(cls, activity): 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() return not Status.objects.filter(remote_id=activity.object).exists()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" update user active time """ """update user active time"""
self.user.last_active_date = timezone.now() self.user.last_active_date = timezone.now()
self.user.save(broadcast=False) self.user.save(broadcast=False)
super().save(*args, **kwargs) super().save(*args, **kwargs)
@ -45,7 +45,7 @@ class Favorite(ActivityMixin, BookWyrmModel):
) )
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" delete and delete notifications """ """delete and delete notifications"""
# check for notification # check for notification
if self.status.user.local: if self.status.user.local:
notification_model = apps.get_model( notification_model = apps.get_model(
@ -62,6 +62,6 @@ class Favorite(ActivityMixin, BookWyrmModel):
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
class Meta: class Meta:
""" can't fav things twice """ """can't fav things twice"""
unique_together = ("user", "status") unique_together = ("user", "status")

View file

@ -13,7 +13,7 @@ FederationStatus = models.TextChoices(
class FederatedServer(BookWyrmModel): class FederatedServer(BookWyrmModel):
""" store which servers we federate with """ """store which servers we federate with"""
server_name = models.CharField(max_length=255, unique=True) server_name = models.CharField(max_length=255, unique=True)
status = models.CharField( status = models.CharField(
@ -25,7 +25,7 @@ class FederatedServer(BookWyrmModel):
notes = models.TextField(null=True, blank=True) notes = models.TextField(null=True, blank=True)
def block(self): def block(self):
""" block a server """ """block a server"""
self.status = "blocked" self.status = "blocked"
self.save() self.save()
@ -35,7 +35,7 @@ class FederatedServer(BookWyrmModel):
) )
def unblock(self): def unblock(self):
""" unblock a server """ """unblock a server"""
self.status = "federated" self.status = "federated"
self.save() self.save()
@ -45,7 +45,7 @@ class FederatedServer(BookWyrmModel):
@classmethod @classmethod
def is_blocked(cls, url): def is_blocked(cls, url):
""" look up if a domain is blocked """ """look up if a domain is blocked"""
url = urlparse(url) url = urlparse(url)
domain = url.netloc domain = url.netloc
return cls.objects.filter(server_name=domain, status="blocked").exists() return cls.objects.filter(server_name=domain, status="blocked").exists()

View file

@ -18,7 +18,7 @@ from bookwyrm.settings import DOMAIN
def validate_remote_id(value): def validate_remote_id(value):
""" make sure the remote_id looks like a url """ """make sure the remote_id looks like a url"""
if not value or not re.match(r"^http.?:\/\/[^\s]+$", value): if not value or not re.match(r"^http.?:\/\/[^\s]+$", value):
raise ValidationError( raise ValidationError(
_("%(value)s is not a valid remote_id"), _("%(value)s is not a valid remote_id"),
@ -27,7 +27,7 @@ def validate_remote_id(value):
def validate_localname(value): def validate_localname(value):
""" make sure localnames look okay """ """make sure localnames look okay"""
if not re.match(r"^[A-Za-z\-_\.0-9]+$", value): if not re.match(r"^[A-Za-z\-_\.0-9]+$", value):
raise ValidationError( raise ValidationError(
_("%(value)s is not a valid username"), _("%(value)s is not a valid username"),
@ -36,7 +36,7 @@ def validate_localname(value):
def validate_username(value): def validate_username(value):
""" make sure usernames look okay """ """make sure usernames look okay"""
if not re.match(r"^[A-Za-z\-_\.0-9]+@[A-Za-z\-_\.0-9]+\.[a-z]{2,}$", value): if not re.match(r"^[A-Za-z\-_\.0-9]+@[A-Za-z\-_\.0-9]+\.[a-z]{2,}$", value):
raise ValidationError( raise ValidationError(
_("%(value)s is not a valid username"), _("%(value)s is not a valid username"),
@ -45,7 +45,7 @@ def validate_username(value):
class ActivitypubFieldMixin: class ActivitypubFieldMixin:
""" make a database field serializable """ """make a database field serializable"""
def __init__( def __init__(
self, self,
@ -64,7 +64,7 @@ class ActivitypubFieldMixin:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def set_field_from_activity(self, instance, data): 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: try:
value = getattr(data, self.get_activitypub_field()) value = getattr(data, self.get_activitypub_field())
except AttributeError: except AttributeError:
@ -78,7 +78,7 @@ class ActivitypubFieldMixin:
setattr(instance, self.name, formatted) setattr(instance, self.name, formatted)
def set_activity_from_field(self, activity, instance): def set_activity_from_field(self, activity, instance):
""" update the json object """ """update the json object"""
value = getattr(instance, self.name) value = getattr(instance, self.name)
formatted = self.field_to_activity(value) formatted = self.field_to_activity(value)
if formatted is None: if formatted is None:
@ -94,19 +94,19 @@ class ActivitypubFieldMixin:
activity[key] = formatted activity[key] = formatted
def field_to_activity(self, value): def field_to_activity(self, value):
""" formatter to convert a model value into activitypub """ """formatter to convert a model value into activitypub"""
if hasattr(self, "activitypub_wrapper"): if hasattr(self, "activitypub_wrapper"):
return {self.activitypub_wrapper: value} return {self.activitypub_wrapper: value}
return value return value
def field_from_activity(self, value): def field_from_activity(self, value):
""" formatter to convert activitypub into a model value """ """formatter to convert activitypub into a model value"""
if value and hasattr(self, "activitypub_wrapper"): if value and hasattr(self, "activitypub_wrapper"):
value = value.get(self.activitypub_wrapper) value = value.get(self.activitypub_wrapper)
return value return value
def get_activitypub_field(self): def get_activitypub_field(self):
""" model_field_name to activitypubFieldName """ """model_field_name to activitypubFieldName"""
if self.activitypub_field: if self.activitypub_field:
return self.activitypub_field return self.activitypub_field
name = self.name.split(".")[-1] name = self.name.split(".")[-1]
@ -115,7 +115,7 @@ class ActivitypubFieldMixin:
class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): 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): def __init__(self, *args, load_remote=True, **kwargs):
self.load_remote = load_remote self.load_remote = load_remote
@ -146,7 +146,7 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin):
class RemoteIdField(ActivitypubFieldMixin, models.CharField): 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): def __init__(self, *args, max_length=255, validators=None, **kwargs):
validators = validators or [validate_remote_id] validators = validators or [validate_remote_id]
@ -156,7 +156,7 @@ class RemoteIdField(ActivitypubFieldMixin, models.CharField):
class UsernameField(ActivitypubFieldMixin, models.CharField): class UsernameField(ActivitypubFieldMixin, models.CharField):
""" activitypub-aware username field """ """activitypub-aware username field"""
def __init__(self, activitypub_field="preferredUsername", **kwargs): def __init__(self, activitypub_field="preferredUsername", **kwargs):
self.activitypub_field = activitypub_field self.activitypub_field = activitypub_field
@ -172,7 +172,7 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
) )
def deconstruct(self): def deconstruct(self):
""" implementation of models.Field deconstruct """ """implementation of models.Field deconstruct"""
name, path, args, kwargs = super().deconstruct() name, path, args, kwargs = super().deconstruct()
del kwargs["verbose_name"] del kwargs["verbose_name"]
del kwargs["max_length"] del kwargs["max_length"]
@ -191,7 +191,7 @@ PrivacyLevels = models.TextChoices(
class PrivacyField(ActivitypubFieldMixin, models.CharField): class PrivacyField(ActivitypubFieldMixin, models.CharField):
""" this maps to two differente activitypub fields """ """this maps to two differente activitypub fields"""
public = "https://www.w3.org/ns/activitystreams#Public" public = "https://www.w3.org/ns/activitystreams#Public"
@ -236,7 +236,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey): class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey):
""" activitypub-aware foreign key field """ """activitypub-aware foreign key field"""
def field_to_activity(self, value): def field_to_activity(self, value):
if not value: if not value:
@ -245,7 +245,7 @@ class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey):
class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField): class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField):
""" activitypub-aware foreign key field """ """activitypub-aware foreign key field"""
def field_to_activity(self, value): def field_to_activity(self, value):
if not value: if not value:
@ -254,14 +254,14 @@ class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField):
class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): 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): def __init__(self, *args, link_only=False, **kwargs):
self.link_only = link_only self.link_only = link_only
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def set_field_from_activity(self, instance, data): 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()) value = getattr(data, self.get_activitypub_field())
formatted = self.field_from_activity(value) formatted = self.field_from_activity(value)
if formatted is None or formatted is MISSING: if formatted is None or formatted is MISSING:
@ -293,7 +293,7 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
class TagField(ManyToManyField): 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -333,7 +333,7 @@ class TagField(ManyToManyField):
def image_serializer(value, alt): def image_serializer(value, alt):
""" helper for serializing images """ """helper for serializing images"""
if value and hasattr(value, "url"): if value and hasattr(value, "url"):
url = value.url url = value.url
else: else:
@ -343,7 +343,7 @@ def image_serializer(value, alt):
class ImageField(ActivitypubFieldMixin, models.ImageField): class ImageField(ActivitypubFieldMixin, models.ImageField):
""" activitypub-aware image field """ """activitypub-aware image field"""
def __init__(self, *args, alt_field=None, **kwargs): def __init__(self, *args, alt_field=None, **kwargs):
self.alt_field = alt_field self.alt_field = alt_field
@ -351,7 +351,7 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def set_field_from_activity(self, instance, data, save=True): 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()) value = getattr(data, self.get_activitypub_field())
formatted = self.field_from_activity(value) formatted = self.field_from_activity(value)
if formatted is None or formatted is MISSING: if formatted is None or formatted is MISSING:
@ -397,7 +397,7 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
class DateTimeField(ActivitypubFieldMixin, models.DateTimeField): class DateTimeField(ActivitypubFieldMixin, models.DateTimeField):
""" activitypub-aware datetime field """ """activitypub-aware datetime field"""
def field_to_activity(self, value): def field_to_activity(self, value):
if not value: if not value:
@ -416,7 +416,7 @@ class DateTimeField(ActivitypubFieldMixin, models.DateTimeField):
class HtmlField(ActivitypubFieldMixin, models.TextField): class HtmlField(ActivitypubFieldMixin, models.TextField):
""" a text field for storing html """ """a text field for storing html"""
def field_from_activity(self, value): def field_from_activity(self, value):
if not value or value == MISSING: if not value or value == MISSING:
@ -427,30 +427,30 @@ class HtmlField(ActivitypubFieldMixin, models.TextField):
class ArrayField(ActivitypubFieldMixin, DjangoArrayField): class ArrayField(ActivitypubFieldMixin, DjangoArrayField):
""" activitypub-aware array field """ """activitypub-aware array field"""
def field_to_activity(self, value): def field_to_activity(self, value):
return [str(i) for i in value] return [str(i) for i in value]
class CharField(ActivitypubFieldMixin, models.CharField): class CharField(ActivitypubFieldMixin, models.CharField):
""" activitypub-aware char field """ """activitypub-aware char field"""
class TextField(ActivitypubFieldMixin, models.TextField): class TextField(ActivitypubFieldMixin, models.TextField):
""" activitypub-aware text field """ """activitypub-aware text field"""
class BooleanField(ActivitypubFieldMixin, models.BooleanField): class BooleanField(ActivitypubFieldMixin, models.BooleanField):
""" activitypub-aware boolean field """ """activitypub-aware boolean field"""
class IntegerField(ActivitypubFieldMixin, models.IntegerField): class IntegerField(ActivitypubFieldMixin, models.IntegerField):
""" activitypub-aware boolean field """ """activitypub-aware boolean field"""
class DecimalField(ActivitypubFieldMixin, models.DecimalField): class DecimalField(ActivitypubFieldMixin, models.DecimalField):
""" activitypub-aware boolean field """ """activitypub-aware boolean field"""
def field_to_activity(self, value): def field_to_activity(self, value):
if not value: if not value:

View file

@ -20,7 +20,7 @@ GOODREADS_SHELVES = {
def unquote_string(text): def unquote_string(text):
""" resolve csv quote weirdness """ """resolve csv quote weirdness"""
match = re.match(r'="([^"]*)"', text) match = re.match(r'="([^"]*)"', text)
if match: if match:
return match.group(1) return match.group(1)
@ -28,7 +28,7 @@ def unquote_string(text):
def construct_search_term(title, author): 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) # 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. # Open library doesn't like including author initials in search term.
@ -38,7 +38,7 @@ def construct_search_term(title, author):
class ImportJob(models.Model): 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) user = models.ForeignKey(User, on_delete=models.CASCADE)
created_date = models.DateTimeField(default=timezone.now) created_date = models.DateTimeField(default=timezone.now)
@ -51,7 +51,7 @@ class ImportJob(models.Model):
retry = models.BooleanField(default=False) retry = models.BooleanField(default=False)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" save and notify """ """save and notify"""
super().save(*args, **kwargs) super().save(*args, **kwargs)
if self.complete: if self.complete:
notification_model = apps.get_model( notification_model = apps.get_model(
@ -65,7 +65,7 @@ class ImportJob(models.Model):
class ImportItem(models.Model): class ImportItem(models.Model):
""" a single line of a csv being imported """ """a single line of a csv being imported"""
job = models.ForeignKey(ImportJob, on_delete=models.CASCADE, related_name="items") job = models.ForeignKey(ImportJob, on_delete=models.CASCADE, related_name="items")
index = models.IntegerField() index = models.IntegerField()
@ -74,11 +74,11 @@ class ImportItem(models.Model):
fail_reason = models.TextField(null=True) fail_reason = models.TextField(null=True)
def resolve(self): def resolve(self):
""" try various ways to lookup a book """ """try various ways to lookup a book"""
self.book = self.get_book_from_isbn() or self.get_book_from_title_author() self.book = self.get_book_from_isbn() or self.get_book_from_title_author()
def get_book_from_isbn(self): def get_book_from_isbn(self):
""" search by isbn """ """search by isbn"""
search_result = connector_manager.first_search_result( search_result = connector_manager.first_search_result(
self.isbn, min_confidence=0.999 self.isbn, min_confidence=0.999
) )
@ -88,7 +88,7 @@ class ImportItem(models.Model):
return None return None
def get_book_from_title_author(self): def get_book_from_title_author(self):
""" search by title and author """ """search by title and author"""
search_term = construct_search_term(self.title, self.author) search_term = construct_search_term(self.title, self.author)
search_result = connector_manager.first_search_result( search_result = connector_manager.first_search_result(
search_term, min_confidence=0.999 search_term, min_confidence=0.999
@ -100,60 +100,60 @@ class ImportItem(models.Model):
@property @property
def title(self): def title(self):
""" get the book title """ """get the book title"""
return self.data["Title"] return self.data["Title"]
@property @property
def author(self): def author(self):
""" get the book title """ """get the book title"""
return self.data["Author"] return self.data["Author"]
@property @property
def isbn(self): def isbn(self):
""" pulls out the isbn13 field from the csv line data """ """pulls out the isbn13 field from the csv line data"""
return unquote_string(self.data["ISBN13"]) return unquote_string(self.data["ISBN13"])
@property @property
def shelf(self): def shelf(self):
""" the goodreads shelf field """ """the goodreads shelf field"""
if self.data["Exclusive Shelf"]: if self.data["Exclusive Shelf"]:
return GOODREADS_SHELVES.get(self.data["Exclusive Shelf"]) return GOODREADS_SHELVES.get(self.data["Exclusive Shelf"])
return None return None
@property @property
def review(self): def review(self):
""" a user-written review, to be imported with the book data """ """a user-written review, to be imported with the book data"""
return self.data["My Review"] return self.data["My Review"]
@property @property
def rating(self): def rating(self):
""" x/5 star rating for a book """ """x/5 star rating for a book"""
return int(self.data["My Rating"]) return int(self.data["My Rating"])
@property @property
def date_added(self): def date_added(self):
""" when the book was added to this dataset """ """when the book was added to this dataset"""
if self.data["Date Added"]: if self.data["Date Added"]:
return timezone.make_aware(dateutil.parser.parse(self.data["Date Added"])) return timezone.make_aware(dateutil.parser.parse(self.data["Date Added"]))
return None return None
@property @property
def date_started(self): def date_started(self):
""" when the book was started """ """when the book was started"""
if "Date Started" in self.data and self.data["Date Started"]: if "Date Started" in self.data and self.data["Date Started"]:
return timezone.make_aware(dateutil.parser.parse(self.data["Date Started"])) return timezone.make_aware(dateutil.parser.parse(self.data["Date Started"]))
return None return None
@property @property
def date_read(self): def date_read(self):
""" the date a book was completed """ """the date a book was completed"""
if self.data["Date Read"]: if self.data["Date Read"]:
return timezone.make_aware(dateutil.parser.parse(self.data["Date Read"])) return timezone.make_aware(dateutil.parser.parse(self.data["Date Read"]))
return None return None
@property @property
def reads(self): 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 start_date = self.date_started
# Goodreads special case (no 'date started' field) # Goodreads special case (no 'date started' field)

View file

@ -21,7 +21,7 @@ CurationType = models.TextChoices(
class List(OrderedCollectionMixin, BookWyrmModel): class List(OrderedCollectionMixin, BookWyrmModel):
""" a list of books """ """a list of books"""
name = fields.CharField(max_length=100) name = fields.CharField(max_length=100)
user = fields.ForeignKey( user = fields.ForeignKey(
@ -41,22 +41,22 @@ class List(OrderedCollectionMixin, BookWyrmModel):
activity_serializer = activitypub.BookList activity_serializer = activitypub.BookList
def get_remote_id(self): def get_remote_id(self):
""" don't want the user to be in there in this case """ """don't want the user to be in there in this case"""
return "https://%s/list/%d" % (DOMAIN, self.id) return "https://%s/list/%d" % (DOMAIN, self.id)
@property @property
def collection_queryset(self): def collection_queryset(self):
""" list of books for this shelf, overrides OrderedCollectionMixin """ """list of books for this shelf, overrides OrderedCollectionMixin"""
return self.books.filter(listitem__approved=True).order_by("listitem") return self.books.filter(listitem__approved=True).order_by("listitem")
class Meta: class Meta:
""" default sorting """ """default sorting"""
ordering = ("-updated_date",) ordering = ("-updated_date",)
class ListItem(CollectionItemMixin, BookWyrmModel): class ListItem(CollectionItemMixin, BookWyrmModel):
""" ok """ """ok"""
book = fields.ForeignKey( book = fields.ForeignKey(
"Edition", on_delete=models.PROTECT, activitypub_field="book" "Edition", on_delete=models.PROTECT, activitypub_field="book"
@ -74,7 +74,7 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
collection_field = "book_list" collection_field = "book_list"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" create a notification too """ """create a notification too"""
created = not bool(self.id) created = not bool(self.id)
super().save(*args, **kwargs) super().save(*args, **kwargs)
# tick the updated date on the parent list # tick the updated date on the parent list

View file

@ -10,7 +10,7 @@ NotificationType = models.TextChoices(
class Notification(BookWyrmModel): class Notification(BookWyrmModel):
""" you've been tagged, liked, followed, etc """ """you've been tagged, liked, followed, etc"""
user = models.ForeignKey("User", on_delete=models.CASCADE) user = models.ForeignKey("User", on_delete=models.CASCADE)
related_book = models.ForeignKey("Edition", on_delete=models.CASCADE, null=True) related_book = models.ForeignKey("Edition", on_delete=models.CASCADE, null=True)
@ -29,7 +29,7 @@ class Notification(BookWyrmModel):
) )
def save(self, *args, **kwargs): 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 # there's probably a better way to do this
if self.__class__.objects.filter( if self.__class__.objects.filter(
user=self.user, user=self.user,
@ -45,7 +45,7 @@ class Notification(BookWyrmModel):
super().save(*args, **kwargs) super().save(*args, **kwargs)
class Meta: class Meta:
""" checks if notifcation is in enum list for valid types """ """checks if notifcation is in enum list for valid types"""
constraints = [ constraints = [
models.CheckConstraint( models.CheckConstraint(

View file

@ -7,14 +7,14 @@ from .base_model import BookWyrmModel
class ProgressMode(models.TextChoices): class ProgressMode(models.TextChoices):
""" types of prgress available """ """types of prgress available"""
PAGE = "PG", "page" PAGE = "PG", "page"
PERCENT = "PCT", "percent" PERCENT = "PCT", "percent"
class ReadThrough(BookWyrmModel): class ReadThrough(BookWyrmModel):
""" Store a read through a book in the database. """ """Store a read through a book in the database."""
user = models.ForeignKey("User", on_delete=models.PROTECT) user = models.ForeignKey("User", on_delete=models.PROTECT)
book = models.ForeignKey("Edition", on_delete=models.PROTECT) book = models.ForeignKey("Edition", on_delete=models.PROTECT)
@ -28,13 +28,13 @@ class ReadThrough(BookWyrmModel):
finish_date = models.DateTimeField(blank=True, null=True) finish_date = models.DateTimeField(blank=True, null=True)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" update user active time """ """update user active time"""
self.user.last_active_date = timezone.now() self.user.last_active_date = timezone.now()
self.user.save(broadcast=False) self.user.save(broadcast=False)
super().save(*args, **kwargs) super().save(*args, **kwargs)
def create_update(self): def create_update(self):
""" add update to the readthrough """ """add update to the readthrough"""
if self.progress: if self.progress:
return self.progressupdate_set.create( 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
@ -43,7 +43,7 @@ class ReadThrough(BookWyrmModel):
class ProgressUpdate(BookWyrmModel): class ProgressUpdate(BookWyrmModel):
""" Store progress through a book in the database. """ """Store progress through a book in the database."""
user = models.ForeignKey("User", on_delete=models.PROTECT) user = models.ForeignKey("User", on_delete=models.PROTECT)
readthrough = models.ForeignKey("ReadThrough", on_delete=models.CASCADE) readthrough = models.ForeignKey("ReadThrough", on_delete=models.CASCADE)
@ -53,7 +53,7 @@ class ProgressUpdate(BookWyrmModel):
) )
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" update user active time """ """update user active time"""
self.user.last_active_date = timezone.now() self.user.last_active_date = timezone.now()
self.user.save(broadcast=False) self.user.save(broadcast=False)
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -11,7 +11,7 @@ from . import fields
class UserRelationship(BookWyrmModel): class UserRelationship(BookWyrmModel):
""" many-to-many through table for followers """ """many-to-many through table for followers"""
user_subject = fields.ForeignKey( user_subject = fields.ForeignKey(
"User", "User",
@ -28,16 +28,16 @@ class UserRelationship(BookWyrmModel):
@property @property
def privacy(self): def privacy(self):
""" all relationships are handled directly with the participants """ """all relationships are handled directly with the participants"""
return "direct" return "direct"
@property @property
def recipients(self): 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] return [u for u in [self.user_subject, self.user_object] if not u.local]
class Meta: class Meta:
""" relationships should be unique """ """relationships should be unique"""
abstract = True abstract = True
constraints = [ constraints = [
@ -51,22 +51,22 @@ class UserRelationship(BookWyrmModel):
] ]
def get_remote_id(self): def get_remote_id(self):
""" use shelf identifier in remote_id """ """use shelf identifier in remote_id"""
base_path = self.user_subject.remote_id base_path = self.user_subject.remote_id
return "%s#follows/%d" % (base_path, self.id) return "%s#follows/%d" % (base_path, self.id)
class UserFollows(ActivityMixin, UserRelationship): class UserFollows(ActivityMixin, UserRelationship):
""" Following a user """ """Following a user"""
status = "follows" status = "follows"
def to_activity(self): # pylint: disable=arguments-differ def to_activity(self): # pylint: disable=arguments-differ
""" overrides default to manually set serializer """ """overrides default to manually set serializer"""
return activitypub.Follow(**generate_activity(self)) return activitypub.Follow(**generate_activity(self))
def save(self, *args, **kwargs): 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 # blocking in either direction is a no-go
if UserBlocks.objects.filter( if UserBlocks.objects.filter(
Q( Q(
@ -85,7 +85,7 @@ class UserFollows(ActivityMixin, UserRelationship):
@classmethod @classmethod
def from_request(cls, follow_request): 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( return cls.objects.create(
user_subject=follow_request.user_subject, user_subject=follow_request.user_subject,
user_object=follow_request.user_object, user_object=follow_request.user_object,
@ -94,13 +94,13 @@ class UserFollows(ActivityMixin, UserRelationship):
class UserFollowRequest(ActivitypubMixin, UserRelationship): class UserFollowRequest(ActivitypubMixin, UserRelationship):
""" following a user requires manual or automatic confirmation """ """following a user requires manual or automatic confirmation"""
status = "follow_request" status = "follow_request"
activity_serializer = activitypub.Follow activity_serializer = activitypub.Follow
def save(self, *args, broadcast=True, **kwargs): 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"""
# if there's a request for a follow that already exists, accept it # if there's a request for a follow that already exists, accept it
# without changing the local database state # without changing the local database state
if UserFollows.objects.filter( if UserFollows.objects.filter(
@ -141,13 +141,13 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
) )
def get_accept_reject_id(self, status): def get_accept_reject_id(self, status):
""" get id for sending an accept or reject of a local user """ """get id for sending an accept or reject of a local user"""
base_path = self.user_object.remote_id base_path = self.user_object.remote_id
return "%s#%s/%d" % (base_path, status, self.id or 0) return "%s#%s/%d" % (base_path, status, self.id or 0)
def accept(self, broadcast_only=False): def accept(self, broadcast_only=False):
""" turn this request into the real deal""" """turn this request into the real deal"""
user = self.user_object user = self.user_object
if not self.user_subject.local: if not self.user_subject.local:
activity = activitypub.Accept( activity = activitypub.Accept(
@ -164,7 +164,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
self.delete() self.delete()
def reject(self): def reject(self):
""" generate a Reject for this follow request """ """generate a Reject for this follow request"""
if self.user_object.local: if self.user_object.local:
activity = activitypub.Reject( activity = activitypub.Reject(
id=self.get_accept_reject_id(status="rejects"), id=self.get_accept_reject_id(status="rejects"),
@ -177,13 +177,13 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
class UserBlocks(ActivityMixin, UserRelationship): class UserBlocks(ActivityMixin, UserRelationship):
""" prevent another user from following you and seeing your posts """ """prevent another user from following you and seeing your posts"""
status = "blocks" status = "blocks"
activity_serializer = activitypub.Block activity_serializer = activitypub.Block
def save(self, *args, **kwargs): 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) super().save(*args, **kwargs)
UserFollows.objects.filter( UserFollows.objects.filter(

View file

@ -6,7 +6,7 @@ from .base_model import BookWyrmModel
class Report(BookWyrmModel): class Report(BookWyrmModel):
""" reported status or user """ """reported status or user"""
reporter = models.ForeignKey( reporter = models.ForeignKey(
"User", related_name="reporter", on_delete=models.PROTECT "User", related_name="reporter", on_delete=models.PROTECT
@ -17,7 +17,7 @@ class Report(BookWyrmModel):
resolved = models.BooleanField(default=False) resolved = models.BooleanField(default=False)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" notify admins when a report is created """ """notify admins when a report is created"""
super().save(*args, **kwargs) super().save(*args, **kwargs)
user_model = apps.get_model("bookwyrm.User", require_ready=True) user_model = apps.get_model("bookwyrm.User", require_ready=True)
# moderators and superusers should be notified # moderators and superusers should be notified
@ -34,7 +34,7 @@ class Report(BookWyrmModel):
) )
class Meta: class Meta:
""" don't let users report themselves """ """don't let users report themselves"""
constraints = [ constraints = [
models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report") models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report")
@ -43,13 +43,13 @@ class Report(BookWyrmModel):
class ReportComment(BookWyrmModel): class ReportComment(BookWyrmModel):
""" updates on a report """ """updates on a report"""
user = models.ForeignKey("User", on_delete=models.PROTECT) user = models.ForeignKey("User", on_delete=models.PROTECT)
note = models.TextField() note = models.TextField()
report = models.ForeignKey(Report, on_delete=models.PROTECT) report = models.ForeignKey(Report, on_delete=models.PROTECT)
class Meta: class Meta:
""" sort comments """ """sort comments"""
ordering = ("-created_date",) ordering = ("-created_date",)

View file

@ -9,7 +9,7 @@ from . import fields
class Shelf(OrderedCollectionMixin, BookWyrmModel): class Shelf(OrderedCollectionMixin, BookWyrmModel):
""" a list of books owned by a user """ """a list of books owned by a user"""
TO_READ = "to-read" TO_READ = "to-read"
READING = "reading" READING = "reading"
@ -34,36 +34,36 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
activity_serializer = activitypub.Shelf activity_serializer = activitypub.Shelf
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" set the identifier """ """set the identifier"""
super().save(*args, **kwargs) super().save(*args, **kwargs)
if not self.identifier: if not self.identifier:
self.identifier = self.get_identifier() self.identifier = self.get_identifier()
super().save(*args, **kwargs, broadcast=False) super().save(*args, **kwargs, broadcast=False)
def get_identifier(self): def get_identifier(self):
""" custom-shelf-123 for the url """ """custom-shelf-123 for the url"""
slug = re.sub(r"[^\w]", "", self.name).lower() slug = re.sub(r"[^\w]", "", self.name).lower()
return "{:s}-{:d}".format(slug, self.id) return "{:s}-{:d}".format(slug, self.id)
@property @property
def collection_queryset(self): def collection_queryset(self):
""" list of books for this shelf, overrides OrderedCollectionMixin """ """list of books for this shelf, overrides OrderedCollectionMixin"""
return self.books.order_by("shelfbook") return self.books.order_by("shelfbook")
def get_remote_id(self): def get_remote_id(self):
""" shelf identifier instead of id """ """shelf identifier instead of id"""
base_path = self.user.remote_id base_path = self.user.remote_id
identifier = self.identifier or self.get_identifier() identifier = self.identifier or self.get_identifier()
return "%s/books/%s" % (base_path, identifier) return "%s/books/%s" % (base_path, identifier)
class Meta: class Meta:
""" user/shelf unqiueness """ """user/shelf unqiueness"""
unique_together = ("user", "identifier") unique_together = ("user", "identifier")
class ShelfBook(CollectionItemMixin, BookWyrmModel): 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( book = fields.ForeignKey(
"Edition", on_delete=models.PROTECT, activitypub_field="book" "Edition", on_delete=models.PROTECT, activitypub_field="book"

View file

@ -12,7 +12,7 @@ from .user import User
class SiteSettings(models.Model): class SiteSettings(models.Model):
""" customized settings for this instance """ """customized settings for this instance"""
name = models.CharField(default="BookWyrm", max_length=100) name = models.CharField(default="BookWyrm", max_length=100)
instance_tagline = models.CharField( instance_tagline = models.CharField(
@ -35,7 +35,7 @@ class SiteSettings(models.Model):
@classmethod @classmethod
def get(cls): def get(cls):
""" gets the site settings db entry or defaults """ """gets the site settings db entry or defaults"""
try: try:
return cls.objects.get(id=1) return cls.objects.get(id=1)
except cls.DoesNotExist: except cls.DoesNotExist:
@ -45,12 +45,12 @@ class SiteSettings(models.Model):
def new_access_code(): def new_access_code():
""" the identifier for a user invite """ """the identifier for a user invite"""
return base64.b32encode(Random.get_random_bytes(5)).decode("ascii") return base64.b32encode(Random.get_random_bytes(5)).decode("ascii")
class SiteInvite(models.Model): 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) created_date = models.DateTimeField(auto_now_add=True)
code = models.CharField(max_length=32, default=new_access_code) code = models.CharField(max_length=32, default=new_access_code)
@ -61,19 +61,19 @@ class SiteInvite(models.Model):
invitees = models.ManyToManyField(User, related_name="invitees") invitees = models.ManyToManyField(User, related_name="invitees")
def valid(self): def valid(self):
""" make sure it hasn't expired or been used """ """make sure it hasn't expired or been used"""
return (self.expiry is None or self.expiry > timezone.now()) and ( return (self.expiry is None or self.expiry > timezone.now()) and (
self.use_limit is None or self.times_used < self.use_limit self.use_limit is None or self.times_used < self.use_limit
) )
@property @property
def link(self): def link(self):
""" formats the invite link """ """formats the invite link"""
return "https://{}/invite/{}".format(DOMAIN, self.code) return "https://{}/invite/{}".format(DOMAIN, self.code)
class InviteRequest(BookWyrmModel): class InviteRequest(BookWyrmModel):
""" prospective users can request an invite """ """prospective users can request an invite"""
email = models.EmailField(max_length=255, unique=True) email = models.EmailField(max_length=255, unique=True)
invite = models.ForeignKey( invite = models.ForeignKey(
@ -83,30 +83,30 @@ class InviteRequest(BookWyrmModel):
ignored = models.BooleanField(default=False) ignored = models.BooleanField(default=False)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" don't create a request for a registered email """ """don't create a request for a registered email"""
if not self.id and User.objects.filter(email=self.email).exists(): if not self.id and User.objects.filter(email=self.email).exists():
raise IntegrityError() raise IntegrityError()
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_passowrd_reset_expiry(): 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() now = timezone.now()
return now + datetime.timedelta(days=1) return now + datetime.timedelta(days=1)
class PasswordReset(models.Model): 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) code = models.CharField(max_length=32, default=new_access_code)
expiry = models.DateTimeField(default=get_passowrd_reset_expiry) expiry = models.DateTimeField(default=get_passowrd_reset_expiry)
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
def valid(self): 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() return self.expiry > timezone.now()
@property @property
def link(self): def link(self):
""" formats the invite link """ """formats the invite link"""
return "https://{}/password-reset/{}".format(DOMAIN, self.code) return "https://{}/password-reset/{}".format(DOMAIN, self.code)

View file

@ -19,7 +19,7 @@ from . import fields
class Status(OrderedCollectionPageMixin, BookWyrmModel): 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 = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="attributedTo" "User", on_delete=models.PROTECT, activitypub_field="attributedTo"
@ -59,12 +59,12 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
deserialize_reverse_fields = [("attachments", "attachment")] deserialize_reverse_fields = [("attachments", "attachment")]
class Meta: class Meta:
""" default sorting """ """default sorting"""
ordering = ("-published_date",) ordering = ("-published_date",)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" save and notify """ """save and notify"""
super().save(*args, **kwargs) super().save(*args, **kwargs)
notification_model = apps.get_model("bookwyrm.Notification", require_ready=True) notification_model = apps.get_model("bookwyrm.Notification", require_ready=True)
@ -98,7 +98,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
) )
def delete(self, *args, **kwargs): # pylint: disable=unused-argument def delete(self, *args, **kwargs): # pylint: disable=unused-argument
""" "delete" a status """ """ "delete" a status"""
if hasattr(self, "boosted_status"): if hasattr(self, "boosted_status"):
# okay but if it's a boost really delete it # okay but if it's a boost really delete it
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
@ -109,7 +109,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
@property @property
def recipients(self): 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] mentions = [u for u in self.mention_users.all() if not u.local]
if ( if (
hasattr(self, "reply_parent") hasattr(self, "reply_parent")
@ -121,7 +121,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
@classmethod @classmethod
def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements
""" keep notes if they are replies to existing statuses """ """keep notes if they are replies to existing statuses"""
if activity.type == "Announce": if activity.type == "Announce":
try: try:
boosted = activitypub.resolve_remote_id( boosted = activitypub.resolve_remote_id(
@ -163,16 +163,16 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
@property @property
def status_type(self): 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__ return self.activity_serializer.__name__
@property @property
def boostable(self): def boostable(self):
""" you can't boost dms """ """you can't boost dms"""
return self.privacy in ["unlisted", "public"] return self.privacy in ["unlisted", "public"]
def to_replies(self, **kwargs): 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( return self.to_ordered_collection(
self.replies(self), self.replies(self),
remote_id="%s/replies" % self.remote_id, remote_id="%s/replies" % self.remote_id,
@ -181,7 +181,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
).serialize() ).serialize()
def to_activity_dataclass(self, pure=False): # pylint: disable=arguments-differ def to_activity_dataclass(self, pure=False): # pylint: disable=arguments-differ
""" return tombstone if the status is deleted """ """return tombstone if the status is deleted"""
if self.deleted: if self.deleted:
return activitypub.Tombstone( return activitypub.Tombstone(
id=self.remote_id, id=self.remote_id,
@ -210,16 +210,16 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
return activity return activity
def to_activity(self, pure=False): # pylint: disable=arguments-differ def to_activity(self, pure=False): # pylint: disable=arguments-differ
""" json serialized activitypub class """ """json serialized activitypub class"""
return self.to_activity_dataclass(pure=pure).serialize() return self.to_activity_dataclass(pure=pure).serialize()
class GeneratedNote(Status): class GeneratedNote(Status):
""" these are app-generated messages about user activity """ """these are app-generated messages about user activity"""
@property @property
def pure_content(self): 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 message = self.content
books = ", ".join( books = ", ".join(
'<a href="%s">"%s"</a>' % (book.remote_id, book.title) '<a href="%s">"%s"</a>' % (book.remote_id, book.title)
@ -232,7 +232,7 @@ class GeneratedNote(Status):
class Comment(Status): class Comment(Status):
""" like a review but without a rating and transient """ """like a review but without a rating and transient"""
book = fields.ForeignKey( book = fields.ForeignKey(
"Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook" "Edition", on_delete=models.PROTECT, activitypub_field="inReplyToBook"
@ -253,7 +253,7 @@ class Comment(Status):
@property @property
def pure_content(self): 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 '%s<p>(comment on <a href="%s">"%s"</a>)</p>' % ( return '%s<p>(comment on <a href="%s">"%s"</a>)</p>' % (
self.content, self.content,
self.book.remote_id, self.book.remote_id,
@ -265,7 +265,7 @@ class Comment(Status):
class Quotation(Status): class Quotation(Status):
""" like a review but without a rating and transient """ """like a review but without a rating and transient"""
quote = fields.HtmlField() quote = fields.HtmlField()
book = fields.ForeignKey( book = fields.ForeignKey(
@ -274,7 +274,7 @@ class Quotation(Status):
@property @property
def pure_content(self): 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"""
quote = re.sub(r"^<p>", '<p>"', self.quote) quote = re.sub(r"^<p>", '<p>"', self.quote)
quote = re.sub(r"</p>$", '"</p>', quote) quote = re.sub(r"</p>$", '"</p>', quote)
return '%s <p>-- <a href="%s">"%s"</a></p>%s' % ( return '%s <p>-- <a href="%s">"%s"</a></p>%s' % (
@ -289,7 +289,7 @@ class Quotation(Status):
class Review(Status): class Review(Status):
""" a book review """ """a book review"""
name = fields.CharField(max_length=255, null=True) name = fields.CharField(max_length=255, null=True)
book = fields.ForeignKey( book = fields.ForeignKey(
@ -306,7 +306,7 @@ class Review(Status):
@property @property
def pure_name(self): def pure_name(self):
""" clarify review names for mastodon serialization """ """clarify review names for mastodon serialization"""
template = get_template("snippets/generated_status/review_pure_name.html") template = get_template("snippets/generated_status/review_pure_name.html")
return template.render( return template.render(
{"book": self.book, "rating": self.rating, "name": self.name} {"book": self.book, "rating": self.rating, "name": self.name}
@ -314,7 +314,7 @@ class Review(Status):
@property @property
def pure_content(self): 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 return self.content
activity_serializer = activitypub.Review activity_serializer = activitypub.Review
@ -322,7 +322,7 @@ class Review(Status):
class ReviewRating(Review): class ReviewRating(Review):
""" a subtype of review that only contains a rating """ """a subtype of review that only contains a rating"""
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.rating: if not self.rating:
@ -339,7 +339,7 @@ class ReviewRating(Review):
class Boost(ActivityMixin, Status): class Boost(ActivityMixin, Status):
""" boost'ing a post """ """boost'ing a post"""
boosted_status = fields.ForeignKey( boosted_status = fields.ForeignKey(
"Status", "Status",
@ -350,7 +350,7 @@ class Boost(ActivityMixin, Status):
activity_serializer = activitypub.Announce activity_serializer = activitypub.Announce
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" save and notify """ """save and notify"""
# This constraint can't work as it would cross tables. # This constraint can't work as it would cross tables.
# class Meta: # class Meta:
# unique_together = ('user', 'boosted_status') # unique_together = ('user', 'boosted_status')
@ -374,7 +374,7 @@ class Boost(ActivityMixin, Status):
) )
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" delete and un-notify """ """delete and un-notify"""
notification_model = apps.get_model("bookwyrm.Notification", require_ready=True) notification_model = apps.get_model("bookwyrm.Notification", require_ready=True)
notification_model.objects.filter( notification_model.objects.filter(
user=self.boosted_status.user, user=self.boosted_status.user,
@ -385,7 +385,7 @@ class Boost(ActivityMixin, Status):
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
def __init__(self, *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) super().__init__(*args, **kwargs)
reserve_fields = ["user", "boosted_status", "published_date", "privacy"] reserve_fields = ["user", "boosted_status", "published_date", "privacy"]

View file

@ -35,7 +35,7 @@ DeactivationReason = models.TextChoices(
class User(OrderedCollectionPageMixin, AbstractUser): class User(OrderedCollectionPageMixin, AbstractUser):
""" a user who wants to read books """ """a user who wants to read books"""
username = fields.UsernameField() username = fields.UsernameField()
email = models.EmailField(unique=True, null=True) email = models.EmailField(unique=True, null=True)
@ -130,38 +130,38 @@ class User(OrderedCollectionPageMixin, AbstractUser):
@property @property
def following_link(self): def following_link(self):
""" just how to find out the following info """ """just how to find out the following info"""
return "{:s}/following".format(self.remote_id) return "{:s}/following".format(self.remote_id)
@property @property
def alt_text(self): def alt_text(self):
""" alt text with username """ """alt text with username"""
return "avatar for %s" % (self.localname or self.username) return "avatar for %s" % (self.localname or self.username)
@property @property
def display_name(self): def display_name(self):
""" show the cleanest version of the user's name possible """ """show the cleanest version of the user's name possible"""
if self.name and self.name != "": if self.name and self.name != "":
return self.name return self.name
return self.localname or self.username return self.localname or self.username
@property @property
def deleted(self): def deleted(self):
""" for consistent naming """ """for consistent naming"""
return not self.is_active return not self.is_active
activity_serializer = activitypub.Person activity_serializer = activitypub.Person
@classmethod @classmethod
def viewer_aware_objects(cls, viewer): 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) queryset = cls.objects.filter(is_active=True)
if viewer and viewer.is_authenticated: if viewer and viewer.is_authenticated:
queryset = queryset.exclude(blocks=viewer) queryset = queryset.exclude(blocks=viewer)
return queryset return queryset
def to_outbox(self, filter_type=None, **kwargs): def to_outbox(self, filter_type=None, **kwargs):
""" an ordered collection of statuses """ """an ordered collection of statuses"""
if filter_type: if filter_type:
filter_class = apps.get_model( filter_class = apps.get_model(
"bookwyrm.%s" % filter_type, require_ready=True "bookwyrm.%s" % filter_type, require_ready=True
@ -188,7 +188,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
).serialize() ).serialize()
def to_following_activity(self, **kwargs): def to_following_activity(self, **kwargs):
""" activitypub following list """ """activitypub following list"""
remote_id = "%s/following" % self.remote_id remote_id = "%s/following" % self.remote_id
return self.to_ordered_collection( return self.to_ordered_collection(
self.following.order_by("-updated_date").all(), self.following.order_by("-updated_date").all(),
@ -198,7 +198,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
) )
def to_followers_activity(self, **kwargs): def to_followers_activity(self, **kwargs):
""" activitypub followers list """ """activitypub followers list"""
remote_id = "%s/followers" % self.remote_id remote_id = "%s/followers" % self.remote_id
return self.to_ordered_collection( return self.to_ordered_collection(
self.followers.order_by("-updated_date").all(), self.followers.order_by("-updated_date").all(),
@ -227,7 +227,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
return activity_object return activity_object
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" populate fields for new local users """ """populate fields for new local users"""
created = not bool(self.id) created = not bool(self.id)
if not self.local and not re.match(regex.full_username, self.username): if not self.local and not re.match(regex.full_username, self.username):
# generate a username that uses the domain (webfinger format) # generate a username that uses the domain (webfinger format)
@ -292,19 +292,19 @@ class User(OrderedCollectionPageMixin, AbstractUser):
).save(broadcast=False) ).save(broadcast=False)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" deactivate rather than delete a user """ """deactivate rather than delete a user"""
self.is_active = False self.is_active = False
# skip the logic in this class's save() # skip the logic in this class's save()
super().save(*args, **kwargs) super().save(*args, **kwargs)
@property @property
def local_path(self): def local_path(self):
""" this model doesn't inherit bookwyrm model, so here we are """ """this model doesn't inherit bookwyrm model, so here we are"""
return "/user/%s" % (self.localname or self.username) return "/user/%s" % (self.localname or self.username)
class KeyPair(ActivitypubMixin, BookWyrmModel): 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) private_key = models.TextField(blank=True, null=True)
public_key = fields.TextField( public_key = fields.TextField(
@ -319,7 +319,7 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
return "%s/#main-key" % self.owner.remote_id return "%s/#main-key" % self.owner.remote_id
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" create a key pair """ """create a key pair"""
# no broadcasting happening here # no broadcasting happening here
if "broadcast" in kwargs: if "broadcast" in kwargs:
del kwargs["broadcast"] del kwargs["broadcast"]
@ -337,7 +337,7 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
class AnnualGoal(BookWyrmModel): class AnnualGoal(BookWyrmModel):
""" set a goal for how many books you read in a year """ """set a goal for how many books you read in a year"""
user = models.ForeignKey("User", on_delete=models.PROTECT) user = models.ForeignKey("User", on_delete=models.PROTECT)
goal = models.IntegerField(validators=[MinValueValidator(1)]) goal = models.IntegerField(validators=[MinValueValidator(1)])
@ -347,17 +347,17 @@ class AnnualGoal(BookWyrmModel):
) )
class Meta: class Meta:
""" unqiueness constraint """ """unqiueness constraint"""
unique_together = ("user", "year") unique_together = ("user", "year")
def get_remote_id(self): def get_remote_id(self):
""" put the year in the path """ """put the year in the path"""
return "%s/goal/%d" % (self.user.remote_id, self.year) return "%s/goal/%d" % (self.user.remote_id, self.year)
@property @property
def books(self): def books(self):
""" the books you've read this year """ """the books you've read this year"""
return ( return (
self.user.readthrough_set.filter(finish_date__year__gte=self.year) self.user.readthrough_set.filter(finish_date__year__gte=self.year)
.order_by("-finish_date") .order_by("-finish_date")
@ -366,7 +366,7 @@ class AnnualGoal(BookWyrmModel):
@property @property
def ratings(self): 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] book_ids = [r.book.id for r in self.books]
reviews = Review.objects.filter( reviews = Review.objects.filter(
user=self.user, user=self.user,
@ -376,12 +376,12 @@ class AnnualGoal(BookWyrmModel):
@property @property
def progress_percent(self): 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) return int(float(self.book_count / self.goal) * 100)
@property @property
def book_count(self): 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( return self.user.readthrough_set.filter(
finish_date__year__gte=self.year finish_date__year__gte=self.year
).count() ).count()
@ -389,7 +389,7 @@ class AnnualGoal(BookWyrmModel):
@app.task @app.task
def set_remote_server(user_id): 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) user = User.objects.get(id=user_id)
actor_parts = urlparse(user.remote_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)
@ -399,7 +399,7 @@ def set_remote_server(user_id):
def get_or_create_remote_server(domain): def get_or_create_remote_server(domain):
""" get info on a remote server """ """get info on a remote server"""
try: try:
return FederatedServer.objects.get(server_name=domain) return FederatedServer.objects.get(server_name=domain)
except FederatedServer.DoesNotExist: except FederatedServer.DoesNotExist:
@ -428,7 +428,7 @@ def get_or_create_remote_server(domain):
@app.task @app.task
def get_remote_reviews(outbox): def get_remote_reviews(outbox):
""" ingest reviews by a new remote bookwyrm user """ """ingest reviews by a new remote bookwyrm user"""
outbox_page = outbox + "?page=true&type=Review" outbox_page = outbox + "?page=true&type=Review"
data = get_data(outbox_page) data = get_data(outbox_page)

View file

@ -10,16 +10,16 @@ r = redis.Redis(
class RedisStore(ABC): class RedisStore(ABC):
""" sets of ranked, related objects, like statuses for a user's feed """ """sets of ranked, related objects, like statuses for a user's feed"""
max_length = settings.MAX_STREAM_LENGTH max_length = settings.MAX_STREAM_LENGTH
def get_value(self, obj): def get_value(self, obj):
""" the object and rank """ """the object and rank"""
return {obj.id: self.get_rank(obj)} return {obj.id: self.get_rank(obj)}
def add_object_to_related_stores(self, obj, execute=True): def add_object_to_related_stores(self, obj, execute=True):
""" add an object to all suitable stores """ """add an object to all suitable stores"""
value = self.get_value(obj) value = self.get_value(obj)
# we want to do this as a bulk operation, hence "pipeline" # we want to do this as a bulk operation, hence "pipeline"
pipeline = r.pipeline() pipeline = r.pipeline()
@ -34,14 +34,14 @@ class RedisStore(ABC):
return pipeline.execute() return pipeline.execute()
def remove_object_from_related_stores(self, obj): def remove_object_from_related_stores(self, obj):
""" remove an object from all stores """ """remove an object from all stores"""
pipeline = r.pipeline() pipeline = r.pipeline()
for store in self.get_stores_for_object(obj): for store in self.get_stores_for_object(obj):
pipeline.zrem(store, -1, obj.id) pipeline.zrem(store, -1, obj.id)
pipeline.execute() pipeline.execute()
def bulk_add_objects_to_store(self, objs, store): def bulk_add_objects_to_store(self, objs, store):
""" add a list of objects to a given store """ """add a list of objects to a given store"""
pipeline = r.pipeline() pipeline = r.pipeline()
for obj in objs[: self.max_length]: for obj in objs[: self.max_length]:
pipeline.zadd(store, self.get_value(obj)) pipeline.zadd(store, self.get_value(obj))
@ -50,18 +50,18 @@ class RedisStore(ABC):
pipeline.execute() pipeline.execute()
def bulk_remove_objects_from_store(self, objs, store): def bulk_remove_objects_from_store(self, objs, store):
""" remoev a list of objects from a given store """ """remoev a list of objects from a given store"""
pipeline = r.pipeline() pipeline = r.pipeline()
for obj in objs[: self.max_length]: for obj in objs[: self.max_length]:
pipeline.zrem(store, -1, obj.id) pipeline.zrem(store, -1, obj.id)
pipeline.execute() pipeline.execute()
def get_store(self, store): # pylint: disable=no-self-use def get_store(self, store): # pylint: disable=no-self-use
""" load the values in a store """ """load the values in a store"""
return r.zrevrange(store, 0, -1) return r.zrevrange(store, 0, -1)
def populate_store(self, store): def populate_store(self, store):
""" go from zero to a store """ """go from zero to a store"""
pipeline = r.pipeline() pipeline = r.pipeline()
queryset = self.get_objects_for_store(store) queryset = self.get_objects_for_store(store)
@ -75,12 +75,12 @@ class RedisStore(ABC):
@abstractmethod @abstractmethod
def get_objects_for_store(self, store): def get_objects_for_store(self, store):
""" a queryset of what should go in a store, used for populating it """ """a queryset of what should go in a store, used for populating it"""
@abstractmethod @abstractmethod
def get_stores_for_object(self, obj): def get_stores_for_object(self, obj):
""" the stores that an object belongs in """ """the stores that an object belongs in"""
@abstractmethod @abstractmethod
def get_rank(self, obj): def get_rank(self, obj):
""" how to rank an object """ """how to rank an object"""

View file

@ -3,7 +3,7 @@ from html.parser import HTMLParser
class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method
""" Removes any html that isn't allowed_tagsed from a block """ """Removes any html that isn't allowed_tagsed from a block"""
def __init__(self): def __init__(self):
HTMLParser.__init__(self) HTMLParser.__init__(self)
@ -28,7 +28,7 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method
self.allow_html = True self.allow_html = True
def handle_starttag(self, tag, attrs): 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: 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) self.tag_stack.append(tag)
@ -36,7 +36,7 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method
self.output.append(("data", "")) self.output.append(("data", ""))
def handle_endtag(self, tag): 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: if not self.allow_html or tag not in self.allowed_tags:
self.output.append(("data", "")) self.output.append(("data", ""))
return return
@ -51,11 +51,11 @@ class InputHtmlParser(HTMLParser): # pylint: disable=abstract-method
self.output.append(("tag", "</%s>" % tag)) self.output.append(("tag", "</%s>" % tag))
def handle_data(self, data): def handle_data(self, data):
""" extract the answer, if we're in an answer tag """ """extract the answer, if we're in an answer tag"""
self.output.append(("data", data)) self.output.append(("data", data))
def get_output(self): 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: if self.tag_stack:
self.allow_html = False self.allow_html = False
if not self.allow_html: if not self.allow_html:

View file

@ -13,7 +13,7 @@ MAX_SIGNATURE_AGE = 300
def create_key_pair(): 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 random_generator = Random.new().read
key = RSA.generate(1024, random_generator) key = RSA.generate(1024, random_generator)
private_key = key.export_key().decode("utf8") private_key = key.export_key().decode("utf8")
@ -23,7 +23,7 @@ def create_key_pair():
def make_signature(sender, destination, date, digest): 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) inbox_parts = urlparse(destination)
signature_headers = [ signature_headers = [
"(request-target): post %s" % inbox_parts.path, "(request-target): post %s" % inbox_parts.path,
@ -44,14 +44,14 @@ def make_signature(sender, destination, date, digest):
def make_digest(data): def make_digest(data):
""" creates a message digest for signing """ """creates a message digest for signing"""
return "SHA-256=" + b64encode(hashlib.sha256(data.encode("utf-8")).digest()).decode( return "SHA-256=" + b64encode(hashlib.sha256(data.encode("utf-8")).digest()).decode(
"utf-8" "utf-8"
) )
def verify_digest(request): def verify_digest(request):
""" checks if a digest is syntactically valid and matches the message """ """checks if a digest is syntactically valid and matches the message"""
algorithm, digest = request.headers["digest"].split("=", 1) algorithm, digest = request.headers["digest"].split("=", 1)
if algorithm == "SHA-256": if algorithm == "SHA-256":
hash_function = hashlib.sha256 hash_function = hashlib.sha256
@ -66,7 +66,7 @@ def verify_digest(request):
class Signature: class Signature:
""" read and validate incoming signatures """ """read and validate incoming signatures"""
def __init__(self, key_id, headers, signature): def __init__(self, key_id, headers, signature):
self.key_id = key_id self.key_id = key_id
@ -75,7 +75,7 @@ class Signature:
@classmethod @classmethod
def parse(cls, request): def parse(cls, request):
""" extract and parse a signature from an http request """ """extract and parse a signature from an http request"""
signature_dict = {} signature_dict = {}
for pair in request.headers["Signature"].split(","): for pair in request.headers["Signature"].split(","):
k, v = pair.split("=", 1) k, v = pair.split("=", 1)
@ -92,7 +92,7 @@ class Signature:
return cls(key_id, headers, signature) return cls(key_id, headers, signature)
def verify(self, public_key, request): def verify(self, public_key, request):
""" verify rsa signature """ """verify rsa signature"""
if http_date_age(request.headers["date"]) > MAX_SIGNATURE_AGE: if http_date_age(request.headers["date"]) > MAX_SIGNATURE_AGE:
raise ValueError("Request too old: %s" % (request.headers["date"],)) raise ValueError("Request too old: %s" % (request.headers["date"],))
public_key = RSA.import_key(public_key) public_key = RSA.import_key(public_key)
@ -118,7 +118,7 @@ class Signature:
def http_date_age(datestr): def http_date_age(datestr):
""" age of a signature in seconds """ """age of a signature in seconds"""
parsed = datetime.datetime.strptime(datestr, "%a, %d %b %Y %H:%M:%S GMT") parsed = datetime.datetime.strptime(datestr, "%a, %d %b %Y %H:%M:%S GMT")
delta = datetime.datetime.utcnow() - parsed delta = datetime.datetime.utcnow() - parsed
return delta.total_seconds() return delta.total_seconds()

View file

@ -6,7 +6,7 @@ from bookwyrm.sanitize_html import InputHtmlParser
def create_generated_note(user, content, mention_books=None, privacy="public"): def create_generated_note(user, content, mention_books=None, privacy="public"):
""" a note created by the app about user activity """ """a note created by the app about user activity"""
# sanitize input html # sanitize input html
parser = InputHtmlParser() parser = InputHtmlParser()
parser.feed(content) parser.feed(content)

View file

@ -13,13 +13,13 @@ register = template.Library()
@register.filter(name="dict_key") @register.filter(name="dict_key")
def dict_key(d, k): 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 return d.get(k) or 0
@register.filter(name="rating") @register.filter(name="rating")
def get_rating(book, user): def get_rating(book, user):
""" get the overall rating of a book """ """get the overall rating of a book"""
queryset = views.helpers.privacy_filter( queryset = views.helpers.privacy_filter(
user, models.Review.objects.filter(book=book) user, models.Review.objects.filter(book=book)
) )
@ -28,7 +28,7 @@ def get_rating(book, user):
@register.filter(name="user_rating") @register.filter(name="user_rating")
def get_user_rating(book, user): def get_user_rating(book, user):
""" get a user's rating of a book """ """get a user's rating of a book"""
rating = ( rating = (
models.Review.objects.filter( models.Review.objects.filter(
user=user, user=user,
@ -45,19 +45,19 @@ def get_user_rating(book, user):
@register.filter(name="username") @register.filter(name="username")
def get_user_identifier(user): 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 return user.localname if user.localname else user.username
@register.filter(name="notification_count") @register.filter(name="notification_count")
def get_notification_count(user): 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() return user.notification_set.filter(read=False).count()
@register.filter(name="replies") @register.filter(name="replies")
def get_replies(status): def get_replies(status):
""" get all direct replies to a status """ """get all direct replies to a status"""
# TODO: this limit could cause problems # TODO: this limit could cause problems
return models.Status.objects.filter( return models.Status.objects.filter(
reply_parent=status, reply_parent=status,
@ -67,7 +67,7 @@ def get_replies(status):
@register.filter(name="parent") @register.filter(name="parent")
def get_parent(status): def get_parent(status):
""" get the reply parent for a status """ """get the reply parent for a status"""
return ( return (
models.Status.objects.filter(id=status.reply_parent_id) models.Status.objects.filter(id=status.reply_parent_id)
.select_subclasses() .select_subclasses()
@ -77,7 +77,7 @@ def get_parent(status):
@register.filter(name="liked") @register.filter(name="liked")
def get_user_liked(user, status): def get_user_liked(user, status):
""" did the given user fav a status? """ """did the given user fav a status?"""
try: try:
models.Favorite.objects.get(user=user, status=status) models.Favorite.objects.get(user=user, status=status)
return True return True
@ -87,13 +87,13 @@ def get_user_liked(user, status):
@register.filter(name="boosted") @register.filter(name="boosted")
def get_user_boosted(user, status): def get_user_boosted(user, status):
""" did the given user fav a status? """ """did the given user fav a status?"""
return user.id in status.boosters.all().values_list("user", flat=True) 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): 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: try:
models.UserFollowRequest.objects.filter( models.UserFollowRequest.objects.filter(
user_subject=requester, user_subject=requester,
@ -106,7 +106,7 @@ def follow_request_exists(user, requester):
@register.filter(name="boosted_status") @register.filter(name="boosted_status")
def get_boosted(boost): def get_boosted(boost):
""" load a boosted status. have to do this or it wont get foregin keys """ """load a boosted status. have to do this or it wont get foregin keys"""
return ( return (
models.Status.objects.select_subclasses() models.Status.objects.select_subclasses()
.filter(id=boost.boosted_status.id) .filter(id=boost.boosted_status.id)
@ -116,19 +116,19 @@ def get_boosted(boost):
@register.filter(name="book_description") @register.filter(name="book_description")
def get_book_description(book): 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 return book.description or book.parent_work.description
@register.filter(name="uuid") @register.filter(name="uuid")
def get_uuid(identifier): def get_uuid(identifier):
""" for avoiding clashing ids when there are many forms """ """for avoiding clashing ids when there are many forms"""
return "%s%s" % (identifier, uuid4()) return "%s%s" % (identifier, uuid4())
@register.filter(name="to_markdown") @register.filter(name="to_markdown")
def get_markdown(content): def get_markdown(content):
""" convert markdown to html """ """convert markdown to html"""
if content: if content:
return to_markdown(content) return to_markdown(content)
return None return None
@ -136,7 +136,7 @@ def get_markdown(content):
@register.filter(name="mentions") @register.filter(name="mentions")
def get_mentions(status, user): def get_mentions(status, user):
""" people to @ in a reply: the parent and all mentions """ """people to @ in a reply: the parent and all mentions"""
mentions = set([status.user] + list(status.mention_users.all())) mentions = set([status.user] + list(status.mention_users.all()))
return ( return (
" ".join("@" + get_user_identifier(m) for m in mentions if not m == user) + " " " ".join("@" + get_user_identifier(m) for m in mentions if not m == user) + " "
@ -145,7 +145,7 @@ def get_mentions(status, user):
@register.filter(name="status_preview_name") @register.filter(name="status_preview_name")
def get_status_preview_name(obj): 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() name = obj.__class__.__name__.lower()
if name == "review": if name == "review":
return "%s of <em>%s</em>" % (name, obj.book.title) return "%s of <em>%s</em>" % (name, obj.book.title)
@ -158,7 +158,7 @@ def get_status_preview_name(obj):
@register.filter(name="next_shelf") @register.filter(name="next_shelf")
def get_next_shelf(current_shelf): def get_next_shelf(current_shelf):
""" shelf you'd use to update reading progress """ """shelf you'd use to update reading progress"""
if current_shelf == "to-read": if current_shelf == "to-read":
return "reading" return "reading"
if current_shelf == "reading": if current_shelf == "reading":
@ -170,7 +170,7 @@ def get_next_shelf(current_shelf):
@register.filter(name="title") @register.filter(name="title")
def get_title(book): def get_title(book):
""" display the subtitle if the title is short """ """display the subtitle if the title is short"""
if not book: if not book:
return "" return ""
title = book.title title = book.title
@ -181,7 +181,7 @@ def get_title(book):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def related_status(notification): def related_status(notification):
""" for notifications """ """for notifications"""
if not notification.related_status: if not notification.related_status:
return None return None
if hasattr(notification.related_status, "quotation"): if hasattr(notification.related_status, "quotation"):
@ -195,7 +195,7 @@ def related_status(notification):
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def active_shelf(context, book): 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 = 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() ).first()
@ -204,7 +204,7 @@ def active_shelf(context, book):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def latest_read_through(book, user): def latest_read_through(book, user):
""" the most recent read activity """ """the most recent read activity"""
return ( return (
models.ReadThrough.objects.filter(user=user, book=book) models.ReadThrough.objects.filter(user=user, book=book)
.order_by("-start_date") .order_by("-start_date")
@ -214,7 +214,7 @@ def latest_read_through(book, user):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def active_read_through(book, user): def active_read_through(book, user):
""" the most recent read activity """ """the most recent read activity"""
return ( return (
models.ReadThrough.objects.filter( models.ReadThrough.objects.filter(
user=user, book=book, finish_date__isnull=True user=user, book=book, finish_date__isnull=True
@ -226,12 +226,12 @@ def active_read_through(book, user):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def comparison_bool(str1, str2): 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 return str1 == str2
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_lang(): def get_lang():
""" get current language, strip to the first two letters """ """get current language, strip to the first two letters"""
language = utils.translation.get_language() language = utils.translation.get_language()
return language[0 : language.find("-")] return language[0 : language.find("-")]

View file

@ -21,10 +21,10 @@ from bookwyrm import models
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class BaseActivity(TestCase): class BaseActivity(TestCase):
""" the super class for model-linked activitypub dataclasses """ """the super class for model-linked activitypub dataclasses"""
def setUp(self): 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( self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
@ -45,28 +45,28 @@ class BaseActivity(TestCase):
self.image_data = output.getvalue() self.image_data = output.getvalue()
def test_init(self, _): def test_init(self, _):
""" simple successfuly init """ """simple successfuly init"""
instance = ActivityObject(id="a", type="b") instance = ActivityObject(id="a", type="b")
self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "id"))
self.assertTrue(hasattr(instance, "type")) self.assertTrue(hasattr(instance, "type"))
def test_init_missing(self, _): def test_init_missing(self, _):
""" init with missing required params """ """init with missing required params"""
with self.assertRaises(ActivitySerializerError): with self.assertRaises(ActivitySerializerError):
ActivityObject() ActivityObject()
def test_init_extra_fields(self, _): def test_init_extra_fields(self, _):
""" init ignoring additional fields """ """init ignoring additional fields"""
instance = ActivityObject(id="a", type="b", fish="c") instance = ActivityObject(id="a", type="b", fish="c")
self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "id"))
self.assertTrue(hasattr(instance, "type")) self.assertTrue(hasattr(instance, "type"))
def test_init_default_field(self, _): 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) @dataclass(init=False)
class TestClass(ActivityObject): class TestClass(ActivityObject):
""" test class with default field """ """test class with default field"""
type: str = "TestObject" type: str = "TestObject"
@ -75,7 +75,7 @@ class BaseActivity(TestCase):
self.assertEqual(instance.type, "TestObject") self.assertEqual(instance.type, "TestObject")
def test_serialize(self, _): def test_serialize(self, _):
""" simple function for converting dataclass to dict """ """simple function for converting dataclass to dict"""
instance = ActivityObject(id="a", type="b") instance = ActivityObject(id="a", type="b")
serialized = instance.serialize() serialized = instance.serialize()
self.assertIsInstance(serialized, dict) self.assertIsInstance(serialized, dict)
@ -84,7 +84,7 @@ class BaseActivity(TestCase):
@responses.activate @responses.activate
def test_resolve_remote_id(self, _): def test_resolve_remote_id(self, _):
""" look up or load remote data """ """look up or load remote data"""
# existing item # 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) self.assertEqual(result, self.user)
@ -106,14 +106,14 @@ class BaseActivity(TestCase):
self.assertEqual(result.name, "MOUSE?? MOUSE!!") self.assertEqual(result.name, "MOUSE?? MOUSE!!")
def test_to_model_invalid_model(self, _): def test_to_model_invalid_model(self, _):
""" catch mismatch between activity type and model type """ """catch mismatch between activity type and model type"""
instance = ActivityObject(id="a", type="b") instance = ActivityObject(id="a", type="b")
with self.assertRaises(ActivitySerializerError): with self.assertRaises(ActivitySerializerError):
instance.to_model(model=models.User) instance.to_model(model=models.User)
@responses.activate @responses.activate
def test_to_model_image(self, _): def test_to_model_image(self, _):
""" update an image field """ """update an image field"""
activity = activitypub.Person( activity = activitypub.Person(
id=self.user.remote_id, id=self.user.remote_id,
name="New Name", name="New Name",
@ -146,7 +146,7 @@ class BaseActivity(TestCase):
self.assertEqual(self.user.key_pair.public_key, "hi") self.assertEqual(self.user.key_pair.public_key, "hi")
def test_to_model_many_to_many(self, _): def test_to_model_many_to_many(self, _):
""" annoying that these all need special handling """ """annoying that these all need special handling"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create( status = models.Status.objects.create(
content="test status", content="test status",
@ -216,7 +216,7 @@ class BaseActivity(TestCase):
@responses.activate @responses.activate
def test_set_related_field(self, _): def test_set_related_field(self, _):
""" celery task to add back-references to created objects """ """celery task to add back-references to created objects"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create( status = models.Status.objects.create(
content="test status", content="test status",

View file

@ -8,10 +8,10 @@ from bookwyrm import activitypub, models
class Quotation(TestCase): class Quotation(TestCase):
""" we have hecka ways to create statuses """ """we have hecka ways to create statuses"""
def setUp(self): def setUp(self):
""" model objects we'll need """ """model objects we'll need"""
with patch("bookwyrm.models.user.set_remote_server.delay"): with patch("bookwyrm.models.user.set_remote_server.delay"):
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mouse", "mouse",
@ -30,7 +30,7 @@ class Quotation(TestCase):
self.status_data = json.loads(datafile.read_bytes()) self.status_data = json.loads(datafile.read_bytes())
def test_quotation_activity(self): 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) quotation = activitypub.Quotation(**self.status_data)
self.assertEqual(quotation.type, "Quotation") self.assertEqual(quotation.type, "Quotation")
@ -41,7 +41,7 @@ class Quotation(TestCase):
self.assertEqual(quotation.published, "2020-05-10T02:38:31.150343+00:00") self.assertEqual(quotation.published, "2020-05-10T02:38:31.150343+00:00")
def test_activity_to_model(self): 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) activity = activitypub.Quotation(**self.status_data)
quotation = activity.to_model(model=models.Quotation) quotation = activity.to_model(model=models.Quotation)

View file

@ -10,10 +10,10 @@ from bookwyrm.settings import DOMAIN
class AbstractConnector(TestCase): class AbstractConnector(TestCase):
""" generic code for connecting to outside data sources """ """generic code for connecting to outside data sources"""
def setUp(self): def setUp(self):
""" we need an example connector """ """we need an example connector"""
self.connector_info = models.Connector.objects.create( self.connector_info = models.Connector.objects.create(
identifier="example.com", identifier="example.com",
connector_file="openlibrary", connector_file="openlibrary",
@ -38,7 +38,7 @@ class AbstractConnector(TestCase):
self.edition_data = edition_data self.edition_data = edition_data
class TestConnector(abstract_connector.AbstractConnector): class TestConnector(abstract_connector.AbstractConnector):
""" nothing added here """ """nothing added here"""
def format_search_result(self, search_result): def format_search_result(self, search_result):
return search_result return search_result
@ -81,18 +81,18 @@ class AbstractConnector(TestCase):
) )
def test_abstract_connector_init(self): 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) self.assertIsInstance(self.connector.book_mappings, list)
def test_is_available(self): def test_is_available(self):
""" this isn't used.... """ """this isn't used...."""
self.assertTrue(self.connector.is_available()) self.assertTrue(self.connector.is_available())
self.connector.max_query_count = 1 self.connector.max_query_count = 1
self.connector.connector.query_count = 2 self.connector.connector.query_count = 2
self.assertFalse(self.connector.is_available()) self.assertFalse(self.connector.is_available())
def test_get_or_create_book_existing(self): 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(models.Book.objects.count(), 1)
self.assertEqual( self.assertEqual(
self.book.remote_id, "https://%s/book/%d" % (DOMAIN, self.book.id) self.book.remote_id, "https://%s/book/%d" % (DOMAIN, self.book.id)
@ -113,7 +113,7 @@ class AbstractConnector(TestCase):
@responses.activate @responses.activate
def test_get_or_create_book_deduped(self): def test_get_or_create_book_deduped(self):
""" load remote data and deduplicate """ """load remote data and deduplicate"""
responses.add( responses.add(
responses.GET, "https://example.com/book/abcd", json=self.edition_data responses.GET, "https://example.com/book/abcd", json=self.edition_data
) )
@ -125,7 +125,7 @@ class AbstractConnector(TestCase):
@responses.activate @responses.activate
def test_get_or_create_author(self): def test_get_or_create_author(self):
""" load an author """ """load an author"""
self.connector.author_mappings = [ self.connector.author_mappings = [
Mapping("id"), Mapping("id"),
Mapping("name"), Mapping("name"),
@ -142,7 +142,7 @@ class AbstractConnector(TestCase):
self.assertEqual(result.origin_id, "https://www.example.com/author") self.assertEqual(result.origin_id, "https://www.example.com/author")
def test_get_or_create_author_existing(self): def test_get_or_create_author_existing(self):
""" get an existing author """ """get an existing author"""
author = models.Author.objects.create(name="Test Author") author = models.Author.objects.create(name="Test Author")
result = self.connector.get_or_create_author(author.remote_id) result = self.connector.get_or_create_author(author.remote_id)
self.assertEqual(author, result) self.assertEqual(author, result)

View file

@ -8,10 +8,10 @@ from bookwyrm.connectors.abstract_connector import Mapping, SearchResult
class AbstractConnector(TestCase): class AbstractConnector(TestCase):
""" generic code for connecting to outside data sources """ """generic code for connecting to outside data sources"""
def setUp(self): def setUp(self):
""" we need an example connector """ """we need an example connector"""
self.connector_info = models.Connector.objects.create( self.connector_info = models.Connector.objects.create(
identifier="example.com", identifier="example.com",
connector_file="openlibrary", connector_file="openlibrary",
@ -23,7 +23,7 @@ class AbstractConnector(TestCase):
) )
class TestConnector(abstract_connector.AbstractMinimalConnector): class TestConnector(abstract_connector.AbstractMinimalConnector):
""" nothing added here """ """nothing added here"""
def format_search_result(self, search_result): def format_search_result(self, search_result):
return search_result return search_result
@ -43,7 +43,7 @@ class AbstractConnector(TestCase):
self.test_connector = TestConnector("example.com") self.test_connector = TestConnector("example.com")
def test_abstract_minimal_connector_init(self): def test_abstract_minimal_connector_init(self):
""" barebones connector for search with defaults """ """barebones connector for search with defaults"""
connector = self.test_connector connector = self.test_connector
self.assertEqual(connector.connector, self.connector_info) self.assertEqual(connector.connector, self.connector_info)
self.assertEqual(connector.base_url, "https://example.com") self.assertEqual(connector.base_url, "https://example.com")
@ -58,7 +58,7 @@ class AbstractConnector(TestCase):
@responses.activate @responses.activate
def test_search(self): def test_search(self):
""" makes an http request to the outside service """ """makes an http request to the outside service"""
responses.add( responses.add(
responses.GET, responses.GET,
"https://example.com/search?q=a%20book%20title", "https://example.com/search?q=a%20book%20title",
@ -73,7 +73,7 @@ class AbstractConnector(TestCase):
@responses.activate @responses.activate
def test_search_min_confidence(self): def test_search_min_confidence(self):
""" makes an http request to the outside service """ """makes an http request to the outside service"""
responses.add( responses.add(
responses.GET, responses.GET,
"https://example.com/search?q=a%20book%20title&min_confidence=1", "https://example.com/search?q=a%20book%20title&min_confidence=1",
@ -85,7 +85,7 @@ class AbstractConnector(TestCase):
@responses.activate @responses.activate
def test_isbn_search(self): def test_isbn_search(self):
""" makes an http request to the outside service """ """makes an http request to the outside service"""
responses.add( responses.add(
responses.GET, responses.GET,
"https://example.com/isbn?q=123456", "https://example.com/isbn?q=123456",
@ -96,7 +96,7 @@ class AbstractConnector(TestCase):
self.assertEqual(len(results), 10) self.assertEqual(len(results), 10)
def test_search_result(self): 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( result = SearchResult(
title="Title", title="Title",
key="https://example.com/book/1", key="https://example.com/book/1",
@ -109,21 +109,21 @@ class AbstractConnector(TestCase):
self.assertEqual(result.title, "Title") self.assertEqual(result.title, "Title")
def test_create_mapping(self): def test_create_mapping(self):
""" maps remote fields for book data to bookwyrm activitypub fields """ """maps remote fields for book data to bookwyrm activitypub fields"""
mapping = Mapping("isbn") mapping = Mapping("isbn")
self.assertEqual(mapping.local_field, "isbn") self.assertEqual(mapping.local_field, "isbn")
self.assertEqual(mapping.remote_field, "isbn") self.assertEqual(mapping.remote_field, "isbn")
self.assertEqual(mapping.formatter("bb"), "bb") self.assertEqual(mapping.formatter("bb"), "bb")
def test_create_mapping_with_remote(self): def test_create_mapping_with_remote(self):
""" the remote field is different than the local field """ """the remote field is different than the local field"""
mapping = Mapping("isbn", remote_field="isbn13") mapping = Mapping("isbn", remote_field="isbn13")
self.assertEqual(mapping.local_field, "isbn") self.assertEqual(mapping.local_field, "isbn")
self.assertEqual(mapping.remote_field, "isbn13") self.assertEqual(mapping.remote_field, "isbn13")
self.assertEqual(mapping.formatter("bb"), "bb") self.assertEqual(mapping.formatter("bb"), "bb")
def test_create_mapping_with_formatter(self): def test_create_mapping_with_formatter(self):
""" a function is provided to modify the data """ """a function is provided to modify the data"""
formatter = lambda x: "aa" + x formatter = lambda x: "aa" + x
mapping = Mapping("isbn", formatter=formatter) mapping = Mapping("isbn", formatter=formatter)
self.assertEqual(mapping.local_field, "isbn") self.assertEqual(mapping.local_field, "isbn")

View file

@ -9,10 +9,10 @@ from bookwyrm.connectors.abstract_connector import SearchResult
class BookWyrmConnector(TestCase): class BookWyrmConnector(TestCase):
""" this connector doesn't do much, just search """ """this connector doesn't do much, just search"""
def setUp(self): def setUp(self):
""" create the connector """ """create the connector"""
models.Connector.objects.create( models.Connector.objects.create(
identifier="example.com", identifier="example.com",
connector_file="bookwyrm_connector", connector_file="bookwyrm_connector",
@ -24,14 +24,14 @@ class BookWyrmConnector(TestCase):
self.connector = Connector("example.com") self.connector = Connector("example.com")
def test_get_or_create_book_existing(self): def test_get_or_create_book_existing(self):
""" load book activity """ """load book activity"""
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
book = models.Edition.objects.create(title="Test Edition", parent_work=work) book = models.Edition.objects.create(title="Test Edition", parent_work=work)
result = self.connector.get_or_create_book(book.remote_id) result = self.connector.get_or_create_book(book.remote_id)
self.assertEqual(book, result) self.assertEqual(book, result)
def test_format_search_result(self): def test_format_search_result(self):
""" create a SearchResult object from search response json """ """create a SearchResult object from search response json"""
datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_search.json") datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_search.json")
search_data = json.loads(datafile.read_bytes()) search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_search_data(search_data) results = self.connector.parse_search_data(search_data)
@ -46,7 +46,7 @@ class BookWyrmConnector(TestCase):
self.assertEqual(result.connector, self.connector) self.assertEqual(result.connector, self.connector)
def test_format_isbn_search_result(self): def test_format_isbn_search_result(self):
""" just gotta attach the connector """ """just gotta attach the connector"""
datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_search.json") datafile = pathlib.Path(__file__).parent.joinpath("../data/bw_search.json")
search_data = json.loads(datafile.read_bytes()) search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_isbn_search_data(search_data) results = self.connector.parse_isbn_search_data(search_data)

View file

@ -8,10 +8,10 @@ from bookwyrm.connectors.self_connector import Connector as SelfConnector
class ConnectorManager(TestCase): class ConnectorManager(TestCase):
""" interface between the app and various connectors """ """interface between the app and various connectors"""
def setUp(self): def setUp(self):
""" we'll need some books and a connector info entry """ """we'll need some books and a connector info entry"""
self.work = models.Work.objects.create(title="Example Work") self.work = models.Work.objects.create(title="Example Work")
self.edition = models.Edition.objects.create( self.edition = models.Edition.objects.create(
@ -32,7 +32,7 @@ class ConnectorManager(TestCase):
) )
def test_get_or_create_connector(self): def test_get_or_create_connector(self):
""" loads a connector if the data source is known or creates one """ """loads a connector if the data source is known or creates one"""
remote_id = "https://example.com/object/1" remote_id = "https://example.com/object/1"
connector = connector_manager.get_or_create_connector(remote_id) connector = connector_manager.get_or_create_connector(remote_id)
self.assertIsInstance(connector, BookWyrmConnector) self.assertIsInstance(connector, BookWyrmConnector)
@ -43,7 +43,7 @@ class ConnectorManager(TestCase):
self.assertEqual(connector.identifier, same_connector.identifier) self.assertEqual(connector.identifier, same_connector.identifier)
def test_get_connectors(self): def test_get_connectors(self):
""" load all connectors """ """load all connectors"""
remote_id = "https://example.com/object/1" remote_id = "https://example.com/object/1"
connector_manager.get_or_create_connector(remote_id) connector_manager.get_or_create_connector(remote_id)
connectors = list(connector_manager.get_connectors()) connectors = list(connector_manager.get_connectors())
@ -52,7 +52,7 @@ class ConnectorManager(TestCase):
self.assertIsInstance(connectors[1], BookWyrmConnector) self.assertIsInstance(connectors[1], BookWyrmConnector)
def test_search(self): def test_search(self):
""" search all connectors """ """search all connectors"""
results = connector_manager.search("Example") results = connector_manager.search("Example")
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)
self.assertIsInstance(results[0]["connector"], SelfConnector) self.assertIsInstance(results[0]["connector"], SelfConnector)
@ -60,7 +60,7 @@ class ConnectorManager(TestCase):
self.assertEqual(results[0]["results"][0].title, "Example Edition") self.assertEqual(results[0]["results"][0].title, "Example Edition")
def test_search_isbn(self): def test_search_isbn(self):
""" special handling if a query resembles an isbn """ """special handling if a query resembles an isbn"""
results = connector_manager.search("0000000000") results = connector_manager.search("0000000000")
self.assertEqual(len(results), 1) self.assertEqual(len(results), 1)
self.assertIsInstance(results[0]["connector"], SelfConnector) self.assertIsInstance(results[0]["connector"], SelfConnector)
@ -68,20 +68,20 @@ class ConnectorManager(TestCase):
self.assertEqual(results[0]["results"][0].title, "Example Edition") self.assertEqual(results[0]["results"][0].title, "Example Edition")
def test_local_search(self): def test_local_search(self):
""" search only the local database """ """search only the local database"""
results = connector_manager.local_search("Example") results = connector_manager.local_search("Example")
self.assertEqual(len(results), 1) 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): def test_first_search_result(self):
""" only get one search result """ """only get one search result"""
result = connector_manager.first_search_result("Example") result = connector_manager.first_search_result("Example")
self.assertEqual(result.title, "Example Edition") self.assertEqual(result.title, "Example Edition")
no_result = connector_manager.first_search_result("dkjfhg") no_result = connector_manager.first_search_result("dkjfhg")
self.assertIsNone(no_result) self.assertIsNone(no_result)
def test_load_connector(self): 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) connector = connector_manager.load_connector(self.connector)
self.assertIsInstance(connector, SelfConnector) self.assertIsInstance(connector, SelfConnector)
self.assertEqual(connector.identifier, "test_connector") self.assertEqual(connector.identifier, "test_connector")

View file

@ -16,10 +16,10 @@ from bookwyrm.connectors.connector_manager import ConnectorException
class Openlibrary(TestCase): class Openlibrary(TestCase):
""" test loading data from openlibrary.org """ """test loading data from openlibrary.org"""
def setUp(self): def setUp(self):
""" creates the connector we'll use """ """creates the connector we'll use"""
models.Connector.objects.create( models.Connector.objects.create(
identifier="openlibrary.org", identifier="openlibrary.org",
name="OpenLibrary", name="OpenLibrary",
@ -42,7 +42,7 @@ class Openlibrary(TestCase):
self.edition_list_data = json.loads(edition_list_file.read_bytes()) self.edition_list_data = json.loads(edition_list_file.read_bytes())
def test_get_remote_id_from_data(self): def test_get_remote_id_from_data(self):
""" format the remote id from the data """ """format the remote id from the data"""
data = {"key": "/work/OL1234W"} data = {"key": "/work/OL1234W"}
result = self.connector.get_remote_id_from_data(data) 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")
@ -51,13 +51,13 @@ class Openlibrary(TestCase):
self.connector.get_remote_id_from_data({}) self.connector.get_remote_id_from_data({})
def test_is_work_data(self): 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.work_data), True)
self.assertEqual(self.connector.is_work_data(self.edition_data), False) self.assertEqual(self.connector.is_work_data(self.edition_data), False)
@responses.activate @responses.activate
def test_get_edition_from_work_data(self): def test_get_edition_from_work_data(self):
""" loads a list of editions """ """loads a list of editions"""
data = {"key": "/work/OL1234W"} data = {"key": "/work/OL1234W"}
responses.add( responses.add(
responses.GET, responses.GET,
@ -74,7 +74,7 @@ class Openlibrary(TestCase):
@responses.activate @responses.activate
def test_get_work_from_edition_data(self): def test_get_work_from_edition_data(self):
""" loads a list of editions """ """loads a list of editions"""
data = {"works": [{"key": "/work/OL1234W"}]} data = {"works": [{"key": "/work/OL1234W"}]}
responses.add( responses.add(
responses.GET, responses.GET,
@ -87,7 +87,7 @@ class Openlibrary(TestCase):
@responses.activate @responses.activate
def test_get_authors_from_data(self): def test_get_authors_from_data(self):
""" find authors in data """ """find authors in data"""
responses.add( responses.add(
responses.GET, responses.GET,
"https://openlibrary.org/authors/OL382982A", "https://openlibrary.org/authors/OL382982A",
@ -112,13 +112,13 @@ class Openlibrary(TestCase):
self.assertEqual(result.openlibrary_key, "OL453734A") self.assertEqual(result.openlibrary_key, "OL453734A")
def test_get_cover_url(self): def test_get_cover_url(self):
""" formats a url that should contain the cover image """ """formats a url that should contain the cover image"""
blob = ["image"] blob = ["image"]
result = self.connector.get_cover_url(blob) 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): def test_parse_search_result(self):
""" extract the results from the search json response """ """extract the results from the search json response"""
datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_search.json") datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_search.json")
search_data = json.loads(datafile.read_bytes()) search_data = json.loads(datafile.read_bytes())
result = self.connector.parse_search_data(search_data) result = self.connector.parse_search_data(search_data)
@ -126,7 +126,7 @@ class Openlibrary(TestCase):
self.assertEqual(len(result), 2) self.assertEqual(len(result), 2)
def test_format_search_result(self): def test_format_search_result(self):
""" translate json from openlibrary into SearchResult """ """translate json from openlibrary into SearchResult"""
datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_search.json") datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_search.json")
search_data = json.loads(datafile.read_bytes()) search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_search_data(search_data) results = self.connector.parse_search_data(search_data)
@ -141,7 +141,7 @@ class Openlibrary(TestCase):
self.assertEqual(result.connector, self.connector) self.assertEqual(result.connector, self.connector)
def test_parse_isbn_search_result(self): def test_parse_isbn_search_result(self):
""" extract the results from the search json response """ """extract the results from the search json response"""
datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_isbn_search.json") datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_isbn_search.json")
search_data = json.loads(datafile.read_bytes()) search_data = json.loads(datafile.read_bytes())
result = self.connector.parse_isbn_search_data(search_data) result = self.connector.parse_isbn_search_data(search_data)
@ -149,7 +149,7 @@ class Openlibrary(TestCase):
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
def test_format_isbn_search_result(self): def test_format_isbn_search_result(self):
""" translate json from openlibrary into SearchResult """ """translate json from openlibrary into SearchResult"""
datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_isbn_search.json") datafile = pathlib.Path(__file__).parent.joinpath("../data/ol_isbn_search.json")
search_data = json.loads(datafile.read_bytes()) search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_isbn_search_data(search_data) results = self.connector.parse_isbn_search_data(search_data)
@ -165,7 +165,7 @@ class Openlibrary(TestCase):
@responses.activate @responses.activate
def test_load_edition_data(self): def test_load_edition_data(self):
""" format url from key and make request """ """format url from key and make request"""
key = "OL1234W" key = "OL1234W"
responses.add( responses.add(
responses.GET, responses.GET,
@ -177,7 +177,7 @@ class Openlibrary(TestCase):
@responses.activate @responses.activate
def test_expand_book_data(self): def test_expand_book_data(self):
""" given a book, get more editions """ """given a book, get more editions"""
work = models.Work.objects.create(title="Test Work", openlibrary_key="OL1234W") work = models.Work.objects.create(title="Test Work", openlibrary_key="OL1234W")
edition = models.Edition.objects.create(title="Test Edition", parent_work=work) edition = models.Edition.objects.create(title="Test Edition", parent_work=work)
@ -194,29 +194,29 @@ class Openlibrary(TestCase):
self.connector.expand_book_data(work) self.connector.expand_book_data(work)
def test_get_description(self): def test_get_description(self):
""" should do some cleanup on the description data """ """should do some cleanup on the description data"""
description = get_description(self.work_data["description"]) description = get_description(self.work_data["description"])
expected = "First in the Old Kingdom/Abhorsen series." expected = "First in the Old Kingdom/Abhorsen series."
self.assertEqual(description, expected) self.assertEqual(description, expected)
def test_get_openlibrary_key(self): def test_get_openlibrary_key(self):
""" extracts the uuid """ """extracts the uuid"""
key = get_openlibrary_key("/books/OL27320736M") key = get_openlibrary_key("/books/OL27320736M")
self.assertEqual(key, "OL27320736M") self.assertEqual(key, "OL27320736M")
def test_get_languages(self): def test_get_languages(self):
""" looks up languages from a list """ """looks up languages from a list"""
languages = get_languages(self.edition_data["languages"]) languages = get_languages(self.edition_data["languages"])
self.assertEqual(languages, ["English"]) self.assertEqual(languages, ["English"])
def test_pick_default_edition(self): def test_pick_default_edition(self):
""" detect if the loaded json is an edition """ """detect if the loaded json is an edition"""
edition = pick_default_edition(self.edition_list_data["entries"]) edition = pick_default_edition(self.edition_list_data["entries"])
self.assertEqual(edition["key"], "/books/OL9788823M") self.assertEqual(edition["key"], "/books/OL9788823M")
@responses.activate @responses.activate
def test_create_edition_from_data(self): def test_create_edition_from_data(self):
""" okay but can it actually create an edition with proper metadata """ """okay but can it actually create an edition with proper metadata"""
work = models.Work.objects.create(title="Hello") work = models.Work.objects.create(title="Hello")
responses.add( responses.add(
responses.GET, responses.GET,
@ -240,7 +240,7 @@ class Openlibrary(TestCase):
self.assertEqual(result.physical_format, "Hardcover") self.assertEqual(result.physical_format, "Hardcover")
def test_ignore_edition(self): def test_ignore_edition(self):
""" skip editions with poor metadata """ """skip editions with poor metadata"""
self.assertFalse(ignore_edition({"isbn_13": "hi"})) self.assertFalse(ignore_edition({"isbn_13": "hi"}))
self.assertFalse(ignore_edition({"oclc_numbers": "hi"})) self.assertFalse(ignore_edition({"oclc_numbers": "hi"}))
self.assertFalse(ignore_edition({"covers": "hi"})) self.assertFalse(ignore_edition({"covers": "hi"}))

View file

@ -9,10 +9,10 @@ from bookwyrm.settings import DOMAIN
class SelfConnector(TestCase): class SelfConnector(TestCase):
""" just uses local data """ """just uses local data"""
def setUp(self): def setUp(self):
""" creating the connector """ """creating the connector"""
models.Connector.objects.create( models.Connector.objects.create(
identifier=DOMAIN, identifier=DOMAIN,
name="Local", name="Local",
@ -27,7 +27,7 @@ class SelfConnector(TestCase):
self.connector = Connector(DOMAIN) self.connector = Connector(DOMAIN)
def test_format_search_result(self): def test_format_search_result(self):
""" create a SearchResult """ """create a SearchResult"""
author = models.Author.objects.create(name="Anonymous") author = models.Author.objects.create(name="Anonymous")
edition = models.Edition.objects.create( edition = models.Edition.objects.create(
title="Edition of Example Work", title="Edition of Example Work",
@ -42,7 +42,7 @@ class SelfConnector(TestCase):
self.assertEqual(result.connector, self.connector) self.assertEqual(result.connector, self.connector)
def test_search_rank(self): def test_search_rank(self):
""" prioritize certain results """ """prioritize certain results"""
author = models.Author.objects.create(name="Anonymous") author = models.Author.objects.create(name="Anonymous")
edition = models.Edition.objects.create( edition = models.Edition.objects.create(
title="Edition of Example Work", title="Edition of Example Work",
@ -78,7 +78,7 @@ class SelfConnector(TestCase):
self.assertEqual(results[2].title, "Edition of Example Work") self.assertEqual(results[2].title, "Edition of Example Work")
def test_search_multiple_editions(self): def test_search_multiple_editions(self):
""" it should get rid of duplicate editions for the same work """ """it should get rid of duplicate editions for the same work"""
work = models.Work.objects.create(title="Work Title") work = models.Work.objects.create(title="Work Title")
edition_1 = models.Edition.objects.create( edition_1 = models.Edition.objects.create(
title="Edition 1 Title", parent_work=work title="Edition 1 Title", parent_work=work

View file

@ -14,10 +14,10 @@ from bookwyrm.settings import DOMAIN
class GoodreadsImport(TestCase): class GoodreadsImport(TestCase):
""" importing from goodreads csv """ """importing from goodreads csv"""
def setUp(self): def setUp(self):
""" use a test csv """ """use a test csv"""
self.importer = GoodreadsImporter() self.importer = GoodreadsImporter()
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
self.csv = open(datafile, "r", encoding=self.importer.encoding) self.csv = open(datafile, "r", encoding=self.importer.encoding)
@ -44,7 +44,7 @@ class GoodreadsImport(TestCase):
) )
def test_create_job(self): def test_create_job(self):
""" creates the import job entry and checks csv """ """creates the import job entry and checks csv"""
import_job = self.importer.create_job(self.user, self.csv, False, "public") import_job = self.importer.create_job(self.user, self.csv, False, "public")
self.assertEqual(import_job.user, self.user) self.assertEqual(import_job.user, self.user)
self.assertEqual(import_job.include_reviews, False) self.assertEqual(import_job.include_reviews, False)
@ -60,7 +60,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(import_items[2].data["Book Id"], "28694510") self.assertEqual(import_items[2].data["Book Id"], "28694510")
def test_create_retry_job(self): def test_create_retry_job(self):
""" trying again with items that didn't import """ """trying again with items that didn't import"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
@ -78,7 +78,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(retry_items[1].data["Book Id"], "52691223") self.assertEqual(retry_items[1].data["Book Id"], "52691223")
def test_start_import(self): def test_start_import(self):
""" begin loading books """ """begin loading books"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
MockTask = namedtuple("Task", ("id")) MockTask = namedtuple("Task", ("id"))
mock_task = MockTask(7) mock_task = MockTask(7)
@ -90,7 +90,7 @@ class GoodreadsImport(TestCase):
@responses.activate @responses.activate
def test_import_data(self): def test_import_data(self):
""" resolve entry """ """resolve entry"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
book = models.Edition.objects.create(title="Test Book") book = models.Edition.objects.create(title="Test Book")
@ -105,7 +105,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(import_item.book.id, book.id) self.assertEqual(import_item.book.id, book.id)
def test_handle_imported_book(self): def test_handle_imported_book(self):
""" goodreads import added a book, this adds related connections """ """goodreads import added a book, this adds related connections"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
self.assertIsNone(shelf.books.first()) self.assertIsNone(shelf.books.first())
@ -138,7 +138,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(readthrough.finish_date.day, 25) self.assertEqual(readthrough.finish_date.day, 25)
def test_handle_imported_book_already_shelved(self): def test_handle_imported_book_already_shelved(self):
""" goodreads import added a book, this adds related connections """ """goodreads import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first() shelf = self.user.shelf_set.filter(identifier="to-read").first()
models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book)
@ -171,7 +171,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(readthrough.finish_date.day, 25) self.assertEqual(readthrough.finish_date.day, 25)
def test_handle_import_twice(self): def test_handle_import_twice(self):
""" re-importing books """ """re-importing books"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
@ -206,7 +206,7 @@ class GoodreadsImport(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _): def test_handle_imported_book_review(self, _):
""" goodreads review import """ """goodreads review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
csv_file = open(datafile, "r") csv_file = open(datafile, "r")
@ -229,7 +229,7 @@ class GoodreadsImport(TestCase):
self.assertEqual(review.privacy, "unlisted") self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self): def test_handle_imported_book_reviews_disabled(self):
""" goodreads review import """ """goodreads review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
csv_file = open(datafile, "r") csv_file = open(datafile, "r")

View file

@ -13,10 +13,10 @@ from bookwyrm.settings import DOMAIN
class LibrarythingImport(TestCase): class LibrarythingImport(TestCase):
""" importing from librarything tsv """ """importing from librarything tsv"""
def setUp(self): def setUp(self):
""" use a test tsv """ """use a test tsv"""
self.importer = LibrarythingImporter() self.importer = LibrarythingImporter()
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
@ -45,7 +45,7 @@ class LibrarythingImport(TestCase):
) )
def test_create_job(self): def test_create_job(self):
""" creates the import job entry and checks csv """ """creates the import job entry and checks csv"""
import_job = self.importer.create_job(self.user, self.csv, False, "public") import_job = self.importer.create_job(self.user, self.csv, False, "public")
self.assertEqual(import_job.user, self.user) self.assertEqual(import_job.user, self.user)
self.assertEqual(import_job.include_reviews, False) self.assertEqual(import_job.include_reviews, False)
@ -61,7 +61,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(import_items[2].data["Book Id"], "5015399") self.assertEqual(import_items[2].data["Book Id"], "5015399")
def test_create_retry_job(self): def test_create_retry_job(self):
""" trying again with items that didn't import """ """trying again with items that didn't import"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] import_items = models.ImportItem.objects.filter(job=import_job).all()[:2]
@ -80,7 +80,7 @@ class LibrarythingImport(TestCase):
@responses.activate @responses.activate
def test_import_data(self): def test_import_data(self):
""" resolve entry """ """resolve entry"""
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
book = models.Edition.objects.create(title="Test Book") book = models.Edition.objects.create(title="Test Book")
@ -95,7 +95,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(import_item.book.id, book.id) self.assertEqual(import_item.book.id, book.id)
def test_handle_imported_book(self): def test_handle_imported_book(self):
""" librarything import added a book, this adds related connections """ """librarything import added a book, this adds related connections"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
self.assertIsNone(shelf.books.first()) self.assertIsNone(shelf.books.first())
@ -130,7 +130,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(readthrough.finish_date.day, 8) self.assertEqual(readthrough.finish_date.day, 8)
def test_handle_imported_book_already_shelved(self): def test_handle_imported_book_already_shelved(self):
""" librarything import added a book, this adds related connections """ """librarything import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first() shelf = self.user.shelf_set.filter(identifier="to-read").first()
models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book) models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book)
@ -165,7 +165,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(readthrough.finish_date.day, 8) self.assertEqual(readthrough.finish_date.day, 8)
def test_handle_import_twice(self): def test_handle_import_twice(self):
""" re-importing books """ """re-importing books"""
shelf = self.user.shelf_set.filter(identifier="read").first() shelf = self.user.shelf_set.filter(identifier="read").first()
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
@ -202,7 +202,7 @@ class LibrarythingImport(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _): def test_handle_imported_book_review(self, _):
""" librarything review import """ """librarything review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
csv_file = open(datafile, "r", encoding=self.importer.encoding) csv_file = open(datafile, "r", encoding=self.importer.encoding)
@ -225,7 +225,7 @@ class LibrarythingImport(TestCase):
self.assertEqual(review.privacy, "unlisted") self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self): def test_handle_imported_book_reviews_disabled(self):
""" librarything review import """ """librarything review import"""
import_job = models.ImportJob.objects.create(user=self.user) import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv")
csv_file = open(datafile, "r", encoding=self.importer.encoding) csv_file = open(datafile, "r", encoding=self.importer.encoding)

View file

@ -8,10 +8,10 @@ from bookwyrm.management.commands.populate_streams import populate_streams
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
class Activitystreams(TestCase): class Activitystreams(TestCase):
""" using redis to build activity streams """ """using redis to build activity streams"""
def setUp(self): def setUp(self):
""" we need some stuff """ """we need some stuff"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
) )
@ -31,7 +31,7 @@ class Activitystreams(TestCase):
self.book = models.Edition.objects.create(title="test book") self.book = models.Edition.objects.create(title="test book")
def test_populate_streams(self, _): def test_populate_streams(self, _):
""" make sure the function on the redis manager gets called """ """make sure the function on the redis manager gets called"""
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
models.Comment.objects.create( models.Comment.objects.create(
user=self.local_user, content="hi", book=self.book user=self.local_user, content="hi", book=self.book

View file

@ -15,10 +15,10 @@ from bookwyrm.models.activitypub_mixin import ActivityMixin, ObjectMixin
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class ActivitypubMixins(TestCase): class ActivitypubMixins(TestCase):
""" functionality shared across models """ """functionality shared across models"""
def setUp(self): def setUp(self):
""" shared data """ """shared data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
) )
@ -46,16 +46,16 @@ class ActivitypubMixins(TestCase):
# ActivitypubMixin # ActivitypubMixin
def test_to_activity(self, _): def test_to_activity(self, _):
""" model to ActivityPub json """ """model to ActivityPub json"""
@dataclass(init=False) @dataclass(init=False)
class TestActivity(ActivityObject): class TestActivity(ActivityObject):
""" real simple mock """ """real simple mock"""
type: str = "Test" type: str = "Test"
class TestModel(ActivitypubMixin, base_model.BookWyrmModel): 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 = TestModel()
instance.remote_id = "https://www.example.com/test" instance.remote_id = "https://www.example.com/test"
@ -67,7 +67,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(activity["type"], "Test") self.assertEqual(activity["type"], "Test")
def test_find_existing_by_remote_id(self, _): 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 # uses a different remote id scheme
# this isn't really part of this test directly but it's helpful to state # this isn't really part of this test directly but it's helpful to state
book = models.Edition.objects.create( book = models.Edition.objects.create(
@ -100,7 +100,7 @@ class ActivitypubMixins(TestCase):
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, _): 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( book = models.Edition.objects.create(
title="Test edition", title="Test edition",
openlibrary_key="OL1234", openlibrary_key="OL1234",
@ -110,7 +110,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(result, book) self.assertEqual(result, book)
def test_get_recipients_public_object(self, _): def test_get_recipients_public_object(self, _):
""" determines the recipients for an object's broadcast """ """determines the recipients for an object's broadcast"""
MockSelf = namedtuple("Self", ("privacy")) MockSelf = namedtuple("Self", ("privacy"))
mock_self = MockSelf("public") mock_self = MockSelf("public")
recipients = ActivitypubMixin.get_recipients(mock_self) recipients = ActivitypubMixin.get_recipients(mock_self)
@ -118,7 +118,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(recipients[0], self.remote_user.inbox) self.assertEqual(recipients[0], self.remote_user.inbox)
def test_get_recipients_public_user_object_no_followers(self, _): def test_get_recipients_public_user_object_no_followers(self, _):
""" determines the recipients for a user's object broadcast """ """determines the recipients for a user's object broadcast"""
MockSelf = namedtuple("Self", ("privacy", "user")) MockSelf = namedtuple("Self", ("privacy", "user"))
mock_self = MockSelf("public", self.local_user) mock_self = MockSelf("public", self.local_user)
@ -126,7 +126,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(len(recipients), 0) self.assertEqual(len(recipients), 0)
def test_get_recipients_public_user_object(self, _): def test_get_recipients_public_user_object(self, _):
""" determines the recipients for a user's object broadcast """ """determines the recipients for a user's object broadcast"""
MockSelf = namedtuple("Self", ("privacy", "user")) MockSelf = namedtuple("Self", ("privacy", "user"))
mock_self = MockSelf("public", self.local_user) mock_self = MockSelf("public", self.local_user)
self.local_user.followers.add(self.remote_user) self.local_user.followers.add(self.remote_user)
@ -136,7 +136,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(recipients[0], self.remote_user.inbox) self.assertEqual(recipients[0], self.remote_user.inbox)
def test_get_recipients_public_user_object_with_mention(self, _): def test_get_recipients_public_user_object_with_mention(self, _):
""" determines the recipients for a user's object broadcast """ """determines the recipients for a user's object broadcast"""
MockSelf = namedtuple("Self", ("privacy", "user")) MockSelf = namedtuple("Self", ("privacy", "user"))
mock_self = MockSelf("public", self.local_user) mock_self = MockSelf("public", self.local_user)
self.local_user.followers.add(self.remote_user) self.local_user.followers.add(self.remote_user)
@ -159,7 +159,7 @@ class ActivitypubMixins(TestCase):
self.assertTrue(self.remote_user.inbox in recipients) self.assertTrue(self.remote_user.inbox in recipients)
def test_get_recipients_direct(self, _): def test_get_recipients_direct(self, _):
""" determines the recipients for a user's object broadcast """ """determines the recipients for a user's object broadcast"""
MockSelf = namedtuple("Self", ("privacy", "user")) MockSelf = namedtuple("Self", ("privacy", "user"))
mock_self = MockSelf("public", self.local_user) mock_self = MockSelf("public", self.local_user)
self.local_user.followers.add(self.remote_user) self.local_user.followers.add(self.remote_user)
@ -181,7 +181,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(recipients[0], another_remote_user.inbox) self.assertEqual(recipients[0], another_remote_user.inbox)
def test_get_recipients_combine_inboxes(self, _): def test_get_recipients_combine_inboxes(self, _):
""" should combine users with the same shared_inbox """ """should combine users with the same shared_inbox"""
self.remote_user.shared_inbox = "http://example.com/inbox" self.remote_user.shared_inbox = "http://example.com/inbox"
self.remote_user.save(broadcast=False) self.remote_user.save(broadcast=False)
with patch("bookwyrm.models.user.set_remote_server.delay"): with patch("bookwyrm.models.user.set_remote_server.delay"):
@ -205,7 +205,7 @@ class ActivitypubMixins(TestCase):
self.assertEqual(recipients[0], "http://example.com/inbox") self.assertEqual(recipients[0], "http://example.com/inbox")
def test_get_recipients_software(self, _): def test_get_recipients_software(self, _):
""" should differentiate between bookwyrm and other remote users """ """should differentiate between bookwyrm and other remote users"""
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( another_remote_user = models.User.objects.create_user(
"nutria", "nutria",
@ -235,13 +235,13 @@ class ActivitypubMixins(TestCase):
# ObjectMixin # ObjectMixin
def test_object_save_create(self, _): def test_object_save_create(self, _):
""" should save uneventufully when broadcast is disabled """ """should save uneventufully when broadcast is disabled"""
class Success(Exception): 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): class ObjectModel(ObjectMixin, base_model.BookWyrmModel):
""" real simple mock model because BookWyrmModel is abstract """ """real simple mock model because BookWyrmModel is abstract"""
user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE) user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE)
@ -252,7 +252,7 @@ class ActivitypubMixins(TestCase):
def broadcast( def broadcast(
self, activity, sender, **kwargs self, activity, sender, **kwargs
): # pylint: disable=arguments-differ ): # pylint: disable=arguments-differ
""" do something """ """do something"""
raise Success() raise Success()
def to_create_activity(self, user): # pylint: disable=arguments-differ def to_create_activity(self, user): # pylint: disable=arguments-differ
@ -266,13 +266,13 @@ class ActivitypubMixins(TestCase):
ObjectModel(user=None).save() ObjectModel(user=None).save()
def test_object_save_update(self, _): def test_object_save_update(self, _):
""" should save uneventufully when broadcast is disabled """ """should save uneventufully when broadcast is disabled"""
class Success(Exception): 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): class UpdateObjectModel(ObjectMixin, base_model.BookWyrmModel):
""" real simple mock model because BookWyrmModel is abstract """ """real simple mock model because BookWyrmModel is abstract"""
user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE) user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE)
last_edited_by = models.fields.ForeignKey( last_edited_by = models.fields.ForeignKey(
@ -292,13 +292,13 @@ class ActivitypubMixins(TestCase):
UpdateObjectModel(id=1, last_edited_by=self.local_user).save() UpdateObjectModel(id=1, last_edited_by=self.local_user).save()
def test_object_save_delete(self, _): 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): 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): class DeletableObjectModel(ObjectMixin, base_model.BookWyrmModel):
""" real simple mock model because BookWyrmModel is abstract """ """real simple mock model because BookWyrmModel is abstract"""
user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE) user = models.fields.ForeignKey("User", on_delete=db.models.CASCADE)
deleted = models.fields.BooleanField() deleted = models.fields.BooleanField()
@ -314,7 +314,7 @@ class ActivitypubMixins(TestCase):
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, _): def test_to_delete_activity(self, _):
""" wrapper for Delete activity """ """wrapper for Delete activity"""
MockSelf = namedtuple("Self", ("remote_id", "to_activity")) MockSelf = namedtuple("Self", ("remote_id", "to_activity"))
mock_self = MockSelf( mock_self = MockSelf(
"https://example.com/status/1", lambda *args: self.object_mock "https://example.com/status/1", lambda *args: self.object_mock
@ -329,7 +329,7 @@ class ActivitypubMixins(TestCase):
) )
def test_to_update_activity(self, _): def test_to_update_activity(self, _):
""" ditto above but for Update """ """ditto above but for Update"""
MockSelf = namedtuple("Self", ("remote_id", "to_activity")) MockSelf = namedtuple("Self", ("remote_id", "to_activity"))
mock_self = MockSelf( mock_self = MockSelf(
"https://example.com/status/1", lambda *args: self.object_mock "https://example.com/status/1", lambda *args: self.object_mock
@ -347,7 +347,7 @@ class ActivitypubMixins(TestCase):
# Activity mixin # Activity mixin
def test_to_undo_activity(self, _): def test_to_undo_activity(self, _):
""" and again, for Undo """ """and again, for Undo"""
MockSelf = namedtuple("Self", ("remote_id", "to_activity", "user")) MockSelf = namedtuple("Self", ("remote_id", "to_activity", "user"))
mock_self = MockSelf( mock_self = MockSelf(
"https://example.com/status/1", "https://example.com/status/1",

View file

@ -8,10 +8,10 @@ from bookwyrm.settings import DOMAIN
class BaseModel(TestCase): class BaseModel(TestCase):
""" functionality shared across models """ """functionality shared across models"""
def setUp(self): def setUp(self):
""" shared data """ """shared data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
) )
@ -27,14 +27,14 @@ class BaseModel(TestCase):
) )
def test_remote_id(self): def test_remote_id(self):
""" these should be generated """ """these should be generated"""
instance = base_model.BookWyrmModel() instance = base_model.BookWyrmModel()
instance.id = 1 instance.id = 1
expected = instance.get_remote_id() 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): 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"""
instance = base_model.BookWyrmModel() instance = base_model.BookWyrmModel()
instance.user = self.local_user instance.user = self.local_user
instance.id = 1 instance.id = 1
@ -42,7 +42,7 @@ class BaseModel(TestCase):
self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN) self.assertEqual(expected, "https://%s/user/mouse/bookwyrmmodel/1" % DOMAIN)
def test_set_remote_id(self): def test_set_remote_id(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 # using Work because it BookWrymModel is abstract and this requires save
# Work is a relatively not-fancy model. # Work is a relatively not-fancy model.
instance = models.Work.objects.create(title="work title") instance = models.Work.objects.create(title="work title")
@ -59,7 +59,7 @@ class BaseModel(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_object_visible_to_user(self, _): 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( obj = models.Status.objects.create(
content="hi", user=self.remote_user, privacy="public" content="hi", user=self.remote_user, privacy="public"
) )
@ -88,7 +88,7 @@ class BaseModel(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_object_visible_to_user_follower(self, _): 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) self.remote_user.followers.add(self.local_user)
obj = models.Status.objects.create( obj = models.Status.objects.create(
content="hi", user=self.remote_user, privacy="followers" content="hi", user=self.remote_user, privacy="followers"
@ -108,7 +108,7 @@ class BaseModel(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_object_visible_to_user_blocked(self, _): 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) self.remote_user.blocks.add(self.local_user)
obj = models.Status.objects.create( obj = models.Status.objects.create(
content="hi", user=self.remote_user, privacy="public" content="hi", user=self.remote_user, privacy="public"

View file

@ -8,10 +8,10 @@ from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10
class Book(TestCase): 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): def setUp(self):
""" we'll need some books """ """we'll need some books"""
self.work = models.Work.objects.create( 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"
) )
@ -25,17 +25,17 @@ class Book(TestCase):
) )
def test_remote_id(self): def test_remote_id(self):
""" fanciness with remote/origin ids """ """fanciness with remote/origin ids"""
remote_id = "https://%s/book/%d" % (settings.DOMAIN, self.work.id) remote_id = "https://%s/book/%d" % (settings.DOMAIN, self.work.id)
self.assertEqual(self.work.get_remote_id(), remote_id) self.assertEqual(self.work.get_remote_id(), remote_id)
self.assertEqual(self.work.remote_id, remote_id) self.assertEqual(self.work.remote_id, remote_id)
def test_create_book(self): def test_create_book(self):
""" you shouldn't be able to create Books (only editions and works) """ """you shouldn't be able to create Books (only editions and works)"""
self.assertRaises(ValueError, models.Book.objects.create, title="Invalid Book") self.assertRaises(ValueError, models.Book.objects.create, title="Invalid Book")
def test_isbn_10_to_13(self): def test_isbn_10_to_13(self):
""" checksums and so on """ """checksums and so on"""
isbn_10 = "178816167X" isbn_10 = "178816167X"
isbn_13 = isbn_10_to_13(isbn_10) isbn_13 = isbn_10_to_13(isbn_10)
self.assertEqual(isbn_13, "9781788161671") self.assertEqual(isbn_13, "9781788161671")
@ -45,7 +45,7 @@ class Book(TestCase):
self.assertEqual(isbn_13, "9781788161671") self.assertEqual(isbn_13, "9781788161671")
def test_isbn_13_to_10(self): def test_isbn_13_to_10(self):
""" checksums and so on """ """checksums and so on"""
isbn_13 = "9781788161671" isbn_13 = "9781788161671"
isbn_10 = isbn_13_to_10(isbn_13) isbn_10 = isbn_13_to_10(isbn_13)
self.assertEqual(isbn_10, "178816167X") self.assertEqual(isbn_10, "178816167X")
@ -55,7 +55,7 @@ class Book(TestCase):
self.assertEqual(isbn_10, "178816167X") self.assertEqual(isbn_10, "178816167X")
def test_get_edition_info(self): def test_get_edition_info(self):
""" text slug about an edition """ """text slug about an edition"""
book = models.Edition.objects.create(title="Test Edition") book = models.Edition.objects.create(title="Test Edition")
self.assertEqual(book.edition_info, "") self.assertEqual(book.edition_info, "")
@ -77,7 +77,7 @@ class Book(TestCase):
self.assertEqual(book.alt_text, "Test Edition (worm, Glorbish language, 2020)") self.assertEqual(book.alt_text, "Test Edition (worm, Glorbish language, 2020)")
def test_get_rank(self): def test_get_rank(self):
""" sets the data quality index for the book """ """sets the data quality index for the book"""
# basic rank # basic rank
self.assertEqual(self.first_edition.edition_rank, 0) self.assertEqual(self.first_edition.edition_rank, 0)

View file

@ -6,10 +6,10 @@ from bookwyrm import models
class FederatedServer(TestCase): class FederatedServer(TestCase):
""" federate server management """ """federate server management"""
def setUp(self): def setUp(self):
""" we'll need a user """ """we'll need a user"""
self.server = models.FederatedServer.objects.create(server_name="test.server") self.server = models.FederatedServer.objects.create(server_name="test.server")
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( self.remote_user = models.User.objects.create_user(
@ -36,7 +36,7 @@ class FederatedServer(TestCase):
) )
def test_block_unblock(self): def test_block_unblock(self):
""" block a server and all users on it """ """block a server and all users on it"""
self.assertEqual(self.server.status, "federated") self.assertEqual(self.server.status, "federated")
self.assertTrue(self.remote_user.is_active) self.assertTrue(self.remote_user.is_active)
self.assertFalse(self.inactive_remote_user.is_active) self.assertFalse(self.inactive_remote_user.is_active)

View file

@ -25,10 +25,10 @@ from bookwyrm.models.activitypub_mixin import ActivitypubMixin
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class ActivitypubFields(TestCase): 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): def test_validate_remote_id(self):
""" should look like a url """ """should look like a url"""
self.assertIsNone(fields.validate_remote_id("http://www.example.com")) 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("https://www.example.com"))
self.assertIsNone(fields.validate_remote_id("http://exle.com/dlg-23/x")) self.assertIsNone(fields.validate_remote_id("http://exle.com/dlg-23/x"))
@ -45,7 +45,7 @@ class ActivitypubFields(TestCase):
) )
def test_activitypub_field_mixin(self): 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() instance = fields.ActivitypubFieldMixin()
self.assertEqual(instance.field_to_activity("fish"), "fish") self.assertEqual(instance.field_to_activity("fish"), "fish")
self.assertEqual(instance.field_from_activity("fish"), "fish") self.assertEqual(instance.field_from_activity("fish"), "fish")
@ -63,11 +63,11 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.get_activitypub_field(), "snakeCaseName") self.assertEqual(instance.get_activitypub_field(), "snakeCaseName")
def test_set_field_from_activity(self): def test_set_field_from_activity(self):
""" setter from entire json blob """ """setter from entire json blob"""
@dataclass @dataclass
class TestModel: class TestModel:
""" real simple mock """ """real simple mock"""
field_name: str field_name: str
@ -82,11 +82,11 @@ class ActivitypubFields(TestCase):
self.assertEqual(mock_model.field_name, "hi") self.assertEqual(mock_model.field_name, "hi")
def test_set_activity_from_field(self): def test_set_activity_from_field(self):
""" set json field given entire model """ """set json field given entire model"""
@dataclass @dataclass
class TestModel: class TestModel:
""" real simple mock """ """real simple mock"""
field_name: str field_name: str
unrelated: str unrelated: str
@ -100,7 +100,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(data["fieldName"], "bip") self.assertEqual(data["fieldName"], "bip")
def test_remote_id_field(self): def test_remote_id_field(self):
""" just sets some defaults on charfield """ """just sets some defaults on charfield"""
instance = fields.RemoteIdField() instance = fields.RemoteIdField()
self.assertEqual(instance.max_length, 255) self.assertEqual(instance.max_length, 255)
self.assertTrue(instance.deduplication_field) self.assertTrue(instance.deduplication_field)
@ -109,7 +109,7 @@ class ActivitypubFields(TestCase):
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): def test_username_field(self):
""" again, just setting defaults on username field """ """again, just setting defaults on username field"""
instance = fields.UsernameField() instance = fields.UsernameField()
self.assertEqual(instance.activitypub_field, "preferredUsername") self.assertEqual(instance.activitypub_field, "preferredUsername")
self.assertEqual(instance.max_length, 150) self.assertEqual(instance.max_length, 150)
@ -130,7 +130,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.field_to_activity("test@example.com"), "test") self.assertEqual(instance.field_to_activity("test@example.com"), "test")
def test_privacy_field_defaults(self): def test_privacy_field_defaults(self):
""" post privacy field's many default values """ """post privacy field's many default values"""
instance = fields.PrivacyField() instance = fields.PrivacyField()
self.assertEqual(instance.max_length, 255) self.assertEqual(instance.max_length, 255)
self.assertEqual( self.assertEqual(
@ -143,11 +143,11 @@ class ActivitypubFields(TestCase):
) )
def test_privacy_field_set_field_from_activity(self): 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) @dataclass(init=False)
class TestActivity(ActivityObject): class TestActivity(ActivityObject):
""" real simple mock """ """real simple mock"""
to: List[str] to: List[str]
cc: List[str] cc: List[str]
@ -155,7 +155,7 @@ class ActivitypubFields(TestCase):
type: str = "Test" type: str = "Test"
class TestPrivacyModel(ActivitypubMixin, BookWyrmModel): class TestPrivacyModel(ActivitypubMixin, BookWyrmModel):
""" real simple mock model because BookWyrmModel is abstract """ """real simple mock model because BookWyrmModel is abstract"""
privacy_field = fields.PrivacyField() privacy_field = fields.PrivacyField()
mention_users = fields.TagField(User) mention_users = fields.TagField(User)
@ -187,7 +187,7 @@ class ActivitypubFields(TestCase):
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_privacy_field_set_activity_from_field(self, *_): 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( user = User.objects.create_user(
"rat", "rat@rat.rat", "ratword", local=True, localname="rat" "rat", "rat@rat.rat", "ratword", local=True, localname="rat"
) )
@ -231,7 +231,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(activity["cc"], []) self.assertEqual(activity["cc"], [])
def test_foreign_key(self): def test_foreign_key(self):
""" should be able to format a related model """ """should be able to format a related model"""
instance = fields.ForeignKey("User", on_delete=models.CASCADE) instance = fields.ForeignKey("User", on_delete=models.CASCADE)
Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
item = Serializable(lambda: {"a": "b"}, "https://e.b/c") item = Serializable(lambda: {"a": "b"}, "https://e.b/c")
@ -240,7 +240,7 @@ class ActivitypubFields(TestCase):
@responses.activate @responses.activate
def test_foreign_key_from_activity_str(self): 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) 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()) userdata = json.loads(datafile.read_bytes())
@ -264,7 +264,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(value.name, "MOUSE?? MOUSE!!") self.assertEqual(value.name, "MOUSE?? MOUSE!!")
def test_foreign_key_from_activity_dict(self): def test_foreign_key_from_activity_dict(self):
""" test recieving activity json """ """test recieving activity json"""
instance = fields.ForeignKey(User, on_delete=models.CASCADE) 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()) userdata = json.loads(datafile.read_bytes())
@ -284,7 +284,7 @@ class ActivitypubFields(TestCase):
# et cetera but we're not testing serializing user json # et cetera but we're not testing serializing user json
def test_foreign_key_from_activity_dict_existing(self): 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) 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()) userdata = json.loads(datafile.read_bytes())
@ -302,7 +302,7 @@ class ActivitypubFields(TestCase):
self.assertEqual(value, user) self.assertEqual(value, user)
def test_foreign_key_from_activity_str_existing(self): 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) instance = fields.ForeignKey(User, on_delete=models.CASCADE)
user = User.objects.create_user( user = User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
@ -315,14 +315,14 @@ class ActivitypubFields(TestCase):
self.assertEqual(value, user) self.assertEqual(value, user)
def test_one_to_one_field(self): def test_one_to_one_field(self):
""" a gussied up foreign key """ """a gussied up foreign key"""
instance = fields.OneToOneField("User", on_delete=models.CASCADE) instance = fields.OneToOneField("User", on_delete=models.CASCADE)
Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
item = Serializable(lambda: {"a": "b"}, "https://e.b/c") item = Serializable(lambda: {"a": "b"}, "https://e.b/c")
self.assertEqual(instance.field_to_activity(item), {"a": "b"}) self.assertEqual(instance.field_to_activity(item), {"a": "b"})
def test_many_to_many_field(self): def test_many_to_many_field(self):
""" lists! """ """lists!"""
instance = fields.ManyToManyField("User") instance = fields.ManyToManyField("User")
Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) Serializable = namedtuple("Serializable", ("to_activity", "remote_id"))
@ -340,7 +340,7 @@ class ActivitypubFields(TestCase):
@responses.activate @responses.activate
def test_many_to_many_field_from_activity(self): 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) 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()) userdata = json.loads(datafile.read_bytes())
@ -360,7 +360,7 @@ class ActivitypubFields(TestCase):
self.assertIsInstance(value[0], User) self.assertIsInstance(value[0], User)
def test_tag_field(self): def test_tag_field(self):
""" a special type of many to many field """ """a special type of many to many field"""
instance = fields.TagField("User") instance = fields.TagField("User")
Serializable = namedtuple( Serializable = namedtuple(
@ -379,13 +379,13 @@ class ActivitypubFields(TestCase):
self.assertEqual(result[0].type, "Serializable") self.assertEqual(result[0].type, "Serializable")
def test_tag_field_from_activity(self): def test_tag_field_from_activity(self):
""" loadin' a list of items from Links """ """loadin' a list of items from Links"""
# TODO # TODO
@responses.activate @responses.activate
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
def test_image_field(self, _): def test_image_field(self, _):
""" storing images """ """storing images"""
user = User.objects.create_user( user = User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
@ -423,7 +423,7 @@ class ActivitypubFields(TestCase):
self.assertIsInstance(loaded_image[1], ContentFile) self.assertIsInstance(loaded_image[1], ContentFile)
def test_datetime_field(self): 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() instance = fields.DateTimeField()
now = timezone.now() now = timezone.now()
self.assertEqual(instance.field_to_activity(now), now.isoformat()) self.assertEqual(instance.field_to_activity(now), now.isoformat())
@ -431,12 +431,12 @@ class ActivitypubFields(TestCase):
self.assertEqual(instance.field_from_activity("bip"), None) self.assertEqual(instance.field_from_activity("bip"), None)
def test_array_field(self): 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) 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): def test_html_field(self):
""" sanitizes html, the sanitizer has its own tests """ """sanitizes html, the sanitizer has its own tests"""
instance = fields.HtmlField() instance = fields.HtmlField()
self.assertEqual( self.assertEqual(
instance.field_from_activity("<marquee><p>hi</p></marquee>"), "<p>hi</p>" instance.field_from_activity("<marquee><p>hi</p></marquee>"), "<p>hi</p>"

View file

@ -14,10 +14,10 @@ from bookwyrm.connectors.abstract_connector import SearchResult
class ImportJob(TestCase): class ImportJob(TestCase):
""" this is a fancy one!!! """ """this is a fancy one!!!"""
def setUp(self): 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 = { read_data = {
"Book Id": 39395857, "Book Id": 39395857,
"Title": "The Raven Tower", "Title": "The Raven Tower",
@ -72,30 +72,30 @@ class ImportJob(TestCase):
) )
def test_isbn(self): def test_isbn(self):
""" it unquotes the isbn13 field from data """ """it unquotes the isbn13 field from data"""
expected = "9780356506999" expected = "9780356506999"
item = models.ImportItem.objects.get(index=1) item = models.ImportItem.objects.get(index=1)
self.assertEqual(item.isbn, expected) self.assertEqual(item.isbn, expected)
def test_shelf(self): def test_shelf(self):
""" converts to the local shelf typology """ """converts to the local shelf typology"""
expected = "reading" expected = "reading"
self.assertEqual(self.item_1.shelf, expected) self.assertEqual(self.item_1.shelf, expected)
def test_date_added(self): 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) expected = datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)
item = models.ImportItem.objects.get(index=1) item = models.ImportItem.objects.get(index=1)
self.assertEqual(item.date_added, expected) self.assertEqual(item.date_added, expected)
def test_date_read(self): 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) expected = datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc)
item = models.ImportItem.objects.get(index=2) item = models.ImportItem.objects.get(index=2)
self.assertEqual(item.date_read, expected) self.assertEqual(item.date_read, expected)
def test_currently_reading_reads(self): def test_currently_reading_reads(self):
""" infer currently reading dates where available """ """infer currently reading dates where available"""
expected = [ expected = [
models.ReadThrough( models.ReadThrough(
start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc) start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)
@ -106,7 +106,7 @@ class ImportJob(TestCase):
self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date) self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date)
def test_read_reads(self): def test_read_reads(self):
""" infer read dates where available """ """infer read dates where available"""
actual = self.item_2 actual = self.item_2
self.assertEqual( self.assertEqual(
actual.reads[0].start_date, actual.reads[0].start_date,
@ -118,14 +118,14 @@ class ImportJob(TestCase):
) )
def test_unread_reads(self): def test_unread_reads(self):
""" handle books with no read dates """ """handle books with no read dates"""
expected = [] expected = []
actual = models.ImportItem.objects.get(index=3) actual = models.ImportItem.objects.get(index=3)
self.assertEqual(actual.reads, expected) self.assertEqual(actual.reads, expected)
@responses.activate @responses.activate
def test_get_book_from_isbn(self): 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( connector_info = models.Connector.objects.create(
identifier="openlibrary.org", identifier="openlibrary.org",
name="OpenLibrary", name="OpenLibrary",

View file

@ -7,10 +7,10 @@ from bookwyrm import models, settings
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
class List(TestCase): class List(TestCase):
""" some activitypub oddness ahead """ """some activitypub oddness ahead"""
def setUp(self): def setUp(self):
""" look, a list """ """look, a list"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
@ -18,7 +18,7 @@ class List(TestCase):
self.book = models.Edition.objects.create(title="hi", parent_work=work) self.book = models.Edition.objects.create(title="hi", parent_work=work)
def test_remote_id(self, _): def test_remote_id(self, _):
""" shelves use custom remote ids """ """shelves use custom remote ids"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
book_list = models.List.objects.create( book_list = models.List.objects.create(
name="Test List", user=self.local_user name="Test List", user=self.local_user
@ -27,7 +27,7 @@ class List(TestCase):
self.assertEqual(book_list.get_remote_id(), expected_id) self.assertEqual(book_list.get_remote_id(), expected_id)
def test_to_activity(self, _): def test_to_activity(self, _):
""" jsonify it """ """jsonify it"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
book_list = models.List.objects.create( book_list = models.List.objects.create(
name="Test List", user=self.local_user name="Test List", user=self.local_user
@ -41,7 +41,7 @@ class List(TestCase):
self.assertEqual(activity_json["owner"], self.local_user.remote_id) self.assertEqual(activity_json["owner"], self.local_user.remote_id)
def test_list_item(self, _): def test_list_item(self, _):
""" a list entry """ """a list entry"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
book_list = models.List.objects.create( book_list = models.List.objects.create(
name="Test List", user=self.local_user, privacy="unlisted" name="Test List", user=self.local_user, privacy="unlisted"
@ -59,7 +59,7 @@ class List(TestCase):
self.assertEqual(item.recipients, []) self.assertEqual(item.recipients, [])
def test_list_item_pending(self, _): def test_list_item_pending(self, _):
""" a list entry """ """a list entry"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
book_list = models.List.objects.create( book_list = models.List.objects.create(
name="Test List", user=self.local_user name="Test List", user=self.local_user

View file

@ -6,10 +6,10 @@ from bookwyrm import models, settings
class ReadThrough(TestCase): class ReadThrough(TestCase):
""" some activitypub oddness ahead """ """some activitypub oddness ahead"""
def setUp(self): def setUp(self):
""" look, a shelf """ """look, a shelf"""
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
@ -27,7 +27,7 @@ class ReadThrough(TestCase):
) )
def test_progress_update(self): def test_progress_update(self):
""" Test progress updates """ """Test progress updates"""
self.readthrough.create_update() # No-op, no progress yet self.readthrough.create_update() # No-op, no progress yet
self.readthrough.progress = 10 self.readthrough.progress = 10
self.readthrough.create_update() self.readthrough.create_update()

View file

@ -6,10 +6,10 @@ from bookwyrm import models
class Relationship(TestCase): class Relationship(TestCase):
""" following, blocking, stuff like that """ """following, blocking, stuff like that"""
def setUp(self): def setUp(self):
""" we need some users for this """ """we need some users for this"""
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( self.remote_user = models.User.objects.create_user(
"rat", "rat",
@ -27,11 +27,11 @@ class Relationship(TestCase):
self.local_user.save(broadcast=False) self.local_user.save(broadcast=False)
def test_user_follows_from_request(self): 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 real_broadcast = models.UserFollowRequest.broadcast
def mock_broadcast(_, activity, user): 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(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity["type"], "Follow") self.assertEqual(activity["type"], "Follow")
@ -54,7 +54,7 @@ class Relationship(TestCase):
models.UserFollowRequest.broadcast = real_broadcast models.UserFollowRequest.broadcast = real_broadcast
def test_user_follows_from_request_custom_remote_id(self): def test_user_follows_from_request_custom_remote_id(self):
""" store a specific remote id for a relationship provided by remote """ """store a specific remote id for a relationship provided by remote"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
request = models.UserFollowRequest.objects.create( request = models.UserFollowRequest.objects.create(
user_subject=self.local_user, user_subject=self.local_user,
@ -71,7 +71,7 @@ class Relationship(TestCase):
self.assertEqual(rel.user_object, self.remote_user) self.assertEqual(rel.user_object, self.remote_user)
def test_follow_request_activity(self): 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 real_broadcast = models.UserFollowRequest.broadcast
def mock_broadcast(_, activity, user): def mock_broadcast(_, activity, user):
@ -88,7 +88,7 @@ class Relationship(TestCase):
models.UserFollowRequest.broadcast = real_broadcast models.UserFollowRequest.broadcast = real_broadcast
def test_follow_request_accept(self): 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 real_broadcast = models.UserFollowRequest.broadcast
def mock_broadcast(_, activity, user): def mock_broadcast(_, activity, user):
@ -115,7 +115,7 @@ class Relationship(TestCase):
models.UserFollowRequest.broadcast = real_broadcast models.UserFollowRequest.broadcast = real_broadcast
def test_follow_request_reject(self): 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 real_broadcast = models.UserFollowRequest.broadcast
def mock_reject(_, activity, user): def mock_reject(_, activity, user):

View file

@ -8,10 +8,10 @@ from bookwyrm import models, settings
# pylint: disable=unused-argument # pylint: disable=unused-argument
class Shelf(TestCase): class Shelf(TestCase):
""" some activitypub oddness ahead """ """some activitypub oddness ahead"""
def setUp(self): def setUp(self):
""" look, a shelf """ """look, a shelf"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
@ -19,7 +19,7 @@ class Shelf(TestCase):
self.book = models.Edition.objects.create(title="test book", parent_work=work) self.book = models.Edition.objects.create(title="test book", parent_work=work)
def test_remote_id(self): def test_remote_id(self):
""" shelves use custom remote ids """ """shelves use custom remote ids"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create( 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
@ -28,7 +28,7 @@ class Shelf(TestCase):
self.assertEqual(shelf.get_remote_id(), expected_id) self.assertEqual(shelf.get_remote_id(), expected_id)
def test_to_activity(self): def test_to_activity(self):
""" jsonify it """ """jsonify it"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create( 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
@ -42,7 +42,7 @@ class Shelf(TestCase):
self.assertEqual(activity_json["owner"], self.local_user.remote_id) self.assertEqual(activity_json["owner"], self.local_user.remote_id)
def test_create_update_shelf(self): def test_create_update_shelf(self):
""" create and broadcast shelf creation """ """create and broadcast shelf creation"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
shelf = models.Shelf.objects.create( shelf = models.Shelf.objects.create(
@ -63,7 +63,7 @@ class Shelf(TestCase):
self.assertEqual(shelf.name, "arthur russel") self.assertEqual(shelf.name, "arthur russel")
def test_shelve(self): def test_shelve(self):
""" create and broadcast shelf creation """ """create and broadcast shelf creation"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = models.Shelf.objects.create( 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

View file

@ -17,10 +17,10 @@ from bookwyrm import activitypub, models, settings
@patch("bookwyrm.models.Status.broadcast") @patch("bookwyrm.models.Status.broadcast")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class Status(TestCase): class Status(TestCase):
""" lotta types of statuses """ """lotta types of statuses"""
def setUp(self): def setUp(self):
""" useful things for creating a status """ """useful things for creating a status"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
) )
@ -46,14 +46,14 @@ class Status(TestCase):
self.book.cover.save("test.jpg", ContentFile(output.getvalue())) self.book.cover.save("test.jpg", ContentFile(output.getvalue()))
def test_status_generated_fields(self, *_): def test_status_generated_fields(self, *_):
""" setting remote id """ """setting remote id"""
status = models.Status.objects.create(content="bleh", user=self.local_user) status = models.Status.objects.create(content="bleh", user=self.local_user)
expected_id = "https://%s/user/mouse/status/%d" % (settings.DOMAIN, status.id) expected_id = "https://%s/user/mouse/status/%d" % (settings.DOMAIN, status.id)
self.assertEqual(status.remote_id, expected_id) self.assertEqual(status.remote_id, expected_id)
self.assertEqual(status.privacy, "public") self.assertEqual(status.privacy, "public")
def test_replies(self, *_): def test_replies(self, *_):
""" get a list of replies """ """get a list of replies"""
parent = models.Status.objects.create(content="hi", user=self.local_user) parent = models.Status.objects.create(content="hi", user=self.local_user)
child = models.Status.objects.create( child = models.Status.objects.create(
content="hello", reply_parent=parent, user=self.local_user content="hello", reply_parent=parent, user=self.local_user
@ -72,7 +72,7 @@ class Status(TestCase):
self.assertIsInstance(replies.last(), models.Review) self.assertIsInstance(replies.last(), models.Review)
def test_status_type(self, *_): def test_status_type(self, *_):
""" class name """ """class name"""
self.assertEqual(models.Status().status_type, "Note") self.assertEqual(models.Status().status_type, "Note")
self.assertEqual(models.Review().status_type, "Review") self.assertEqual(models.Review().status_type, "Review")
self.assertEqual(models.Quotation().status_type, "Quotation") self.assertEqual(models.Quotation().status_type, "Quotation")
@ -80,14 +80,14 @@ class Status(TestCase):
self.assertEqual(models.Boost().status_type, "Announce") self.assertEqual(models.Boost().status_type, "Announce")
def test_boostable(self, *_): def test_boostable(self, *_):
""" can a status be boosted, based on privacy """ """can a status be boosted, based on privacy"""
self.assertTrue(models.Status(privacy="public").boostable) self.assertTrue(models.Status(privacy="public").boostable)
self.assertTrue(models.Status(privacy="unlisted").boostable) self.assertTrue(models.Status(privacy="unlisted").boostable)
self.assertFalse(models.Status(privacy="followers").boostable) self.assertFalse(models.Status(privacy="followers").boostable)
self.assertFalse(models.Status(privacy="direct").boostable) self.assertFalse(models.Status(privacy="direct").boostable)
def test_to_replies(self, *_): def test_to_replies(self, *_):
""" activitypub replies collection """ """activitypub replies collection"""
parent = models.Status.objects.create(content="hi", user=self.local_user) parent = models.Status.objects.create(content="hi", user=self.local_user)
child = models.Status.objects.create( child = models.Status.objects.create(
content="hello", reply_parent=parent, user=self.local_user content="hello", reply_parent=parent, user=self.local_user
@ -104,7 +104,7 @@ class Status(TestCase):
self.assertEqual(replies["totalItems"], 2) self.assertEqual(replies["totalItems"], 2)
def test_status_to_activity(self, *_): def test_status_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.Status.objects.create( status = models.Status.objects.create(
content="test content", user=self.local_user content="test content", user=self.local_user
) )
@ -115,7 +115,7 @@ class Status(TestCase):
self.assertEqual(activity["sensitive"], False) self.assertEqual(activity["sensitive"], False)
def test_status_to_activity_tombstone(self, *_): 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"""
with patch( with patch(
"bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores" "bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores"
): ):
@ -131,7 +131,7 @@ class Status(TestCase):
self.assertFalse(hasattr(activity, "content")) self.assertFalse(hasattr(activity, "content"))
def test_status_to_pure_activity(self, *_): def test_status_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.Status.objects.create( status = models.Status.objects.create(
content="test content", user=self.local_user content="test content", user=self.local_user
) )
@ -143,7 +143,7 @@ class Status(TestCase):
self.assertEqual(activity["attachment"], []) self.assertEqual(activity["attachment"], [])
def test_generated_note_to_activity(self, *_): 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( status = models.GeneratedNote.objects.create(
content="test content", user=self.local_user content="test content", user=self.local_user
) )
@ -157,7 +157,7 @@ class Status(TestCase):
self.assertEqual(len(activity["tag"]), 2) self.assertEqual(len(activity["tag"]), 2)
def test_generated_note_to_pure_activity(self, *_): 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( status = models.GeneratedNote.objects.create(
content="test content", user=self.local_user content="test content", user=self.local_user
) )
@ -181,7 +181,7 @@ class Status(TestCase):
self.assertEqual(activity["attachment"][0].name, "Test Edition") self.assertEqual(activity["attachment"][0].name, "Test Edition")
def test_comment_to_activity(self, *_): 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( status = models.Comment.objects.create(
content="test content", user=self.local_user, book=self.book content="test content", user=self.local_user, book=self.book
) )
@ -192,7 +192,7 @@ class Status(TestCase):
self.assertEqual(activity["inReplyToBook"], self.book.remote_id) self.assertEqual(activity["inReplyToBook"], self.book.remote_id)
def test_comment_to_pure_activity(self, *_): 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( status = models.Comment.objects.create(
content="test content", user=self.local_user, book=self.book content="test content", user=self.local_user, book=self.book
) )
@ -212,7 +212,7 @@ class Status(TestCase):
self.assertEqual(activity["attachment"][0].name, "Test Edition") self.assertEqual(activity["attachment"][0].name, "Test Edition")
def test_quotation_to_activity(self, *_): 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( status = models.Quotation.objects.create(
quote="a sickening sense", quote="a sickening sense",
content="test content", content="test content",
@ -227,7 +227,7 @@ class Status(TestCase):
self.assertEqual(activity["inReplyToBook"], self.book.remote_id) self.assertEqual(activity["inReplyToBook"], self.book.remote_id)
def test_quotation_to_pure_activity(self, *_): 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( status = models.Quotation.objects.create(
quote="a sickening sense", quote="a sickening sense",
content="test content", content="test content",
@ -250,7 +250,7 @@ class Status(TestCase):
self.assertEqual(activity["attachment"][0].name, "Test Edition") self.assertEqual(activity["attachment"][0].name, "Test Edition")
def test_review_to_activity(self, *_): 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( status = models.Review.objects.create(
name="Review name", name="Review name",
content="test content", content="test content",
@ -267,7 +267,7 @@ class Status(TestCase):
self.assertEqual(activity["inReplyToBook"], self.book.remote_id) self.assertEqual(activity["inReplyToBook"], self.book.remote_id)
def test_review_to_pure_activity(self, *_): 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( status = models.Review.objects.create(
name="Review's name", name="Review's name",
content="test content", content="test content",
@ -291,7 +291,7 @@ class Status(TestCase):
self.assertEqual(activity["attachment"][0].name, "Test Edition") self.assertEqual(activity["attachment"][0].name, "Test Edition")
def test_review_to_pure_activity_no_rating(self, *_): def test_review_to_pure_activity_no_rating(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( status = models.Review.objects.create(
name="Review name", name="Review name",
content="test content", content="test content",
@ -313,7 +313,7 @@ class Status(TestCase):
self.assertEqual(activity["attachment"][0].name, "Test Edition") self.assertEqual(activity["attachment"][0].name, "Test Edition")
def test_reviewrating_to_pure_activity(self, *_): def test_reviewrating_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.ReviewRating.objects.create( status = models.ReviewRating.objects.create(
rating=3.0, rating=3.0,
user=self.local_user, user=self.local_user,
@ -335,11 +335,11 @@ class Status(TestCase):
self.assertEqual(activity["attachment"][0].name, "Test Edition") self.assertEqual(activity["attachment"][0].name, "Test Edition")
def test_favorite(self, *_): def test_favorite(self, *_):
""" fav a status """ """fav a status"""
real_broadcast = models.Favorite.broadcast real_broadcast = models.Favorite.broadcast
def fav_broadcast_mock(_, activity, user): def fav_broadcast_mock(_, activity, user):
""" ok """ """ok"""
self.assertEqual(user.remote_id, self.local_user.remote_id) self.assertEqual(user.remote_id, self.local_user.remote_id)
self.assertEqual(activity["type"], "Like") self.assertEqual(activity["type"], "Like")
@ -361,7 +361,7 @@ class Status(TestCase):
models.Favorite.broadcast = real_broadcast models.Favorite.broadcast = real_broadcast
def test_boost(self, *_): def test_boost(self, *_):
""" boosting, this one's a bit fussy """ """boosting, this one's a bit fussy"""
status = models.Status.objects.create( status = models.Status.objects.create(
content="test content", user=self.local_user content="test content", user=self.local_user
) )
@ -373,7 +373,7 @@ class Status(TestCase):
self.assertEqual(activity, boost.to_activity(pure=True)) self.assertEqual(activity, boost.to_activity(pure=True))
def test_notification(self, *_): def test_notification(self, *_):
""" a simple model """ """a simple model"""
notification = models.Notification.objects.create( notification = models.Notification.objects.create(
user=self.local_user, notification_type="FAVORITE" user=self.local_user, notification_type="FAVORITE"
) )
@ -385,7 +385,7 @@ class Status(TestCase):
) )
def test_create_broadcast(self, _, broadcast_mock): def test_create_broadcast(self, _, broadcast_mock):
""" should send out two verions of a status on create """ """should send out two verions of a status on create"""
models.Comment.objects.create( models.Comment.objects.create(
content="hi", user=self.local_user, book=self.book content="hi", user=self.local_user, book=self.book
) )
@ -405,7 +405,7 @@ class Status(TestCase):
self.assertEqual(args["object"]["type"], "Comment") self.assertEqual(args["object"]["type"], "Comment")
def test_recipients_with_mentions(self, *_): def test_recipients_with_mentions(self, *_):
""" get recipients to broadcast a status """ """get recipients to broadcast a status"""
status = models.GeneratedNote.objects.create( status = models.GeneratedNote.objects.create(
content="test content", user=self.local_user content="test content", user=self.local_user
) )
@ -414,7 +414,7 @@ class Status(TestCase):
self.assertEqual(status.recipients, [self.remote_user]) self.assertEqual(status.recipients, [self.remote_user])
def test_recipients_with_reply_parent(self, *_): def test_recipients_with_reply_parent(self, *_):
""" get recipients to broadcast a status """ """get recipients to broadcast a status"""
parent_status = models.GeneratedNote.objects.create( parent_status = models.GeneratedNote.objects.create(
content="test content", user=self.remote_user content="test content", user=self.remote_user
) )
@ -425,7 +425,7 @@ class Status(TestCase):
self.assertEqual(status.recipients, [self.remote_user]) self.assertEqual(status.recipients, [self.remote_user])
def test_recipients_with_reply_parent_and_mentions(self, *_): def test_recipients_with_reply_parent_and_mentions(self, *_):
""" get recipients to broadcast a status """ """get recipients to broadcast a status"""
parent_status = models.GeneratedNote.objects.create( parent_status = models.GeneratedNote.objects.create(
content="test content", user=self.remote_user content="test content", user=self.remote_user
) )
@ -438,7 +438,7 @@ class Status(TestCase):
@responses.activate @responses.activate
def test_ignore_activity_boost(self, *_): def test_ignore_activity_boost(self, *_):
""" don't bother with most remote statuses """ """don't bother with most remote statuses"""
activity = activitypub.Announce( activity = activitypub.Announce(
id="http://www.faraway.com/boost/12", id="http://www.faraway.com/boost/12",
actor=self.remote_user.remote_id, actor=self.remote_user.remote_id,

View file

@ -22,7 +22,7 @@ class User(TestCase):
) )
def test_computed_fields(self): def test_computed_fields(self):
""" username instead of id here """ """username instead of id here"""
expected_id = "https://%s/user/mouse" % DOMAIN expected_id = "https://%s/user/mouse" % DOMAIN
self.assertEqual(self.user.remote_id, expected_id) self.assertEqual(self.user.remote_id, expected_id)
self.assertEqual(self.user.username, "mouse@%s" % DOMAIN) self.assertEqual(self.user.username, "mouse@%s" % DOMAIN)
@ -155,7 +155,7 @@ class User(TestCase):
self.assertIsNone(server.application_version) self.assertIsNone(server.application_version)
def test_delete_user(self): def test_delete_user(self):
""" deactivate a user """ """deactivate a user"""
self.assertTrue(self.user.is_active) self.assertTrue(self.user.is_active)
with patch( with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay" "bookwyrm.models.activitypub_mixin.broadcast_task.delay"

View file

@ -7,10 +7,10 @@ from bookwyrm import activitystreams, models
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class Activitystreams(TestCase): class Activitystreams(TestCase):
""" using redis to build activity streams """ """using redis to build activity streams"""
def setUp(self): def setUp(self):
""" use a test csv """ """use a test csv"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
) )
@ -30,14 +30,14 @@ class Activitystreams(TestCase):
self.book = models.Edition.objects.create(title="test book") self.book = models.Edition.objects.create(title="test book")
class TestStream(activitystreams.ActivityStream): class TestStream(activitystreams.ActivityStream):
""" test stream, don't have to do anything here """ """test stream, don't have to do anything here"""
key = "test" key = "test"
self.test_stream = TestStream() self.test_stream = TestStream()
def test_activitystream_class_ids(self, *_): def test_activitystream_class_ids(self, *_):
""" the abstract base class for stream objects """ """the abstract base class for stream objects"""
self.assertEqual( self.assertEqual(
self.test_stream.stream_id(self.local_user), self.test_stream.stream_id(self.local_user),
"{}-test".format(self.local_user.id), "{}-test".format(self.local_user.id),
@ -48,7 +48,7 @@ class Activitystreams(TestCase):
) )
def test_abstractstream_get_audience(self, *_): def test_abstractstream_get_audience(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.remote_user, content="hi", privacy="public"
) )
@ -59,7 +59,7 @@ class Activitystreams(TestCase):
self.assertTrue(self.another_user in users) self.assertTrue(self.another_user in users)
def test_abstractstream_get_audience_direct(self, *_): def test_abstractstream_get_audience_direct(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, user=self.remote_user,
content="hi", content="hi",
@ -82,7 +82,7 @@ class Activitystreams(TestCase):
self.assertFalse(self.remote_user in users) self.assertFalse(self.remote_user in users)
def test_abstractstream_get_audience_followers_remote_user(self, *_): def test_abstractstream_get_audience_followers_remote_user(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, user=self.remote_user,
content="hi", content="hi",
@ -92,7 +92,7 @@ class Activitystreams(TestCase):
self.assertFalse(users.exists()) self.assertFalse(users.exists())
def test_abstractstream_get_audience_followers_self(self, *_): def test_abstractstream_get_audience_followers_self(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Comment.objects.create( status = models.Comment.objects.create(
user=self.local_user, user=self.local_user,
content="hi", content="hi",
@ -105,7 +105,7 @@ class Activitystreams(TestCase):
self.assertFalse(self.remote_user in users) self.assertFalse(self.remote_user in users)
def test_abstractstream_get_audience_followers_with_mention(self, *_): def test_abstractstream_get_audience_followers_with_mention(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Comment.objects.create( status = models.Comment.objects.create(
user=self.remote_user, user=self.remote_user,
content="hi", content="hi",
@ -120,7 +120,7 @@ class Activitystreams(TestCase):
self.assertFalse(self.remote_user in users) self.assertFalse(self.remote_user in users)
def test_abstractstream_get_audience_followers_with_relationship(self, *_): def test_abstractstream_get_audience_followers_with_relationship(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
self.remote_user.followers.add(self.local_user) self.remote_user.followers.add(self.local_user)
status = models.Comment.objects.create( status = models.Comment.objects.create(
user=self.remote_user, user=self.remote_user,
@ -134,7 +134,7 @@ class Activitystreams(TestCase):
self.assertFalse(self.remote_user in users) self.assertFalse(self.remote_user in users)
def test_homestream_get_audience(self, *_): def test_homestream_get_audience(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.remote_user, content="hi", privacy="public"
) )
@ -142,7 +142,7 @@ class Activitystreams(TestCase):
self.assertFalse(users.exists()) self.assertFalse(users.exists())
def test_homestream_get_audience_with_mentions(self, *_): def test_homestream_get_audience_with_mentions(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.remote_user, content="hi", privacy="public"
) )
@ -152,7 +152,7 @@ class Activitystreams(TestCase):
self.assertFalse(self.another_user in users) self.assertFalse(self.another_user in users)
def test_homestream_get_audience_with_relationship(self, *_): def test_homestream_get_audience_with_relationship(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
self.remote_user.followers.add(self.local_user) self.remote_user.followers.add(self.local_user)
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.remote_user, content="hi", privacy="public"
@ -162,7 +162,7 @@ class Activitystreams(TestCase):
self.assertFalse(self.another_user in users) self.assertFalse(self.another_user in users)
def test_localstream_get_audience_remote_status(self, *_): def test_localstream_get_audience_remote_status(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.remote_user, content="hi", privacy="public"
) )
@ -170,7 +170,7 @@ class Activitystreams(TestCase):
self.assertEqual(users, []) self.assertEqual(users, [])
def test_localstream_get_audience_local_status(self, *_): def test_localstream_get_audience_local_status(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.local_user, content="hi", privacy="public" user=self.local_user, content="hi", privacy="public"
) )
@ -179,7 +179,7 @@ class Activitystreams(TestCase):
self.assertTrue(self.another_user in users) self.assertTrue(self.another_user in users)
def test_localstream_get_audience_unlisted(self, *_): def test_localstream_get_audience_unlisted(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.local_user, content="hi", privacy="unlisted" user=self.local_user, content="hi", privacy="unlisted"
) )
@ -187,7 +187,7 @@ class Activitystreams(TestCase):
self.assertEqual(users, []) self.assertEqual(users, [])
def test_federatedstream_get_audience(self, *_): def test_federatedstream_get_audience(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="public" user=self.remote_user, content="hi", privacy="public"
) )
@ -196,7 +196,7 @@ class Activitystreams(TestCase):
self.assertTrue(self.another_user in users) self.assertTrue(self.another_user in users)
def test_federatedstream_get_audience_unlisted(self, *_): def test_federatedstream_get_audience_unlisted(self, *_):
""" get a list of users that should see a status """ """get a list of users that should see a status"""
status = models.Status.objects.create( status = models.Status.objects.create(
user=self.remote_user, content="hi", privacy="unlisted" user=self.remote_user, content="hi", privacy="unlisted"
) )

View file

@ -10,10 +10,10 @@ from bookwyrm import emailing, models
@patch("bookwyrm.emailing.send_email.delay") @patch("bookwyrm.emailing.send_email.delay")
class Emailing(TestCase): class Emailing(TestCase):
""" every response to a get request, html or json """ """every response to a get request, html or json"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -25,7 +25,7 @@ class Emailing(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_invite_email(self, email_mock): def test_invite_email(self, email_mock):
""" load the invite email """ """load the invite email"""
invite_request = models.InviteRequest.objects.create( invite_request = models.InviteRequest.objects.create(
email="test@email.com", email="test@email.com",
invite=models.SiteInvite.objects.create(user=self.local_user), invite=models.SiteInvite.objects.create(user=self.local_user),
@ -40,7 +40,7 @@ class Emailing(TestCase):
self.assertEqual(len(args), 4) self.assertEqual(len(args), 4)
def test_password_reset_email(self, email_mock): def test_password_reset_email(self, email_mock):
""" load the password reset email """ """load the password reset email"""
reset = models.PasswordReset.objects.create(user=self.local_user) reset = models.PasswordReset.objects.create(user=self.local_user)
emailing.password_reset_email(reset) emailing.password_reset_email(reset)

View file

@ -5,10 +5,10 @@ from bookwyrm.sanitize_html import InputHtmlParser
class Sanitizer(TestCase): class Sanitizer(TestCase):
""" sanitizer tests """ """sanitizer tests"""
def test_no_html(self): def test_no_html(self):
""" just text """ """just text"""
input_text = "no html " input_text = "no html "
parser = InputHtmlParser() parser = InputHtmlParser()
parser.feed(input_text) parser.feed(input_text)
@ -16,7 +16,7 @@ class Sanitizer(TestCase):
self.assertEqual(input_text, output) self.assertEqual(input_text, output)
def test_valid_html(self): def test_valid_html(self):
""" leave the html untouched """ """leave the html untouched"""
input_text = "<b>yes </b> <i>html</i>" input_text = "<b>yes </b> <i>html</i>"
parser = InputHtmlParser() parser = InputHtmlParser()
parser.feed(input_text) parser.feed(input_text)
@ -24,7 +24,7 @@ class Sanitizer(TestCase):
self.assertEqual(input_text, output) self.assertEqual(input_text, output)
def test_valid_html_attrs(self): def test_valid_html_attrs(self):
""" and don't remove attributes """ """and don't remove attributes"""
input_text = '<a href="fish.com">yes </a> <i>html</i>' input_text = '<a href="fish.com">yes </a> <i>html</i>'
parser = InputHtmlParser() parser = InputHtmlParser()
parser.feed(input_text) parser.feed(input_text)
@ -32,7 +32,7 @@ class Sanitizer(TestCase):
self.assertEqual(input_text, output) self.assertEqual(input_text, output)
def test_invalid_html(self): def test_invalid_html(self):
""" remove all html when the html is malformed """ """remove all html when the html is malformed"""
input_text = "<b>yes <i>html</i>" input_text = "<b>yes <i>html</i>"
parser = InputHtmlParser() parser = InputHtmlParser()
parser.feed(input_text) parser.feed(input_text)
@ -46,7 +46,7 @@ class Sanitizer(TestCase):
self.assertEqual("yes html ", output) self.assertEqual("yes html ", output)
def test_disallowed_html(self): def test_disallowed_html(self):
""" remove disallowed html but keep allowed html """ """remove disallowed html but keep allowed html"""
input_text = "<div> yes <i>html</i></div>" input_text = "<div> yes <i>html</i></div>"
parser = InputHtmlParser() parser = InputHtmlParser()
parser.feed(input_text) parser.feed(input_text)

View file

@ -20,7 +20,7 @@ from bookwyrm.signatures import create_key_pair, make_signature, make_digest
def get_follow_activity(follower, followee): def get_follow_activity(follower, followee):
""" generates a test activity """ """generates a test activity"""
return Follow( return Follow(
id="https://test.com/user/follow/id", id="https://test.com/user/follow/id",
actor=follower.remote_id, actor=follower.remote_id,
@ -33,10 +33,10 @@ Sender = namedtuple("Sender", ("remote_id", "key_pair"))
class Signature(TestCase): class Signature(TestCase):
""" signature test """ """signature test"""
def setUp(self): def setUp(self):
""" create users and test data """ """create users and test data"""
self.mouse = models.User.objects.create_user( 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"
) )
@ -56,7 +56,7 @@ class Signature(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def send(self, signature, now, data, digest): def send(self, signature, now, data, digest):
""" test request """ """test request"""
c = Client() c = Client()
return c.post( return c.post(
urlsplit(self.rat.inbox).path, urlsplit(self.rat.inbox).path,
@ -74,7 +74,7 @@ class Signature(TestCase):
def send_test_request( # pylint: disable=too-many-arguments def send_test_request( # pylint: disable=too-many-arguments
self, sender, signer=None, send_data=None, digest=None, date=None self, sender, signer=None, send_data=None, digest=None, date=None
): ):
""" sends a follow request to the "rat" user """ """sends a follow request to the "rat" user"""
now = date or http_date() now = date or http_date()
data = json.dumps(get_follow_activity(sender, self.rat)) data = json.dumps(get_follow_activity(sender, self.rat))
digest = digest or make_digest(data) digest = digest or make_digest(data)
@ -84,7 +84,7 @@ class Signature(TestCase):
return self.send(signature, now, send_data or data, digest) return self.send(signature, now, send_data or data, digest)
def test_correct_signature(self): def test_correct_signature(self):
""" this one should just work """ """this one should just work"""
response = self.send_test_request(sender=self.mouse) response = self.send_test_request(sender=self.mouse)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -96,7 +96,7 @@ class Signature(TestCase):
@responses.activate @responses.activate
def test_remote_signer(self): def test_remote_signer(self):
""" signtures for remote users """ """signtures for remote users"""
datafile = pathlib.Path(__file__).parent.joinpath("data/ap_user.json") datafile = pathlib.Path(__file__).parent.joinpath("data/ap_user.json")
data = json.loads(datafile.read_bytes()) data = json.loads(datafile.read_bytes())
data["id"] = self.fake_remote.remote_id data["id"] = self.fake_remote.remote_id
@ -119,7 +119,7 @@ class Signature(TestCase):
@responses.activate @responses.activate
def test_key_needs_refresh(self): def test_key_needs_refresh(self):
""" an out of date key should be updated and the new key work """ """an out of date key should be updated and the new key work"""
datafile = pathlib.Path(__file__).parent.joinpath("data/ap_user.json") datafile = pathlib.Path(__file__).parent.joinpath("data/ap_user.json")
data = json.loads(datafile.read_bytes()) data = json.loads(datafile.read_bytes())
data["id"] = self.fake_remote.remote_id data["id"] = self.fake_remote.remote_id
@ -155,7 +155,7 @@ class Signature(TestCase):
@responses.activate @responses.activate
def test_nonexistent_signer(self): def test_nonexistent_signer(self):
""" fail when unable to look up signer """ """fail when unable to look up signer"""
responses.add( responses.add(
responses.GET, responses.GET,
self.fake_remote.remote_id, self.fake_remote.remote_id,
@ -177,7 +177,7 @@ class Signature(TestCase):
@pytest.mark.integration @pytest.mark.integration
def test_invalid_digest(self): def test_invalid_digest(self):
""" signature digest must be valid """ """signature digest must be valid"""
with patch("bookwyrm.activitypub.resolve_remote_id"): with patch("bookwyrm.activitypub.resolve_remote_id"):
response = self.send_test_request( response = self.send_test_request(
self.mouse, digest="SHA-256=AAAAAAAAAAAAAAAAAA" self.mouse, digest="SHA-256=AAAAAAAAAAAAAAAAAA"

View file

@ -12,10 +12,10 @@ from bookwyrm.templatetags import bookwyrm_tags
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class TemplateTags(TestCase): class TemplateTags(TestCase):
""" lotta different things here """ """lotta different things here"""
def setUp(self): def setUp(self):
""" create some filler objects """ """create some filler objects"""
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.mouse", "mouse@mouse.mouse",
@ -34,34 +34,34 @@ class TemplateTags(TestCase):
self.book = models.Edition.objects.create(title="Test Book") self.book = models.Edition.objects.create(title="Test Book")
def test_dict_key(self, _): def test_dict_key(self, _):
""" just getting a value out of a dict """ """just getting a value out of a dict"""
test_dict = {"a": 1, "b": 3} test_dict = {"a": 1, "b": 3}
self.assertEqual(bookwyrm_tags.dict_key(test_dict, "a"), 1) self.assertEqual(bookwyrm_tags.dict_key(test_dict, "a"), 1)
self.assertEqual(bookwyrm_tags.dict_key(test_dict, "c"), 0) self.assertEqual(bookwyrm_tags.dict_key(test_dict, "c"), 0)
def test_get_user_rating(self, _): def test_get_user_rating(self, _):
""" get a user's most recent rating of a book """ """get a user's most recent rating of a book"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.Review.objects.create(user=self.user, book=self.book, rating=3) models.Review.objects.create(user=self.user, book=self.book, rating=3)
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3) self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3)
def test_get_user_rating_doesnt_exist(self, _): def test_get_user_rating_doesnt_exist(self, _):
""" there is no rating available """ """there is no rating available"""
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0) self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 0)
def test_get_user_identifer_local(self, _): 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.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, _): 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( 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, _): def test_get_notification_count(self, _):
""" just countin' """ """just countin'"""
self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 0) 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="FAVORITE")
@ -74,7 +74,7 @@ class TemplateTags(TestCase):
self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 2) self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 2)
def test_get_replies(self, _): def test_get_replies(self, _):
""" direct replies to a status """ """direct replies to a status"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
parent = models.Review.objects.create( parent = models.Review.objects.create(
user=self.user, book=self.book, content="hi" user=self.user, book=self.book, content="hi"
@ -102,7 +102,7 @@ class TemplateTags(TestCase):
self.assertFalse(third_child in replies) self.assertFalse(third_child in replies)
def test_get_parent(self, _): def test_get_parent(self, _):
""" get the reply parent of a status """ """get the reply parent of a status"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
parent = models.Review.objects.create( parent = models.Review.objects.create(
user=self.user, book=self.book, content="hi" user=self.user, book=self.book, content="hi"
@ -116,7 +116,7 @@ class TemplateTags(TestCase):
self.assertIsInstance(result, models.Review) self.assertIsInstance(result, models.Review)
def test_get_user_liked(self, _): def test_get_user_liked(self, _):
""" did a user like a status """ """did a user like a status"""
status = models.Review.objects.create(user=self.remote_user, book=self.book) status = models.Review.objects.create(user=self.remote_user, book=self.book)
self.assertFalse(bookwyrm_tags.get_user_liked(self.user, status)) self.assertFalse(bookwyrm_tags.get_user_liked(self.user, status))
@ -125,7 +125,7 @@ class TemplateTags(TestCase):
self.assertTrue(bookwyrm_tags.get_user_liked(self.user, status)) self.assertTrue(bookwyrm_tags.get_user_liked(self.user, status))
def test_get_user_boosted(self, _): def test_get_user_boosted(self, _):
""" did a user boost a status """ """did a user boost a status"""
status = models.Review.objects.create(user=self.remote_user, book=self.book) status = models.Review.objects.create(user=self.remote_user, book=self.book)
self.assertFalse(bookwyrm_tags.get_user_boosted(self.user, status)) self.assertFalse(bookwyrm_tags.get_user_boosted(self.user, status))
@ -134,7 +134,7 @@ class TemplateTags(TestCase):
self.assertTrue(bookwyrm_tags.get_user_boosted(self.user, status)) self.assertTrue(bookwyrm_tags.get_user_boosted(self.user, status))
def test_follow_request_exists(self, _): def test_follow_request_exists(self, _):
""" does a user want to follow """ """does a user want to follow"""
self.assertFalse( self.assertFalse(
bookwyrm_tags.follow_request_exists(self.user, self.remote_user) bookwyrm_tags.follow_request_exists(self.user, self.remote_user)
) )
@ -152,7 +152,7 @@ class TemplateTags(TestCase):
) )
def test_get_boosted(self, _): def test_get_boosted(self, _):
""" load a boosted status """ """load a boosted status"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Review.objects.create(user=self.remote_user, book=self.book) status = models.Review.objects.create(user=self.remote_user, book=self.book)
boost = models.Boost.objects.create(user=self.user, boosted_status=status) boost = models.Boost.objects.create(user=self.user, boosted_status=status)
@ -161,7 +161,7 @@ class TemplateTags(TestCase):
self.assertEqual(boosted, status) self.assertEqual(boosted, status)
def test_get_book_description(self, _): def test_get_book_description(self, _):
""" grab it from the edition or the parent """ """grab it from the edition or the parent"""
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book.parent_work = work self.book.parent_work = work
self.book.save() self.book.save()
@ -177,12 +177,12 @@ class TemplateTags(TestCase):
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, _): def test_get_uuid(self, _):
""" uuid functionality """ """uuid functionality"""
uuid = bookwyrm_tags.get_uuid("hi") uuid = bookwyrm_tags.get_uuid("hi")
self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid)) self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid))
def test_get_markdown(self, _): def test_get_markdown(self, _):
""" mardown format data """ """mardown format data"""
result = bookwyrm_tags.get_markdown("_hi_") result = bookwyrm_tags.get_markdown("_hi_")
self.assertEqual(result, "<p><em>hi</em></p>") self.assertEqual(result, "<p><em>hi</em></p>")
@ -190,13 +190,13 @@ class TemplateTags(TestCase):
self.assertEqual(result, "<p><em>hi</em></p>") self.assertEqual(result, "<p><em>hi</em></p>")
def test_get_mentions(self, _): def test_get_mentions(self, _):
""" list of people mentioned """ """list of people mentioned"""
status = models.Status.objects.create(content="hi", user=self.remote_user) status = models.Status.objects.create(content="hi", user=self.remote_user)
result = bookwyrm_tags.get_mentions(status, self.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, _): def test_get_status_preview_name(self, _):
""" status context string """ """status context string"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create(content="hi", user=self.user) status = models.Status.objects.create(content="hi", user=self.user)
result = bookwyrm_tags.get_status_preview_name(status) result = bookwyrm_tags.get_status_preview_name(status)
@ -221,7 +221,7 @@ class TemplateTags(TestCase):
self.assertEqual(result, "quotation from <em>Test Book</em>") self.assertEqual(result, "quotation from <em>Test Book</em>")
def test_related_status(self, _): def test_related_status(self, _):
""" gets the subclass model for a notification status """ """gets the subclass model for a notification status"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create(content="hi", user=self.user) status = models.Status.objects.create(content="hi", user=self.user)
notification = models.Notification.objects.create( notification = models.Notification.objects.create(

View file

@ -12,10 +12,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class Inbox(TestCase): class Inbox(TestCase):
""" readthrough tests """ """readthrough tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.client = Client() self.client = Client()
self.factory = RequestFactory() self.factory = RequestFactory()
local_user = models.User.objects.create_user( local_user = models.User.objects.create_user(
@ -48,12 +48,12 @@ class Inbox(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_inbox_invalid_get(self): def test_inbox_invalid_get(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.get("/inbox", content_type="application/json") result = self.client.get("/inbox", content_type="application/json")
self.assertIsInstance(result, HttpResponseNotAllowed) self.assertIsInstance(result, HttpResponseNotAllowed)
def test_inbox_invalid_user(self): 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( result = self.client.post(
"/user/bleh/inbox", "/user/bleh/inbox",
'{"type": "Test", "object": "exists"}', '{"type": "Test", "object": "exists"}',
@ -62,7 +62,7 @@ class Inbox(TestCase):
self.assertIsInstance(result, HttpResponseNotFound) self.assertIsInstance(result, HttpResponseNotFound)
def test_inbox_invalid_bad_signature(self): def test_inbox_invalid_bad_signature(self):
""" bad request for invalid signature """ """bad request for invalid signature"""
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 = False mock_valid.return_value = False
result = self.client.post( result = self.client.post(
@ -73,7 +73,7 @@ class Inbox(TestCase):
self.assertEqual(result.status_code, 401) self.assertEqual(result.status_code, 401)
def test_inbox_invalid_bad_signature_delete(self): def test_inbox_invalid_bad_signature_delete(self):
""" invalid signature for Delete is okay though """ """invalid signature for Delete is okay though"""
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 = False mock_valid.return_value = False
result = self.client.post( result = self.client.post(
@ -84,7 +84,7 @@ class Inbox(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_inbox_unknown_type(self): def test_inbox_unknown_type(self):
""" never heard of that activity type, don't have a handler for it """ """never heard of that activity type, don't have a handler for it"""
with patch("bookwyrm.views.inbox.has_valid_signature") as mock_valid: with patch("bookwyrm.views.inbox.has_valid_signature") as mock_valid:
result = self.client.post( result = self.client.post(
"/inbox", "/inbox",
@ -95,7 +95,7 @@ class Inbox(TestCase):
self.assertIsInstance(result, HttpResponseNotFound) self.assertIsInstance(result, HttpResponseNotFound)
def test_inbox_success(self): 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 = self.create_json
activity["object"] = { activity["object"] = {
"id": "https://example.com/list/22", "id": "https://example.com/list/22",
@ -121,7 +121,7 @@ class Inbox(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_is_blocked_user_agent(self): def test_is_blocked_user_agent(self):
""" check for blocked servers """ """check for blocked servers"""
request = self.factory.post( request = self.factory.post(
"", "",
HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)", HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)",
@ -134,7 +134,7 @@ class Inbox(TestCase):
self.assertTrue(views.inbox.is_blocked_user_agent(request)) self.assertTrue(views.inbox.is_blocked_user_agent(request))
def test_is_blocked_activity(self): def test_is_blocked_activity(self):
""" check for blocked servers """ """check for blocked servers"""
activity = {"actor": "https://mastodon.social/user/whaatever/else"} activity = {"actor": "https://mastodon.social/user/whaatever/else"}
self.assertFalse(views.inbox.is_blocked_activity(activity)) self.assertFalse(views.inbox.is_blocked_activity(activity))
@ -144,7 +144,7 @@ class Inbox(TestCase):
self.assertTrue(views.inbox.is_blocked_activity(activity)) self.assertTrue(views.inbox.is_blocked_activity(activity))
def test_create_by_deactivated_user(self): def test_create_by_deactivated_user(self):
""" don't let deactivated users post """ """don't let deactivated users post"""
self.remote_user.delete(broadcast=False) self.remote_user.delete(broadcast=False)
self.assertTrue(self.remote_user.deleted) self.assertTrue(self.remote_user.deleted)
datafile = pathlib.Path(__file__).parent.joinpath("../../data/ap_note.json") datafile = pathlib.Path(__file__).parent.joinpath("../../data/ap_note.json")

View file

@ -9,10 +9,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxAdd(TestCase): class InboxAdd(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
local_user = models.User.objects.create_user( local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -42,7 +42,7 @@ class InboxAdd(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_handle_add_book_to_shelf(self): def test_handle_add_book_to_shelf(self):
""" shelving a book """ """shelving a book"""
shelf = models.Shelf.objects.create(user=self.remote_user, name="Test Shelf") shelf = models.Shelf.objects.create(user=self.remote_user, name="Test Shelf")
shelf.remote_id = "https://bookwyrm.social/user/mouse/shelf/to-read" shelf.remote_id = "https://bookwyrm.social/user/mouse/shelf/to-read"
shelf.save() shelf.save()
@ -65,7 +65,7 @@ class InboxAdd(TestCase):
@responses.activate @responses.activate
def test_handle_add_book_to_list(self): def test_handle_add_book_to_list(self):
""" listing a book """ """listing a book"""
responses.add( responses.add(
responses.GET, responses.GET,
"https://bookwyrm.social/user/mouse/list/to-read", "https://bookwyrm.social/user/mouse/list/to-read",

View file

@ -9,10 +9,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxActivities(TestCase): class InboxActivities(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -52,7 +52,7 @@ class InboxActivities(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_boost(self, redis_mock): def test_boost(self, redis_mock):
""" boost a status """ """boost a status"""
self.assertEqual(models.Notification.objects.count(), 0) self.assertEqual(models.Notification.objects.count(), 0)
activity = { activity = {
"type": "Announce", "type": "Announce",
@ -82,7 +82,7 @@ class InboxActivities(TestCase):
@responses.activate @responses.activate
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_boost_remote_status(self, redis_mock): def test_boost_remote_status(self, redis_mock):
""" boost a status from a remote server """ """boost a status from a remote server"""
work = models.Work.objects.create(title="work title") work = models.Work.objects.create(title="work title")
book = models.Edition.objects.create( book = models.Edition.objects.create(
title="Test", title="Test",
@ -131,7 +131,7 @@ class InboxActivities(TestCase):
@responses.activate @responses.activate
def test_discarded_boost(self): def test_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( status = models.Status(
content="hi", content="hi",
user=self.remote_user, user=self.remote_user,
@ -154,7 +154,7 @@ class InboxActivities(TestCase):
self.assertEqual(models.Boost.objects.count(), 0) self.assertEqual(models.Boost.objects.count(), 0)
def test_unboost(self): def test_unboost(self):
""" undo a boost """ """undo a boost"""
with patch("bookwyrm.activitystreams.ActivityStream.add_status"): with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
boost = models.Boost.objects.create( boost = models.Boost.objects.create(
boosted_status=self.status, user=self.remote_user boosted_status=self.status, user=self.remote_user
@ -183,7 +183,7 @@ class InboxActivities(TestCase):
self.assertFalse(models.Boost.objects.exists()) self.assertFalse(models.Boost.objects.exists())
def test_unboost_unknown_boost(self): def test_unboost_unknown_boost(self):
""" undo a boost """ """undo a boost"""
activity = { activity = {
"type": "Undo", "type": "Undo",
"actor": "hi", "actor": "hi",

View file

@ -8,10 +8,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxBlock(TestCase): class InboxBlock(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -35,7 +35,7 @@ class InboxBlock(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_handle_blocks(self): 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) 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( models.UserFollowRequest.objects.create(
@ -67,7 +67,7 @@ class InboxBlock(TestCase):
self.assertFalse(models.UserFollowRequest.objects.exists()) self.assertFalse(models.UserFollowRequest.objects.exists())
def test_handle_unblock(self): def test_handle_unblock(self):
""" unblock a user """ """unblock a user"""
self.remote_user.blocks.add(self.local_user) self.remote_user.blocks.add(self.local_user)
block = models.UserBlocks.objects.get() block = models.UserBlocks.objects.get()

View file

@ -11,10 +11,10 @@ from bookwyrm.activitypub import ActivitySerializerError
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxCreate(TestCase): class InboxCreate(TestCase):
""" readthrough tests """ """readthrough tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -53,7 +53,7 @@ class InboxCreate(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_create_status(self): def test_create_status(self):
""" the "it justs works" mode """ """the "it justs works" mode"""
self.assertEqual(models.Status.objects.count(), 1) self.assertEqual(models.Status.objects.count(), 1)
datafile = pathlib.Path(__file__).parent.joinpath( datafile = pathlib.Path(__file__).parent.joinpath(
@ -84,7 +84,7 @@ class InboxCreate(TestCase):
self.assertEqual(models.Status.objects.count(), 2) self.assertEqual(models.Status.objects.count(), 2)
def test_create_status_remote_note_with_mention(self): def test_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.assertEqual(models.Status.objects.count(), 1)
self.assertFalse( self.assertFalse(
models.Notification.objects.filter(user=self.local_user).exists() models.Notification.objects.filter(user=self.local_user).exists()
@ -107,7 +107,7 @@ class InboxCreate(TestCase):
self.assertEqual(models.Notification.objects.get().notification_type, "MENTION") self.assertEqual(models.Notification.objects.get().notification_type, "MENTION")
def test_create_status_remote_note_with_reply(self): def test_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.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))
@ -128,7 +128,7 @@ class InboxCreate(TestCase):
self.assertEqual(models.Notification.objects.get().notification_type, "REPLY") self.assertEqual(models.Notification.objects.get().notification_type, "REPLY")
def test_create_list(self): def test_create_list(self):
""" a new list """ """a new list"""
activity = self.create_json activity = self.create_json
activity["object"] = { activity["object"] = {
"id": "https://example.com/list/22", "id": "https://example.com/list/22",
@ -152,7 +152,7 @@ class InboxCreate(TestCase):
self.assertEqual(book_list.remote_id, "https://example.com/list/22") self.assertEqual(book_list.remote_id, "https://example.com/list/22")
def test_create_unsupported_type(self): def test_create_unsupported_type(self):
""" ignore activities we know we can't handle """ """ignore activities we know we can't handle"""
activity = self.create_json activity = self.create_json
activity["object"] = { activity["object"] = {
"id": "https://example.com/status/887", "id": "https://example.com/status/887",
@ -162,7 +162,7 @@ class InboxCreate(TestCase):
views.inbox.activity_task(activity) views.inbox.activity_task(activity)
def test_create_unknown_type(self): def test_create_unknown_type(self):
""" ignore activities we know we've never heard of """ """ignore activities we know we've never heard of"""
activity = self.create_json activity = self.create_json
activity["object"] = { activity["object"] = {
"id": "https://example.com/status/887", "id": "https://example.com/status/887",

View file

@ -9,10 +9,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxActivities(TestCase): class InboxActivities(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -50,7 +50,7 @@ class InboxActivities(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_delete_status(self): def test_delete_status(self):
""" remove a status """ """remove a status"""
self.assertFalse(self.status.deleted) self.assertFalse(self.status.deleted)
activity = { activity = {
"type": "Delete", "type": "Delete",
@ -71,7 +71,7 @@ class InboxActivities(TestCase):
self.assertIsInstance(status.deleted_date, datetime) self.assertIsInstance(status.deleted_date, datetime)
def test_delete_status_notifications(self): def test_delete_status_notifications(self):
""" remove a status with related notifications """ """remove a status with related notifications"""
models.Notification.objects.create( models.Notification.objects.create(
related_status=self.status, related_status=self.status,
user=self.local_user, user=self.local_user,
@ -106,7 +106,7 @@ class InboxActivities(TestCase):
self.assertEqual(models.Notification.objects.get(), notif) self.assertEqual(models.Notification.objects.get(), notif)
def test_delete_user(self): def test_delete_user(self):
""" delete a user """ """delete a user"""
self.assertTrue(models.User.objects.get(username="rat@example.com").is_active) self.assertTrue(models.User.objects.get(username="rat@example.com").is_active)
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
@ -121,7 +121,7 @@ class InboxActivities(TestCase):
self.assertFalse(models.User.objects.get(username="rat@example.com").is_active) self.assertFalse(models.User.objects.get(username="rat@example.com").is_active)
def test_delete_user_unknown(self): def test_delete_user_unknown(self):
""" don't worry about it if we don't know the user """ """don't worry about it if we don't know the user"""
self.assertEqual(models.User.objects.filter(is_active=True).count(), 2) self.assertEqual(models.User.objects.filter(is_active=True).count(), 2)
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",

View file

@ -9,10 +9,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxRelationships(TestCase): class InboxRelationships(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -36,7 +36,7 @@ class InboxRelationships(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_follow(self): def test_follow(self):
""" remote user wants to follow local user """ """remote user wants to follow local user"""
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123", "id": "https://example.com/users/rat/follows/123",
@ -65,7 +65,7 @@ class InboxRelationships(TestCase):
self.assertEqual(follow.user_subject, self.remote_user) self.assertEqual(follow.user_subject, self.remote_user)
def test_follow_duplicate(self): def test_follow_duplicate(self):
""" remote user wants to follow local user twice """ """remote user wants to follow local user twice"""
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123", "id": "https://example.com/users/rat/follows/123",
@ -92,7 +92,7 @@ class InboxRelationships(TestCase):
self.assertEqual(follow.user_subject, self.remote_user) self.assertEqual(follow.user_subject, self.remote_user)
def test_follow_manually_approved(self): def test_follow_manually_approved(self):
""" needs approval before following """ """needs approval before following"""
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/rat/follows/123", "id": "https://example.com/users/rat/follows/123",
@ -122,7 +122,7 @@ class InboxRelationships(TestCase):
self.assertEqual(list(follow), []) self.assertEqual(list(follow), [])
def test_undo_follow_request(self): def test_undo_follow_request(self):
""" the requester cancels a follow request """ """the requester cancels a follow request"""
self.local_user.manually_approves_followers = True self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False) self.local_user.save(broadcast=False)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
@ -152,7 +152,7 @@ class InboxRelationships(TestCase):
self.assertFalse(self.local_user.follower_requests.exists()) self.assertFalse(self.local_user.follower_requests.exists())
def test_unfollow(self): def test_unfollow(self):
""" remove a relationship """ """remove a relationship"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
rel = models.UserFollows.objects.create( 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
@ -177,7 +177,7 @@ class InboxRelationships(TestCase):
self.assertIsNone(self.local_user.followers.first()) self.assertIsNone(self.local_user.followers.first())
def test_follow_accept(self): def test_follow_accept(self):
""" a remote user approved a follow request from local """ """a remote user approved a follow request from local"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
rel = models.UserFollowRequest.objects.create( 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
@ -208,7 +208,7 @@ class InboxRelationships(TestCase):
self.assertEqual(follows.first(), self.local_user) self.assertEqual(follows.first(), self.local_user)
def test_follow_reject(self): def test_follow_reject(self):
""" turn down a follow request """ """turn down a follow request"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
rel = models.UserFollowRequest.objects.create( 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

View file

@ -8,10 +8,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxActivities(TestCase): class InboxActivities(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -50,7 +50,7 @@ class InboxActivities(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_handle_favorite(self): def test_handle_favorite(self):
""" fav a status """ """fav a status"""
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/fav/1", "id": "https://example.com/fav/1",
@ -68,7 +68,7 @@ class InboxActivities(TestCase):
self.assertEqual(fav.user, self.remote_user) self.assertEqual(fav.user, self.remote_user)
def test_ignore_favorite(self): def test_ignore_favorite(self):
""" don't try to save an unknown status """ """don't try to save an unknown status"""
activity = { activity = {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/fav/1", "id": "https://example.com/fav/1",
@ -83,7 +83,7 @@ class InboxActivities(TestCase):
self.assertFalse(models.Favorite.objects.exists()) self.assertFalse(models.Favorite.objects.exists())
def test_handle_unfavorite(self): def test_handle_unfavorite(self):
""" fav a status """ """fav a status"""
activity = { activity = {
"id": "https://example.com/fav/1#undo", "id": "https://example.com/fav/1#undo",
"type": "Undo", "type": "Undo",

View file

@ -8,10 +8,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxRemove(TestCase): class InboxRemove(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -41,7 +41,7 @@ class InboxRemove(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_handle_unshelve_book(self): def test_handle_unshelve_book(self):
""" remove a book from a shelf """ """remove a book from a shelf"""
shelf = models.Shelf.objects.create(user=self.remote_user, name="Test Shelf") shelf = models.Shelf.objects.create(user=self.remote_user, name="Test Shelf")
shelf.remote_id = "https://bookwyrm.social/user/mouse/shelf/to-read" shelf.remote_id = "https://bookwyrm.social/user/mouse/shelf/to-read"
shelf.save() shelf.save()
@ -70,7 +70,7 @@ class InboxRemove(TestCase):
self.assertFalse(shelf.books.exists()) self.assertFalse(shelf.books.exists())
def test_handle_remove_book_from_list(self): def test_handle_remove_book_from_list(self):
""" listing a book """ """listing a book"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
booklist = models.List.objects.create( booklist = models.List.objects.create(
name="test list", name="test list",

View file

@ -10,10 +10,10 @@ from bookwyrm import models, views
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class InboxUpdate(TestCase): class InboxUpdate(TestCase):
""" inbox tests """ """inbox tests"""
def setUp(self): def setUp(self):
""" basic user and book data """ """basic user and book data"""
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@example.com", "mouse@example.com",
"mouse@mouse.com", "mouse@mouse.com",
@ -45,7 +45,7 @@ class InboxUpdate(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_update_list(self): def test_update_list(self):
""" a new list """ """a new list"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
book_list = models.List.objects.create( 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
@ -79,7 +79,7 @@ class InboxUpdate(TestCase):
self.assertEqual(book_list.remote_id, "https://example.com/list/22") self.assertEqual(book_list.remote_id, "https://example.com/list/22")
def test_update_user(self): def test_update_user(self):
""" update an existing user """ """update an existing user"""
models.UserFollows.objects.create( models.UserFollows.objects.create(
user_subject=self.local_user, user_subject=self.local_user,
user_object=self.remote_user, user_object=self.remote_user,
@ -116,7 +116,7 @@ class InboxUpdate(TestCase):
self.assertTrue(self.local_user in self.remote_user.followers.all()) self.assertTrue(self.local_user in self.remote_user.followers.all())
def test_update_edition(self): def test_update_edition(self):
""" update an existing edition """ """update an existing edition"""
datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_edition.json") datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_edition.json")
bookdata = json.loads(datafile.read_bytes()) bookdata = json.loads(datafile.read_bytes())
@ -146,7 +146,7 @@ class InboxUpdate(TestCase):
self.assertEqual(book.last_edited_by, self.remote_user) self.assertEqual(book.last_edited_by, self.remote_user)
def test_update_work(self): def test_update_work(self):
""" update an existing edition """ """update an existing edition"""
datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_work.json") datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_work.json")
bookdata = json.loads(datafile.read_bytes()) bookdata = json.loads(datafile.read_bytes())

View file

@ -14,10 +14,10 @@ from bookwyrm.settings import DOMAIN
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
class AuthenticationViews(TestCase): class AuthenticationViews(TestCase):
""" login and password management """ """login and password management"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -31,7 +31,7 @@ class AuthenticationViews(TestCase):
self.settings = models.SiteSettings.objects.create(id=1) self.settings = models.SiteSettings.objects.create(id=1)
def test_login_get(self): 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() login = views.Login.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.anonymous_user request.user = self.anonymous_user
@ -47,7 +47,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
def test_register(self): def test_register(self):
""" create a user """ """create a user"""
view = views.Register.as_view() view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1) self.assertEqual(models.User.objects.count(), 1)
request = self.factory.post( request = self.factory.post(
@ -68,7 +68,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(nutria.local, True) self.assertEqual(nutria.local, True)
def test_register_trailing_space(self): def test_register_trailing_space(self):
""" django handles this so weirdly """ """django handles this so weirdly"""
view = views.Register.as_view() view = views.Register.as_view()
request = self.factory.post( request = self.factory.post(
"register/", "register/",
@ -84,7 +84,7 @@ class AuthenticationViews(TestCase):
self.assertEqual(nutria.local, True) self.assertEqual(nutria.local, True)
def test_register_invalid_email(self): def test_register_invalid_email(self):
""" gotta have an email """ """gotta have an email"""
view = views.Register.as_view() view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1) self.assertEqual(models.User.objects.count(), 1)
request = self.factory.post( request = self.factory.post(
@ -95,7 +95,7 @@ class AuthenticationViews(TestCase):
response.render() response.render()
def test_register_invalid_username(self): def test_register_invalid_username(self):
""" gotta have an email """ """gotta have an email"""
view = views.Register.as_view() view = views.Register.as_view()
self.assertEqual(models.User.objects.count(), 1) self.assertEqual(models.User.objects.count(), 1)
request = self.factory.post( request = self.factory.post(
@ -123,7 +123,7 @@ class AuthenticationViews(TestCase):
response.render() response.render()
def test_register_closed_instance(self): def test_register_closed_instance(self):
""" you can't just register """ """you can't just register"""
view = views.Register.as_view() view = views.Register.as_view()
self.settings.allow_registration = False self.settings.allow_registration = False
self.settings.save() self.settings.save()
@ -135,7 +135,7 @@ class AuthenticationViews(TestCase):
view(request) view(request)
def test_register_invite(self): def test_register_invite(self):
""" you can't just register """ """you can't just register"""
view = views.Register.as_view() view = views.Register.as_view()
self.settings.allow_registration = False self.settings.allow_registration = False
self.settings.save() self.settings.save()

View file

@ -12,10 +12,10 @@ from bookwyrm.activitypub import ActivitypubResponse
class AuthorViews(TestCase): class AuthorViews(TestCase):
""" author views""" """author views"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -42,7 +42,7 @@ class AuthorViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_author_page(self): 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() view = views.Author.as_view()
author = models.Author.objects.create(name="Jessica") author = models.Author.objects.create(name="Jessica")
request = self.factory.get("") request = self.factory.get("")
@ -62,7 +62,7 @@ class AuthorViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_edit_author_page(self): 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() view = views.EditAuthor.as_view()
author = models.Author.objects.create(name="Test Author") author = models.Author.objects.create(name="Test Author")
request = self.factory.get("") request = self.factory.get("")
@ -76,7 +76,7 @@ class AuthorViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_edit_author(self): def test_edit_author(self):
""" edit an author """ """edit an author"""
view = views.EditAuthor.as_view() 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) self.local_user.groups.add(self.group)
@ -93,7 +93,7 @@ class AuthorViews(TestCase):
self.assertEqual(author.last_edited_by, self.local_user) self.assertEqual(author.last_edited_by, self.local_user)
def test_edit_author_non_editor(self): 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() 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 = forms.AuthorForm(instance=author)
@ -108,7 +108,7 @@ class AuthorViews(TestCase):
self.assertEqual(author.name, "Test Author") self.assertEqual(author.name, "Test Author")
def test_edit_author_invalid_form(self): 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() 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) self.local_user.groups.add(self.group)

View file

@ -9,10 +9,10 @@ from bookwyrm import models, views
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
class BlockViews(TestCase): class BlockViews(TestCase):
""" view user and edit profile """ """view user and edit profile"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -34,7 +34,7 @@ class BlockViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_block_get(self, _): 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() view = views.Block.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -44,7 +44,7 @@ class BlockViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_block_post(self, _): 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() view = views.Block.as_view()
self.local_user.followers.add(self.remote_user) self.local_user.followers.add(self.remote_user)
models.UserFollowRequest.objects.create( models.UserFollowRequest.objects.create(
@ -65,7 +65,7 @@ class BlockViews(TestCase):
self.assertFalse(models.UserFollowRequest.objects.exists()) self.assertFalse(models.UserFollowRequest.objects.exists())
def test_unblock(self, _): def test_unblock(self, _):
""" undo a block """ """undo a block"""
self.local_user.blocks.add(self.remote_user) self.local_user.blocks.add(self.remote_user)
request = self.factory.post("") request = self.factory.post("")
request.user = self.local_user request.user = self.local_user

View file

@ -18,10 +18,10 @@ from bookwyrm.activitypub import ActivitypubResponse
class BookViews(TestCase): class BookViews(TestCase):
""" books books books """ """books books books"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -81,7 +81,7 @@ class BookViews(TestCase):
) )
def test_book_page(self): 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() view = views.Book.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -100,7 +100,7 @@ class BookViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_edit_book_page(self): 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() view = views.EditBook.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -111,7 +111,7 @@ class BookViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_edit_book(self): def test_edit_book(self):
""" lets a user edit a book """ """lets a user edit a book"""
view = views.EditBook.as_view() view = views.EditBook.as_view()
self.local_user.groups.add(self.group) self.local_user.groups.add(self.group)
form = forms.EditionForm(instance=self.book) form = forms.EditionForm(instance=self.book)
@ -125,7 +125,7 @@ class BookViews(TestCase):
self.assertEqual(self.book.title, "New Title") self.assertEqual(self.book.title, "New Title")
def test_edit_book_add_author(self): def test_edit_book_add_author(self):
""" lets a user edit a book with new authors """ """lets a user edit a book with new authors"""
view = views.EditBook.as_view() view = views.EditBook.as_view()
self.local_user.groups.add(self.group) self.local_user.groups.add(self.group)
form = forms.EditionForm(instance=self.book) form = forms.EditionForm(instance=self.book)
@ -143,7 +143,7 @@ class BookViews(TestCase):
self.assertEqual(self.book.title, "Example Edition") self.assertEqual(self.book.title, "Example Edition")
def test_edit_book_add_new_author_confirm(self): def test_edit_book_add_new_author_confirm(self):
""" lets a user edit a book confirmed with new authors """ """lets a user edit a book confirmed with new authors"""
view = views.ConfirmEditBook.as_view() view = views.ConfirmEditBook.as_view()
self.local_user.groups.add(self.group) self.local_user.groups.add(self.group)
form = forms.EditionForm(instance=self.book) form = forms.EditionForm(instance=self.book)
@ -162,7 +162,7 @@ class BookViews(TestCase):
self.assertEqual(self.book.authors.first().name, "Sappho") self.assertEqual(self.book.authors.first().name, "Sappho")
def test_edit_book_remove_author(self): def test_edit_book_remove_author(self):
""" remove an author from a book """ """remove an author from a book"""
author = models.Author.objects.create(name="Sappho") author = models.Author.objects.create(name="Sappho")
self.book.authors.add(author) self.book.authors.add(author)
form = forms.EditionForm(instance=self.book) form = forms.EditionForm(instance=self.book)
@ -182,7 +182,7 @@ class BookViews(TestCase):
self.assertFalse(self.book.authors.exists()) self.assertFalse(self.book.authors.exists())
def test_create_book(self): def test_create_book(self):
""" create an entirely new book and work """ """create an entirely new book and work"""
view = views.ConfirmEditBook.as_view() view = views.ConfirmEditBook.as_view()
self.local_user.groups.add(self.group) self.local_user.groups.add(self.group)
form = forms.EditionForm() form = forms.EditionForm()
@ -196,7 +196,7 @@ class BookViews(TestCase):
self.assertEqual(book.parent_work.title, "New Title") self.assertEqual(book.parent_work.title, "New Title")
def test_create_book_existing_work(self): def test_create_book_existing_work(self):
""" create an entirely new book and work """ """create an entirely new book and work"""
view = views.ConfirmEditBook.as_view() view = views.ConfirmEditBook.as_view()
self.local_user.groups.add(self.group) self.local_user.groups.add(self.group)
form = forms.EditionForm() form = forms.EditionForm()
@ -211,7 +211,7 @@ class BookViews(TestCase):
self.assertEqual(book.parent_work, self.work) self.assertEqual(book.parent_work, self.work)
def test_create_book_with_author(self): def test_create_book_with_author(self):
""" create an entirely new book and work """ """create an entirely new book and work"""
view = views.ConfirmEditBook.as_view() view = views.ConfirmEditBook.as_view()
self.local_user.groups.add(self.group) self.local_user.groups.add(self.group)
form = forms.EditionForm() form = forms.EditionForm()
@ -229,7 +229,7 @@ class BookViews(TestCase):
self.assertEqual(book.authors.first(), book.parent_work.authors.first()) self.assertEqual(book.authors.first(), book.parent_work.authors.first())
def test_switch_edition(self): def test_switch_edition(self):
""" updates user's relationships to a book """ """updates user's relationships to a book"""
work = models.Work.objects.create(title="test work") work = models.Work.objects.create(title="test work")
edition1 = models.Edition.objects.create(title="first ed", parent_work=work) edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
edition2 = models.Edition.objects.create(title="second ed", parent_work=work) edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
@ -253,7 +253,7 @@ class BookViews(TestCase):
self.assertEqual(models.ReadThrough.objects.get().book, edition2) self.assertEqual(models.ReadThrough.objects.get().book, edition2)
def test_editions_page(self): 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() view = views.Editions.as_view()
request = self.factory.get("") request = self.factory.get("")
with patch("bookwyrm.views.books.is_api_request") as is_api: with patch("bookwyrm.views.books.is_api_request") as is_api:
@ -271,7 +271,7 @@ class BookViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_upload_cover_file(self): def test_upload_cover_file(self):
""" add a cover via file upload """ """add a cover via file upload"""
self.assertFalse(self.book.cover) self.assertFalse(self.book.cover)
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg" "../../static/images/default_avi.jpg"
@ -296,7 +296,7 @@ class BookViews(TestCase):
@responses.activate @responses.activate
def test_upload_cover_url(self): def test_upload_cover_url(self):
""" add a cover via url """ """add a cover via url"""
self.assertFalse(self.book.cover) self.assertFalse(self.book.cover)
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg" "../../static/images/default_avi.jpg"

View file

@ -7,10 +7,10 @@ from bookwyrm import models, views
# pylint: disable=unused-argument # pylint: disable=unused-argument
class DirectoryViews(TestCase): class DirectoryViews(TestCase):
""" tag views""" """tag views"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -32,7 +32,7 @@ class DirectoryViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_directory_page(self): def test_directory_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.Directory.as_view() view = views.Directory.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user

View file

@ -10,10 +10,10 @@ from bookwyrm import forms, models, views
class FederationViews(TestCase): class FederationViews(TestCase):
""" every response to a get request, html or json """ """every response to a get request, html or json"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -35,7 +35,7 @@ class FederationViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_federation_page(self): 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() view = views.Federation.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -46,7 +46,7 @@ class FederationViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_server_page(self): def test_server_page(self):
""" there are so many views, this just makes sure it LOADS """ """there are so many views, this just makes sure it LOADS"""
server = models.FederatedServer.objects.create(server_name="hi.there.com") server = models.FederatedServer.objects.create(server_name="hi.there.com")
view = views.FederatedServer.as_view() view = views.FederatedServer.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -59,7 +59,7 @@ class FederationViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_server_page_block(self): def test_server_page_block(self):
""" block a server """ """block a server"""
server = models.FederatedServer.objects.create(server_name="hi.there.com") server = models.FederatedServer.objects.create(server_name="hi.there.com")
self.remote_user.federated_server = server self.remote_user.federated_server = server
self.remote_user.save() self.remote_user.save()
@ -79,7 +79,7 @@ class FederationViews(TestCase):
self.assertFalse(self.remote_user.is_active) self.assertFalse(self.remote_user.is_active)
def test_server_page_unblock(self): def test_server_page_unblock(self):
""" unblock a server """ """unblock a server"""
server = models.FederatedServer.objects.create( server = models.FederatedServer.objects.create(
server_name="hi.there.com", status="blocked" server_name="hi.there.com", status="blocked"
) )
@ -100,7 +100,7 @@ class FederationViews(TestCase):
self.assertTrue(self.remote_user.is_active) self.assertTrue(self.remote_user.is_active)
def test_add_view_get(self): def test_add_view_get(self):
""" there are so many views, this just makes sure it LOADS """ """there are so many views, this just makes sure it LOADS"""
# create mode # create mode
view = views.AddFederatedServer.as_view() view = views.AddFederatedServer.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -113,7 +113,7 @@ class FederationViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_add_view_post_create(self): def test_add_view_post_create(self):
""" create a server entry """ """create a server entry"""
form = forms.ServerForm() form = forms.ServerForm()
form.data["server_name"] = "remote.server" form.data["server_name"] = "remote.server"
form.data["application_type"] = "coolsoft" form.data["application_type"] = "coolsoft"
@ -131,7 +131,7 @@ class FederationViews(TestCase):
self.assertEqual(server.status, "blocked") self.assertEqual(server.status, "blocked")
def test_import_blocklist(self): def test_import_blocklist(self):
""" load a json file with a list of servers to block """ """load a json file with a list of servers to block"""
server = models.FederatedServer.objects.create(server_name="hi.there.com") server = models.FederatedServer.objects.create(server_name="hi.there.com")
self.remote_user.federated_server = server self.remote_user.federated_server = server
self.remote_user.save() self.remote_user.save()

View file

@ -17,10 +17,10 @@ from bookwyrm.activitypub import ActivitypubResponse
@patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") @patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream")
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class FeedViews(TestCase): class FeedViews(TestCase):
""" activity feed, statuses, dms """ """activity feed, statuses, dms"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -37,7 +37,7 @@ class FeedViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_feed(self, *_): 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() view = views.Feed.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -47,7 +47,7 @@ class FeedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_status_page(self, *_): 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() view = views.Status.as_view()
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create(content="hi", user=self.local_user) status = models.Status.objects.create(content="hi", user=self.local_user)
@ -67,7 +67,7 @@ class FeedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_status_page_not_found(self, *_): def test_status_page_not_found(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() view = views.Status.as_view()
request = self.factory.get("") request = self.factory.get("")
@ -79,7 +79,7 @@ class FeedViews(TestCase):
self.assertEqual(result.status_code, 404) self.assertEqual(result.status_code, 404)
def test_status_page_with_image(self, *_): def test_status_page_with_image(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() view = views.Status.as_view()
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
@ -115,7 +115,7 @@ class FeedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_replies_page(self, *_): 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() view = views.Replies.as_view()
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Status.objects.create(content="hi", user=self.local_user) status = models.Status.objects.create(content="hi", user=self.local_user)
@ -135,7 +135,7 @@ class FeedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_direct_messages_page(self, *_): 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() view = views.DirectMessage.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -145,7 +145,7 @@ class FeedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_get_suggested_book(self, *_): def test_get_suggested_book(self, *_):
""" gets books the ~*~ algorithm ~*~ thinks you want to post about """ """gets books the ~*~ algorithm ~*~ thinks you want to post about"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
models.ShelfBook.objects.create( models.ShelfBook.objects.create(
book=self.book, book=self.book,

View file

@ -11,10 +11,10 @@ from bookwyrm import models, views
class BookViews(TestCase): class BookViews(TestCase):
""" books books books """ """books books books"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -50,7 +50,7 @@ class BookViews(TestCase):
) )
def test_handle_follow_remote(self): def test_handle_follow_remote(self):
""" send a follow request """ """send a follow request"""
request = self.factory.post("", {"user": self.remote_user.username}) request = self.factory.post("", {"user": self.remote_user.username})
request.user = self.local_user request.user = self.local_user
self.assertEqual(models.UserFollowRequest.objects.count(), 0) self.assertEqual(models.UserFollowRequest.objects.count(), 0)
@ -65,7 +65,7 @@ class BookViews(TestCase):
self.assertEqual(rel.status, "follow_request") self.assertEqual(rel.status, "follow_request")
def test_handle_follow_local_manually_approves(self): def test_handle_follow_local_manually_approves(self):
""" send a follow request """ """send a follow request"""
rat = models.User.objects.create_user( rat = models.User.objects.create_user(
"rat@local.com", "rat@local.com",
"rat@rat.com", "rat@rat.com",
@ -88,7 +88,7 @@ class BookViews(TestCase):
self.assertEqual(rel.status, "follow_request") self.assertEqual(rel.status, "follow_request")
def test_handle_follow_local(self): def test_handle_follow_local(self):
""" send a follow request """ """send a follow request"""
rat = models.User.objects.create_user( rat = models.User.objects.create_user(
"rat@local.com", "rat@local.com",
"rat@rat.com", "rat@rat.com",
@ -111,7 +111,7 @@ class BookViews(TestCase):
self.assertEqual(rel.status, "follows") self.assertEqual(rel.status, "follows")
def test_handle_unfollow(self): def test_handle_unfollow(self):
""" send an unfollow """ """send an unfollow"""
request = self.factory.post("", {"user": self.remote_user.username}) request = self.factory.post("", {"user": self.remote_user.username})
request.user = self.local_user request.user = self.local_user
self.remote_user.followers.add(self.local_user) self.remote_user.followers.add(self.local_user)
@ -125,7 +125,7 @@ class BookViews(TestCase):
self.assertEqual(self.remote_user.followers.count(), 0) self.assertEqual(self.remote_user.followers.count(), 0)
def test_handle_accept(self): def test_handle_accept(self):
""" accept a follow request """ """accept a follow request"""
self.local_user.manually_approves_followers = True self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False) self.local_user.save(broadcast=False)
request = self.factory.post("", {"user": self.remote_user.username}) request = self.factory.post("", {"user": self.remote_user.username})
@ -142,7 +142,7 @@ class BookViews(TestCase):
self.assertEqual(self.local_user.followers.first(), self.remote_user) self.assertEqual(self.local_user.followers.first(), self.remote_user)
def test_handle_reject(self): def test_handle_reject(self):
""" reject a follow request """ """reject a follow request"""
self.local_user.manually_approves_followers = True self.local_user.manually_approves_followers = True
self.local_user.save(broadcast=False) self.local_user.save(broadcast=False)
request = self.factory.post("", {"user": self.remote_user.username}) request = self.factory.post("", {"user": self.remote_user.username})

View file

@ -8,10 +8,10 @@ from bookwyrm import forms, models, views
class GetStartedViews(TestCase): class GetStartedViews(TestCase):
""" helping new users get oriented """ """helping new users get oriented"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -31,7 +31,7 @@ class GetStartedViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_profile_view(self): def test_profile_view(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.GetStartedProfile.as_view() view = views.GetStartedProfile.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -43,7 +43,7 @@ class GetStartedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_profile_view_post(self): def test_profile_view_post(self):
""" save basic user details """ """save basic user details"""
view = views.GetStartedProfile.as_view() view = views.GetStartedProfile.as_view()
form = forms.LimitedEditUserForm(instance=self.local_user) form = forms.LimitedEditUserForm(instance=self.local_user)
form.data["name"] = "New Name" form.data["name"] = "New Name"
@ -61,7 +61,7 @@ class GetStartedViews(TestCase):
self.assertTrue(self.local_user.discoverable) self.assertTrue(self.local_user.discoverable)
def test_books_view(self): def test_books_view(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.GetStartedBooks.as_view() view = views.GetStartedBooks.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -73,7 +73,7 @@ class GetStartedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_books_view_with_query(self): def test_books_view_with_query(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.GetStartedBooks.as_view() view = views.GetStartedBooks.as_view()
request = self.factory.get("?query=Example") request = self.factory.get("?query=Example")
request.user = self.local_user request.user = self.local_user
@ -85,7 +85,7 @@ class GetStartedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_books_view_post(self): def test_books_view_post(self):
""" shelve some books """ """shelve some books"""
view = views.GetStartedBooks.as_view() view = views.GetStartedBooks.as_view()
data = {self.book.id: self.local_user.shelf_set.first().id} data = {self.book.id: self.local_user.shelf_set.first().id}
request = self.factory.post("", data) request = self.factory.post("", data)
@ -103,7 +103,7 @@ class GetStartedViews(TestCase):
self.assertEqual(shelfbook.user, self.local_user) self.assertEqual(shelfbook.user, self.local_user)
def test_users_view(self): def test_users_view(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.GetStartedUsers.as_view() view = views.GetStartedUsers.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -115,7 +115,7 @@ class GetStartedViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_users_view_with_query(self): def test_users_view_with_query(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.GetStartedUsers.as_view() view = views.GetStartedUsers.as_view()
request = self.factory.get("?query=rat") request = self.factory.get("?query=rat")
request.user = self.local_user request.user = self.local_user

View file

@ -11,10 +11,10 @@ from bookwyrm import models, views
class GoalViews(TestCase): class GoalViews(TestCase):
""" viewing and creating statuses """ """viewing and creating statuses"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -41,7 +41,7 @@ class GoalViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_goal_page_no_goal(self): 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() view = views.Goal.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.rat request.user = self.rat
@ -50,7 +50,7 @@ class GoalViews(TestCase):
self.assertEqual(result.status_code, 404) self.assertEqual(result.status_code, 404)
def test_goal_page_no_goal_self(self): 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() view = views.Goal.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -60,7 +60,7 @@ class GoalViews(TestCase):
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
def test_goal_page_anonymous(self): def test_goal_page_anonymous(self):
""" can't view it without login """ """can't view it without login"""
view = views.Goal.as_view() view = views.Goal.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.anonymous_user request.user = self.anonymous_user
@ -69,7 +69,7 @@ class GoalViews(TestCase):
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
def test_goal_page_public(self): def test_goal_page_public(self):
""" view a user's public goal """ """view a user's public goal"""
models.ReadThrough.objects.create( models.ReadThrough.objects.create(
finish_date=timezone.now(), finish_date=timezone.now(),
user=self.local_user, user=self.local_user,
@ -91,7 +91,7 @@ class GoalViews(TestCase):
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
def test_goal_page_private(self): def test_goal_page_private(self):
""" view a user's private goal """ """view a user's private goal"""
models.AnnualGoal.objects.create( models.AnnualGoal.objects.create(
user=self.local_user, year=2020, goal=15, privacy="followers" user=self.local_user, year=2020, goal=15, privacy="followers"
) )
@ -104,7 +104,7 @@ class GoalViews(TestCase):
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_create_goal(self, _): def test_create_goal(self, _):
""" create a new goal """ """create a new goal"""
view = views.Goal.as_view() view = views.Goal.as_view()
request = self.factory.post( request = self.factory.post(
"", "",

View file

@ -13,10 +13,10 @@ from bookwyrm.settings import USER_AGENT
@patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.activitystreams.ActivityStream.add_status")
class ViewsHelpers(TestCase): class ViewsHelpers(TestCase):
""" viewing and creating statuses """ """viewing and creating statuses"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -53,12 +53,12 @@ class ViewsHelpers(TestCase):
) )
def test_get_edition(self, _): def test_get_edition(self, _):
""" given an edition or a work, returns an edition """ """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.book.id), self.book)
self.assertEqual(views.helpers.get_edition(self.work.id), self.book) self.assertEqual(views.helpers.get_edition(self.work.id), self.book)
def test_get_user_from_username(self, _): def test_get_user_from_username(self, _):
""" works for either localname or username """ """works for either localname or username"""
self.assertEqual( self.assertEqual(
views.helpers.get_user_from_username(self.local_user, "mouse"), views.helpers.get_user_from_username(self.local_user, "mouse"),
self.local_user, self.local_user,
@ -71,7 +71,7 @@ class ViewsHelpers(TestCase):
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, _): def test_is_api_request(self, _):
""" should it return html or json """ """should it return html or json"""
request = self.factory.get("/path") request = self.factory.get("/path")
request.headers = {"Accept": "application/json"} request.headers = {"Accept": "application/json"}
self.assertTrue(views.helpers.is_api_request(request)) self.assertTrue(views.helpers.is_api_request(request))
@ -85,12 +85,12 @@ class ViewsHelpers(TestCase):
self.assertFalse(views.helpers.is_api_request(request)) self.assertFalse(views.helpers.is_api_request(request))
def test_is_api_request_no_headers(self, _): def test_is_api_request_no_headers(self, _):
""" should it return html or json """ """should it return html or json"""
request = self.factory.get("/path") request = self.factory.get("/path")
self.assertFalse(views.helpers.is_api_request(request)) self.assertFalse(views.helpers.is_api_request(request))
def test_is_bookwyrm_request(self, _): def test_is_bookwyrm_request(self, _):
""" checks if a request came from a bookwyrm instance """ """checks if a request came from a bookwyrm instance"""
request = self.factory.get("", {"q": "Test Book"}) request = self.factory.get("", {"q": "Test Book"})
self.assertFalse(views.helpers.is_bookwyrm_request(request)) self.assertFalse(views.helpers.is_bookwyrm_request(request))
@ -105,7 +105,7 @@ class ViewsHelpers(TestCase):
self.assertTrue(views.helpers.is_bookwyrm_request(request)) self.assertTrue(views.helpers.is_bookwyrm_request(request))
def test_existing_user(self, _): def test_existing_user(self, _):
""" simple database lookup by username """ """simple database lookup by username"""
result = views.helpers.handle_remote_webfinger("@mouse@local.com") result = views.helpers.handle_remote_webfinger("@mouse@local.com")
self.assertEqual(result, self.local_user) self.assertEqual(result, self.local_user)
@ -117,7 +117,7 @@ class ViewsHelpers(TestCase):
@responses.activate @responses.activate
def test_load_user(self, _): def test_load_user(self, _):
""" find a remote user using webfinger """ """find a remote user using webfinger"""
username = "mouse@example.com" username = "mouse@example.com"
wellknown = { wellknown = {
"subject": "acct:mouse@example.com", "subject": "acct:mouse@example.com",
@ -147,7 +147,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(result.username, "mouse@example.com") self.assertEqual(result.username, "mouse@example.com")
def test_user_on_blocked_server(self, _): def test_user_on_blocked_server(self, _):
""" find a remote user using webfinger """ """find a remote user using webfinger"""
models.FederatedServer.objects.create( models.FederatedServer.objects.create(
server_name="example.com", status="blocked" server_name="example.com", status="blocked"
) )
@ -156,7 +156,7 @@ class ViewsHelpers(TestCase):
self.assertIsNone(result) self.assertIsNone(result)
def test_handle_reading_status_to_read(self, _): def test_handle_reading_status_to_read(self, _):
""" posts shelve activities """ """posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="to-read") shelf = self.local_user.shelf_set.get(identifier="to-read")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.helpers.handle_reading_status( views.helpers.handle_reading_status(
@ -168,7 +168,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(status.content, "wants to read") self.assertEqual(status.content, "wants to read")
def test_handle_reading_status_reading(self, _): def test_handle_reading_status_reading(self, _):
""" posts shelve activities """ """posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="reading") shelf = self.local_user.shelf_set.get(identifier="reading")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.helpers.handle_reading_status( views.helpers.handle_reading_status(
@ -180,7 +180,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(status.content, "started reading") self.assertEqual(status.content, "started reading")
def test_handle_reading_status_read(self, _): def test_handle_reading_status_read(self, _):
""" posts shelve activities """ """posts shelve activities"""
shelf = self.local_user.shelf_set.get(identifier="read") shelf = self.local_user.shelf_set.get(identifier="read")
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.helpers.handle_reading_status( views.helpers.handle_reading_status(
@ -192,7 +192,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(status.content, "finished reading") self.assertEqual(status.content, "finished reading")
def test_handle_reading_status_other(self, _): def test_handle_reading_status_other(self, _):
""" posts shelve activities """ """posts shelve activities"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
views.helpers.handle_reading_status( views.helpers.handle_reading_status(
self.local_user, self.shelf, self.book, "public" self.local_user, self.shelf, self.book, "public"
@ -200,7 +200,7 @@ class ViewsHelpers(TestCase):
self.assertFalse(models.GeneratedNote.objects.exists()) self.assertFalse(models.GeneratedNote.objects.exists())
def test_get_annotated_users(self, _): def test_get_annotated_users(self, _):
""" list of people you might know """ """list of people you might know"""
user_1 = models.User.objects.create_user( user_1 = models.User.objects.create_user(
"nutria@local.com", "nutria@local.com",
"nutria@nutria.com", "nutria@nutria.com",
@ -247,7 +247,7 @@ class ViewsHelpers(TestCase):
self.assertEqual(remote_user_annotated.shared_books, 0) self.assertEqual(remote_user_annotated.shared_books, 0)
def test_get_annotated_users_counts(self, _): def test_get_annotated_users_counts(self, _):
""" correct counting for multiple shared attributed """ """correct counting for multiple shared attributed"""
user_1 = models.User.objects.create_user( user_1 = models.User.objects.create_user(
"nutria@local.com", "nutria@local.com",
"nutria@nutria.com", "nutria@nutria.com",

View file

@ -9,10 +9,10 @@ from bookwyrm import views
class ImportViews(TestCase): class ImportViews(TestCase):
""" goodreads import views """ """goodreads import views"""
def setUp(self): def setUp(self):
""" we need basic test data and mocks """ """we need basic test data and mocks"""
self.factory = RequestFactory() self.factory = RequestFactory()
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse@local.com", "mouse@local.com",
@ -24,7 +24,7 @@ class ImportViews(TestCase):
models.SiteSettings.objects.create() models.SiteSettings.objects.create()
def test_import_page(self): 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() view = views.Import.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
@ -34,7 +34,7 @@ class ImportViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_import_status(self): 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() view = views.ImportStatus.as_view()
import_job = models.ImportJob.objects.create(user=self.local_user) import_job = models.ImportJob.objects.create(user=self.local_user)
request = self.factory.get("") request = self.factory.get("")
@ -47,7 +47,7 @@ class ImportViews(TestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_retry_import(self): def test_retry_import(self):
""" retry failed items """ """retry failed items"""
view = views.ImportStatus.as_view() view = views.ImportStatus.as_view()
import_job = models.ImportJob.objects.create( import_job = models.ImportJob.objects.create(
user=self.local_user, privacy="unlisted" user=self.local_user, privacy="unlisted"

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