Merge branch 'main' into progress_update
This commit is contained in:
commit
85edee42ef
|
@ -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)]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(),
|
||||||
|
})
|
Loading…
Reference in New Issue