mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-31 07:13:02 +00:00 
			
		
		
		
	Merge branch 'master' into development
# Conflicts: # cps/static/css/style.css
This commit is contained in:
		
							
								
								
									
										70
									
								
								cps/comic.py
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								cps/comic.py
									
									
									
									
									
								
							| @@ -74,6 +74,41 @@ def _cover_processing(tmp_file_name, img, extension): | ||||
|     return tmp_cover_name | ||||
|  | ||||
|  | ||||
| def _extract_Cover_from_archive(original_file_extension, tmp_file_name, rarExecutable): | ||||
|     cover_data = None | ||||
|     if original_file_extension.upper() == '.CBZ': | ||||
|         cf = zipfile.ZipFile(tmp_file_name) | ||||
|         for name in cf.namelist(): | ||||
|             ext = os.path.splitext(name) | ||||
|             if len(ext) > 1: | ||||
|                 extension = ext[1].lower() | ||||
|                 if extension in COVER_EXTENSIONS: | ||||
|                     cover_data = cf.read(name) | ||||
|                     break | ||||
|     elif original_file_extension.upper() == '.CBT': | ||||
|         cf = tarfile.TarFile(tmp_file_name) | ||||
|         for name in cf.getnames(): | ||||
|             ext = os.path.splitext(name) | ||||
|             if len(ext) > 1: | ||||
|                 extension = ext[1].lower() | ||||
|                 if extension in COVER_EXTENSIONS: | ||||
|                     cover_data = cf.extractfile(name).read() | ||||
|                     break | ||||
|     elif original_file_extension.upper() == '.CBR' and use_rarfile: | ||||
|         try: | ||||
|             rarfile.UNRAR_TOOL = rarExecutable | ||||
|             cf = rarfile.RarFile(tmp_file_name) | ||||
|             for name in cf.getnames(): | ||||
|                 ext = os.path.splitext(name) | ||||
|                 if len(ext) > 1: | ||||
|                     extension = ext[1].lower() | ||||
|                     if extension in COVER_EXTENSIONS: | ||||
|                         cover_data = cf.read(name) | ||||
|                         break | ||||
|         except Exception as e: | ||||
|             log.debug('Rarfile failed with error: %s', e) | ||||
|     return cover_data | ||||
|  | ||||
|  | ||||
| def _extractCover(tmp_file_name, original_file_extension, rarExecutable): | ||||
|     cover_data = extension = None | ||||
| @@ -87,37 +122,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExecutable): | ||||
|                     cover_data = archive.getPage(index) | ||||
|                     break | ||||
|     else: | ||||
|         if original_file_extension.upper() == '.CBZ': | ||||
|             cf = zipfile.ZipFile(tmp_file_name) | ||||
|             for name in cf.namelist(): | ||||
|                 ext = os.path.splitext(name) | ||||
|                 if len(ext) > 1: | ||||
|                     extension = ext[1].lower() | ||||
|                     if extension in COVER_EXTENSIONS: | ||||
|                         cover_data = cf.read(name) | ||||
|                         break | ||||
|         elif original_file_extension.upper() == '.CBT': | ||||
|             cf = tarfile.TarFile(tmp_file_name) | ||||
|             for name in cf.getnames(): | ||||
|                 ext = os.path.splitext(name) | ||||
|                 if len(ext) > 1: | ||||
|                     extension = ext[1].lower() | ||||
|                     if extension in COVER_EXTENSIONS: | ||||
|                         cover_data = cf.extractfile(name).read() | ||||
|                         break | ||||
|         elif original_file_extension.upper() == '.CBR' and use_rarfile: | ||||
|             try: | ||||
|                 rarfile.UNRAR_TOOL = rarExecutable | ||||
|                 cf = rarfile.RarFile(tmp_file_name) | ||||
|                 for name in cf.getnames(): | ||||
|                     ext = os.path.splitext(name) | ||||
|                     if len(ext) > 1: | ||||
|                         extension = ext[1].lower() | ||||
|                         if extension in COVER_EXTENSIONS: | ||||
|                             cover_data = cf.read(name) | ||||
|                             break | ||||
|             except Exception as e: | ||||
|                 log.debug('Rarfile failed with error: %s', e) | ||||
|         cover_data = _extract_Cover_from_archive(original_file_extension, tmp_file_name, rarExecutable) | ||||
|     return _cover_processing(tmp_file_name, cover_data, extension) | ||||
|  | ||||
|  | ||||
| @@ -142,7 +147,8 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r | ||||
|                 file_path=tmp_file_path, | ||||
|                 extension=original_file_extension, | ||||
|                 title=loadedMetadata.title or original_file_name, | ||||
|                 author=" & ".join([credit["person"] for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u'Unknown', | ||||
|                 author=" & ".join([credit["person"] | ||||
|                                    for credit in loadedMetadata.credits if credit["role"] == "Writer"]) or u'Unknown', | ||||
|                 cover=_extractCover(tmp_file_path, original_file_extension, rarExecutable), | ||||
|                 description=loadedMetadata.comments or "", | ||||
|                 tags="", | ||||
|   | ||||
| @@ -146,15 +146,16 @@ class _ConfigSQL(object): | ||||
|         self.load() | ||||
|  | ||||
|         change = False | ||||
|         if self.config_converterpath == None: | ||||
|         if self.config_converterpath == None:  # pylint: disable=access-member-before-definition | ||||
|             change = True | ||||
|             self.config_converterpath = autodetect_calibre_binary() | ||||
|  | ||||
|         if self.config_kepubifypath == None: | ||||
|         if self.config_kepubifypath == None:  # pylint: disable=access-member-before-definition | ||||
|  | ||||
|             change = True | ||||
|             self.config_kepubifypath = autodetect_kepubify_binary() | ||||
|  | ||||
|         if self.config_rarfile_location == None: | ||||
|         if self.config_rarfile_location == None:  # pylint: disable=access-member-before-definition | ||||
|             change = True | ||||
|             self.config_rarfile_location = autodetect_unrar_binary() | ||||
|         if change: | ||||
| @@ -181,7 +182,8 @@ class _ConfigSQL(object): | ||||
|             return None | ||||
|         return self.config_keyfile | ||||
|  | ||||
|     def get_config_ipaddress(self): | ||||
|     @staticmethod | ||||
|     def get_config_ipaddress(): | ||||
|         return cli.ipadress or "" | ||||
|  | ||||
|     def _has_role(self, role_flag): | ||||
| @@ -299,6 +301,7 @@ class _ConfigSQL(object): | ||||
|                 have_metadata_db = os.path.isfile(db_file) | ||||
|         self.db_configured = have_metadata_db | ||||
|         constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip().lower() for x in self.config_upload_formats.split(',')] | ||||
|         # pylint: disable=access-member-before-definition | ||||
|         logfile = logger.setup(self.config_logfile, self.config_log_level) | ||||
|         if logfile != self.config_logfile: | ||||
|             log.warning("Log path %s not valid, falling back to default", self.config_logfile) | ||||
|   | ||||
| @@ -126,7 +126,7 @@ LDAP_AUTH_SIMPLE         = 0 | ||||
|  | ||||
| DEFAULT_MAIL_SERVER = "mail.example.org" | ||||
|  | ||||
| DEFAULT_PASSWORD    = "admin123" | ||||
| DEFAULT_PASSWORD    = "admin123"  # nosec  # noqa | ||||
| DEFAULT_PORT        = 8083 | ||||
| env_CALIBRE_PORT = os.environ.get("CALIBRE_PORT", DEFAULT_PORT) | ||||
| try: | ||||
|   | ||||
| @@ -156,10 +156,8 @@ class Identifiers(Base): | ||||
|             return u"https://portal.issn.org/resource/ISSN/{0}".format(self.val) | ||||
|         elif format_type == "isfdb": | ||||
|             return u"http://www.isfdb.org/cgi-bin/pl.cgi?{0}".format(self.val) | ||||
|         elif format_type == "url": | ||||
|             return u"{0}".format(self.val) | ||||
|         else: | ||||
|             return u"" | ||||
|             return u"{0}".format(self.val) | ||||
|  | ||||
|  | ||||
| class Comments(Base): | ||||
|   | ||||
							
								
								
									
										104
									
								
								cps/helper.py
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								cps/helper.py
									
									
									
									
									
								
							| @@ -134,63 +134,71 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False): | ||||
