user can track featured collections

This commit is contained in:
Your Name 2023-01-13 23:48:28 -05:00 committed by Henri Dickson
parent 0cf37ef0fd
commit 6a745b6dfa
10 changed files with 280 additions and 15 deletions

View file

@ -114,17 +114,17 @@ div.jsoneditor-menu {
@keyframes fadeIn {
0% {opacity: 0;}
100% {opacity: 1;}
}
}
@keyframes fadeOut {
0% {opacity: 1;}
100% {opacity: 0;}
}
}
@keyframes zoomIn {
0% {transform: scale(0.9);}
100% {transform: scale(1);}
}
}
@keyframes zoomOut {
0% {transform: scale(1);}
@ -159,3 +159,25 @@ div.jsoneditor-menu {
#modal li, #modal ul, #modal label {
display: inline;
}
.donut {
margin-left: 20%;
width: 60%;
aspect-ratio : 1 / 1;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.donut .hole {
width: 80%;
aspect-ratio : 1 / 1;
border-radius: 50%;
background: #f7f7f7;
display: table;
}
.donut .text {
display: table-cell;
vertical-align: middle;
text-align: center;
}

View file

@ -5,6 +5,7 @@
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
{% load collection %}
<div class="grid__aside grid__aside--reverse-order grid__aside--tablet-column">
<div class="aside-section-wrapper aside-section-wrapper--no-margin">
@ -43,11 +44,47 @@
</svg>
</span>
</div>
{% if user.featured_collections %}
<div class="relation-dropdown__body">
<div class="aside-section-wrapper aside-section-wrapper--transparent aside-section-wrapper--collapse">
<style type="text/css">
progress {
border-radius: 7px;
width: 80%;
height: 10px;
box-shadow: 1px 1px 4px rgba( 0, 0, 0, 0.2 );
}
::-webkit-progress-bar {
background-color: #ccc;
}
::-webkit-progress-value {
background-color: #00a1cc;
}
</style>
<div class="user-relation">
<h5 class="user-relation__label">
{% trans '当前目标' %}
</h5>
{% for featured_collection in user.featured_collections.all %}
{% user_visibility_of featured_collection as visible %}
{% if visible %}
{% user_progress_of collection=featured_collection user=user as progress %}
<div class="tag-collection" style="margin-left: 10%;">
<a href="{{ featured_collection.collection.url }}">{{ featured_collection.collection.title }}</a><br>
已完成{{ progress }}% <br>
<progress value="{{ progress }}" max="100">
</div>
{% endif %}
{% endfor %}
<br>
</div>
</div>
</div>
{% endif %}
{% if user == request.user %}
<div class="relation-dropdown__body">
<div
class="aside-section-wrapper aside-section-wrapper--transparent aside-section-wrapper--collapse">
<div class="aside-section-wrapper aside-section-wrapper--transparent aside-section-wrapper--collapse">
<div class="user-relation" id="followings">
<h5 class="user-relation__label">
{% trans '关注的人' %}
@ -100,7 +137,7 @@
{% endif %}
</div>
<div
class="aside-section-wrapper aside-section-wrapper--transparent aside-section-wrapper--collapse">
{% if request.user.is_staff and request.user == user%}

View file

@ -0,0 +1,58 @@
# Generated by Django 3.2.16 on 2023-01-14 03:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("journal", "0004_alter_shelflogentry_timestamp"),
]
operations = [
migrations.CreateModel(
name="FeaturedCollection",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_time", models.DateTimeField(auto_now_add=True)),
("edited_time", models.DateTimeField(auto_now=True)),
(
"collection",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="journal.collection",
),
),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"unique_together": {("owner", "collection")},
},
),
migrations.AddField(
model_name="collection",
name="featured_by_users",
field=models.ManyToManyField(
related_name="featured_collections",
through="journal.FeaturedCollection",
to=settings.AUTH_USER_MODEL,
),
),
]

View file

