add game module

This commit is contained in:
doubaniux 2021-02-25 19:43:43 +01:00
parent ac5ac98b22
commit b15497d4db
45 changed files with 3248 additions and 288 deletions

View file

@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/3.0/ref/settings/
import os
import psycopg2.extensions
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -29,6 +28,11 @@ DEBUG = True
ALLOWED_HOSTS = ['*']
# To allow debug in template context
# https://docs.djangoproject.com/en/3.1/ref/settings/#internal-ips
INTERNAL_IPS = [
"127.0.0.1"
]
# Application definition
@ -39,10 +43,18 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django.contrib.postgres',
'markdownx',
'users.apps.UsersConfig',
'management.apps.ManagementConfig',
'mastodon.apps.MastodonConfig',
'common.apps.CommonConfig',
'users.apps.UsersConfig',
'books.apps.BooksConfig',
'movies.apps.MoviesConfig',
'music.apps.MusicConfig',
'games.apps.GamesConfig',
'easy_thumbnails',
]
MIDDLEWARE = [
@ -86,7 +98,7 @@ if DEBUG:
'NAME': 'test',
'USER': 'donotban',
'PASSWORD': 'donotbansilvousplait',
'HOST': '172.18.47.7',
'HOST': '172.18.116.29',
'OPTIONS': {
'client_encoding': 'UTF8',
# 'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_DEFAULT,
@ -112,7 +124,7 @@ else:
# https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#authentication-backends
AUTHENTICATION_BACKENDS = [
'common.mastodon.auth.OAuth2Backend',
'mastodon.auth.OAuth2Backend',
]
@ -137,31 +149,30 @@ if not DEBUG:
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 31536000
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '{levelname} {asctime} {name}:{lineno} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'log'),
'formatter': 'simple'
},
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'log'),
'formatter': 'simple'
},
},
'loggers': {
'django': {
'root': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
}
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
@ -169,22 +180,31 @@ if not DEBUG:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
AUTH_USER_MODEL = 'users.User'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
CLIENT_ID = 'kEbwT9Je5HHg4FoLx4nb0tNaIrPNs5Mw6AYlQlsj2_4'
CLIENT_SECRET = 'xwmEvlmudLCkBmvdzGf8m41Ug5o5di9xnDqeVLrcKSg'
# Mastodon configs
CLIENT_NAME = 'NiceDB'
APP_WEBSITE = 'https://nicedb.org'
REDIRECT_URIS = "https://nicedb.org/users/OAuth2_login/\nhttps://www.nicedb.org/users/OAuth2_login/"
# Path to save report related images, ends without slash
# Path to save report related images, ends with slash
REPORT_MEDIA_PATH_ROOT = 'report/'
MARKDOWNX_MEDIA_PATH = 'review/'
BOOK_MEDIA_PATH_ROOT = 'book/'
DEFAULT_BOOK_IMAGE = os.path.join(BOOK_MEDIA_PATH_ROOT, 'default.svg')
# Mastodon domain name
MASTODON_DOMAIN_NAME = 'donotban.com'
MOVIE_MEDIA_PATH_ROOT = 'movie/'
DEFAULT_MOVIE_IMAGE = os.path.join(MOVIE_MEDIA_PATH_ROOT, 'default.svg')
SONG_MEDIA_PATH_ROOT = 'song/'
DEFAULT_SONG_IMAGE = os.path.join(SONG_MEDIA_PATH_ROOT, 'default.svg')
ALBUM_MEDIA_PATH_ROOT = 'album/'
DEFAULT_ALBUM_IMAGE = os.path.join(ALBUM_MEDIA_PATH_ROOT, 'default.svg')
GAME_MEDIA_PATH_ROOT = 'game/'
DEFAULT_GAME_IMAGE = os.path.join(GAME_MEDIA_PATH_ROOT, 'default.svg')
# Timeout of requests to Mastodon, in seconds
MASTODON_TIMEOUT = 30
@ -208,8 +228,31 @@ LOGIN_URL = '/users/login/'
ADMIN_URL = 'lpLuTqX72Bt2hLfxxRYKeTZdE59Y2hLfpLuTqX72Btx9sXuljYK4tYEmjrHd'
# Luminati proxy settings
LUMINATI_USERNAME = '***REMOVED***'
LUMINATI_PASSWORD = '***REMOVED***'
LUMINATI_USERNAME = 'lum-customer-hl_7bed6f85-zone-static'
LUMINATI_PASSWORD = 'dwy4lz5ck438'
# Spotify credentials
# SPOTIFY_CLIENT_ID = "***REMOVED***"
# SPOTIFY_CLIENT_SECRET = "***REMOVED***"
SPOTIFY_CREDENTIAL = "***REMOVED***"
# IMDb API service https://imdb-api.com/
IMDB_API_KEY = "***REMOVED***"
# Thumbnail setting
# It is possible to optimize the image size even more: https://easy-thumbnails.readthedocs.io/en/latest/ref/optimize/
THUMBNAIL_ALIASES = {
'': {
'normal': {
'size': (200, 200),
'crop': 'scale',
'autocrop': True,
},
},
}
# THUMBNAIL_PRESERVE_EXTENSIONS = ('svg',)
if DEBUG:
THUMBNAIL_DEBUG = True
# https://django-debug-toolbar.readthedocs.io/en/latest/
# maybe benchmarking before deployment

View file

@ -26,6 +26,7 @@ urlpatterns = [
path('books/', include('books.urls')),
path('movies/', include('movies.urls')),
path('music/', include('music.urls')),
path('games/', include('games.urls')),
path('announcement/', include('management.urls')),
path('', include('common.urls')),

View file

@ -89,7 +89,7 @@
{% if book.other_info %}
{% for k, v in book.other_info.items %}
<div>
{{k}}{{v}}
{{ k }}{{ v | urlize }}
</div>
{% endfor %}
{% endif %}

View file

@ -44,7 +44,7 @@
<h5>
{% trans '根据豆瓣内容填写下方表单' %}
</h5>
<iframe id='test' sandbox="allow-same-origin allow-scripts allow-popups allow-forms" src="https://search.douban.com/book/subject_search{% if q %}?search_text={{ q }}{% endif %}" frameborder="0"></iframe>
<iframe id='test' sandbox="allow-same-origin allow-scripts" src="https://search.douban.com/book/subject_search{% if q %}?search_text={{ q }}{% endif %}" frameborder="0"></iframe>
<div class="dividing-line"></div>
<div id="parser">
<label style="font-size: small;">{% trans '解析器:' %}</label>

20
common/config.py Normal file
View file

@ -0,0 +1,20 @@
# how many items are showed in one search result page
ITEMS_PER_PAGE = 20
# how many pages links in the pagination
PAGE_LINK_NUMBER = 7
# max tags on list page
TAG_NUMBER_ON_LIST = 5
# how many books have in each set at the home page
BOOKS_PER_SET = 5
# how many movies have in each set at the home page
MOVIES_PER_SET = 5
# how many music items have in each set at the home page
MUSIC_PER_SET = 5
# how many games have in each set at the home page
GAMES_PER_SET = 5

View file

@ -22,6 +22,7 @@ class SourceSiteEnum(models.TextChoices):
DOUBAN = "douban", _("豆瓣")
SPOTIFY = "spotify", _("Spotify")
IMDB = "imdb", _("IMDb")
STEAM = "steam", _("STEAM")
class Entity(models.Model):

View file

@ -22,6 +22,8 @@ from books.models import Book
from books.forms import BookForm
from music.models import Album, Song
from music.forms import AlbumForm, SongForm
from games.models import Game
from games.forms import GameForm
RE_NUMBERS = re.compile(r"\d+\d*")
@ -1018,3 +1020,131 @@ class ImdbMovieScraper(AbstractScraper):
@classmethod
def get_api_url(cls, url):
return f"https://imdb-api.com/zh/API/Title/{IMDB_API_KEY}/{cls.regex.findall(url)[0]}/FullActor,"
class DoubanGameScraper(AbstractScraper):
site_name = SourceSiteEnum.DOUBAN.value
host = 'www.douban.com/game/'
data_class = Game
form_class = GameForm
regex = re.compile(r"https://www\.douban\.com/game/\d+/{0,1}")
def scrape(self, url):
headers = DEFAULT_REQUEST_HEADERS.copy()
headers['Host'] = 'www.douban.com'
content = self.download_page(url, headers)
try:
raw_title = content.xpath(
"//div[@id='content']/h1/text()")[0].strip()
except IndexError:
raise ValueError("given url contains no movie info")
title = raw_title
other_title_elem = content.xpath(
"//dl[@class='game-attr']//dt[text()='别名:']/following-sibling::dd[1]/text()")
other_title = other_title_elem[0].strip().split(' / ') if other_title_elem else None
developer_elem = content.xpath(
"//dl[@class='game-attr']//dt[text()='开发商:']/following-sibling::dd[1]/text()")
developer = developer_elem[0].strip().split(' / ') if developer_elem else None
publisher_elem = content.xpath(
"//dl[@class='game-attr']//dt[text()='发行商:']/following-sibling::dd[1]/text()")
publisher = publisher_elem[0].strip().split(' / ') if publisher_elem else None
platform_elem = content.xpath(
"//dl[@class='game-attr']//dt[text()='平台:']/following-sibling::dd[1]/a/text()")
platform = platform_elem if platform_elem else None
genre_elem = content.xpath(
"//dl[@class='game-attr']//dt[text()='类型:']/following-sibling::dd[1]/a/text()")
genre = None
if genre_elem:
genre = [g for g in genre_elem if g != '游戏']
date_elem = content.xpath(
"//dl[@class='game-attr']//dt[text()='发行日期:']/following-sibling::dd[1]/text()")
release_date = dateparser.parse(date_elem[0].strip(), settings={
"RELATIVE_BASE": datetime.datetime(1900, 1, 1)}) if date_elem else None
brief_elem = content.xpath("//div[@class='mod item-desc']/p/text()")
brief = '\n'.join(brief_elem) if brief_elem else None
img_url_elem = content.xpath(
"//div[@class='item-subject-info']/div[@class='pic']//img/@src")
img_url = img_url_elem[0].strip() if img_url_elem else None
raw_img, ext = self.download_image(img_url)
data = {
'title': title,
'other_title': other_title,
'developer': developer,
'publisher': publisher,
'release_date': release_date,
'genre': genre,
'platform': platform,
'brief': brief,
'other_info': None,
'source_site': self.site_name,
'source_url': self.get_effective_url(url),
}
self.raw_data, self.raw_img, self.img_ext = data, raw_img, ext
return data, raw_img
class SteamGameScraper(AbstractScraper):
site_name = SourceSiteEnum.STEAM.value
host = 'store.steampowered.com'
data_class = Game
form_class = GameForm
regex = re.compile(r"https://store\.steampowered\.com/app/\d+/{0,1}")
def scrape(self, url):
headers = DEFAULT_REQUEST_HEADERS.copy()
headers['Host'] = self.host
content = self.download_page(url, headers)
title = content.xpath("//div[@class='apphub_AppName']/text()")[0]
developer = content.xpath("//div[@id='developers_list']/a/text()")
publisher = content.xpath("//div[@class='glance_ctn']//div[@class='dev_row'][2]//a/text()")
release_date = dateparser.parse(
content.xpath(
"//div[@class='release_date']/div[@class='date']/text()")[0],
settings={
"RELATIVE_BASE": datetime.datetime(1900, 1, 1)
}
)
genre = content.xpath(
"//div[@class='details_block']/b[2]/following-sibling::a/text()")
platform = ['PC']
brief = content.xpath(
"//div[@class='game_description_snippet']/text()")[0].strip()
img_url = content.xpath("//img[@class='game_header_image_full']/@src")[
0].replace("header.jpg", "library_600x900.jpg")
raw_img, ext = self.download_image(img_url)
data = {
'title': title,
'other_title': None,
'developer': developer,
'publisher': publisher,
'release_date': release_date,
'genre': genre,
'platform': platform,
'brief': brief,
'other_info': None,
'source_site': self.site_name,
'source_url': self.get_effective_url(url),
}
self.raw_data, self.raw_img, self.img_ext = data, raw_img, ext
return data, raw_img

View file

@ -1246,6 +1246,14 @@ select::placeholder {
font-weight: bold;
}
.source-label.source-label__steam {
background: linear-gradient(30deg, #1387b8, #111d2e);
color: white;
border: none;
font-weight: 600;
padding-top: 2px;
}
.main-section-wrapper {
padding: 32px 48px 32px 36px;
background-color: #f7f7f7;

File diff suppressed because one or more lines are too long

View file

@ -7,6 +7,8 @@ $spotify-color-primary: #1ed760
$spotify-color-secondary: black
$imdb-color-primary: #F5C518
$imdb-color-secondary: #121212
$steam-color-primary: #1387b8
$steam-color-secondary: #111d2e
.source-label
display: inline
@ -46,3 +48,9 @@ $imdb-color-secondary: #121212
color: $imdb-color-secondary
border: none
font-weight: bold
&.source-label__steam
background: linear-gradient(30deg, $steam-color-primary, $steam-color-secondary)
color: white
border: none
font-weight: 600
padding-top: 2px

View file

@ -65,7 +65,7 @@
{% endif %}
</a>
{% if not request.GET.c or request.GET.c != 'movie' and request.GET.c != 'book'%}
{% if not request.GET.c or not request.GET.c in categories %}
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
{% endif %}
<a href="{{ book.source_url }}">
@ -161,7 +161,7 @@
{% endif %}
</a>
{% if not request.GET.c or request.GET.c != 'movie' and request.GET.c != 'book'%}
{% if not request.GET.c or not request.GET.c in categories %}
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
{% endif %}
<a href="{{ movie.source_url }}">
@ -221,6 +221,86 @@
</li>
{% endwith %}
{% elif item.category_name|lower == 'game' %}
{% with game=item %}
<li class="entity-list__entity">
<div class="entity-list__entity-img-wrapper">
<a href="{% url 'games:retrieve' game.id %}">
<img src="{{ game.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
</a>
</div>
<div class="entity-list__entity-text">
<div class="entity-list__entity-title">
<a href="{% url 'games:retrieve' game.id %}" class="entity-list__entity-link">
{% if request.GET.q %}
{{ game.title | highlight:request.GET.q }}
{% else %}
{{ game.title }}
{% endif %}
</a>
{% if not request.GET.c or not request.GET.c in categories %}
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
{% endif %}
<a href="{{ game.source_url }}">
<span class="source-label source-label__{{ game.source_site }}">{{ game.get_source_site_display }}</span>
</a>
</div>
{% if game.rating %}
<div class="rating-star entity-list__rating-star" data-rating-score="{{ game.rating | floatformat:"0" }}"></div>
<span class="entity-list__rating-score rating-score">{{ game.rating }}</span>
{% else %}
<div class="entity-list__rating entity-list__rating--empty"> {% trans '暂无评分' %}</div>
{% endif %}
<span class="entity-list__entity-info entity-list__entity-info--full-length">
{% if game.other_title %}{% trans '别名' %}
{% for other_title in game.other_title %}
{{ other_title }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if game.developer %}{% trans '开发商' %}
{% for developer in game.developer %}
{{ developer }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if game.genre %}{% trans '类型' %}
{% for genre in game.genre %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if game.platform %}{% trans '平台' %}
{% for platform in game.platform %}
{{ platform }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ game.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in game.tag_list %}
{% for k, v in tag_dict.items %}
{% if k == 'content' %}
<span class="tag-collection__tag">
<a href="{% url 'common:search' %}?tag={{ v }}">{{ v }}</a>
</span>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</div>
</li>
{% endwith %}
{% elif item.category_name|lower == 'album' or item.category_name|lower == 'song' %}
{% with music=item %}
@ -260,7 +340,7 @@
{% endif %}
{% if not request.GET.c or request.GET.c != 'music' and request.GET.c != 'book' and request.GET.c != 'music' %}
{% if not request.GET.c or not request.GET.c in categories %}
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
{% endif %}
<a href="{{ music.source_url }}">
@ -371,7 +451,7 @@
<div class="add-entity-entries__label">
{% trans '没有想要的结果?' %}
</div>
{% if request.GET.c %}
{% if request.GET.c and request.GET.c in categories %}
{% if request.GET.c|lower == 'book' %}
@ -387,17 +467,6 @@
{% elif request.GET.c|lower == 'music' %}
<a href="{% url 'movies:create' %}">
<button class="add-entity-entries__button">{% trans '添加音乐' %}</button>
</a>
{% else %}
<a href="{% url 'books:create' %}">
<button class="add-entity-entries__button">{% trans '添加书' %}</button>
</a>
<a href="{% url 'movies:create' %}">
<button class="add-entity-entries__button">{% trans '添加电影/剧集' %}</button>
</a>
<a href="{% url 'music:create_album' %}">
<button class="add-entity-entries__button">{% trans '添加专辑' %}</button>
</a>
@ -405,6 +474,12 @@
<button class="add-entity-entries__button">{% trans '添加单曲' %}</button>
</a>
{% elif request.GET.c|lower == 'game' %}
<a href="{% url 'games:create' %}">
<button class="add-entity-entries__button">{% trans '添加游戏' %}</button>
</a>
{% endif %}
{% else %}
@ -420,10 +495,13 @@
<a href="{% url 'music:create_song' %}">
<button class="add-entity-entries__button">{% trans '添加单曲' %}</button>
</a>
<a href="{% url 'games:create' %}">
<button class="add-entity-entries__button">{% trans '添加游戏' %}</button>
</a>
{% endif %}
</div>
<div class="add-entity-entries__entry">
{% if request.GET.c %}
{% if request.GET.c and request.GET.c in categories %}
{% if request.GET.c|lower == 'book' %}
@ -443,6 +521,15 @@
<button class="add-entity-entries__button">{% trans '从表瓣剽取数据' %}</button>
</a>
{% elif request.GET.c|lower == 'game' %}
<div class="add-entity-entries__label">
{% trans '或者(≖ ◡ ≖)✧' %}
</div>
<a href="{% url 'games:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '从表瓣剽取数据' %}</button>
</a>
{% elif request.GET.c|lower == 'music' %}
<div class="add-entity-entries__label">
@ -451,22 +538,7 @@
<a href="{% url 'music:scrape_album' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '从表瓣剽取数据' %}</button>
</a>
{% else %}
<div class="add-entity-entries__label">
{% trans '或从表瓣剽取' %}
</div>
<a href="{% url 'books:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '书' %}</button>
</a>
<a href="{% url 'movies:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '电影/剧集' %}</button>
</a>
<a href="{% url 'music:scrape_album' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '专辑' %}</button>
</a>
{% endif %}
{% else %}
@ -483,6 +555,9 @@
<a href="{% url 'music:scrape_album' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '专辑' %}</button>
</a>
<a href="{% url 'games:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '游戏' %}</button>
</a>
{% endif %}
</div>

View file

@ -18,6 +18,7 @@
<option value="book" {% if request.GET.c and request.GET.c == 'book' %}selected{% endif %}>{% trans '书籍' %}</option>
<option value="movie" {% if request.GET.c and request.GET.c == 'movie' %}selected{% endif %}>{% trans '电影' %}</option>
<option value="music" {% if request.GET.c and request.GET.c == 'music' %}selected{% endif %}>{% trans '音乐' %}</option>
<option value="game" {% if request.GET.c and request.GET.c == 'game' %}selected{% endif %}>{% trans '游戏' %}</option>
</select>
</div>
<button class="navbar__dropdown-btn">• • •</button>

View file

@ -1,7 +1,6 @@
from django.urls import path
from .views import *
app_name = 'common'
urlpatterns = [
path('', home),

View file

@ -12,137 +12,22 @@ from django.db.models import Q, Count
from django.http import HttpResponseBadRequest
from books.models import Book
from movies.models import Movie
from games.models import Game
from music.models import Album, Song, AlbumMark, SongMark
from users.models import Report, User, Preference
from mastodon.decorators import mastodon_request_included
from users.views import home as user_home
from common.models import MarkStatusEnum
from common.utils import PageLinksGenerator
from common.scraper import scraper_registry
from common.config import *
from management.models import Announcement
# how many books have in each set at the home page
BOOKS_PER_SET = 5
# how many movies have in each set at the home page
MOVIES_PER_SET = 5
MUSIC_PER_SET = 5
# how many items are showed in one search result page
ITEMS_PER_PAGE = 20
# how many pages links in the pagination
PAGE_LINK_NUMBER = 7
# max tags on list page
TAG_NUMBER_ON_LIST = 5
logger = logging.getLogger(__name__)
@login_required
def home(request):
"""
Should be merged to users:home in the future
"""
if request.method == 'GET':
# really shitty code here
unread_announcements = Announcement.objects.filter(
pk__gt=request.user.read_announcement_index).order_by('-pk')
try:
request.user.read_announcement_index = Announcement.objects.latest('pk').pk
request.user.save(update_fields=['read_announcement_index'])
except ObjectDoesNotExist as e:
# when there is no annoucenment
pass
do_book_marks = request.user.user_bookmarks.filter(
status=MarkStatusEnum.DO).order_by("-edited_time")
do_books_more = True if do_book_marks.count() > BOOKS_PER_SET else False
wish_book_marks = request.user.user_bookmarks.filter(
status=MarkStatusEnum.WISH).order_by("-edited_time")
wish_books_more = True if wish_book_marks.count() > BOOKS_PER_SET else False
collect_book_marks = request.user.user_bookmarks.filter(
status=MarkStatusEnum.COLLECT).order_by("-edited_time")
collect_books_more = True if collect_book_marks.count() > BOOKS_PER_SET else False
do_movie_marks = request.user.user_moviemarks.filter(
status=MarkStatusEnum.DO).order_by("-edited_time")
do_movies_more = True if do_movie_marks.count() > MOVIES_PER_SET else False
wish_movie_marks = request.user.user_moviemarks.filter(
status=MarkStatusEnum.WISH).order_by("-edited_time")
wish_movies_more = True if wish_movie_marks.count() > MOVIES_PER_SET else False
collect_movie_marks = request.user.user_moviemarks.filter(
status=MarkStatusEnum.COLLECT).order_by("-edited_time")
collect_movies_more = True if collect_movie_marks.count() > MOVIES_PER_SET else False
do_music_marks = list(request.user.user_songmarks.filter(status=MarkStatusEnum.DO)[:MUSIC_PER_SET]) \
+ list(request.user.user_albummarks.filter(status=MarkStatusEnum.DO)[:MUSIC_PER_SET])
do_music_more = True if len(do_music_marks) > MUSIC_PER_SET else False
do_music_marks = sorted(do_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
wish_music_marks = list(request.user.user_songmarks.filter(status=MarkStatusEnum.WISH)[:MUSIC_PER_SET]) \
+ list(request.user.user_albummarks.filter(status=MarkStatusEnum.WISH)[:MUSIC_PER_SET])
wish_music_more = True if len(wish_music_marks) > MUSIC_PER_SET else False
wish_music_marks = sorted(wish_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
collect_music_marks = list(request.user.user_songmarks.filter(status=MarkStatusEnum.COLLECT)[:MUSIC_PER_SET]) \
+ list(request.user.user_albummarks.filter(status=MarkStatusEnum.COLLECT)[:MUSIC_PER_SET])
collect_music_more = True if len(collect_music_marks) > MUSIC_PER_SET else False
collect_music_marks = sorted(collect_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
for mark in do_music_marks + wish_music_marks + collect_music_marks:
# for template convenience
if mark.__class__ == AlbumMark:
mark.type = "album"
else:
mark.type = "song"
reports = Report.objects.order_by('-submitted_time').filter(is_read=False)
# reports = Report.objects.latest('submitted_time').filter(is_read=False)
try:
layout = request.user.preference.get_serialized_home_layout()
except ObjectDoesNotExist:
Preference.objects.create(user=request.user)
layout = request.user.preference.get_serialized_home_layout()
return render(
request,
'common/home.html',
{
'do_book_marks': do_book_marks[:BOOKS_PER_SET],
'wish_book_marks': wish_book_marks[:BOOKS_PER_SET],
'collect_book_marks': collect_book_marks[:BOOKS_PER_SET],
'do_books_more': do_books_more,
'wish_books_more': wish_books_more,
'collect_books_more': collect_books_more,
'do_movie_marks': do_movie_marks[:MOVIES_PER_SET],
'wish_movie_marks': wish_movie_marks[:MOVIES_PER_SET],
'collect_movie_marks': collect_movie_marks[:MOVIES_PER_SET],
'do_movies_more': do_movies_more,
'wish_movies_more': wish_movies_more,
'collect_movies_more': collect_movies_more,
'do_music_marks': do_music_marks,
'wish_music_marks': wish_music_marks,
'collect_music_marks': collect_music_marks,
'do_music_more': do_music_more,
'wish_music_more': wish_music_more,
'collect_music_more': collect_music_more,
'reports': reports,
'unread_announcements': unread_announcements,
'layout': layout,
}
)
else:
return HttpResponseBadRequest()
return user_home(request, request.user.id)
@login_required
@ -266,6 +151,51 @@ def search(request):
ordered_queryset = list(queryset)
return ordered_queryset
def game_param_handler(**kwargs):
# keywords
keywords = kwargs.get('keywords')
# tag
tag = kwargs.get('tag')
query_args = []
q = Q()
for keyword in keywords:
q = q | Q(title__icontains=keyword)
q = q | Q(other_title__icontains=keyword)
q = q | Q(developer__icontains=keyword)
q = q | Q(publisher__icontains=keyword)
if tag:
q = q & Q(game_tags__content__iexact=tag)
query_args.append(q)
queryset = Game.objects.filter(*query_args).distinct()
def calculate_similarity(game):
if keywords:
# search by name
developer_dump = ' '.join(game.developer)
publisher_dump = ' '.join(game.publisher)
similarity, n = 0, 0
for keyword in keywords:
similarity += 1/2 * SequenceMatcher(None, keyword, game.title).quick_ratio()
+ 1/4 * SequenceMatcher(None, keyword, game.other_title).quick_ratio()
+ 1/16 * SequenceMatcher(None, keyword, developer_dump).quick_ratio()
+ 1/16 * SequenceMatcher(None, keyword, publisher_dump).quick_ratio()
n += 1
game.similarity = similarity / n
elif tag:
# search by single tag
game.similarity = 0 if game.rating_number is None else game.rating_number
else:
game.similarity = 0
return game.similarity
if len(queryset) > 0:
ordered_queryset = sorted(queryset, key=calculate_similarity, reverse=True)
else:
ordered_queryset = list(queryset)
return ordered_queryset
def music_param_handler(**kwargs):
# keywords
keywords = kwargs.get('keywords')
@ -329,8 +259,9 @@ def search(request):
book_queryset = book_param_handler(**kwargs)
movie_queryset = movie_param_handler(**kwargs)
music_queryset = music_param_handler(**kwargs)
game_queryset = game_param_handler(**kwargs)
ordered_queryset = sorted(
book_queryset + movie_queryset + music_queryset,
book_queryset + movie_queryset + music_queryset + game_queryset,
key=operator.attrgetter('similarity'),
reverse=True
)
@ -340,10 +271,13 @@ def search(request):
'book': book_param_handler,
'movie': movie_param_handler,
'music': music_param_handler,
'game': game_param_handler,
'all': all_param_handler,
'': all_param_handler
}
categories = [k for k in param_handler.keys() if not k in ['all', '']]
try:
queryset = param_handler[category](
keywords=keywords,
@ -367,6 +301,7 @@ def search(request):
"common/search_result.html",
{
"items": items,
"categories": categories,
}
)

0
games/__init__.py Normal file
View file

3
games/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
games/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class GamesConfig(AppConfig):
name = 'games'

98
games/forms.py Normal file
View file

@ -0,0 +1,98 @@
from django import forms
from django.contrib.postgres.forms import SimpleArrayField
from django.utils.translation import gettext_lazy as _
from .models import Game, GameMark, GameReview
from common.models import MarkStatusEnum
from common.forms import *
def GameMarkStatusTranslator(status):
trans_dict = {
MarkStatusEnum.DO.value: _("在玩"),
MarkStatusEnum.WISH.value: _("想玩"),
MarkStatusEnum.COLLECT.value: _("玩过")
}
return trans_dict[status]
class GameForm(forms.ModelForm):
# id = forms.IntegerField(required=False, widget=forms.HiddenInput())
other_info = JSONField(required=False, label=_("其他信息"))
class Meta:
model = Game
fields = [
'title',
'source_site',
'source_url',
'other_title',
'developer',
'publisher',
'release_date',
'genre',
'platform',
'cover',
'brief',
'other_info'
]
widgets = {
'other_title': forms.TextInput(attrs={'placeholder': _("多个别名使用英文逗号分隔")}),
'developer': forms.TextInput(attrs={'placeholder': _("多个开发商使用英文逗号分隔")}),
'publisher': forms.TextInput(attrs={'placeholder': _("多个发行商使用英文逗号分隔")}),
'genre': forms.TextInput(attrs={'placeholder': _("多个类型使用英文逗号分隔")}),
'platform': forms.TextInput(attrs={'placeholder': _("多个平台使用英文逗号分隔")}),
'cover': PreviewImageInput(),
}
class GameMarkForm(MarkForm):
STATUS_CHOICES = [(v, GameMarkStatusTranslator(v))
for v in MarkStatusEnum.values]
status = forms.ChoiceField(
label=_(""),
widget=forms.RadioSelect(),
choices=STATUS_CHOICES
)
class Meta:
model = GameMark
fields = [
'id',
'game',
'status',
'rating',
'text',
'is_private',
]
labels = {
'rating': _("评分"),
}
widgets = {
'game': forms.TextInput(attrs={"hidden": ""}),
}
class GameReviewForm(ReviewForm):
class Meta:
model = GameReview
fields = [
'id',
'game',
'title',
'content',
'is_private'
]
labels = {
'book': "",
'title': _("标题"),
'content': _("正文"),
'share_to_mastodon': _("分享到长毛象")
}
widgets = {
'game': forms.TextInput(attrs={"hidden": ""}),
}

128
games/models.py Normal file
View file

@ -0,0 +1,128 @@
import uuid
import django.contrib.postgres.fields as postgres
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.core.serializers.json import DjangoJSONEncoder
from django.shortcuts import reverse
from common.models import Entity, Mark, Review, Tag
from common.utils import ChoicesDictGenerator
from boofilsic.settings import GAME_MEDIA_PATH_ROOT, DEFAULT_GAME_IMAGE
from django.utils import timezone
def game_cover_path(instance, filename):
ext = filename.split('.')[-1]
filename = "%s.%s" % (uuid.uuid4(), ext)
root = ''
if GAME_MEDIA_PATH_ROOT.endswith('/'):
root = GAME_MEDIA_PATH_ROOT
else:
root = GAME_MEDIA_PATH_ROOT + '/'
return root + timezone.now().strftime('%Y/%m/%d') + f'{filename}'
class Game(Entity):
"""
"""
title = models.CharField(_("名称"), max_length=500)
other_title = postgres.ArrayField(
models.CharField(blank=True,default='', max_length=500),
null=True,
blank=True,
default=list,
verbose_name=_("别名")
)
developer = postgres.ArrayField(
models.CharField(blank=True, default='', max_length=500),
null=True,
blank=True,
default=list,
verbose_name=_("开发商")
)
publisher = postgres.ArrayField(
models.CharField(blank=True, default='', max_length=500),
null=True,
blank=True,
default=list,
verbose_name=_("发行商")
)
release_date = models.DateField(
_('发行日期'),
auto_now=False,
auto_now_add=False,
null=True,
blank=True
)
genre = postgres.ArrayField(
models.CharField(blank=True, default='', max_length=50),
null=True,
blank=True,
default=list,
verbose_name=_("类型")
)
platform = postgres.ArrayField(
models.CharField(blank=True, default='', max_length=50),
null=True,
blank=True,
default=list,
verbose_name=_("平台")
)
cover = models.ImageField(_("封面"), upload_to=game_cover_path, default=DEFAULT_GAME_IMAGE, blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("games:retrieve", args=[self.id])
def get_tags_manager(self):
return self.game_tags
@property
def verbose_category_name(self):
return _("游戏")
class GameMark(Mark):
game = models.ForeignKey(
Game, on_delete=models.CASCADE, related_name='game_marks', null=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['owner', 'game'], name='unique_game_mark')
]
class GameReview(Review):
game = models.ForeignKey(
Game, on_delete=models.CASCADE, related_name='game_reviews', null=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['owner', 'game'], name='unique_game_review')
]
class GameTag(Tag):
game = models.ForeignKey(
Game, on_delete=models.CASCADE, related_name='game_tags', null=True)
mark = models.ForeignKey(
GameMark, on_delete=models.CASCADE, related_name='gamemark_tags', null=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['content', 'mark'], name="unique_gamemark_tag")
]

View file

@ -0,0 +1,95 @@
{% load static %}
{% load i18n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - ' %}{{ title }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content" class="container">
<div class="grid">
<div class="single-section-wrapper" id="main">
<a href="{% url 'games:scrape' %}"
class="single-section-wrapper__link single-section-wrapper__link--secondary">{% trans '>>> 试试一键剽取~ <<<' %}</a>
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}
{% for field in form %}
{% if field.name == 'release_date' %}
{{ field.label_tag }}
<input type="date" name="{{ field.name }}" id="{{ field.id_for_label }}" value="{{ form.instance.release_date | date:"
Y-m-d" }}">
{% else %}
{% if field.name != 'id' %}
{{ field.label_tag }}
{% endif %}
{{ field }}
{% endif %}
{% endfor %}
<input class="button" type="submit" value="{% trans '提交' %}">
</form>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
// mark required
$("#content *[required]").each(function () {
$(this).prev().prepend("*");
});
// when source site is this site, hide url input box and populate it with fake url
// the backend would update this field
if ($("select[name='source_site']").val() == "{{ this_site_enum_value }}") {
$("input[name='source_url']").hide();
$("label[for='id_source_url']").hide();
$("input[name='source_url']").val("https://www.temp.com/" + Date.now() + Math.random());
}
$("select[name='source_site']").change(function () {
let value = $(this).val();
if (value == "{{ this_site_enum_value }}") {
$("input[name='source_url']").hide();
$("label[for='id_source_url']").hide();
$("input[name='source_url']").val("https://www.temp.com/" + Date.now() + Math.random());
} else {
$("input[name='source_url']").show();
$("label[for='id_source_url']").show();
$("input[name='source_url']").val("");
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,130 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - ' %}{{ title }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'js/create_update_review.js' %}"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="single-section-wrapper">
<div class="entity-card entity-card--horizontal">
<div class="entity-card__img-wrapper">
<a href="{% url 'games:retrieve' game.id %}">
<img src="{{ game.cover|thumb:'normal' }}" alt="" class="item-image float-left">
</a>
</div>
<div class="entity-card__info-wrapper entity-card__info-wrapper--horizontal">
<h5 class="entity-card__title"><a href="{% url 'games:retrieve' game.id %}">
{{ game.title }}
</a>
<a href="{{ game.source_url }}"><span class="source-label source-label__{{ game.source_site }}">{{ game.get_source_site_display }}</span></a>
</h5>
<div>
{% if game.genre %}{% trans '类型:' %}
{% for genre in game.genre %}
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>
{% if game.developer %}{% trans '开发商:' %}
{% for developer in game.developer %}
<span>{{ developer }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>{% if game.release_date %}
{% trans '发行日期:' %}{{ game.release_date }}
{% endif %}
</div>
{% if game.rating %}
{% trans '评分:' %}<span class="entity-card__rating-star rating-star" data-rating-score="{{ game.rating | floatformat:"0" }}"> </span>
<span class="entity-card__rating-score rating-score"> {{ game.rating }} </span>
{% endif %}
</div>
</div>
<div class="dividing-line"></div>
<form action="{{ submit_url }}" method="post" class="review-form">
{% csrf_token %}
{{ form.game }}
<div>
{{ form.title.label }}
</div>
{{ form.title }}
<div class="clearfix">
<span class="float-left">
{{ form.content.label }}
</span>
<span class="float-right">
<span class="review-form__preview-button">{% trans '预览' %}</span>
</span>
</div>
<div id="rawContent">
{{ form.content }}
</div>
<div class="review-form__fyi">{% trans '不知道什么是Markdown可以参考' %}<a target="_blank" href="https://www.markdownguide.org/">{% trans '这里' %}</a></div>
<div class="review-form__option">
<div class="review-form__visibility-radio">
{{ form.is_private.label }}{{ form.is_private }}
</div>
<div class="review-form__share-checkbox">
{{ form.share_to_mastodon }}{{ form.share_to_mastodon.label }}
</div>
</div>
<div class="clearfix">
<input class="button float-right" type="submit" value="{% trans '提交' %}">
</div>
{{ form.media }}
</form>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,105 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - 删除电影/剧集' %}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="single-section-wrapper" id="main">
<h5>{% trans '确认删除这个游戏吗?相关评论和标记将一并删除。' %}</h5>
<div class="entity-card entity-card--horizontal">
<div class="entity-card__img-wrapper">
<a href="{% url 'games:retrieve' game.id %}">
<img src="{{ game.cover|thumb:'normal' }}" alt="" class="item-image float-left">
</a>
</div>
<div class="entity-card__info-wrapper entity-card__info-wrapper--horizontal">
<a href="{% url 'games:retrieve' game.id %}">
<h5 class="entity-card__title">
{{ game.title }}
<a href="{{ game.source_url }}"><span class="source-label source-label__{{ game.source_site }}">{{ game.get_source_site_display }}</span></a>
</h5>
</a>
{% if game.rating %}
{% trans '评分:' %}<span class="entity-card__rating-star rating-star" data-rating-score="{{ game.rating | floatformat:"0" }}">
</span>
<span class="entity-card__rating-score">{{ game.rating }}</span>
{% else %}
<span>{% trans '评分:暂无评分' %}</span>
{% endif %}
{% if game.last_editor %}
<div>
{% trans '最近编辑者:' %}
<a href="{% url 'users:home' game.last_editor.id %}">
<span>{{ game.last_editor | default:"" }}</span>
</a>
</div>
{% endif %}
<div>{% trans '上次编辑时间:' %}{{ game.edited_time }}</div>
{% if game.game_marks.all %}
<div><strong>{% trans '这个条目有' %} <a href="javascript:void();">{{ game.game_marks.count }}</a> 个标记</strong></div>
{% endif %}
{% if game.game_reviews.all %}
<div><strong>{% trans '这个条目有' %} <a href="javascript:void();">{{ game.game_reviews.count }}</a> 个评论</strong></div>
{% endif %}
</div>
</div>
<div class="dividing-line"></div>
<div class="clearfix">
<form action="{% url 'games:delete' game.id %}" method="post" class="float-right">
{% csrf_token %}
<input class="button" type="submit" value="{% trans '确认' %}">
</form>
<button onclick="history.back()" class="button button-clear float-right">{% trans '返回' %}</button>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,107 @@
{% load static %}
{% load i18n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - 删除评论' %}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="single-section-wrapper" id="main">
<h5>{% trans '确认删除这篇评论吗?' %}</h5>
<div class="dividing-line"></div>
<div class="review-head">
<h5 class="review-head__title">
{{ review.title }}
</h5>
{% if review.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<path
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
</svg></span>
{% endif %}
<div class="review-head__body">
<div class="review-head__info">
<a href="{% url 'users:home' review.owner.id %}"
class="review-head__owner-link">{{ review.owner.username }}</a>
{% if mark %}
{% if mark.rating %}
<span class="review-head__rating-star rating-star"
data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
{% endif %}
{% endif %}
<span class="review-head__time">{{ review.edited_time }}</span>
</div>
</div>
</div>
<div id="rawContent" class="delete-preview">
{{ form.content }}
</div>
{{ form.media }}
<div class="dividing-line"></div>
<div class="clearfix">
<form action="{% url 'games:delete_review' review.id %}" method="post" class="float-right">
{% csrf_token %}
<input class="button" type="submit" value="{% trans '确认' %}">
</form>
<button onclick="history.back()"
class="button button-clear float-right">{% trans '返回' %}</button>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
$(".markdownx textarea").hide();
$(".markdownx .markdownx-preview").show();
</script>
</body>
</html>

View file

@ -0,0 +1,420 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load strip_scheme %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:title" content="NiceDB游戏 - {{ game.title }}">
<meta property="og:type" content="game">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ game.cover.url }}">
<meta property="og:site_name" content="NiceDB">
<meta property="og:description"content="{{ game.brief }}">
<title>{% trans 'NiceDB - 游戏详情' %} | {{ game.title }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/detail.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/boofilsic.css' %}">
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="grid__main" id="main">
<div class="main-section-wrapper">
<div class="entity-detail">
<a href="{{ game.cover.url }}" class="entity-detail__img-origin" target="_blank" title="{% trans '查看原图' %}">
<img src="{{ game.cover|thumb:'normal' }}" class="entity-detail__img" alt="{{ game.title }}">
</a>
<div class="entity-detail__info">
<h5 class="entity-detail__title">
{{ game.title }}
<a href="{{ game.source_url }}"><span class="source-label source-label__{{ game.source_site }}">{{ game.get_source_site_display }}</span></a>
</h5>
<div class="entity-detail__fields">
<div class="entity-detail__rating">
{% if game.rating %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ game.rating | floatformat:"0" }}"></span>
<span class="entity-detail__rating-score"> {{ game.rating }} </span>
{% else %}
<span> {% trans '评分:暂无评分' %}</span>
{% endif %}
</div>
<div>{% if game.other_title %}{% trans '别名:' %}
{% for other_title in game.other_title %}
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
<span class="other_title">{{ other_title }}</span>
{% if not forloop.last %} / {% endif %}
</span>
{% endfor %}
{% if game.other_title|length > 5 %}
<a href="javascript:void(0);" id="otherTitleMore">{% trans '更多' %}</a>
<script>
$("#otherTitleMore").click(function (e) {
$("span.other_title:not(:visible)").each(function (e) {
$(this).parent().removeAttr('style');
});
$(this).remove();
})
</script>
{% endif %}
{% endif %}
</div>
<div>
{% if game.genre %}{% trans '类型:' %}
{% for genre in game.genre %}
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>
{% if game.developer %}{% trans '开发商:' %}
{% for developer in game.developer %}
<span>{{ developer }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>
{% if game.publisher %}{% trans '发行商:' %}
{% for publisher in game.publisher %}
<span>{{ publisher }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
</div>
<div class="entity-detail__fields">
<div>{% if game.release_date %}
{% trans '发行日期:' %}{{ game.release_date }}
{% endif %}
</div>
<div>
{% if game.platform %}{% trans '平台:' %}
{% for platform in game.platform %}
<span>{{ platform }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
{% if game.other_info %}
{% for k, v in game.other_info.items %}
<div>
{{ k }}{{ v | urlize }}
</div>
{% endfor %}
{% endif %}
{% if game.last_editor %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' game.last_editor.id %}">{{ game.last_editor | default:"" }}</a></div>
{% endif %}
<div>
<a href="{% url 'games:update' game.id %}">{% trans '编辑这个游戏' %}</a>
{% if user.is_staff %}
/<a href="{% url 'games:delete' game.id %}"> {% trans '删除' %}</a>
{% endif %}
</div>
</div>
<div class="tag-collection">
{% for tag_dict in game_tag_list %}
{% for k, v in tag_dict.items %}
{% if k == 'content' %}
<span class="tag-collection__tag">
<a href="{% url 'common:search' %}?tag={{ v }}">{{ v }}</a>
</span>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</div>
</div>
<div class="dividing-line"></div>
{% if game.brief %}
<div class="entity-desc" id="description">
<h5 class="entity-desc__title">{% trans '简介' %}</h5>
<p class="entity-desc__content">{{ game.brief | linebreaksbr }}</p>
<div class="entity-desc__unfold-button entity-desc__unfold-button--hidden">
<a href="javascript:void(0);">展开全部</a>
</div>
</div>
{% endif %}
<div class="entity-marks">
<h5 class="entity-marks__title">{% trans '这个游戏的标记' %}</h5>
{% if mark_list_more %}
<a href="{% url 'games:retrieve_mark_list' game.id %}" class="entity-marks__more-link">{% trans '更多' %}</a>
{% endif %}
{% if mark_list %}
<ul class="entity-marks__mark-list">
{% for others_mark in mark_list %}
<li class="entity-marks__mark">
<a href="{% url 'users:home' others_mark.owner.id %}" class="entity-marks__owner-link">{{ others_mark.owner.username }}</a>
<span>{{ others_mark.get_status_display }}</span>
{% if others_mark.rating %}
<span class="entity-marks__rating-star rating-star" data-rating-score="{{ others_mark.rating | floatformat:"0" }}"></span>
{% endif %}
{% if others_mark.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="entity-marks__mark-time">{{ others_mark.edited_time }}</span>
{% if others_mark.text %}
<p class="entity-marks__mark-content">{{ others_mark.text }}</p>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div>{% trans '暂无标记' %}</div>
{% endif %}
</div>
<div class="entity-reviews">
<h5 class="entity-reviews__title">{% trans '这个游戏的评论' %}</h5>
{% if review_list_more %}
<a href="{% url 'games:retrieve_review_list' game.id %}" class="entity-reviews__more-link">{% trans '更多' %}</a>
{% endif %}
{% if review_list %}
<ul class="entity-reviews__review-list">
{% for others_review in review_list %}
<li class="entity-reviews__review">
<a href="{% url 'users:home' others_review.owner.id %}" class="entity-reviews__owner-link">{{ others_review.owner.username }}</a>
{% if others_review.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="entity-reviews__review-time">{{ others_review.edited_time }}</span>
<span class="entity-reviews__review-title"> <a href="{% url 'games:retrieve_review' others_review.id %}">{{ others_review.title }}</a></span>
<span>{{ others_review.get_plain_content | truncate:100 }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<div>{% trans '暂无评论' %}</div>
{% endif %}
</div>
</div>
</div>
<div class="grid__aside" id="aside">
<div class="aside-section-wrapper">
{% if mark %}
<div class="mark-panel">
<span class="mark-panel__status">{% trans '我' %}{{ mark.get_status_display }}</span>
{% if mark.status == status_enum.DO.value or mark.status == status_enum.COLLECT.value%}
{% if mark.rating %}
<span class="mark-panel__rating-star rating-star" data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
{% endif %}
{% endif %}
{% if mark.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="mark-panel__actions">
<a href="" class="edit">{% trans '修改' %}</a>
<form action="{% url 'games:delete_mark' mark.id %}" method="post">
{% csrf_token %}
<a href="" class="delete">{% trans '删除' %}</a>
</form>
</span>
<div class="mark-panel__clear"></div>
<div class="mark-panel__time">{{ mark.edited_time }}</div>
{% if mark.text %}
<p class="mark-panel__text">{{ mark.text }}</p>
{% endif %}
<div class="tag-collection">
{% for tag in mark_tags %}
<span class="tag-collection__tag">{{ tag }}</span>
{% endfor %}
</div>
</div>
{% else %}
<div class="action-panel" id="addMarkPanel">
<div class="action-panel__label">{% trans '标记这个游戏' %}</div>
<div class="action-panel__button-group">
<button class="action-panel__button" data-status="{{ status_enum.WISH.value }}" id="wishButton">{% trans '想玩' %}</button>
<button class="action-panel__button" data-status="{{ status_enum.DO.value }}">{% trans '在玩' %}</button>
<button class="action-panel__button" data-status="{{ status_enum.COLLECT.value }}">{% trans '玩过' %}</button>
</div>
</div>
{% endif %}
</div>
<div class="aside-section-wrapper">
{% if review %}
<div class="review-panel">
<span class="review-panel__label">{% trans '我的评论' %}</span>
{% if review.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="review-panel__actions">
<a href="{% url 'games:update_review' review.id %}">{% trans '编辑' %}</a>
<a href="{% url 'games:delete_review' review.id %}">{% trans '删除' %}</a>
</span>
<div class="review-panel__time">{{ review.edited_time }}</div>
<a href="{% url 'games:retrieve_review' review.id %}" class="review-panel__review-title">
{{ review.title }}
</a>
</div>
{% else %}
<div class="action-panel">
<div class="action-panel__label">{% trans '我的评论' %}</div>
<div class="action-panel__button-group action-panel__button-group--center">
<a href="{% url 'games:create_review' game.id %}">
<button class="action-panel__button">{% trans '去写评论' %}</button>
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
<div id="modals">
<div class="mark-modal modal">
<div class="mark-modal__head">
{% if not mark %}
<style>
.mark-modal__title::after {
content: "{% trans '这个游戏' %}";
}
</style>
<span class="mark-modal__title"></span>
{% else %}
<span class="mark-modal__title">{% trans '我的标记' %}</span>
{% endif %}
<span class="mark-modal__close-button modal-close">
<span class="icon-cross">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<polygon
points="20 2.61 17.39 0 10 7.39 2.61 0 0 2.61 7.39 10 0 17.39 2.61 20 10 12.61 17.39 20 20 17.39 12.61 10 20 2.61">
</polygon>
</svg>
</span>
</span>
</div>
<div class="mark-modal__body">
<form action="{% url 'games:create_update_mark' %}" method="post">
{{ mark_form.media }}
{% csrf_token %}
{{ mark_form.id }}
{{ mark_form.game }}
{% if mark.rating %}
{% endif %}
<div class="mark-modal__rating-star rating-star-edit"></div>
{{ mark_form.rating }}
<div id="statusSelection" class="mark-modal__status-radio" {% if not mark %}hidden{% endif %}>
{{ mark_form.status }}
</div>
<div class="mark-modal__clear"></div>
{{ mark_form.text }}
<div class="mark-modal__tag">
<label>{{ mark_form.tags.label }}</label>
{{ mark_form.tags }}
</div>
<div class="mark-modal__option">
<div class="mark-modal__visibility-radio">
<span>{{ mark_form.is_private.label }}:</span>
{{ mark_form.is_private }}
</div>
<div class="mark-modal__share-checkbox">
{{ mark_form.share_to_mastodon }}{{ mark_form.share_to_mastodon.label }}
</div>
</div>
<div class="mark-modal__confirm-button">
<input type="submit" class="button float-right" value="{% trans '提交' %}">
</div>
</form>
</div>
</div>
<div class="confirm-modal modal">
<div class="confirm-modal__head">
<span class="confirm-modal__title">{% trans '确定要删除你的标记吗?' %}</span>
<span class="confirm-modal__close-button modal-close">
<span class="icon-cross">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<polygon
points="20 2.61 17.39 0 10 7.39 2.61 0 0 2.61 7.39 10 0 17.39 2.61 20 10 12.61 17.39 20 20 17.39 12.61 10 20 2.61">
</polygon>
</svg>
</span>
</span>
</div>
<div class="confirm-modal__body">
<div class="confirm-modal__confirm-button">
<input type="submit" class="button float-right" value="{% trans '确认' %}">
</div>
</div>
</div>
</div>
<div class="bg-mask"></div>
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,165 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load highlight %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - ' %}{{ game.title }}{% trans '的标记' %}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="grid__main" id="main">
<div class="main-section-wrapper">
<div class="entity-marks">
<h5 class="entity-marks__title entity-marks__title--stand-alone">
<a href="{% url 'games:retrieve' game.id %}">{{ game.title }}</a>{% trans ' 的标记' %}
</h5>
<ul class="entity-marks__mark-list">
{% for mark in marks %}
<li class="entity-marks__mark entity-marks__mark--wider">
<a href="{% url 'users:home' mark.owner.id %}"
class="entity-marks__owner-link">{{ mark.owner.username }}</a>
<span>{{ mark.get_status_display }}</span>
{% if mark.rating %}
<span class="entity-marks__rating-star rating-star"
data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
{% endif %}
{% if mark.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
</svg></span>
{% endif %}
<span class="entity-marks__mark-time">{{ mark.edited_time }}</span>
{% if mark.text %}
<p class="entity-marks__mark-content">{{ mark.text }}</p>
{% endif %}
</li>
{% empty %}
<div>
{% trans '无结果' %}
</div>
{% endfor %}
</ul>
</div>
<div class="pagination">
{% if marks.pagination.has_prev %}
<a href="?page=1"
class="pagination__nav-link pagination__nav-link">&laquo;</a>
<a href="?page={{ marks.previous_page_number }}"
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">&lsaquo;</a>
{% endif %}
{% for page in marks.pagination.page_range %}
{% if page == marks.pagination.current_page %}
<a href="?page={{ page }}"
class="pagination__page-link pagination__page-link--current">{{ page }}</a>
{% else %}
<a href="?page={{ page }}"
class="pagination__page-link">{{ page }}</a>
{% endif %}
{% endfor %}
{% if marks.pagination.has_next %}
<a href="?page={{ marks.next_page_number }}"
class="pagination__nav-link pagination__nav-link--left-margin">&rsaquo;</a>
<a href="?page={{ marks.pagination.last_page }}"
class="pagination__nav-link">&raquo;</a>
{% endif %}
</div>
</div>
</div>
<div class="grid__aside" id="aside">
<div class="aside-section-wrapper">
<div class="entity-card">
<div class="entity-card__img-wrapper">
<a href="{% url 'games:retrieve' game.id %}"><img src="{{ game.cover|thumb:'normal' }}" alt="" class="entity-card__img"></a>
</div>
<div class="entity-card__info-wrapper">
<h5 class="entity-card__title"><a href="{% url 'games:retrieve' game.id %}">
{{ game.title }}
</a>
<a href="{{ game.source_url }}"><span class="source-label source-label__{{ game.source_site }}">{{ game.get_source_site_display }}</span></a>
</h5>
<div>
{% if game.genre %}{% trans '类型:' %}
{% for genre in game.genre %}
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>
{% if game.developer %}{% trans '开发商:' %}
{% for developer in game.developer %}
<span>{{ developer }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>{% if game.release_date %}
{% trans '发行日期:' %}{{ game.release_date }}
{% endif %}
</div>
{% if game.rating %}
{% trans '评分:' %}<span class="entity-card__rating-star rating-star" data-rating-score="{{ game.rating | floatformat:'0'}}"> </span>
<span class="entity-card__rating-score rating-score"> {{ game.rating }} </span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,152 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:title" content="NiceDB影评 - {{ review.title }}">
<meta property="og:type" content="article">
<meta property="og:article:author" content="{{ review.owner.username }}">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'img/logo_square.svg' %}">
<title>{% trans 'NiceDB - 评论详情' %}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="grid__main" id="main">
<div class="main-section-wrapper">
<div class="review-head">
<h5 class="review-head__title">
{{ review.title }}
</h5>
{% if review.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
</svg></span>
{% endif %}
<div class="review-head__body">
<div class="review-head__info">
<a href="{% url 'users:home' review.owner.id %}" class="review-head__owner-link">{{ review.owner.username }}</a>
{% if mark %}
{% if mark.rating %}
<span class="review-head__rating-star rating-star" data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
{% endif %}
{% endif %}
<span class="review-head__time">{{ review.edited_time }}</span>
</div>
<div class="review-head__actions">
{% if request.user == review.owner %}
<a class="review-head__action-link" href="{% url 'games:update_review' review.id %}">{% trans '编辑' %}</a>
<a class="review-head__action-link" href="{% url 'games:delete_review' review.id %}">{% trans '删除' %}</a>
{% endif %}
</div>
</div>
<!-- <div class="dividing-line"></div> -->
<div id="rawContent">
{{ form.content }}
</div>
{{ form.media }}
</div>
</div>
</div>
<div class="grid__aside" id="aside">
<div class="aside-section-wrapper">
<div class="entity-card">
<div class="entity-card__img-wrapper">
<a href="{% url 'games:retrieve' game.id %}"><img src="{{ game.cover|thumb:'normal' }}" alt=""
class="entity-card__img"></a>
</div>
<div class="entity-card__info-wrapper">
<h5 class="entity-card__title">
<a href="{% url 'games:retrieve' game.id %}">
{{ game.title }}
</a>
<a href="{{ game.source_url }}">
<span class="source-label source-label__{{ game.source_site }}">
{{ game.get_source_site_display }}
</span>
</a>
</h5>
<div>
{% if game.genre %}{% trans '类型:' %}
{% for genre in game.genre %}
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>
{% if game.developer %}{% trans '开发商:' %}
{% for developer in game.developer %}
<span>{{ developer }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>{% if game.release_date %}
{% trans '发行日期:' %}{{ game.release_date }}
{% endif %}
</div>
{% if game.rating %}
{% trans '评分:' %}<span class="entity-card__rating-star rating-star"
data-rating-score="{{ game.rating | floatformat:'0'}}"> </span>
<span class="entity-card__rating-score rating-score"> {{ game.rating }} </span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
$(".markdownx textarea").hide();
</script>
</body>
</html>

View file

@ -0,0 +1,153 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load highlight %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - ' %}{{ game.title }}{% trans '的评论' %}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="grid__main" id="main">
<div class="main-section-wrapper">
<div class="entity-reviews">
<h5 class="entity-reviews__title entity-reviews__title--stand-alone">
<a href="{% url 'games:retrieve' game.id %}">{{ game.title }}</a>{% trans ' 的评论' %}
</h5>
<ul class="entity-reviews__review-list">
{% for review in reviews %}
<li class="entity-reviews__review entity-reviews__review--wider">
<a href="{% url 'users:home' review.owner.id %}" class="entity-reviews__owner-link">{{ review.owner.username }}</a>
{% if review.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="entity-reviews__review-time">{{ review.edited_time }}</span>
<span href="{% url 'games:retrieve_review' review.id %}" class="entity-reviews__review-title"><a href="{% url 'games:retrieve_review' review.id %}">{{ review.title }}</a></span>
</li>
{% empty %}
<div>{% trans '无结果' %}</div>
{% endfor %}
</ul>
</div>
<div class="pagination">
{% if reviews.pagination.has_prev %}
<a href="?page=1" class="pagination__nav-link pagination__nav-link">&laquo;</a>
<a href="?page={{ reviews.previous_page_number }}"
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">&lsaquo;</a>
{% endif %}
{% for page in reviews.pagination.page_range %}
{% if page == reviews.pagination.current_page %}
<a href="?page={{ page }}" class="pagination__page-link pagination__page-link--current">{{ page }}</a>
{% else %}
<a href="?page={{ page }}" class="pagination__page-link">{{ page }}</a>
{% endif %}
{% endfor %}
{% if reviews.pagination.has_next %}
<a href="?page={{ reviews.next_page_number }}"
class="pagination__nav-link pagination__nav-link--left-margin">&rsaquo;</a>
<a href="?page={{ reviews.pagination.last_page }}" class="pagination__nav-link">&raquo;</a>
{% endif %}
</div>
</div>
</div>
<div class="grid__aside" id="aside">
<div class="aside-section-wrapper">
<div class="entity-card">
<div class="entity-card__img-wrapper">
<a href="{% url 'games:retrieve' game.id %}"><img src="{{ game.cover|thumb:'normal' }}" alt=""
class="entity-card__img"></a>
</div>
<div class="entity-card__info-wrapper">
<h5 class="entity-card__title"><a href="{% url 'games:retrieve' game.id %}">
{{ game.title }}
</a>
<a href="{{ game.source_url }}"><span class="source-label source-label__{{ game.source_site }}">{{ game.get_source_site_display }}</span></a>
</h5>
<div>
{% if game.genre %}{% trans '类型:' %}
{% for genre in game.genre %}
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>
{% if game.developer %}{% trans '开发商:' %}
{% for developer in game.developer %}
<span>{{ developer }}</span>{% if not forloop.last %} / {% endif %}
{% endfor %}
{% endif %}
</div>
<div>{% if game.release_date %}
{% trans '发行日期:' %}{{ game.release_date }}
{% endif %}
</div>
{% if game.rating %}
{% trans '评分:' %}<span class="entity-card__rating-star rating-star"
data-rating-score="{{ game.rating | floatformat:'0'}}"> </span>
<span class="entity-card__rating-score rating-score"> {{ game.rating }} </span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
{% comment %}
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% endcomment %}
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,112 @@
{% load static %}
{% load i18n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - 从豆瓣获取数据' %}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'js/scrape.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<style>
#scrape {
overflow: auto;
}
#scrape iframe {
width: 100%;
}
#scrape textarea {
height: 200px;
resize: vertical;
}
#scrape iframe {
height: 500px;
}
</style>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid grid--reverse-order">
<div class="grid__main grid__main--reverse-order" id="main">
<div class="main-section-wrapper">
<div id="scrape">
<h5>
{% trans '根据豆瓣内容填写下方表单' %}
</h5>
<iframe id='test' sandbox="allow-same-origin allow-scripts" src="https://www.douban.com/game/explore/?genres=&platforms=&sort=rating&q={% if q %}{{ q }}{% endif %}" frameborder="0"></iframe>
<div class="dividing-line"></div>
<div id="scrapeForm">
<form action="{% url 'games:create' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}
{% for field in form %}
{% if field.name == 'release_date' %}
{{ field.label_tag }}
<input type="date" name="{{ field.name }}" id="{{ field.id_for_label }}" value="{{ form.instance.release_date | date:"
Y-m-d" }}">
{% else %}
{% if field.name != 'id' %}
{{ field.label_tag }}
{% endif %}
{{ field }}
{% endif %}
{% endfor %}
</form>
<a href="#" class="button add-button submit">{% trans '剽取!' %}</a>
</div>
</div>
</div>
</div>
<div class="grid__aside grid__aside--reverse-order" id="aside">
<div class="aside-section-wrapper aside-section-wrapper--singular">
<h5>
{% trans '复制详情页链接' %}
</h5>
<form action="{% url 'games:click_to_scrape' %}" method="post">
{% csrf_token %}
<input type="text" name="url" required placeholder="https://game.douban.com/subject/1000000/">
<input type="submit" class="button add-button" value="{% trans '一键剽取!' %}">
</form>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
<script>
// mark required
$("#content *[required]").each(function () {
$(this).prev().prepend("*");
});
$('form').submit(function () {
$(this).find("input[type='submit']").prop('disabled', true);
$(this).find("button[type='submit']").prop('disabled', true);
});
</script>
</body>
</html>

3
games/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

23
games/urls.py Normal file
View file

@ -0,0 +1,23 @@
from django.urls import path
from .views import *
app_name = 'games'
urlpatterns = [
path('create/', create, name='create'),
path('<int:id>/', retrieve, name='retrieve'),
path('update/<int:id>/', update, name='update'),
path('delete/<int:id>/', delete, name='delete'),
path('mark/', create_update_mark, name='create_update_mark'),
path('<int:game_id>/mark/list/',
retrieve_mark_list, name='retrieve_mark_list'),
path('mark/delete/<int:id>/', delete_mark, name='delete_mark'),
path('<int:game_id>/review/create/', create_review, name='create_review'),
path('review/update/<int:id>/', update_review, name='update_review'),
path('review/delete/<int:id>/', delete_review, name='delete_review'),
path('review/<int:id>/', retrieve_review, name='retrieve_review'),
path('<int:game_id>/review/list/',
retrieve_review_list, name='retrieve_review_list'),
path('scrape/', scrape, name='scrape'),
path('click_to_scrape/', click_to_scrape, name='click_to_scrape'),
]

591
games/views.py Normal file
View file

@ -0,0 +1,591 @@
import logging
from django.shortcuts import render, get_object_or_404, redirect, reverse
from django.contrib.auth.decorators import login_required, permission_required
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponseBadRequest, HttpResponseServerError
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import IntegrityError, transaction
from django.db.models import Count
from django.utils import timezone
from django.core.paginator import Paginator
from mastodon import mastodon_request_included
from mastodon.api import check_visibility, post_toot, TootVisibilityEnum
from mastodon.utils import rating_to_emoji
from common.utils import PageLinksGenerator
from common.views import PAGE_LINK_NUMBER, jump_or_scrape
from common.models import SourceSiteEnum
from .models import *
from .forms import *
from boofilsic.settings import MASTODON_TAGS
logger = logging.getLogger(__name__)
mastodon_logger = logging.getLogger("django.mastodon")
# how many marks showed on the detail page
MARK_NUMBER = 5
# how many marks at the mark page
MARK_PER_PAGE = 20
# how many reviews showed on the detail page
REVIEW_NUMBER = 5
# how many reviews at the mark page
REVIEW_PER_PAGE = 20
# max tags on detail page
TAG_NUMBER = 10
# public data
###########################
@login_required
def create(request):
if request.method == 'GET':
form = GameForm()
return render(
request,
'games/create_update.html',
{
'form': form,
'title': _('添加游戏'),
'submit_url': reverse("games:create"),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
}
)
elif request.method == 'POST':
if request.user.is_authenticated:
# only local user can alter public data
form = GameForm(request.POST, request.FILES)
if form.is_valid():
form.instance.last_editor = request.user
try:
with transaction.atomic():
form.save()
if form.instance.source_site == SourceSiteEnum.IN_SITE.value:
real_url = form.instance.get_absolute_url()
form.instance.source_url = real_url
form.instance.save()
except IntegrityError as e:
logger.error(e.__str__())
return HttpResponseServerError("integrity error")
return redirect(reverse("games:retrieve", args=[form.instance.id]))
else:
return render(
request,
'games/create_update.html',
{
'form': form,
'title': _('添加游戏'),
'submit_url': reverse("games:create"),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
}
)
else:
return redirect(reverse("users:login"))
else:
return HttpResponseBadRequest()
@login_required
def update(request, id):
if request.method == 'GET':
game = get_object_or_404(Game, pk=id)
form = GameForm(instance=game)
page_title = _('修改游戏')
return render(
request,
'games/create_update.html',
{
'form': form,
'title': page_title,
'submit_url': reverse("games:update", args=[game.id]),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
}
)
elif request.method == 'POST':
game = get_object_or_404(Game, pk=id)
form = GameForm(request.POST, request.FILES, instance=game)
page_title = _("修改游戏")
if form.is_valid():
form.instance.last_editor = request.user
form.instance.edited_time = timezone.now()
try:
with transaction.atomic():
form.save()
if form.instance.source_site == SourceSiteEnum.IN_SITE.value:
real_url = form.instance.get_absolute_url()
form.instance.source_url = real_url
form.instance.save()
except IntegrityError as e:
logger.error(e.__str__())
return HttpResponseServerError("integrity error")
else:
return render(
request,
'games/create_update.html',
{
'form': form,
'title': page_title,
'submit_url': reverse("games:update", args=[game.id]),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
}
)
return redirect(reverse("games:retrieve", args=[form.instance.id]))
else:
return HttpResponseBadRequest()
@mastodon_request_included
# @login_required
def retrieve(request, id):
if request.method == 'GET':
game = get_object_or_404(Game, pk=id)
mark = None
mark_tags = None
review = None
# retreive tags
game_tag_list = game.game_tags.values('content').annotate(
tag_frequency=Count('content')).order_by('-tag_frequency')[:TAG_NUMBER]
# retrieve user mark and initialize mark form
try:
if request.user.is_authenticated:
mark = GameMark.objects.get(owner=request.user, game=game)
except ObjectDoesNotExist:
mark = None
if mark:
mark_tags = mark.gamemark_tags.all()
mark.get_status_display = GameMarkStatusTranslator(mark.status)
mark_form = GameMarkForm(instance=mark, initial={
'tags': mark_tags
})
else:
mark_form = GameMarkForm(initial={
'game': game,
'tags': mark_tags
})
# retrieve user review
try:
if request.user.is_authenticated:
review = GameReview.objects.get(
owner=request.user, game=game)
except ObjectDoesNotExist:
review = None
# retrieve other related reviews and marks
if request.user.is_anonymous:
# hide all marks and reviews for anonymous user
mark_list = None
review_list = None
mark_list_more = None
review_list_more = None
else:
mark_list = GameMark.get_available(
game, request.user, request.session['oauth_token'])
review_list = GameReview.get_available(
game, request.user, request.session['oauth_token'])
mark_list_more = True if len(mark_list) > MARK_NUMBER else False
mark_list = mark_list[:MARK_NUMBER]
for m in mark_list:
m.get_status_display = GameMarkStatusTranslator(m.status)
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
review_list = review_list[:REVIEW_NUMBER]
# def strip_html_tags(text):
# import re
# regex = re.compile('<.*?>')
# return re.sub(regex, '', text)
# for r in review_list:
# r.content = strip_html_tags(r.content)
return render(
request,
'games/detail.html',
{
'game': game,
'mark': mark,
'review': review,
'status_enum': MarkStatusEnum,
'mark_form': mark_form,
'mark_list': mark_list,
'mark_list_more': mark_list_more,
'review_list': review_list,
'review_list_more': review_list_more,
'game_tag_list': game_tag_list,
'mark_tags': mark_tags,
}
)
else:
logger.warning('non-GET method at /games/<id>')
return HttpResponseBadRequest()
@permission_required("games.delete_game")
@login_required
def delete(request, id):
if request.method == 'GET':
game = get_object_or_404(Game, pk=id)
return render(
request,
'games/delete.html',
{
'game': game,
}
)
elif request.method == 'POST':
if request.user.is_staff:
# only staff has right to delete
game = get_object_or_404(Game, pk=id)
game.delete()
return redirect(reverse("common:home"))
else:
raise PermissionDenied()
else:
return HttpResponseBadRequest()
# user owned entites
###########################
@mastodon_request_included
@login_required
def create_update_mark(request):
# check list:
# clean rating if is wish
# transaction on updating game rating
# owner check(guarantee)
if request.method == 'POST':
pk = request.POST.get('id')
old_rating = None
old_tags = None
if pk:
mark = get_object_or_404(GameMark, pk=pk)
if request.user != mark.owner:
return HttpResponseBadRequest()
old_rating = mark.rating
old_tags = mark.gamemark_tags.all()
# update
form = GameMarkForm(request.POST, instance=mark)
else:
# create
form = GameMarkForm(request.POST)
if form.is_valid():
if form.instance.status == MarkStatusEnum.WISH.value:
form.instance.rating = None
form.cleaned_data['rating'] = None
form.instance.owner = request.user
form.instance.edited_time = timezone.now()
game = form.instance.game
try:
with transaction.atomic():
# update game rating
game.update_rating(old_rating, form.instance.rating)
form.save()
# update tags
if old_tags:
for tag in old_tags:
tag.delete()
if form.cleaned_data['tags']:
for tag in form.cleaned_data['tags']:
GameTag.objects.create(
content=tag,
game=game,
mark=form.instance
)
except IntegrityError as e:
logger.error(e.__str__())
return HttpResponseServerError("integrity error")
if form.cleaned_data['share_to_mastodon']:
if form.cleaned_data['is_private']:
visibility = TootVisibilityEnum.PRIVATE
else:
visibility = TootVisibilityEnum.UNLISTED
url = "https://" + request.get_host() + reverse("games:retrieve",
args=[game.id])
words = GameMarkStatusTranslator(form.cleaned_data['status']) +\
f"{game.title}" + \
rating_to_emoji(form.cleaned_data['rating'])
# tags = MASTODON_TAGS % {'category': '书', 'type': '标记'}
tags = ''
content = words + '\n' + url + '\n' + \
form.cleaned_data['text'] + '\n' + tags
response = post_toot(request.user.mastodon_site, content, visibility,
request.session['oauth_token'])
if response.status_code != 200:
mastodon_logger.error(
f"CODE:{response.status_code} {response.text}")
return HttpResponseServerError("publishing mastodon status failed")
else:
return HttpResponseBadRequest("invalid form data")
return redirect(reverse("games:retrieve", args=[form.instance.game.id]))
else:
return HttpResponseBadRequest("invalid method")
@mastodon_request_included
@login_required
def retrieve_mark_list(request, game_id):
if request.method == 'GET':
game = get_object_or_404(Game, pk=game_id)
queryset = GameMark.get_available(
game, request.user, request.session['oauth_token'])
paginator = Paginator(queryset, MARK_PER_PAGE)
page_number = request.GET.get('page', default=1)
marks = paginator.get_page(page_number)
marks.pagination = PageLinksGenerator(
PAGE_LINK_NUMBER, page_number, paginator.num_pages)
for m in marks:
m.get_status_display = GameMarkStatusTranslator(m.status)
return render(
request,
'games/mark_list.html',
{
'marks': marks,
'game': game,
}
)
else:
return HttpResponseBadRequest()
@login_required
def delete_mark(request, id):
if request.method == 'POST':
mark = get_object_or_404(GameMark, pk=id)
if request.user != mark.owner:
return HttpResponseBadRequest()
game_id = mark.game.id
try:
with transaction.atomic():
# update game rating
mark.game.update_rating(mark.rating, None)
mark.delete()
except IntegrityError as e:
return HttpResponseServerError()
return redirect(reverse("games:retrieve", args=[game_id]))
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def create_review(request, game_id):
if request.method == 'GET':
form = GameReviewForm(initial={'game': game_id})
game = get_object_or_404(Game, pk=game_id)
return render(
request,
'games/create_update_review.html',
{
'form': form,
'title': _("添加评论"),
'game': game,
'submit_url': reverse("games:create_review", args=[game_id]),
}
)
elif request.method == 'POST':
form = GameReviewForm(request.POST)
if form.is_valid():
form.instance.owner = request.user
form.save()
if form.cleaned_data['share_to_mastodon']:
if form.cleaned_data['is_private']:
visibility = TootVisibilityEnum.PRIVATE
else:
visibility = TootVisibilityEnum.UNLISTED
url = "https://" + request.get_host() + reverse("games:retrieve_review",
args=[form.instance.id])
words = "发布了关于" + f"{form.instance.game.title}" + "的评论"
# tags = MASTODON_TAGS % {'category': '书', 'type': '评论'}
tags = ''
content = words + '\n' + url + \
'\n' + form.cleaned_data['title'] + '\n' + tags
response = post_toot(request.user.mastodon_site, content, visibility,
request.session['oauth_token'])
if response.status_code != 200:
mastodon_logger.error(
f"CODE:{response.status_code} {response.text}")
return HttpResponseServerError("publishing mastodon status failed")
return redirect(reverse("games:retrieve_review", args=[form.instance.id]))
else:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def update_review(request, id):
if request.method == 'GET':
review = get_object_or_404(GameReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
form = GameReviewForm(instance=review)
game = review.game
return render(
request,
'games/create_update_review.html',
{
'form': form,
'title': _("编辑评论"),
'game': game,
'submit_url': reverse("games:update_review", args=[review.id]),
}
)
elif request.method == 'POST':
review = get_object_or_404(GameReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
form = GameReviewForm(request.POST, instance=review)
if form.is_valid():
form.instance.edited_time = timezone.now()
form.save()
if form.cleaned_data['share_to_mastodon']:
if form.cleaned_data['is_private']:
visibility = TootVisibilityEnum.PRIVATE
else:
visibility = TootVisibilityEnum.UNLISTED
url = "https://" + request.get_host() + reverse("games:retrieve_review",
args=[form.instance.id])
words = "发布了关于" + f"{form.instance.game.title}" + "的评论"
# tags = MASTODON_TAGS % {'category': '书', 'type': '评论'}
tags = ''
content = words + '\n' + url + \
'\n' + form.cleaned_data['title'] + '\n' + tags
response = post_toot(request.user.mastodon_site, content, visibility,
request.session['oauth_token'])
if response.status_code != 200:
mastodon_logger.error(
f"CODE:{response.status_code} {response.text}")
return HttpResponseServerError("publishing mastodon status failed")
return redirect(reverse("games:retrieve_review", args=[form.instance.id]))
else:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
@login_required
def delete_review(request, id):
if request.method == 'GET':
review = get_object_or_404(GameReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
review_form = GameReviewForm(instance=review)
return render(
request,
'games/delete_review.html',
{
'form': review_form,
'review': review,
}
)
elif request.method == 'POST':
review = get_object_or_404(GameReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
game_id = review.game.id
review.delete()
return redirect(reverse("games:retrieve", args=[game_id]))
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def retrieve_review(request, id):
if request.method == 'GET':
review = get_object_or_404(GameReview, pk=id)
if not check_visibility(review, request.session['oauth_token'], request.user):
msg = _("你没有访问这个页面的权限😥")
return render(
request,
'common/error.html',
{
'msg': msg,
}
)
review_form = GameReviewForm(instance=review)
game = review.game
try:
mark = GameMark.objects.get(owner=review.owner, game=game)
mark.get_status_display = GameMarkStatusTranslator(mark.status)
except ObjectDoesNotExist:
mark = None
return render(
request,
'games/review_detail.html',
{
'form': review_form,
'review': review,
'game': game,
'mark': mark,
}
)
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def retrieve_review_list(request, game_id):
if request.method == 'GET':
game = get_object_or_404(Game, pk=game_id)
queryset = GameReview.get_available(
game, request.user, request.session['oauth_token'])
paginator = Paginator(queryset, REVIEW_PER_PAGE)
page_number = request.GET.get('page', default=1)
reviews = paginator.get_page(page_number)
reviews.pagination = PageLinksGenerator(
PAGE_LINK_NUMBER, page_number, paginator.num_pages)
return render(
request,
'games/review_list.html',
{
'reviews': reviews,
'game': game,
}
)
else:
return HttpResponseBadRequest()
@login_required
def scrape(request):
if request.method == 'GET':
keywords = request.GET.get('q')
form = GameForm()
return render(
request,
'games/scrape.html',
{
'q': keywords,
'form': form,
}
)
else:
return HttpResponseBadRequest()
@login_required
def click_to_scrape(request):
if request.method == "POST":
url = request.POST.get("url")
if url:
return jump_or_scrape(request, url)
else:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()

View file

@ -49,7 +49,7 @@
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
</a>
<a href="{{ movie.source_url }}"><a href="{{ movie.source_url }}"><span class="source-label source-label__{{ movie.source_site }}">{{ movie.get_source_site_display }}</span></a></a>
<a href="{{ movie.source_url }}"><span class="source-label source-label__{{ movie.source_site }}">{{ movie.get_source_site_display }}</span></a>
</h5>
<div>{% if movie.director %}{% trans '导演:' %}
{% for director in movie.director %}

View file

@ -190,7 +190,7 @@
{% if movie.other_info %}
{% for k, v in movie.other_info.items %}
<div>
{{k}}{{v}}
{{ k }}{{ v | urlize }}
</div>
{% endfor %}
{% endif %}

View file

@ -44,7 +44,7 @@
<h5>
{% trans '根据豆瓣内容填写下方表单' %}
</h5>
<iframe id='test' sandbox="allow-same-origin allow-scripts allow-popups allow-forms" src="https://search.douban.com/movie/subject_search{% if q %}?search_text={{ q }}{% endif %}" frameborder="0"></iframe>
<iframe id='test' sandbox="allow-same-origin allow-scripts" src="https://search.douban.com/movie/subject_search{% if q %}?search_text={{ q }}{% endif %}" frameborder="0"></iframe>
<div class="dividing-line"></div>
<div id="scrapeForm">
<form action="{% url 'movies:create' %}" method="POST" enctype="multipart/form-data">

View file

@ -119,7 +119,7 @@
{% if album.other_info %}
{% for k, v in album.other_info.items %}
<div>
{{k}}{{v}}
{{ k }}{{ v | urlize }}
</div>
{% endfor %}
{% endif %}

View file

@ -40,7 +40,7 @@
<h5 class="entity-card__title"><a href="{% url 'music:retrieve_album' album.id %}">
{{ album.title }}
</a>
<a href="{{ album.source_url }}"><a href="{{ album.source_url }}"><span class="source-label source-label__{{ album.source_site }}">{{ album.get_source_site_display }}</span></a></a>
<a href="{{ album.source_url }}"><span class="source-label source-label__{{ album.source_site }}">{{ album.get_source_site_display }}</span></a>
</h5>
<div>{% if album.artist %}{% trans '艺术家:' %}
{% for artist in album.artist %}

View file

@ -40,7 +40,7 @@
<h5 class="entity-card__title"><a href="{% url 'music:retrieve_song' song.id %}">
{{ song.title }}
</a>
<a href="{{ song.source_url }}"><a href="{{ song.source_url }}"><span class="source-label source-label__{{ song.source_site }}">{{ song.get_source_site_display }}</span></a></a>
<a href="{{ song.source_url }}"><span class="source-label source-label__{{ song.source_site }}">{{ song.get_source_site_display }}</span></a>
</h5>
<div>{% if song.artist %}{% trans '艺术家:' %}
{% for artist in song.artist %}

View file

@ -44,7 +44,7 @@
<h5>
{% trans '根据豆瓣内容填写下方表单' %}
</h5>
<iframe id='test' sandbox="allow-same-origin allow-scripts allow-popups allow-forms" src="https://search.douban.com/music/subject_search{% if q %}?search_text={{ q }}{% endif %}" frameborder="0"></iframe>
<iframe id='test' sandbox="allow-same-origin allow-scripts" src="https://search.douban.com/music/subject_search{% if q %}?search_text={{ q }}{% endif %}" frameborder="0"></iframe>
<div class="dividing-line"></div>
<div id="scrapeForm">
<form action="{% url 'music:create_album' %}" method="POST" enctype="multipart/form-data">
@ -54,14 +54,17 @@
{% for field in form %}
{% if field.id_for_label == 'id_is_series' %}
<label for="{{ field.id_for_label }}" style="display: inline-block; position: relative; left: -4px;">{{ field.label }}</label>
{{ field }}
{% if field.name == 'release_date' %}
{{ field.label_tag }}
<input type="date" name="{{ field.name }}" id="{{ field.id_for_label }}" value="{{ form.instance.release_date | date:"
Y-m-d" }}">
{% else %}
{% if field.id_for_label != 'id_id' %}
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{% endif %}
{{ field }}
{% if field.name != 'id' %}
{{ field.label_tag }}
{% endif %}
{{ field }}
{% endif %}
{% endfor %}

View file

@ -107,7 +107,7 @@
{% if song.other_info %}
{% for k, v in song.other_info.items %}
<div>
{{k}}{{v}}
{{ k }}{{ v | urlize }}
</div>
{% endfor %}
{% endif %}

View file

@ -95,7 +95,7 @@
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ mark.book.brief | truncate:170 }}
{{ mark.book.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in mark.book.tag_list %}

View file

@ -0,0 +1,272 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans 'NiceDB - ' %}{{ user.username }}{{ list_title }}</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<script src="{% static 'js/mastodon.js' %}"></script>
<script src="{% static 'js/home.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content" class="container">
<div class="grid grid--reverse-order">
<div class="grid__main grid__main--reverse-order">
<div class="main-section-wrapper">
<div class="entity-list">
<div class="set">
<h5 class="entity-list__title">
{{ user.username }}{{ list_title }}
</h5>
</div>
<ul class="entity-list__entities">
{% for mark in marks %}
<li class="entity-list__entity">
<div class="entity-list__entity-img-wrapper">
<a href="{% url 'games:retrieve' mark.game.id %}">
<img src="{{ mark.game.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
</a>
</div>
<div class="entity-list__entity-text">
<div class="entity-list__entity-title">
<a href="{% url 'games:retrieve' mark.game.id %}" class="entity-list__entity-link">
{{ mark.game.title }}
</a>
<a href="{{ mark.game.source_url }}">
<span class="source-label source-label__{{ mark.game.source_site }}">{{ mark.game.get_source_site_display }}</span>
</a>
</div>
<span class="entity-list__entity-info entity-list__entity-info--full-length">
{% if mark.game.other_title %}{% trans '别名' %}
{% for other_title in mark.game.other_title %}
{{ other_title }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if mark.game.developer %}{% trans '开发商' %}
{% for developer in mark.game.developer %}
{{ developer }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if mark.game.genre %}{% trans '类型' %}
{% for genre in mark.game.genre %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if mark.game.platform %}{% trans '平台' %}
{% for platform in mark.game.platform %}
{{ platform }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ mark.game.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in mark.game.tag_list %}
{% for k, v in tag_dict.items %}
{% if k == 'content' %}
<span class="tag-collection__tag">
<a href="{% url 'common:search' %}?tag={{ v }}">{{ v }}</a>
</span>
{% endif %}
{% endfor %}
{% endfor %}
</div>
<div class="clearfix"></div>
<div class="dividing-line dividing-line--dashed"></div>
<div class="entity-marks" style="margin-bottom: 0;">
<ul class="entity-marks__mark-list">
<li class="entity-marks__mark">
{% if mark.rating %}
<span class="entity-marks__rating-star rating-star"
data-rating-score="{{ mark.rating | floatformat:"0" }}" style="left: -4px;"></span>
{% endif %}
{% if mark.is_private %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
</svg></span>
{% endif %}
<span class="entity-marks__mark-time">{% trans '于' %} {{ mark.edited_time }} {% trans '标记' %}</span>
{% if mark.text %}
<p class="entity-marks__mark-content">{{ mark.text }}</p>
{% endif %}
</li>
</ul>
</div>
</div>
</li>
{% empty %}
<div>{% trans '无结果' %}</div>
{% endfor %}
<!-- user mark -->
</ul>
</div>
<div class="pagination">
{% if marks.pagination.has_prev %}
<a href="?page=1" class="pagination__nav-link pagination__nav-link">&laquo;</a>
<a href="?page={{ marks.previous_page_number }}"
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">&lsaquo;</a>
{% endif %}
{% for page in marks.pagination.page_range %}
{% if page == marks.pagination.current_page %}
<a href="?page={{ page }}" class="pagination__page-link pagination__page-link--current">{{ page }}</a>
{% else %}
<a href="?page={{ page }}" class="pagination__page-link">{{ page }}</a>
{% endif %}
{% endfor %}
{% if marks.pagination.has_next %}
<a href="?page={{ marks.next_page_number }}"
class="pagination__nav-link pagination__nav-link--left-margin">&rsaquo;</a>
<a href="?page={{ marks.pagination.last_page }}" class="pagination__nav-link">&raquo;</a>
{% endif %}
</div>
</div>
</div>
<div class="grid__aside grid__aside--reverse-order grid__aside--tablet-column">
<div class="aside-section-wrapper aside-section-wrapper--no-margin">
<div class="user-profile" id="userInfoCard">
<div class="user-profile__header">
<!-- <img src="" class="user-profile__avatar mast-avatar" alt="{{ user.username }}"> -->
<img src="" class="user-profile__avatar mast-avatar">
<a href="{% url 'users:home' user.id %}">
<h5 class="user-profile__username mast-displayname"></h5>
</a>
</div>
<p class="user-profile__bio mast-brief"></p>
<!-- <a href="#" class="follow">{% trans '关注TA' %}</a> -->
{% if request.user != user %}
<a href="{% url 'users:report' %}?user_id={{ user.id }}"
class="user-profile__report-link">{% trans '举报用户' %}</a>
{% endif %}
</div>
</div>
<div class="relation-dropdown">
<div class="relation-dropdown__button">
<span class="icon-arrow">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
<path d="M8.12,3.29,5,6.42,1.86,3.29H.45L5,7.84,9.55,3.29Z" />
</svg>
</span>
</div>
<div class="relation-dropdown__body">
<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 '关注的人' %}
</h5>
<a href="{% url 'users:following' user.id %}"
class="user-relation__more-link mast-following-more">{% trans '更多' %}</a>
<ul class="user-relation__related-user-list mast-following">
<li class="user-relation__related-user">
<a>
<img src="" alt="" class="user-relation__related-user-avatar">
<div class="user-relation__related-user-name mast-displayname">
</div>
</a>
</li>
</ul>
</div>
<div class="user-relation" id="followers">
<h5 class="user-relation__label">
{% trans '被他们关注' %}
</h5>
<a href="{% url 'users:followers' user.id %}"
class="user-relation__more-link mast-followers-more">{% trans '更多' %}</a>
<ul class="user-relation__related-user-list mast-followers">
<li class="user-relation__related-user">
<a>
<img src="" alt="" class="user-relation__related-user-avatar">
<div class="user-relation__related-user-name mast-displayname">
</div>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
<!--current user mastodon id-->
{% if user == request.user %}
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
{% else %}
<div id="userMastodonID" hidden="true">{{ user.target_site_id }}</div>
{% endif %}
<div id="userPageURL" hidden="true">{% url 'users:home' 0 %}</div>
<div id="spinner" hidden>
<div class="spinner">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<script>
</script>
</body>
</html>

View file

@ -40,7 +40,7 @@
<h5 class="entity-sort__label">
{% trans '想读的书' %}
</h5>
{% if wish_books_more %}
{% if wish_book_more %}
<a href="{% url 'users:book_list' user.id 'wish' %}"
class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
@ -65,7 +65,7 @@
<h5 class="entity-sort__label">
{% trans '在读的书' %}
</h5>
{% if do_books_more %}
{% if do_book_more %}
<a href="{% url 'users:book_list' user.id 'do' %}"
class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
@ -91,7 +91,7 @@
<h5 class="entity-sort__label">
{% trans '读过的书' %}
</h5>
{% if collect_books_more %}
{% if collect_book_more %}
<a href="{% url 'users:book_list' user.id 'collect' %}"
class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
@ -117,7 +117,7 @@
<h5 class="entity-sort__label">
{% trans '想看的电影/剧集' %}
</h5>
{% if wish_movies_more %}
{% if wish_movie_more %}
<a href="{% url 'users:movie_list' user.id 'wish' %}"
class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
@ -143,7 +143,7 @@
<h5 class="entity-sort__label">
{% trans '在看的电影/剧集' %}
</h5>
{% if do_movies_more %}
{% if do_movie_more %}
<a href="{% url 'users:movie_list' user.id 'do' %}"
class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
@ -169,7 +169,7 @@
<h5 class="entity-sort__label">
{% trans '看过的电影/剧集' %}
</h5>
{% if collect_movies_more %}
{% if collect_movie_more %}
<a href="{% url 'users:movie_list' user.id 'collect' %}"
class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
@ -299,6 +299,81 @@
</div>
<div class="entity-sort" id="gameWish">
<h5 class="entity-sort__label">
{% trans '想玩的游戏' %}
</h5>
{% if wish_game_more %}
<a href="{% url 'users:game_list' user.id 'wish' %}" class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
<ul class="entity-sort__entity-list">
{% for wish_game_mark in wish_game_marks %}
<li class="entity-sort__entity">
<a href="{% url 'games:retrieve' wish_game_mark.game.id %}">
<img src="{{ wish_game_mark.game.cover|thumb:'normal' }}" alt="{{wish_game_mark.game.title}}"
class="entity-sort__entity-img">
<div class="entity-sort__entity-name" title="{{wish_game_mark.game.title}}">
{{ wish_game_mark.game.title }}</div>
</a>
</li>
{% empty %}
<div>暂无记录</div>
{% endfor %}
</ul>
</div>
<div class="entity-sort" id="gameDo">
<h5 class="entity-sort__label">
{% trans '在玩的游戏' %}
</h5>
{% if do_game_more %}
<a href="{% url 'users:game_list' user.id 'do' %}" class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
<ul class="entity-sort__entity-list">
{% for do_game_mark in do_game_marks %}
<li class="entity-sort__entity">
<a href="{% url 'games:retrieve' do_game_mark.game.id %}">
<img src="{{ do_game_mark.game.cover|thumb:'normal' }}" alt="{{do_game_mark.game.title}}"
class="entity-sort__entity-img">
<div class="entity-sort__entity-name" title="{{do_game_mark.game.title}}">
{{ do_game_mark.game.title }}</div>
</a>
</li>
{% empty %}
<div>暂无记录</div>
{% endfor %}
</ul>
</div>
<div class="entity-sort" id="gameCollect">
<h5 class="entity-sort__label">
{% trans '玩过的游戏' %}
</h5>
{% if collect_game_more %}
<a href="{% url 'users:game_list' user.id 'collect' %}" class="entity-sort__more-link">{% trans '更多' %}</a>
{% endif %}
<ul class="entity-sort__entity-list">
{% for collect_game_mark in collect_game_marks %}
<li class="entity-sort__entity">
<a href="{% url 'games:retrieve' collect_game_mark.game.id %}">
<img src="{{ collect_game_mark.game.cover|thumb:'normal' }}" alt="{{collect_game_mark.game.title}}"
class="entity-sort__entity-img">
<span class="entity-sort__entity-name" title="{{collect_game_mark.game.title}}">{{collect_game_mark.game.title }}</span>
</a>
</li>
{% empty %}
<div>暂无记录</div>
{% endfor %}
</ul>
</div>
</div>
{% if user == request.user %}

View file

@ -98,7 +98,7 @@
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ mark.movie.brief | truncate:170 }}
{{ mark.movie.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in mark.movie.tag_list %}

View file

@ -15,6 +15,7 @@ urlpatterns = [
path('<int:id>/book/<str:status>/', book_list, name='book_list'),
path('<int:id>/movie/<str:status>/', movie_list, name='movie_list'),
path('<int:id>/music/<str:status>/', music_list, name='music_list'),
path('<int:id>/game/<str:status>/', game_list, name='game_list'),
path('<str:id>/', home, name='home'),
path('<str:id>/followers/', followers, name='followers'),
path('<str:id>/following/', following, name='following'),

View file

@ -12,15 +12,18 @@ from .forms import ReportForm
from mastodon.auth import *
from mastodon.api import *
from mastodon import mastodon_request_included
from common.views import BOOKS_PER_SET, ITEMS_PER_PAGE, PAGE_LINK_NUMBER, TAG_NUMBER_ON_LIST, MOVIES_PER_SET, MUSIC_PER_SET
from common.config import *
from common.models import MarkStatusEnum
from common.utils import PageLinksGenerator
from management.models import Announcement
from books.models import *
from movies.models import *
from music.models import *
from games.models import *
from books.forms import BookMarkStatusTranslator
from movies.forms import MovieMarkStatusTranslator
from music.forms import MusicMarkStatusTranslator
from games.forms import GameMarkStatusTranslator
from mastodon.models import MastodonApplication
@ -172,10 +175,37 @@ def home(request, id):
'secondary_msg': sec_msg,
}
)
# access one's own home page
if user == request.user:
return redirect("common:home")
reports = Report.objects.order_by(
'-submitted_time').filter(is_read=False)
unread_announcements = Announcement.objects.filter(
pk__gt=request.user.read_announcement_index).order_by('-pk')
try:
request.user.read_announcement_index = Announcement.objects.latest(
'pk').pk
request.user.save(update_fields=['read_announcement_index'])
except ObjectDoesNotExist as e:
# when there is no annoucenment
pass
book_marks = request.user.user_bookmarks.all()
movie_marks = request.user.user_moviemarks.all()
album_marks = request.user.user_albummarks.all()
song_marks = request.user.user_songmarks.all()
game_marks = request.user.user_gamemarks.all()
# visit other's home page
else:
# mastodon request
# no these value on other's home page
reports = None
unread_announcements = None
# cross site info for visiting other's home page
user.target_site_id = get_cross_site_id(
user, request.user.mastodon_site, request.session['oauth_token'])
# make queries
relation = get_relationship(request.user, user, request.session['oauth_token'])[0]
if relation['blocked_by']:
msg = _("你没有访问TA主页的权限😥")
@ -187,89 +217,86 @@ def home(request, id):
}
)
book_marks = BookMark.get_available_by_user(user, relation['following'])
do_book_marks = book_marks.filter(status=MarkStatusEnum.DO)
do_books_more = True if do_book_marks.count() > BOOKS_PER_SET else False
wish_book_marks = book_marks.filter(status=MarkStatusEnum.WISH)
wish_books_more = True if wish_book_marks.count() > BOOKS_PER_SET else False
collect_book_marks = book_marks.filter(status=MarkStatusEnum.COLLECT)
collect_books_more = True if collect_book_marks.count() > BOOKS_PER_SET else False
movie_marks = MovieMark.get_available_by_user(user, relation['following'])
do_movie_marks = movie_marks.filter(status=MarkStatusEnum.DO)
do_movies_more = True if do_movie_marks.count() > BOOKS_PER_SET else False
wish_movie_marks = movie_marks.filter(status=MarkStatusEnum.WISH)
wish_movies_more = True if wish_movie_marks.count() > BOOKS_PER_SET else False
collect_movie_marks = movie_marks.filter(status=MarkStatusEnum.COLLECT)
collect_movies_more = True if collect_movie_marks.count() > BOOKS_PER_SET else False
song_marks = SongMark.get_available_by_user(user, relation['following'])
album_marks = AlbumMark.get_available_by_user(user, relation['following'])
game_marks = GameMark.get_available_by_user(user, relation['following'])
do_music_marks = list(song_marks.filter(status=MarkStatusEnum.DO)[:MUSIC_PER_SET]) \
+ list(album_marks.filter(status=MarkStatusEnum.DO)[:MUSIC_PER_SET])
do_music_more = True if len(do_music_marks) > MUSIC_PER_SET else False
do_music_marks = sorted(do_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
# book marks
filtered_book_marks = filter_marks(book_marks, BOOKS_PER_SET, 'book')
wish_music_marks = list(song_marks.filter(status=MarkStatusEnum.WISH)[:MUSIC_PER_SET]) \
+ list(album_marks.filter(status=MarkStatusEnum.WISH)[:MUSIC_PER_SET])
wish_music_more = True if len(wish_music_marks) > MUSIC_PER_SET else False
wish_music_marks = sorted(wish_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
# movie marks
filtered_movie_marks = filter_marks(movie_marks, MOVIES_PER_SET, 'movie')
collect_music_marks = list(song_marks.filter(status=MarkStatusEnum.COLLECT)[:MUSIC_PER_SET]) \
+ list(album_marks.filter(status=MarkStatusEnum.COLLECT)[:MUSIC_PER_SET])
collect_music_more = True if len(collect_music_marks) > MUSIC_PER_SET else False
collect_music_marks = sorted(collect_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
# game marks
filtered_game_marks = filter_marks(game_marks, GAMES_PER_SET, 'game')
for mark in do_music_marks + wish_music_marks + collect_music_marks:
# for template convenience
if mark.__class__ == AlbumMark:
mark.type = "album"
else:
mark.type = "song"
# music marks
do_music_marks = list(song_marks.filter(status=MarkStatusEnum.DO)[:MUSIC_PER_SET]) \
+ list(album_marks.filter(status=MarkStatusEnum.DO)[:MUSIC_PER_SET])
do_music_more = True if len(do_music_marks) > MUSIC_PER_SET else False
do_music_marks = sorted(do_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
user.target_site_id = get_cross_site_id(
user, request.user.mastodon_site, request.session['oauth_token'])
wish_music_marks = list(song_marks.filter(status=MarkStatusEnum.WISH)[:MUSIC_PER_SET]) \
+ list(album_marks.filter(status=MarkStatusEnum.WISH)[:MUSIC_PER_SET])
wish_music_more = True if len(wish_music_marks) > MUSIC_PER_SET else False
wish_music_marks = sorted(wish_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
try:
layout = user.preference.get_serialized_home_layout()
except ObjectDoesNotExist:
Preference.objects.create(user=user)
layout = user.preference.get_serialized_home_layout()
collect_music_marks = list(song_marks.filter(status=MarkStatusEnum.COLLECT)[:MUSIC_PER_SET]) \
+ list(album_marks.filter(status=MarkStatusEnum.COLLECT)[:MUSIC_PER_SET])
collect_music_more = True if len(collect_music_marks) > MUSIC_PER_SET else False
collect_music_marks = sorted(collect_music_marks, key=lambda e: e.edited_time, reverse=True)[:MUSIC_PER_SET]
return render(
request,
'common/home.html',
{
'user': user,
'do_book_marks': do_book_marks[:BOOKS_PER_SET],
'wish_book_marks': wish_book_marks[:BOOKS_PER_SET],
'collect_book_marks': collect_book_marks[:BOOKS_PER_SET],
'do_books_more': do_books_more,
'wish_books_more': wish_books_more,
'collect_books_more': collect_books_more,
'do_movie_marks': do_movie_marks[:MOVIES_PER_SET],
'wish_movie_marks': wish_movie_marks[:MOVIES_PER_SET],
'collect_movie_marks': collect_movie_marks[:MOVIES_PER_SET],
'do_movies_more': do_movies_more,
'wish_movies_more': wish_movies_more,
'collect_movies_more': collect_movies_more,
'do_music_marks': do_music_marks,
'wish_music_marks': wish_music_marks,
'collect_music_marks': collect_music_marks,
'do_music_more': do_music_more,
'wish_music_more': wish_music_more,
'collect_music_more': collect_music_more,
'layout': layout,
}
)
for mark in do_music_marks + wish_music_marks + collect_music_marks:
# for template convenience
if mark.__class__ == AlbumMark:
mark.type = "album"
else:
mark.type = "song"
try:
layout = user.preference.get_serialized_home_layout()
except ObjectDoesNotExist:
Preference.objects.create(user=user)
layout = user.preference.get_serialized_home_layout()
return render(
request,
'users/home.html',
{
'user': user,
**filtered_book_marks,
**filtered_movie_marks,
**filtered_game_marks,
'do_music_marks': do_music_marks,
'wish_music_marks': wish_music_marks,
'collect_music_marks': collect_music_marks,
'do_music_more': do_music_more,
'wish_music_more': wish_music_more,
'collect_music_more': collect_music_more,
'layout': layout,
'reports': reports,
'unread_announcements': unread_announcements,
}
)
else:
return HttpResponseBadRequest()
def filter_marks(queryset, maximum, type_name):
result = {}
for k in MarkStatusEnum.names:
result[f"{k}_{type_name}_marks"] = queryset.filter(
status=MarkStatusEnum[k.upper()]
).order_by("-edited_time")
if result[f"{k}_{type_name}_marks"].count() > maximum:
result[f"{k}_{type_name}_more"] = True
result[f"{k}_{type_name}_marks"] = result[f"{k}_{type_name}_marks"][:maximum]
else:
result[f"{k}_{type_name}_more"] = False
return result
@mastodon_request_included
@login_required
def followers(request, id):
@ -429,7 +456,7 @@ def book_list(request, id, status):
mark.book.tag_list = mark.book.get_tags_manager().values('content').annotate(
tag_frequency=Count('content')).order_by('-tag_frequency')[:TAG_NUMBER_ON_LIST]
marks.pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages)
list_title = str(BookMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("标记的书"))
list_title = str(BookMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的书"))
return render(
request,
'users/book_list.html',
@ -471,7 +498,7 @@ def movie_list(request, id, status):
'msg': msg,
'secondary_msg': sec_msg,
}
)
)
if not user == request.user:
# mastodon request
relation = get_relationship(request.user, user, request.session['oauth_token'])[0]
@ -484,10 +511,11 @@ def movie_list(request, id, status):
'msg': msg,
}
)
queryset = MovieMark.get_available_by_user(user, relation['following']).filter(
status=MarkStatusEnum[status.upper()]).order_by("-edited_time")
user.target_site_id = get_cross_site_id(
user, request.user.mastodon_site, request.session['oauth_token'])
queryset = MovieMark.get_available_by_user(user, relation['following']).filter(
status=MarkStatusEnum[status.upper()]).order_by("-edited_time")
else:
queryset = MovieMark.objects.filter(
owner=user, status=MarkStatusEnum[status.upper()]).order_by("-edited_time")
@ -498,7 +526,7 @@ def movie_list(request, id, status):
mark.movie.tag_list = mark.movie.get_tags_manager().values('content').annotate(
tag_frequency=Count('content')).order_by('-tag_frequency')[:TAG_NUMBER_ON_LIST]
marks.pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages)
list_title = str(MovieMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("标记的电影和剧集"))
list_title = str(MovieMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的电影和剧集"))
return render(
request,
'users/movie_list.html',
@ -512,6 +540,76 @@ def movie_list(request, id, status):
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def game_list(request, id, status):
if request.method == 'GET':
if not status.upper() in MarkStatusEnum.names:
return HttpResponseBadRequest()
if isinstance(id, str):
try:
username = id.split('@')[0]
site = id.split('@')[1]
except IndexError as e:
return HttpResponseBadRequest("Invalid user id")
query_kwargs = {'username': username, 'mastodon_site': site}
elif isinstance(id, int):
query_kwargs = {'pk': id}
try:
user = User.objects.get(**query_kwargs)
except ObjectDoesNotExist:
msg = _("😖哎呀这位老师还没有注册书影音呢快去长毛象喊TA来吧")
sec_msg = _("目前只开放本站用户注册")
return render(
request,
'common/error.html',
{
'msg': msg,
'secondary_msg': sec_msg,
}
)
if not user == request.user:
# mastodon request
relation = get_relationship(request.user, user, request.session['oauth_token'])[0]
if relation['blocked_by']:
msg = _("你没有访问TA主页的权限😥")
return render(
request,
'common/error.html',
{
'msg': msg,
}
)
user.target_site_id = get_cross_site_id(
user, request.user.mastodon_site, request.session['oauth_token'])
queryset = GameMark.get_available_by_user(user, relation['following']).filter(
status=MarkStatusEnum[status.upper()]).order_by("-edited_time")
else:
queryset = GameMark.objects.filter(
owner=user, status=MarkStatusEnum[status.upper()]).order_by("-edited_time")
paginator = Paginator(queryset, ITEMS_PER_PAGE)
page_number = request.GET.get('page', default=1)
marks = paginator.get_page(page_number)
for mark in marks:
mark.game.tag_list = mark.game.get_tags_manager().values('content').annotate(
tag_frequency=Count('content')).order_by('-tag_frequency')[:TAG_NUMBER_ON_LIST]
marks.pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages)
list_title = str(GameMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的游戏"))
return render(
request,
'users/game_list.html',
{
'marks': marks,
'user': user,
'list_title' : list_title,
}
)
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def music_list(request, id, status):
@ -578,7 +676,7 @@ def music_list(request, id, status):
tag_frequency=Count('content')).order_by('-tag_frequency')[:TAG_NUMBER_ON_LIST]
marks.pagination = PageLinksGenerator(PAGE_LINK_NUMBER, page_number, paginator.num_pages)
list_title = str(MovieMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("标记的音乐"))
list_title = str(MusicMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的音乐"))
return render(
request,
'users/music_list.html',