Fix appearance of easybake errors; more exception improvements

New class EasyBakeException helps distinguish whether or not it should be displayed to the user; Exception class `error_type` attributes are now applied to the class via decorator instead of to the instance via init; Fixed easybake errors looking for the old json response format; Fixed incorrect error bubble when deleting a synonym after the tag has already been deleted
This commit is contained in:
voussoir 2017-03-04 22:27:24 -08:00
parent 888c3b48cd
commit e413e996d9
5 changed files with 68 additions and 31 deletions

View file

@ -6,59 +6,74 @@ def pascal_to_loudsnakes(text):
text = text.upper() text = text.upper()
return text return text
def with_error_type(cls):
cls.error_type = pascal_to_loudsnakes(cls.__name__)
return cls
class EtiquetteException(Exception): class EtiquetteException(Exception):
error_message = '' error_message = ''
def __init__(self, *args):
self.error_type = pascal_to_loudsnakes(type(self).__name__)
Exception.__init__(self, *args)
class WithFormat(EtiquetteException): class WithFormat(EtiquetteException):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.given_args = args
self.given_kwargs = kwargs
self.error_message = self.error_message.format(*args, **kwargs) self.error_message = self.error_message.format(*args, **kwargs)
EtiquetteException.__init__(self, self.error_message) EtiquetteException.__init__(self, self.error_message)
# NO SUCH # NO SUCH
@with_error_type
class NoSuch(WithFormat): class NoSuch(WithFormat):
pass pass
@with_error_type
class NoSuchAlbum(NoSuch): class NoSuchAlbum(NoSuch):
error_message = 'Album "{}" does not exist.' error_message = 'Album "{}" does not exist.'
@with_error_type
class NoSuchBookmark(NoSuch): class NoSuchBookmark(NoSuch):
error_message = 'Bookmark "{}" does not exist.' error_message = 'Bookmark "{}" does not exist.'
@with_error_type
class NoSuchGroup(NoSuch): class NoSuchGroup(NoSuch):
error_message = 'Group "{}" does not exist.' error_message = 'Group "{}" does not exist.'
@with_error_type
class NoSuchPhoto(NoSuch): class NoSuchPhoto(NoSuch):
error_message = 'Photo "{}" does not exist.' error_message = 'Photo "{}" does not exist.'
@with_error_type
class NoSuchSynonym(NoSuch): class NoSuchSynonym(NoSuch):
error_message = 'Synonym "{}" does not exist.' error_message = 'Synonym "{}" does not exist.'
@with_error_type
class NoSuchTag(NoSuch): class NoSuchTag(NoSuch):
error_message = 'Tag "{}" does not exist.' error_message = 'Tag "{}" does not exist.'
@with_error_type
class NoSuchUser(NoSuch): class NoSuchUser(NoSuch):
error_message = 'User "{}" does not exist.' error_message = 'User "{}" does not exist.'
# EXISTS # EXISTS
@with_error_type
class GroupExists(WithFormat): class GroupExists(WithFormat):
error_message = '{member} already in group {group}' error_message = '{member} already in group {group}'
@with_error_type
class PhotoExists(WithFormat): class PhotoExists(WithFormat):
error_message = 'Photo "{}" already exists.' error_message = 'Photo "{}" already exists.'
def __init__(self, photo): def __init__(self, photo):
self.photo = photo self.photo = photo
WithFormat.__init__(self, photo.id) WithFormat.__init__(self, photo.id)
@with_error_type
class TagExists(WithFormat): class TagExists(WithFormat):
error_message = 'Tag "{}" already exists.' error_message = 'Tag "{}" already exists.'
def __init__(self, tag): def __init__(self, tag):
self.tag = tag self.tag = tag
WithFormat.__init__(self, tag.name) WithFormat.__init__(self, tag.name)
@with_error_type
class UserExists(WithFormat): class UserExists(WithFormat):
error_message = 'User "{}" already exists.' error_message = 'User "{}" already exists.'
def __init__(self, user): def __init__(self, user):
@ -67,43 +82,61 @@ class UserExists(WithFormat):
# TAG ERRORS # TAG ERRORS
@with_error_type
class CantSynonymSelf(EtiquetteException): class CantSynonymSelf(EtiquetteException):
error_message = 'Cannot apply synonym to self.' error_message = 'Cannot apply synonym to self.'
@with_error_type
class EasyBakeError(EtiquetteException):
error_message = ''
def __init__(self, message):
self.error_message = message
EtiquetteException.__init__(self)
@with_error_type
class RecursiveGrouping(EtiquetteException): class RecursiveGrouping(EtiquetteException):
error_message = '{group} is an ancestor of {member}.' error_message = '{group} is an ancestor of {member}.'
@with_error_type
class TagTooLong(WithFormat): class TagTooLong(WithFormat):
error_message = 'Tag "{}" is too long.' error_message = 'Tag "{}" is too long.'
@with_error_type
class TagTooShort(WithFormat): class TagTooShort(WithFormat):
error_message = 'Tag "{}" has too few valid characters.' error_message = 'Tag "{}" has too few valid characters.'
# USER ERRORS # USER ERRORS
@with_error_type
class InvalidUsernameChars(WithFormat): class InvalidUsernameChars(WithFormat):
error_message = 'Username "{username}" contains invalid characters: {badchars}.' error_message = 'Username "{username}" contains invalid characters: {badchars}.'
@with_error_type
class PasswordTooShort(WithFormat): class PasswordTooShort(WithFormat):
error_message = 'Password is shorter than the minimum of {min_length}.' error_message = 'Password is shorter than the minimum of {min_length}.'
@with_error_type
class UsernameTooLong(WithFormat): class UsernameTooLong(WithFormat):
error_message = 'Username "{username}" is longer than maximum of {max_length}.' error_message = 'Username "{username}" is longer than maximum of {max_length}.'
@with_error_type
class UsernameTooShort(WithFormat): class UsernameTooShort(WithFormat):
error_message = 'Username "{username}" is shorter than minimum of {min_length}.' error_message = 'Username "{username}" is shorter than minimum of {min_length}.'
@with_error_type
class WrongLogin(EtiquetteException): class WrongLogin(EtiquetteException):
error_message = 'Wrong username-password combination.' error_message = 'Wrong username-password combination.'
# GENERAL ERRORS # GENERAL ERRORS
@with_error_type
class NotExclusive(EtiquetteException): class NotExclusive(EtiquetteException):
''' '''
For when two or more mutually exclusive actions have been requested. For when two or more mutually exclusive actions have been requested.
''' '''
pass pass
@with_error_type
class OutOfOrder(WithFormat): class OutOfOrder(WithFormat):
''' '''
For when a requested minmax range (a, b) has b > a For when a requested minmax range (a, b) has b > a

