Merge remote-tracking branch 'neo/main' into generalization

This commit is contained in:
doubaniux 2022-11-26 19:41:14 +01:00
commit 2645bc05f0
35 changed files with 863 additions and 430 deletions

3
.gitignore vendored
View file

@ -31,3 +31,6 @@ log
# typesense folder
/typesense-data
# test coverage
.coverage

1034
LICENSE

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
# Boofilsic
An application allows you to mark any books, movies and more things you love.
Depends on Mastodon.
Works with Mastodon API and Twitter API.
## Install
Please see [doc/GUIDE.md](doc/GUIDE.md)

View file

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
import requests
import psycopg2.extensions
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@ -296,9 +297,9 @@ TMDB_API3_KEY = "***REMOVED***"
GOOGLE_API_KEY = '***REMOVED***'
# IGDB
IGDB_CLIENT_ID = '***REMOVED***'
IGDB_SECRET = "***REMOVED***"
IGDB_ACCESS_TOKEN = '***REMOVED***'
IGDB_CLIENT_ID = 'deadbeef'
IGDB_CLIENT_SECRET = 'deadbeef'
IGDB_ACCESS_TOKEN = requests.post(f'https://id.twitch.tv/oauth2/token?client_id={IGDB_CLIENT_ID}&client_secret={IGDB_CLIENT_SECRET}&grant_type=client_credentials').json()['access_token']
# Thumbnail setting
# It is possible to optimize the image size even more: https://easy-thumbnails.readthedocs.io/en/latest/ref/optimize/

View file

@ -120,6 +120,10 @@ class Book(Entity):
else:
return [self] # Book.objects.filter(id=self.id)
@property
def year(self):
return self.pub_year
@property
def verbose_category_name(self):
return _("书籍")

View file

@ -99,7 +99,7 @@
{% endif %}
{% if book.last_editor %}
{% if book.last_editor and book.last_editor.preference.show_last_edit or user.is_staff %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' book.last_editor.mastodon_username %}">{{ book.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -210,7 +210,8 @@ def retrieve(request, id):
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
review_list = review_list[:REVIEW_NUMBER]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, CollectionItem.objects.filter(book=book)))
all_collections = CollectionItem.objects.filter(book=book).annotate(num_marks=Count('collection__collection_marks')).order_by('-num_marks')[:20]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, all_collections))
# def strip_html_tags(text):
# import re
@ -346,7 +347,7 @@ def wish(request, id):
params = {
'owner': request.user,
'status': MarkStatusEnum.WISH,
'visibility': 0,
'visibility': request.user.preference.default_visibility,
'book': book,
}
try:

View file

@ -92,6 +92,9 @@
{{ collection.title }}
</a>
</h5>
{% if follower_count %}
被 {{ follower_count }} 人关注
{% endif %}
</div>
</div>
</div>

View file

@ -10,7 +10,7 @@ from django.utils import timezone
from django.core.paginator import Paginator
from mastodon import mastodon_request_included
from mastodon.models import MastodonApplication
from mastodon.api import post_toot, TootVisibilityEnum, share_collection
from mastodon.api import share_collection
from common.utils import PageLinksGenerator
from common.views import PAGE_LINK_NUMBER, jump_or_scrape, go_relogin
from common.models import SourceSiteEnum
@ -153,12 +153,8 @@ def retrieve(request, id):
raise PermissionDenied()
form = CollectionForm(instance=collection)
if request.user.is_authenticated:
following = True if CollectionMark.objects.filter(owner=request.user, collection=collection).first() is not None else False
followers = []
else:
following = False
followers = []
following = True if request.user.is_authenticated and CollectionMark.objects.filter(owner=request.user, collection=collection).first() is not None else False
follower_count = CollectionMark.objects.filter(collection=collection).count()
return render(
request,
@ -167,7 +163,7 @@ def retrieve(request, id):
'collection': collection,
'form': form,
'editable': request.user.is_authenticated and collection.is_editable_by(request.user),
'followers': followers,
'follower_count': follower_count,
'following': following,
}
)
@ -184,10 +180,6 @@ def retrieve_entity_list(request, id):
raise PermissionDenied()
form = CollectionForm(instance=collection)
followers = []
if request.user.is_authenticated:
followers = []
return render(
request,
'entity_list.html',
@ -195,8 +187,6 @@ def retrieve_entity_list(request, id):
'collection': collection,
'form': form,
'editable': request.user.is_authenticated and collection.is_editable_by(request.user),
'followers': followers,
}
)

