Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2020-11-08 20:17:52 -08:00
commit 2d96c8a35a
16 changed files with 192 additions and 139 deletions

2
.coveragerc Normal file
View file

@ -0,0 +1,2 @@
[run]
omit = */test*,celerywyrm*,bookwyrm/migrations/*

7
.dockerignore Normal file
View file

@ -0,0 +1,7 @@
__pycache__
*.pyc
*.pyo
*.pyd
.git
.github
.pytest*

3
.gitignore vendored
View file

@ -13,3 +13,6 @@
# BookWyrm
.env
/images/
# Testing
.coverage

View file

@ -65,9 +65,8 @@ You'll have to install the Docker and docker-compose. When you're ready, run:
```bash
docker-compose build
docker-compose up
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py shell -c 'import init_db'
docker-compose run --rm web python manage.py migrate
docker-compose run --rm web python manage.py initdb
```
### Without Docker

View file

@ -0,0 +1,100 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from bookwyrm.models import Connector, User
from bookwyrm.settings import DOMAIN
def init_groups():
groups = ['admin', 'moderator', 'editor']
for group in groups:
Group.objects.create(name=group)
def init_permissions():
permissions = [{
'codename': 'edit_instance_settings',
'name': 'change the instance info',
'groups': ['admin',]
}, {
'codename': 'set_user_group',
'name': 'change what group a user is in',
'groups': ['admin', 'moderator']
}, {
'codename': 'control_federation',
'name': 'control who to federate with',
'groups': ['admin', 'moderator']
}, {
'codename': 'create_invites',
'name': 'issue invitations to join',
'groups': ['admin', 'moderator']
}, {
'codename': 'moderate_user',
'name': 'deactivate or silence a user',
'groups': ['admin', 'moderator']
}, {
'codename': 'moderate_post',
'name': 'delete other users\' posts',
'groups': ['admin', 'moderator']
}, {
'codename': 'edit_book',
'name': 'edit book info',
'groups': ['admin', 'moderator', 'editor']
}]
content_type = ContentType.objects.get_for_model(User)
for permission in permissions:
permission_obj = Permission.objects.create(
codename=permission['codename'],
name=permission['name'],
content_type=content_type,
)
# add the permission to the appropriate groups
for group_name in permission['groups']:
Group.objects.get(name=group_name).permissions.add(permission_obj)
# while the groups and permissions shouldn't be changed because the code
# depends on them, what permissions go with what groups should be editable
def init_connectors():
Connector.objects.create(
identifier=DOMAIN,
name='Local',
local=True,
connector_file='self_connector',
base_url='https://%s' % DOMAIN,
books_url='https://%s/book' % DOMAIN,
covers_url='https://%s/images/covers' % DOMAIN,
search_url='https://%s/search?q=' % DOMAIN,
priority=1,
)
Connector.objects.create(
identifier='bookwyrm.social',
name='BookWyrm dot Social',
connector_file='bookwyrm_connector',
base_url='https://bookwyrm.social' ,
books_url='https://bookwyrm.social/book',
covers_url='https://bookwyrm.social/images/covers',
search_url='https://bookwyrm.social/search?q=',
priority=2,
)
Connector.objects.create(
identifier='openlibrary.org',
name='OpenLibrary',
connector_file='openlibrary',
base_url='https://openlibrary.org',
books_url='https://openlibrary.org',
covers_url='https://covers.openlibrary.org',
search_url='https://openlibrary.org/search?q=',
priority=3,
)
class Command(BaseCommand):
help = 'Initializes the database with starter data'
def handle(self, *args, **options):
init_groups()
init_permissions()
init_connectors()

View file

@ -31,22 +31,23 @@ function rate_stars(e) {
return true;
}
function tabChange(e) {
function tabChange(e, nested) {
var target = e.target.closest('li')
var identifier = target.getAttribute('data-id');
var parent_element = target.parentElement.closest('li').parentElement;
var tabs = parent_element.getElementsByTagName('label');
for (i = 0; i < tabs.length; i++) {
var tab = tabs[i].parentElement;
if (tab.getAttribute('data-id') == identifier) {
tab.className += ' is-active';
} else {
tab.className = tab.className.replace('is-active', '');
}
if (nested) {
var parent_element = target.parentElement.closest('li').parentElement;
} else {
var parent_element = target.parentElement;
}
var el = document.getElementById(identifier);
parent_element.querySelectorAll('[aria-selected="true"]')
.forEach(t => t.setAttribute("aria-selected", false));
target.querySelector('[role="tab"]').setAttribute("aria-selected", true);
parent_element.querySelectorAll('li')
.forEach(t => t.className='');
target.className = 'is-active';
}
function ajaxPost(form) {

View file

@ -21,7 +21,13 @@
<ul>
{% for book in shelf.books %}
<li class="{% if shelf_counter == 1 and forloop.first %}is-active{% endif %}" data-id="tab-book-{{ book.id }}">
<label for="book-{{ book.id }}" onclick="tabChange(event)"><a>{% include 'snippets/book_cover.html' with book=book size="medium" %}</a></label>
<label for="book-{{ book.id }}" onclick="tabChange(event, nested=true)">
<div role="tab" tabindex="0" aria-selected="{% if shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}">
<a>
{% include 'snippets/book_cover.html' with book=book size="medium" %}
</a>
</div>
</label>
</li>
{% endfor %}
</ul>

View file

@ -2,15 +2,27 @@
{% load fr_display %}
<div class="tabs is-boxed">
<ul>
<ul role="tablist">
<li class="is-active" data-id="tab-review-{{ book.id }}" data-category="tab-option-{{ book.id }}">
<label for="review-{{ book.id }}" onclick="tabChange(event)"><a>Review</a></label>
<label for="review-{{ book.id }}">
<div onclick="tabChange(event)" role="tab" aria-selected="true" tabindex="0">
<a>Review</a>
</div>
</label>
</li>
<li data-id="tab-comment-{{ book.id }}" data-category="tab-option-{{ book.id }}">
<label for="comment-{{ book.id}}" onclick="tabChange(event)"><a>Comment</a></label>
<label for="comment-{{ book.id}}">
<div onclick="tabChange(event)" role="tab" tabindex="0">
<a>Comment</a>
</div>
</label>
</li>
<li data-id="tab-quotation-{{ book.id }}" data-category="tab-option-{{ book.id }}">
<label for="quote-{{ book.id }}" onclick="tabChange(event)"><a>Quote</a></label>
<label for="quote-{{ book.id }}">
<div onclick="tabChange(event)" role="tab" tabindex="0">
<a>Quote</a>
</div>
</label>
</li>
</ul>
</div>
@ -22,7 +34,7 @@
<div>
<input class="toggle-control" type="radio" name="status-tabs-{{ book.id }}" id="comment-{{ book.id }}">
{% include 'snippets/create_status_form.html' with type="comment" placeholder="Some thougts on '"|add:book.title|add:"'" %}
{% include 'snippets/create_status_form.html' with type="comment" placeholder="Some thoughts on '"|add:book.title|add:"'" %}
</div>
<div>

View file

@ -38,14 +38,6 @@
<ul class="dropdown-content">
{% for shelf in request.user.shelf_set.all %}
<li>
{% if shelf.identifier == 'reading' and active_shelf.identifier != 'reading' %}
<div class="dropdown-item pt-0 pb-0">
<label class="button is-small" for="start-reading-{{ uuid }}" role="button" tabindex="0">
{{ shelf.name }}
</label>
{% include 'snippets/start_reading_modal.html' %}
</div>
{% else %}
<form class="dropdown-item pt-0 pb-0" name="shelve" action="/shelve/" method="post">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
@ -54,7 +46,6 @@
{% if shelf in book.shelf_set.all %}<span class="icon icon-check"></span>{% endif %}
</button>
</form>
{% endif %}
</li>
{% endfor %}
</ul>

View file

@ -6,6 +6,8 @@ import pathlib
import json
import responses
import pytest
from django.test import TestCase, Client
from django.utils.http import http_date
@ -167,6 +169,7 @@ class Signature(TestCase):
response = self.send_test_request(sender=self.fake_remote)
self.assertEqual(response.status_code, 401)
@pytest.mark.integration
def test_changed_data(self):
'''Message data must match the digest header.'''
response = self.send_test_request(
@ -174,12 +177,14 @@ class Signature(TestCase):
send_data=get_follow_data(self.mouse, self.cat))
self.assertEqual(response.status_code, 401)
@pytest.mark.integration
def test_invalid_digest(self):
response = self.send_test_request(
self.mouse,
digest='SHA-256=AAAAAAAAAAAAAAAAAA')
self.assertEqual(response.status_code, 401)
@pytest.mark.integration
def test_old_message(self):
'''Old messages should be rejected to prevent replay attacks.'''
response = self.send_test_request(

View file

@ -33,6 +33,7 @@ services:
- main
web:
build: .
env_file: .env
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app

44
fr-dev
View file

@ -3,41 +3,49 @@
set -e
set -x
function execdb {
docker-compose exec db $@
}
function execweb {
docker-compose exec web "$@"
}
function initdb {
execweb python manage.py migrate
execweb python manage.py initdb
}
case "$1" in
up)
docker-compose up --build
;;
run)
docker-compose run --service-ports web
;;
initdb)
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py shell -c 'import init_db'
initdb
;;
makemigrations)
docker-compose exec web python manage.py makemigrations
execweb python manage.py makemigrations
;;
migrate)
docker-compose exec web python manage.py migrate
execweb python manage.py migrate
;;
bash)
execweb bash
;;
shell)
docker-compose exec web python manage.py shell
execweb python manage.py shell
;;
dbshell)
docker-compose exec db psql -U fedireads fedireads
execdb psql -U fedireads fedireads
;;
restart_celery)
docker-compose restart celery_worker
;;
test)
shift 1
docker-compose exec web coverage run --source='.' --omit="*/test*,celerywyrm*,bookwyrm/migrations/*" manage.py test "$@"
;;
test_report)
docker-compose exec web coverage report
;;
collectstatic)
docker-compose exec web python manage.py collectstatic --no-input
execweb python manage.py collectstatic --no-input
;;
build)
docker-compose build
;;
update)
git pull
@ -46,6 +54,6 @@ case "$1" in
docker-compose restart
;;
*)
echo "Unrecognised command. Try: up, initdb, makemigrations, migrate, shell, dbshell, restart_celery, test, test_report, update"
echo "Unrecognised command. Try: build, up, initdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, update"
;;
esac

View file

@ -1,91 +0,0 @@
''' starter data '''
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from bookwyrm.models import Connector, User
from bookwyrm.settings import DOMAIN
groups = ['admin', 'moderator', 'editor']
for group in groups:
Group.objects.create(name=group)
permissions = [{
'codename': 'edit_instance_settings',
'name': 'change the instance info',
'groups': ['admin',]
}, {
'codename': 'set_user_group',
'name': 'change what group a user is in',
'groups': ['admin', 'moderator']
}, {
'codename': 'control_federation',
'name': 'control who to federate with',
'groups': ['admin', 'moderator']
}, {
'codename': 'create_invites',
'name': 'issue invitations to join',
'groups': ['admin', 'moderator']
}, {
'codename': 'moderate_user',
'name': 'deactivate or silence a user',
'groups': ['admin', 'moderator']
}, {
'codename': 'moderate_post',
'name': 'delete other users\' posts',
'groups': ['admin', 'moderator']
}, {
'codename': 'edit_book',
'name': 'edit book info',
'groups': ['admin', 'moderator', 'editor']
}]
content_type = ContentType.objects.get_for_model(User)
for permission in permissions:
permission_obj = Permission.objects.create(
codename=permission['codename'],
name=permission['name'],
content_type=content_type,
)
# add the permission to the appropriate groups
for group_name in permission['groups']:
Group.objects.get(name=group_name).permissions.add(permission_obj)
# while the groups and permissions shouldn't be changed because the code
# depends on them, what permissions go with what groups should be editable
Connector.objects.create(
identifier=DOMAIN,
name='Local',
local=True,
connector_file='self_connector',
base_url='https://%s' % DOMAIN,
books_url='https://%s/book' % DOMAIN,
covers_url='https://%s/images/covers' % DOMAIN,
search_url='https://%s/search?q=' % DOMAIN,
priority=1,
)
Connector.objects.create(
identifier='bookwyrm.social',
name='BookWyrm dot Social',
connector_file='bookwyrm_connector',
base_url='https://bookwyrm.social' ,
books_url='https://bookwyrm.social/book',
covers_url='https://bookwyrm.social/images/covers',
search_url='https://bookwyrm.social/search?q=',
priority=2,
)
Connector.objects.create(
identifier='openlibrary.org',
name='OpenLibrary',
connector_file='openlibrary',
base_url='https://openlibrary.org',
books_url='https://openlibrary.org',
covers_url='https://covers.openlibrary.org',
search_url='https://openlibrary.org/search?q=',
priority=3,
)

6
pytest.ini Normal file
View file

@ -0,0 +1,6 @@
[pytest]
DJANGO_SETTINGS_MODULE = bookwyrm.settings
python_files = tests.py test_*.py *_tests.py
addopts = --cov=bookwyrm --cov-config=.coveragerc
markers =
integration: marks tests as requiring external resources (deselect with '-m "not integration"')

View file

@ -21,5 +21,5 @@ fi
python manage.py makemigrations fedireads
python manage.py migrate
python manage.py shell < init_db.py
python manage.py initdb
python manage.py runserver

View file

@ -7,6 +7,9 @@ flower==0.9.4
Pillow>=7.1.0
psycopg2==2.8.4
pycryptodome==3.9.4
pytest-django==4.1.0
pytest==6.1.2
pytest-cov==2.10.1
python-dateutil==2.8.1
redis==3.4.1
requests==2.22.0