Merge pull request #199 from mouse-reeve/style-fixes

Style fixes
This commit is contained in:
Mouse Reeve 2020-09-23 07:37:25 -07:00 committed by GitHub
commit b4cf193a81
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 65 additions and 44 deletions

View file

@ -162,7 +162,7 @@ class AbstractConnector(ABC):
def update_book(self, book, data=None):
''' load new data '''
if not book.sync and not book.sync_cover:
return
return book
if not data:
key = getattr(book, self.key_name)
@ -286,7 +286,7 @@ def get_data(url):
return data
class SearchResult(object):
class SearchResult:
''' standardized search result object '''
def __init__(self, title, key, author, year):
self.title = title
@ -299,7 +299,7 @@ class SearchResult(object):
self.key, self.title, self.author)
class Mapping(object):
class Mapping:
''' associate a local database field with a field in an external dataset '''
def __init__(
self, local_field, remote_field=None, formatter=None, model=None):

View file

@ -123,15 +123,15 @@ class Connector(AbstractConnector):
return data.get('docs')
def format_search_result(self, doc):
def format_search_result(self, search_result):
# build the remote id from the openlibrary key
key = self.books_url + doc['key']
author = doc.get('author_name') or ['Unknown']
key = self.books_url + search_result['key']
author = search_result.get('author_name') or ['Unknown']
return SearchResult(
doc.get('title'),
search_result.get('title'),
key,
', '.join(author),
doc.get('first_publish_year'),
search_result.get('first_publish_year'),
)

View file

@ -7,10 +7,6 @@ from .abstract_connector import AbstractConnector, SearchResult
class Connector(AbstractConnector):
''' instantiate a connector '''
def __init__(self, identifier):
super().__init__(identifier)
def search(self, query):
''' right now you can't search bookwyrm sorry, but when
that gets implemented it will totally rule '''
@ -44,18 +40,18 @@ class Connector(AbstractConnector):
return search_results
def format_search_result(self, book):
def format_search_result(self, search_result):
return SearchResult(
book.title,
book.local_id,
book.author_text,
book.published_date.year if book.published_date else None,
search_result.title,
search_result.local_id,
search_result.author_text,
search_result.published_date.year if \
search_result.published_date else None,
)
def get_or_create_book(self, remote_id):
''' this COULD be semi-implemented but I think it shouldn't be used '''
pass
def is_work_data(self, data):

View file

@ -1,7 +1,6 @@
''' usin django model forms '''
''' using django model forms '''
import datetime
from django.core.exceptions import ValidationError
from django.forms import ModelForm, PasswordInput, widgets
from django import forms

View file

@ -38,10 +38,8 @@ def shared_inbox(request):
try:
activity = json.loads(request.body)
except json.decoder.JSONDecodeError:
return HttpResponseBadRequest()
if not activity.get('object'):
activity_object = activity['object']
except (json.decoder.JSONDecodeError, KeyError):
return HttpResponseBadRequest()
if not has_valid_signature(request, activity):
@ -74,7 +72,7 @@ def shared_inbox(request):
handler = handlers.get(activity_type, None)
if isinstance(handler, dict):
handler = handler.get(activity['object']['type'], None)
handler = handler.get(activity_object['type'], None)
if not handler:
return HttpResponseNotFound()

View file