@ -679,6 +679,9 @@ class Collection(List):
collaborative = models.PositiveSmallIntegerField(
default=0
) # 0: Editable by owner only / 1: Editable by bi-direction followers
featured_by_users = models.ManyToManyField(
to=User, related_name="featured_collections", through="FeaturedCollection"
)
@property
def html(self):
@ -690,6 +693,26 @@ class Collection(List):
html = markdown(self.brief)
return _RE_HTML_TAG.sub(" ", html)
def is_featured_by_user(self, user):
return self.featured_by_users.all().filter(id=user.id).exists()
def get_stats_for_user(self, user):
items = list(self.members.all().values_list("item_id", flat=True))
stats = {"total": len(items)}
for st, shelf in user.shelf_manager.shelf_list.items():
stats[st] = shelf.members.all().filter(item_id__in=items).count()
stats["percentage"] = (
round(stats["complete"] * 100 / stats["total"]) if stats["total"] else 0
)
return stats
def get_progress_for_user(self, user):
items = list(self.members.all().values_list("item_id", flat=True))
if len(items) == 0:
return 0
shelf = user.shelf_manager.shelf_list["complete"]
return shelf.members.all().filter(item_id__in=items).count() * 100 / len(items)
def save(self, *args, **kwargs):
if getattr(self, "catalog_item", None) is None:
self.catalog_item = CatalogCollection()
@ -704,6 +727,16 @@ class Collection(List):
super().save(*args, **kwargs)
class FeaturedCollection(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
created_time = models.DateTimeField(auto_now_add=True)
edited_time = models.DateTimeField(auto_now=True)
class Meta:
unique_together = [["owner", "collection"]]
"""
Tag
"""

View file

@ -62,7 +62,7 @@
{% endif %}
</div>
</div>
<!-- <div class="dividing-line"></div> --> <!-- <div class="entity-card__img-wrapper" style="text-align: center;"> <img src="{{ collection.cover|thumb:'normal' }}" alt="" class="entity-card__img"> </div> -->
<!-- <div class="dividing-line"></div> --> <!-- <div class="entity-card__img-wrapper" style="text-align: center;"> <img src="{{ collection.cover|thumb:'normal' }}" alt="" class="entity-card__img"> </div> -->
{{ collection.html | safe }}
</div>
@ -91,7 +91,46 @@
</div>
</div>
{% if request.user.is_authenticated and request.user != collection.owner %}
{% if is_featured %}
<div class="aside-section-wrapper">
<div class="action-panel">
<div class="donut" style="background: conic-gradient(#7CBDFE 0deg {{ stats.complete_deg }}deg, #B4D2A5 {{ stats.complete_deg }}deg {{ stats.complete_deg|add:stats.progress_deg }}deg, #ccc {{ stats.complete_deg|add:stats.progress_deg }}deg 1deg );"><div class="hole"><div class="text">
{% if stats.progress %}
{{ stats.progress }} 进行中<br>
{% endif %}
{% if stats.complete %}
{{ stats.complete }} 已完成
{% elif not stats.progress %}
尚未开始
{% endif %}
</div></div></div>
</div>
<div class="action-panel" style="margin-bottom: 0;">
<div class="action-panel__button-group action-panel__button-group--center">
<form action="{% url 'journal:collection_remove_featured' collection.uuid %}" method="post">
{% csrf_token %}
<button class="action-panel__button" title="点击取消目标设置">{% trans '当前进度' %} {{ stats.percentage }}%</button>
</form>
</div>
</div>
</div>
{% endif %}
{% if available_as_featured %}
<div class="aside-section-wrapper">
<div class="action-panel">
<div class="action-panel__button-group action-panel__button-group--center">
<form action="{% url 'journal:collection_add_featured' collection.uuid %}" method="post">
{% csrf_token %}
<button class="action-panel__button">{% trans '设为目标' %}</button>
</form>
</div>
</div>
</div>
{% endif %}
{% if not is_featured and request.user.is_authenticated and request.user != collection.owner %}
<div class="aside-section-wrapper">
<div class="action-panel">
<div class="action-panel__button-group action-panel__button-group--center">
@ -105,7 +144,7 @@
{% csrf_token %}
<button class="action-panel__button">{% trans '关注' %}</button>
</form>
{% endif %}
{% endif %}
</div>
</div>
</div>

View file

@ -0,0 +1,18 @@
from django import template
from journal.models import Collection, Like
from django.shortcuts import reverse
register = template.Library()
@register.simple_tag(takes_context=True)
def user_visibility_of(context, piece):
user = context["request"].user
return piece.is_visible_to(user)
@register.simple_tag()
def user_progress_of(collection, user):
return (
collection.get_progress_for_user(user) if user and user.is_authenticated else 0
)

View file

@ -70,6 +70,16 @@ urlpatterns = [
collection_update_item_note,
name="collection_update_item_note",
),
path(
"collection/<str:collection_uuid>/add_featured",
collection_add_featured,
name="collection_add_featured",
),
path(
"collection/<str:collection_uuid>/remove_featured",
collection_remove_featured,
name="collection_remove_featured",
),
re_path(
r"^users/(?P<user_name>[A-Za-z0-9_\-.@]+)/(?P<shelf_type>"
+ _get_all_shelf_types()

View file

@ -1,4 +1,5 @@
import logging
from os import stat
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required, permission_required
@ -179,6 +180,27 @@ def collection_retrieve(request, collection_uuid):
if request.user.is_authenticated
else False
)
is_featured = request.user.is_authenticated and collection.is_featured_by_user(
request.user
)
available_as_featured = (
request.user.is_authenticated
and (following or request.user == collection.owner)
and not is_featured
and collection.members.all().exists()
)
stats = {}
if is_featured:
stats = collection.get_stats_for_user(request.user)
stats["wishlist_deg"] = (
stats["wishlist"] / stats["total"] * 360 if stats["total"] else 0
)
stats["progress_deg"] = (
stats["progress"] / stats["total"] * 360 if stats["total"] else 0
)
stats["complete_deg"] = (
stats["complete"] / stats["total"] * 360 if stats["total"] else 0
)
return render(
request,
"collection.html",
@ -186,10 +208,33 @@ def collection_retrieve(request, collection_uuid):
"collection": collection,
"follower_count": follower_count,
"following": following,
"stats": stats,
"available_as_featured": available_as_featured,
"is_featured": is_featured,
},
)
def collection_add_featured(request, collection_uuid):
if request.method != "POST":
return HttpResponseBadRequest()
collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
if not collection.is_visible_to(request.user):
raise PermissionDenied()
request.user.featured_collections.add(collection)
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
def collection_remove_featured(request, collection_uuid):
if request.method != "POST":
return HttpResponseBadRequest()
collection = get_object_or_404(Collection, uid=base62.decode(collection_uuid))
if not collection.is_visible_to(request.user):
raise PermissionDenied()
request.user.featured_collections.remove(collection)
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
def collection_share(request, collection_uuid):
pass

