finish complete movie app

This commit is contained in:
doubaniux 2020-10-04 16:16:50 +02:00
parent 8fd0788047
commit 3dfd97b0a9
27 changed files with 540 additions and 158 deletions

View file

@ -70,6 +70,10 @@ class Book(Resource):
def get_tags_manager(self):
return self.book_tags
@property
def verbose_category_name(self):
return _("书籍")
class BookMark(Mark):
# maybe this is the better solution, for it has less complex index
@ -95,4 +99,4 @@ class BookTag(Tag):
class Meta:
constraints = [
models.UniqueConstraint(fields=['content', 'mark'], name="unique_bookmark_tag")
]
]

View file

@ -25,6 +25,7 @@
<section id="content" class="container">
<div class="grid">
<div class="single-section-wrapper" id="main">
<a href="{% url 'books:scrape' %}" class="single-section-wrapper__link single-section-wrapper__link--secondary">{% trans '>>> 试试一键剽取~ <<<' %}</a>
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}

View file

@ -131,6 +131,12 @@ class TagField(forms.CharField):
class MultiSelect(forms.SelectMultiple):
template_name = 'widgets/multi_select.html'
class Media:
css = {
'all': ('lib/css/multiple-select.min.css',)
}
js = ('lib/js/multiple-select.min.js',)
class HstoreInput(forms.Widget):
template_name = 'widgets/hstore.html'
@ -154,6 +160,9 @@ class HstoreField(forms.CharField):
def to_python(self, value):
if not value:
return None
# already in python types
if isinstance(value, list):
return value
pairs = eval(value)
if len(pairs) == 1:
pairs = (pairs,)

View file

