Skip to content

Commit

Permalink
Merge branch 'main' into production
Browse files Browse the repository at this point in the history
  • Loading branch information
mouse-reeve committed Jul 15, 2022
2 parents cf59591 + 086ec10 commit 99b64ae
Show file tree
Hide file tree
Showing 59 changed files with 1,934 additions and 1,267 deletions.
71 changes: 28 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,45 @@
# BookWyrm

Social reading and reviewing, decentralized with ActivityPub
[![](https://img.shields.io/github/release/bookwyrm-social/bookwyrm.svg?colorB=58839b)](https://github.com/bookwyrm-social/bookwyrm/releases)
[![Run Python Tests](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/django-tests.yml/badge.svg)](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/django-tests.yml)
[![Pylint](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/pylint.yml/badge.svg)](https://github.com/bookwyrm-social/bookwyrm/actions/workflows/pylint.yml)

## Contents
- [Joining BookWyrm](#joining-bookwyrm)
- [Contributing](#contributing)
- [About BookWyrm](#about-bookwyrm)
- [What it is and isn't](#what-it-is-and-isnt)
- [The role of federation](#the-role-of-federation)
- [Features](#features)
- [Set up BookWyrm](#set-up-bookwyrm)
BookWyrm is a social network for tracking your reading, talking about books, writing reviews, and discovering what to read next. Federation allows BookWyrm users to join small, trusted communities that can connect with one another, and with other ActivityPub services like [Mastodon](https://joinmastodon.org/) and [Pleroma](http://pleroma.social/).

## Joining BookWyrm
If you'd like to join an instance, you can check out the [instances](https://joinbookwyrm.com/instances/) list.

## Links

[![Mastodon Follow](https://img.shields.io/mastodon/follow/000146121?domain=https%3A%2F%2Ftech.lgbt&style=social)](https://tech.lgbt/@bookwyrm)
[![Twitter Follow](https://img.shields.io/twitter/follow/BookWyrmSocial?style=social)](https://twitter.com/BookWyrmSocial)

- [Project homepage](https://joinbookwyrm.com/)
- [Support](https://patreon.com/bookwyrm)
- [Documentation](https://docs.joinbookwyrm.com/)

## Contributing
See [contributing](https://docs.joinbookwyrm.com/contributing.html) for code, translation or monetary contributions.

## About BookWyrm
### What it is and isn't
BookWyrm is a platform for social reading. You can use it to track what you're reading, review books, and follow your friends. It isn't primarily meant for cataloguing or as a data-source for books, but it does do both of those things to some degree.

### The role of federation
## Federation
BookWyrm is built on [ActivityPub](http://activitypub.rocks/). With ActivityPub, it inter-operates with different instances of BookWyrm, and other ActivityPub compliant services, like Mastodon. This means you can run an instance for your book club, and still follow your friend who posts on a server devoted to 20th century Russian speculative fiction. It also means that your friend on mastodon can read and comment on a book review that you post on your BookWyrm instance.

Federation makes it possible to have small, self-determining communities, in contrast to the monolithic service you find on GoodReads or Twitter. An instance can be focused on a particular interest, be just for a group of friends, or anything else that brings people together. Each community can choose which other instances they want to federate with, and moderate and run their community autonomously. Check out https://runyourown.social/ to get a sense of the philosophy and logistics behind small, high-trust social networks.

### Features
Since the project is still in its early stages, the features are growing every day, and there is plenty of room for suggestions and ideas. Open an [issue](https://github.com/bookwyrm-social/bookwyrm/issues) to get the conversation going!
- Posting about books
- Compose reviews, with or without ratings, which are aggregated in the book page
- Compose other kinds of statuses about books, such as:
- Comments on a book
- Quotes or excerpts
- Reply to statuses
- View aggregate reviews of a book across connected BookWyrm instances
- Differentiate local and federated reviews and rating in your activity feed
- Track reading activity
- Shelve books on default "to-read," "currently reading," and "read" shelves
- Create custom shelves
- Store started reading/finished reading dates, as well as progress updates along the way
- Update followers about reading activity (optionally, and with granular privacy controls)
- Create lists of books which can be open to submissions from anyone, curated, or only edited by the creator
- Federation with ActivityPub
- Broadcast and receive user statuses and activity
- Share book data between instances to create a networked database of metadata
- Identify shared books across instances and aggregate related content
- Follow and interact with users across BookWyrm instances
- Inter-operate with non-BookWyrm ActivityPub services (currently, Mastodon is supported)
- Granular privacy controls
- Private, followers-only, and public privacy levels for posting, shelves, and lists
- Option for users to manually approve followers
- Allow blocking and flagging for moderation

### The Tech Stack
## Features

### Post about books
Compose reviews, comment on what you're reading, and post quotes from books. You can converse with other BookWyrm users across the network about what they're reading.

### Track reading activity
Keep track of what books you've read, and what books you'd like to read in the future.

### Federation with ActivityPub
Federation allows you to interact with users on other instances and services, and also shares metadata about books and authors, which collaboratively builds a decentralized database of books.

### Privacy and moderation
Users and administrators can control who can see thier posts and what other instances to federate with.

## Tech Stack
Web backend
- [Django](https://www.djangoproject.com/) web server
- [PostgreSQL](https://www.postgresql.org/) database
Expand Down
2 changes: 1 addition & 1 deletion bookwyrm/connectors/connector_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async def get_results(session, url, min_confidence, query, connector):
except asyncio.TimeoutError:
logger.info("Connection timed out for url: %s", url)
except aiohttp.ClientError as err:
logger.exception(err)
logger.info(err)


async def async_connector_search(query, items, min_confidence):
Expand Down
33 changes: 33 additions & 0 deletions bookwyrm/forms/edit_user.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
""" using django model forms """
from django import forms
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from bookwyrm import models
from bookwyrm.models.fields import ClearableFileInputWithWarning
Expand Down Expand Up @@ -66,3 +69,33 @@ class DeleteUserForm(CustomForm):
class Meta:
model = models.User
fields = ["password"]


class ChangePasswordForm(CustomForm):
current_password = forms.CharField(widget=forms.PasswordInput)
confirm_password = forms.CharField(widget=forms.PasswordInput)

class Meta:
model = models.User
fields = ["password"]
widgets = {
"password": forms.PasswordInput(),
}

def clean(self):
"""Make sure passwords match and are valid"""
current_password = self.data.get("current_password")
if not self.instance.check_password(current_password):
self.add_error("current_password", _("Incorrect password"))

cleaned_data = super().clean()
new_password = cleaned_data.get("password")
confirm_password = self.data.get("confirm_password")

if new_password != confirm_password:
self.add_error("confirm_password", _("Password does not match"))

try:
validate_password(new_password)
except ValidationError as err:
self.add_error("password", err)
37 changes: 34 additions & 3 deletions bookwyrm/forms/landing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
""" Forms for the landing pages """
from django.forms import PasswordInput
from django import forms
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from bookwyrm import models
Expand All @@ -13,7 +15,7 @@ class Meta:
fields = ["localname", "password"]
help_texts = {f: None for f in fields}
widgets = {
"password": PasswordInput(),
"password": forms.PasswordInput(),
}


Expand All @@ -22,12 +24,16 @@ class Meta:
model = models.User
fields = ["localname", "email", "password"]
help_texts = {f: None for f in fields}
widgets = {"password": PasswordInput()}
widgets = {"password": forms.PasswordInput()}

def clean(self):
"""Check if the username is taken"""
cleaned_data = super().clean()
localname = cleaned_data.get("localname").strip()
try:
validate_password(cleaned_data.get("password"))
except ValidationError as err:
self.add_error("password", err)
if models.User.objects.filter(localname=localname).first():
self.add_error("localname", _("User with this username already exists"))

Expand All @@ -43,3 +49,28 @@ def clean(self):
class Meta:
model = models.InviteRequest
fields = ["email", "answer"]


class PasswordResetForm(CustomForm):
confirm_password = forms.CharField(widget=forms.PasswordInput)

class Meta:
model = models.User
fields = ["password"]
widgets = {
"password": forms.PasswordInput(),
}

def clean(self):
"""Make sure the passwords match and are valid"""
cleaned_data = super().clean()
new_password = cleaned_data.get("password")
confirm_password = self.data.get("confirm_password")

if new_password != confirm_password:
self.add_error("confirm_password", _("Password does not match"))

try:
validate_password(new_password)
except ValidationError as err:
self.add_error("password", err)
2 changes: 2 additions & 0 deletions bookwyrm/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,10 @@ def notify_user_on_follow(sender, instance, created, *args, **kwargs):
notification.read = False
notification.save()
else:
# Only group unread follows
Notification.notify(
instance.user_object,
instance.user_subject,
notification_type=Notification.FOLLOW,
read=False,
)
8 changes: 0 additions & 8 deletions bookwyrm/templates/confirm_email/resend_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,8 @@
name="email"
class="input"
id="email"
aria-described-by="id_email_errors"
required
>
{% if error %}
<div id="id_email_errors">
<p class="help is-danger">
{% trans "No user matching this email address found." %}
</p>
</div>
{% endif %}
</div>
</div>
{% endblock %}
Expand Down
14 changes: 12 additions & 2 deletions bookwyrm/templates/landing/password_reset.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,25 @@ <h1 class="title">{% trans "Reset Password" %}</h1>
{% trans "Password:" %}
</label>
<div class="control">
<input type="password" name="password" maxlength="128" class="input" required="" id="id_new_password" aria-describedby="form_errors">
<input
type="password"
name="password"
maxlength="128"
class="input"
required=""
id="id_new_password"
aria-describedby="desc_password"
>
{% include 'snippets/form_errors.html' with errors_list=form.password.errors id="desc_password" %}
</div>
</div>
<div class="field">
<label class="label" for="id_confirm_password">
{% trans "Confirm password:" %}
</label>
<div class="control">
<input type="password" name="confirm-password" maxlength="128" class="input" required="" id="id_confirm_password" aria-describedby="form_errors">
{{ form.confirm_password }}
{% include 'snippets/form_errors.html' with errors_list=form.confirm_password.errors id="desc_confirm_password" %}
</div>
</div>
<div class="field is-grouped">
Expand Down
11 changes: 5 additions & 6 deletions bookwyrm/templates/notifications/items/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% load humanize %}
{% related_status notification as related_status %}

{% with related_users=notification.related_users.all.distinct %}
{% get_related_users notification as related_users %}
{% with related_user_count=notification.related_users.count %}
<div class="notification {% if notification.id in unread %}has-background-primary{% endif %}">
<div class="columns is-mobile {% if notification.id in unread %}has-text-white{% else %}has-text-more-muted{% endif %}">
Expand All @@ -16,7 +16,7 @@
{% if related_user_count > 1 %}
<div class="block">
<ul class="is-flex">
{% for user in related_users|slice:10 %}
{% for user in related_users %}
<li class="mr-2">
<a href="{{ user.local_path }}">
{% include 'snippets/avatar.html' with user=user %}
Expand All @@ -28,7 +28,7 @@
{% endif %}
<div class="block content">
{% if related_user_count == 1 %}
{% with user=related_users.first %}
{% with user=related_users.0 %}
{% spaceless %}
<a href="{{ user.local_path }}" class="mr-2">
{% include 'snippets/avatar.html' with user=user %}
Expand All @@ -37,8 +37,8 @@
{% endwith %}
{% endif %}

{% with related_user=related_users.first.display_name %}
{% with related_user_link=related_users.first.local_path %}
{% with related_user=related_users.0.display_name %}
{% with related_user_link=related_users.0.local_path %}
{% with second_user=related_users.1.display_name %}
{% with second_user_link=related_users.1.local_path %}
{% with other_user_count=related_user_count|add:"-1" %}
Expand All @@ -61,4 +61,3 @@
</div>
</div>
{% endwith %}
{% endwith %}
20 changes: 18 additions & 2 deletions bookwyrm/templates/preferences/change_password.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,31 @@
{% endblock %}

{% block panel %}
{% if success %}
<div class="notification is-success is-light">
<span class="icon icon-check" aria-hidden="true"></span>
<span>
{% trans "Successfully changed password" %}
</span>
</div>
{% endif %}
<form name="edit-profile" action="{% url 'prefs-password' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="field">
<label class="label" for="id_password">{% trans "Current password:" %}</label>
{{ form.current_password }}
{% include 'snippets/form_errors.html' with errors_list=form.current_password.errors id="desc_current_password" %}
</div>
<hr aria-hidden="true" />
<div class="field">
<label class="label" for="id_password">{% trans "New password:" %}</label>
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password">
{{ form.password }}
{% include 'snippets/form_errors.html' with errors_list=form.password.errors id="desc_current_password" %}
</div>
<div class="field">
<label class="label" for="id_confirm_password">{% trans "Confirm password:" %}</label>
<input type="password" name="confirm-password" maxlength="128" class="input" required="" id="id_confirm_password">
{{ form.confirm_password }}
{% include 'snippets/form_errors.html' with errors_list=form.confirm_password.errors id="desc_confirm_password" %}
</div>
<button class="button is-primary" type="submit">{% trans "Change Password" %}</button>
</form>
Expand Down
11 changes: 7 additions & 4 deletions bookwyrm/templates/preferences/export.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
{% trans "Your export will include all the books on your shelves, books you have reviewed, and books with reading activity." %}
</p>
<p>
<a href="{% url 'prefs-export-file' %}" class="button">
<span class="icon icon-download" aria-hidden="true"></span>
<span>Download file</span>
</a>
<form name="export" method="POST" href="{% url 'prefs-export' %}">
{% csrf_token %}
<button type="submit" class="button">
<span class="icon icon-download" aria-hidden="true"></span>
<span>{% trans "Download file" %}</span>
</button>
</form>
</p>
</div>
{% endblock %}
6 changes: 6 additions & 0 deletions bookwyrm/templatetags/notification_page_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ def related_status(notification):
if not notification.related_status:
return None
return load_subclass(notification.related_status)


@register.simple_tag(takes_context=False)
def get_related_users(notification):
"""Who actually was it who liked your post"""
return list(reversed(list(notification.related_users.distinct())))[:10]
Loading

0 comments on commit 99b64ae

Please sign in to comment.