|         taskMessage=_(u"Registration e-mail for user: %(name)s", name=user_name), | ||||
|         text=txt | ||||
|     )) | ||||
|  | ||||
|     return | ||||
|  | ||||
|  | ||||
| def check_send_to_kindle_without_converter(entry): | ||||
|     bookformats = list() | ||||
|     # no converter - only for mobi and pdf formats | ||||
|     for ele in iter(entry.data): | ||||
|         if ele.uncompressed_size < config.mail_size: | ||||
|             if 'MOBI' in ele.format: | ||||
|                 bookformats.append({'format': 'Mobi', | ||||
|                                     'convert': 0, | ||||
|                                     'text': _('Send %(format)s to Kindle', format='Mobi')}) | ||||
|             if 'PDF' in ele.format: | ||||
|                 bookformats.append({'format': 'Pdf', | ||||
|                                     'convert': 0, | ||||
|                                     'text': _('Send %(format)s to Kindle', format='Pdf')}) | ||||
|             if 'AZW' in ele.format: | ||||
|                 bookformats.append({'format': 'Azw', | ||||
|                                     'convert': 0, | ||||
|                                     'text': _('Send %(format)s to Kindle', format='Azw')}) | ||||
|     return bookformats | ||||
|  | ||||
| def check_send_to_kindle_with_converter(entry): | ||||
|     bookformats = list() | ||||
|     formats = list() | ||||
|     for ele in iter(entry.data): | ||||
|         if ele.uncompressed_size < config.mail_size: | ||||
|             formats.append(ele.format) | ||||
|     if 'MOBI' in formats: | ||||
|         bookformats.append({'format': 'Mobi', | ||||
|                             'convert': 0, | ||||
|                             'text': _('Send %(format)s to Kindle', format='Mobi')}) | ||||
|     if 'AZW' in formats: | ||||
|         bookformats.append({'format': 'Azw', | ||||
|                             'convert': 0, | ||||
|                             'text': _('Send %(format)s to Kindle', format='Azw')}) | ||||
|     if 'PDF' in formats: | ||||
|         bookformats.append({'format': 'Pdf', | ||||
|                             'convert': 0, | ||||
|                             'text': _('Send %(format)s to Kindle', format='Pdf')}) | ||||
|     if 'EPUB' in formats and 'MOBI' not in formats: | ||||
|         bookformats.append({'format': 'Mobi', | ||||
|                             'convert': 1, | ||||
|                             'text': _('Convert %(orig)s to %(format)s and send to Kindle', | ||||
|                                       orig='Epub', | ||||
|                                       format='Mobi')}) | ||||
|     if 'AZW3' in formats and not 'MOBI' in formats: | ||||
|         bookformats.append({'format': 'Mobi', | ||||
|                             'convert': 2, | ||||
|                             'text': _('Convert %(orig)s to %(format)s and send to Kindle', | ||||
|                                       orig='Azw3', | ||||
|                                       format='Mobi')}) | ||||
|     return bookformats | ||||
|  | ||||
|  | ||||
| def check_send_to_kindle(entry): | ||||
|     """ | ||||
|         returns all available book formats for sending to Kindle | ||||
|     """ | ||||
|     if len(entry.data): | ||||
|         bookformats = list() | ||||
|         if not config.config_converterpath: | ||||
|             # no converter - only for mobi and pdf formats | ||||
|             for ele in iter(entry.data): | ||||
|                 if ele.uncompressed_size < config.mail_size: | ||||
|                     if 'MOBI' in ele.format: | ||||
|                         bookformats.append({'format': 'Mobi', | ||||
|                                             'convert': 0, | ||||
|                                             'text': _('Send %(format)s to Kindle', format='Mobi')}) | ||||
|                     if 'PDF' in ele.format: | ||||
|                         bookformats.append({'format': 'Pdf', | ||||
|                                             'convert': 0, | ||||
|                                             'text': _('Send %(format)s to Kindle', format='Pdf')}) | ||||
|                     if 'AZW' in ele.format: | ||||
|                         bookformats.append({'format': 'Azw', | ||||
|                                             'convert': 0, | ||||
|                                             'text': _('Send %(format)s to Kindle', format='Azw')}) | ||||
|             book_formats = check_send_to_kindle_with_converter(entry) | ||||
|         else: | ||||
|             formats = list() | ||||
|             for ele in iter(entry.data): | ||||
|                 if ele.uncompressed_size < config.mail_size: | ||||
|                     formats.append(ele.format) | ||||
|             if 'MOBI' in formats: | ||||
|                 bookformats.append({'format': 'Mobi', | ||||
|                                     'convert': 0, | ||||
|                                     'text': _('Send %(format)s to Kindle', format='Mobi')}) | ||||
|             if 'AZW' in formats: | ||||
|                 bookformats.append({'format': 'Azw', | ||||
|                                     'convert': 0, | ||||
|                                     'text': _('Send %(format)s to Kindle', format='Azw')}) | ||||
|             if 'PDF' in formats: | ||||
|                 bookformats.append({'format': 'Pdf', | ||||
|                                     'convert': 0, | ||||
|                                     'text': _('Send %(format)s to Kindle', format='Pdf')}) | ||||
|             if config.config_converterpath: | ||||
|                 if 'EPUB' in formats and 'MOBI' not in formats: | ||||
|                     bookformats.append({'format': 'Mobi', | ||||
|                                         'convert':1, | ||||
|                                         'text': _('Convert %(orig)s to %(format)s and send to Kindle', | ||||
|                                                   orig='Epub', | ||||
|                                                   format='Mobi')}) | ||||
|                 if 'AZW3' in formats and not 'MOBI' in formats: | ||||
|                     bookformats.append({'format': 'Mobi', | ||||
|                                         'convert': 2, | ||||
|                                         'text': _('Convert %(orig)s to %(format)s and send to Kindle', | ||||
|                                                   orig='Azw3', | ||||
|                                                   format='Mobi')}) | ||||
|         return bookformats | ||||
|             book_formats = check_send_to_kindle_with_converter(entry) | ||||
|         return book_formats | ||||
|     else: | ||||
|         log.error(u'Cannot find book entry %d', entry.id) | ||||
|         return None | ||||
| @@ -742,7 +750,7 @@ def format_runtime(runtime): | ||||
| # helper function to apply localize status information in tasklist entries | ||||
| def render_task_status(tasklist): | ||||
|     renderedtasklist = list() | ||||
|     for num, user, added, task in tasklist: | ||||
|     for __, user, added, task in tasklist: | ||||
|         if user == current_user.nickname or current_user.role_admin(): | ||||
|             ret = {} | ||||
|             if task.start_time: | ||||
|   | ||||
| @@ -72,7 +72,7 @@ def get_valid_language_codes(locale, language_names, remainder=None): | ||||
|     languages = list() | ||||
|     if "" in language_names: | ||||
|         language_names.remove("") | ||||
|     for k, v in get_language_names(locale).items(): | ||||
|     for k, __ in get_language_names(locale).items(): | ||||
|         if k in language_names: | ||||
|             languages.append(k) | ||||
|             language_names.remove(k) | ||||
|   | ||||
							
								
								
									
										238
									
								
								cps/oauth.py
									
									
									
									
									
								
							
							
						
						
									
										238
									
								
								cps/oauth.py
									
									
									
									
									
								
							| @@ -19,7 +19,6 @@ | ||||
