From 112b9f933229251eb46fa8f9a8dcac4c2fe4f1d6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 16 Jan 2021 11:34:19 -0800 Subject: [PATCH] Create goal with privacy --- bookwyrm/forms.py | 2 +- bookwyrm/migrations/0036_annualgoal.py | 7 ++- bookwyrm/models/user.py | 28 ++++++++- bookwyrm/templates/feed.html | 3 +- bookwyrm/templates/goal.html | 43 +++++++++++++ .../snippets/discover/small-book.html | 2 + bookwyrm/templates/snippets/goal_form.html | 15 +++-- .../templates/snippets/goal_progress.html | 10 +++ bookwyrm/urls.py | 3 + bookwyrm/views/__init__.py | 1 + bookwyrm/views/goal.py | 63 +++++++++++++++++++ 11 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 bookwyrm/templates/goal.html create mode 100644 bookwyrm/templates/snippets/goal_progress.html create mode 100644 bookwyrm/views/goal.py diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index d57d90f3a..0e3ac9c1f 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -192,4 +192,4 @@ class ShelfForm(CustomForm): class GoalForm(CustomForm): class Meta: model = models.AnnualGoal - fields = ['user', 'year', 'goal'] + fields = ['user', 'year', 'goal', 'privacy'] diff --git a/bookwyrm/migrations/0036_annualgoal.py b/bookwyrm/migrations/0036_annualgoal.py index 9e86f6243..fb12833ea 100644 --- a/bookwyrm/migrations/0036_annualgoal.py +++ b/bookwyrm/migrations/0036_annualgoal.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.7 on 2021-01-15 23:01 +# Generated by Django 3.0.7 on 2021-01-16 18:43 import bookwyrm.models.fields from django.conf import settings @@ -20,9 +20,10 @@ class Migration(migrations.Migration): ('created_date', models.DateTimeField(auto_now_add=True)), ('updated_date', models.DateTimeField(auto_now=True)), ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), - ('goal', bookwyrm.models.fields.IntegerField()), + ('goal', models.IntegerField()), ('year', models.IntegerField(default=2021)), - ('user', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('privacy', models.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ], options={ 'unique_together': {('user', 'year')}, diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 5c59496e4..6697b1b8e 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -19,7 +19,7 @@ from bookwyrm.utils import regex from .base_model import OrderedCollectionPageMixin from .base_model import ActivitypubMixin, BookWyrmModel from .federated_server import FederatedServer -from . import fields +from . import fields, Review class User(OrderedCollectionPageMixin, AbstractUser): @@ -224,9 +224,14 @@ class KeyPair(ActivitypubMixin, BookWyrmModel): class AnnualGoal(BookWyrmModel): ''' set a goal for how many books you read in a year ''' - user = fields.ForeignKey('User', on_delete=models.PROTECT) - goal = fields.IntegerField() + user = models.ForeignKey('User', on_delete=models.PROTECT) + goal = models.IntegerField() year = models.IntegerField(default=timezone.now().year) + privacy = models.CharField( + max_length=255, + default='public', + choices=fields.PrivacyLevels.choices + ) class Meta: ''' unqiueness constraint ''' @@ -243,6 +248,23 @@ class AnnualGoal(BookWyrmModel): finish_date__year__gte=self.year ).order_by('finish_date').all() + + @property + def ratings(self): + ''' ratings for books read this year ''' + book_ids = [r.book.id for r in self.books] + reviews = Review.objects.filter( + user=self.user, + book__in=book_ids, + ) + return {r.book.id: r.rating for r in reviews} + + + @property + def progress_percent(self): + return int(float(self.book_count / self.goal) * 100) + + @property def book_count(self): ''' how many books you've read this year ''' diff --git a/bookwyrm/templates/feed.html b/bookwyrm/templates/feed.html index b37c6b74b..79dd4b85e 100644 --- a/bookwyrm/templates/feed.html +++ b/bookwyrm/templates/feed.html @@ -71,7 +71,8 @@ {% if goal %}
- {{ goal }} hi +

{{ goal.year }} Reading Goal

+ {% include 'snippets/goal_progress.html' with goal=goal %}
{% endif %} diff --git a/bookwyrm/templates/goal.html b/bookwyrm/templates/goal.html new file mode 100644 index 000000000..79d694de5 --- /dev/null +++ b/bookwyrm/templates/goal.html @@ -0,0 +1,43 @@ +{% extends 'layout.html' %} +{% block content %} + +
+

{{ year }} Reading Progress

+ {% if not goal and user == request.user %} + {% now 'Y' as year %} +
+
+

+ {{ year }} reading goal +

+
+
+

Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.

+ + {% include 'snippets/goal_form.html' %} +
+
+ {% elif not goal and user != request.user %} +

{{ user.display_name }} hasn't set a reading goal for {{ year }}.

