forked from mirrors/bookwyrm
stars filter
This commit is contained in:
parent
668e714c31
commit
093f2d4fdd
8 changed files with 141 additions and 26 deletions
|
@ -28,6 +28,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.humanize',
|
||||||
'fedireads',
|
'fedireads',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ h2 {
|
||||||
background-color: #B2DBBF;
|
background-color: #B2DBBF;
|
||||||
padding: 0.5rem 0.2rem;
|
padding: 0.5rem 0.2rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#top-bar {
|
#top-bar {
|
||||||
|
@ -51,18 +52,21 @@ h2 {
|
||||||
#feed, #content, #sidebar {
|
#feed, #content, #sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 0 1rem 0 0;
|
margin: 0 1rem 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
min-width: 20rem;
|
min-width: 20rem;
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-pic {
|
.user-pic {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
vertical-align: middle;
|
vertical-align: top;
|
||||||
|
position: relative;
|
||||||
|
bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-preview {
|
.book-preview {
|
||||||
|
@ -72,7 +76,6 @@ h2 {
|
||||||
.book-preview img {
|
.book-preview img {
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-cover.small {
|
.book-cover.small {
|
||||||
|
@ -110,3 +113,23 @@ blockquote {
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #DDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 1em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
{% load fr_display %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div>
|
<div>
|
||||||
|
@ -6,13 +7,14 @@
|
||||||
<img class="book-cover" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}">
|
<img class="book-cover" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}">
|
||||||
<h1>{{ book.data.title }}</h1>
|
<h1>{{ book.data.title }}</h1>
|
||||||
by {{ book.authors.first.data.name }}
|
by {{ book.authors.first.data.name }}
|
||||||
{{ rating }} stars
|
{{ rating | stars }} {{ rating }}
|
||||||
<blockquote>{{ book.data.description }}</blockquote>
|
<blockquote>{{ book.data.description }}</blockquote>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Leave a review</h3>
|
<h3>Leave a review</h3>
|
||||||
|
|
||||||
<form class="review-form" name="review" action="/review/" method="post">
|
<form class="review-form" name="review" action="/review/" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="book" value="{{ book.openlibrary_key }}"></input>
|
<input type="hidden" name="book" value="{{ book.openlibrary_key }}"></input>
|
||||||
{{ review_form.as_p }}
|
{{ review_form.as_p }}
|
||||||
<button type="submit">Post review</button>
|
<button type="submit">Post review</button>
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
{% for review in reviews %}
|
{% for review in reviews %}
|
||||||
<div class="review">
|
<div class="review">
|
||||||
<h4>{{ review.name }}
|
<h4>{{ review.name }}
|
||||||
<small>{{ review.rating }} stars, by {{ review.user.username }}</small>
|
<small>{{ review.rating | stars }} stars, by {{ review.user.username }}</small>
|
||||||
</h4>
|
</h4>
|
||||||
<blockquote>{{ review.review_content }}</blockquote>
|
<blockquote>{{ review.review_content }}</blockquote>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
{% load fr_display %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
<div>
|
<div>
|
||||||
|
@ -103,7 +104,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>{{ activity.name }}</h3>
|
<h3>{{ activity.name }}</h3>
|
||||||
<p>{{ activity.rating }} stars</p>
|
<p>{{ activity.rating | stars }} stars</p>
|
||||||
<p>{{ activity.review_content }}</p>
|
<p>{{ activity.review_content }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="interaction"><button>⭐️ Like</button></div>
|
<div class="interaction"><button>⭐️ Like</button></div>
|
||||||
|
|
|
@ -1,51 +1,122 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load fr_display %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="content">
|
<div id="sidebar">
|
||||||
<div class="user-profile">
|
<div class="user-profile">
|
||||||
<img class="user-pic" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}">
|
<h2>
|
||||||
<h1>{% if user.name %}{{ user.name }}<br>{% endif %}{% if user.localname %}{{ user.localname }}{% else %}{{ user.username }}{% endif %}</h1>
|
<img class="user-pic" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}">
|
||||||
|
{% if user.name %}{{ user.name }}{% endif %}
|
||||||
|
<small>{{ user.username }}</small>
|
||||||
|
</h2>
|
||||||
{% if user.summary %}
|
{% if user.summary %}
|
||||||
<blockquote>{{ user.summary }}</blockquote>
|
<blockquote>{{ user.summary }}</blockquote>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_self %}<a href="/user/{{ user.localname }}/edit">Edit profile</a>
|
|
||||||
<p>Since {{ user.created_date }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if not is_self %}
|
{% if not is_self %}
|
||||||
{% if not following %}
|
{% if not request.user in user.followers.all %}
|
||||||
<form action="/follow/" method="post">
|
<form action="/follow/" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ user.id }}"></input>
|
<input type="hidden" name="user" value="{{ user.id }}"></input>
|
||||||
<input type="submit" value="Follow"></input>
|
<input type="submit" value="Follow"></input>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form action="/unfollow/" method="post">
|
<form action="/unfollow/" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ user.id }}"></input>
|
<input type="hidden" name="user" value="{{ user.id }}"></input>
|
||||||
<input type="submit" value="Unfollow"></input>
|
<input type="submit" value="Unfollow"></input>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
{% if is_self %}
|
||||||
<h2>Books</h2>
|
<div class="interaction">
|
||||||
{% for book in books.all %}
|
<a href="/user/{{ user.localname }}/edit">Edit profile</a>
|
||||||
<div class="book">
|
|
||||||
{{ book.data.title }} by {{ book.authors.first.data.name }}
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Followers</h2>
|
<h2>Followers</h2>
|
||||||
{% for follower in user.followers.all %}
|
{% for follower in user.followers.all %}
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ follower.actor }}">{{ follower.username }}</a>
|
<a href="{{ follower.actor }}">{{ follower.username }}</a>
|
||||||
|
{% if not request.user in follower.followers.all %}
|
||||||
<form action="/follow/" method="post">
|
<form action="/follow/" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ follower.id }}"></input>
|
<input type="hidden" name="user" value="{{ follower.id }}"></input>
|
||||||
<input type="submit" value="Follow"></input>
|
<input type="submit" value="Follow"></input>
|
||||||
</form>
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form action="/unfollow/" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="user" value="{{ follower.id }}"></input>
|
||||||
|
<input type="submit" value="Unfollow"></input>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
{% for shelf in shelves %}
|
||||||
|
<div>
|
||||||
|
<h2>{{ shelf.shelf.name }}</h2>
|
||||||
|
{% if shelf.books %}
|
||||||
|
<table>
|
||||||
|
<tr class="book-preview">
|
||||||
|
<th>
|
||||||
|
Cover
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Title
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Author
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Published
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Shelved
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
External links
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Rating
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for book in shelf.books.all %}
|
||||||
|
<tr class="book-preview">
|
||||||
|
<td>
|
||||||
|
<img src="/images/{{ book.cover }}" class="book-cover small">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ book.openlibrary_key }}">{{ book.data.title }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ book.authors.first.data.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ book.data.first_publish_date }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ book.added_date | naturalday }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://openlibrary.org{{ book.data.key }}" target="_blank">OpenLibrary</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ ratings | dict_key:book.id | stars}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>This shelf is empty.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
0
fedireads/templatetags/__init__.py
Normal file
0
fedireads/templatetags/__init__.py
Normal file
15
fedireads/templatetags/fr_display.py
Normal file
15
fedireads/templatetags/fr_display.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
''' template filters '''
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter(name='dict_key')
|
||||||
|
def dict_key(d, k):
|
||||||
|
'''Returns the given key from a dictionary.'''
|
||||||
|
return d.get(k) or 0
|
||||||
|
|
||||||
|
@register.filter(name='stars')
|
||||||
|
def stars(number):
|
||||||
|
''' turn integers into stars '''
|
||||||
|
number = int(number)
|
||||||
|
return ('★' * number) + '☆' * (5 - number)
|
|
@ -1,11 +1,10 @@
|
||||||
''' application views/pages '''
|
''' application views/pages '''
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Avg, Q
|
from django.db.models import Avg, FilteredRelation, Q
|
||||||
from django.http import HttpResponseNotFound
|
from django.http import HttpResponseNotFound
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from fedireads import forms, models, openlibrary, outgoing, incoming
|
from fedireads import forms, models, openlibrary, outgoing, incoming
|
||||||
|
@ -120,10 +119,13 @@ def user_profile(request, username):
|
||||||
except models.User.DoesNotExist:
|
except models.User.DoesNotExist:
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
books = models.Book.objects.filter(shelves__user=user)
|
shelves = models.Shelf.objects.filter(user=user)
|
||||||
|
ratings = {r.book.id: r.rating for r in models.Review.objects.filter(user=user, book__shelves__user=user)}
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'user': user,
|
'user': user,
|
||||||
'books': books,
|
'shelves': shelves,
|
||||||
|
'ratings': ratings,
|
||||||
'is_self': request.user.id == user.id,
|
'is_self': request.user.id == user.id,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'user.html', data)
|
return TemplateResponse(request, 'user.html', data)
|
||||||
|
|
Loading…
Reference in a new issue