@ -35,6 +35,7 @@ def construct_search_term(title, author):
class ImportJob(models.Model):
''' entry for a specific request for book data import '''
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_date = models.DateTimeField(default=timezone.now)
task_id = models.CharField(max_length=100, null=True)
@ -42,6 +43,7 @@ class ImportJob(models.Model):
'Status', null=True, on_delete=models.PROTECT)
class ImportItem(models.Model):
''' a single line of a csv being imported '''
job = models.ForeignKey(
ImportJob,
on_delete=models.CASCADE,
@ -77,6 +79,7 @@ class ImportItem(models.Model):
@property
def isbn(self):
''' pulls out the isbn13 field from the csv line data '''
return unquote_string(self.data['ISBN13'])
@property
@ -87,24 +90,29 @@ class ImportItem(models.Model):
@property
def review(self):
''' a user-written review, to be imported with the book data '''
return self.data['My Review']
@property
def rating(self):
''' x/5 star rating for a book '''
return int(self.data['My Rating'])
@property
def date_added(self):
''' when the book was added to this dataset '''
if self.data['Date Added']:
return dateutil.parser.parse(self.data['Date Added'])
@property
def date_read(self):
''' the date a book was completed '''
if self.data['Date Read']:
return dateutil.parser.parse(self.data['Date Read'])
@property
def reads(self):
''' formats a read through dataset for the book in this line '''
if (self.shelf == 'reading'
and self.date_added and not self.date_read):
return [ReadThrough(start_date=self.date_added)]

View file

@ -1,14 +1,15 @@
''' the particulars for this instance of BookWyrm '''
import base64
from Crypto import Random
from django.db import models
from django.utils import timezone
import datetime
from bookwyrm.settings import DOMAIN
from .user import User
class SiteSettings(models.Model):
''' customized settings for this instance '''
name = models.CharField(default=DOMAIN, max_length=100)
instance_description = models.TextField(
default="This instance has no description.")
@ -18,6 +19,7 @@ class SiteSettings(models.Model):
@classmethod
def get(cls):
''' gets the site settings db entry or defaults '''
try:
return cls.objects.get(id=1)
except cls.DoesNotExist:
@ -26,9 +28,11 @@ class SiteSettings(models.Model):
return default_settings
def new_invite_code():
''' the identifier for a user invite '''
return base64.b32encode(Random.get_random_bytes(5)).decode('ascii')
class SiteInvite(models.Model):
''' gives someone access to create an account on the instance '''
code = models.CharField(max_length=32, default=new_invite_code)
expiry = models.DateTimeField(blank=True, null=True)
use_limit = models.IntegerField(blank=True, null=True)
@ -36,10 +40,12 @@ class SiteInvite(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
def valid(self):
''' make sure it hasn't expired or been used '''
return (
(self.expiry is None or self.expiry > timezone.now()) and
(self.use_limit is None or self.times_used < self.use_limit))
@property
def link(self):
''' formats the invite link '''
return "https://{}/invite/{}".format(DOMAIN, self.code)

View file

@ -2,11 +2,11 @@
from html.parser import HTMLParser
class InputHtmlParser(HTMLParser):
''' Removes any html that isn't whitelisted from a block '''
''' Removes any html that isn't allowed_tagsed from a block '''
def __init__(self):
HTMLParser.__init__(self)
self.whitelist = ['p', 'b', 'i', 'pre', 'a', 'span']
self.allowed_tags = ['p', 'b', 'i', 'pre', 'a', 'span']
self.tag_stack = []
self.output = []
# if the html appears invalid, we just won't allow any at all
@ -15,7 +15,7 @@ class InputHtmlParser(HTMLParser):
def handle_starttag(self, tag, attrs):
''' check if the tag is valid '''
if self.allow_html and tag in self.whitelist:
if self.allow_html and tag in self.allowed_tags:
self.output.append(('tag', self.get_starttag_text()))
self.tag_stack.append(tag)
else:
@ -24,7 +24,7 @@ class InputHtmlParser(HTMLParser):
def handle_endtag(self, tag):
''' keep the close tag '''
if not self.allow_html or tag not in self.whitelist:
if not self.allow_html or tag not in self.allowed_tags:
self.output.append(('data', ''))
return

View file

@ -31,7 +31,7 @@ OL_URL = env('OL_URL')
# Application definition
INSTALLED_APPS = [
#'django.contrib.admin',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',

View file

@ -1,3 +1,4 @@
''' signs activitypub activities '''
import hashlib
from urllib.parse import urlparse
import datetime
@ -11,6 +12,7 @@ from Crypto.Hash import SHA256
MAX_SIGNATURE_AGE = 300
def create_key_pair():
''' a new public/private key pair, used for creating new users '''
random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
private_key = key.export_key().decode('utf8')
@ -20,6 +22,7 @@ def create_key_pair():
def make_signature(sender, destination, date, digest):
''' uses a private key to sign an outgoing message '''
inbox_parts = urlparse(destination)
signature_headers = [
'(request-target): post %s' % inbox_parts.path,
@ -38,10 +41,15 @@ def make_signature(sender, destination, date, digest):
}
return ','.join('%s="%s"' % (k, v) for (k, v) in signature.items())
def make_digest(data):
return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8')).digest()).decode('utf-8')
''' creates a message digest for signing '''
return 'SHA-256=' + b64encode(hashlib.sha256(data.encode('utf-8'))\
.digest()).decode('utf-8')
def verify_digest(request):
''' checks if a digest is syntactically valid and matches the message '''
algorithm, digest = request.headers['digest'].split('=', 1)
if algorithm == 'SHA-256':
hash_function = hashlib.sha256
@ -55,6 +63,7 @@ def verify_digest(request):
raise ValueError("Invalid HTTP Digest header")
class Signature:
''' read and validate incoming signatures '''
def __init__(self, key_id, headers, signature):
self.key_id = key_id
self.headers = headers
@ -62,6 +71,7 @@ class Signature:
@classmethod
def parse(cls, request):
''' extract and parse a signature from an http request '''
signature_dict = {}
for pair in request.headers['Signature'].split(','):
k, v = pair.split('=', 1)
@ -105,7 +115,9 @@ class Signature:
# raises a ValueError if it fails
signer.verify(digest, self.signature)
def http_date_age(datestr):
''' age of a signature in seconds '''
parsed = datetime.datetime.strptime(datestr, '%a, %d %b %Y %H:%M:%S GMT')
delta = datetime.datetime.utcnow() - parsed
return delta.total_seconds()

View file

@ -122,7 +122,7 @@ def get_boosted(boost):
def get_edition_info(book):
''' paperback, French language, 1982 '''
if not book:
return
return ''
items = [
book.physical_format if isinstance(book, models.Edition) else None,
book.languages[0] + ' language' if book.languages and \
@ -184,6 +184,7 @@ def current_shelf(context, book):
@register.simple_tag(takes_context=False)
def latest_read_through(book, user):
''' the most recent read activity '''
return models.ReadThrough.objects.filter(
user=user,
book=book).order_by('-created_date').first()

View file

@ -7,7 +7,7 @@ from bookwyrm.connectors.abstract_connector import Mapping,\
from bookwyrm.connectors.bookwyrm_connector import Connector
class BookWyrmConnector(TestCase):
class AbstractConnector(TestCase):
def setUp(self):
self.book = models.Edition.objects.create(title='Example Edition')

View file

@ -1,7 +1,6 @@
from django.test import TestCase
from bookwyrm import models, broadcast
from bookwyrm.settings import DOMAIN
class Book(TestCase):

View file

@ -1,6 +1,6 @@
''' url routing for the app and api '''
from django.conf.urls.static import static
#from django.contrib import admin
from django.contrib import admin
from django.urls import path, re_path
from bookwyrm import incoming, outgoing, views, settings, wellknown
@ -20,7 +20,7 @@ book_path = r'^book/(?P<book_id>\d+)'
handler404 = 'bookwyrm.views.not_found_page'
handler500 = 'bookwyrm.views.server_error_page'
urlpatterns = [
# path('admin/', admin.site.urls),
path('admin/', admin.site.urls),
# federation endpoints
re_path(r'^inbox/?$', incoming.shared_inbox),
@ -66,15 +66,18 @@ urlpatterns = [
# books
re_path(r'%s(.json)?/?$' % book_path, views.book_page),
re_path(r'%s/(?P<tab>friends|local|federated)?$' % book_path, views.book_page),
re_path(r'%s/(?P<tab>friends|local|federated)?$' % \
book_path, views.book_page),
re_path(r'%s/edit/?$' % book_path, views.edit_book_page),
re_path(r'^editions/(?P<work_id>\d+)/?$', views.editions_page),
re_path(r'^author/(?P<author_id>[\w\-]+)(.json)?/?$', views.author_page),
# TODO: tag needs a .json path
re_path(r'^tag/(?P<tag_id>.+)/?$', views.tag_page),
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % user_path, views.shelf_page),
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % local_user_path, views.shelf_page),
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \
user_path, views.shelf_page),
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \
local_user_path, views.shelf_page),
re_path(r'^search/?$', views.search),

View file

@ -435,6 +435,7 @@ def import_data(request):
@login_required
def create_invite(request):
''' creates a user invite database entry '''
form = forms.CreateInviteForm(request.POST)
if not form.is_valid():
return HttpResponseBadRequest("ERRORS : %s" % (form.errors,))

View file

@ -6,5 +6,3 @@ from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ('celery_app',)

View file

@ -1,3 +1,4 @@
''' configures celery for task management '''
from __future__ import absolute_import, unicode_literals
from . import settings
@ -22,4 +23,3 @@ app.autodiscover_tasks(['bookwyrm'], related_name='incoming')
app.autodiscover_tasks(['bookwyrm'], related_name='broadcast')
app.autodiscover_tasks(['bookwyrm'], related_name='books_manager')
app.autodiscover_tasks(['bookwyrm'], related_name='goodreads_import')

2
fr-dev
View file

@ -45,6 +45,6 @@ case "$1" in
docker-compose exec web coverage report
;;
*)
echo "Unrecognised command. Try: up, initdb, resetdb,makemigrations, migrate, shell, dbshell, restart_celery, test, test_report"
echo "Unrecognised command. Try: up, initdb, resetdb, makemigrations, migrate, shell, dbshell, restart_celery, test, test_report"
;;
esac