mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-10-31 22:19:00 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
0f49436475
32 changed files with 426 additions and 216 deletions
|
@ -35,7 +35,7 @@ class CustomForm(ModelForm):
|
|||
class LoginForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ['username', 'password']
|
||||
fields = ['localname', 'password']
|
||||
help_texts = {f: None for f in fields}
|
||||
widgets = {
|
||||
'password': PasswordInput(),
|
||||
|
@ -45,7 +45,7 @@ class LoginForm(CustomForm):
|
|||
class RegisterForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ['username', 'email', 'password']
|
||||
fields = ['localname', 'email', 'password']
|
||||
help_texts = {f: None for f in fields}
|
||||
widgets = {
|
||||
'password': PasswordInput()
|
||||
|
|
19
bookwyrm/migrations/0030_auto_20201224_1939.py
Normal file
19
bookwyrm/migrations/0030_auto_20201224_1939.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.0.7 on 2020-12-24 19:39
|
||||
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bookwyrm', '0029_auto_20201221_2014'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='localname',
|
||||
field=models.CharField(max_length=255, null=True, unique=True, validators=[bookwyrm.models.fields.validate_localname]),
|
||||
),
|
||||
]
|
28
bookwyrm/migrations/0031_auto_20210104_2040.py
Normal file
28
bookwyrm/migrations/0031_auto_20210104_2040.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 3.0.7 on 2021-01-04 20:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bookwyrm', '0030_auto_20201224_1939'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sitesettings',
|
||||
name='favicon',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='logos/'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sitesettings',
|
||||
name='logo',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='logos/'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sitesettings',
|
||||
name='logo_small',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='logos/'),
|
||||
),
|
||||
]
|
23
bookwyrm/migrations/0032_auto_20210104_2055.py
Normal file
23
bookwyrm/migrations/0032_auto_20210104_2055.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.0.7 on 2021-01-04 20:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bookwyrm', '0031_auto_20210104_2040'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sitesettings',
|
||||
name='instance_tagline',
|
||||
field=models.CharField(default='Social Reading and Reviewing', max_length=150),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sitesettings',
|
||||
name='registration_closed_text',
|
||||
field=models.TextField(default='Contact an administrator to get an invite'),
|
||||
),
|
||||
]
|
|
@ -26,11 +26,20 @@ def validate_remote_id(value):
|
|||
)
|
||||
|
||||
|
||||
def validate_localname(value):
|
||||
''' make sure localnames look okay '''
|
||||
if not re.match(r'^[A-Za-z\-_\.0-9]+$', value):
|
||||
raise ValidationError(
|
||||
_('%(value)s is not a valid username'),
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
|
||||
def validate_username(value):
|
||||
''' make sure usernames look okay '''
|
||||
if not re.match(r'^[A-Za-z\-_\.]+$', value):
|
||||
if not re.match(r'^[A-Za-z\-_\.0-9]+@[A-Za-z\-_\.0-9]+\.[a-z]{2,}$', value):
|
||||
raise ValidationError(
|
||||
_('%(value)s is not a valid remote_id'),
|
||||
_('%(value)s is not a valid username'),
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
|
@ -147,7 +156,7 @@ class RemoteIdField(ActivitypubFieldMixin, models.CharField):
|
|||
|
||||
class UsernameField(ActivitypubFieldMixin, models.CharField):
|
||||
''' activitypub-aware username field '''
|
||||
def __init__(self, activitypub_field='preferredUsername'):
|
||||
def __init__(self, activitypub_field='preferredUsername', **kwargs):
|
||||
self.activitypub_field = activitypub_field
|
||||
# I don't totally know why pylint is mad at this, but it makes it work
|
||||
super( #pylint: disable=bad-super-call
|
||||
|
|
|
@ -12,11 +12,24 @@ from .user import User
|
|||
class SiteSettings(models.Model):
|
||||
''' customized settings for this instance '''
|
||||
name = models.CharField(default='BookWyrm', max_length=100)
|
||||
instance_tagline = models.CharField(
|
||||
max_length=150, default='Social Reading and Reviewing')
|
||||
instance_description = models.TextField(
|
||||
default="This instance has no description.")
|
||||
default='This instance has no description.')
|
||||
registration_closed_text = models.TextField(
|
||||
default='Contact an administrator to get an invite')
|
||||
code_of_conduct = models.TextField(
|
||||
default="Add a code of conduct here.")
|
||||
default='Add a code of conduct here.')
|
||||
allow_registration = models.BooleanField(default=True)
|
||||
logo = models.ImageField(
|
||||
upload_to='logos/', null=True, blank=True
|
||||
)
|
||||
logo_small = models.ImageField(
|
||||
upload_to='logos/', null=True, blank=True
|
||||
)
|
||||
favicon = models.ImageField(
|
||||
upload_to='logos/', null=True, blank=True
|
||||
)
|
||||
support_link = models.CharField(max_length=255, null=True, blank=True)
|
||||
support_title = models.CharField(max_length=100, null=True, blank=True)
|
||||
admin_email = models.EmailField(max_length=255, null=True, blank=True)
|
||||
|
@ -52,7 +65,7 @@ class SiteInvite(models.Model):
|
|||
@property
|
||||
def link(self):
|
||||
''' formats the invite link '''
|
||||
return "https://{}/invite/{}".format(DOMAIN, self.code)
|
||||
return 'https://{}/invite/{}'.format(DOMAIN, self.code)
|
||||
|
||||
|
||||
def get_passowrd_reset_expiry():
|
||||
|
@ -74,4 +87,4 @@ class PasswordReset(models.Model):
|
|||
@property
|
||||
def link(self):
|
||||
''' formats the invite link '''
|
||||
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
|
||||
return 'https://{}/password-reset/{}'.format(DOMAIN, self.code)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
''' database schema for user data '''
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.apps import apps
|
||||
|
@ -13,6 +14,7 @@ from bookwyrm.models.status import Status, Review
|
|||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.signatures import create_key_pair
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.utils import regex
|
||||
from .base_model import OrderedCollectionPageMixin
|
||||
from .base_model import ActivitypubMixin, BookWyrmModel
|
||||
from .federated_server import FederatedServer
|
||||
|
@ -49,7 +51,8 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
localname = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
unique=True
|
||||
unique=True,
|
||||
validators=[fields.validate_localname],
|
||||
)
|
||||
# name is your display name, which you can change at will
|
||||
name = fields.CharField(max_length=100, null=True, blank=True)
|
||||
|
@ -167,20 +170,17 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
def save(self, *args, **kwargs):
|
||||
''' populate fields for new local users '''
|
||||
# this user already exists, no need to populate fields
|
||||
if self.id:
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
if not self.local:
|
||||
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)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
if self.id or not self.local:
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
# populate fields for local users
|
||||
self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.username)
|
||||
self.localname = self.username
|
||||
self.username = '%s@%s' % (self.username, DOMAIN)
|
||||
self.actor = 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
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
<h1 class="title">
|
||||
{{ book.title }}{% if book.subtitle %}:
|
||||
<small>{{ book.subtitle }}</small>{% endif %}
|
||||
{% if book.series %}
|
||||
<small class="has-text-grey-dark">({{ book.series }}{% if book.series_number %} #{{ book.series_number }}{% endif %})</small><br>
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% if book.authors %}
|
||||
<h2 class="subtitle">
|
||||
|
@ -49,19 +52,44 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<dl class="content">
|
||||
{% for field in info_fields %}
|
||||
{% if field.value %}
|
||||
<dt>{{ field.name }}:</dt>
|
||||
<dd>{{ field.value }}</dd>
|
||||
<section class="content">
|
||||
<dl>
|
||||
{% if book.isbn_13 %}
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<dt>ISBN:</dt>
|
||||
<dd>{{ book.isbn_13 }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if book.oclc_number %}
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<dt>OCLC Number:</dt>
|
||||
<dd>{{ book.oclc_number }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if book.asin %}
|
||||
<div class="is-flex is-justify-content-space-between">
|
||||
<dt>ASIN:</dt>
|
||||
<dd>{{ book.asin }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<p>
|
||||
{% if book.physical_format %}{{ book.physical_format | title }}{% if book.pages %},<br>{% endif %}{% endif %}
|
||||
{% if book.pages %}{{ book.pages }} pages{% endif %}
|
||||
</p>
|
||||
|
||||
{% if book.openlibrary_key %}
|
||||
<p><a href="https://openlibrary.org/books/{{ book.openlibrary_key }}" target="_blank" rel="noopener">View on OpenLibrary</a></p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="block">
|
||||
<h3 class="field is-grouped">{% include 'snippets/stars.html' with rating=rating %} ({{ reviews|length }} review{{ reviews|length|pluralize }})</h3>
|
||||
<h3 class="field is-grouped">{% include 'snippets/stars.html' with rating=rating %} ({{ review_count }} review{{ reviews|length|pluralize }})</h3>
|
||||
|
||||
{% include 'snippets/trimmed_text.html' with full=book|book_description %}
|
||||
|
||||
|
@ -145,16 +173,32 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
{% if book.subjects %}
|
||||
<section class="content block">
|
||||
<h2 class="title is-5">Subjects</h2>
|
||||
<ul>
|
||||
{% for subject in book.subjects %}
|
||||
<li>{{ subject }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if book.subject_places %}
|
||||
<section class="content block">
|
||||
<h2 class="title is-5">Places</h2>
|
||||
<ul>
|
||||
{% for place in book.subject_placess %}
|
||||
<li>{{ place }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if not reviews %}
|
||||
<div class="block">
|
||||
<p>No reviews yet!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="block">
|
||||
{% for review in reviews %}
|
||||
<div class="block">
|
||||
|
@ -162,9 +206,9 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="block columns">
|
||||
<div class="block is-flex is-flex-wrap-wrap">
|
||||
{% for rating in ratings %}
|
||||
<div class="column">
|
||||
<div class="block mr-5">
|
||||
<div class="media">
|
||||
<div class="media-left">{% include 'snippets/avatar.html' with user=rating.user %}</div>
|
||||
<div class="media-content">
|
||||
|
@ -185,6 +229,5 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% if not request.user.is_authenticated %}
|
||||
<div class="block">
|
||||
<h1 class="title has-text-centered">{{ site.name }}: Social Reading and Reviewing</h1>
|
||||
<h1 class="title has-text-centered">{{ site.name }}: {{ site.instance_tagline }}</h1>
|
||||
</div>
|
||||
|
||||
<section class="tile is-ancestor">
|
||||
|
@ -21,7 +21,7 @@
|
|||
</form>
|
||||
{% else %}
|
||||
<h2 class="title">This instance is closed</h2>
|
||||
<p>Contact an administrator to get an invite</p>
|
||||
<p>{{ site.registration_closed_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,10 +37,6 @@
|
|||
{% for error in form.title.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
<p class="fields is-grouped"><label class="label" for="id_sort_title">Sort title:</label> {{ form.sort_title }} </p>
|
||||
{% for error in form.sort_title.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
<p class="fields is-grouped"><label class="label" for="id_subtitle">Subtitle:</label> {{ form.subtitle }} </p>
|
||||
{% for error in form.subtitle.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
|
@ -113,12 +109,12 @@
|
|||
{% for error in form.openlibrary_key.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
<p class="fields is-grouped"><label class="label" for="id_librarything_key">Librarything key:</label> {{ form.librarything_key }} </p>
|
||||
{% for error in form.librarything_key.errors %}
|
||||
<p class="fields is-grouped"><label class="label" for="id_librarything_key">OCLC Number:</label> {{ form.oclc_number }} </p>
|
||||
{% for error in form.oclc_number.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
<p class="fields is-grouped"><label class="label" for="id_goodreads_key">Goodreads key:</label> {{ form.goodreads_key }} </p>
|
||||
{% for error in form.goodreads_key.errors %}
|
||||
<p class="fields is-grouped"><label class="label" for="id_asin">ASIN:</label> {{ form.asin }} </p>
|
||||
{% for error in form.ASIN.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -8,20 +8,23 @@
|
|||
<link type="text/css" rel="stylesheet" href="/static/css/format.css">
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/icons.css">
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/static/images/favicon.ico">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}/images/{{ site.favicon }}{% else %}/static/images/favicon.ico{% endif %}">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="{{ site.name }}">
|
||||
<meta name="og:title" content="{{ site.name }}">
|
||||
<meta name="twitter:description" content="Federated Social Reading">
|
||||
<meta name="og:description" content="Federated Social Reading">
|
||||
<meta name="twitter:title" content="{% if title %}{{ title }} | {% endif %}{{ site.name }}">
|
||||
<meta name="og:title" content="{% if title %}{{ title }} | {% endif %}{{ site.name }}">
|
||||
<meta name="twitter:description" content="{{ site.instance_tagline }}">
|
||||
<meta name="og:description" content="{{ site.instance_tagline }}">
|
||||
|
||||
<meta name="twitter:image" content="{% if site.logo %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}">
|
||||
<meta name="og:image" content="{% if site.logo %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}">
|
||||
<meta name="twitter:image:alt" content="BookWyrm Logo">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar container" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img class="image logo" src="/static/images/logo-small.png" alt="Home page">
|
||||
<img class="image logo" src="{% if site.logo_small %}/images/{{ site.logo_small }}{% else %}/static/images/logo-small.png{% endif %}" alt="Home page">
|
||||
</a>
|
||||
<form class="navbar-item column" action="/search/">
|
||||
<div class="field has-addons">
|
||||
|
@ -119,15 +122,15 @@
|
|||
</div>
|
||||
{% else %}
|
||||
<div class="navbar-item">
|
||||
{% if request.path != '/login' and request.path != '/login/' %}
|
||||
{% if request.path != '/login' and request.path != '/login/' and request.path != '/user-login' %}
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<form name="login" method="post" action="/user-login">
|
||||
{% csrf_token %}
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<label class="is-sr-only" for="id_username">Username:</label>
|
||||
<input type="text" name="username" maxlength="150" class="input" required="" id="id_username" placeholder="username">
|
||||
<label class="is-sr-only" for="id_localname">Username:</label>
|
||||
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname" placeholder="username">
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="is-sr-only" for="id_password">Username:</label>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<form name="login" method="post" action="/user-login">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label" for="id_username">Username:</label>
|
||||
<label class="label" for="id_localname">Username:</label>
|
||||
<div class="control">
|
||||
{{ login_form.username }}
|
||||
{{ login_form.localname }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="columns">
|
||||
<div class="column is-narrow is-hidden-mobile">
|
||||
<figure class="block">
|
||||
<img src="/static/images/logo.png" alt="BookWyrm">
|
||||
<img src="{% if site.logo_small %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}" alt="BookWyrm logo">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
{% load bookwyrm_tags %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<span class="is-sr-only">Leave a rating</span>
|
||||
<div class="field is-grouped stars rate-stars">
|
||||
{% for i in '12345'|make_list %}
|
||||
<form name="rate" action="/rate/" method="POST" onsubmit="return rate_stars(event)">
|
||||
<form name="rate" action="/rate/" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
|
@ -14,3 +15,4 @@
|
|||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label" for="id_username_register">Username:</label>
|
||||
<label class="label" for="id_localname_register">Username:</label>
|
||||
<div class="control">
|
||||
<input type="text" name="username" maxlength="150" class="input" required="" id="id_username_register" value="{% if register_form.username.value %}{{ register_form.username.value }} {% endif %}">
|
||||
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname_register" value="{% if register_form.localname.value %}{{ register_form.localname.value }}{% endif %}">
|
||||
</div>
|
||||
{% for error in register_form.username.errors %}
|
||||
{% for error in register_form.localname.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,8 @@ class BaseActivity(TestCase):
|
|||
def setUp(self):
|
||||
''' we're probably going to re-use this so why copy/paste '''
|
||||
self.user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
self.user.remote_id = 'http://example.com/a/b'
|
||||
self.user.save()
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ class BaseModel(TestCase):
|
|||
def test_remote_id_with_user(self):
|
||||
''' format of remote id when there's a user object '''
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
instance = base_model.BookWyrmModel()
|
||||
instance.user = user
|
||||
instance.id = 1
|
||||
|
@ -51,7 +52,8 @@ class BaseModel(TestCase):
|
|||
def test_to_create_activity(self):
|
||||
''' wrapper for ActivityPub "create" action '''
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
|
||||
object_activity = {
|
||||
'to': 'to field', 'cc': 'cc field',
|
||||
|
@ -81,7 +83,8 @@ class BaseModel(TestCase):
|
|||
def test_to_delete_activity(self):
|
||||
''' wrapper for Delete activity '''
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
|
||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
||||
mock_self = MockSelf(
|
||||
|
@ -105,7 +108,8 @@ class BaseModel(TestCase):
|
|||
def test_to_update_activity(self):
|
||||
''' ditto above but for Update '''
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
|
||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
||||
mock_self = MockSelf(
|
||||
|
@ -129,7 +133,8 @@ class BaseModel(TestCase):
|
|||
def test_to_undo_activity(self):
|
||||
''' and again, for Undo '''
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
|
||||
MockSelf = namedtuple('Self', ('remote_id', 'to_activity'))
|
||||
mock_self = MockSelf(
|
||||
|
@ -173,7 +178,8 @@ class BaseModel(TestCase):
|
|||
book = models.Edition.objects.create(
|
||||
title='Test Edition', remote_id='http://book.com/book')
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
user.remote_id = 'http://example.com/a/b'
|
||||
user.save()
|
||||
|
||||
|
|
|
@ -111,10 +111,17 @@ class ActivitypubFields(TestCase):
|
|||
self.assertEqual(instance.max_length, 150)
|
||||
self.assertEqual(instance.unique, True)
|
||||
with self.assertRaises(ValidationError):
|
||||
instance.run_validators('one two')
|
||||
instance.run_validators('a*&')
|
||||
instance.run_validators('trailingwhite ')
|
||||
self.assertIsNone(instance.run_validators('aksdhf'))
|
||||
instance.run_validators('mouse')
|
||||
instance.run_validators('mouseexample.com')
|
||||
instance.run_validators('mouse@example.c')
|
||||
instance.run_validators('@example.com')
|
||||
instance.run_validators('mouse@examplecom')
|
||||
instance.run_validators('one two@fish.aaaa')
|
||||
instance.run_validators('a*&@exampke.com')
|
||||
instance.run_validators('trailingwhite@example.com ')
|
||||
self.assertIsNone(instance.run_validators('mouse@example.com'))
|
||||
self.assertIsNone(instance.run_validators('mo-2use@ex3ample.com'))
|
||||
self.assertIsNone(instance.run_validators('aksdhf@sdkjf-df.cm'))
|
||||
|
||||
self.assertEqual(instance.field_to_activity('test@example.com'), 'test')
|
||||
|
||||
|
@ -173,7 +180,8 @@ class ActivitypubFields(TestCase):
|
|||
def test_privacy_field_set_activity_from_field(self):
|
||||
''' translate between to/cc fields and privacy '''
|
||||
user = User.objects.create_user(
|
||||
'rat', 'rat@rat.rat', 'ratword', local=True)
|
||||
'rat', 'rat@rat.rat', 'ratword',
|
||||
local=True, localname='rat')
|
||||
public = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
followers = '%s/followers' % user.remote_id
|
||||
|
||||
|
@ -230,7 +238,8 @@ class ActivitypubFields(TestCase):
|
|||
|
||||
# it shouldn't match with this unrelated user:
|
||||
unrelated_user = User.objects.create_user(
|
||||
'rat', 'rat@rat.rat', 'ratword', local=True)
|
||||
'rat', 'rat@rat.rat', 'ratword',
|
||||
local=True, localname='rat')
|
||||
|
||||
# test receiving an unknown remote id and loading data
|
||||
responses.add(
|
||||
|
@ -258,7 +267,8 @@ class ActivitypubFields(TestCase):
|
|||
|
||||
# it shouldn't match with this unrelated user:
|
||||
unrelated_user = User.objects.create_user(
|
||||
'rat', 'rat@rat.rat', 'ratword', local=True)
|
||||
'rat', 'rat@rat.rat', 'ratword',
|
||||
local=True, localname='rat')
|
||||
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||
value = instance.field_from_activity(userdata)
|
||||
self.assertIsInstance(value, User)
|
||||
|
@ -276,11 +286,13 @@ class ActivitypubFields(TestCase):
|
|||
)
|
||||
userdata = json.loads(datafile.read_bytes())
|
||||
user = User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
user.remote_id = 'https://example.com/user/mouse'
|
||||
user.save()
|
||||
User.objects.create_user(
|
||||
'rat', 'rat@rat.rat', 'ratword', local=True)
|
||||
'rat', 'rat@rat.rat', 'ratword',
|
||||
local=True, localname='rat')
|
||||
|
||||
value = instance.field_from_activity(userdata)
|
||||
self.assertEqual(value, user)
|
||||
|
@ -290,9 +302,11 @@ class ActivitypubFields(TestCase):
|
|||
''' test receiving a remote id of an existing object in the db '''
|
||||
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
||||
user = User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
User.objects.create_user(
|
||||
'rat', 'rat@rat.rat', 'ratword', local=True)
|
||||
'rat', 'rat@rat.rat', 'ratword',
|
||||
local=True, localname='rat')
|
||||
|
||||
value = instance.field_from_activity(user.remote_id)
|
||||
self.assertEqual(value, user)
|
||||
|
@ -382,7 +396,8 @@ class ActivitypubFields(TestCase):
|
|||
def test_image_field(self):
|
||||
''' storing images '''
|
||||
user = User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
image_file = pathlib.Path(__file__).parent.joinpath(
|
||||
'../../static/images/default_avi.jpg')
|
||||
image = Image.open(image_file)
|
||||
|
|
|
@ -59,7 +59,8 @@ class ImportJob(TestCase):
|
|||
unknown_read_data['Date Read'] = ''
|
||||
|
||||
user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
job = models.ImportJob.objects.create(user=user)
|
||||
self.item_1 = models.ImportItem.objects.create(
|
||||
job=job, index=1, data=currently_reading_data)
|
||||
|
|
|
@ -16,7 +16,8 @@ class Relationship(TestCase):
|
|||
outbox='https://example.com/users/rat/outbox',
|
||||
)
|
||||
self.local_user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
self.local_user.remote_id = 'http://local.com/user/mouse'
|
||||
self.local_user.save()
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ class Shelf(TestCase):
|
|||
def setUp(self):
|
||||
''' look, a shelf '''
|
||||
self.user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
self.shelf = models.Shelf.objects.create(
|
||||
name='Test Shelf', identifier='test-shelf', user=self.user)
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ class Status(TestCase):
|
|||
def setUp(self):
|
||||
''' useful things for creating a status '''
|
||||
self.user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
self.book = models.Edition.objects.create(title='Test Edition')
|
||||
|
||||
image_file = pathlib.Path(__file__).parent.joinpath(
|
||||
|
|
|
@ -9,7 +9,8 @@ from bookwyrm.settings import DOMAIN
|
|||
class User(TestCase):
|
||||
def setUp(self):
|
||||
self.user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse@%s' % DOMAIN, 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
|
||||
def test_computed_fields(self):
|
||||
''' username instead of id here '''
|
||||
|
|
|
@ -7,10 +7,12 @@ from bookwyrm import models, broadcast
|
|||
class Book(TestCase):
|
||||
def setUp(self):
|
||||
self.user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
|
||||
local_follower = models.User.objects.create_user(
|
||||
'joe', 'joe@mouse.mouse', 'jeoword', local=True)
|
||||
'joe', 'joe@mouse.mouse', 'jeoword',
|
||||
local=True, localname='joe')
|
||||
self.user.followers.add(local_follower)
|
||||
|
||||
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||
|
|
|
@ -19,7 +19,8 @@ class Incoming(TestCase):
|
|||
def setUp(self):
|
||||
''' we need basic things, like users '''
|
||||
self.local_user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse@example.com', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
self.local_user.remote_id = 'https://example.com/user/mouse'
|
||||
self.local_user.save()
|
||||
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||
|
@ -486,6 +487,10 @@ class Incoming(TestCase):
|
|||
|
||||
def test_handle_update_user(self):
|
||||
''' update an existing user '''
|
||||
# we only do this with remote users
|
||||
self.local_user.local = False
|
||||
self.local_user.save()
|
||||
|
||||
datafile = pathlib.Path(__file__).parent.joinpath(
|
||||
'data/ap_user.json')
|
||||
userdata = json.loads(datafile.read_bytes())
|
||||
|
@ -494,6 +499,8 @@ class Incoming(TestCase):
|
|||
incoming.handle_update_user({'object': userdata})
|
||||
user = models.User.objects.get(id=self.local_user.id)
|
||||
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
|
||||
self.assertEqual(user.username, 'mouse@example.com')
|
||||
self.assertEqual(user.localname, 'mouse')
|
||||
|
||||
|
||||
def test_handle_update_edition(self):
|
||||
|
|
|
@ -21,15 +21,16 @@ class Outgoing(TestCase):
|
|||
self.factory = RequestFactory()
|
||||
with patch('bookwyrm.models.user.set_remote_server'):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
'rat', 'rat@rat.com', 'ratword',
|
||||
'rat', 'rat@email.com', 'ratword',
|
||||
local=False,
|
||||
remote_id='https://example.com/users/rat',
|
||||
inbox='https://example.com/users/rat/inbox',
|
||||
outbox='https://example.com/users/rat/outbox',
|
||||
)
|
||||
self.local_user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True,
|
||||
localname='mouse', remote_id='https://example.com/users/mouse',
|
||||
'mouse@local.com', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse',
|
||||
remote_id='https://example.com/users/mouse',
|
||||
)
|
||||
|
||||
datafile = pathlib.Path(__file__).parent.joinpath(
|
||||
|
@ -175,10 +176,10 @@ class Outgoing(TestCase):
|
|||
|
||||
def test_existing_user(self):
|
||||
''' simple database lookup by username '''
|
||||
result = outgoing.handle_remote_webfinger('@mouse@%s' % DOMAIN)
|
||||
result = outgoing.handle_remote_webfinger('@mouse@local.com')
|
||||
self.assertEqual(result, self.local_user)
|
||||
|
||||
result = outgoing.handle_remote_webfinger('mouse@%s' % DOMAIN)
|
||||
result = outgoing.handle_remote_webfinger('mouse@local.com')
|
||||
self.assertEqual(result, self.local_user)
|
||||
|
||||
|
||||
|
@ -398,7 +399,8 @@ class Outgoing(TestCase):
|
|||
def test_handle_status_mentions(self):
|
||||
''' @mention a user in a post '''
|
||||
user = models.User.objects.create_user(
|
||||
'rat', 'rat@rat.com', 'password', local=True)
|
||||
'rat@%s' % DOMAIN, 'rat@rat.com', 'password',
|
||||
local=True, localname='rat')
|
||||
form = forms.CommentForm({
|
||||
'content': 'hi @rat',
|
||||
'user': self.local_user.id,
|
||||
|
@ -409,16 +411,17 @@ class Outgoing(TestCase):
|
|||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||
outgoing.handle_status(self.local_user, form)
|
||||
status = models.Status.objects.get()
|
||||
self.assertEqual(list(status.mention_users.all()), [user])
|
||||
self.assertEqual(models.Notification.objects.get().user, user)
|
||||
self.assertEqual(
|
||||
status.content,
|
||||
'<p>hi <a href="%s">@rat</a></p>' % user.remote_id)
|
||||
self.assertEqual(list(status.mention_users.all()), [user])
|
||||
self.assertEqual(models.Notification.objects.get().user, user)
|
||||
|
||||
def test_handle_status_reply_with_mentions(self):
|
||||
''' reply to a post with an @mention'ed user '''
|
||||
user = models.User.objects.create_user(
|
||||
'rat', 'rat@rat.com', 'password', local=True)
|
||||
'rat', 'rat@rat.com', 'password',
|
||||
local=True, localname='rat')
|
||||
form = forms.CommentForm({
|
||||
'content': 'hi @rat@example.com',
|
||||
'user': self.local_user.id,
|
||||
|
|
|
@ -31,11 +31,14 @@ Sender = namedtuple('Sender', ('remote_id', 'key_pair'))
|
|||
class Signature(TestCase):
|
||||
def setUp(self):
|
||||
self.mouse = User.objects.create_user(
|
||||
'mouse', 'mouse@example.com', '', local=True)
|
||||
'mouse@%s' % DOMAIN, 'mouse@example.com', '',
|
||||
local=True, localname='mouse')
|
||||
self.rat = User.objects.create_user(
|
||||
'rat', 'rat@example.com', '', local=True)
|
||||
'rat@%s' % DOMAIN, 'rat@example.com', '',
|
||||
local=True, localname='rat')
|
||||
self.cat = User.objects.create_user(
|
||||
'cat', 'cat@example.com', '', local=True)
|
||||
'cat@%s' % DOMAIN, 'cat@example.com', '',
|
||||
local=True, localname='cat')
|
||||
|
||||
private_key, public_key = create_key_pair()
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ class TemplateTags(TestCase):
|
|||
def setUp(self):
|
||||
''' create some filler objects '''
|
||||
self.user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
'mouse@example.com', 'mouse@mouse.mouse', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
'rat', 'rat@rat.rat', 'ratword',
|
||||
|
|
|
@ -18,7 +18,8 @@ class ViewActions(TestCase):
|
|||
def setUp(self):
|
||||
''' we need basic things, like users '''
|
||||
self.local_user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.com', 'mouseword', local=True)
|
||||
'mouse', 'mouse@mouse.com', 'mouseword',
|
||||
local=True, localname='mouse')
|
||||
self.local_user.remote_id = 'https://example.com/user/mouse'
|
||||
self.local_user.save()
|
||||
self.group = Group.objects.create(name='editor')
|
||||
|
@ -54,7 +55,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria-user.user_nutria',
|
||||
'localname': 'nutria-user.user_nutria',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.cccc'
|
||||
})
|
||||
|
@ -72,7 +73,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria ',
|
||||
'localname': 'nutria ',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc'
|
||||
})
|
||||
|
@ -91,7 +92,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria',
|
||||
'localname': 'nutria',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa'
|
||||
})
|
||||
|
@ -105,7 +106,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nut@ria',
|
||||
'localname': 'nut@ria',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc'
|
||||
})
|
||||
|
@ -116,7 +117,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutr ia',
|
||||
'localname': 'nutr ia',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc'
|
||||
})
|
||||
|
@ -127,7 +128,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nut@ria',
|
||||
'localname': 'nut@ria',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc'
|
||||
})
|
||||
|
@ -143,7 +144,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria ',
|
||||
'localname': 'nutria ',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc'
|
||||
})
|
||||
|
@ -161,7 +162,7 @@ class ViewActions(TestCase):
|
|||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria',
|
||||
'localname': 'nutria',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc',
|
||||
'invite_code': 'testcode'
|
||||
|
@ -172,23 +173,24 @@ class ViewActions(TestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(models.SiteInvite.objects.get().times_used, 1)
|
||||
|
||||
# invalid invite
|
||||
# invite already used to max capacity
|
||||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria2',
|
||||
'localname': 'nutria2',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc',
|
||||
'invite_code': 'testcode'
|
||||
})
|
||||
response = actions.register(request)
|
||||
with self.assertRaises(PermissionDenied):
|
||||
response = actions.register(request)
|
||||
self.assertEqual(models.User.objects.count(), 3)
|
||||
|
||||
# bad invite code
|
||||
request = self.factory.post(
|
||||
'register/',
|
||||
{
|
||||
'username': 'nutria3',
|
||||
'localname': 'nutria3',
|
||||
'password': 'mouseword',
|
||||
'email': 'aa@bb.ccc',
|
||||
'invite_code': 'dkfkdjgdfkjgkdfj'
|
||||
|
@ -379,7 +381,7 @@ class ViewActions(TestCase):
|
|||
def test_untag(self):
|
||||
''' remove a tag from a book '''
|
||||
tag = models.Tag.objects.create(name='A Tag!?')
|
||||
user_tag = models.UserTag.objects.create(
|
||||
models.UserTag.objects.create(
|
||||
user=self.local_user, book=self.book, tag=tag)
|
||||
request = self.factory.post(
|
||||
'', {
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.test import TestCase
|
|||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.connectors import abstract_connector
|
||||
from bookwyrm.settings import DOMAIN, USER_AGENT
|
||||
|
||||
|
@ -28,7 +29,8 @@ class Views(TestCase):
|
|||
local=True
|
||||
)
|
||||
self.local_user = models.User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'password', local=True)
|
||||
'mouse@local.com', 'mouse@mouse.mouse', 'password',
|
||||
local=True, localname='mouse')
|
||||
with patch('bookwyrm.models.user.set_remote_server.delay'):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
'rat', 'rat@rat.com', 'ratword',
|
||||
|
@ -52,7 +54,7 @@ class Views(TestCase):
|
|||
self.assertEqual(
|
||||
views.get_user_from_username('mouse'), self.local_user)
|
||||
self.assertEqual(
|
||||
views.get_user_from_username('mouse@%s' % DOMAIN), self.local_user)
|
||||
views.get_user_from_username('mouse@local.com'), self.local_user)
|
||||
with self.assertRaises(models.User.DoesNotExist):
|
||||
views.get_user_from_username('mojfse@example.com')
|
||||
|
||||
|
@ -114,17 +116,20 @@ class Views(TestCase):
|
|||
content='blah blah blah', user=rat, privacy='followers')
|
||||
rat_mention.mention_users.set([self.local_user])
|
||||
|
||||
statuses = views.get_activity_feed(self.local_user, 'home')
|
||||
self.assertEqual(len(statuses), 2)
|
||||
self.assertEqual(statuses[1], public_status)
|
||||
self.assertEqual(statuses[0], rat_mention)
|
||||
|
||||
statuses = views.get_activity_feed(
|
||||
self.local_user, 'home', model=models.Comment)
|
||||
self.local_user,
|
||||
['public', 'unlisted', 'followers'],
|
||||
following_only=True,
|
||||
queryset=models.Comment.objects
|
||||
)
|
||||
self.assertEqual(len(statuses), 1)
|
||||
self.assertEqual(statuses[0], public_status)
|
||||
|
||||
statuses = views.get_activity_feed(self.local_user, 'local')
|
||||
statuses = views.get_activity_feed(
|
||||
self.local_user,
|
||||
['public', 'followers'],
|
||||
local_only=True
|
||||
)
|
||||
self.assertEqual(len(statuses), 2)
|
||||
self.assertEqual(statuses[1], public_status)
|
||||
self.assertEqual(statuses[0], rat_public)
|
||||
|
@ -133,19 +138,30 @@ class Views(TestCase):
|
|||
self.assertEqual(len(statuses), 1)
|
||||
self.assertEqual(statuses[0], direct_status)
|
||||
|
||||
statuses = views.get_activity_feed(self.local_user, 'federated')
|
||||
statuses = views.get_activity_feed(
|
||||
self.local_user,
|
||||
['public', 'followers'],
|
||||
)
|
||||
self.assertEqual(len(statuses), 3)
|
||||
self.assertEqual(statuses[2], public_status)
|
||||
self.assertEqual(statuses[1], rat_public)
|
||||
self.assertEqual(statuses[0], remote_status)
|
||||
|
||||
statuses = views.get_activity_feed(self.local_user, 'friends')
|
||||
statuses = views.get_activity_feed(
|
||||
self.local_user,
|
||||
['public', 'unlisted', 'followers'],
|
||||
following_only=True
|
||||
)
|
||||
self.assertEqual(len(statuses), 2)
|
||||
self.assertEqual(statuses[1], public_status)
|
||||
self.assertEqual(statuses[0], rat_mention)
|
||||
|
||||
rat.followers.add(self.local_user)
|
||||
statuses = views.get_activity_feed(self.local_user, 'friends')
|
||||
statuses = views.get_activity_feed(
|
||||
self.local_user,
|
||||
['public', 'unlisted', 'followers'],
|
||||
following_only=True
|
||||
)
|
||||
self.assertEqual(len(statuses), 5)
|
||||
self.assertEqual(statuses[4], public_status)
|
||||
self.assertEqual(statuses[3], rat_public)
|
||||
|
@ -343,7 +359,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.user_page(request, 'mouse')
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -361,7 +377,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.followers_page(request, 'mouse')
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -379,7 +395,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.following_page(request, 'mouse')
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -399,7 +415,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.status_page(request, 'mouse', status.id)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -419,7 +435,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.replies_page(request, 'mouse', status.id)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -448,7 +464,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.book_page(request, self.book.id)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -489,7 +505,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.editions_page(request, self.work.id)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -508,7 +524,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.author_page(request, author.id)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -529,7 +545,7 @@ class Views(TestCase):
|
|||
with patch('bookwyrm.views.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = views.tag_page(request, tag.identifier)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
|
@ -550,18 +566,22 @@ class Views(TestCase):
|
|||
is_api.return_value = True
|
||||
result = views.shelf_page(
|
||||
request, self.local_user.username, shelf.identifier)
|
||||
self.assertIsInstance(result, JsonResponse)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
def test_is_bookwyrm_request(self):
|
||||
''' tests the function that checks if a request came from a bookwyrm instance '''
|
||||
''' checks if a request came from a bookwyrm instance '''
|
||||
request = self.factory.get('', {'q': 'Test Book'})
|
||||
self.assertFalse(views.is_bookworm_request(request))
|
||||
|
||||
request = self.factory.get('', {'q': 'Test Book'},
|
||||
HTTP_USER_AGENT="http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)")
|
||||
request = self.factory.get(
|
||||
'', {'q': 'Test Book'},
|
||||
HTTP_USER_AGENT=\
|
||||
"http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)"
|
||||
)
|
||||
self.assertFalse(views.is_bookworm_request(request))
|
||||
|
||||
request = self.factory.get('', {'q': 'Test Book'}, HTTP_USER_AGENT=USER_AGENT)
|
||||
request = self.factory.get(
|
||||
'', {'q': 'Test Book'}, HTTP_USER_AGENT=USER_AGENT)
|
||||
self.assertTrue(views.is_bookworm_request(request))
|
||||
|
|
|
@ -30,8 +30,8 @@ def user_login(request):
|
|||
''' authenticate user login '''
|
||||
login_form = forms.LoginForm(request.POST)
|
||||
|
||||
username = login_form.data['username']
|
||||
username = '%s@%s' % (username, DOMAIN)
|
||||
localname = login_form.data['localname']
|
||||
username = '%s@%s' % (localname, DOMAIN)
|
||||
password = login_form.data['password']
|
||||
user = authenticate(request, username=username, password=password)
|
||||
if user is not None:
|
||||
|
@ -59,6 +59,8 @@ def register(request):
|
|||
raise PermissionDenied
|
||||
|
||||
invite = get_object_or_404(models.SiteInvite, code=invite_code)
|
||||
if not invite.valid():
|
||||
raise PermissionDenied
|
||||
else:
|
||||
invite = None
|
||||
|
||||
|
@ -67,13 +69,13 @@ def register(request):
|
|||
if not form.is_valid():
|
||||
errors = True
|
||||
|
||||
username = form.data['username'].strip()
|
||||
localname = form.data['localname'].strip()
|
||||
email = form.data['email']
|
||||
password = form.data['password']
|
||||
|
||||
# check username and email uniqueness
|
||||
if models.User.objects.filter(localname=username).first():
|
||||
form.add_error('username', 'User with this username already exists')
|
||||
# check localname and email uniqueness
|
||||
if models.User.objects.filter(localname=localname).first():
|
||||
form.errors['localname'] = ['User with this username already exists']
|
||||
errors = True
|
||||
|
||||
if errors:
|
||||
|
@ -83,8 +85,9 @@ def register(request):
|
|||
}
|
||||
return TemplateResponse(request, 'login.html', data)
|
||||
|
||||
username = '%s@%s' % (localname, DOMAIN)
|
||||
user = models.User.objects.create_user(
|
||||
username, email, password, local=True)
|
||||
username, email, password, localname=localname, local=True)
|
||||
if invite:
|
||||
invite.times_used += 1
|
||||
invite.save()
|
||||
|
|
|
@ -83,7 +83,16 @@ def home_tab(request, tab):
|
|||
|
||||
suggested_books = get_suggested_books(request.user)
|
||||
|
||||
activities = get_activity_feed(request.user, tab)
|
||||
if tab == 'home':
|
||||
activities = get_activity_feed(
|
||||
request.user, ['public', 'unlisted', 'followers'],
|
||||
following_only=True)
|
||||
elif tab == 'local':
|
||||
activities = get_activity_feed(
|
||||
request.user, ['public', 'followers'], local_only=True)
|
||||
else:
|
||||
activities = get_activity_feed(
|
||||
request.user, ['public', 'followers'])
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
activity_page = paginated.page(page)
|
||||
|
||||
|
@ -151,7 +160,7 @@ def discover_page(request):
|
|||
book__in=book.parent_work.editions.all()
|
||||
)
|
||||
reviews = get_activity_feed(
|
||||
request.user, 'federated', model=reviews)
|
||||
request.user, ['public', 'unlisted'], queryset=reviews)
|
||||
ratings[book.id] = reviews.aggregate(Avg('rating'))['rating__avg']
|
||||
data = {
|
||||
'title': 'Discover',
|
||||
|
@ -187,68 +196,62 @@ def direct_messages_page(request, page=1):
|
|||
return TemplateResponse(request, 'direct_messages.html', data)
|
||||
|
||||
|
||||
def get_activity_feed(user, filter_level, model=models.Status):
|
||||
def get_activity_feed(
|
||||
user, privacy, local_only=False, following_only=False,
|
||||
queryset=models.Status.objects):
|
||||
''' get a filtered queryset of statuses '''
|
||||
privacy = privacy if isinstance(privacy, list) else [privacy]
|
||||
# if we're looking at Status, we need this. We don't if it's Comment
|
||||
if hasattr(queryset, 'select_subclasses'):
|
||||
queryset = queryset.select_subclasses()
|
||||
|
||||
# exclude deleted
|
||||
queryset = queryset.exclude(deleted=True).order_by('-published_date')
|
||||
|
||||
# you can't see followers only or direct messages if you're not logged in
|
||||
if user.is_anonymous:
|
||||
user = None
|
||||
privacy = [p for p in privacy if not p in ['followers', 'direct']]
|
||||
|
||||
if user:
|
||||
following = models.User.objects.filter(
|
||||
Q(followers=user) | Q(id=user.id)
|
||||
# filter to only privided privacy levels
|
||||
queryset = queryset.filter(privacy__in=privacy)
|
||||
|
||||
# only include statuses the user follows
|
||||
if following_only:
|
||||
queryset = queryset.exclude(
|
||||
~Q(# remove everythign except
|
||||
Q(user__in=user.following.all()) | # user follwoing
|
||||
Q(user=user) |# is self
|
||||
Q(mention_users=user)# mentions user
|
||||
),
|
||||
)
|
||||
else:
|
||||
following = []
|
||||
|
||||
activities = model
|
||||
if hasattr(model, 'objects'):
|
||||
activities = model.objects
|
||||
|
||||
activities = activities.filter(
|
||||
deleted=False,
|
||||
).order_by(
|
||||
'-published_date'
|
||||
)
|
||||
|
||||
if filter_level == 'direct':
|
||||
return activities.filter(
|
||||
Q(user=user) | Q(mention_users=user),
|
||||
privacy='direct'
|
||||
).distinct()
|
||||
|
||||
# never show DMs in the regular feed
|
||||
activities = activities.filter(~Q(privacy='direct'))
|
||||
|
||||
|
||||
if hasattr(activities, 'select_subclasses'):
|
||||
activities = activities.select_subclasses()
|
||||
|
||||
if filter_level in ['friends', 'home']:
|
||||
# people you follow and direct mentions
|
||||
activities = activities.filter(
|
||||
Q(user__in=following, privacy__in=[
|
||||
'public', 'unlisted', 'followers'
|
||||
]) | Q(mention_users=user) | Q(user=user)
|
||||
).distinct()
|
||||
elif filter_level == 'self':
|
||||
activities = activities.filter(user=user, privacy='public')
|
||||
elif filter_level == 'local':
|
||||
# everyone on this instance except unlisted
|
||||
activities = activities.filter(
|
||||
Q(user__in=following, privacy='followers') | Q(privacy='public'),
|
||||
user__local=True
|
||||
)
|
||||
else:
|
||||
# all activities from everyone you federate with
|
||||
activities = activities.filter(
|
||||
Q(user__in=following, privacy='followers') | Q(privacy='public')
|
||||
# exclude followers-only statuses the user doesn't follow
|
||||
elif 'followers' in privacy:
|
||||
queryset = queryset.exclude(
|
||||
~Q(# user isn't following and it isn't their own status
|
||||
Q(user__in=user.following.all()) | Q(user=user)
|
||||
),
|
||||
privacy='followers' # and the status is followers only
|
||||
)
|
||||
|
||||
# exclude direct messages not intended for the user
|
||||
if 'direct' in privacy:
|
||||
queryset = queryset.exclude(
|
||||
~Q(
|
||||
Q(user=user) | Q(mention_users=user)
|
||||
), privacy='direct'
|
||||
)
|
||||
|
||||
# filter for only local status
|
||||
if local_only:
|
||||
queryset = queryset.filter(user__local=True)
|
||||
|
||||
# remove statuses that have boosts in the same queryset
|
||||
try:
|
||||
activities = activities.filter(~Q(boosters__in=activities))
|
||||
queryset = queryset.filter(~Q(boosters__in=queryset))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return activities
|
||||
return queryset
|
||||
|
||||
|
||||
@require_GET
|
||||
|
@ -462,7 +465,11 @@ def user_page(request, username):
|
|||
break
|
||||
|
||||
# user's posts
|
||||
activities = get_activity_feed(user, 'self')
|
||||
activities = get_activity_feed(
|
||||
user,
|
||||
['public', 'unlisted', 'followers'],
|
||||
queryset=models.Status.objects.filter(user=request.user)
|
||||
)
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
activity_page = paginated.page(page)
|
||||
|
||||
|
@ -629,10 +636,16 @@ def book_page(request, book_id):
|
|||
book__in=work.editions.all(),
|
||||
)
|
||||
# all reviews for the book
|
||||
reviews = get_activity_feed(request.user, 'federated', model=reviews)
|
||||
reviews = get_activity_feed(
|
||||
request.user,
|
||||
['public', 'unlisted', 'followers', 'direct'],
|
||||
queryset=reviews
|
||||
)
|
||||
|
||||
# the reviews to show
|
||||
paginated = Paginator(reviews.filter(content__isnull=False), PAGE_LENGTH)
|
||||
paginated = Paginator(reviews.exclude(
|
||||
Q(content__isnull=True) | Q(content='')
|
||||
), PAGE_LENGTH)
|
||||
reviews_page = paginated.page(page)
|
||||
|
||||
prev_page = next_page = None
|
||||
|
@ -668,7 +681,8 @@ def book_page(request, book_id):
|
|||
'title': book.title,
|
||||
'book': book,
|
||||
'reviews': reviews_page,
|
||||
'ratings': reviews.filter(content__isnull=True),
|
||||
'review_count': reviews.count(),
|
||||
'ratings': reviews.filter(Q(content__isnull=True) | Q(content='')),
|
||||
'rating': reviews.aggregate(Avg('rating'))['rating__avg'],
|
||||
'tags': models.UserTag.objects.filter(book=book),
|
||||
'user_tags': user_tags,
|
||||
|
@ -676,14 +690,6 @@ def book_page(request, book_id):
|
|||
'other_edition_shelves': other_edition_shelves,
|
||||
'readthroughs': readthroughs,
|
||||
'path': '/book/%s' % book_id,
|
||||
'info_fields': [
|
||||
{'name': 'ISBN', 'value': book.isbn_13},
|
||||
{'name': 'OCLC number', 'value': book.oclc_number},
|
||||
{'name': 'OpenLibrary ID', 'value': book.openlibrary_key},
|
||||
{'name': 'Goodreads ID', 'value': book.goodreads_key},
|
||||
{'name': 'Format', 'value': book.physical_format},
|
||||
{'name': 'Pages', 'value': book.pages},
|
||||
],
|
||||
'next': next_page,
|
||||
'prev': prev_page,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue