mirror of
https://gitlab.com/LazyLibrarian/LazyLibrarian.git
synced 2026-02-06 10:47:15 +00:00
New modals for magazine and issue edits (thanks @txzeenath)
This commit is contained in:
parent
6c1b0f8d5e
commit
164b37060b
@ -213,12 +213,87 @@
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) { document.getElementById("mark_issues").submit(); }
|
||||
if (result) { submitFormAjax(); }
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else { document.getElementById("mark_issues").submit(); }
|
||||
else { submitFormAjax(); }
|
||||
}
|
||||
|
||||
function submitFormAjax() {
|
||||
var form = document.getElementById("mark_issues");
|
||||
var formData = new FormData(form);
|
||||
|
||||
// Show loading modal
|
||||
bootbox.dialog({
|
||||
message: '<div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i><br><br>Processing...</div>',
|
||||
size: 'small',
|
||||
backdrop: false
|
||||
});
|
||||
|
||||
// Convert FormData to URLSearchParams for the AJAX call
|
||||
var params = new URLSearchParams();
|
||||
var formDataEntries = formData.entries();
|
||||
for (var pair of formDataEntries) {
|
||||
params.append(pair[0], pair[1]);
|
||||
}
|
||||
|
||||
// Make AJAX call to the new endpoint
|
||||
fetch('mark_issues_ajax', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: params
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Close loading modal
|
||||
bootbox.hideAll();
|
||||
|
||||
// Show result modal
|
||||
var message = '<div class="alert alert-info"><strong>Summary:</strong><br>' + data.summary + '</div>';
|
||||
if (data.passed > 0 || data.failed > 0) {
|
||||
message += '<div class="row">';
|
||||
message += '<div class="col-md-6"><span class="label label-success">Successful: ' + data.passed + '</span></div>';
|
||||
if (data.failed > 0) {
|
||||
message += '<div class="col-md-6"><span class="label label-danger">Failed: ' + data.failed + '</span></div>';
|
||||
}
|
||||
message += '</div>';
|
||||
}
|
||||
|
||||
bootbox.dialog({
|
||||
title: 'Operation Complete',
|
||||
message: message,
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'OK',
|
||||
className: 'btn-primary',
|
||||
callback: function() {
|
||||
// Refresh the page to show updated data
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
// Close loading modal
|
||||
bootbox.hideAll();
|
||||
|
||||
// Show error modal
|
||||
bootbox.dialog({
|
||||
title: 'Error',
|
||||
message: '<div class="alert alert-danger">An error occurred: ' + error.message + '</div>',
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'OK',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
@ -275,12 +275,86 @@
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) { document.getElementById("mark_magazines").submit(); }
|
||||
if (result) { submitMagazinesFormAjax(); }
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
else { document.getElementById("mark_magazines").submit(); }
|
||||
else { submitMagazinesFormAjax(); }
|
||||
}
|
||||
function submitMagazinesFormAjax() {
|
||||
var form = document.getElementById("mark_magazines");
|
||||
var formData = new FormData(form);
|
||||
|
||||
// Show loading modal
|
||||
bootbox.dialog({
|
||||
message: '<div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i><br><br>Processing...</div>',
|
||||
size: 'small',
|
||||
backdrop: false
|
||||
});
|
||||
|
||||
// Convert FormData to URLSearchParams for the AJAX call
|
||||
var params = new URLSearchParams();
|
||||
var formDataEntries = formData.entries();
|
||||
for (var pair of formDataEntries) {
|
||||
params.append(pair[0], pair[1]);
|
||||
}
|
||||
|
||||
// Make AJAX call to the new endpoint
|
||||
fetch('mark_magazines_ajax', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: params
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Close loading modal
|
||||
bootbox.hideAll();
|
||||
|
||||
// Show result modal
|
||||
var message = '<div class="alert alert-info"><strong>Summary:</strong><br>' + data.summary + '</div>';
|
||||
if (data.passed > 0 || data.failed > 0) {
|
||||
message += '<div class="row">';
|
||||
message += '<div class="col-md-6"><span class="label label-success">Successful: ' + data.passed + '</span></div>';
|
||||
if (data.failed > 0) {
|
||||
message += '<div class="col-md-6"><span class="label label-danger">Failed: ' + data.failed + '</span></div>';
|
||||
}
|
||||
message += '</div>';
|
||||
}
|
||||
|
||||
bootbox.dialog({
|
||||
title: 'Operation Complete',
|
||||
message: message,
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'OK',
|
||||
className: 'btn-primary',
|
||||
callback: function() {
|
||||
// Refresh the page to show updated data
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
// Close loading modal
|
||||
bootbox.hideAll();
|
||||
|
||||
// Show error modal
|
||||
bootbox.dialog({
|
||||
title: 'Error',
|
||||
message: '<div class="alert alert-danger">An error occurred: ' + error.message + '</div>',
|
||||
buttons: {
|
||||
ok: {
|
||||
label: 'OK',
|
||||
className: 'btn-danger'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
@ -1016,6 +1016,7 @@ class WebInterface:
|
||||
self.check_permitted(lazylibrarian.perm_admin)
|
||||
db = database.DBConnection()
|
||||
try:
|
||||
res = ''
|
||||
match = db.match('SELECT * from users where UserName=?', (kwargs['user'],))
|
||||
if match:
|
||||
subs = db.select('SELECT Type,WantID from subscribers WHERE UserID=?', (match['userid'],))
|
||||
@ -1040,7 +1041,7 @@ class WebInterface:
|
||||
'theme': '', 'hc_id': '', 'hc_token': ''})
|
||||
finally:
|
||||
db.close()
|
||||
return res
|
||||
return res
|
||||
|
||||
@cherrypy.expose
|
||||
def admin_users(self, **kwargs):
|
||||
@ -1209,6 +1210,7 @@ class WebInterface:
|
||||
self.label_thread('PASSWORD_RESET')
|
||||
logger = logging.getLogger(__name__)
|
||||
res = {}
|
||||
msg = ''
|
||||
remote_ip = cherrypy.request.remote.ip
|
||||
db = database.DBConnection()
|
||||
try:
|
||||
@ -6070,6 +6072,151 @@ class WebInterface:
|
||||
else:
|
||||
raise cherrypy.HTTPRedirect("magazines")
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def mark_issues_ajax(self, action=None, **args):
|
||||
self.check_permitted(lazylibrarian.perm_status)
|
||||
logger = logging.getLogger(__name__)
|
||||
db = database.DBConnection()
|
||||
passed = 0
|
||||
failed = 0
|
||||
try:
|
||||
title = ''
|
||||
args.pop('book_table_length', None)
|
||||
|
||||
if action:
|
||||
for item in args:
|
||||
issue = db.match('SELECT IssueFile,Title,IssueDate,Cover from issues WHERE IssueID=?', (item,))
|
||||
if issue:
|
||||
issue = dict(issue)
|
||||
title = issue['Title']
|
||||
issuefile = issue['IssueFile']
|
||||
if not issuefile or not path_exists(issuefile):
|
||||
logger.error(f"No IssueFile found for IssueID {item}")
|
||||
failed += 1
|
||||
continue
|
||||
if 'reCover' in action and issuefile:
|
||||
coverfile = create_mag_cover(issuefile, refresh=True,
|
||||
pagenum=check_int(action[-1], 1))
|
||||
if coverfile:
|
||||
myhash = uuid.uuid4().hex
|
||||
hashname = os.path.join(DIRS.CACHEDIR, 'magazine', f'{myhash}.jpg')
|
||||
copyfile(coverfile, hashname)
|
||||
setperm(hashname)
|
||||
control_value_dict = {"IssueFile": issue['IssueFile']}
|
||||
newcover = f'cache/magazine/{myhash}.jpg'
|
||||
new_value_dict = {"Cover": newcover}
|
||||
db.upsert("Issues", new_value_dict, control_value_dict)
|
||||
latest = db.match("select Title,LatestCover,IssueDate from magazines "
|
||||
"where title=? COLLATE NOCASE", (title,))
|
||||
if latest:
|
||||
title = latest['Title']
|
||||
if latest['IssueDate'] == issue['IssueDate'] and latest['LatestCover'] != newcover:
|
||||
db.action("UPDATE magazines SET LatestCover=? "
|
||||
"WHERE Title=? COLLATE NOCASE", (newcover, title))
|
||||
issue['Cover'] = newcover
|
||||
issue['CoverFile'] = coverfile # for updating calibre cover
|
||||
if CONFIG['IMP_CALIBREDB'] and CONFIG.get_bool('IMP_CALIBRE_MAGAZINE'):
|
||||
self.update_calibre_issue_cover(issue)
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
logger.warning(f"No coverfile created for IssueID {item} {issuefile}")
|
||||
|
||||
if action == 'tag' and issuefile:
|
||||
logger.debug(f"Tagging {issuefile}")
|
||||
res = tag_issue(issuefile, title, issue['IssueDate'])
|
||||
if not res:
|
||||
failed += 1
|
||||
else:
|
||||
passed += 1
|
||||
if CONFIG.get_bool('IMP_MAGOPF'):
|
||||
logger.debug(f"Writing opf for {issuefile}")
|
||||
entry = db.match('SELECT Language FROM magazines where Title=?', (title,))
|
||||
_, _ = lazylibrarian.postprocess.create_mag_opf(issuefile, title,
|
||||
issue['IssueDate'], item,
|
||||
language=entry[0],
|
||||
overwrite=True)
|
||||
if action == 'coverswap' and issuefile:
|
||||
coverfile = None
|
||||
if CONFIG['MAG_COVERSWAP']:
|
||||
params = [CONFIG['MAG_COVERSWAP'], issuefile]
|
||||
logger.debug(f"Coverswap {params}")
|
||||
try:
|
||||
res = subprocess.check_output(params, stderr=subprocess.STDOUT)
|
||||
logger.info(res)
|
||||
coverfile = create_mag_cover(issuefile, refresh=True, pagenum=1)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(e.output)
|
||||
else:
|
||||
res = coverswap(issuefile, 2) # cover from page 2 (counted from 1)
|
||||
if res:
|
||||
coverfile = create_mag_cover(issuefile, refresh=True, pagenum=1)
|
||||
if coverfile:
|
||||
myhash = uuid.uuid4().hex
|
||||
hashname = os.path.join(DIRS.CACHEDIR, 'magazine', f'{myhash}.jpg')
|
||||
copyfile(coverfile, hashname)
|
||||
setperm(hashname)
|
||||
control_value_dict = {"IssueFile": issuefile}
|
||||
newcover = f'cache/magazine/{myhash}.jpg'
|
||||
new_value_dict = {"Cover": newcover}
|
||||
db.upsert("Issues", new_value_dict, control_value_dict)
|
||||
latest = db.match("select Title,LatestCover,IssueDate from magazines "
|
||||
"where title=? COLLATE NOCASE", (title,))
|
||||
if latest:
|
||||
title = latest['Title']
|
||||
if latest['IssueDate'] == issue['IssueDate'] and latest['LatestCover'] != newcover:
|
||||
db.action("UPDATE magazines SET LatestCover=? "
|
||||
"WHERE Title=? COLLATE NOCASE", (newcover, title))
|
||||
issue['Cover'] = newcover
|
||||
issue['CoverFile'] = coverfile # for updating calibre cover
|
||||
if CONFIG['IMP_CALIBREDB'] and CONFIG.get_bool('IMP_CALIBRE_MAGAZINE'):
|
||||
self.update_calibre_issue_cover(issue)
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
logger.warning(f"No coverfile created for IssueID {item} {issuefile}")
|
||||
|
||||
if action == "Delete" and issuefile:
|
||||
result = self.delete_issue(issuefile)
|
||||
if result:
|
||||
passed += 1
|
||||
logger.info(f'Issue {issue["IssueDate"]} of {issue["Title"]} deleted from disc')
|
||||
if CONFIG['IMP_CALIBREDB'] and CONFIG.get_bool('IMP_CALIBRE_MAGAZINE'):
|
||||
self.delete_from_calibre(issue)
|
||||
else:
|
||||
failed += 1
|
||||
if action == "Remove" or action == "Delete":
|
||||
db.action('DELETE from issues WHERE IssueID=?', (item,))
|
||||
logger.info(f'Issue {issue["IssueDate"]} of {issue["Title"]} removed from database')
|
||||
_ = self.mag_set_latest(title)
|
||||
passed += 1
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
logger.debug(f"{action.title()}: Pass {passed}, Fail {failed}")
|
||||
|
||||
# Return JSON response instead of redirect
|
||||
total = passed + failed
|
||||
summary = f"Operation '{action}' completed."
|
||||
if total > 0:
|
||||
summary += f" {passed} successful"
|
||||
if failed > 0:
|
||||
summary += f", {failed} failed"
|
||||
summary += f" out of {total} total items."
|
||||
else:
|
||||
summary += " No items were processed."
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'action': action,
|
||||
'passed': passed,
|
||||
'failed': failed,
|
||||
'total': total,
|
||||
'title': title,
|
||||
'summary': summary
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def mag_set_latest(title):
|
||||
# Set magazine_issuedate to issuedate of most recent issue we have
|
||||
@ -6256,6 +6403,134 @@ class WebInterface:
|
||||
logger.debug(f"{action.title()}: Pass {passed}, Fail {failed}")
|
||||
raise cherrypy.HTTPRedirect("magazines")
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def mark_magazines_ajax(self, action=None, **args):
|
||||
self.check_permitted(lazylibrarian.perm_status)
|
||||
logger = logging.getLogger(__name__)
|
||||
db = database.DBConnection()
|
||||
try:
|
||||
args.pop('book_table_length', None)
|
||||
passed = 0
|
||||
failed = 0
|
||||
for item in args:
|
||||
title = item
|
||||
if action == "Paused" or action == "Active":
|
||||
control_value_dict = {"Title": title}
|
||||
new_value_dict = {"Status": action}
|
||||
db.upsert("magazines", new_value_dict, control_value_dict)
|
||||
logger.info(f'Status of magazine {title} changed to {action}')
|
||||
|
||||
if action == "Delete":
|
||||
issues = db.select('SELECT * from issues WHERE Title=?', (title,))
|
||||
logger.debug(f'Deleting magazine {title} from disc')
|
||||
issuedir = ''
|
||||
for issue in issues: # delete all issues of this magazine
|
||||
result = self.delete_issue(issue['IssueFile'])
|
||||
if result:
|
||||
logger.debug(f'Issue {issue["IssueFile"]} deleted from disc')
|
||||
if CONFIG['IMP_CALIBREDB'] and CONFIG.get_bool('IMP_CALIBRE_MAGAZINE'):
|
||||
self.delete_from_calibre(issue)
|
||||
issuedir = os.path.dirname(issue['IssueFile'])
|
||||
else:
|
||||
logger.debug(f'Failed to delete {issue["IssueFile"]}')
|
||||
|
||||
# if the directory is now empty, delete that too
|
||||
if issuedir and CONFIG.get_bool('MAG_DELFOLDER'):
|
||||
magdir = os.path.dirname(issuedir)
|
||||
try:
|
||||
os.rmdir(syspath(magdir))
|
||||
logger.debug(f'Magazine directory {magdir} deleted from disc')
|
||||
except OSError:
|
||||
logger.debug(f'Magazine directory {magdir} is not empty')
|
||||
logger.info(f'Magazine {title} deleted from disc')
|
||||
|
||||
if action == 'tag':
|
||||
issues = db.select('SELECT * from issues WHERE Title=?', (title,))
|
||||
mag = db.match('SELECT Language FROM magazines where Title=?', (title,))
|
||||
for issue in issues:
|
||||
logger.debug(f"Tagging {issue['IssueFile']}")
|
||||
res = tag_issue(issue['IssueFile'], title, issue["IssueDate"])
|
||||
if not res:
|
||||
failed += 1
|
||||
else:
|
||||
passed += 1
|
||||
if CONFIG.get_bool('IMP_MAGOPF'):
|
||||
logger.debug(f"Writing opf for {issue['IssueFile']}")
|
||||
_, _ = lazylibrarian.postprocess.create_mag_opf(issue['IssueFile'], title,
|
||||
issue["IssueDate"],
|
||||
issue["IssueID"],
|
||||
language=mag[0],
|
||||
overwrite=True)
|
||||
|
||||
if action == "Remove" or action == "Delete":
|
||||
db.action('DELETE from magazines WHERE Title=? COLLATE NOCASE', (title,))
|
||||
db.action('DELETE from pastissues WHERE BookID=? COLLATE NOCASE', (title,))
|
||||
db.action('DELETE from wanted where BookID=? COLLATE NOCASE', (title,))
|
||||
logger.info(f'Magazine {title} removed from database')
|
||||
passed += 1
|
||||
elif action == "Reset":
|
||||
control_value_dict = {"Title": title}
|
||||
new_value_dict = {
|
||||
"LastAcquired": '',
|
||||
"IssueDate": '',
|
||||
"LatestCover": '',
|
||||
"IssueStatus": "Wanted"
|
||||
}
|
||||
db.upsert("magazines", new_value_dict, control_value_dict)
|
||||
logger.info(f'Magazine {title} details reset')
|
||||
passed += 1
|
||||
elif action == 'Subscribe':
|
||||
cookie = cherrypy.request.cookie
|
||||
if cookie and 'll_uid' in list(cookie.keys()):
|
||||
userid = cookie['ll_uid'].value
|
||||
res = db.match("SELECT * from subscribers WHERE UserID=? and Type=? and WantID=?",
|
||||
(userid, 'magazine', title))
|
||||
if res:
|
||||
logger.debug(f"User {userid} is already subscribed to {title}")
|
||||
failed += 1
|
||||
else:
|
||||
db.action('INSERT into subscribers (UserID, Type, WantID) VALUES (?, ?, ?)',
|
||||
(userid, 'magazine', title))
|
||||
logger.debug(f"Subscribe {userid} to magazine {title}")
|
||||
passed += 1
|
||||
elif action == 'Unsubscribe':
|
||||
cookie = cherrypy.request.cookie
|
||||
if cookie and 'll_uid' in list(cookie.keys()):
|
||||
userid = cookie['ll_uid'].value
|
||||
db.action('DELETE from subscribers WHERE UserID=? and Type=? and WantID=?',
|
||||
(userid, 'magazine', title))
|
||||
res = db.select('SELECT issueid from issues where title=?', (title, ))
|
||||
for iss in res:
|
||||
db.action('DELETE from subscribers WHERE UserID=? and Type=? and WantID=?',
|
||||
(userid, 'magazine', iss['issueid']))
|
||||
logger.debug(f"Unsubscribe {userid} to magazine {title}")
|
||||
passed += 1
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
logger.debug(f"{action.title()}: Pass {passed}, Fail {failed}")
|
||||
|
||||
# Return JSON response instead of redirect
|
||||
total = passed + failed
|
||||
summary = f"Operation '{action}' completed."
|
||||
if total > 0:
|
||||
summary += f" {passed} successful"
|
||||
if failed > 0:
|
||||
summary += f", {failed} failed"
|
||||
summary += f" out of {total} total items."
|
||||
else:
|
||||
summary += " No items were processed."
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'action': action,
|
||||
'passed': passed,
|
||||
'failed': failed,
|
||||
'total': total,
|
||||
'summary': summary
|
||||
}
|
||||
|
||||
@cherrypy.expose
|
||||
def search_for_mag(self, bookid=None):
|
||||
self.check_permitted(lazylibrarian.perm_search)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user