forked from mirrors/bookwyrm
Create goal with privacy
This commit is contained in:
parent
739b6e19e2
commit
112b9f9332
11 changed files with 161 additions and 16 deletions
|
@ -192,4 +192,4 @@ class ShelfForm(CustomForm):
|
|||
class GoalForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.AnnualGoal
|
||||
fields = ['user', 'year', 'goal']
|
||||
fields = ['user', 'year', 'goal', 'privacy']
|
||||
|
|
|
@ -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')},
|
||||
|
|
|
@ -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 '''
|
||||
|
|
|
@ -71,7 +71,8 @@
|
|||
{% if goal %}
|
||||
<section class="section">
|
||||
<div class="block">
|
||||
{{ goal }} hi
|
||||
<h3 class="title is-4">{{ goal.year }} Reading Goal</h3>
|
||||
{% include 'snippets/goal_progress.html' with goal=goal %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
43
bookwyrm/templates/goal.html
Normal file
43
bookwyrm/templates/goal.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block content %}
|
||||
|
||||
<section class="block">
|
||||
<h1 class="title">{{ year }} Reading Progress</h1>
|
||||
{% if not goal and user == request.user %}
|
||||
{% now 'Y' as year %}
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<h2 class="card-header-title has-background-primary has-text-white">
|
||||
<span class="icon icon-book is-size-3 mr-2" aria-hidden="true"></span> {{ year }} reading goal
|
||||
</h2>
|
||||
</header>
|
||||
<section class="card-content content">
|
||||
<p>Set a goal for how many books you'll finish reading in {{ year }}, and track your progress throughout the year.</p>
|
||||
|
||||
{% include 'snippets/goal_form.html' %}
|
||||
</section>
|
||||
</article>
|
||||
{% elif not goal and user != request.user %}
|
||||
<p>{{ user.display_name }} hasn't set a reading goal for {{ year }}.</p>
|
||||
{% else %}
|
||||
{% include 'snippets/goal_progress.html' with goal=goal %}
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if goal.books %}
|
||||
<section>
|
||||
<h2 class="title">{% if goal.user == request.user %}Your{% else %}{{ goal.user.display_name }}'s{% endif %} {{ year }} Books</h2>
|
||||
<div class="columns is-multiline">
|
||||
{% for book in goal.books %}
|
||||
<div class="column is-narrow">
|
||||
<div class="box">
|
||||
<a href="{{ book.book.local_path }}">
|
||||
{% include 'snippets/discover/small-book.html' with book=book.book rating=goal.ratings %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -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 %}
|
||||
|
||||
<h3 class="title is-6"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
|
||||
{% if book.authors %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form method="post" name="goal" action="/goal/{{ year }}">
|
||||
<form method="post" name="goal" action="{{ request.user.local_path }}/goal/{{ year }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="year" value="{{ year }}">
|
||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||
|
@ -15,16 +15,15 @@
|
|||
</div>
|
||||
|
||||
<div class="column">
|
||||
<label for="post_status" class="label">
|
||||
<input type="checkbox" name="post-status" id="post_status" class="checkbox" checked>
|
||||
Post to feed
|
||||
<label class="label"><p class="mb-2">Goal privacy:</p>
|
||||
{% include 'snippets/privacy_select.html' with no_label=True %}
|
||||
</label>
|
||||
|
||||
<div class="field">
|
||||
{% include 'snippets/privacy_select.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="post_status" class="label">
|
||||
<input type="checkbox" name="post-status" id="post_status" class="checkbox" checked>
|
||||
Post to feed
|
||||
</label>
|
||||
|
||||
<p>
|
||||
<button type="submit" class="button is-link">Set goal</button>
|
||||
|
|
10
bookwyrm/templates/snippets/goal_progress.html
Normal file
10
bookwyrm/templates/snippets/goal_progress.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<p>
|
||||
{% 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 %}<a href="{{ goal.local_path }}">{% endif %}{{ goal.book_count }} of {{ goal.goal }} books{% if request.path != goal.local_path %}</a>{% endif %}.
|
||||
</p>
|
||||
<progress class="progress is-large" value="{{ goal.book_count }}" max="{{ goal.goal }}">{{ goal.progress_percent }}%</progress>
|
||||
|
|
@ -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<year>\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()),
|
||||
|
|
|
@ -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
|
||||
|
|
63
bookwyrm/views/goal.py
Normal file
63
bookwyrm/views/goal.py
Normal file
|
@ -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', '/'))
|
Loading…
Reference in a new issue