made major changes to interface.

can now mark comics as read!
also added a recently added section.
This commit is contained in:
ajurna@gmail.com
2016-04-13 15:51:35 +01:00
parent 34188665e9
commit 58ff1060e6
8 changed files with 305 additions and 46 deletions

View File

@@ -88,7 +88,7 @@ DATABASES = {
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en-ie'
TIME_ZONE = 'UTC'

View File

@@ -72,7 +72,7 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/jasny-bootstrap.min.js"></script>
<script src="/static/js/jQuery-2.2.2.min.js"></script>
<script src="/static/js/jquery-2.2.2.min.js"></script>
<script src="/static/js/js.cookie.js"></script>
<script type="text/javascript" src="https://cdn.datatables.net/t/bs/dt-1.10.11,b-colvis-1.1.2,r-2.0.2/datatables.min.js"></script>
{% block script %}

View File

@@ -3,10 +3,19 @@
{% block content %}
<div class="container">
<form id="comic_form" method="post" action="/comic/edit/">
{% csrf_token %}
<table class="table table-bordered table-striped table-hover" id="comic_list">
<caption><h2>Comics</h2></caption>
<caption><h2>Comics</h2> mark selected issues as:
<select name="func" id="func_selector">
<option value="choose">Choose...</option>
<option value="read">Read</option>
<option value="unread">Un-Read</option>
</select>
</caption>
<thead>
<tr>
<th id="select-all"><input type="checkbox" id="select-all-cb"></th>
<th><center><span class="glyphicon glyphicon-file"></span></center></th>
<th width="100%">File/Folder</th>
<th>Status</th>
@@ -20,30 +29,17 @@
</tr>
</tbody>
</table>
<!--/.nav-collapse
<h2 class="center">Comics</h2>
<div class="list-group">
{% if file_list %}
{% for file in file_list %}
{% if file.isdir %}
<a href="/comic/{{ file.location }}/" class="glyphicon {{ file.icon }} list-group-item"> {{ file }}{{ file.label | safe }}</a>
{% endif %}
{% if file.iscb %}
<a href="/comic/read/{{ file.location }}/{{ file.cur_page }}/" class="glyphicon {{ file.icon }} list-group-item"> {{ file }}{{ file.label | safe }}</a>
{% endif %}
{% endfor %}
{% else %}
<p class="list-group-item">No comics.</p>
{% endif %}
</div>-->
</form>
</div>
{% endblock %}
{% block script %}
<script>
$(document).ready(function() {
$('#comic_list').DataTable({
var table = $('#comic_list').DataTable({
"processing": true,
"stateSave": true,
"ajax": {
"type": "POST",
"url": "{{ json_url }}",
@@ -52,25 +48,68 @@ $(document).ready(function() {
},
},
"rowCallback": function( row, data, index ) {
var r = $(row)
r.attr('data-href', data['url']);
r.attr('style', 'cursor: pointer;')
r.click(function() {
var r = $(row);
var cols = $('td:nth-child(n+2)', row);
cols.attr('data-href', data['url']);
cols.attr('style', 'cursor: pointer;')
cols.click(function() {
window.document.location = $(this).data("href");
});
var tds = $('td:eq(0)', row);
if (data['type'] == 'directory'){
tds.html('');
} else {
tds.html('<input type="checkbox" name="selected" value="'+data['selector']+'" data-type="'+data['type']+'"/>');
var cb = $('input', tds);
cb.change(function() {
$(this).closest('tr').toggleClass('info')
});
};
},
"drawCallback": function( settings ) {
var tds = $('table tr td:first-child');
tds.click(function(event){
if (!$(event.target).is('input')) {
var $cb = $('input', this)
$cb.click();
};
});
},
"columns": [
{ "data" : "blank", "orderable": false },
{ "data" : "icon", "orderable": false },
{ "data" : "name" },
{ "data" : "label" },
],
"order": [[ 1, 'asc' ]],
"order": [[ 2, 'asc' ]],
});
$(".clickable-row").click(function() {
window.document.location = $(this).data("href");
});
$('#func_selector').on('change', function() {
$.post('/comic/edit/', $('#comic_form').serialize())
.done(function(){
$('#func_selector').val('choose');
$('#select-all input').prop('checked', false);
table.ajax.reload();
}).fail(function(){
alert('Error Submitting Change');
})
});
$('#select-all').click(function(event){
var cb = $('input', this)
if (!$(event.target).is('input')) {
cb.click();
};
$('table tr td:first-child input').each(function(chkbx) {
row = $(this)
if (row.prop('checked') != cb.prop('checked')){
row.click();
}
});
});
} );
</script>
{% endblock %}

View File

@@ -0,0 +1,123 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container">
<form id="comic_form" method="post" action="/comic/edit/">
{% csrf_token %}
<table class="table table-bordered table-striped table-hover" id="comic_list">
<caption><h2>Recent Comics</h2>
mark selected issues as:
<select name="func" id="func_selector">
<option value="choose">Choose...</option>
<option value="read">Read</option>
<option value="unread">Un-Read</option>
</select>
</caption>
<thead>
<tr>
<th id="select-all"><input type="checkbox" id="select-all-cb"></th>
<th>
<center><span class="glyphicon glyphicon-file"></span></center>
</th>
<th width="100%">File/Folder</th>
<th>Date&nbsp;Added</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr class="clickable-row" data-href="/comic/">
<td>
<center><span class="glyphicon glyphicon-file"></span></center>
</td>
<td>loading data</td>
<td><span class="label label-primary pull-right">1/23</span></td>
</tr>
</tbody>
</table>
</form>
</div>
{% endblock %}
{% block script %}
<script>
$(document).ready(function() {
var table = $('#comic_list').DataTable({
"processing": true,
"stateSave": true,
"serverSide": true,
"ajax": {
"type": "POST",
"url": "/comic/recent/json/",
"data": function ( d ) {
d.csrfmiddlewaretoken = Cookies.get('csrftoken');
},
},
"rowCallback": function( row, data, index ) {
var r = $(row);
var cols = $('td:nth-child(n+2)', row);
cols.attr('data-href', data['url']);
cols.attr('style', 'cursor: pointer;')
cols.click(function() {
window.document.location = $(this).data("href");
});
var tds = $('td:eq(0)', row);
tds.html('<input type="checkbox" name="selected" value="'+data['selector']+'" data-type="'+data['type']+'"/>');
var cb = $('input', tds);
cb.change(function() {
$(this).closest('tr').toggleClass('info')
});
},
"drawCallback": function( settings ) {
var tds = $('table tr td:first-child');
tds.click(function(event){
if (!$(event.target).is('input')) {
var $cb = $('input', this)
$cb.click();
};
});
},
"columns": [
{ "data" : "selector", "orderable": false },
{ "data" : "icon", "orderable": false },
{ "data" : "name" },
{ "data" : "date" },
{ "data" : "label", "orderable": false },
],
"order": [[ 3, 'asc' ]],
});
$(".clickable-row").click(function() {
window.document.location = $(this).data("href");
});
$('#func_selector').on('change', function() {
$.post('/comic/edit/', $('#comic_form').serialize())
.done(function(){
$('#func_selector').val('choose');
$('#select-all input').prop('checked', false);
table.ajax.reload();
}).fail(function(){
alert('Error Submitting Change');
})
});
$('#select-all').click(function(event){
var cb = $('input', this)
if (!$(event.target).is('input')) {
cb.click();
};
$('table tr td:first-child input').each(function(chkbx) {
row = $(this)
if (row.prop('checked') != cb.prop('checked')){
row.click();
}
});
});
} );
</script>
{% endblock %}

View File

@@ -13,5 +13,8 @@ urlpatterns = [
url(r'^read/(?P<comic_selector>[\w-]+)/(?P<page>[0-9]+)/img$', views.get_image, name='get_image'),
url(r'^list_json/$', views.comic_list_json, name='comic_list_json1'),
url(r'^list_json/(?P<directory_selector>[\w-]+)/$', views.comic_list_json, name='comic_list_json2'),
url(r'^recent/$', views.recent_comics, name='recent_comics'),
url(r'^recent/json/$', views.recent_comics_json, name='recent_comics_json'),
url(r'^edit/$', views.comic_edit, name='comic_edit'),
url(r'^(?P<directory_selector>[\w-]+)/$', views.comic_list, name='comic_list'),
]

View File

@@ -20,6 +20,7 @@ class Menu:
"""
self.menu_items = OrderedDict()
self.menu_items['Browse'] = '/comic/'
self.menu_items['Recent'] = '/comic/recent/'
self.menu_items['Account'] = '/comic/account/'
if user.is_superuser:
self.menu_items['Settings'] = '/comic/settings/'
@@ -84,6 +85,8 @@ class DirFile:
self.location = ''
self.label = ''
self.cur_page = 0
self.type = ''
self.selector = ''
def __str__(self):
return self.name
@@ -91,7 +94,6 @@ class DirFile:
def generate_directory(user, directory=False):
"""
:type user: User
:type directory: Directory
"""
@@ -123,8 +125,10 @@ def generate_directory(user, directory=False):
d.save()
df.isdir = True
df.icon = 'glyphicon-folder-open'
df.location = '/comic/{0}/'.format(urlsafe_base64_encode(d.selector.bytes).decode())
df.selector = urlsafe_base64_encode(d.selector.bytes).decode()
df.location = '/comic/{0}/'.format(df.selector)
df.label = generate_dir_status(user, d)
df.type = 'directory'
elif file_name.lower()[-4:] in ['.rar', '.zip', '.cbr', '.cbz']:
df.iscb = True
df.icon = 'glyphicon-book'
@@ -138,26 +142,32 @@ def generate_directory(user, directory=False):
except ComicBook.DoesNotExist:
book = ComicBook.process_comic_book(file_name, directory)
status, _ = ComicStatus.objects.get_or_create(comic=book, user=user)
status, created = ComicStatus.objects.get_or_create(comic=book, user=user)
if created:
status.save()
last_page = status.last_read_page
df.location = '/comic/read/{0}/{1}/'.format(urlsafe_base64_encode(book.selector.bytes).decode(),
df.selector = urlsafe_base64_encode(book.selector.bytes).decode()
df.location = '/comic/read/{0}/{1}/'.format(df.selector,
last_page)
if status.unread:
df.label = '<center><span class="label label-default">Unread</span></center>'
elif (last_page + 1) == book.page_count:
df.label = '<center><span class="label label-success">Read</span></center>'
df.cur_page = last_page
else:
label_text = '<center><span class="label label-primary">%s/%s</span></center>' % \
(last_page + 1, book.page_count)
df.label = label_text
df.cur_page = last_page
df.label = generate_label(book, status)
df.type = 'book'
# df.label = '<span class="label label-danger pull-right">Unprocessed</span>'
files.append(df)
return files
def generate_label(book, status):
if status.unread:
label_text = '<center><span class="label label-default">Unread</span></center>'
elif (status.last_read_page + 1) == book.page_count:
label_text = '<center><span class="label label-success">Read</span></center>'
else:
label_text = '<center><span class="label label-primary">%s/%s</span></center>' % \
(status.last_read_page + 1, book.page_count)
return label_text
def generate_dir_status(user, directory):
cb_list = ComicBook.objects.filter(directory=directory)
total = cb_list.count()

View File

@@ -6,18 +6,20 @@ from django.contrib.auth import login, authenticate
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User
from django.db.models import Max
from django.db.transaction import atomic
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.http import urlsafe_base64_decode
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_POST
from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm
from .models import Setting, ComicBook, ComicStatus, Directory, ComicPage
from .util import generate_breadcrumbs_from_path, generate_breadcrumbs_from_menu, \
generate_title_from_path, Menu, generate_directory
generate_title_from_path, Menu, generate_directory, generate_label
# noinspection PyTypeChecker
@ensure_csrf_cookie
@login_required
def comic_list(request, directory_selector=False):
@@ -42,10 +44,8 @@ def comic_list(request, directory_selector=False):
title = generate_title_from_path('Home')
breadcrumbs = generate_breadcrumbs_from_path()
json_url = '/comic/list_json/'
files = generate_directory(request.user)
return render(request, 'comic/comic_list.html', {
'file_list': files,
'breadcrumbs': breadcrumbs,
'menu': Menu(request.user, 'Browse'),
'title': title,
@@ -67,6 +67,9 @@ def comic_list_json(request, directory_selector=False):
response_data['data'] = []
for file in files:
response_data['data'].append({
'blank': '',
'selector': file.selector,
'type': file.type,
'icon': icon_str.format(file.icon),
'name': file.name,
'label': file.label,
@@ -78,6 +81,87 @@ def comic_list_json(request, directory_selector=False):
)
@login_required
def recent_comics(request):
return render(request,
'comic/recent_comics.html',
{
'breadcrumbs': generate_breadcrumbs_from_menu([('Recent', '/comic/recent/')]),
'menu': Menu(request.user, 'Recent'),
'title': 'Recent Comics'
})
@login_required
@require_POST
def recent_comics_json(request):
start = int(request.POST['start'])
end = start + int(request.POST['length'])
icon = '<span class="glyphicon glyphicon-book"></span>'
comics = ComicBook.objects.all()
response_data = dict()
response_data['recordsTotal'] = comics.count()
if request.POST['search[value]']:
comics = comics.filter(file_name__contains=request.POST['search[value]'])
order_string = ''
# Ordering
if request.POST['order[0][dir]'] == 'desc':
order_string += '-'
if request.POST['order[0][dir]'] == '3':
order_string += 'date_added'
elif request.POST['order[0][dir]'] == '2':
order_string += 'date_added'
else:
order_string += 'date_added'
comics = comics.order_by(order_string)
response_data['recordsFiltered'] = comics.count()
response_data['data'] = list()
for book in comics[start:end]:
status, created = ComicStatus.objects.get_or_create(comic=book,
user=request.user)
if created:
status.save()
response_data['data'].append({
'selector': urlsafe_base64_encode(book.selector.bytes).decode(),
'icon': icon,
'type': 'book',
'name': book.file_name,
'date': book.date_added.strftime('%d/%m/%y-%H:%M'),
'label': generate_label(book, status),
'url': '/comic/read/{0}/{1}/'.format(urlsafe_base64_encode(book.selector.bytes).decode(),
status.last_read_page)
})
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
@login_required
@require_POST
def comic_edit(request):
if 'selected' not in request.POST:
return HttpResponse(status=200)
if request.POST['func'] == 'choose':
return HttpResponse(status=200)
selected = [uuid.UUID(bytes=urlsafe_base64_decode(item)) for item in request.POST.getlist('selected')]
comics = ComicBook.objects.filter(selector__in=selected)
with atomic():
for comic in comics:
status, _ = ComicStatus.objects.get_or_create(comic=comic,
user=request.user)
if request.POST['func'] == 'read':
status.unread = False
status.finished = True
status.last_read_page = comic.page_count - 1
elif request.POST['func'] == 'unread':
status.unread = True
status.finished = False
status.last_read_page = 0
status.save()
return HttpResponse(status=200)
@login_required
def account_page(request):
success_message = []