View file

@ -813,11 +813,11 @@ class Tag(ObjectBase, GroupableMixin):
raise exceptions.CantSynonymSelf() raise exceptions.CantSynonymSelf()
try: try:
self.photodb.get_tag_by_name(synname) existing_tag = self.photodb.get_tag_by_name(synname)
except exceptions.NoSuchTag: except exceptions.NoSuchTag:
pass pass
else: else:
raise exceptions.TagExists(synname) raise exceptions.TagExists(existing_tag)
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None
cur = self.photodb.sql.cursor() cur = self.photodb.sql.cursor()
@ -926,11 +926,11 @@ class Tag(ObjectBase, GroupableMixin):
return return
try: try:
self.photodb.get_tag(new_name) existing_tag = self.photodb.get_tag(new_name)
except exceptions.NoSuchTag: except exceptions.NoSuchTag:
pass pass
else: else:
raise exceptions.TagExists(new_name) raise exceptions.TagExists(existing_tag)
self._cached_qualified_name = None self._cached_qualified_name = None
self.photodb._cached_frozen_children = None self.photodb._cached_frozen_children = None

View file

@ -912,11 +912,11 @@ class PDBTagMixin:
''' '''
tagname = self.normalize_tagname(tagname) tagname = self.normalize_tagname(tagname)
try: try:
self.get_tag_by_name(tagname) existing_tag = self.get_tag_by_name(tagname)
except exceptions.NoSuchTag: except exceptions.NoSuchTag:
pass pass
else: else:
raise exceptions.TagExists(tagname) raise exceptions.TagExists(existing_tag)
tagid = self.generate_id('tags') tagid = self.generate_id('tags')
self._cached_frozen_children = None self._cached_frozen_children = None
@ -1051,10 +1051,12 @@ class PDBUserMixin:
if len(password) < self.config['min_password_length']: if len(password) < self.config['min_password_length']:
raise exceptions.PasswordTooShort(min_length=self.config['min_password_length']) raise exceptions.PasswordTooShort(min_length=self.config['min_password_length'])
cur = self.sql.cursor() try:
cur.execute('SELECT * FROM users WHERE username == ?', [username]) existing_user = self.get_user(username=username)
if cur.fetchone() is not None: except exceptions.NoSuchUser:
raise exceptions.UserExists(username) pass
else:
raise exceptions.UserExists(existing_user)
user_id = self.generate_user_id() user_id = self.generate_user_id()
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt()) hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
@ -1291,10 +1293,10 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs
ebstring = ebstring.strip() ebstring = ebstring.strip()
ebstring = ebstring.strip('.+=') ebstring = ebstring.strip('.+=')
if ebstring == '': if ebstring == '':
return output_notes raise exceptions.EasyBakeError('No tag supplied')
if '=' in ebstring and '+' in ebstring: if '=' in ebstring and '+' in ebstring:
raise ValueError('Cannot rename and assign snynonym at once') raise exceptions.EasyBakeError('Cannot rename and assign snynonym at once')
rename_parts = ebstring.split('=') rename_parts = ebstring.split('=')
if len(rename_parts) == 2: if len(rename_parts) == 2:
@ -1303,7 +1305,7 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs
ebstring = rename_parts[0] ebstring = rename_parts[0]
rename_to = None rename_to = None
else: else:
raise ValueError('Too many equals signs') raise exceptions.EasyBakeError('Too many equals signs')
create_parts = ebstring.split('+') create_parts = ebstring.split('+')
if len(create_parts) == 2: if len(create_parts) == 2:
@ -1312,10 +1314,10 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs
tag = create_parts[0] tag = create_parts[0]
synonym = None synonym = None
else: else:
raise ValueError('Too many plus signs') raise exceptions.EasyBakeError('Too many plus signs')
if not tag: if not tag:
return output_notes raise exceptions.EasyBakeError('No tag supplied')
if rename_to: if rename_to:
tag = self.get_tag(tag) tag = self.get_tag(tag)
@ -1335,13 +1337,10 @@ class PhotoDB(PDBAlbumMixin, PDBBookmarkMixin, PDBPhotoMixin, PDBTagMixin, PDBUs
tag = tags[-1] tag = tags[-1]
if synonym: if synonym:
try: tag.add_synonym(synonym)
tag.add_synonym(synonym) note = ('new_synonym', '%s+%s' % (tag.name, synonym))
note = ('new_synonym', '%s+%s' % (tag.name, synonym)) output_notes.append(note)
output_notes.append(note) print('New syn %s' % synonym)
print('New syn %s' % synonym)
except exceptions.TagExists:
pass
return output_notes return output_notes
def generate_id(self, table): def generate_id(self, table):

View file

@ -65,7 +65,10 @@ def delete_synonym(synonym):
synonym = synonym.split('+')[-1].split('.')[-1] synonym = synonym.split('+')[-1].split('.')[-1]
synonym = P.normalize_tagname(synonym) synonym = P.normalize_tagname(synonym)
master_tag = P.get_tag(synonym) try:
master_tag = P.get_tag(synonym)
except exceptions.NoSuchTag as e:
raise exceptions.NoSuchSynonym(*e.given_args, **e.given_kwargs)
master_tag.remove_synonym(synonym) master_tag.remove_synonym(synonym)
return {'action':'delete_synonym', 'synonym': synonym} return {'action':'delete_synonym', 'synonym': synonym}
@ -694,6 +697,7 @@ def post_photo_refresh_metadata(photoid):
photo.reload_metadata() photo.reload_metadata()
return jsonify.make_json_response({}) return jsonify.make_json_response({})
def post_tag_create_delete_core(tagname, function): def post_tag_create_delete_core(tagname, function):
try: try:
response = function(tagname) response = function(tagname)
@ -704,6 +708,7 @@ def post_tag_create_delete_core(tagname, function):
'error_message': e.error_message, 'error_message': e.error_message,
} }
status = 400 status = 400
print(response)
return jsonify.make_json_response(response, status=status) return jsonify.make_json_response(response, status=status)

