diff --git a/bookwyrm/migrations/0038_auto_20210119_1534.py b/bookwyrm/migrations/0038_auto_20210119_1534.py
new file mode 100644
index 000000000..ac7a0d68f
--- /dev/null
+++ b/bookwyrm/migrations/0038_auto_20210119_1534.py
@@ -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)]),
+ ),
+ ]
diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py
index 1e9db4011..79d112062 100644
--- a/bookwyrm/models/user.py
+++ b/bookwyrm/models/user.py
@@ -4,6 +4,7 @@ from urllib.parse import urlparse
from django.apps import apps
from django.contrib.auth.models import AbstractUser
+from django.core.validators import MinValueValidator
from django.db import models
from django.dispatch import receiver
from django.utils import timezone
@@ -226,7 +227,9 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
class AnnualGoal(BookWyrmModel):
''' set a goal for how many books you read in a year '''
user = models.ForeignKey('User', on_delete=models.PROTECT)
- goal = models.IntegerField()
+ goal = models.IntegerField(
+ validators=[MinValueValidator(1)]
+ )
year = models.IntegerField(default=timezone.now().year)
privacy = models.CharField(
max_length=255,
diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js
index 55a5c1b20..e8ff9c469 100644
--- a/bookwyrm/static/js/shared.js
+++ b/bookwyrm/static/js/shared.js
@@ -31,8 +31,32 @@ window.onload = function() {
// hidden submit button in a form
document.querySelectorAll('.hidden-form input')
.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) {
var hidden = e.currentTarget.closest('.hidden-form').getElementsByClassName('hidden')[0];
if (hidden) {
diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html
index 30c8bf7a4..ddbafbb42 100644
--- a/bookwyrm/templates/layout.html
+++ b/bookwyrm/templates/layout.html
@@ -106,17 +106,15 @@
{% else %}
diff --git a/bookwyrm/templates/snippets/goal_form.html b/bookwyrm/templates/snippets/goal_form.html
index bd36f6090..475c5da5c 100644
--- a/bookwyrm/templates/snippets/goal_form.html
+++ b/bookwyrm/templates/snippets/goal_form.html
@@ -8,7 +8,7 @@
diff --git a/bookwyrm/templates/snippets/rate_action.html b/bookwyrm/templates/snippets/rate_action.html
index 2a376f50d..7a82501ab 100644
--- a/bookwyrm/templates/snippets/rate_action.html
+++ b/bookwyrm/templates/snippets/rate_action.html
@@ -10,11 +10,11 @@
-
-
+
+
{% for i in '12345'|make_list %}
-
-