View file

@ -28,7 +28,7 @@ from django.contrib import messages
from journal.importers.douban import DoubanImporter
from journal.importers.goodreads import GoodreadsImporter
from journal.models import reset_visibility_for_user
from journal.models import reset_visibility_for_user, Collection
@mastodon_request_included
@ -54,7 +54,11 @@ def preferences(request):
"show_last_edit",
]
)
return render(request, "users/preferences.html")
return render(
request,
"users/preferences.html",
{"collections": Collection.objects.filter(owner=request.user)},
)
@mastodon_request_included

View file

@ -27,11 +27,11 @@
<div class="grid__main grid__main--reverse-order">
<div class="main-section-wrapper">
<form action="{% url 'users:preferences' %}" method="POST">
{% csrf_token %}
<div class="tools-section-wrapper">
<div class="import-panel">
<h5 class="import-panel__label">{% trans '使用偏好设置' %}</h5>
<div class="import-panel__body">
{% csrf_token %}
<span>{% trans '新标记默认可见性:' %}</span>
<div class="import-panel__checkbox import-panel__checkbox--last">
<label for="id_visibility_0"><input type="radio" name="default_visibility" value="0" required="" id="id_visibility_0" {%if request.user.preference.default_visibility == 0 %}checked{% endif %}>
@ -67,7 +67,6 @@
<div class="import-panel">
<h5 class="import-panel__label">{% trans '社交网络分享相关设置' %}</h5>
<div class="import-panel__body">
{% csrf_token %}
<span>{% trans '在联邦网络上以公开方式分享的帖文是否发布到公共时间轴上:' %}</span>
<div class="import-panel__checkbox import-panel__checkbox--last">
<input type="checkbox" name="mastodon_publish_public" id="visibility" {%if request.user.preference.mastodon_publish_public %}checked{% endif %}>
@ -102,4 +101,4 @@
</body>
</html>
</html>