@ -81,21 +81,37 @@ class Resource(models.Model):
Since relation between tag and resource is foreign key, and related name has to be unique,
this method works like interface.
"""
raise NotImplementedError
raise NotImplementedError("Subclass should implement this method.")
def get_marks_manager(self):
"""
Normally this won't be used.
There is no ocassion where visitor can simply view all the marks.
"""
raise NotImplementedError
raise NotImplementedError("Subclass should implement this method.")
def get_revies_manager(self):
"""
Normally this won't be used.
There is no ocassion where visitor can simply view all the reviews.
"""
raise NotImplementedError
raise NotImplementedError("Subclass should implement this method.")
@classmethod
def get_category_mapping_dict(cls):
category_mapping_dict = {}
for subclass in cls.__subclasses__():
category_mapping_dict[subclass.__name__.lower()] = subclass
return category_mapping_dict
@property
def category_name(self):
return self.__class__.__name__
@property
def verbose_category_name(self):
raise NotImplementedError("Subclass should implement this.")
class UserOwnedEntity(models.Model):
is_private = models.BooleanField()

View file

@ -510,19 +510,39 @@ select::placeholder {
.navbar .navbar__search-box {
margin: 0 12% 0 15px;
display: inline-block;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
}
.navbar .navbar__search-box > input[type="search"] {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
margin: 0;
height: 32px;
background-color: white !important;
width: 100%;
}
.navbar .navbar__search-box .navbar__search-dropdown {
margin: 0;
margin-left: -1px;
padding: 0;
padding-left: 10px;
color: #606c76;
-webkit-appearance: auto;
-moz-appearance: auto;
appearance: auto;
background-color: white;
height: 32px;
width: 80px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.navbar .navbar__dropdown-btn {
display: none;
padding: 0;
@ -578,6 +598,11 @@ select::placeholder {
height: 26px;
padding: 4px 6px;
}
.navbar .navbar__search-box .navbar__search-dropdown {
height: 26px;
width: 80px;
padding-left: 5px;
}
}
@media (max-width: 991.98px) {
@ -1128,6 +1153,13 @@ select::placeholder {
display: block;
}
.entity-list .entity-list__entity-category {
color: #bbb;
margin-left: 5px;
position: relative;
top: -1px;
}
.entity-list .entity-list__entity-info {
max-width: 73%;
white-space: nowrap;
@ -1635,6 +1667,13 @@ select::placeholder {
margin-bottom: 8px;
}
.add-entity-entries .add-entity-entries__button {
line-height: unset;
height: unset;
padding: 4px 15px;
margin: 0 5px;
}
.action-panel {
margin-bottom: 20px;
}
@ -1866,6 +1905,7 @@ select::placeholder {
}
.add-entity-entries .add-entity-entries__button {
width: 100%;
margin: 5px 0 5px 0;
}
.aside-section-wrapper:first-child {
margin-right: 0 !important;
@ -1987,6 +2027,16 @@ select::placeholder {
overflow: auto;
}
.single-section-wrapper .single-section-wrapper__link--secondary {
display: inline-block;
color: #ccc;
margin-bottom: 20px;
}
.single-section-wrapper .single-section-wrapper__link--secondary:hover {
color: #00a1cc;
}
.entity-form, .review-form {
overflow: auto;
}

View file

@ -0,0 +1,10 @@
/**
* multiple-select - Multiple select is a jQuery plugin to select multiple elements with checkboxes :).
*
* @version v1.5.2
* @homepage http://multiple-select.wenzhixin.net.cn
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT
*/
@charset "UTF-8";.ms-offscreen{clip:rect(0 0 0 0)!important;width:1px!important;height:1px!important;border:0!important;margin:0!important;padding:0!important;overflow:hidden!important;position:absolute!important;outline:0!important;left:auto!important;top:auto!important}.ms-parent{display:inline-block;position:relative;vertical-align:middle}.ms-choice{display:block;width:100%;height:26px;padding:0;overflow:hidden;cursor:pointer;border:1px solid #aaa;text-align:left;white-space:nowrap;line-height:26px;color:#444;text-decoration:none;border-radius:4px;background-color:#fff}.ms-choice.disabled{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default}.ms-choice>span{position:absolute;top:0;left:0;right:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;padding-left:8px}.ms-choice>span.placeholder{color:#999}.ms-choice>div.icon-close{position:absolute;top:0;right:16px;height:100%;width:16px}.ms-choice>div.icon-close:before{content:'×';color:#888;font-weight:bold;position:absolute;top:50%;margin-top:-14px}.ms-choice>div.icon-close:hover:before{color:#333}.ms-choice>div.icon-caret{position:absolute;width:0;height:0;top:50%;right:8px;margin-top:-2px;border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px}.ms-choice>div.icon-caret.open{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.ms-drop{width:auto;min-width:100%;overflow:hidden;display:none;margin-top:-1px;padding:0;position:absolute;z-index:1000;background:#fff;color:#000;border:1px solid #aaa;border-radius:4px}.ms-drop.bottom{top:100%;box-shadow:0 4px 5px rgba(0,0,0,0.15)}.ms-drop.top{bottom:100%;box-shadow:0 -4px 5px rgba(0,0,0,0.15)}.ms-search{display:inline-block;margin:0;min-height:26px;padding:2px;position:relative;white-space:nowrap;width:100%;z-index:10000;box-sizing:border-box}.ms-search input{width:100%;height:auto!important;min-height:24px;padding:0 5px;margin:0;outline:0;font-family:sans-serif;border:1px solid #aaa;border-radius:5px;box-shadow:none}.ms-drop ul{overflow:auto;margin:0;padding:0}.ms-drop ul>li{list-style:none;display:list-item;background-image:none;position:static;padding:.25rem 8px}.ms-drop ul>li .disabled{font-weight:normal!important;opacity:.35;filter:Alpha(Opacity=35);cursor:default}.ms-drop ul>li.multiple{display:block;float:left}.ms-drop ul>li.group{clear:both}.ms-drop ul>li.multiple label{width:100%;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ms-drop ul>li label{position:relative;padding-left:1.25rem;margin-bottom:0;font-weight:normal;display:block;white-space:nowrap;cursor:pointer}.ms-drop ul>li label.optgroup{font-weight:bold}.ms-drop ul>li.hide-radio{padding:0}.ms-drop ul>li.hide-radio:focus,.ms-drop ul>li.hide-radio:hover{background-color:#f8f9fa}.ms-drop ul>li.hide-radio.selected{color:#fff;background-color:#007bff}.ms-drop ul>li.hide-radio label{margin-bottom:0;padding:5px 8px}.ms-drop ul>li.hide-radio input{display:none}.ms-drop ul>li.option-level-1 label{padding-left:28px}.ms-drop input[type="radio"],.ms-drop input[type="checkbox"]{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.ms-drop .ms-no-results{display:none}

File diff suppressed because one or more lines are too long

View file

@ -27,6 +27,10 @@ $aside-section-padding-mobile: 24px 25px 10px 25px
margin-bottom: 8px
& &__button
line-height: unset;
height: unset;
padding: 4px 15px;
margin: 0 5px;
.action-panel
margin-bottom: 20px
@ -201,6 +205,7 @@ $aside-section-padding-mobile: 24px 25px 10px 25px
display: block !important
& &__button
width: 100%
margin: 5px 0 5px 0;
.aside-section-wrapper
&:first-child

View file

@ -47,7 +47,13 @@ $sub-section-title-margin: 8px
font-size: 1.2em
& &__entity-title
display: block
display: block
& &__entity-category
color: $color-tertiary
margin-left: 5px
position: relative;
top: -1px;
& &__entity-info
max-width: 73%

View file

@ -37,14 +37,30 @@
& &__search-box
margin: 0 12% 0 15px
display: inline-block
display: inline-flex
flex: 1
$widget-height: 32px
& > input[type="search"]
border-top-right-radius: 0
border-bottom-right-radius: 0
margin: 0
height: 32px
height: $widget-height
background-color: white !important
width: 100%
& .navbar__search-dropdown
margin: 0
margin-left: -1px
padding: 0
padding-left: 10px
color: $color-secondary
appearance: auto
background-color: white
height: $widget-height
width: 80px
border-top-left-radius: 0
border-bottom-left-radius: 0
& &__dropdown-btn
display: none
padding: 0
@ -91,9 +107,14 @@
& &__search-box
margin: 0
width: 46vw
$widget-height: 26px
& > input[type="search"]
height: 26px
height: $widget-height
padding: 4px 6px
& .navbar__search-dropdown
height: $widget-height
width: 80px
padding-left: 5px
// Medium devices (tablets, 768px and up)
@media (max-width: $medium-devices)
pass

View file

@ -8,6 +8,12 @@ $single-section-padding-mobile: 32px 28px
// & input, & select
// width: 100%
& &__link--secondary
display: inline-block
color: $color-light
margin-bottom: 20px
&:hover
color: $color-primary
.entity-form, .review-form
overflow: auto

View file

@ -1,5 +1,7 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load humanize %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
@ -41,79 +43,181 @@
{% endif %}
<ul class="entity-list__entities">
{% for item in items %}
{% for book in items %}
<li class="entity-list__entity">
<div class="entity-list__entity-img-wrapper">
<a href="{% url 'books:retrieve' book.id %}">
<img src="{{ book.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 'books:retrieve' book.id %}" class="entity-list__entity-link">
{% if request.GET.q %}
{{ book.title | highlight:request.GET.q }}
{% else %}
{{ book.title }}
{% endif %}
{% if item.category_name|lower == 'book' %}
{% with book=item %}
<li class="entity-list__entity">
<div class="entity-list__entity-img-wrapper">
<a href="{% url 'books:retrieve' book.id %}">
<img src="{{ book.cover.url }}" alt="" class="entity-list__entity-img">
</a>
</div>
{% if book.rating %}
<div class="rating-star entity-list__rating-star" data-rating-score="{{ book.rating | floatformat:"0" }}"></div>
<span class="entity-list__rating-score rating-score">{{ book.rating }}</span>
{% else %}
<div class="entity-list__rating entity-list__rating--empty"> {% trans '暂无评分' %}</div>
{% endif %}
<span class="entity-list__entity-info">
{% if book.pub_year %}
{{ book.pub_year }}{% trans '年' %}
{% if book.pub_month %}
{{book.pub_month }}{% trans '月' %} /
{% endif %}
{% endif %}
{% if book.author %}
{% trans '作者' %}
{% for author in book.author %}
{{ author }}{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if book.translator %}
{% trans '译者' %}
{% for translator in book.translator %}
{{ translator }}{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if book.orig_title %}
&nbsp;{% trans '原名' %}
{{ book.orig_title }}
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ book.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in book.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>
<div class="entity-list__entity-text">
<div class="entity-list__entity-title">
<a href="{% url 'books:retrieve' book.id %}" class="entity-list__entity-link">
{% if request.GET.q %}
{{ book.title | highlight:request.GET.q }}
{% else %}
{{ book.title }}
{% endif %}
</a>
{% if not request.GET.c or request.GET.c != 'movie' and request.GET.c != 'book'%}
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
{% endif %}
</div>
{% if book.rating %}
<div class="rating-star entity-list__rating-star" data-rating-score="{{ book.rating | floatformat:"0" }}"></div>
<span class="entity-list__rating-score rating-score">{{ book.rating }}</span>
{% else %}
<div class="entity-list__rating entity-list__rating--empty"> {% trans '暂无评分' %}</div>
{% endif %}
{% endfor %}
{% endfor %}
<span class="entity-list__entity-info">
{% if book.pub_year %}
{{ book.pub_year }}{% trans '年' %}
{% if book.pub_month %}
{{book.pub_month }}{% trans '月' %} /
{% endif %}
{% endif %}
{% if book.author %}
{% trans '作者' %}
{% for author in book.author %}
{{ author }}{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if book.translator %}
{% trans '译者' %}
{% for translator in book.translator %}
{{ translator }}{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if book.orig_title %}
&nbsp;{% trans '原名' %}
{{ book.orig_title }}
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ book.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in book.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>
</li>
</li>
{% endwith %}
{% elif item.category_name|lower == 'movie' %}
{% with movie=item %}
<li class="entity-list__entity">
<div class="entity-list__entity-img-wrapper">
<a href="{% url 'movies:retrieve' movie.id %}">
<img src="{{ 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' movie.id %}" class="entity-list__entity-link">
{% if movie.season %}
{% if request.GET.q %}
{{ movie.title | highlight:request.GET.q }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %}
{{ movie.orig_title | highlight:request.GET.q }} Season {{ movie.season }}
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %}
{{ movie.orig_title }} Season {{ movie.season }}
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
{% else %}
{% if request.GET.q %}
{{ movie.title | highlight:request.GET.q }} {{ movie.orig_title | highlight:request.GET.q }}
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {{ movie.orig_title }}
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
{% endif %}
</a>
{% if not request.GET.c or request.GET.c != 'movie' and request.GET.c != 'book'%}
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
{% endif %}
</div>
<span class="entity-list__entity-info entity-list__entity-info--full-length">
{% if movie.director %}{% trans '导演' %}
{% for director in movie.director %}
{{ director }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if movie.genre %}{% trans '类型' %}
{% for genre in movie.get_genre_display %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if movie.other_title %}{% trans '又名' %}
{% for other_title in movie.other_title %}
{{ other_title }}{% if not forloop.last %} {% endif %}
{% endfor %}
{% endif %}
</span>
<span class="entity-list__entity-info entity-list__entity-info--full-length">
{% if movie.actor %}{% trans '主演' %}
{% for actor in movie.actor %}
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>{{ actor }}</span>
{% if forloop.counter <= 5 %}
{% if not forloop.counter == 5 %} / {% endif %}
{% endif %}
{% endfor %}
{% endif %}
</span>
<p class="entity-list__entity-brief">
{{ movie.brief | truncate:170 }}
</p>
<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>
</li>
{% endwith %}
{% endif %}
{% empty %}
{% trans '无结果' %}
{% endfor %}
@ -153,19 +257,90 @@
<div class="add-entity-entries">
<div class="add-entity-entries__entry">
<div class="add-entity-entries__label">
没有想要的结果?
{% trans '没有想要的结果?' %}
</div>
<a href="{% url 'books:create' %}">
<button class="add-entity-entries__button">添加一个条目</button>
</a>
{% if request.GET.c %}
{% if request.GET.c|lower == 'book' %}
<a href="{% url 'books:create' %}">
<button class="add-entity-entries__button">{% trans '添加书' %}</button>
</a>
{% elif request.GET.c|lower == 'movie' %}
<a href="{% url 'movies:create' %}">
<button class="add-entity-entries__button">{% trans '添加电影/剧集' %}</button>
</a>
{% else %}
<a href="{% url 'books:create' %}">
<button class="add-entity-entries__button">{% trans '添加书' %}</button>
</a>
<a href="{% url 'movies:create' %}">
<button class="add-entity-entries__button">{% trans '添加电影/剧集' %}</button>
</a>
{% endif %}
{% else %}
<a href="{% url 'books:create' %}">
<button class="add-entity-entries__button">{% trans '添加书' %}</button>
</a>
<a href="{% url 'movies:create' %}">
<button class="add-entity-entries__button">{% trans '添加电影/剧集' %}</button>
</a>
{% endif %}
</div>
<div class="add-entity-entries__entry">
{% if request.GET.c %}
{% if request.GET.c|lower == 'book' %}
<div class="add-entity-entries__label">
{% trans '或者(≖ ◡ ≖)✧' %}
</div>
<a href="{% url 'books:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '从表瓣剽取数据' %}</button>
</a>
{% elif request.GET.c|lower == 'movie' %}
<div class="add-entity-entries__label">
{% trans '或者(≖ ◡ ≖)✧' %}
</div>
<a href="{% url 'movies:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '从表瓣剽取数据' %}</button>
</a>
{% else %}
<div class="add-entity-entries__label">
{% trans '或从表瓣剽取' %}
</div>
<a href="{% url 'books:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '书' %}</button>
</a>
<a href="{% url 'movies:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '电影/剧集' %}</button>
</a>
{% endif %}
{% else %}
<div class="add-entity-entries__label">
或者(≖ ◡ ≖)✧
{% trans '或从表瓣剽取' %}
</div>
<a href="{% url 'books:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">从表瓣剽取数据</button>
<button class="add-entity-entries__button">{% trans '书' %}</button>
</a>
<a href="{% url 'movies:scrape' %}{% if request.GET.q %}?q={{ request.GET.q }}{% endif %}">
<button class="add-entity-entries__button">{% trans '电影/剧集' %}</button>
</a>
{% endif %}
</div>
</div>

View file

@ -13,6 +13,11 @@
<!-- <input type="search" class="" name="q" id="searchInput" required="true" value="{% for v in request.GET.values %}{{ v }}{% endfor %}" -->
<input type="search" class="" name="q" id="searchInput" required="true" value="{% if request.GET.q %}{{ request.GET.q }}{% endif %}"
placeholder="搜索书影音">
<select class="navbar__search-dropdown" id="searchCategory">
<option value="all" {% if request.GET.c and request.GET.c != 'movie' and request.GET.c != 'book' or not request.GET.c %}selected{% endif %}>{% trans '任意' %}</option>
<option value="book" {% if request.GET.c and request.GET.c == 'book' %}selected{% endif %}>{% trans '书籍' %}</option>
<option value="movie" {% if request.GET.c and request.GET.c == 'movie' %}selected{% endif %}>{% trans '电影' %}</option>
</select>
</form>
<button class="navbar__dropdown-btn">• • •</button>
<ul class="navbar__link-list">
@ -38,8 +43,9 @@
$("#searchInput").on('keyup', function (e) {
if (e.keyCode === 13) {
let q = $(this).val();
let c = $("#searchCategory").val();
if (q) {
location.href = "{% url 'common:search' %}" + "?q=" + q;
location.href = "{% url 'common:search' %}" + "?c=" + c + "&q=" + q;
}
}
});

View file

@ -4,8 +4,6 @@
{% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
</optgroup>{% endif %}{% endfor %}
</select>
<link rel="stylesheet" href="https://unpkg.com/multiple-select@1.5.2/dist/multiple-select.min.css">
<script src="https://unpkg.com/multiple-select@1.5.2/dist/multiple-select.min.js"></script>
<script>
$('select[name="{{ widget.name }}"]').multipleSelect({
selectAll: false,

View file

@ -1,6 +1,9 @@
import operator
from difflib import SequenceMatcher
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from books.models import Book
from movies.models import Movie
from common.models import MarkStatusEnum
from common.utils import PageLinksGenerator
from users.models import Report, User
@ -83,28 +86,93 @@ def home(request):
@login_required
def search(request):
if request.method == 'GET':
# in the future when more modules are added...
# category = request.GET.get("category")
q = Q()
query_args = []
# keywords
keywords = request.GET.get("q", default='').strip()
# category, book/movie/record etc
category = request.GET.get("c", default='').strip().lower()
for keyword in [keywords]:
def book_param_handler():
q = Q()
query_args = []
# keywords
keywords = request.GET.get("q", default='').strip()
q = q | Q(title__icontains=keyword)
q = q | Q(subtitle__icontains=keyword)
q = q | Q(orig_title__icontains=keyword)
for keyword in [keywords]:
q = q | Q(title__icontains=keyword)
q = q | Q(subtitle__icontains=keyword)
q = q | Q(orig_title__icontains=keyword)
# tag
tag = request.GET.get("tag", default='')
if tag:
q = q & Q(book_tags__content__iexact=tag)
# tag
tag = request.GET.get("tag", default='')
if tag:
q = q & Q(book_tags__content__iexact=tag)
query_args.append(q)
queryset = Book.objects.filter(*query_args).distinct()
query_args.append(q)
queryset = Book.objects.filter(*query_args).distinct()
def calculate_similarity(book):
similarity, n = 0, 0
for keyword in keywords:
similarity += 1/2 * SequenceMatcher(None, keyword, book.title).quick_ratio()
+ 1/3 * SequenceMatcher(None, keyword, book.orig_title).quick_ratio()
+ 1/6 * SequenceMatcher(None, keyword, book.subtitle).quick_ratio()
n += 1
book.similarity = similarity / n
return book.similarity
ordered_queryset = sorted(queryset, key=calculate_similarity, reverse=True)
return ordered_queryset
def movie_param_handler():
q = Q()
query_args = []
# keywords
keywords = request.GET.get("q", default='').strip()
for keyword in [keywords]:
q = q | Q(title__icontains=keyword)
q = q | Q(other_title__icontains=keyword)
q = q | Q(orig_title__icontains=keyword)
# tag
tag = request.GET.get("tag", default='')
if tag:
q = q & Q(movie_tags__content__iexact=tag)
query_args.append(q)
queryset = Movie.objects.filter(*query_args).distinct()
def calculate_similarity(movie):
similarity, n = 0, 0
for keyword in keywords:
similarity += 1/2 * SequenceMatcher(None, keyword, movie.title).quick_ratio()
+ 1/4 * SequenceMatcher(None, keyword, movie.orig_title).quick_ratio()
+ 1/4 * SequenceMatcher(None, keyword, movie.other_title).quick_ratio()
n += 1
movie.similarity = similarity / n
return movie.similarity
ordered_queryset = sorted(queryset, key=calculate_similarity, reverse=True)
return ordered_queryset
def all_param_handler():
book_queryset = book_param_handler()
movie_queryset = movie_param_handler()
ordered_queryset = sorted(
book_queryset + movie_queryset,
key=operator.attrgetter('similarity'),
reverse=True
)
return ordered_queryset
param_handler = {
'book': book_param_handler,
'movie': movie_param_handler,
'all': all_param_handler,
'': all_param_handler
}
try:
queryset = param_handler[category]()
except KeyError as e:
queryset = param_handler['all']()
paginator = Paginator(queryset, ITEMS_PER_PAGE)
page_number = request.GET.get('page', default=1)
items = paginator.get_page(page_number)

View file

@ -23,6 +23,7 @@ class MovieForm(forms.ModelForm):
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
genre = forms.MultipleChoiceField(
required=False,
choices=MovieGenreEnum.choices,
widget= MultiSelect,
label=_("类型")

View file

@ -177,6 +177,13 @@ class Movie(Resource):
translated_genre.append(MovieGenreTranslator[g])
return translated_genre
@property
def verbose_category_name(self):
if self.is_series:
return _("剧集")
else:
return _("电影")
class MovieMark(Mark):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='movie_marks', null=True)

View file

@ -25,6 +25,8 @@
<section id="content" class="container">
<div class="grid">
<div class="single-section-wrapper" id="main">
<a href="{% url 'movies:scrape' %}"
class="single-section-wrapper__link single-section-wrapper__link--secondary">{% trans '>>> 试试一键剽取~ <<<' %}</a>
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.media }}
@ -32,7 +34,7 @@
{% if field.id_for_label == 'id_is_series' %}
<label for="{{ field.id_for_label }}"
style="display: inline-block; position: relative; left: -4px;">{{ field.label }}</label>
style="display: inline-block; position: relative;">{{ field.label }}</label>
{{ field }}
{% else %}
{% if field.id_for_label != 'id_id' %}
@ -61,11 +63,29 @@
{% endcomment %}
<script>
// mark required
$("#content input[required]").each(function () {
$(this).prev().prepend("*");
})
// `is_seires` checkbox
$("#id_season, label[for='id_season']").hide();
$("#id_episodes, label[for='id_episodes']").hide();
$("#id_single_episode_length, label[for='id_single_episode_length']").hide();
$('#id_is_series').change(
function () {
if ($(this).is(':checked')) {
$("#id_season, label[for='id_season']").show();
$("#id_episodes, label[for='id_episodes']").show();
$("#id_single_episode_length, label[for='id_single_episode_length']").show();
} else {
$("#id_season, label[for='id_season']").hide();
$("#id_episodes, label[for='id_episodes']").hide();
$("#id_single_episode_length, label[for='id_single_episode_length']").hide();
}
}
);
</script>
</body>

View file

@ -42,14 +42,12 @@
{% if movie.season %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %} {{ movie.orig_title }} Season
{{ movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {{ movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
</a></h5>
<div>{% if movie.director %}{% trans '导演:' %}

View file

@ -44,14 +44,10 @@
{% if movie.season %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %} {{ movie.orig_title }} Season
{{ movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {{ movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
</h5>
</a>

View file

@ -63,12 +63,12 @@
{% if movie.season %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %} {{ movie.orig_title }} Season {{ movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
{% if movie.year %}({{ movie.year }}){% endif %}
</span>
{% else %}
{{ movie.title }} {{ movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
{% if movie.year %}({{ movie.year }}){% endif %}
</span>
{% endif %}
</h5>
@ -331,9 +331,9 @@
<div class="action-panel__label">{% trans '标记这部电影' %}</div>
{% endif %}
<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>
<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 %}

View file

@ -112,14 +112,10 @@
{% if movie.season %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %} {{ movie.orig_title }} Season
{{ movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {{ movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
</a></h5>

View file

@ -91,14 +91,10 @@
{% if movie.season %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %} {{ movie.orig_title }} Season
{{ movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {{ movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
</a></h5>

View file

@ -98,14 +98,10 @@
{% if movie.season %}
{{ movie.title }} {% trans '第' %}{{ movie.season|apnumber }}{% trans '季' %} {{ movie.orig_title }} Season
{{ movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% else %}
{{ movie.title }} {{ movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ movie.year }})
</span>
{% if movie.year %}({{ movie.year }}){% endif %}
{% endif %}
</a></h5>

View file

@ -57,27 +57,14 @@
{% if mark.movie.season %}
{{ mark.movie.title }} {% trans '第' %}{{ mark.movie.season|apnumber }}{% trans '季' %} {{ mark.movie.orig_title }} Season
{{ mark.movie.season }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ mark.movie.year }})
</span>
{% if mark.movie.year %}({{ mark.movie.year }}){% endif %}
{% else %}
{{ mark.movie.title }} {{ mark.movie.orig_title }}
<span class="entity-detail__title entity-detail__title--secondary">
({{ mark.movie.year }})
</span>
{% if mark.movie.year %}({{ mark.movie.year }}){% endif %}
{% endif %}
</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">
@ -104,7 +91,7 @@
{% for actor in mark.movie.actor %}
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>{{ actor }}</span>
{% if forloop.counter <= 5 %}
{% if not forloop.last %} / {% endif %}
{% if not forloop.counter == 5 %} / {% endif %}
{% endif %}
{% endfor %}
{% endif %}

View file

@ -336,7 +336,7 @@ def book_list(request, id, status):
list_title = str(BookMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的书"))
return render(
request,
'books/list.html',
'users/book_list.html',
{
'marks': marks,
'user': user,
@ -393,7 +393,7 @@ def movie_list(request, id, status):
list_title = str(MovieMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的电影和剧集"))
return render(
request,
'movies/list.html',
'users/movie_list.html',
{
'marks': marks,
'user': user,