View file

@ -67,13 +67,13 @@ body
{% for tag in tags %} {% for tag in tags %}
{% set qualname = tag.qualified_name() %} {% set qualname = tag.qualified_name() %}
<li> <li>
<a target="_blank" class="tag_object" href="/search?tag_musts={{tag[1]}}">{{qualname}}</a><!-- <a target="_blank" class="tag_object" href="/search?tag_musts={{tag.name}}">{{qualname}}</a><!--
--><button class="remove_tag_button" onclick="delete_tag('{{tag[0]}}', receive_callback);"></button> --><button class="remove_tag_button" onclick="delete_tag('{{tag.name}}', receive_callback);"></button>
</li> </li>
{% for synonym in tag.synonyms() %} {% for synonym in tag.synonyms() %}
<li> <li>
<a target="_blank" class="tag_object" href="/search?tag_musts={{tag.name}}">{{qualname + "+" + synonym}}</a> <a target="_blank" class="tag_object" href="/search?tag_musts={{tag.name}}">{{qualname + "+" + synonym}}</a><!--
<button class="remove_tag_button" onclick="delete_tag_synonym('{{synonym}}', receive_callback);"></button> --><button class="remove_tag_button" onclick="delete_tag_synonym('{{synonym}}', receive_callback);"></button>
</li> </li>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
@ -136,7 +136,7 @@ function receive_callback(responses)
if ("error_type" in response) if ("error_type" in response)
{ {
message_positivity = "message_negative"; message_positivity = "message_negative";
message_text = '"' + tagname + '" ' + response["error_message"]; message_text = response["error_message"];
} }
else if ("action" in response) else if ("action" in response)
{ {