server/tag-categories: fix default categories

- Don't cache default category in its entirety - cache only its name
- Purge cache on category name changes and default category changes
- Lock records for updates where applicable
This commit is contained in:
rr- 2016-08-27 12:39:59 +02:00
parent 06ab98fa70
commit ef0f74297f
5 changed files with 73 additions and 31 deletions

View file

@ -40,7 +40,8 @@ def get_tag_category(ctx, params):
@routes.put('/tag-category/(?P<category_name>[^/]+)/?')
def update_tag_category(ctx, params):
category = tag_categories.get_category_by_name(params['category_name'])
category = tag_categories.get_category_by_name(
params['category_name'], lock=True)
versions.verify_version(category, ctx)
versions.bump_version(category)
if ctx.has_param('name'):
@ -60,7 +61,8 @@ def update_tag_category(ctx, params):
@routes.delete('/tag-category/(?P<category_name>[^/]+)/?')
def delete_tag_category(ctx, params):
category = tag_categories.get_category_by_name(params['category_name'])
category = tag_categories.get_category_by_name(
params['category_name'], lock=True)
versions.verify_version(category, ctx)
auth.verify_privilege(ctx.user, 'tag_categories:delete')
tag_categories.delete_category(category)
@ -73,8 +75,10 @@ def delete_tag_category(ctx, params):
@routes.put('/tag-category/(?P<category_name>[^/]+)/default/?')
def set_tag_category_as_default(ctx, params):
auth.verify_privilege(ctx.user, 'tag_categories:set_default')
category = tag_categories.get_category_by_name(params['category_name'])
category = tag_categories.get_category_by_name(
params['category_name'], lock=True)
tag_categories.set_default_category(category)
ctx.session.flush()
snapshots.modify(category, ctx.user)
ctx.session.commit()
tags.export_to_json()

View file

@ -55,5 +55,10 @@ def get(key):
return _CACHE.hash[key].value
def remove(key):
if has(key):
del _CACHE.hash[key]
def put(key, value):
_CACHE.insert_item(LruCacheItem(key, value))

View file

@ -4,6 +4,9 @@ from szurubooru import config, db, errors
from szurubooru.func import util, cache
DEFAULT_CATEGORY_NAME_CACHE_KEY = 'default-tag-category'
class TagCategoryNotFoundError(errors.NotFoundError):
pass
@ -69,6 +72,7 @@ def update_category_name(category, name):
raise InvalidTagCategoryNameError('Name is too long.')
_verify_name_validity(name)
category.name = name
cache.remove(DEFAULT_CATEGORY_NAME_CACHE_KEY)
def update_category_color(category, color):
@ -82,15 +86,17 @@ def update_category_color(category, color):
category.color = color
def try_get_category_by_name(name):
return db.session \
def try_get_category_by_name(name, lock=False):
query = db.session \
.query(db.TagCategory) \
.filter(sqlalchemy.func.lower(db.TagCategory.name) == name.lower()) \
.one_or_none()
.filter(sqlalchemy.func.lower(db.TagCategory.name) == name.lower())
if lock:
query = query.with_lockmode('update')
return query.one_or_none()
def get_category_by_name(name):
category = try_get_category_by_name(name)
def get_category_by_name(name, lock=False):
category = try_get_category_by_name(name, lock)
if not category:
raise TagCategoryNotFoundError('Tag category %r not found.' % name)
return category
@ -104,38 +110,50 @@ def get_all_categories():
return db.session.query(db.TagCategory).all()
def try_get_default_category():
key = 'default-tag-category'
if cache.has(key):
return cache.get(key)
category = db.session \
def try_get_default_category(lock=False):
query = db.session \
.query(db.TagCategory) \
.filter(db.TagCategory.default) \
.first()
.filter(db.TagCategory.default)
if lock:
query = query.with_lockmode('update')
category = query.first()
# if for some reason (e.g. as a result of migration) there's no default
# category, get the first record available.
if not category:
category = db.session \
query = db.session \
.query(db.TagCategory) \
.order_by(db.TagCategory.tag_category_id.asc()) \
.first()
cache.put(key, category)
.order_by(db.TagCategory.tag_category_id.asc())
if lock:
query = query.with_lockmode('update')
category = query.first()
return category
def get_default_category():
category = try_get_default_category()
def get_default_category(lock=False):
category = try_get_default_category(lock)
if not category:
raise TagCategoryNotFoundError('No tag category created yet.')
return category
def get_default_category_name():
if cache.has(DEFAULT_CATEGORY_NAME_CACHE_KEY):
return cache.get(DEFAULT_CATEGORY_NAME_CACHE_KEY)
default_category = try_get_default_category()
default_category_name = default_category.name if default_category else None
cache.put(DEFAULT_CATEGORY_NAME_CACHE_KEY, default_category_name)
return default_category_name
def set_default_category(category):
assert category
old_category = try_get_default_category()
old_category = try_get_default_category(lock=True)
if old_category:
db.session.refresh(old_category)
old_category.default = False
db.session.refresh(category)
category.default = True
cache.remove(DEFAULT_CATEGORY_NAME_CACHE_KEY)
def delete_category(category):

View file

@ -58,8 +58,7 @@ def _check_name_intersection(names1, names2, case_sensitive):
def sort_tags(tags):
default_category = tag_categories.try_get_default_category()
default_category_name = default_category.name if default_category else None
default_category_name = tag_categories.get_default_category_name()
return sorted(
tags,
key=lambda tag: (
@ -170,7 +169,7 @@ def get_or_create_tags_by_names(names):
names = util.icase_unique(names)
existing_tags = get_tags_by_names(names)
new_tags = []
tag_category_name = tag_categories.get_default_category().name
tag_category_name = tag_categories.get_default_category_name()
for name in names:
found = False
for existing_tag in existing_tags:

View file

@ -181,16 +181,32 @@ def test_try_get_default_category_when_default(tag_category_factory):
assert actual_default_category != category1
def test_try_get_default_category_from_cache(tag_category_factory):
def test_get_default_category_name(tag_category_factory):
category1 = tag_category_factory()
category2 = tag_category_factory(default=True)
db.session.add_all([category1, category2])
db.session.flush()
assert tag_categories.get_default_category_name() == category2.name
category2.default = False
db.session.flush()
cache.purge()
assert tag_categories.get_default_category_name() == category1.name
db.session.query(db.TagCategory).delete()
cache.purge()
assert tag_categories.get_default_category_name() is None
def test_get_default_category_name_caching(tag_category_factory):
category1 = tag_category_factory()
category2 = tag_category_factory()
db.session.add_all([category1, category2])
db.session.flush()
tag_categories.try_get_default_category()
db.session.query(db.TagCategory).delete()
assert tag_categories.try_get_default_category() == category1
tag_categories.get_default_category_name()
db.session.delete(category1)
db.session.flush()
assert tag_categories.get_default_category_name() == category1.name
cache.purge()
assert tag_categories.try_get_default_category() is None
assert tag_categories.get_default_category_name() == category2.name
def test_get_default_category():