add douban movie scraper & movie app skeleton
This commit is contained in:
parent
3a83c954cd
commit
ac9289c289
19 changed files with 2532 additions and 37 deletions
|
@ -24,6 +24,7 @@ urlpatterns = [
|
|||
path('markdownx/', include('markdownx.urls')),
|
||||
path('users/', include('users.urls')),
|
||||
path('books/', include('books.urls')),
|
||||
path('movies/', include('movies.urls')),
|
||||
path('', include('common.urls')),
|
||||
|
||||
]
|
||||
|
|
|
@ -3,10 +3,13 @@ import random
|
|||
from lxml import html
|
||||
import re
|
||||
from boofilsic.settings import LUMINATI_USERNAME, LUMINATI_PASSWORD, DEBUG
|
||||
from movies.models import MovieGenreEnum
|
||||
|
||||
RE_NUMBERS = re.compile(r"\d+\d*")
|
||||
RE_WHITESPACES = re.compile(r"\s+")
|
||||
RE_DOUBAN_BOOK_URL = re.compile(r"https://book.douban.com/subject/\d+/")
|
||||
# without slash at the end
|
||||
RE_DOUBAN_BOOK_URL = re.compile(r"https://book.douban.com/subject/\d+")
|
||||
RE_DOUBAN_MOVIE_URL = re.compile(r"https://movie.douban.com/subject/\d+")
|
||||
|
||||
DEFAULT_REQUEST_HEADERS = {
|
||||
'Host': 'book.douban.com',
|
||||
|
@ -18,7 +21,7 @@ DEFAULT_REQUEST_HEADERS = {
|
|||
'Connection': 'keep-alive',
|
||||
'DNT': '1',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Cache-Control': 'no-cache',
|
||||
}
|
||||
|
||||
# in seconds
|
||||
|
@ -28,24 +31,67 @@ TIMEOUT = 10
|
|||
PORT = 22225
|
||||
|
||||
|
||||
def scrape_douban_book(url):
|
||||
if RE_DOUBAN_BOOK_URL.match(url) is None:
|
||||
raise ValueError("not valid douban book url")
|
||||
def download_page(url, regex, headers):
|
||||
url = regex.findall(url)
|
||||
if not url:
|
||||
raise ValueError("not valid url")
|
||||
else:
|
||||
url = url[0] + '/'
|
||||
|
||||
session_id = random.random()
|
||||
proxy_url = ('http://%s-country-cn-session-%s:%s@zproxy.lum-superproxy.io:%d' %
|
||||
(LUMINATI_USERNAME, session_id, LUMINATI_PASSWORD, PORT))
|
||||
(LUMINATI_USERNAME, session_id, LUMINATI_PASSWORD, PORT))
|
||||
proxies = {
|
||||
'http': proxy_url,
|
||||
'https': proxy_url,
|
||||
'https': proxy_url,
|
||||
}
|
||||
if DEBUG:
|
||||
proxies = None
|
||||
r = requests.get(url, proxies=proxies, headers=DEFAULT_REQUEST_HEADERS, timeout=TIMEOUT)
|
||||
# if DEBUG:
|
||||
# proxies = None
|
||||
r = requests.get(url, proxies=proxies, headers=headers, timeout=TIMEOUT)
|
||||
# r = requests.get(url, headers=DEFAULT_REQUEST_HEADERS, timeout=TIMEOUT)
|
||||
|
||||
content = html.fromstring(r.content.decode('utf-8'))
|
||||
|
||||
return html.fromstring(r.content.decode('utf-8'))
|
||||
|
||||
|
||||
def download_image(url):
|
||||
if url is None:
|
||||
return
|
||||
raw_img = None
|
||||
session_id = random.random()
|
||||
proxy_url = ('http://%s-country-cn-session-%s:%s@zproxy.lum-superproxy.io:%d' %
|
||||
(LUMINATI_USERNAME, session_id, LUMINATI_PASSWORD, PORT))
|
||||
proxies = {
|
||||
'http': proxy_url,
|
||||
'https': proxy_url,
|
||||
}
|
||||
# if DEBUG:
|
||||
# proxies = None
|
||||
if url:
|
||||
img_response = requests.get(
|
||||
url,
|
||||
headers={
|
||||
'accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
|
||||
'accept-encoding': 'gzip, deflate',
|
||||
'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,fr-FR;q=0.6,fr;q=0.5,zh-TW;q=0.4',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72',
|
||||
'cache-control': 'no-cache',
|
||||
'dnt': '1',
|
||||
},
|
||||
proxies=proxies,
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
if img_response.status_code == 200:
|
||||
raw_img = img_response.content
|
||||
return raw_img
|
||||
|
||||
|
||||
def scrape_douban_book(url):
|
||||
regex = RE_DOUBAN_BOOK_URL
|
||||
headers = DEFAULT_REQUEST_HEADERS.copy()
|
||||
headers['Host'] = 'book.douban.com'
|
||||
content = download_page(url, regex, headers)
|
||||
|
||||
# parsing starts here
|
||||
try:
|
||||
title = content.xpath("/html/body/div[3]/h1/span/text()")[0].strip()
|
||||
except IndexError:
|
||||
|
@ -111,23 +157,7 @@ def scrape_douban_book(url):
|
|||
|
||||
img_url_elem = content.xpath("//*[@id='mainpic']/a/img/@src")
|
||||
img_url = img_url_elem[0].strip() if img_url_elem else None
|
||||
raw_img = None
|
||||
if img_url:
|
||||
img_response = requests.get(
|
||||
img_url,
|
||||
headers={
|
||||
'accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
|
||||
'accept-encoding': 'gzip, deflate',
|
||||
'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,fr-FR;q=0.6,fr;q=0.5,zh-TW;q=0.4',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edg/81.0.416.72',
|
||||
'cache-control': 'no-cache',
|
||||
'dnt': '1' ,
|
||||
},
|
||||
proxies=proxies,
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
if img_response.status_code == 200:
|
||||
raw_img = img_response.content
|
||||
raw_img = download_image(img_url)
|
||||
|
||||
# there are two html formats for authors and translators
|
||||
authors_elem = content.xpath("""//div[@id='info']//span[text()='作者:']/following-sibling::br[1]/
|
||||
|
@ -182,3 +212,143 @@ def scrape_douban_book(url):
|
|||
'other_info' : other
|
||||
}
|
||||
return data, raw_img
|
||||
|
||||
|
||||
def scrape_douban_movie(url):
|
||||
regex = RE_DOUBAN_MOVIE_URL
|
||||
headers = DEFAULT_REQUEST_HEADERS.copy()
|
||||
headers['Host'] = 'movie.douban.com'
|
||||
content = download_page(url, regex, headers)
|
||||
|
||||
# parsing starts here
|
||||
try:
|
||||
raw_title = content.xpath(
|
||||
"//span[@property='v:itemreviewed']/text()")[0].strip()
|
||||
except IndexError:
|
||||
raise ValueError("given url contains no movie info")
|
||||
|
||||
orig_title = content.xpath(
|
||||
"//img[@rel='v:image']/@alt")[0].strip()
|
||||
title = raw_title.split(orig_title)[0].strip()
|
||||
# if has no chinese title
|
||||
if title == '':
|
||||
title = orig_title
|
||||
|
||||
# there are two html formats for authors and translators
|
||||
other_title_elem = content.xpath(
|
||||
"//div[@id='info']//span[text()='又名:']/following-sibling::text()[1]")
|
||||
other_title = other_title_elem[0].strip().split(' / ') if other_title_elem else None
|
||||
|
||||
imbd_elem = content.xpath(
|
||||
"//div[@id='info']//span[text()='IMDb链接:']/following-sibling::a[1]/text()")
|
||||
imbd_code = imbd_elem[0].strip() if imbd_elem else None
|
||||
|
||||
director_elem = content.xpath("//div[@id='info']//span[text()='导演']/following-sibling::span[1]/a/text()")
|
||||
director = director_elem if director_elem else None
|
||||
|
||||
playwright_elem = content.xpath("//div[@id='info']//span[text()='编剧']/following-sibling::span[1]/a/text()")
|
||||
playwright = playwright_elem if playwright_elem else None
|
||||
|
||||
actor_elem = content.xpath("//div[@id='info']//span[text()='主演']/following-sibling::span[1]/a/text()")
|
||||
actor = actor_elem if actor_elem else None
|
||||
|
||||
# construct genre translator
|
||||
genre_translator = {}
|
||||
attrs = [attr for attr in dir(MovieGenreEnum) if not '__' in attr]
|
||||
for attr in attrs:
|
||||
genre_translator[getattr(MovieGenreEnum, attr).label] = getattr(
|
||||
MovieGenreEnum, attr).value
|
||||
|
||||
genre_elem = content.xpath("//span[@property='v:genre']/text()")
|
||||
if genre_elem:
|
||||
genre = []
|
||||
for g in genre_elem:
|
||||
genre.append(genre_translator[g])
|
||||
else:
|
||||
genre = None
|
||||
|
||||
showtime_elem = content.xpath("//span[@property='v:initialReleaseDate']/text()")
|
||||
if showtime_elem:
|
||||
showtime = []
|
||||
for st in showtime_elem:
|
||||
time = st.split('(')[0]
|
||||
region = st.split('(')[1][0:-1]
|
||||
showtime.append({time: region})
|
||||
else:
|
||||
showtime = None
|
||||
|
||||
site_elem = content.xpath("//div[@id='info']//span[text()='官方网站:']/following-sibling::a[1]/@href")
|
||||
site = site_elem[0].strip() if site_elem else None
|
||||
|
||||
area_elem = content.xpath("//div[@id='info']//span[text()='制片国家/地区:']/following-sibling::text()[1]")
|
||||
if area_elem:
|
||||
area = [a.strip() for a in area_elem[0].split(' / ')]
|
||||
else:
|
||||
area = None
|
||||
|
||||
language_elem = content.xpath("//div[@id='info']//span[text()='语言:']/following-sibling::text()[1]")
|
||||
if language_elem:
|
||||
language = [a.strip() for a in language_elem[0].split(' / ')]
|
||||
else:
|
||||
language = None
|
||||
|
||||
year_elem = content.xpath("//span[@class='year']/text()")
|
||||
year = int(year_elem[0][1:-1]) if year_elem else None
|
||||
|
||||
duration_elem = content.xpath("//span[@property='v:runtime']/text()")
|
||||
other_duration_elem = content.xpath("//span[@property='v:runtime']/following-sibling::text()[1]")
|
||||
if duration_elem:
|
||||
duration = duration_elem[0].strip()
|
||||
if other_duration_elem:
|
||||
duration += other_duration_elem[0]
|
||||
else:
|
||||
duration = None
|
||||
|
||||
season_elem = content.xpath("//*[@id='season']/option[@selected='selected']/text()")
|
||||
if not season_elem:
|
||||
season_elem = content.xpath(
|
||||
"//div[@id='info']//span[text()='季数:']/following-sibling::text()[1]")
|
||||
season = int(season_elem[0].strip()) if season_elem else None
|
||||
else:
|
||||
season = int(season_elem[0].strip())
|
||||
|
||||
episodes_elem = content.xpath(
|
||||
"//div[@id='info']//span[text()='集数:']/following-sibling::text()[1]")
|
||||
episodes = int(episodes_elem[0].strip()) if episodes_elem else None
|
||||
|
||||
single_episode_length_elem = content.xpath(
|
||||
"//div[@id='info']//span[text()='单集片长:']/following-sibling::text()[1]")
|
||||
single_episode_length = int(single_episode_length_elem[0].strip()) if single_episode_length_elem else None
|
||||
|
||||
# if has field `episodes` not none then must be series
|
||||
is_series = True if episodes else False
|
||||
|
||||
brief_elem = content.xpath("//span[@property='v:summary']/text()")
|
||||
brief = brief[0].strip() if brief_elem else None
|
||||
|
||||
img_url_elem = content.xpath("//img[@rel='v:image']/@src")
|
||||
img_url = img_url_elem[0].strip() if img_url_elem else None
|
||||
raw_img = download_image(img_url)
|
||||
|
||||
data = {
|
||||
'title': title,
|
||||
'orig_title': orig_title,
|
||||
'other_title': other_title,
|
||||
'imbd_code': imbd_code,
|
||||
'director': director,
|
||||
'playwright': playwright,
|
||||
'actor': actor,
|
||||
'genre': genre,
|
||||
'showtime': showtime,
|
||||
'site': site,
|
||||
'area': area,
|
||||
'language': language,
|
||||
'year': year,
|
||||
'duration': duration,
|
||||
'season': season,
|
||||
'episodes': episodes,
|
||||
'single_episode_length': single_episode_length,
|
||||
'brief': brief,
|
||||
'is_series': is_series,
|
||||
}
|
||||
return data, raw_img
|
||||
|
|
0
movies/__init__.py
Normal file
0
movies/__init__.py
Normal file
5
movies/apps.py
Normal file
5
movies/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MoviesConfig(AppConfig):
|
||||
name = 'movies'
|
179
movies/forms.py
Normal file
179
movies/forms.py
Normal file
|
@ -0,0 +1,179 @@
|
|||
from django import forms
|
||||
from django.contrib.postgres.forms import SimpleArrayField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .models import Movie, MovieMark, MovieReview
|
||||
from common.models import MarkStatusEnum
|
||||
from common.forms import RadioBooleanField, RatingValidator, TagField, TagInput
|
||||
from common.forms import KeyValueInput
|
||||
from common.forms import PreviewImageInput
|
||||
|
||||
|
||||
def MovieMarkStatusTranslator(status):
|
||||
trans_dict = {
|
||||
MarkStatusEnum.DO.value: _("在看"),
|
||||
MarkStatusEnum.WISH.value: _("想看"),
|
||||
MarkStatusEnum.COLLECT.value: _("看过")
|
||||
}
|
||||
return trans_dict[status]
|
||||
|
||||
|
||||
class MovieForm(forms.ModelForm):
|
||||
# pub_year = forms.IntegerField(
|
||||
# required=False, max_value=9999, min_value=0, label=_("出版年份"))
|
||||
# pub_month = forms.IntegerField(
|
||||
# required=False, max_value=12, min_value=1, label=_("出版月份"))
|
||||
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||||
|
||||
class Meta:
|
||||
model = Movie
|
||||
fields = [
|
||||
'id',
|
||||
'title',
|
||||
'orig_title',
|
||||
'other_title',
|
||||
'imbd_code',
|
||||
'director',
|
||||
'playwright',
|
||||
'actor',
|
||||
'genre',
|
||||
'showtime',
|
||||
'site',
|
||||
'area',
|
||||
'language',
|
||||
'year',
|
||||
'duration',
|
||||
'season',
|
||||
'episodes',
|
||||
'single_episode_length',
|
||||
'cover',
|
||||
'brief',
|
||||
'is_series',
|
||||
]
|
||||
labels = {
|
||||
'title': _("标题"),
|
||||
'orig_title': _("原名"),
|
||||
'other_title': _("又名"),
|
||||
'imbd_code': _("IMBd编号"),
|
||||
'director': _("导演"),
|
||||
'playwright': _("编剧"),
|
||||
'actor': _("主演"),
|
||||
'genre': _("类型"),
|
||||
'showtime': _("上映时间"),
|
||||
'site': _("官方网站"),
|
||||
'area': _("国家/地区"),
|
||||
'language': _("语言"),
|
||||
'year': _("年份"),
|
||||
'duration': _("片长"),
|
||||
'season': _("季数"),
|
||||
'episodes': _("集数"),
|
||||
'single_episode_length': _("单集片长"),
|
||||
'cover': _("封面"),
|
||||
'brief': _("简介"),
|
||||
'is_series': _("是否是剧集"),
|
||||
}
|
||||
|
||||
# widgets = {
|
||||
# 'author': forms.TextInput(attrs={'placeholder': _("多个作者使用英文逗号分隔")}),
|
||||
# 'translator': forms.TextInput(attrs={'placeholder': _("多个译者使用英文逗号分隔")}),
|
||||
# 'other_info': KeyValueInput(),
|
||||
# # 'cover': forms.FileInput(),
|
||||
# 'cover': PreviewImageInput(),
|
||||
# }
|
||||
|
||||
# def clean_isbn(self):
|
||||
# isbn = self.cleaned_data.get('isbn')
|
||||
# if isbn:
|
||||
# isbn = isbn.strip()
|
||||
# return isbn
|
||||
|
||||
|
||||
class MovieMarkForm(forms.ModelForm):
|
||||
IS_PRIVATE_CHOICES = [
|
||||
(True, _("仅关注者")),
|
||||
(False, _("公开")),
|
||||
]
|
||||
STATUS_CHOICES = [(v, MovieMarkStatusTranslator(v))
|
||||
for v in MarkStatusEnum.values]
|
||||
|
||||
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||||
share_to_mastodon = forms.BooleanField(
|
||||
label=_("分享到长毛象"), initial=True, required=False)
|
||||
rating = forms.IntegerField(
|
||||
validators=[RatingValidator()], widget=forms.HiddenInput(), required=False)
|
||||
status = forms.ChoiceField(
|
||||
label=_(""),
|
||||
widget=forms.RadioSelect(),
|
||||
choices=STATUS_CHOICES
|
||||
)
|
||||
is_private = RadioBooleanField(
|
||||
label=_("可见性"),
|
||||
initial=True,
|
||||
choices=IS_PRIVATE_CHOICES
|
||||
)
|
||||
tags = TagField(
|
||||
required=False,
|
||||
widget=TagInput(attrs={'placeholder': _("回车增加标签")}),
|
||||
label=_("标签")
|
||||
)
|
||||
text = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.Textarea(
|
||||
attrs={
|
||||
"placeholder": _("最多只能写360字哦~"),
|
||||
"maxlength": 360
|
||||
}
|
||||
),
|
||||
|
||||
label=_("短评"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = MovieMark
|
||||
fields = [
|
||||
'id',
|
||||
'movie',
|
||||
'status',
|
||||
'rating',
|
||||
'text',
|
||||
'is_private',
|
||||
]
|
||||
labels = {
|
||||
'rating': _("评分"),
|
||||
}
|
||||
widgets = {
|
||||
'movie': forms.TextInput(attrs={"hidden": ""}),
|
||||
}
|
||||
|
||||
|
||||
class MovieReviewForm(forms.ModelForm):
|
||||
IS_PRIVATE_CHOICES = [
|
||||
(True, _("仅关注者")),
|
||||
(False, _("公开")),
|
||||
]
|
||||
share_to_mastodon = forms.BooleanField(
|
||||
label=_("分享到长毛象"), initial=True, required=False)
|
||||
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||||
is_private = RadioBooleanField(
|
||||
label=_("可见性"),
|
||||
initial=True,
|
||||
choices=IS_PRIVATE_CHOICES
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = MovieReview
|
||||
fields = [
|
||||
'id',
|
||||
'movie',
|
||||
'title',
|
||||
'content',
|
||||
'is_private'
|
||||
]
|
||||
labels = {
|
||||
'book': "",
|
||||
'title': _("标题"),
|
||||
'content': _("正文"),
|
||||
'share_to_mastodon': _("分享到长毛象")
|
||||
}
|
||||
widgets = {
|
||||
'movie': forms.TextInput(attrs={"hidden": ""}),
|
||||
}
|
|
@ -4,15 +4,22 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.db import models
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from common.models import Resource, Mark, Review, Tag
|
||||
from boofilsic.settings import BOOK_MEDIA_PATH_ROOT, DEFAULT_BOOK_IMAGE
|
||||
from boofilsic.settings import MOVIE_MEDIA_PATH_ROOT, DEFAULT_MOVIE_IMAGE
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def movie_cover_path():
|
||||
pass
|
||||
def movie_cover_path(instance, filename):
|
||||
ext = filename.split('.')[-1]
|
||||
filename = "%s.%s" % (uuid.uuid4(), ext)
|
||||
root = ''
|
||||
if MOVIE_MEDIA_PATH_ROOT.endswith('/'):
|
||||
root = MOVIE_MEDIA_PATH_ROOT
|
||||
else:
|
||||
root = MOVIE_MEDIA_PATH_ROOT + '/'
|
||||
return root + timezone.now().strftime('%Y/%m/%d') + f'{filename}'
|
||||
|
||||
|
||||
class GenreEnum(models.TextChoices):
|
||||
class MovieGenreEnum(models.TextChoices):
|
||||
DRAMA = 'Drama', _('剧情')
|
||||
KIDS = 'Kids', _('儿童')
|
||||
COMEDY = 'Comedy', _('喜剧')
|
||||
|
@ -90,7 +97,8 @@ class Movie(Resource):
|
|||
_("genre"),
|
||||
blank=True,
|
||||
default='',
|
||||
choices=GenreEnum.choices
|
||||
choices=MovieGenreEnum.choices,
|
||||
max_length=50
|
||||
)
|
||||
showtime = postgres.ArrayField(
|
||||
# HStoreField stores showtime-region pair
|
||||
|
@ -101,9 +109,10 @@ class Movie(Resource):
|
|||
)
|
||||
site = models.URLField(_('site url'), max_length=200)
|
||||
|
||||
# country or area
|
||||
# country or region
|
||||
area = postgres.ArrayField(
|
||||
models.CharField(
|
||||
_("country or region"),
|
||||
blank=True,
|
||||
default='',
|
||||
max_length=100,
|
||||
|
@ -127,13 +136,17 @@ class Movie(Resource):
|
|||
year = models.PositiveIntegerField(null=True, blank=True)
|
||||
duration = models.CharField(blank=True, default='', max_length=100)
|
||||
|
||||
cover = models.ImageField(_("poster"), upload_to=movie_cover_path, default=DEFAULT_MOVIE_IMAGE, blank=True)
|
||||
|
||||
############################################
|
||||
# exclusive fields to series
|
||||
############################################
|
||||
season = models.PositiveSmallIntegerField(null=True, blank=True)
|
||||
# how many episodes in the season
|
||||
episodes = models.PositiveIntegerField(null=True, blank=True)
|
||||
tv_station = models.CharField(blank=True, default='', max_length=200)
|
||||
# deprecated
|
||||
# tv_station = models.CharField(blank=True, default='', max_length=200)
|
||||
single_episode_length = models.CharField(blank=True, default='', max_length=100)
|
||||
|
||||
############################################
|
||||
# category identifier
|
||||
|
@ -146,7 +159,7 @@ class Movie(Resource):
|
|||
|
||||
|
||||
def get_tags_manager(self):
|
||||
raise NotImplementedError
|
||||
return self.movie_tags
|
||||
|
||||
|
||||
class MovieMark(Mark):
|
||||
|
|
58
movies/templates/movies/create_update.html
Normal file
58
movies/templates/movies/create_update.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% 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://cdn.staticfile.org/jquery/3.5.0/jquery.min.js"></script>
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_edit.css' %}"> -->
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.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">
|
||||
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
// mark required
|
||||
$("#content input[required]").each(function () {
|
||||
$(this).prev().prepend("*");
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
112
movies/templates/movies/create_update_review.html
Normal file
112
movies/templates/movies/create_update_review.html
Normal 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 }}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.5.0/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.css' %}">
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_edit.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.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 'movies:retrieve' movie.id %}">
|
||||
<img src="{{ movie.cover.url }}" 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 'movies:retrieve' movie.id %}">{{ movie.title }}</a></h5>
|
||||
<div>{% if movie.isbn %}{% trans 'ISBN:' %}{{ movie.isbn }}{% endif %}</div>
|
||||
<div>{% if movie.author %}{% trans '作者:' %}
|
||||
{% for author in movie.author %}
|
||||
<span>{{ author }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}</div>
|
||||
<div>{% if movie.pub_house %}{% trans '出版社:' %}{{ movie.pub_house }}{% endif %}</div>
|
||||
<div>{%if movie.pub_year %}{% trans '出版时间:' %}{{ movie.pub_year }}{% trans '年' %}{% if movie.pub_month %}{{ movie.pub_month }}{% trans '月' %}{% endif %}{% endif %}</div>
|
||||
|
||||
{% if movie.rating %}
|
||||
{% trans '评分:' %}<span class="entity-card__rating-star rating-star" data-rating-score="{{ movie.rating | floatformat:"0" }}"> </span>
|
||||
<span class="entity-card__rating-score rating-score"> {{ movie.rating }} </span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dividing-line"></div>
|
||||
|
||||
<form action="{{ submit_url }}" method="post" class="review-form">
|
||||
{% csrf_token %}
|
||||
{{ form.movie }}
|
||||
<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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
92
movies/templates/movies/delete.html
Normal file
92
movies/templates/movies/delete.html
Normal file
|
@ -0,0 +1,92 @@
|
|||
{% 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://cdn.staticfile.org/jquery/3.5.0/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 'lib/css/milligram.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_edit.css' %}"> -->
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.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 'movies:retrieve' movie.id %}">
|
||||
<img src="{{ movie.cover.url }}" alt="" class="item-image float-left">
|
||||
</a>
|
||||
</div>
|
||||
<div class="entity-card__info-wrapper entity-card__info-wrapper--horizontal">
|
||||
<a href="{% url 'movies:retrieve' movie.id %}">
|
||||
<h5 class="entity-card__title">
|
||||
{{ movie.title }}
|
||||
</h5>
|
||||
</a>
|
||||
{% if movie.rating %}
|
||||
{% trans '评分:' %}<span class="entity-card__rating-star rating-star" data-rating-score="{{ movie.rating | floatformat:"0" }}">
|
||||
</span>
|
||||
<span class="entity-card__rating-score">{{ movie.rating }}</span>
|
||||
{% else %}
|
||||
<span>{% trans '评分:暂无评分' %}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if movie.last_editor %}
|
||||
<a href="{% url 'users:home' movie.last_editor.id %}">
|
||||
<div>{% trans '最近编辑者:' %}{{ movie.last_editor | default:"" }}</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div>{% trans '上次编辑时间:' %}{{ movie.edited_time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dividing-line"></div>
|
||||
<div class="clearfix">
|
||||
<form action="{% url 'movies:delete' movie.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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
110
movies/templates/movies/delete_review.html
Normal file
110
movies/templates/movies/delete_review.html
Normal file
|
@ -0,0 +1,110 @@
|
|||
{% 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://cdn.staticfile.org/jquery/3.5.0/jquery.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.css' %}">
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_edit.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"><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 'movies: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 %}</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>
|
385
movies/templates/movies/detail.html
Normal file
385
movies/templates/movies/detail.html
Normal file
|
@ -0,0 +1,385 @@
|
|||
{% 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">
|
||||
<meta property="og:title" content="NiceDB书 - {{ movie.title }}">
|
||||
<meta property="og:type" content="movie">
|
||||
<meta property="og:url" content="{{ request.build_absolute_uri }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ movie.cover.url }}">
|
||||
{% if movie.author %}
|
||||
<meta property="og:movie:author" content="{% for author in movie.author %}{{ author }}{% if not forloop.last %},{% endif %}{% endfor %}">
|
||||
{% endif %}
|
||||
{% if movie.isbn %}
|
||||
<meta property="og:movie:isbn" content="{{ movie.isbn }}">
|
||||
{% endif %}
|
||||
|
||||
<title>{% trans 'Nicedb - 电影/剧集详情' %} | {{ movie.title }}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.5.0/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_browse.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_modal.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.css' %}"> -->
|
||||
<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">
|
||||
|
||||
<img src="{{ movie.cover.url }}" class="entity-detail__img" alt="{{ movie.title }}">
|
||||
|
||||
<div class="entity-detail__info">
|
||||
<h5 class="entity-detail__title">
|
||||
{{ movie.title }}
|
||||
</h5>
|
||||
|
||||
<div class="entity-detail__fields">
|
||||
<div class="entity-detail__rating">
|
||||
{% if movie.rating %}
|
||||
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ movie.rating | floatformat:"0" }}"></span>
|
||||
<span class="entity-detail__rating-score"> {{ movie.rating }} </span>
|
||||
{% else %}
|
||||
<span> {% trans '评分:暂无评分' %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>{% if movie.isbn %}{% trans 'ISBN:' %}{{ movie.isbn }}{% endif %}</div>
|
||||
<div>{% if movie.author %}{% trans '作者:' %}
|
||||
{% for author in movie.author %}
|
||||
<span>{{ author }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}</div>
|
||||
<div>{% if movie.pub_house %}{% trans '出版社:' %}{{ movie.pub_house }}{% endif %}</div>
|
||||
<div>{% if movie.subtitle %}{% trans '副标题:' %}{{ movie.subtitle }}{% endif %}</div>
|
||||
<div>{% if movie.translator %}{% trans '译者:' %}
|
||||
{% for translator in movie.translator %}
|
||||
<span>{{ translator }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}</div>
|
||||
<div>{% if movie.orig_title %}{% trans '原作名:' %}{{ movie.orig_title }}{% endif %}</div>
|
||||
<div>{% if movie.language %}{% trans '语言:' %}{{ movie.language }}{% endif %}</div>
|
||||
<div>{%if movie.pub_year %}{% trans '出版时间:' %}{{ movie.pub_year }}{% trans '年' %}{% if movie.pub_month %}{{ movie.pub_month }}{% trans '月' %}{% endif %}{% endif %}</div>
|
||||
</div>
|
||||
<div class="entity-detail__fields">
|
||||
|
||||
<div>{% if movie.binding %}{% trans '装帧:' %}{{ movie.binding }}{% endif %}</div>
|
||||
<div>{% if movie.price %}{% trans '定价:' %}{{ movie.price }}{% endif %}</div>
|
||||
<div>{% if movie.pages %}{% trans '页数:' %}{{ movie.pages }}{% endif %}</div>
|
||||
{% if movie.other_info %}
|
||||
{% for k, v in movie.other_info.items %}
|
||||
<div>
|
||||
{{k}}:{{v}}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if movie.last_editor %}
|
||||
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' movie.last_editor.id %}">{{ movie.last_editor | default:"" }}</a></div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<a href="{% url 'movies:update' movie.id %}">{% trans '编辑这部电影/剧集' %}</a>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'movies:delete' movie.id %}"> / {% trans '删除' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tag-collection">
|
||||
|
||||
{% for tag_dict in movie_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>
|
||||
<div class="entity-desc" id="description">
|
||||
<h5 class="entity-desc__title">{% trans '简介' %}</h5>
|
||||
{% if movie.brief %}
|
||||
|
||||
<p class="entity-desc__content">{{ movie.brief | linebreaksbr }}</p>
|
||||
<div class="entity-desc__unfold-button entity-desc__unfold-button--hidden">
|
||||
<a href="javascript:void(0);">展开全部</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div>{% trans '暂无简介' %}</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if movie.contents %}
|
||||
<div class="entity-desc" id="contents">
|
||||
<h5 class="entity-desc__title">{% trans '目录' %}</h5>
|
||||
<p class="entity-desc__content">{{ movie.contents | 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 'movies:retrieve_mark_list' movie.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"><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 'movies:retrieve_review_list' movie.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"><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 'movies:retrieve_review' others_review.id %}">{{ others_review.title }}</a></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"><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 'movies: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"><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 'movies:update_review' review.id %}">{% trans '编辑' %}</a>
|
||||
<a href="{% url 'movies:delete_review' review.id %}">{% trans '删除' %}</a>
|
||||
</span>
|
||||
|
||||
<div class="review-panel__time">{{ review.edited_time }}</div>
|
||||
|
||||
<a href="{% url 'movies: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 'movies:create_review' movie.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 'movies:create_update_mark' %}" method="post">
|
||||
{{ mark_form.media }}
|
||||
{% csrf_token %}
|
||||
{{ mark_form.id }}
|
||||
{{ mark_form.movie }}
|
||||
{% 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>
|
277
movies/templates/movies/list.html
Normal file
277
movies/templates/movies/list.html
Normal file
|
@ -0,0 +1,277 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load admin_url %}
|
||||
{% load mastodon %}
|
||||
{% load oauth_token %}
|
||||
{% load truncate %}
|
||||
{% load highlight %}
|
||||
<!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://cdn.staticfile.org/jquery/3.5.0/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.css' %}">
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_browse.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.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 'movies:retrieve' mark.movie.id %}">
|
||||
<img src="{{ mark.movie.cover.url }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
</div>
|
||||
<div class="entity-list__entity-text">
|
||||
<div class="entity-list__entity-title">
|
||||
<a href="{% url 'movies:retrieve' mark.movie.id %}" class="entity-list__entity-link">
|
||||
{{ mark.movie.title }}
|
||||
</a>
|
||||
</div>
|
||||
{% comment %}
|
||||
<!-- {% if mark.movie.rating %}
|
||||
<div class="rating-star entity-list__rating-star" data-rating-score="{{ mark.movie.rating | floatformat:"0" }}"></div>
|
||||
<span class="entity-list__rating-score rating-score">
|
||||
{{ mark.movie.rating }}
|
||||
</span>
|
||||
{% else %}
|
||||
<div class="entity-list__rating entity-list__rating--empty"> {% trans '暂无评分' %}</div>
|
||||
{% endif %} -->
|
||||
{% endcomment %}
|
||||
<span class="entity-list__entity-info entity-list__entity-info--full-length">
|
||||
{% if mark.movie.pub_year %}
|
||||
{{ mark.movie.pub_year }}{% trans '年' %} /
|
||||
{% if mark.movie.pub_month %}
|
||||
{{ mark.movie.pub_month }}{% trans '月' %} /
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if mark.movie.author %}
|
||||
{% trans '作者' %}
|
||||
{% for author in mark.movie.author %}
|
||||
{{ author }}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}/
|
||||
{% endif %}
|
||||
|
||||
{% if mark.movie.translator %}
|
||||
{% trans '译者' %}
|
||||
{% for translator in mark.movie.translator %}
|
||||
{{ translator }}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}/
|
||||
{% endif %}
|
||||
|
||||
{% if mark.movie.orig_title %}
|
||||
{% trans '原名' %}
|
||||
{{ mark.movie.orig_title }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<p class="entity-list__entity-brief">
|
||||
{{ mark.movie.brief | truncate:170 }}
|
||||
</p>
|
||||
<div class="tag-collection">
|
||||
{% for tag_dict in mark.movie.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"><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">«</a>
|
||||
<a href="?page={{ marks.previous_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">‹</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">›</a>
|
||||
<a href="?page={{ marks.pagination.last_page }}" class="pagination__nav-link">»</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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
<div id="userPageURL" hidden="true">{% url 'users:home' 0 %}?is_mastodon_id=true</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>
|
145
movies/templates/movies/mark_list.html
Normal file
145
movies/templates/movies/mark_list.html
Normal file
|
@ -0,0 +1,145 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load admin_url %}
|
||||
{% load mastodon %}
|
||||
{% load oauth_token %}
|
||||
{% load truncate %}
|
||||
{% load highlight %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% trans 'Nicedb - ' %}{{ movie.title }}{% trans '的标记' %}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.5.0/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.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_browse.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.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 'movies:retrieve' movie.id %}">{{ movie.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"><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">«</a>
|
||||
<a href="?page={{ marks.previous_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">‹</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">›</a>
|
||||
<a href="?page={{ marks.pagination.last_page }}"
|
||||
class="pagination__nav-link">»</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 'movies:retrieve' movie.id %}"><img src="{{ movie.cover.url }}" alt="" class="entity-card__img"></a>
|
||||
</div>
|
||||
<div class="entity-card__info-wrapper">
|
||||
<h5 class="entity-card__title"><a href="{% url 'movies:retrieve' movie.id %}">{{ movie.title }}</a></h5>
|
||||
|
||||
{% if movie.isbn %}
|
||||
<div>ISBN: {{ movie.isbn }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>{% if movie.pub_house %}{% trans '出版社:' %}{{ movie.pub_house }}{% endif %}</div>
|
||||
{% if movie.rating %}
|
||||
{% trans '评分: ' %}<span class="entity-card__rating-star rating-star" data-rating-score="{{ movie.rating | floatformat:"0" }}"></span>
|
||||
<span class="entity-card__rating-score rating-score">{{ movie.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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
127
movies/templates/movies/review_detail.html
Normal file
127
movies/templates/movies/review_detail.html
Normal file
|
@ -0,0 +1,127 @@
|
|||
{% 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">
|
||||
<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://cdn.staticfile.org/jquery/3.5.0/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 'lib/css/milligram.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_browse.css' %}"> -->
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.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"><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 'movies:update_review' review.id %}">{% trans '编辑' %}</a>
|
||||
<a class="review-head__action-link" href="{% url 'movies: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 'movies:retrieve' movie.id %}"><img src="{{ movie.cover.url }}" alt=""
|
||||
class="entity-card__img"></a>
|
||||
</div>
|
||||
<div class="entity-card__info-wrapper">
|
||||
<h5 class="entity-card__title"><a href="{% url 'movies:retrieve' movie.id %}">{{ movie.title }}</a></h5>
|
||||
|
||||
{% if movie.isbn %}
|
||||
<div>ISBN: {{ movie.isbn }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>{% if movie.pub_house %}{% trans '出版社:' %}{{ movie.pub_house }}{% endif %}</div>
|
||||
{% if movie.rating %}
|
||||
{% trans '评分: ' %}<span class="entity-card__rating-star rating-star"
|
||||
data-rating-score="{{ movie.rating | floatformat:"0" }}"></span>
|
||||
<span class="entity-card__rating-score rating-score">{{ movie.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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
$(".markdownx textarea").hide();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
132
movies/templates/movies/review_list.html
Normal file
132
movies/templates/movies/review_list.html
Normal file
|
@ -0,0 +1,132 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load admin_url %}
|
||||
{% load mastodon %}
|
||||
{% load oauth_token %}
|
||||
{% load truncate %}
|
||||
{% load highlight %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% trans 'Nicedb - ' %}{{ movie.title }}{% trans '的评论' %}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.5.0/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.css' %}">
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_browse.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.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 'movies:retrieve' movie.id %}">{{ movie.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"><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 'movies:retrieve_review' review.id %}" class="entity-reviews__review-title"><a href="{% url 'movies: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">«</a>
|
||||
<a href="?page={{ reviews.previous_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">‹</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">›</a>
|
||||
<a href="?page={{ reviews.pagination.last_page }}" class="pagination__nav-link">»</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 'movies:retrieve' movie.id %}"><img src="{{ movie.cover.url }}" alt=""
|
||||
class="entity-card__img"></a>
|
||||
</div>
|
||||
<div class="entity-card__info-wrapper">
|
||||
<h5 class="entity-card__title"><a href="{% url 'movies:retrieve' movie.id %}">{{ movie.title }}</a></h5>
|
||||
|
||||
{% if movie.isbn %}
|
||||
<div>ISBN: {{ movie.isbn }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div>{% if movie.pub_house %}{% trans '出版社:' %}{{ movie.pub_house }}{% endif %}</div>
|
||||
{% if movie.rating %}
|
||||
{% trans '评分: ' %}<span class="entity-card__rating-star rating-star"
|
||||
data-rating-score="{{ movie.rating | floatformat:"0" }}"></span>
|
||||
<span class="entity-card__rating-score rating-score">{{ movie.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 %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
88
movies/templates/movies/scrape.html
Normal file
88
movies/templates/movies/scrape.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
{% 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://cdn.staticfile.org/jquery/3.5.0/jquery.min.js"></script>
|
||||
<script src="{% static 'js/scrape.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.css' %}">
|
||||
<!-- <link rel="stylesheet" href="{% static 'css/boofilsic_browse.css' %}"> -->
|
||||
<!-- <link rel="stylesheet" href="{% static 'lib/css/milligram.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 allow-popups allow-forms" 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">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
</form>
|
||||
<a href="{% url 'movies:scrape' %}" 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 'movies:click_to_scrape' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="url" required placeholder="https://movie.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>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
22
movies/urls.py
Normal file
22
movies/urls.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
|
||||
app_name = 'movies'
|
||||
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:movie_id>/mark/list/', retrieve_mark_list, name='retrieve_mark_list'),
|
||||
path('mark/delete/<int:id>/', delete_mark, name='delete_mark'),
|
||||
path('<int:movie_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:movie_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'),
|
||||
]
|
579
movies/views.py
Normal file
579
movies/views.py
Normal file
|
@ -0,0 +1,579 @@
|
|||
import logging
|
||||
from django.shortcuts import render, get_object_or_404, redirect, reverse
|
||||
from django.contrib.auth.decorators import login_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 django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from common.mastodon import mastodon_request_included
|
||||
from common.mastodon.api import check_visibility, post_toot, TootVisibilityEnum
|
||||
from common.mastodon.utils import rating_to_emoji
|
||||
from common.utils import PageLinksGenerator
|
||||
from common.views import PAGE_LINK_NUMBER
|
||||
from .models import *
|
||||
from .forms import *
|
||||
from .forms import MovieMarkStatusTranslator
|
||||
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 = MovieForm()
|
||||
return render(
|
||||
request,
|
||||
'movies/create_update.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('添加书籍'),
|
||||
'submit_url': reverse("movies:create")
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
if request.user.is_authenticated:
|
||||
# only local user can alter public data
|
||||
form = MovieForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
form.instance.last_editor = request.user
|
||||
form.save()
|
||||
return redirect(reverse("movies:retrieve", args=[form.instance.id]))
|
||||
else:
|
||||
return render(
|
||||
request,
|
||||
'movies/create_update.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('添加书籍'),
|
||||
'submit_url': reverse("movies:create")
|
||||
}
|
||||
)
|
||||
else:
|
||||
return redirect(reverse("users:login"))
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def update(request, id):
|
||||
if request.method == 'GET':
|
||||
movie = get_object_or_404(Movie, pk=id)
|
||||
form = MovieForm(instance=movie)
|
||||
return render(
|
||||
request,
|
||||
'movies/create_update.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('修改书籍'),
|
||||
'submit_url': reverse("movies:update", args=[movie.id])
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
movie = get_object_or_404(Movie, pk=id)
|
||||
form = MovieForm(request.POST, request.FILES, instance=movie)
|
||||
if form.is_valid():
|
||||
form.instance.last_editor = request.user
|
||||
form.instance.edited_time = timezone.now()
|
||||
form.save()
|
||||
else:
|
||||
return render(
|
||||
request,
|
||||
'movies/create_update.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('修改书籍'),
|
||||
'submit_url': reverse("movies:update", args=[movie.id])
|
||||
}
|
||||
)
|
||||
return redirect(reverse("movies:retrieve", args=[form.instance.id]))
|
||||
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
# @login_required
|
||||
def retrieve(request, id):
|
||||
if request.method == 'GET':
|
||||
movie = get_object_or_404(Movie, pk=id)
|
||||
mark = None
|
||||
mark_tags = None
|
||||
review = None
|
||||
|
||||
# retreive tags
|
||||
movie_tag_list = movie.movie_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 = MovieMark.objects.get(owner=request.user, movie=movie)
|
||||
except ObjectDoesNotExist:
|
||||
mark = None
|
||||
if mark:
|
||||
mark_tags = mark.mark_tags.all()
|
||||
mark.get_status_display = MovieMarkStatusTranslator(mark.status)
|
||||
mark_form = MovieMarkForm(instance=mark, initial={
|
||||
'tags': mark_tags
|
||||
})
|
||||
else:
|
||||
mark_form = MovieMarkForm(initial={
|
||||
'movie': movie,
|
||||
'tags': mark_tags
|
||||
})
|
||||
|
||||
# retrieve user review
|
||||
try:
|
||||
if request.user.is_authenticated:
|
||||
review = MovieReview.objects.get(owner=request.user, movie=movie)
|
||||
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 = MovieMark.get_available(
|
||||
movie, request.user, request.session['oauth_token'])
|
||||
review_list = MovieReview.get_available(
|
||||
movie, 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 = MovieMarkStatusTranslator(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,
|
||||
'movies/detail.html',
|
||||
{
|
||||
'movie': movie,
|
||||
'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,
|
||||
'movie_tag_list': movie_tag_list,
|
||||
'mark_tags': mark_tags,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
logger.warning('non-GET method at /movie/<id>')
|
||||
|
||||
|
||||
@login_required
|
||||
def delete(request, id):
|
||||
if request.method == 'GET':
|
||||
movie = get_object_or_404(Movie, pk=id)
|
||||
return render(
|
||||
request,
|
||||
'movies/delete.html',
|
||||
{
|
||||
'movie': movie,
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
if request.user.is_staff:
|
||||
# only staff has right to delete
|
||||
movie = get_object_or_404(Movie, pk=id)
|
||||
movie.delete()
|
||||
return redirect(reverse("common:search"))
|
||||
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 movie 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(MovieMark, pk=pk)
|
||||
old_rating = mark.rating
|
||||
old_tags = mark.mark_tags.all()
|
||||
# update
|
||||
form = MovieMarkForm(request.POST, instance=mark)
|
||||
else:
|
||||
# create
|
||||
form = MovieMarkForm(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()
|
||||
movie = form.instance.movie
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# update movie rating
|
||||
movie.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']:
|
||||
MovieTag.objects.create(
|
||||
content=tag,
|
||||
movie=movie,
|
||||
mark=form.instance
|
||||
)
|
||||
except IntegrityError as e:
|
||||
return HttpResponseServerError("integrity error")
|
||||
logger.error(e.__str__())
|
||||
|
||||
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("movies:retrieve",
|
||||
args=[movie.id])
|
||||
words = MovieMarkStatusTranslator(int(form.cleaned_data['status'])) +\
|
||||
f"《{movie.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(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("movies:retrieve", args=[form.instance.movie.id]))
|
||||
else:
|
||||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_mark_list(request, movie_id):
|
||||
if request.method == 'GET':
|
||||
movie = get_object_or_404(Movie, pk=movie_id)
|
||||
queryset = MovieMark.get_available(
|
||||
movie, 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 = MovieMarkStatusTranslator(m.status)
|
||||
return render(
|
||||
request,
|
||||
'movies/mark_list.html',
|
||||
{
|
||||
'marks': marks,
|
||||
'movie': movie,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def delete_mark(request, id):
|
||||
if request.method == 'POST':
|
||||
mark = get_object_or_404(MovieMark, pk=id)
|
||||
movie_id = mark.movie.id
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# update movie rating
|
||||
mark.movie.update_rating(mark.rating, None)
|
||||
mark.delete()
|
||||
except IntegrityError as e:
|
||||
return HttpResponseServerError()
|
||||
return redirect(reverse("movies:retrieve", args=[movie_id]))
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def create_review(request, movie_id):
|
||||
if request.method == 'GET':
|
||||
form = MovieReviewForm(initial={'movie': movie_id})
|
||||
movie = get_object_or_404(Movie, pk=movie_id)
|
||||
return render(
|
||||
request,
|
||||
'movies/create_update_review.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _("添加评论"),
|
||||
'movie': movie,
|
||||
'submit_url': reverse("movies:create_review", args=[movie_id]),
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
form = MovieReviewForm(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("movies:retrieve_review",
|
||||
args=[form.instance.id])
|
||||
words = "发布了关于" + f"《{form.instance.movie.title}》" + "的评论"
|
||||
# tags = MASTODON_TAGS % {'category': '书', 'type': '评论'}
|
||||
tags = ''
|
||||
content = words + '\n' + url + \
|
||||
'\n' + form.cleaned_data['title'] + '\n' + tags
|
||||
response = post_toot(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("movies:retrieve_review", args=[form.instance.id]))
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def update_review(request, id):
|
||||
# owner check
|
||||
# edited time
|
||||
if request.method == 'GET':
|
||||
review = get_object_or_404(MovieReview, pk=id)
|
||||
if request.user != review.owner:
|
||||
return HttpResponseBadRequest()
|
||||
form = MovieReviewForm(instance=review)
|
||||
movie = review.movie
|
||||
return render(
|
||||
request,
|
||||
'movies/create_update_review.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _("编辑评论"),
|
||||
'movie': movie,
|
||||
'submit_url': reverse("movies:update_review", args=[review.id]),
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
review = get_object_or_404(MovieReview, pk=id)
|
||||
if request.user != review.owner:
|
||||
return HttpResponseBadRequest()
|
||||
form = MovieReviewForm(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("movies:retrieve_review",
|
||||
args=[form.instance.id])
|
||||
words = "发布了关于" + f"《{form.instance.movie.title}》" + "的评论"
|
||||
# tags = MASTODON_TAGS % {'category': '书', 'type': '评论'}
|
||||
tags = ''
|
||||
content = words + '\n' + url + \
|
||||
'\n' + form.cleaned_data['title'] + '\n' + tags
|
||||
response = post_toot(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("movies: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(MovieReview, pk=id)
|
||||
if request.user != review.owner:
|
||||
return HttpResponseBadRequest()
|
||||
review_form = MovieReviewForm(instance=review)
|
||||
return render(
|
||||
request,
|
||||
'movies/delete_review.html',
|
||||
{
|
||||
'form': review_form,
|
||||
'review': review,
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
review = get_object_or_404(MovieReview, pk=id)
|
||||
if request.user != review.owner:
|
||||
return HttpResponseBadRequest()
|
||||
movie_id = review.movie.id
|
||||
review.delete()
|
||||
return redirect(reverse("movies:retrieve", args=[movie_id]))
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_review(request, id):
|
||||
if request.method == 'GET':
|
||||
review = get_object_or_404(MovieReview, pk=id)
|
||||
if not check_visibility(review, request.session['oauth_token'], request.user):
|
||||
msg = _("你没有访问这个页面的权限😥")
|
||||
return render(
|
||||
request,
|
||||
'common/error.html',
|
||||
{
|
||||
'msg': msg,
|
||||
}
|
||||
)
|
||||
review_form = MovieReviewForm(instance=review)
|
||||
movie = review.movie
|
||||
try:
|
||||
mark = MovieMark.objects.get(owner=review.owner, movie=movie)
|
||||
mark.get_status_display = MovieMarkStatusTranslator(mark.status)
|
||||
except ObjectDoesNotExist:
|
||||
mark = None
|
||||
return render(
|
||||
request,
|
||||
'movies/review_detail.html',
|
||||
{
|
||||
'form': review_form,
|
||||
'review': review,
|
||||
'movie': movie,
|
||||
'mark': mark,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_review_list(request, movie_id):
|
||||
if request.method == 'GET':
|
||||
movie = get_object_or_404(Movie, pk=movie_id)
|
||||
queryset = MovieReview.get_available(
|
||||
movie, 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,
|
||||
'movies/review_list.html',
|
||||
{
|
||||
'reviews': reviews,
|
||||
'movie': movie,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def scrape(request):
|
||||
if request.method == 'GET':
|
||||
keywords = request.GET.get('q')
|
||||
form = MovieForm()
|
||||
return render(
|
||||
request,
|
||||
'movies/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:
|
||||
from common.scraper import scrape_douban_movie
|
||||
try:
|
||||
scraped_movie, raw_cover = scrape_douban_movie(url)
|
||||
except TimeoutError:
|
||||
return render(request, 'common/error.html', {'msg': _("爬取数据失败😫,请重试")})
|
||||
except ValueError:
|
||||
return render(request, 'common/error.html', {'msg': _("链接非法,爬取失败")})
|
||||
scraped_cover = {
|
||||
'cover': SimpleUploadedFile('temp.jpg', raw_cover)}
|
||||
form = MovieForm(scraped_movie, scraped_cover)
|
||||
if form.is_valid():
|
||||
form.instance.last_editor = request.user
|
||||
form.save()
|
||||
return redirect(reverse('movies:retrieve', args=[form.instance.id]))
|
||||
else:
|
||||
if 'isbn' in form.errors:
|
||||
msg = _("ISBN与现有图书重复")
|
||||
else:
|
||||
msg = _("爬取数据失败😫")
|
||||
return render(request, 'common/error.html', {'msg': msg})
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
Loading…
Add table
Reference in a new issue