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 # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-ie'
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'

View File

@@ -39,4 +39,4 @@ body {
} }
tr.clickable-row { tr.clickable-row {
cursor: pointer; cursor: pointer;
} }

View File

@@ -72,7 +72,7 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <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/bootstrap.min.js"></script>
<script src="/static/js/jasny-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 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> <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 %} {% block script %}

View File

@@ -3,10 +3,19 @@
{% block content %} {% block content %}
<div class="container"> <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"> <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> <thead>
<tr> <tr>
<th id="select-all"><input type="checkbox" id="select-all-cb"></th>
<th><center><span class="glyphicon glyphicon-file"></span></center></th> <th><center><span class="glyphicon glyphicon-file"></span></center></th>
<th width="100%">File/Folder</th> <th width="100%">File/Folder</th>
<th>Status</th> <th>Status</th>
@@ -20,30 +29,17 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<!--/.nav-collapse </form>
<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>-->
</div> </div>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#comic_list').DataTable({ var table = $('#comic_list').DataTable({
"processing": true, "processing": true,
"stateSave": true,
"ajax": { "ajax": {
"type": "POST", "type": "POST",
"url": "{{ json_url }}", "url": "{{ json_url }}",
@@ -52,25 +48,68 @@ $(document).ready(function() {
}, },
}, },
"rowCallback": function( row, data, index ) { "rowCallback": function( row, data, index ) {
var r = $(row) var r = $(row);
r.attr('data-href', data['url']); var cols = $('td:nth-child(n+2)', row);
r.attr('style', 'cursor: pointer;') cols.attr('data-href', data['url']);
r.click(function() { cols.attr('style', 'cursor: pointer;')
cols.click(function() {
window.document.location = $(this).data("href"); 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": [ "columns": [
{ "data" : "blank", "orderable": false },
{ "data" : "icon", "orderable": false }, { "data" : "icon", "orderable": false },
{ "data" : "name" }, { "data" : "name" },
{ "data" : "label" }, { "data" : "label" },
], ],
"order": [[ 1, 'asc' ]], "order": [[ 2, 'asc' ]],
}); });
$(".clickable-row").click(function() { $(".clickable-row").click(function() {
window.document.location = $(this).data("href"); 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> </script>
{% endblock %} {% 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'^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/$', 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'^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'), 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 = OrderedDict()
self.menu_items['Browse'] = '/comic/' self.menu_items['Browse'] = '/comic/'
self.menu_items['Recent'] = '/comic/recent/'
self.menu_items['Account'] = '/comic/account/' self.menu_items['Account'] = '/comic/account/'
if user.is_superuser: if user.is_superuser:
self.menu_items['Settings'] = '/comic/settings/' self.menu_items['Settings'] = '/comic/settings/'
@@ -84,6 +85,8 @@ class DirFile:
self.location = '' self.location = ''
self.label = '' self.label = ''
self.cur_page = 0 self.cur_page = 0
self.type = ''
self.selector = ''
def __str__(self): def __str__(self):
return self.name return self.name
@@ -91,7 +94,6 @@ class DirFile:
def generate_directory(user, directory=False): def generate_directory(user, directory=False):
""" """
:type user: User :type user: User
:type directory: Directory :type directory: Directory
""" """
@@ -123,8 +125,10 @@ def generate_directory(user, directory=False):
d.save() d.save()
df.isdir = True df.isdir = True
df.icon = 'glyphicon-folder-open' 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.label = generate_dir_status(user, d)
df.type = 'directory'
elif file_name.lower()[-4:] in ['.rar', '.zip', '.cbr', '.cbz']: elif file_name.lower()[-4:] in ['.rar', '.zip', '.cbr', '.cbz']:
df.iscb = True df.iscb = True
df.icon = 'glyphicon-book' df.icon = 'glyphicon-book'
@@ -138,26 +142,32 @@ def generate_directory(user, directory=False):
except ComicBook.DoesNotExist: except ComicBook.DoesNotExist:
book = ComicBook.process_comic_book(file_name, directory) 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 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) last_page)
if status.unread: df.cur_page = last_page
df.label = '<center><span class="label label-default">Unread</span></center>' df.label = generate_label(book, status)
elif (last_page + 1) == book.page_count: df.type = 'book'
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 = '<span class="label label-danger pull-right">Unprocessed</span>'
files.append(df) files.append(df)
return files 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): def generate_dir_status(user, directory):
cb_list = ComicBook.objects.filter(directory=directory) cb_list = ComicBook.objects.filter(directory=directory)
total = cb_list.count() 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.decorators import login_required, user_passes_test
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Max from django.db.models import Max
from django.db.transaction import atomic
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404 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.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm from .forms import SettingsForm, AccountForm, EditUserForm, AddUserForm, InitialSetupForm
from .models import Setting, ComicBook, ComicStatus, Directory, ComicPage from .models import Setting, ComicBook, ComicStatus, Directory, ComicPage
from .util import generate_breadcrumbs_from_path, generate_breadcrumbs_from_menu, \ 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 @ensure_csrf_cookie
@login_required @login_required
def comic_list(request, directory_selector=False): def comic_list(request, directory_selector=False):
@@ -42,10 +44,8 @@ def comic_list(request, directory_selector=False):
title = generate_title_from_path('Home') title = generate_title_from_path('Home')
breadcrumbs = generate_breadcrumbs_from_path() breadcrumbs = generate_breadcrumbs_from_path()
json_url = '/comic/list_json/' json_url = '/comic/list_json/'
files = generate_directory(request.user)
return render(request, 'comic/comic_list.html', { return render(request, 'comic/comic_list.html', {
'file_list': files,
'breadcrumbs': breadcrumbs, 'breadcrumbs': breadcrumbs,
'menu': Menu(request.user, 'Browse'), 'menu': Menu(request.user, 'Browse'),
'title': title, 'title': title,
@@ -67,6 +67,9 @@ def comic_list_json(request, directory_selector=False):
response_data['data'] = [] response_data['data'] = []
for file in files: for file in files:
response_data['data'].append({ response_data['data'].append({
'blank': '',
'selector': file.selector,
'type': file.type,
'icon': icon_str.format(file.icon), 'icon': icon_str.format(file.icon),
'name': file.name, 'name': file.name,
'label': file.label, '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 @login_required
def account_page(request): def account_page(request):
success_message = [] success_message = []