View file

@ -70,7 +70,7 @@ class GoodreadsImporter:
'rating': book['rating'],
'text': book['review'],
'status': status,
'visibility': 0,
'visibility': user.preference.default_visibility,
'book': book['book'],
}
if book['last_updated']:

View file

@ -154,8 +154,10 @@ class AbstractScraper:
if settings.LUMINATI_USERNAME is None:
proxies = None
if settings.SCRAPESTACK_KEY is not None:
url = f'http://api.scrapestack.com/scrape?access_key={settings.SCRAPESTACK_KEY}&url={url}'
if settings.PROXYCRAWL_KEY is not None:
url = f'https://api.proxycrawl.com/?token={settings.PROXYCRAWL_KEY}&url={url}'
# if settings.SCRAPESTACK_KEY is not None:
# url = f'http://api.scrapestack.com/scrape?access_key={settings.SCRAPESTACK_KEY}&url={url}'
else:
session_id = random.random()
proxy_url = ('http://%s-country-cn-session-%s:%s@zproxy.lum-superproxy.io:%d' %

View file

@ -42,7 +42,7 @@ class GoodreadsScraper(AbstractScraper):
content = self.download_page(url, headers)
try:
title = content.xpath("//h1[@id='bookTitle']/text()")[0].strip()
title = content.xpath("//h1/text()")[0].strip()
except IndexError:
raise ValueError("given url contains no book info")

View file

@ -199,10 +199,8 @@ class Indexer:
# print(r)
import types
results = types.SimpleNamespace()
results.items = list([x for x in map(lambda i: self.item_to_obj(
i['document']), r['hits']) if x is not None])
results.num_pages = (
r['found'] + SEARCH_PAGE_SIZE - 1) // SEARCH_PAGE_SIZE
results.items = list([x for x in map(lambda i: self.item_to_obj(i['document']), r['hits']) if x is not None])
results.num_pages = (r['found'] + SEARCH_PAGE_SIZE - 1) // SEARCH_PAGE_SIZE
# print(results)
return results

View file

@ -3,10 +3,10 @@
<div class="footer__border">
<a class="footer__link" target="_blank" href="https://donotban.com/@whitiewhite">原作者</a>
<a class="footer__link" target="_blank" href="{{ support_link }}">报告错误</a>
<a class="footer__link" target="_blank" href="https://github.com/doubaniux/boofilsic" id="githubLink">Github</a>
<a class="footer__link" target="_blank" href="https://github.com/neodb-social" id="githubLink">源代码</a>
<a class="footer__link" target="_blank" href="https://patreon.com/tertius" id="sponsor">捐助上游项目</a>
<a class="footer__link" target="_blank" href="/announcement/supported-sites/" id="supported-sites">支持的网站</a>
<a class="footer__link" target="_blank" href="/announcement/" id="supported-sites">公告栏</a>
</div>
</div>
</footer>
</footer>

View file

@ -48,11 +48,15 @@ def external_search(request):
category = None
keywords = request.GET.get("q", default='').strip()
page_number = int(request.GET.get('page', default=1))
items = ExternalSources.search(category, keywords, page_number) if keywords else []
dedupe_urls = request.session.get('search_dedupe_urls', [])
items = [i for i in items if i.source_url not in dedupe_urls]
return render(
request,
"common/external_search_result.html",
{
"external_items": ExternalSources.search(category, keywords, page_number) if keywords else [],
"external_items": items,
}
)
@ -85,18 +89,31 @@ def search(request):
pass
result = Indexer.search(keywords, page=page_number, category=category, tag=tag)
for item in result.items:
item.tag_list = item.all_tag_list[:TAG_NUMBER_ON_LIST]
keys = []
items = []
urls = []
for i in result.items:
key = i.isbn if hasattr(i, 'isbn') else (i.imdb_code if hasattr(i, 'imdb_code') else None)
if key is None:
items.append(i)
elif key not in keys:
keys.append(key)
items.append(i)
urls.append(i.source_url)
i.tag_list = i.all_tag_list[:TAG_NUMBER_ON_LIST]
if request.path.endswith('.json/'):
return JsonResponse({
'num_pages': result.num_pages,
'items':list(map(lambda i:i.get_json(), result.items))
'items':list(map(lambda i:i.get_json(), items))
})
request.session['search_dedupe_urls'] = urls
return render(
request,
"common/search_result.html",
{
"items": result.items,
"items": items,
"pagination": PageLinksGenerator(PAGE_LINK_NUMBER, page_number, result.num_pages),
"categories": ['book', 'movie', 'music', 'game'],
}

View file

@ -33,7 +33,7 @@ python3 manage.py check
Initialize database
```
python3 manage.py makemigrations users books movies games music sync mastodon management collection
python3 manage.py makemigrations users books movies games music sync mastodon management collection timeline
python3 manage.py migrate users
python3 manage.py migrate
```
@ -104,3 +104,11 @@ docker-compose build
docker-compose up db && docker exec -it app_db_1 psql -U postgres postgres -c 'CREATE EXTENSION hstore WITH SCHEMA public;' # first time only
docker-compose up
```
Run Tests
```
psql template1 -c 'create extension hstore;' # first time only
coverage run --source='.' manage.py test
coverage report
```

View file

@ -98,6 +98,10 @@ class Game(Entity):
def get_absolute_url(self):
return reverse("games:retrieve", args=[self.id])
@property
def year(self):
return self.release_date.year if self.release_date else None
@property
def wish_url(self):
return reverse("games:wish", args=[self.id])

View file

@ -133,7 +133,7 @@
{% if game.last_editor %}
{% if game.last_editor and game.last_editor.preference.show_last_edit or user.is_staff %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' game.last_editor.mastodon_username %}">{{ game.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -212,7 +212,8 @@ def retrieve(request, id):
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
review_list = review_list[:REVIEW_NUMBER]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, CollectionItem.objects.filter(game=game)))
all_collections = CollectionItem.objects.filter(game=game).annotate(num_marks=Count('collection__collection_marks')).order_by('-num_marks')[:20]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, all_collections))
# def strip_html_tags(text):
# import re
@ -348,7 +349,7 @@ def wish(request, id):
params = {
'owner': request.user,
'status': MarkStatusEnum.WISH,
'visibility': 0,
'visibility': request.user.preference.default_visibility,
'game': game,
}
try:

View file

@ -9,6 +9,7 @@ from django.shortcuts import reverse
from urllib.parse import quote
from .models import CrossSiteUserInfo, MastodonApplication
from mastodon.utils import rating_to_emoji
import re
logger = logging.getLogger(__name__)
@ -63,6 +64,7 @@ TWITTER_API_TOKEN = 'https://api.twitter.com/2/oauth2/token'
USER_AGENT = f"{settings.CLIENT_NAME}/1.0"
get = functools.partial(requests.get, timeout=settings.MASTODON_TIMEOUT)
put = functools.partial(requests.put, timeout=settings.MASTODON_TIMEOUT)
post = functools.partial(requests.post, timeout=settings.MASTODON_TIMEOUT)
@ -78,7 +80,7 @@ def get_relationships(site, id_list, token): # no longer in use
return response.json()
def post_toot(site, content, visibility, token, local_only=False):
def post_toot(site, content, visibility, token, local_only=False, update_id=None):
headers = {
'User-Agent': USER_AGENT,
'Authorization': f'Bearer {token}',
@ -90,6 +92,10 @@ def post_toot(site, content, visibility, token, local_only=False):
'text': content if len(content) <= 150 else content[0:150] + '...'
}
response = post(url, headers=headers, json=payload)
if response.status_code == 201:
response.status_code = 200
if response.status_code != 200:
logger.error(f"Error {url} {response.status_code}")
else:
url = 'https://' + site + API_PUBLISH_TOOT
payload = {
@ -99,7 +105,10 @@ def post_toot(site, content, visibility, token, local_only=False):
if local_only:
payload['local_only'] = True
try:
response = post(url, headers=headers, data=payload)
if update_id:
response = put(url + '/' + update_id, headers=headers, data=payload)
if update_id is None or response.status_code != 200:
response = post(url, headers=headers, data=payload)
if response.status_code == 201:
response.status_code = 200
if response.status_code != 200:
@ -402,7 +411,11 @@ def share_mark(mark):
tags = '\n' + user.get_preference().mastodon_append_tag.replace('[category]', str(mark.item.verbose_category_name)) if user.get_preference().mastodon_append_tag else ''
stars = rating_to_emoji(mark.rating, MastodonApplication.objects.get(domain_name=user.mastodon_site).star_mode)
content = f"{mark.translated_status}{mark.item.title}{stars}\n{mark.item.url}\n{mark.text}{tags}"
response = post_toot(user.mastodon_site, content, visibility, user.mastodon_token)
update_id = None
if mark.shared_link: # "https://mastodon.social/@username/1234567890"
r = re.match(r'.+/(\w+)$', mark.shared_link) # might be re.match(r'.+/([^/]+)$', u) if Pleroma supports edit
update_id = r[1] if r else None
response = post_toot(user.mastodon_site, content, visibility, user.mastodon_token, False, update_id)
if response and response.status_code in [200, 201]:
j = response.json()
if 'url' in j:
@ -428,7 +441,11 @@ def share_review(review):
visibility = TootVisibilityEnum.UNLISTED
tags = '\n' + user.get_preference().mastodon_append_tag.replace('[category]', str(review.item.verbose_category_name)) if user.get_preference().mastodon_append_tag else ''
content = f"发布了关于《{review.item.title}》的评论\n{review.url}\n{review.title}{tags}"
response = post_toot(user.mastodon_site, content, visibility, user.mastodon_token)
update_id = None
if review.shared_link: # "https://mastodon.social/@username/1234567890"
r = re.match(r'.+/(\w+)$', review.shared_link) # might be re.match(r'.+/([^/]+)$', u) if Pleroma supports edit
update_id = r[1] if r else None
response = post_toot(user.mastodon_site, content, visibility, user.mastodon_token, False, update_id)
if response and response.status_code in [200, 201]:
j = response.json()
if 'url' in j:

View file

@ -195,7 +195,7 @@
{% endif %}
{% if movie.last_editor %}
{% if movie.last_editor and movie.last_editor.preference.show_last_edit or user.is_staff %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' movie.last_editor.mastodon_username %}">{{ movie.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -211,7 +211,8 @@ def retrieve(request, id):
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
review_list = review_list[:REVIEW_NUMBER]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, CollectionItem.objects.filter(movie=movie)))
all_collections = CollectionItem.objects.filter(movie=movie).annotate(num_marks=Count('collection__collection_marks')).order_by('-num_marks')[:20]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, all_collections))
# def strip_html_tags(text):
# import re
@ -347,7 +348,7 @@ def wish(request, id):
params = {
'owner': request.user,
'status': MarkStatusEnum.WISH,
'visibility': 0,
'visibility': request.user.preference.default_visibility,
'movie': movie,
}
try:

View file

@ -55,6 +55,10 @@ class Album(Entity):
history = HistoricalRecords()
@property
def year(self):
return self.release_date.year if self.release_date else None
def __str__(self):
return self.title

View file

@ -125,7 +125,7 @@
{% endif %}
{% if album.last_editor %}
{% if album.last_editor and album.last_editor.preference.show_last_edit or user.is_staff %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' album.last_editor.mastodon_username %}">{{ album.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -113,7 +113,7 @@
{% endif %}
{% if song.last_editor %}
{% if song.last_editor and song.last_editor.preference.show_last_edit or user.is_staff %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' song.last_editor.mastodon_username %}">{{ song.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -218,7 +218,8 @@ def retrieve_song(request, id):
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
review_list = review_list[:REVIEW_NUMBER]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, CollectionItem.objects.filter(song=song)))
all_collections = CollectionItem.objects.filter(song=song).annotate(num_marks=Count('collection__collection_marks')).order_by('-num_marks')[:20]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, all_collections))
# def strip_html_tags(text):
# import re
@ -354,7 +355,7 @@ def wish_song(request, id):
params = {
'owner': request.user,
'status': MarkStatusEnum.WISH,
'visibility': 0,
'visibility': request.user.preference.default_visibility,
'song': song,
}
try:
@ -780,7 +781,8 @@ def retrieve_album(request, id):
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
review_list = review_list[:REVIEW_NUMBER]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, CollectionItem.objects.filter(album=album)))
all_collections = CollectionItem.objects.filter(album=album).annotate(num_marks=Count('collection__collection_marks')).order_by('-num_marks')[:20]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, all_collections))
# def strip_html_tags(text):
# import re
@ -916,7 +918,7 @@ def wish_album(request, id):
params = {
'owner': request.user,
'status': MarkStatusEnum.WISH,
'visibility': 0,
'visibility': request.user.preference.default_visibility,
'album': album,
}
try:

View file

@ -1,5 +1,6 @@
coverage
dateparser
django~=3.2.14
django~=3.2.16
django-hstore
django-markdownx @ git+https://github.com/alphatownsman/django-markdownx.git@e69480c64ad9c5d0499f4a8625da78cf2bb7691b
django-sass

View file

@ -10,7 +10,6 @@ from django.utils import timezone
from django.core.paginator import Paginator
from mastodon import mastodon_request_included
from mastodon.models import MastodonApplication
from mastodon.api import post_toot, TootVisibilityEnum
from common.utils import PageLinksGenerator
from .models import *
from books.models import BookTag

View file

@ -52,8 +52,9 @@ def preferences(request):
preference.default_visibility = int(request.POST.get('default_visibility'))
preference.classic_homepage = bool(request.POST.get('classic_homepage'))
preference.mastodon_publish_public = bool(request.POST.get('mastodon_publish_public'))
preference.show_last_edit = bool(request.POST.get('show_last_edit'))
preference.mastodon_append_tag = request.POST.get('mastodon_append_tag', '').strip()
preference.save(update_fields=['default_visibility', 'classic_homepage', 'mastodon_publish_public', 'mastodon_append_tag'])
preference.save(update_fields=['default_visibility', 'classic_homepage', 'mastodon_publish_public', 'mastodon_append_tag', 'show_last_edit'])
return render(request, 'users/preferences.html')

72
users/feeds.py Normal file
View file

@ -0,0 +1,72 @@
from django.contrib.syndication.views import Feed
from django.urls import reverse
from books.models import BookReview
from .models import User
from markdown import markdown
import operator
import mimetypes
MAX_ITEM_PER_TYPE = 10
class ReviewFeed(Feed):
def get_object(self, request, id):
return User.get(id)
def title(self, user):
return "%s的评论" % user.display_name
def link(self, user):
return user.url
def description(self, user):
return "%s的评论合集 - NeoDB" % user.display_name
def items(self, user):
if user is None:
return None
book_reviews = list(user.user_bookreviews.filter(visibility=0)[:MAX_ITEM_PER_TYPE])
movie_reviews = list(user.user_moviereviews.filter(visibility=0)[:MAX_ITEM_PER_TYPE])
album_reviews = list(user.user_albumreviews.filter(visibility=0)[:MAX_ITEM_PER_TYPE])
game_reviews = list(user.user_gamereviews.filter(visibility=0)[:MAX_ITEM_PER_TYPE])
all_reviews = sorted(
book_reviews + movie_reviews + album_reviews + game_reviews,
key=operator.attrgetter('created_time'),
reverse=True
)
return all_reviews
def item_title(self, item):
return f"{item.title} - 评论《{item.item.title}"
def item_description(self, item):
target_html = f'<p><a href="{item.item.url}">{item.item.title}</a></p>\n'
html = markdown(item.content)
return target_html + html
# item_link is only needed if NewsItem has no get_absolute_url method.
def item_link(self, item):
return item.url
def item_categories(self, item):
return [item.item.verbose_category_name]
def item_pubdate(self, item):
return item.created_time
def item_updateddate(self, item):
return item.edited_time
def item_enclosure_url(self, item):
return item.item.cover.url
def item_enclosure_mime_type(self, item):
t, _ = mimetypes.guess_type(item.item.cover.url)
return t
def item_enclosure_length(self, item):
return item.item.cover.file.size
def item_comments(self, item):
return item.shared_link

View file

@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
from common.utils import GenerateDateUUIDMediaFilePath
from django.conf import settings
from mastodon.api import *
from django.shortcuts import reverse
def report_image_path(instance, filename):
@ -59,6 +60,10 @@ class User(AbstractUser):
def display_name(self):
return self.mastodon_account['display_name'] if self.mastodon_account and 'display_name' in self.mastodon_account and self.mastodon_account['display_name'] else self.mastodon_username
@property
def url(self):
return reverse("users:home", args=[self.mastodon_username])
def __str__(self):
return self.mastodon_username
@ -166,6 +171,7 @@ class Preference(models.Model):
classic_homepage = models.BooleanField(null=False, default=False)
mastodon_publish_public = models.BooleanField(null=False, default=False)
mastodon_append_tag = models.CharField(max_length=2048, default='')
show_last_edit = models.PositiveSmallIntegerField(default=0)
def get_serialized_home_layout(self):
return str(self.home_layout).replace("\'", "\"")

View file

@ -16,6 +16,7 @@
{% else %}
<title>{{ site_name }} - {{user.display_name}}</title>
{% endif %}
<link rel="alternate" type="application/rss+xml" title="{{ site_name }} - {{ user.mastodon_username }}的评论" href="{{ request.build_absolute_uri }}feed/reviews/">
{% include "partial/_common_libs.html" with jquery=1 %}

View file

@ -6,6 +6,7 @@
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0;URL={{ login_url }}" />
<title>{{ site_name }} - {{ username }}@{{ site }}</title>
<link rel="alternate" type="application/rss+xml" title="{{ site_name }} - {{ username }}@{{ site }}的评论" href="{{ request.build_absolute_uri }}feed/reviews/">
<meta property="og:title" content="{{ site_name }} - {{ username }}@{{ site }}的书影音">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ request.build_absolute_uri }}">

View file

@ -44,9 +44,15 @@
<br>
<span>{% trans '登录后显示个人主页:' %}</span>
<div class="import-panel__checkbox import-panel__checkbox--last">
<input type="checkbox" name="classic_homepage" id="classic_homepage" {%if request.user.preference.classic_homepage %}checked{% endif %}>
<input type="checkbox" name="classic_homepage" id="classic_homepage" {%if request.user.preference.classic_homepage %}checked{% endif %} style="margin-bottom:1.5em">
<label for="classic_homepage">{% trans '默认登录后显示好友动态,如果希望登录后显示原版风格个人主页可选中此处' %}</label>
</div>
<br>
<span>{% trans '显示最近编辑者:' %}</span>
<div class="import-panel__checkbox import-panel__checkbox--last">
<input type="checkbox" name="show_last_edit" id="show_last_edit" {%if request.user.preference.show_last_edit %}checked{% endif %}>
<label for="show_last_edit">{% trans '默认不显示最近编辑条目的用户,除非该用户选中此选项。' %}</label>
</div>
</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
from django.urls import path
from .views import *
from .feeds import ReviewFeed
app_name = 'users'
urlpatterns = [
@ -37,6 +38,7 @@ urlpatterns = [
path('<str:id>/movie/<str:status>/', movie_list, name='movie_list'),
path('<str:id>/music/<str:status>/', music_list, name='music_list'),
path('<str:id>/game/<str:status>/', game_list, name='game_list'),
path('<str:id>/feed/reviews/', ReviewFeed(), name='review_feed'),
path('report/', report, name='report'),
path('manage_report/', manage_report, name='manage_report'),
]