| from __future__ import division, print_function, unicode_literals | ||||
| from flask import session | ||||
|  | ||||
|  | ||||
| try: | ||||
|     from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user | ||||
|     from sqlalchemy.orm.exc import NoResultFound | ||||
| @@ -34,134 +33,131 @@ except ImportError: | ||||
|     except ImportError: | ||||
|         pass | ||||
|  | ||||
| try: | ||||
|     class OAuthBackend(SQLAlchemyBackend): | ||||
|         """ | ||||
|         Stores and retrieves OAuth tokens using a relational database through | ||||
|         the `SQLAlchemy`_ ORM. | ||||
|  | ||||
|         .. _SQLAlchemy: https://www.sqlalchemy.org/ | ||||
|         """ | ||||
|         def __init__(self, model, session, provider_id, | ||||
|                      user=None, user_id=None, user_required=None, anon_user=None, | ||||
|                      cache=None): | ||||
|             self.provider_id = provider_id | ||||
|             super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache) | ||||
| class OAuthBackend(SQLAlchemyBackend): | ||||
|     """ | ||||
|     Stores and retrieves OAuth tokens using a relational database through | ||||
|     the `SQLAlchemy`_ ORM. | ||||
|  | ||||
|         def get(self, blueprint, user=None, user_id=None): | ||||
|             if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '': | ||||
|                 return session[self.provider_id + '_oauth_token'] | ||||
|             # check cache | ||||
|             cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id) | ||||
|             token = self.cache.get(cache_key) | ||||
|             if token: | ||||
|                 return token | ||||
|  | ||||
|             # if not cached, make database queries | ||||
|             query = ( | ||||
|                 self.session.query(self.model) | ||||
|                 .filter_by(provider=self.provider_id) | ||||
|             ) | ||||
|             uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) | ||||
|             u = first(_get_real_user(ref, self.anon_user) | ||||
|                       for ref in (user, self.user, blueprint.config.get("user"))) | ||||
|  | ||||
|             use_provider_user_id = False | ||||
|             if self.provider_id + '_oauth_user_id' in session and session[self.provider_id + '_oauth_user_id'] != '': | ||||
|                 query = query.filter_by(provider_user_id=session[self.provider_id + '_oauth_user_id']) | ||||
|                 use_provider_user_id = True | ||||
|  | ||||
|             if self.user_required and not u and not uid and not use_provider_user_id: | ||||
|                 # raise ValueError("Cannot get OAuth token without an associated user") | ||||
|                 return None | ||||
|             # check for user ID | ||||
|             if hasattr(self.model, "user_id") and uid: | ||||
|                 query = query.filter_by(user_id=uid) | ||||
|             # check for user (relationship property) | ||||
|             elif hasattr(self.model, "user") and u: | ||||
|                 query = query.filter_by(user=u) | ||||
|             # if we have the property, but not value, filter by None | ||||
|             elif hasattr(self.model, "user_id"): | ||||
|                 query = query.filter_by(user_id=None) | ||||
|             # run query | ||||
|             try: | ||||
|                 token = query.one().token | ||||
|             except NoResultFound: | ||||
|                 token = None | ||||
|  | ||||
|             # cache the result | ||||
|             self.cache.set(cache_key, token) | ||||
|     .. _SQLAlchemy: https://www.sqlalchemy.org/ | ||||
|     """ | ||||
|     def __init__(self, model, session, provider_id, | ||||
|                  user=None, user_id=None, user_required=None, anon_user=None, | ||||
|                  cache=None): | ||||
|         self.provider_id = provider_id | ||||
|         super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache) | ||||
|  | ||||
|     def get(self, blueprint, user=None, user_id=None): | ||||
|         if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '': | ||||
|             return session[self.provider_id + '_oauth_token'] | ||||
|         # check cache | ||||
|         cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id) | ||||
|         token = self.cache.get(cache_key) | ||||
|         if token: | ||||
|             return token | ||||
|  | ||||
|         def set(self, blueprint, token, user=None, user_id=None): | ||||
|             uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) | ||||
|             u = first(_get_real_user(ref, self.anon_user) | ||||
|                       for ref in (user, self.user, blueprint.config.get("user"))) | ||||
|         # if not cached, make database queries | ||||
|         query = ( | ||||
|             self.session.query(self.model) | ||||
|             .filter_by(provider=self.provider_id) | ||||
|         ) | ||||
|         uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) | ||||
|         u = first(_get_real_user(ref, self.anon_user) | ||||
|                   for ref in (user, self.user, blueprint.config.get("user"))) | ||||
|  | ||||
|             if self.user_required and not u and not uid: | ||||
|                 raise ValueError("Cannot set OAuth token without an associated user") | ||||
|         use_provider_user_id = False | ||||
|         if self.provider_id + '_oauth_user_id' in session and session[self.provider_id + '_oauth_user_id'] != '': | ||||
|             query = query.filter_by(provider_user_id=session[self.provider_id + '_oauth_user_id']) | ||||
|             use_provider_user_id = True | ||||
|  | ||||
|             # if there was an existing model, delete it | ||||
|             existing_query = ( | ||||
|                 self.session.query(self.model) | ||||
|                 .filter_by(provider=self.provider_id) | ||||
|             ) | ||||
|             # check for user ID | ||||
|             has_user_id = hasattr(self.model, "user_id") | ||||
|             if has_user_id and uid: | ||||
|                 existing_query = existing_query.filter_by(user_id=uid) | ||||
|             # check for user (relationship property) | ||||
|             has_user = hasattr(self.model, "user") | ||||
|             if has_user and u: | ||||
|                 existing_query = existing_query.filter_by(user=u) | ||||
|             # queue up delete query -- won't be run until commit() | ||||
|             existing_query.delete() | ||||
|             # create a new model for this token | ||||
|             kwargs = { | ||||
|                 "provider": self.provider_id, | ||||
|                 "token": token, | ||||
|             } | ||||
|             if has_user_id and uid: | ||||
|                 kwargs["user_id"] = uid | ||||
|             if has_user and u: | ||||
|                 kwargs["user"] = u | ||||
|             self.session.add(self.model(**kwargs)) | ||||
|             # commit to delete and add simultaneously | ||||
|             self.session.commit() | ||||
|             # invalidate cache | ||||
|             self.cache.delete(self.make_cache_key( | ||||
|                 blueprint=blueprint, user=user, user_id=user_id | ||||
|             )) | ||||
|         if self.user_required and not u and not uid and not use_provider_user_id: | ||||
|             # raise ValueError("Cannot get OAuth token without an associated user") | ||||
|             return None | ||||
|         # check for user ID | ||||
|         if hasattr(self.model, "user_id") and uid: | ||||
|             query = query.filter_by(user_id=uid) | ||||
|         # check for user (relationship property) | ||||
|         elif hasattr(self.model, "user") and u: | ||||
|             query = query.filter_by(user=u) | ||||
|         # if we have the property, but not value, filter by None | ||||
|         elif hasattr(self.model, "user_id"): | ||||
|             query = query.filter_by(user_id=None) | ||||
|         # run query | ||||
|         try: | ||||
|             token = query.one().token | ||||
|         except NoResultFound: | ||||
|             token = None | ||||
|  | ||||
|         def delete(self, blueprint, user=None, user_id=None): | ||||
|             query = ( | ||||
|                 self.session.query(self.model) | ||||
|                 .filter_by(provider=self.provider_id) | ||||
|             ) | ||||
|             uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) | ||||
|             u = first(_get_real_user(ref, self.anon_user) | ||||
|                       for ref in (user, self.user, blueprint.config.get("user"))) | ||||
|         # cache the result | ||||
|         self.cache.set(cache_key, token) | ||||
|  | ||||
|             if self.user_required and not u and not uid: | ||||
|                 raise ValueError("Cannot delete OAuth token without an associated user") | ||||
|         return token | ||||
|  | ||||
|             # check for user ID | ||||
|             if hasattr(self.model, "user_id") and uid: | ||||
|                 query = query.filter_by(user_id=uid) | ||||
|             # check for user (relationship property) | ||||
|             elif hasattr(self.model, "user") and u: | ||||
|                 query = query.filter_by(user=u) | ||||
|             # if we have the property, but not value, filter by None | ||||
|             elif hasattr(self.model, "user_id"): | ||||
|                 query = query.filter_by(user_id=None) | ||||
|             # run query | ||||
|             query.delete() | ||||
|             self.session.commit() | ||||
|             # invalidate cache | ||||
|             self.cache.delete(self.make_cache_key( | ||||
|                 blueprint=blueprint, user=user, user_id=user_id, | ||||
|             )) | ||||
|     def set(self, blueprint, token, user=None, user_id=None): | ||||
|         uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) | ||||
|         u = first(_get_real_user(ref, self.anon_user) | ||||
|                   for ref in (user, self.user, blueprint.config.get("user"))) | ||||
|  | ||||
| except Exception: | ||||
|     pass | ||||
|         if self.user_required and not u and not uid: | ||||
|             raise ValueError("Cannot set OAuth token without an associated user") | ||||
|  | ||||
|         # if there was an existing model, delete it | ||||
|         existing_query = ( | ||||
|             self.session.query(self.model) | ||||
|             .filter_by(provider=self.provider_id) | ||||
|         ) | ||||
|         # check for user ID | ||||
|         has_user_id = hasattr(self.model, "user_id") | ||||
|         if has_user_id and uid: | ||||
|             existing_query = existing_query.filter_by(user_id=uid) | ||||
|         # check for user (relationship property) | ||||
|         has_user = hasattr(self.model, "user") | ||||
|         if has_user and u: | ||||
|             existing_query = existing_query.filter_by(user=u) | ||||
|         # queue up delete query -- won't be run until commit() | ||||
|         existing_query.delete() | ||||
|         # create a new model for this token | ||||
|         kwargs = { | ||||
|             "provider": self.provider_id, | ||||
|             "token": token, | ||||
|         } | ||||
|         if has_user_id and uid: | ||||
|             kwargs["user_id"] = uid | ||||
|         if has_user and u: | ||||
|             kwargs["user"] = u | ||||
|         self.session.add(self.model(**kwargs)) | ||||
|         # commit to delete and add simultaneously | ||||
|         self.session.commit() | ||||
|         # invalidate cache | ||||
|         self.cache.delete(self.make_cache_key( | ||||
|             blueprint=blueprint, user=user, user_id=user_id | ||||
|         )) | ||||
|  | ||||
|     def delete(self, blueprint, user=None, user_id=None): | ||||
|         query = ( | ||||
|             self.session.query(self.model) | ||||
|             .filter_by(provider=self.provider_id) | ||||
|         ) | ||||
|         uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) | ||||
|         u = first(_get_real_user(ref, self.anon_user) | ||||
|                   for ref in (user, self.user, blueprint.config.get("user"))) | ||||
|  | ||||
|         if self.user_required and not u and not uid: | ||||
|             raise ValueError("Cannot delete OAuth token without an associated user") | ||||
|  | ||||
|         # check for user ID | ||||
|         if hasattr(self.model, "user_id") and uid: | ||||
|             query = query.filter_by(user_id=uid) | ||||
|         # check for user (relationship property) | ||||
|         elif hasattr(self.model, "user") and u: | ||||
|             query = query.filter_by(user=u) | ||||
|         # if we have the property, but not value, filter by None | ||||
|         elif hasattr(self.model, "user_id"): | ||||
|             query = query.filter_by(user_id=None) | ||||
|         # run query | ||||
|         query.delete() | ||||
|         self.session.commit() | ||||
|         # invalidate cache | ||||
|         self.cache.delete(self.make_cache_key( | ||||
|             blueprint=blueprint, user=user, user_id=user_id, | ||||
|         )) | ||||
|   | ||||
| @@ -35,7 +35,10 @@ from sqlalchemy.orm.exc import NoResultFound | ||||
|  | ||||
| from . import constants, logger, config, app, ub | ||||
|  | ||||
| from .oauth import OAuthBackend, backend_resultcode | ||||
| try: | ||||
|     from .oauth import OAuthBackend, backend_resultcode | ||||
| except NameError: | ||||
|     pass | ||||
|  | ||||
|  | ||||
| oauth_check = {} | ||||
|   | ||||
| @@ -137,7 +137,7 @@ class WebServer(object): | ||||
|  | ||||
|         return sock, _readable_listen_address(*address) | ||||
|  | ||||
|  | ||||
|     @staticmethod | ||||
|     def _get_args_for_reloading(self): | ||||
|         """Determine how the script was executed, and return the args needed | ||||
|         to execute it again in a new process. | ||||
|   | ||||
| @@ -64,7 +64,7 @@ class SyncToken: | ||||
|         books_last_modified: Datetime representing the last modified book that the device knows about. | ||||
|     """ | ||||
|  | ||||
|     SYNC_TOKEN_HEADER = "x-kobo-synctoken" | ||||
|     SYNC_TOKEN_HEADER = "x-kobo-synctoken"  # nosec | ||||
|     VERSION = "1-1-0" | ||||
|     LAST_MODIFIED_ADDED_VERSION = "1-1-0" | ||||
|     MIN_VERSION = "1-0-0" | ||||
| @@ -91,7 +91,7 @@ class SyncToken: | ||||
|  | ||||
|     def __init__( | ||||
|         self, | ||||
|         raw_kobo_store_token="", | ||||
|         raw_kobo_store_token="",  # nosec | ||||
|         books_last_created=datetime.min, | ||||
|         books_last_modified=datetime.min, | ||||
|         archive_last_modified=datetime.min, | ||||
| @@ -110,7 +110,7 @@ class SyncToken: | ||||
|     @staticmethod | ||||
|     def from_headers(headers): | ||||
|         sync_token_header = headers.get(SyncToken.SYNC_TOKEN_HEADER, "") | ||||
|         if sync_token_header == "": | ||||
|         if sync_token_header == "":  # nosec | ||||
|             return SyncToken() | ||||
|  | ||||
|         # On the first sync from a Kobo device, we may receive the SyncToken | ||||
|   | ||||
| @@ -1,22 +1,24 @@ | ||||
| body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{ | ||||
|     display: none; | ||||
| } | ||||
| .cover .badge{ | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     color: #fff; | ||||
|     background-color: #cc7b19; | ||||
|     border-radius: 0; | ||||
|     padding: 0 8px; | ||||
|     box-shadow: 0 0 4px rgba(0,0,0,.6); | ||||
|     line-height: 24px; | ||||
| } | ||||
| .cover{ | ||||
|     box-shadow: 0 0 4px rgba(0,0,0,.6); | ||||
| body.serieslist.grid-view div.container-fluid > div > div.col-sm-10:before{ | ||||
|   display: none; | ||||
| } | ||||
|  | ||||
| .cover .read{ | ||||
|     padding: 0 0px; | ||||
|     line-height: 15px; | ||||
| .cover .badge{ | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   color: #fff; | ||||
|   background-color: #cc7b19; | ||||
|   border-radius: 0; | ||||
|   padding: 0 8px; | ||||
|   box-shadow: 0 0 4px rgba(0, 0, 0, .6); | ||||
|   line-height: 24px; | ||||
| } | ||||
|  | ||||
| .cover { | ||||
|   box-shadow: 0 0 4px rgba(0, 0, 0, .6); | ||||
| } | ||||
|  | ||||
| .cover .read { | ||||
|   padding: 0px 0px; | ||||
|   line-height: 15px; | ||||
| } | ||||
|   | ||||
| @@ -33,7 +33,6 @@ body { | ||||
|   position: relative; | ||||
|   cursor: pointer; | ||||
|   padding: 4px; | ||||
|  | ||||
|   transition: all 0.2s ease; | ||||
| } | ||||
|  | ||||
| @@ -45,7 +44,7 @@ body { | ||||
|  | ||||
| #sidebar a.active, | ||||
| #sidebar a.active img + span { | ||||
|   background-color: #45B29D; | ||||
|   background-color: #45b29d; | ||||
| } | ||||
|  | ||||
| #sidebar li img { | ||||
| @@ -99,7 +98,7 @@ body { | ||||
|   background-color: #ccc; | ||||
| } | ||||
|  | ||||
| #progress .bar-read  { | ||||
| #progress .bar-read { | ||||
|   color: #fff; | ||||
|   background-color: #45b29d; | ||||
| } | ||||
|   | ||||
| @@ -35,7 +35,6 @@ body { | ||||
|   height: 8%; | ||||
|   min-height: 20px; | ||||
|   padding: 10px; | ||||
|   /* margin: 0 50px 0 50px; */ | ||||
|   position: relative; | ||||
|   color: #4f4f4f; | ||||
|   font-weight: 100; | ||||
| @@ -114,7 +113,7 @@ body { | ||||
|   top: 50%; | ||||
|   margin-top: -192px; | ||||
|   font-size: 64px; | ||||
|   color: #E2E2E2; | ||||
|   color: #e2e2e2; | ||||
|   font-family: arial, sans-serif; | ||||
|   font-weight: bold; | ||||
|   cursor: pointer; | ||||
| @@ -148,12 +147,6 @@ body { | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| #sidebar.open { | ||||
|   /* left: 0; */ | ||||
|   /* -webkit-transform: translate(0, 0); | ||||
|   -moz-transform: translate(0, 0); */ | ||||
| } | ||||
|  | ||||
| #main.closed { | ||||
|   /* left: 300px; */ | ||||
|   -webkit-transform: translate(300px, 0); | ||||
| @@ -238,7 +231,7 @@ input:-moz-placeholder { color: #454545; } | ||||
|   left: 50%; | ||||
|   margin-left: -1px; | ||||
|   top: 10%; | ||||
|   opacity: .15; | ||||
|   opacity: 0.15; | ||||
|   box-shadow: -2px 0 15px rgba(0, 0, 0, 1); | ||||
|   display: none; | ||||
| } | ||||
| @@ -291,7 +284,7 @@ input:-moz-placeholder { color: #454545; } | ||||
|  | ||||
| #tocView li, | ||||
| #bookmarksView li { | ||||
|   margin-bottom:10px; | ||||
|   margin-bottom: 10px; | ||||
|   width: 225px; | ||||
|   font-family: Georgia, "Times New Roman", Times, serif; | ||||
|   list-style: none; | ||||
| @@ -299,8 +292,7 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| #tocView li:active, | ||||
| #tocView li.currentChapter | ||||
| { | ||||
| #tocView li.currentChapter { | ||||
|   list-style: none; | ||||
| } | ||||
|  | ||||
| @@ -319,7 +311,7 @@ input:-moz-placeholder { color: #454545; } | ||||
|  | ||||
| .list_item.currentChapter > a, | ||||
| .list_item a:hover { | ||||
|   color: #f1f1f1 | ||||
|   color: #f1f1f1; | ||||
| } | ||||
|  | ||||
| /* #tocView li.openChapter > a, */ | ||||
| @@ -328,7 +320,7 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| .list_item ul { | ||||
|   padding-left:10px; | ||||
|   padding-left: 10px; | ||||
|   margin-top: 8px; | ||||
|   display: none; | ||||
| } | ||||
| @@ -414,7 +406,7 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| #notes { | ||||
|  padding: 0 0 0 34px; | ||||
|   padding: 0 0 0 34px; | ||||
| } | ||||
|  | ||||
| #notes li { | ||||
| @@ -449,8 +441,9 @@ input:-moz-placeholder { color: #454545; } | ||||
|   border-radius: 5px; | ||||
| } | ||||
|  | ||||
| #note-text[disabled], #note-text[disabled="disabled"]{ | ||||
|     opacity: 0.5; | ||||
| #note-text[disabled], | ||||
| #note-text[disabled="disabled"]{ | ||||
|   opacity: 0.5; | ||||
| } | ||||
|  | ||||
| #note-anchor { | ||||
| @@ -478,26 +471,24 @@ input:-moz-placeholder { color: #454545; } | ||||
|   color: #f1f1f1; | ||||
| } | ||||
|  | ||||
| #settingsPanel .xsmall {  font-size: x-small; } | ||||
| #settingsPanel .small {  font-size: small; } | ||||
| #settingsPanel .medium {  font-size: medium; } | ||||
| #settingsPanel .large {  font-size: large; } | ||||
| #settingsPanel .xlarge {  font-size: x-large; } | ||||
| #settingsPanel .xsmall { font-size: x-small; } | ||||
| #settingsPanel .small { font-size: small; } | ||||
| #settingsPanel .medium { font-size: medium; } | ||||
| #settingsPanel .large { font-size: large; } | ||||
| #settingsPanel .xlarge { font-size: x-large; } | ||||
|  | ||||
| .highlight { background-color: yellow } | ||||
| .highlight { background-color: yellow; } | ||||
|  | ||||
| .modal { | ||||
|   position: fixed; | ||||
|   top: 50%; | ||||
|   left: 50%; | ||||
|   // width: 50%; | ||||
|   width: 630px; | ||||
|   height: auto; | ||||
|   z-index: 2000; | ||||
|   visibility: hidden; | ||||
|   margin-left: -320px; | ||||
|   margin-top: -160px; | ||||
|  | ||||
| } | ||||
|  | ||||
| .overlay { | ||||
| @@ -516,12 +507,12 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| .md-show { | ||||
|     visibility: visible; | ||||
|   visibility: visible; | ||||
| } | ||||
|  | ||||
| .md-show ~ .overlay { | ||||
|     opacity: 1; | ||||
|     visibility: visible; | ||||
|   opacity: 1; | ||||
|   visibility: visible; | ||||
| } | ||||
|  | ||||
| /* Content styles */ | ||||
| @@ -593,7 +584,6 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| .md-content > .closer { | ||||
|   //font-size: 18px; | ||||
|   position: absolute; | ||||
|   right: 0; | ||||
|   top: 0; | ||||
| @@ -602,7 +592,7 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| @media only screen and (max-width: 1040px) and (orientation: portrait) { | ||||
|   #viewer{ | ||||
|   #viewer { | ||||
|     width: 80%; | ||||
|     margin-left: 10%; | ||||
|   } | ||||
| @@ -614,7 +604,7 @@ input:-moz-placeholder { color: #454545; } | ||||
| } | ||||
|  | ||||
| @media only screen and (max-width: 900px) { | ||||
|   #viewer{ | ||||
|   #viewer { | ||||
|     width: 60%; | ||||
|     margin-left: 20%; | ||||
|   } | ||||
| @@ -653,9 +643,9 @@ input:-moz-placeholder { color: #454545; } | ||||
|     -webkit-transform: translate(0, 0); | ||||
|     -moz-transform: translate(0, 0); | ||||
|     -ms-transform: translate(0, 0); | ||||
|     -webkit-transition: -webkit-transform .3s; | ||||
|     -moz-transition: -moz-transform .3s; | ||||
|     transition: -moz-transform .3s; | ||||
|     -webkit-transition: -webkit-transform 0.3s; | ||||
|     -moz-transition: -moz-transform 0.3s; | ||||
|     transition: -moz-transform 0.3s; | ||||
|   } | ||||
|  | ||||
|   #main.closed { | ||||
| @@ -681,12 +671,11 @@ input:-moz-placeholder { color: #454545; } | ||||
|     font-size: 12px; | ||||
|   } | ||||
|  | ||||
|   #tocView > ul{ | ||||
|   #tocView > ul { | ||||
|     padding-left: 10px; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* For iPad portrait layouts only */ | ||||
| @media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: portrait) { | ||||
|     #viewer iframe { | ||||
| @@ -694,20 +683,13 @@ input:-moz-placeholder { color: #454545; } | ||||
|         height: 740px; | ||||
|     } | ||||
| } | ||||
|  /*For iPad landscape layouts only *//* | ||||
| @media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) { | ||||
|     #viewer iframe { | ||||
|         width: 460px; | ||||
|         height: 415px; | ||||
|     } | ||||
| }*/ | ||||
|  | ||||
| @media only screen | ||||
| and (min-device-width : 768px) | ||||
| and (max-device-width : 1024px) | ||||
| and (orientation : landscape) | ||||
| /*and (-webkit-min-device-pixel-ratio: 2)*/ { | ||||
|   #viewer{ | ||||
|   #viewer { | ||||
|     width: 80%; | ||||
|     margin-left: 10%; | ||||
|   } | ||||
| @@ -720,8 +702,8 @@ and (orientation : landscape) | ||||
|  /*For iPad landscape layouts only */ | ||||
| @media only screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation: landscape) { | ||||
|     #viewer iframe { | ||||
|         width: 960px; | ||||
|         height: 515px; | ||||
|       width: 960px; | ||||
|       height: 515px; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -764,8 +746,8 @@ and (orientation : landscape) | ||||
| /* For iPhone landscape layouts only */ | ||||
| @media only screen and (max-device-width: 374px) and (orientation: landscape) { | ||||
|     #viewer iframe { | ||||
|         width: 256px; | ||||
|         height: 124px; | ||||
|       width: 256px; | ||||
|       height: 124px; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
|  | ||||
| .tooltip.bottom .tooltip-inner { | ||||
|   font-size: 13px; | ||||
|   font-family: Open Sans Semibold,Helvetica Neue,Helvetica,Arial,sans-serif; | ||||
|   font-family: Open Sans Semibold, Helvetica Neue, Helvetica, Arial, sans-serif; | ||||
|   -webkit-font-smoothing: antialiased; | ||||
|   -moz-osx-font-smoothing: grayscale; | ||||
|   padding: 3px 10px; | ||||
| @@ -48,10 +48,14 @@ body { | ||||
|  | ||||
| body h2 { | ||||
|   font-weight: normal; | ||||
|   color:#444; | ||||
|   color: #444; | ||||
| } | ||||
|  | ||||
| a, .danger, .book-remove, .user-remove, .editable-empty, .editable-empty:hover { color: #45b29d; } | ||||
| a, | ||||
| .danger, | ||||
| .book-remove, | ||||
| .editable-empty, | ||||
| .editable-empty:hover { color: #45b29d; } | ||||
|  | ||||
| .book-remove:hover { color: #23527c; } | ||||
|  | ||||
| @@ -60,15 +64,17 @@ a, .danger, .book-remove, .user-remove, .editable-empty, .editable-empty:hover { | ||||
| .btn-default a { color: #444; } | ||||
|  | ||||
| .btn-default a:hover { | ||||
|     color: #45b29d; | ||||
|     text-decoration: None; | ||||
|   color: #45b29d; | ||||
|   text-decoration: None; | ||||
| } | ||||
|  | ||||
| .btn-default:hover { | ||||
|     color: #45b29d; | ||||
|   color: #45b29d; | ||||
| } | ||||
|  | ||||
| .editable-click, a.editable-click, a.editable-click:hover  { border-bottom: None; } | ||||
| .editable-click, | ||||
| a.editable-click, | ||||
| a.editable-click:hover { border-bottom: None; } | ||||
|  | ||||
| .navigation .nav-head { | ||||
|   text-transform: uppercase; | ||||
| @@ -121,9 +127,10 @@ a, .danger, .book-remove, .user-remove, .editable-empty, .editable-empty:hover { | ||||
|   max-height: 100%; | ||||
| } | ||||
|  | ||||
| .container-fluid .discover{ margin-bottom: 50px; } | ||||
| .container-fluid .discover { margin-bottom: 50px; } | ||||
| .container-fluid .new-books { border-top: 1px solid #ccc; } | ||||
| .container-fluid .new-books h2 { margin: 50px 0 0 0; } | ||||
|  | ||||
| .container-fluid .book { | ||||
|   margin-top: 20px; | ||||
|   display: flex; | ||||
| @@ -176,9 +183,10 @@ a, .danger, .book-remove, .user-remove, .editable-empty, .editable-empty:hover { | ||||
| .container-fluid .book .meta .rating { margin-top: 5px; } | ||||
| .rating .glyphicon-star-empty { color: #444; } | ||||
| .rating .glyphicon-star.good { color: #444; } | ||||
| .rating-clear .glyphicon-remove { color: #333  } | ||||
| .rating-clear .glyphicon-remove { color: #333; } | ||||
|  | ||||
| .container-fluid .author .author-hidden, .container-fluid .author .author-hidden-divider { display: none; } | ||||
| .container-fluid .author .author-hidden, | ||||
| .container-fluid .author .author-hidden-divider { display: none; } | ||||
|  | ||||
| .navbar-brand { | ||||
|   font-family: 'Grand Hotel', cursive; | ||||
| @@ -192,7 +200,7 @@ a, .danger, .book-remove, .user-remove, .editable-empty, .editable-empty:hover { | ||||
|   border-top: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| .more-stuff>li { margin-bottom: 10px; } | ||||
| .more-stuff > li { margin-bottom: 10px; } | ||||
| .navbar-collapse.in .navbar-nav { margin: 0; } | ||||
|  | ||||
| span.glyphicon.glyphicon-tags { | ||||
| @@ -213,19 +221,20 @@ span.glyphicon.glyphicon-tags { | ||||
|   box-shadow: 0 5px 8px -6px #777; | ||||
| } | ||||
|  | ||||
| .navbar-default .navbar-toggle .icon-bar {background-color: #000; } | ||||
| .navbar-default .navbar-toggle {border-color: #000; } | ||||
| .navbar-default .navbar-toggle .icon-bar { background-color: #000; } | ||||
| .navbar-default .navbar-toggle { border-color: #000; } | ||||
| .cover { margin-bottom: 10px; } | ||||
|  | ||||
| .cover .badge{ | ||||
|    position: absolute; | ||||
|    top: 2px; | ||||
|    left: 2px; | ||||
|    color: #000; | ||||
|    border-radius: 10px; | ||||
|    background-color: #fff; | ||||
| .cover .badge { | ||||
|   position: absolute; | ||||
|   top: 2px; | ||||
|   left: 2px; | ||||
|   color: #000; | ||||
|   border-radius: 10px; | ||||
|   background-color: #fff; | ||||
| } | ||||
| .cover .read{ | ||||
|  | ||||
| .cover .read { | ||||
|   left: auto; | ||||
|   right: 2px; | ||||
|   width: 17px; | ||||
| @@ -233,14 +242,17 @@ span.glyphicon.glyphicon-tags { | ||||
|   display: inline-block; | ||||
|   padding: 2px; | ||||
| } | ||||
| .cover-height { max-height: 100px;} | ||||
| .cover-height { max-height: 100px; } | ||||
|  | ||||
| .col-sm-2 a .cover-small { | ||||
|   margin: 5px; | ||||
|   max-height: 200px; | ||||
| } | ||||
|  | ||||
| .btn-file {position: relative; overflow: hidden;} | ||||
| .btn-file { | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| .btn-file input[type=file] { | ||||
|   position: absolute; | ||||
| @@ -258,24 +270,62 @@ span.glyphicon.glyphicon-tags { | ||||
|   display: block; | ||||
| } | ||||
|  | ||||
| .btn-toolbar .btn,.discover .btn { margin-bottom: 5px; } | ||||
| .button-link {color: #fff; } | ||||
| .btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open .dropdown-toggle.btn-primary{ background-color: #1C5484; } | ||||
| .btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #89B9E2; } | ||||
| .btn-toolbar>.btn+.btn, .btn-toolbar>.btn-group+.btn, .btn-toolbar>.btn+.btn-group, .btn-toolbar>.btn-group+.btn-group { margin-left: 0; } | ||||
| .panel-body {background-color: #f5f5f5; } | ||||
| .spinner {margin: 0 41%; } | ||||
| .spinner2 {margin: 0 41%; } | ||||
| .intend-form { margin-left:20px; } | ||||
| table .bg-dark-danger {background-color: #d9534f; color: #fff; } | ||||
| table .bg-dark-danger a {color: #fff; } | ||||
| table .bg-dark-danger:hover {background-color: #c9302c; } | ||||
| table .bg-primary:hover {background-color: #1C5484; } | ||||
| table .bg-primary a {color: #fff; } | ||||
| .block-label {display: block;} | ||||
| .fake-input {position: absolute; pointer-events: none; top: 0; } | ||||
| .btn-toolbar .btn, | ||||
| .discover .btn { margin-bottom: 5px; } | ||||
| .button-link { color: #fff; } | ||||
|  | ||||
| input.pill { position: absolute; opacity: 0; } | ||||
| .btn-primary:hover, | ||||
| .btn-primary:focus, | ||||
| .btn-primary:active, | ||||
| .btn-primary.active, | ||||
| .open .dropdown-toggle.btn-primary { background-color: #1c5484; } | ||||
|  | ||||
| .btn-primary.disabled, | ||||
| .btn-primary[disabled], | ||||
| fieldset[disabled] .btn-primary, | ||||
| .btn-primary.disabled:hover, | ||||
| .btn-primary[disabled]:hover, | ||||
| fieldset[disabled] .btn-primary:hover, | ||||
| .btn-primary.disabled:focus, | ||||
| .btn-primary[disabled]:focus, | ||||
| fieldset[disabled] .btn-primary:focus, | ||||
| .btn-primary.disabled:active, | ||||
| .btn-primary[disabled]:active, | ||||
| fieldset[disabled] .btn-primary:active, | ||||
| .btn-primary.disabled.active, | ||||
| .btn-primary[disabled].active, | ||||
| fieldset[disabled] .btn-primary.active { background-color: #89b9e2; } | ||||
|  | ||||
| .btn-toolbar > .btn + .btn, | ||||
| .btn-toolbar > .btn-group + .btn, | ||||
| .btn-toolbar > .btn + .btn-group, | ||||
| .btn-toolbar > .btn-group + .btn-group { margin-left: 0; } | ||||
|  | ||||
| .panel-body { background-color: #f5f5f5; } | ||||
| .spinner { margin: 0 41%; } | ||||
| .spinner2 { margin: 0 41%; } | ||||
| .intend-form { margin-left: 20px; } | ||||
|  | ||||
| table .bg-dark-danger { | ||||
|   background-color: #d9534f; | ||||
|   color: #fff; | ||||
| } | ||||
| table .bg-dark-danger a { color: #fff; } | ||||
| table .bg-dark-danger:hover { background-color: #c9302c; } | ||||
| table .bg-primary:hover { background-color: #1c5484; } | ||||
| table .bg-primary a { color: #fff; } | ||||
| .block-label { display: block; } | ||||
|  | ||||
| .fake-input { | ||||
|   position: absolute; | ||||
|   pointer-events: none; | ||||
|   top: 0; | ||||
| } | ||||
|  | ||||
| input.pill { | ||||
|   position: absolute; | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| input.pill + label { | ||||
|   border: 2px solid #45b29d; | ||||
| @@ -298,11 +348,24 @@ input.pill:checked + label { | ||||
| input.pill:not(:checked) + label .glyphicon { display: none; } | ||||
|  | ||||
| .author-bio img { margin: 0 1em 1em 0; } | ||||
| .author-link { display: inline-block; margin-top: 10px; width: 100px; } | ||||
| .author-link img { display: block; height: 100%; } | ||||
| #remove-from-shelves .btn, #shelf-action-errors { margin-left: 5px; } | ||||
|  | ||||
| .tags_click, .serie_click, .language_click { margin-right: 5px; } | ||||
| .author-link { | ||||
|   display: inline-block; | ||||
|   margin-top: 10px; | ||||
|   width: 100px; | ||||
| } | ||||
|  | ||||
| .author-link img { | ||||
|   display: block; | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| #remove-from-shelves .btn, | ||||
| #shelf-action-errors { margin-left: 5px; } | ||||
|  | ||||
| .tags_click, | ||||
| .serie_click, | ||||
| .language_click { margin-right: 5px; } | ||||
|  | ||||
| #meta-info { | ||||
|   height: 600px; | ||||
| @@ -325,11 +388,11 @@ input.pill:not(:checked) + label .glyphicon { display: none; } | ||||
| #btn-upload-cover { display: none; } | ||||
| .panel-title > a { text-decoration: none; } | ||||
| .editable-buttons { | ||||
|   display:inline-block; | ||||
|   display: inline-block; | ||||
|   margin-left: 7px; | ||||
| } | ||||
|  | ||||
| .editable-input { display:inline-block; } | ||||
| .editable-input { display: inline-block; } | ||||
|  | ||||
| .editable-cancel { | ||||
|   margin-bottom: 0 !important; | ||||
|   | ||||
| @@ -677,7 +677,7 @@ $(".navbar-collapse.collapse.in").before('<div class="sidebar-backdrop"></div>') | ||||
| // Get rid of leading white space | ||||
| recentlyAdded = $("#nav_new a:contains('Recently')").text().trim(); | ||||
| $("#nav_new a:contains('Recently')").contents().filter(function () { | ||||
|     return this.nodeType == 3 | ||||
|     return this.nodeType === 3 | ||||
| }).each(function () { | ||||
|     this.textContent = this.textContent.replace(" Recently Added", recentlyAdded); | ||||
| }); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| /** | ||||
|  * Created by SpeedProg on 05.04.2015. | ||||
|  */ | ||||
| /* global Bloodhound, language, Modernizr, tinymce */ | ||||
| /* global Bloodhound, language, Modernizr, tinymce, getPath */ | ||||
|  | ||||
| if ($("#description").length) { | ||||
|     tinymce.init({ | ||||
| @@ -250,14 +250,14 @@ promisePublishers.done(function() { | ||||
| }); | ||||
|  | ||||
| $("#search").on("change input.typeahead:selected", function(event) { | ||||
|     if (event.target.type == "search" && event.target.tagName == "INPUT") { | ||||
|     if (event.target.type === "search" && event.target.tagName === "INPUT") { | ||||
|         return; | ||||
|     } | ||||
|     var form = $("form").serialize(); | ||||
|     $.getJSON( getPath() + "/get_matching_tags", form, function( data ) { | ||||
|         $(".tags_click").each(function() { | ||||
|             if ($.inArray(parseInt($(this).val(), 10), data.tags) === -1) { | ||||
|                 if(!$(this).prop("selected")) { | ||||
|                 if (!$(this).prop("selected")) { | ||||
|                     $(this).prop("disabled", true); | ||||
|                 } | ||||
|             } else { | ||||
| @@ -265,10 +265,10 @@ $("#search").on("change input.typeahead:selected", function(event) { | ||||
|             } | ||||
|         }); | ||||
|         $("#include_tag option:selected").each(function () { | ||||
|             $("#exclude_tag").find("[value="+$(this).val()+"]").prop("disabled", true); | ||||
|             $("#exclude_tag").find("[value="+$(this).val() + "]").prop("disabled", true); | ||||
|         }); | ||||
|         $('#include_tag').selectpicker("refresh"); | ||||
|         $('#exclude_tag').selectpicker("refresh"); | ||||
|         $("#include_tag").selectpicker("refresh"); | ||||
|         $("#exclude_tag").selectpicker("refresh"); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -88,7 +88,7 @@ $("#desc").click(function() { | ||||
|     // Find count of middle element | ||||
|     var count = $(".row:visible").length; | ||||
|     if (count > 20) { | ||||
|         middle = parseInt(count / 2) + (count % 2); | ||||
|         middle = parseInt(count / 2, 10) + (count % 2); | ||||
|  | ||||
|         //var middle = parseInt(count / 2) + (count % 2); | ||||
|         // search for the middle of all visible elements | ||||
| @@ -135,7 +135,7 @@ $("#asc").click(function() { | ||||
|     // Find count of middle element | ||||
|     var count = $(".row:visible").length; | ||||
|     if (count > 20) { | ||||
|         var middle = parseInt(count / 2) + (count % 2); | ||||
|         var middle = parseInt(count / 2, 10) + (count % 2); | ||||
|  | ||||
|         //var middle = parseInt(count / 2) + (count % 2); | ||||
|         // search for the middle of all visible elements | ||||
|   | ||||
| @@ -146,6 +146,9 @@ kthoom.ImageFile = function(file) { | ||||
|         case "jpeg": | ||||
|             this.mimeType = "image/jpeg"; | ||||
|             break; | ||||
|         case "png": | ||||
|             this.mimeType = "image/png"; | ||||
|             break; | ||||
|         case "gif": | ||||
|             this.mimeType = "image/gif"; | ||||
|             break; | ||||
|   | ||||
| @@ -38,10 +38,10 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () { | ||||
| $(document).on("change", "select[data-control]", function() { | ||||
|     var $this = $(this); | ||||
|     var name = $this.data("control"); | ||||
|     var showOrHide = parseInt($this.val()); | ||||
|     var showOrHide = parseInt($this.val(), 10); | ||||
|     // var showOrHideLast = $("#" + name + " option:last").val() | ||||
|     for (var i = 0; i < $(this)[0].length; i++) { | ||||
|         var element = parseInt($(this)[0][i].value); | ||||
|         var element = parseInt($(this)[0][i].value, 10); | ||||
|         if (element === showOrHide) { | ||||
|             $("[data-related^=" + name + "][data-related*=-" + element + "]").show(); | ||||
|         } else { | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
|  */ | ||||
|  | ||||
| /* exported TableActions, RestrictionActions, EbookActions, responseHandler */ | ||||
| /* global getPath, ConfirmDialog */ | ||||
|  | ||||
| var selections = []; | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,6 @@ class TaskUpload(CalibreTask): | ||||
|  | ||||
|     def run(self, worker_thread): | ||||
|         """Upload task doesn't have anything to do, it's simply a way to add information to the task list""" | ||||
|         pass | ||||
|  | ||||
|     @property | ||||
|     def name(self): | ||||
|   | ||||
							
								
								
									
										119
									
								
								cps/ub.py
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								cps/ub.py
									
									
									
									
									
								
							| @@ -138,15 +138,15 @@ class UserBase: | ||||
|         mct = self.allowed_column_value or "" | ||||
|         return [t.strip() for t in mct.split(",")] | ||||
|  | ||||
|     def get_view_property(self, page, property): | ||||
|     def get_view_property(self, page, prop): | ||||
|         if not self.view_settings.get(page): | ||||
|             return None | ||||
|         return self.view_settings[page].get(property) | ||||
|         return self.view_settings[page].get(prop) | ||||
|  | ||||
|     def set_view_property(self, page, property, value): | ||||
|     def set_view_property(self, page, prop, value): | ||||
|         if not self.view_settings.get(page): | ||||
|             self.view_settings[page] = dict() | ||||
|         self.view_settings[page][property] = value | ||||
|         self.view_settings[page][prop] = value | ||||
|         try: | ||||
|             flag_modified(self, "view_settings") | ||||
|         except AttributeError: | ||||
| @@ -437,11 +437,8 @@ class RemoteAuthToken(Base): | ||||
|         return '<Token %r>' % self.id | ||||
|  | ||||
|  | ||||
| # Migrate database to current version, has to be updated after every database change. Currently migration from | ||||
| # everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding | ||||
| # rows with SQL commands | ||||
| def migrate_Database(session): | ||||
|     engine = session.bind | ||||
| # Add missing tables during migration of database | ||||
| def add_missing_tables(engine, session): | ||||
|     if not engine.dialect.has_table(engine.connect(), "book_read_link"): | ||||
|         ReadBook.__table__.create(bind=engine) | ||||
|     if not engine.dialect.has_table(engine.connect(), "bookmark"): | ||||
| @@ -459,6 +456,10 @@ def migrate_Database(session): | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("insert into registration (domain, allow) values('%.%',1)") | ||||
|         session.commit() | ||||
|  | ||||
|  | ||||
| # migrate all settings missing in registration table | ||||
| def migrate_registration_table(engine, session): | ||||
|     try: | ||||
|         session.query(exists().where(Registration.allow)).scalar() | ||||
|         session.commit() | ||||
| @@ -468,27 +469,29 @@ def migrate_Database(session): | ||||
|             conn.execute("update registration set 'allow' = 1") | ||||
|         session.commit() | ||||
|     try: | ||||
|         session.query(exists().where(RemoteAuthToken.token_type)).scalar() | ||||
|         session.commit() | ||||
|     except exc.OperationalError:  # Database is not compatible, some columns are missing | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0") | ||||
|             conn.execute("update remote_auth_token set 'token_type' = 0") | ||||
|         session.commit() | ||||
|         # Handle table exists, but no content | ||||
|         cnt = session.query(Registration).count() | ||||
|         if not cnt: | ||||
|             with engine.connect() as conn: | ||||
|                 conn.execute("insert into registration (domain, allow) values('%.%',1)") | ||||
|             session.commit() | ||||
|     except exc.OperationalError:  # Database is not writeable | ||||
|         print('Settings database is not writeable. Exiting...') | ||||
|         sys.exit(2) | ||||
|  | ||||
|  | ||||
| # Remove login capability of user Guest | ||||
| def migrate_guest_password(engine, session): | ||||
|     try: | ||||
|         session.query(exists().where(ReadBook.read_status)).scalar() | ||||
|     except exc.OperationalError: | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0") | ||||
|             conn.execute("UPDATE book_read_link SET 'read_status' = 1 WHERE is_read") | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'last_modified' DATETIME") | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME") | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0") | ||||
|             conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''") | ||||
|         session.commit() | ||||
|     test = session.query(ReadBook).filter(ReadBook.last_modified == None).all() | ||||
|     for book in test: | ||||
|         book.last_modified = datetime.datetime.utcnow() | ||||
|     session.commit() | ||||
|     except exc.OperationalError: | ||||
|         print('Settings database is not writeable. Exiting...') | ||||
|         sys.exit(2) | ||||
|  | ||||
|  | ||||
| def migrate_shelfs(engine, session): | ||||
|     try: | ||||
|         session.query(exists().where(Shelf.uuid)).scalar() | ||||
|     except exc.OperationalError: | ||||
| @@ -504,22 +507,51 @@ def migrate_Database(session): | ||||
|         for book_shelf in session.query(BookShelf).all(): | ||||
|             book_shelf.date_added = datetime.datetime.now() | ||||
|         session.commit() | ||||
|     try: | ||||
|         # Handle table exists, but no content | ||||
|         cnt = session.query(Registration).count() | ||||
|         if not cnt: | ||||
|             with engine.connect() as conn: | ||||
|                 conn.execute("insert into registration (domain, allow) values('%.%',1)") | ||||
|             session.commit() | ||||
|     except exc.OperationalError:  # Database is not writeable | ||||
|         print('Settings database is not writeable. Exiting...') | ||||
|         sys.exit(2) | ||||
|     try: | ||||
|         session.query(exists().where(BookShelf.order)).scalar() | ||||
|     except exc.OperationalError:  # Database is not compatible, some columns are missing | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1") | ||||
|         session.commit() | ||||
|  | ||||
|  | ||||
| def migrate_readBook(engine, session): | ||||
|     try: | ||||
|         session.query(exists().where(ReadBook.read_status)).scalar() | ||||
|     except exc.OperationalError: | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0") | ||||
|             conn.execute("UPDATE book_read_link SET 'read_status' = 1 WHERE is_read") | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'last_modified' DATETIME") | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME") | ||||
|             conn.execute("ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0") | ||||
|         session.commit() | ||||
|     test = session.query(ReadBook).filter(ReadBook.last_modified == None).all() | ||||
|     for book in test: | ||||
|         book.last_modified = datetime.datetime.utcnow() | ||||
|     session.commit() | ||||
|  | ||||
|  | ||||
| def migrate_remoteAuthToken(engine, session): | ||||
|     try: | ||||
|         session.query(exists().where(RemoteAuthToken.token_type)).scalar() | ||||
|         session.commit() | ||||
|     except exc.OperationalError:  # Database is not compatible, some columns are missing | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0") | ||||
|             conn.execute("update remote_auth_token set 'token_type' = 0") | ||||
|         session.commit() | ||||
|  | ||||
| # Migrate database to current version, has to be updated after every database change. Currently migration from | ||||
| # everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding | ||||
| # rows with SQL commands | ||||
| def migrate_Database(session): | ||||
|     engine = session.bind | ||||
|     add_missing_tables(engine, session) | ||||
|     migrate_registration_table(engine, session) | ||||
|     migrate_readBook(engine, session) | ||||
|     migrate_remoteAuthToken(engine, session) | ||||
|     migrate_shelfs(engine, session) | ||||
|     try: | ||||
|         create = False | ||||
|         session.query(exists().where(User.sidebar_view)).scalar() | ||||
| @@ -578,8 +610,7 @@ def migrate_Database(session): | ||||
|                      "locale VARCHAR(2)," | ||||
|                      "sidebar_view INTEGER," | ||||
|                      "default_language VARCHAR(3)," | ||||
|                      # "series_view VARCHAR(10)," | ||||
|                      "view_settings VARCHAR,"                      | ||||
|                      "view_settings VARCHAR,"                | ||||
|                      "UNIQUE (nickname)," | ||||
|                      "UNIQUE (email))") | ||||
|             conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale," | ||||
| @@ -590,15 +621,7 @@ def migrate_Database(session): | ||||
|             conn.execute("DROP TABLE user") | ||||
|             conn.execute("ALTER TABLE user_id RENAME TO user") | ||||
|         session.commit() | ||||
|  | ||||
|     # Remove login capability of user Guest | ||||
|     try: | ||||
|         with engine.connect() as conn: | ||||
|             conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''") | ||||
|         session.commit() | ||||
|     except exc.OperationalError: | ||||
|         print('Settings database is not writeable. Exiting...') | ||||
|         sys.exit(2) | ||||
|     migrate_guest_password(engine, session) | ||||
|  | ||||
|  | ||||
| def clean_database(session): | ||||
|   | ||||
| @@ -72,7 +72,7 @@ def load_user_from_request(request): | ||||
| def load_user_from_auth_header(header_val): | ||||
|     if header_val.startswith('Basic '): | ||||
|         header_val = header_val.replace('Basic ', '', 1) | ||||
|     basic_username = basic_password = '' | ||||
|     basic_username = basic_password = ''  # nosec | ||||
|     try: | ||||
|         header_val = base64.b64decode(header_val).decode('utf-8') | ||||
|         basic_username = header_val.split(':')[0] | ||||
|   | ||||
							
								
								
									
										147
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								cps/web.py
									
									
									
									
									
								
							| @@ -216,7 +216,7 @@ def update_view(): | ||||
|             for param in to_save[element]: | ||||
|                 current_user.set_view_property(element, param, to_save[element][param]) | ||||
|     except Exception as e: | ||||
|         log.error("Could not save view_settings: %r %r: e", request, to_save, e) | ||||
|         log.error("Could not save view_settings: %r %r: %e", request, to_save, e) | ||||
|         return "Invalid request", 400 | ||||
|     return "1", 200 | ||||
|  | ||||
| @@ -340,7 +340,7 @@ def get_matching_tags(): | ||||
|     return json_dumps | ||||
|  | ||||
|  | ||||
| def render_books_list(data, sort, book_id, page): | ||||
| def get_sort_function(sort, data): | ||||
|     order = [db.Books.timestamp.desc()] | ||||
|     if sort == 'stored': | ||||
|         sort = current_user.get_view_property(data, 'stored') | ||||
| @@ -366,6 +366,11 @@ def render_books_list(data, sort, book_id, page): | ||||
|         order = [db.Books.series_index.asc()] | ||||
|     if sort == 'seriesdesc': | ||||
|         order = [db.Books.series_index.desc()] | ||||
|     return order | ||||
|  | ||||
|  | ||||
| def render_books_list(data, sort, book_id, page): | ||||
|     order = get_sort_function(sort, data) | ||||
|  | ||||
|     if data == "rated": | ||||
|         if current_user.check_visibility(constants.SIDEBAR_BEST_RATED): | ||||
| @@ -453,18 +458,11 @@ def render_hot_books(page): | ||||
|  | ||||
| def render_downloaded_books(page, order): | ||||
|     if current_user.check_visibility(constants.SIDEBAR_DOWNLOAD): | ||||
|         # order = order or [] | ||||
|         if current_user.show_detail_random(): | ||||
|             random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \ | ||||
|                 .order_by(func.random()).limit(config.config_random_books) | ||||
|         else: | ||||
|             random = false() | ||||
|         # off = int(int(config.config_books_per_page) * (page - 1)) | ||||
|         '''entries, random, pagination = calibre_db.fill_indexpage(page, 0, | ||||
|                                                                 db.Books, | ||||
|                                                                 db_filter, | ||||
|                                                                 order, | ||||
|                                                                 ub.ReadBook, db.Books.id==ub.ReadBook.book_id)''' | ||||
|  | ||||
|         entries, __, pagination = calibre_db.fill_indexpage(page, | ||||
|                                                             0, | ||||
| @@ -748,7 +746,7 @@ def list_books(): | ||||
|     search = request.args.get("search") | ||||
|     total_count = calibre_db.session.query(db.Books).count() | ||||
|     if search: | ||||
|         entries, filtered_count, pagination = calibre_db.get_search_results(search, off, order, limit) | ||||
|         entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit) | ||||
|     else: | ||||
|         entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order) | ||||
|         filtered_count = total_count | ||||
| @@ -1411,12 +1409,71 @@ def logout(): | ||||
|  | ||||
|  | ||||
| # ################################### Users own configuration ######################################################### | ||||
| def change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages): | ||||
|     to_save = request.form.to_dict() | ||||
|     current_user.random_books = 0 | ||||
|     if current_user.role_passwd() or current_user.role_admin(): | ||||
|         if "password" in to_save and to_save["password"]: | ||||
|             current_user.password = generate_password_hash(to_save["password"]) | ||||
|     if "kindle_mail" in to_save and to_save["kindle_mail"] != current_user.kindle_mail: | ||||
|         current_user.kindle_mail = to_save["kindle_mail"] | ||||
|     if "allowed_tags" in to_save and to_save["allowed_tags"] != current_user.allowed_tags: | ||||
|         current_user.allowed_tags = to_save["allowed_tags"].strip() | ||||
|     if "email" in to_save and to_save["email"] != current_user.email: | ||||
|         if config.config_public_reg and not check_valid_domain(to_save["email"]): | ||||
|             flash(_(u"E-mail is not from valid domain"), category="error") | ||||
|             return render_title_template("user_edit.html", content=current_user, | ||||
|                                          title=_(u"%(name)s's profile", name=current_user.nickname), page="me", | ||||
|                                          kobo_support=kobo_support, | ||||
|                                          registered_oauth=local_oauth_check, oauth_status=oauth_status) | ||||
|         current_user.email = to_save["email"] | ||||
|     if "nickname" in to_save and to_save["nickname"] != current_user.nickname: | ||||
|         # Query User nickname, if not existing, change | ||||
|         if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar(): | ||||
|             current_user.nickname = to_save["nickname"] | ||||
|         else: | ||||
|             flash(_(u"This username is already taken"), category="error") | ||||
|             return render_title_template("user_edit.html", | ||||
|                                          translations=translations, | ||||
|                                          languages=languages, | ||||
|                                          kobo_support=kobo_support, | ||||
|                                          new_user=0, content=current_user, | ||||
|                                          registered_oauth=local_oauth_check, | ||||
|                                          title=_(u"Edit User %(nick)s", | ||||
|                                                  nick=current_user.nickname), | ||||
|                                          page="edituser") | ||||
|     if "show_random" in to_save and to_save["show_random"] == "on": | ||||
|         current_user.random_books = 1 | ||||
|     if "default_language" in to_save: | ||||
|         current_user.default_language = to_save["default_language"] | ||||
|     if "locale" in to_save: | ||||
|         current_user.locale = to_save["locale"] | ||||
|  | ||||
|     val = 0 | ||||
|     for key, __ in to_save.items(): | ||||
|         if key.startswith('show'): | ||||
|             val += int(key[5:]) | ||||
|     current_user.sidebar_view = val | ||||
|     if "Show_detail_random" in to_save: | ||||
|         current_user.sidebar_view += constants.DETAIL_RANDOM | ||||
|  | ||||
|     try: | ||||
|         ub.session.commit() | ||||
|         flash(_(u"Profile updated"), category="success") | ||||
|         log.debug(u"Profile updated") | ||||
|     except IntegrityError: | ||||
|         ub.session.rollback() | ||||
|         flash(_(u"Found an existing account for this e-mail address."), category="error") | ||||
|         log.debug(u"Found an existing account for this e-mail address.") | ||||
|     except OperationalError as e: | ||||
|         ub.session.rollback() | ||||
|         log.error("Database error: %s", e) | ||||
|         flash(_(u"Database error: %(error)s.", error=e), category="error") | ||||
|  | ||||
|  | ||||
| @web.route("/me", methods=["GET", "POST"]) | ||||
| @login_required | ||||
| def profile(): | ||||
|     # downloads = list() | ||||
|     languages = calibre_db.speaking_language() | ||||
|     translations = babel.list_translations() + [LC('en')] | ||||
|     kobo_support = feature_support['kobo'] and config.config_kobo_sync | ||||
| @@ -1427,74 +1484,8 @@ def profile(): | ||||
|         oauth_status = None | ||||
|         local_oauth_check = {} | ||||
|  | ||||
|     '''entries, __, pagination = calibre_db.fill_indexpage(page, | ||||
|                                                         0, | ||||
|                                                         db.Books, | ||||
|                                                         ub.Downloads.user_id == int(current_user.id), # True, | ||||
|                                                         [], | ||||
|                                                         ub.Downloads, db.Books.id == ub.Downloads.book_id)''' | ||||
|  | ||||
|     if request.method == "POST": | ||||
|         to_save = request.form.to_dict() | ||||
|         current_user.random_books = 0 | ||||
|         if current_user.role_passwd() or current_user.role_admin(): | ||||
|             if "password" in to_save and to_save["password"]: | ||||
|                 current_user.password = generate_password_hash(to_save["password"]) | ||||
|         if "kindle_mail" in to_save and to_save["kindle_mail"] != current_user.kindle_mail: | ||||
|             current_user.kindle_mail = to_save["kindle_mail"] | ||||
|         if "allowed_tags" in to_save and to_save["allowed_tags"] != current_user.allowed_tags: | ||||
|             current_user.allowed_tags = to_save["allowed_tags"].strip() | ||||
|         if "email" in to_save and to_save["email"] != current_user.email: | ||||
|             if config.config_public_reg and not check_valid_domain(to_save["email"]): | ||||
|                 flash(_(u"E-mail is not from valid domain"), category="error") | ||||
|                 return render_title_template("user_edit.html", content=current_user, | ||||
|                                              title=_(u"%(name)s's profile", name=current_user.nickname), page="me", | ||||
|                                              kobo_support=kobo_support, | ||||
|                                              registered_oauth=local_oauth_check, oauth_status=oauth_status) | ||||
|             current_user.email = to_save["email"] | ||||
|         if "nickname" in to_save and to_save["nickname"] != current_user.nickname: | ||||
|             # Query User nickname, if not existing, change | ||||
|             if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar(): | ||||
|                 current_user.nickname = to_save["nickname"] | ||||
|             else: | ||||
|                 flash(_(u"This username is already taken"), category="error") | ||||
|                 return render_title_template("user_edit.html", | ||||
|                                              translations=translations, | ||||
|                                              languages=languages, | ||||
|                                              kobo_support=kobo_support, | ||||
|                                              new_user=0, content=current_user, | ||||
|                                              registered_oauth=local_oauth_check, | ||||
|                                              title=_(u"Edit User %(nick)s", | ||||
|                                                      nick=current_user.nickname), | ||||
|                                              page="edituser") | ||||
|         if "show_random" in to_save and to_save["show_random"] == "on": | ||||
|             current_user.random_books = 1 | ||||
|         if "default_language" in to_save: | ||||
|             current_user.default_language = to_save["default_language"] | ||||
|         if "locale" in to_save: | ||||
|             current_user.locale = to_save["locale"] | ||||
|  | ||||
|         val = 0 | ||||
|         for key, __ in to_save.items(): | ||||
|             if key.startswith('show'): | ||||
|                 val += int(key[5:]) | ||||
|         current_user.sidebar_view = val | ||||
|         if "Show_detail_random" in to_save: | ||||
|             current_user.sidebar_view += constants.DETAIL_RANDOM | ||||
|  | ||||
|         try: | ||||
|             ub.session.commit() | ||||
|             flash(_(u"Profile updated"), category="success") | ||||
|             log.debug(u"Profile updated") | ||||
|         except IntegrityError: | ||||
|             ub.session.rollback() | ||||
|             flash(_(u"Found an existing account for this e-mail address."), category="error") | ||||
|             log.debug(u"Found an existing account for this e-mail address.") | ||||
|         except OperationalError as e: | ||||
|             ub.session.rollback() | ||||
|             log.error("Database error: %s", e) | ||||
|             flash(_(u"Database error: %(error)s.", error=e), category="error") | ||||
|  | ||||
|         change_profile(kobo_support, local_oauth_check, oauth_status, translations, languages) | ||||
|     return render_title_template("user_edit.html", | ||||
|                                  translations=translations, | ||||
|                                  profile=1, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Ozzie Isaacs
					Ozzie Isaacs