Runs black

This commit is contained in:
Mouse Reeve
2021-03-08 08:49:10 -08:00
parent a07f955781
commit 70296e760b
198 changed files with 10239 additions and 8572 deletions

View File

@ -1,4 +1,4 @@
''' database schema for user data '''
""" database schema for user data """
import re
from urllib.parse import urlparse
@ -23,25 +23,28 @@ from . import fields, Review
class User(OrderedCollectionPageMixin, AbstractUser):
''' a user who wants to read books '''
""" a user who wants to read books """
username = fields.UsernameField()
email = models.EmailField(unique=True, null=True)
key_pair = fields.OneToOneField(
'KeyPair',
"KeyPair",
on_delete=models.CASCADE,
blank=True, null=True,
activitypub_field='publicKey',
related_name='owner'
blank=True,
null=True,
activitypub_field="publicKey",
related_name="owner",
)
inbox = fields.RemoteIdField(unique=True)
shared_inbox = fields.RemoteIdField(
activitypub_field='sharedInbox',
activitypub_wrapper='endpoints',
activitypub_field="sharedInbox",
activitypub_wrapper="endpoints",
deduplication_field=False,
null=True)
null=True,
)
federated_server = models.ForeignKey(
'FederatedServer',
"FederatedServer",
on_delete=models.PROTECT,
null=True,
blank=True,
@ -59,54 +62,58 @@ class User(OrderedCollectionPageMixin, AbstractUser):
# name is your display name, which you can change at will
name = fields.CharField(max_length=100, null=True, blank=True)
avatar = fields.ImageField(
upload_to='avatars/', blank=True, null=True,
activitypub_field='icon', alt_field='alt_text')
upload_to="avatars/",
blank=True,
null=True,
activitypub_field="icon",
alt_field="alt_text",
)
followers = fields.ManyToManyField(
'self',
"self",
link_only=True,
symmetrical=False,
through='UserFollows',
through_fields=('user_object', 'user_subject'),
related_name='following'
through="UserFollows",
through_fields=("user_object", "user_subject"),
related_name="following",
)
follow_requests = models.ManyToManyField(
'self',
"self",
symmetrical=False,
through='UserFollowRequest',
through_fields=('user_subject', 'user_object'),
related_name='follower_requests'
through="UserFollowRequest",
through_fields=("user_subject", "user_object"),
related_name="follower_requests",
)
blocks = models.ManyToManyField(
'self',
"self",
symmetrical=False,
through='UserBlocks',
through_fields=('user_subject', 'user_object'),
related_name='blocked_by'
through="UserBlocks",
through_fields=("user_subject", "user_object"),
related_name="blocked_by",
)
favorites = models.ManyToManyField(
'Status',
"Status",
symmetrical=False,
through='Favorite',
through_fields=('user', 'status'),
related_name='favorite_statuses'
through="Favorite",
through_fields=("user", "status"),
related_name="favorite_statuses",
)
remote_id = fields.RemoteIdField(
null=True, unique=True, activitypub_field='id')
remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id")
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
last_active_date = models.DateTimeField(auto_now=True)
manually_approves_followers = fields.BooleanField(default=False)
name_field = 'username'
name_field = "username"
@property
def alt_text(self):
''' alt text with username '''
return 'avatar for %s' % (self.localname or self.username)
""" alt text with username """
return "avatar for %s" % (self.localname or self.username)
@property
def display_name(self):
''' show the cleanest version of the user's name possible '''
if self.name and self.name != '':
""" show the cleanest version of the user's name possible """
if self.name and self.name != "":
return self.name
return self.localname or self.username
@ -114,78 +121,82 @@ class User(OrderedCollectionPageMixin, AbstractUser):
@classmethod
def viewer_aware_objects(cls, viewer):
''' the user queryset filtered for the context of the logged in user '''
""" the user queryset filtered for the context of the logged in user """
queryset = cls.objects.filter(is_active=True)
if viewer.is_authenticated:
queryset = queryset.exclude(
blocks=viewer
)
queryset = queryset.exclude(blocks=viewer)
return queryset
def to_outbox(self, filter_type=None, **kwargs):
''' an ordered collection of statuses '''
""" an ordered collection of statuses """
if filter_type:
filter_class = apps.get_model(
'bookwyrm.%s' % filter_type, require_ready=True)
"bookwyrm.%s" % filter_type, require_ready=True
)
if not issubclass(filter_class, Status):
raise TypeError(
'filter_status_class must be a subclass of models.Status')
"filter_status_class must be a subclass of models.Status"
)
queryset = filter_class.objects
else:
queryset = Status.objects
queryset = queryset.filter(
user=self,
deleted=False,
privacy__in=['public', 'unlisted'],
).select_subclasses().order_by('-published_date')
return self.to_ordered_collection(queryset, \
collection_only=True, remote_id=self.outbox, **kwargs).serialize()
queryset = (
queryset.filter(
user=self,
deleted=False,
privacy__in=["public", "unlisted"],
)
.select_subclasses()
.order_by("-published_date")
)
return self.to_ordered_collection(
queryset, collection_only=True, remote_id=self.outbox, **kwargs
).serialize()
def to_following_activity(self, **kwargs):
''' activitypub following list '''
remote_id = '%s/following' % self.remote_id
""" activitypub following list """
remote_id = "%s/following" % self.remote_id
return self.to_ordered_collection(
self.following.order_by('-updated_date').all(),
self.following.order_by("-updated_date").all(),
remote_id=remote_id,
id_only=True,
**kwargs
)
def to_followers_activity(self, **kwargs):
''' activitypub followers list '''
remote_id = '%s/followers' % self.remote_id
""" activitypub followers list """
remote_id = "%s/followers" % self.remote_id
return self.to_ordered_collection(
self.followers.order_by('-updated_date').all(),
self.followers.order_by("-updated_date").all(),
remote_id=remote_id,
id_only=True,
**kwargs
)
def to_activity(self):
''' override default AP serializer to add context object
idk if this is the best way to go about this '''
"""override default AP serializer to add context object
idk if this is the best way to go about this"""
activity_object = super().to_activity()
activity_object['@context'] = [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
activity_object["@context"] = [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
'schema': 'http://schema.org#',
'PropertyValue': 'schema:PropertyValue',
'value': 'schema:value',
}
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
},
]
return activity_object
def save(self, *args, **kwargs):
''' populate fields for new local users '''
""" populate fields for new local users """
created = not bool(self.id)
if not self.local and not re.match(regex.full_username, self.username):
# generate a username that uses the domain (webfinger format)
actor_parts = urlparse(self.remote_id)
self.username = '%s@%s' % (self.username, actor_parts.netloc)
self.username = "%s@%s" % (self.username, actor_parts.netloc)
super().save(*args, **kwargs)
# this user already exists, no need to populate fields
@ -200,114 +211,120 @@ class User(OrderedCollectionPageMixin, AbstractUser):
return
# populate fields for local users
self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname)
self.inbox = '%s/inbox' % self.remote_id
self.shared_inbox = 'https://%s/inbox' % DOMAIN
self.outbox = '%s/outbox' % self.remote_id
self.remote_id = "https://%s/user/%s" % (DOMAIN, self.localname)
self.inbox = "%s/inbox" % self.remote_id
self.shared_inbox = "https://%s/inbox" % DOMAIN
self.outbox = "%s/outbox" % self.remote_id
# an id needs to be set before we can proceed with related models
super().save(*args, **kwargs)
# make users editors by default
try:
self.groups.add(Group.objects.get(name='editor'))
self.groups.add(Group.objects.get(name="editor"))
except Group.DoesNotExist:
# this should only happen in tests
pass
# create keys and shelves for new local users
self.key_pair = KeyPair.objects.create(
remote_id='%s/#main-key' % self.remote_id)
remote_id="%s/#main-key" % self.remote_id
)
self.save(broadcast=False)
shelves = [{
'name': 'To Read',
'identifier': 'to-read',
}, {
'name': 'Currently Reading',
'identifier': 'reading',
}, {
'name': 'Read',
'identifier': 'read',
}]
shelves = [
{
"name": "To Read",
"identifier": "to-read",
},
{
"name": "Currently Reading",
"identifier": "reading",
},
{
"name": "Read",
"identifier": "read",
},
]
for shelf in shelves:
Shelf(
name=shelf['name'],
identifier=shelf['identifier'],
name=shelf["name"],
identifier=shelf["identifier"],
user=self,
editable=False
editable=False,
).save(broadcast=False)
@property
def local_path(self):
''' this model doesn't inherit bookwyrm model, so here we are '''
return '/user/%s' % (self.localname or self.username)
""" this model doesn't inherit bookwyrm model, so here we are """
return "/user/%s" % (self.localname or self.username)
class KeyPair(ActivitypubMixin, BookWyrmModel):
''' public and private keys for a user '''
""" public and private keys for a user """
private_key = models.TextField(blank=True, null=True)
public_key = fields.TextField(
blank=True, null=True, activitypub_field='publicKeyPem')
blank=True, null=True, activitypub_field="publicKeyPem"
)
activity_serializer = activitypub.PublicKey
serialize_reverse_fields = [('owner', 'owner', 'id')]
serialize_reverse_fields = [("owner", "owner", "id")]
def get_remote_id(self):
# self.owner is set by the OneToOneField on User
return '%s/#main-key' % self.owner.remote_id
return "%s/#main-key" % self.owner.remote_id
def save(self, *args, **kwargs):
''' create a key pair '''
""" create a key pair """
# no broadcasting happening here
if 'broadcast' in kwargs:
del kwargs['broadcast']
if "broadcast" in kwargs:
del kwargs["broadcast"]
if not self.public_key:
self.private_key, self.public_key = create_key_pair()
return super().save(*args, **kwargs)
def to_activity(self):
''' override default AP serializer to add context object
idk if this is the best way to go about this '''
"""override default AP serializer to add context object
idk if this is the best way to go about this"""
activity_object = super().to_activity()
del activity_object['@context']
del activity_object['type']
del activity_object["@context"]
del activity_object["type"]
return activity_object
class AnnualGoal(BookWyrmModel):
''' set a goal for how many books you read in a year '''
user = models.ForeignKey('User', on_delete=models.PROTECT)
goal = models.IntegerField(
validators=[MinValueValidator(1)]
)
""" set a goal for how many books you read in a year """
user = models.ForeignKey("User", on_delete=models.PROTECT)
goal = models.IntegerField(validators=[MinValueValidator(1)])
year = models.IntegerField(default=timezone.now().year)
privacy = models.CharField(
max_length=255,
default='public',
choices=fields.PrivacyLevels.choices
max_length=255, default="public", choices=fields.PrivacyLevels.choices
)
class Meta:
''' unqiueness constraint '''
unique_together = ('user', 'year')
""" unqiueness constraint """
unique_together = ("user", "year")
def get_remote_id(self):
''' put the year in the path '''
return '%s/goal/%d' % (self.user.remote_id, self.year)
""" put the year in the path """
return "%s/goal/%d" % (self.user.remote_id, self.year)
@property
def books(self):
''' the books you've read this year '''
return self.user.readthrough_set.filter(
finish_date__year__gte=self.year
).order_by('-finish_date').all()
""" the books you've read this year """
return (
self.user.readthrough_set.filter(finish_date__year__gte=self.year)
.order_by("-finish_date")
.all()
)
@property
def ratings(self):
''' ratings for books read this year '''
""" ratings for books read this year """
book_ids = [r.book.id for r in self.books]
reviews = Review.objects.filter(
user=self.user,
@ -315,55 +332,50 @@ class AnnualGoal(BookWyrmModel):
)
return {r.book.id: r.rating for r in reviews}
@property
def progress_percent(self):
''' how close to your goal, in percent form '''
""" how close to your goal, in percent form """
return int(float(self.book_count / self.goal) * 100)
@property
def book_count(self):
''' how many books you've read this year '''
""" how many books you've read this year """
return self.user.readthrough_set.filter(
finish_date__year__gte=self.year).count()
finish_date__year__gte=self.year
).count()
@app.task
def set_remote_server(user_id):
''' figure out the user's remote server in the background '''
""" figure out the user's remote server in the background """
user = User.objects.get(id=user_id)
actor_parts = urlparse(user.remote_id)
user.federated_server = \
get_or_create_remote_server(actor_parts.netloc)
user.federated_server = get_or_create_remote_server(actor_parts.netloc)
user.save(broadcast=False)
if user.bookwyrm_user:
get_remote_reviews.delay(user.outbox)
def get_or_create_remote_server(domain):
''' get info on a remote server '''
""" get info on a remote server """
try:
return FederatedServer.objects.get(
server_name=domain
)
return FederatedServer.objects.get(server_name=domain)
except FederatedServer.DoesNotExist:
pass
try:
data = get_data('https://%s/.well-known/nodeinfo' % domain)
data = get_data("https://%s/.well-known/nodeinfo" % domain)
try:
nodeinfo_url = data.get('links')[0].get('href')
nodeinfo_url = data.get("links")[0].get("href")
except (TypeError, KeyError):
raise ConnectorException()
data = get_data(nodeinfo_url)
application_type = data.get('software', {}).get('name')
application_version = data.get('software', {}).get('version')
application_type = data.get("software", {}).get("name")
application_version = data.get("software", {}).get("version")
except ConnectorException:
application_type = application_version = None
server = FederatedServer.objects.create(
server_name=domain,
application_type=application_type,
@ -374,12 +386,12 @@ def get_or_create_remote_server(domain):
@app.task
def get_remote_reviews(outbox):
''' ingest reviews by a new remote bookwyrm user '''
outbox_page = outbox + '?page=true&type=Review'
""" ingest reviews by a new remote bookwyrm user """
outbox_page = outbox + "?page=true&type=Review"
data = get_data(outbox_page)
# TODO: pagination?
for activity in data['orderedItems']:
if not activity['type'] == 'Review':
for activity in data["orderedItems"]:
if not activity["type"] == "Review":
continue
activitypub.Review(**activity).to_model()