+ {% else %} + {% include 'snippets/goal_progress.html' with goal=goal %} + {% endif %} +
+ +{% if goal.books %} +
+

{% if goal.user == request.user %}Your{% else %}{{ goal.user.display_name }}'s{% endif %} {{ year }} Books

+ +
+{% endif %} +{% endblock %} diff --git a/bookwyrm/templates/snippets/discover/small-book.html b/bookwyrm/templates/snippets/discover/small-book.html index 76fd2db78..be399df66 100644 --- a/bookwyrm/templates/snippets/discover/small-book.html +++ b/bookwyrm/templates/snippets/discover/small-book.html @@ -1,7 +1,9 @@ {% load bookwyrm_tags %} {% if book %} {% include 'snippets/book_cover.html' with book=book %} +{% if ratings %} {% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %} +{% endif %}

{{ book.title }}

{% if book.authors %} diff --git a/bookwyrm/templates/snippets/goal_form.html b/bookwyrm/templates/snippets/goal_form.html index d5236f90a..2e23aedd2 100644 --- a/bookwyrm/templates/snippets/goal_form.html +++ b/bookwyrm/templates/snippets/goal_form.html @@ -1,4 +1,4 @@ -
+ {% csrf_token %} @@ -15,16 +15,15 @@
-
+

diff --git a/bookwyrm/templates/snippets/goal_progress.html b/bookwyrm/templates/snippets/goal_progress.html new file mode 100644 index 000000000..b727790dc --- /dev/null +++ b/bookwyrm/templates/snippets/goal_progress.html @@ -0,0 +1,10 @@ +

+ {% if goal.progress_percent >= 100 %} + Success! + {% elif goal.progress_percent %} + {{ goal.progress_percent }}% complete! + {% endif %} + {% if goal.user == request.user %}You've{% else %}{{ goal.user.display_name }} has{% endif %} read {% if request.path != goal.local_path %}{% endif %}{{ goal.book_count }} of {{ goal.goal }} books{% if request.path != goal.local_path %}{% endif %}. +

+{{ goal.progress_percent }}% + diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 4ed162884..f014d44d8 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -75,6 +75,9 @@ urlpatterns = [ re_path(r'%s/following(.json)?/?$' % user_path, views.Following.as_view()), re_path(r'^edit-profile/?$', views.EditUser.as_view()), + # reading goals + re_path(r'%s/goal/(?P\d{4})/?$' % user_path, views.Goal.as_view()), + # statuses re_path(r'%s(.json)?/?$' % status_path, views.Status.as_view()), re_path(r'%s/activity/?$' % status_path, views.Status.as_view()), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 6351a87a6..e1ffeda42 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -7,6 +7,7 @@ from .direct_message import DirectMessage from .error import not_found_page, server_error_page from .follow import follow, unfollow from .follow import accept_follow_request, delete_follow_request, handle_accept +from .goal import Goal from .import_data import Import, ImportStatus from .interaction import Favorite, Unfavorite, Boost, Unboost from .invite import ManageInvites, Invite diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py new file mode 100644 index 000000000..4252af078 --- /dev/null +++ b/bookwyrm/views/goal.py @@ -0,0 +1,63 @@ +''' non-interactive pages ''' +from django.contrib.auth.decorators import login_required +from django.http import HttpResponseNotFound +from django.shortcuts import redirect +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import forms, models +from .helpers import get_user_from_username + + +# pylint: disable= no-self-use +@method_decorator(login_required, name='dispatch') +class Goal(View): + ''' track books for the year ''' + def get(self, request, username, year): + ''' reading goal page ''' + user = get_user_from_username(username) + year = int(year) + goal = models.AnnualGoal.objects.filter( + year=year, user=user + ).first() + if not goal and user != request.user: + return redirect('/') + + if goal and user != request.user: + if goal.privacy == 'direct' or \ + (goal.privacy == 'followers' and not follower): + return HttpResponseNotFound() + + data = { + 'title': '%s\'s %d Reading' % (user.display_name, year), + 'goal_form': forms.GoalForm(instance=goal), + 'goal': goal, + 'user': user, + 'year': year, + } + return TemplateResponse(request, 'goal.html', data) + + + def post(self, request, username, year): + ''' update or create an annual goal ''' + user = get_user_from_username(username) + if user != request.user: + return HttpResponseNotFound() + + year = int(year) + goal = models.AnnualGoal.objects.filter( + year=year, user=request.user + ).first() + form = forms.GoalForm(request.POST, instance=goal) + if not form.is_valid(): + data = { + 'title': '%s\'s %d Reading' % (goal.user.display_name, year), + 'goal_form': form, + 'goal': goal, + 'year': year, + } + return TemplateResponse(request, 'goal.html', data) + form.save() + + return redirect(request.headers.get('Referer', '/'))