server/tags: add order to tag names
The better implementation of a224297
.
Fixes ability to reorder tag aliases, especially - the ability to change
the tag's primary name after it was created. Until now, both of these
scenarios needed sad workarounds on the user part.
This commit is contained in:
parent
c366b608da
commit
243ab15b85
7 changed files with 77 additions and 11 deletions
|
@ -2,7 +2,7 @@
|
|||
function-rgx=^_?[a-z_][a-z0-9_]{2,}$|^test_
|
||||
method-rgx=^[a-z_][a-z0-9_]{2,}$|^test_
|
||||
const-rgx=^[A-Z_]+$|^_[a-zA-Z_]*$
|
||||
good-names=ex,_,logger
|
||||
good-names=ex,_,logger,i
|
||||
|
||||
[variables]
|
||||
dummy-variables-rgx=_|dummy
|
||||
|
|
|
@ -59,9 +59,11 @@ class TagName(Base):
|
|||
tag_id = Column(
|
||||
'tag_id', Integer, ForeignKey('tag.id'), nullable=False, index=True)
|
||||
name = Column('name', Unicode(64), nullable=False, unique=True)
|
||||
order = Column('ord', Integer, nullable=False, index=True)
|
||||
|
||||
def __init__(self, name):
|
||||
def __init__(self, name, order):
|
||||
self.name = name
|
||||
self.order = order
|
||||
|
||||
|
||||
class Tag(Base):
|
||||
|
@ -84,7 +86,7 @@ class Tag(Base):
|
|||
'TagName',
|
||||
cascade='all,delete-orphan',
|
||||
lazy='joined',
|
||||
order_by='TagName.tag_name_id')
|
||||
order_by='TagName.order')
|
||||
suggestions = relationship(
|
||||
'Tag',
|
||||
secondary='tag_suggestion',
|
||||
|
@ -106,7 +108,7 @@ class Tag(Base):
|
|||
first_name = column_property(
|
||||
select([TagName.name])
|
||||
.where(TagName.tag_id == tag_id)
|
||||
.order_by(TagName.tag_name_id)
|
||||
.order_by(TagName.order)
|
||||
.limit(1)
|
||||
.as_scalar(),
|
||||
deferred=True)
|
||||
|
|
|
@ -36,6 +36,8 @@ class InvalidTagDescriptionError(errors.ValidationError):
|
|||
|
||||
|
||||
def _verify_name_validity(name):
|
||||
if util.value_exceeds_column_size(name, db.TagName.name):
|
||||
raise InvalidTagNameError('Name is too long.')
|
||||
name_regex = config.config['tag_name_regex']
|
||||
if not re.match(name_regex, name):
|
||||
raise InvalidTagNameError('Name must satisfy regex %r.' % name_regex)
|
||||
|
@ -250,16 +252,17 @@ def update_tag_category_name(tag, category_name):
|
|||
|
||||
|
||||
def update_tag_names(tag, names):
|
||||
# sanitize
|
||||
assert tag
|
||||
names = util.icase_unique([name for name in names if name])
|
||||
if not len(names):
|
||||
raise InvalidTagNameError('At least one name must be specified.')
|
||||
for name in names:
|
||||
_verify_name_validity(name)
|
||||
|
||||
# check for existing tags
|
||||
expr = sqlalchemy.sql.false()
|
||||
for name in names:
|
||||
if util.value_exceeds_column_size(name, db.TagName.name):
|
||||
raise InvalidTagNameError('Name is too long.')
|
||||
expr = expr | (sqlalchemy.func.lower(db.TagName.name) == name.lower())
|
||||
if tag.tag_id:
|
||||
expr = expr & (db.TagName.tag_id != tag.tag_id)
|
||||
|
@ -267,12 +270,21 @@ def update_tag_names(tag, names):
|
|||
if len(existing_tags):
|
||||
raise TagAlreadyExistsError(
|
||||
'One of names is already used by another tag.')
|
||||
|
||||
# remove unwanted items
|
||||
for tag_name in tag.names[:]:
|
||||
if not _check_name_intersection([tag_name.name], names, True):
|
||||
tag.names.remove(tag_name)
|
||||
# add wanted items
|
||||
for name in names:
|
||||
if not _check_name_intersection(_get_names(tag), [name], True):
|
||||
tag.names.append(db.TagName(name))
|
||||
tag.names.append(db.TagName(name, None))
|
||||
|
||||
# set alias order to match the request
|
||||
for i, name in enumerate(names):
|
||||
for tag_name in tag.names:
|
||||
if tag_name.name.lower() == name.lower():
|
||||
tag_name.order = i
|
||||
|
||||
|
||||
# TODO: what to do with relations that do not yet exist?
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
'''
|
||||
Add order to tag names
|
||||
|
||||
Revision ID: 9837fc981ec7
|
||||
Created at: 2016-08-28 19:03:59.831527
|
||||
'''
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
|
||||
revision = '9837fc981ec7'
|
||||
down_revision = '4a020f1d271a'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
Base = sa.ext.declarative.declarative_base()
|
||||
|
||||
|
||||
class TagName(Base):
|
||||
__tablename__ = 'tag_name'
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
tag_name_id = sa.Column('tag_name_id', sa.Integer, primary_key=True)
|
||||
ord = sa.Column('ord', sa.Integer, nullable=False, index=True)
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('tag_name', sa.Column('ord', sa.Integer(), nullable=True))
|
||||
op.execute(TagName.__table__.update().values(ord=TagName.tag_name_id))
|
||||
op.alter_column('tag_name', 'ord', nullable=False)
|
||||
op.create_index(
|
||||
op.f('ix_tag_name_ord'), 'tag_name', ['ord'], unique=False)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index(op.f('ix_tag_name_ord'), table_name='tag_name')
|
||||
op.drop_column('tag_name', 'ord')
|
|
@ -145,8 +145,9 @@ def tag_factory():
|
|||
category = db.TagCategory(get_unique_name())
|
||||
db.session.add(category)
|
||||
tag = db.Tag()
|
||||
tag.names = [
|
||||
db.TagName(name) for name in names or [get_unique_name()]]
|
||||
tag.names = []
|
||||
for i, name in enumerate(names or [get_unique_name()]):
|
||||
tag.names.append(db.TagName(name, i))
|
||||
tag.category = category
|
||||
tag.creation_time = datetime(1996, 1, 1)
|
||||
return tag
|
||||
|
|
|
@ -8,7 +8,7 @@ def test_saving_tag(tag_factory):
|
|||
imp1 = tag_factory(names=['imp1'])
|
||||
imp2 = tag_factory(names=['imp2'])
|
||||
tag = db.Tag()
|
||||
tag.names = [db.TagName('alias1'), db.TagName('alias2')]
|
||||
tag.names = [db.TagName('alias1', 0), db.TagName('alias2', 1)]
|
||||
tag.suggestions = []
|
||||
tag.implications = []
|
||||
tag.category = db.TagCategory('category')
|
||||
|
@ -49,7 +49,7 @@ def test_cascade_deletions(tag_factory):
|
|||
imp1 = tag_factory(names=['imp1'])
|
||||
imp2 = tag_factory(names=['imp2'])
|
||||
tag = db.Tag()
|
||||
tag.names = [db.TagName('alias1'), db.TagName('alias2')]
|
||||
tag.names = [db.TagName('alias1', 0), db.TagName('alias2', 1)]
|
||||
tag.suggestions = []
|
||||
tag.implications = []
|
||||
tag.category = db.TagCategory('category')
|
||||
|
|
|
@ -483,6 +483,18 @@ def test_update_tag_names_reusing_own_name(config_injector, tag_factory):
|
|||
db.session.rollback()
|
||||
|
||||
|
||||
def test_update_tag_names_changing_primary_name(config_injector, tag_factory):
|
||||
config_injector({'tag_name_regex': '^[a-zA-Z]*$'})
|
||||
tag = tag_factory(names=['a', 'b'])
|
||||
db.session.add(tag)
|
||||
db.session.flush()
|
||||
tags.update_tag_names(tag, ['b', 'a'])
|
||||
db.session.flush()
|
||||
db.session.refresh(tag)
|
||||
assert [tag_name.name for tag_name in tag.names] == ['b', 'a']
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('attempt', ['name', 'NAME', 'alias', 'ALIAS'])
|
||||
def test_update_tag_suggestions_with_itself(attempt, tag_factory):
|
||||
tag = tag_factory(names=['name', 'ALIAS'])
|
||||
|
|
Loading…
Reference in a new issue