Merge branch 'main' into progress_update

This commit is contained in:
Joel Bradshaw 2021-01-19 23:49:35 -08:00
commit 85edee42ef
11 changed files with 84 additions and 21 deletions

View file

@ -0,0 +1,19 @@
# Generated by Django 3.0.7 on 2021-01-19 15:34
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bookwyrm', '0037_auto_20210118_1954'),
]
operations = [
migrations.AlterField(
model_name='annualgoal',
name='goal',
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)]),
),
]

View file

@ -4,6 +4,7 @@ from urllib.parse import urlparse
from django.apps import apps from django.apps import apps
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator
from django.db import models from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
@ -226,7 +227,9 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
class AnnualGoal(BookWyrmModel): class AnnualGoal(BookWyrmModel):
''' set a goal for how many books you read in a year ''' ''' set a goal for how many books you read in a year '''
user = models.ForeignKey('User', on_delete=models.PROTECT) user = models.ForeignKey('User', on_delete=models.PROTECT)
goal = models.IntegerField() goal = models.IntegerField(
validators=[MinValueValidator(1)]
)
year = models.IntegerField(default=timezone.now().year) year = models.IntegerField(default=timezone.now().year)
privacy = models.CharField( privacy = models.CharField(
max_length=255, max_length=255,

View file

@ -31,8 +31,32 @@ window.onload = function() {
// hidden submit button in a form // hidden submit button in a form
document.querySelectorAll('.hidden-form input') document.querySelectorAll('.hidden-form input')
.forEach(t => t.onchange = revealForm); .forEach(t => t.onchange = revealForm);
// polling
document.querySelectorAll('[data-poll]')
.forEach(el => polling(el));
}; };
function polling(el) {
let delay = 10000 + (Math.random() * 1000);
setTimeout(function() {
fetch('/api/updates/' + el.getAttribute('data-poll'))
.then(response => response.json())
.then(data => updateCountElement(el, data));
polling(el);
}, delay, el);
}
function updateCountElement(el, data) {
const currentCount = el.innerText;
const count = data[el.getAttribute('data-poll')];
if (count != currentCount) {
addRemoveClass(el, 'hidden', count < 1);
el.innerText = count;
}
}
function revealForm(e) { function revealForm(e) {
var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0]; var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0];
if (hidden) { if (hidden) {

View file

@ -106,17 +106,15 @@
</ul> </ul>
</div> </div>
<div class="navbar-item"> <div class="navbar-item">
<a href="/notifications"> <a href="/notifications" class="tags has-addons">
<div class="tags has-addons"> <span class="tag is-medium">
<span class="tag is-medium"> <span class="icon icon-bell" title="Notifications">
<span class="icon icon-bell" title="Notifications"> <span class="is-sr-only">Notifications</span>
<span class="is-sr-only">Notifications</span>
</span>
</span> </span>
{% if request.user|notification_count %} </span>
<span class="tag is-danger is-medium">{{ request.user | notification_count }}</span> <span class="{% if not request.user|notification_count %}hidden {% endif %}tag is-danger is-medium" data-poll="notifications">
{% endif %} {{ request.user | notification_count }}
</div> </span>
</a> </a>
</div> </div>
{% else %} {% else %}

View file

@ -8,7 +8,7 @@
<label class="label" for="id_goal">Reading goal:</label> <label class="label" for="id_goal">Reading goal:</label>
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">
<input type="number" class="input" name="goal" id="id_goal" value="{% if goal %}{{ goal.goal }}{% else %}12{% endif %}"> <input type="number" class="input" name="goal" min="1" id="id_goal" value="{% if goal %}{{ goal.goal }}{% else %}12{% endif %}">
</div> </div>
<p class="button is-static" aria-hidden="true">books</p> <p class="button is-static" aria-hidden="true">books</p>
</div> </div>

View file

@ -10,11 +10,11 @@
<input type="hidden" name="rating" value="{{ forloop.counter }}"> <input type="hidden" name="rating" value="{{ forloop.counter }}">
<div class="field is-grouped stars form-rate-stars mb-1"> <div class="field is-grouped stars form-rate-stars mb-1">
<label class="is-sr-only" for="no-rating-{{ book.id }}">No rating</label> <label class="is-sr-only" for="rating-no-rating-{{ book.id }}">No rating</label>
<input class="is-sr-only" type="radio" name="rating" value="" id="no-rating-{{ book.id }}" checked> <input class="is-sr-only" type="radio" name="rating" value="" id="rating-no-rating-{{ book.id }}" checked>
{% for i in '12345'|make_list %} {% for i in '12345'|make_list %}
<input class="is-sr-only" id="book{{book.id}}-star-{{ forloop.counter }}" type="radio" name="rating" value="{{ forloop.counter }}" {% if book|rating:user == forloop.counter %}checked{% endif %}> <input class="is-sr-only" id="rating-book{{book.id}}-star-{{ forloop.counter }}" type="radio" name="rating" value="{{ forloop.counter }}" {% if book|rating:user == forloop.counter %}checked{% endif %}>
<label class="icon icon-star-empty" for="book{{book.id}}-star-{{ forloop.counter }}"> <label class="icon icon-star-empty" for="rating-book{{book.id}}-star-{{ forloop.counter }}">
<span class="is-sr-only">{{ forloop.counter }} star{{ forloop.counter | pluralize }}</span> <span class="is-sr-only">{{ forloop.counter }} star{{ forloop.counter | pluralize }}</span>
</label> </label>
{% endfor %} {% endfor %}

View file

@ -31,14 +31,15 @@ urlpatterns = [
re_path(r'^inbox/?$', incoming.shared_inbox), re_path(r'^inbox/?$', incoming.shared_inbox),
re_path(r'%s/inbox/?$' % local_user_path, incoming.inbox), re_path(r'%s/inbox/?$' % local_user_path, incoming.inbox),
re_path(r'%s/outbox/?$' % local_user_path, views.Outbox.as_view()), re_path(r'%s/outbox/?$' % local_user_path, views.Outbox.as_view()),
# .well-known endpoints
re_path(r'^.well-known/webfinger/?$', wellknown.webfinger), re_path(r'^.well-known/webfinger/?$', wellknown.webfinger),
re_path(r'^.well-known/nodeinfo/?$', wellknown.nodeinfo_pointer), re_path(r'^.well-known/nodeinfo/?$', wellknown.nodeinfo_pointer),
re_path(r'^nodeinfo/2\.0/?$', wellknown.nodeinfo), re_path(r'^nodeinfo/2\.0/?$', wellknown.nodeinfo),
re_path(r'^api/v1/instance/?$', wellknown.instance_info), re_path(r'^api/v1/instance/?$', wellknown.instance_info),
re_path(r'^api/v1/instance/peers/?$', wellknown.peers), re_path(r'^api/v1/instance/peers/?$', wellknown.peers),
# polling updates
re_path('^api/updates/notifications/?$', views.Updates.as_view()),
# authentication # authentication
re_path(r'^login/?$', views.Login.as_view()), re_path(r'^login/?$', views.Login.as_view()),
re_path(r'^register/?$', views.Register.as_view()), re_path(r'^register/?$', views.Register.as_view()),

View file

@ -23,4 +23,5 @@ from .shelf import Shelf
from .shelf import user_shelves_page, create_shelf, delete_shelf from .shelf import user_shelves_page, create_shelf, delete_shelf
from .shelf import shelve, unshelve from .shelf import shelve, unshelve
from .status import Status, Replies, CreateStatus, DeleteStatus from .status import Status, Replies, CreateStatus, DeleteStatus
from .updates import Updates
from .user import User, EditUser, Followers, Following from .user import User, EditUser, Followers, Following

View file

@ -52,7 +52,7 @@ class Goal(View):
form = forms.GoalForm(request.POST, instance=goal) form = forms.GoalForm(request.POST, instance=goal)
if not form.is_valid(): if not form.is_valid():
data = { data = {
'title': '%s\'s %d Reading' % (goal.user.display_name, year), 'title': '%s\'s %d Reading' % (request.user.display_name, year),
'goal_form': form, 'goal_form': form,
'goal': goal, 'goal': goal,
'year': year, 'year': year,

View file

@ -43,7 +43,7 @@ class Import(View):
except (UnicodeDecodeError, ValueError): except (UnicodeDecodeError, ValueError):
return HttpResponseBadRequest('Not a valid csv file') return HttpResponseBadRequest('Not a valid csv file')
goodreads_import.start_import(job) goodreads_import.start_import(job)
return redirect('/import-status/%d' % job.id) return redirect('/import/%d' % job.id)
return HttpResponseBadRequest() return HttpResponseBadRequest()
@ -80,4 +80,4 @@ class ImportStatus(View):
items, items,
) )
goodreads_import.start_import(job) goodreads_import.start_import(job)
return redirect('/import-status/%d' % job.id) return redirect('/import/%d' % job.id)

17
bookwyrm/views/updates.py Normal file
View file

@ -0,0 +1,17 @@
''' endpoints for getting updates about activity '''
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
# pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch')
class Updates(View):
''' so the app can poll '''
def get(self, request):
''' any notifications waiting? '''
return JsonResponse({
'notifications': request.user.notification_set.filter(
read=False
).count(),
})