mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-04 15:26:48 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
2d96c8a35a
16 changed files with 192 additions and 139 deletions
2
.coveragerc
Normal file
2
.coveragerc
Normal file
|
@ -0,0 +1,2 @@
|
|||
[run]
|
||||
omit = */test*,celerywyrm*,bookwyrm/migrations/*
|
7
.dockerignore
Normal file
7
.dockerignore
Normal file
|
@ -0,0 +1,7 @@
|
|||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.git
|
||||
.github
|
||||
.pytest*
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -13,3 +13,6 @@
|
|||
# BookWyrm
|
||||
.env
|
||||
/images/
|
||||
|
||||
# Testing
|
||||
.coverage
|
|
@ -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
|
||||
|
|
100
bookwyrm/management/commands/initdb.py
Normal file
100
bookwyrm/management/commands/initdb.py
Normal 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()
|
|
@ -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');
|
||||
|
||||
if (nested) {
|
||||
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', '');
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
44
fr-dev
|
@ -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
|
||||
|
|
91
init_db.py
91
init_db.py
|
@ -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
6
pytest.ini
Normal 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"')
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue