Skip to content

Commit

Permalink
rename LibraryField, add is_default property
Browse files Browse the repository at this point in the history
  • Loading branch information
yedpodtrzitko committed Sep 9, 2024
1 parent 704237f commit 4e66377
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 41 deletions.
21 changes: 14 additions & 7 deletions tagstudio/src/core/library/alchemy/fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, TYPE_CHECKING

Expand All @@ -11,7 +11,7 @@
from .enums import FieldTypeEnum

if TYPE_CHECKING:
from .models import Entry, Tag, LibraryField
from .models import Entry, Tag, ValueType


class BaseField(Base):
Expand All @@ -26,7 +26,7 @@ def type_key(cls) -> Mapped[str]:
return mapped_column(ForeignKey("library_fields.key"))

@declared_attr
def type(cls) -> Mapped[LibraryField]:
def type(cls) -> Mapped[ValueType]:
return relationship(foreign_keys=[cls.type_key], lazy=False) # type: ignore

@declared_attr
Expand Down Expand Up @@ -127,21 +127,28 @@ def __eq__(self, value) -> bool:
class DefaultField:
id: int
name: str
type: Any # TextFieldTypes | TagBoxTypes | DateTimeTypes
type: FieldTypeEnum
is_default: bool = field(default=False)


class _FieldID(Enum):
"""Only for bootstrapping content of DB table"""

TITLE = DefaultField(id=0, name="Title", type=FieldTypeEnum.TEXT_LINE)
TITLE = DefaultField(
id=0, name="Title", type=FieldTypeEnum.TEXT_LINE, is_default=True
)
AUTHOR = DefaultField(id=1, name="Author", type=FieldTypeEnum.TEXT_LINE)
ARTIST = DefaultField(id=2, name="Artist", type=FieldTypeEnum.TEXT_LINE)
URL = DefaultField(id=3, name="URL", type=FieldTypeEnum.TEXT_LINE)
DESCRIPTION = DefaultField(id=4, name="Description", type=FieldTypeEnum.TEXT_LINE)
NOTES = DefaultField(id=5, name="Notes", type=FieldTypeEnum.TEXT_BOX)
TAGS = DefaultField(id=6, name="Tags", type=FieldTypeEnum.TAGS)
TAGS_CONTENT = DefaultField(id=7, name="Content Tags", type=FieldTypeEnum.TAGS)
TAGS_META = DefaultField(id=8, name="Meta Tags", type=FieldTypeEnum.TAGS)
TAGS_CONTENT = DefaultField(
id=7, name="Content Tags", type=FieldTypeEnum.TAGS, is_default=True
)
TAGS_META = DefaultField(
id=8, name="Meta Tags", type=FieldTypeEnum.TAGS, is_default=True
)
COLLATION = DefaultField(id=9, name="Collation", type=FieldTypeEnum.TEXT_LINE)
DATE = DefaultField(id=10, name="Date", type=FieldTypeEnum.DATETIME)
DATE_CREATED = DefaultField(id=11, name="Date Created", type=FieldTypeEnum.DATETIME)
Expand Down
36 changes: 23 additions & 13 deletions tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
BaseField,
)
from .joins import TagSubtag, TagField
from .models import Entry, Preferences, Tag, TagAlias, LibraryField, Folder
from .models import Entry, Preferences, Tag, TagAlias, ValueType, Folder
from ...constants import (
LibraryPrefs,
TS_FOLDER_NAME,
Expand Down Expand Up @@ -148,16 +148,17 @@ def open_library(
for field in _FieldID:
try:
session.add(
LibraryField(
ValueType(
key=field.name,
name=field.value.name,
type=field.value.type,
order=field.value.id,
key=field.name,
position=field.value.id,
is_default=field.value.is_default,
)
)
session.commit()
except IntegrityError:
logger.debug("preference already exists", pref=pref)
logger.debug("ValueType already exists", field=field)
session.rollback()

# check if folder matching current path exists already
Expand All @@ -178,6 +179,17 @@ def open_library(
# load ignored extensions
self.ignored_extensions = self.prefs(LibraryPrefs.EXTENSION_LIST)

@property
def default_fields(self) -> list[BaseField]:
with Session(self.engine) as session:
types = session.scalars(
select(ValueType).where(
# check if field is default
ValueType.is_default.is_(True)
)
)
return [x.as_field for x in types]

def delete_item(self, item):
logger.info("deleting item", item=item)
with Session(self.engine) as session:
Expand Down Expand Up @@ -579,23 +591,21 @@ def update_entry_field(
session.commit()

@property
def field_types(self) -> dict[str, LibraryField]:
def field_types(self) -> dict[str, ValueType]:
with Session(self.engine) as session:
return {x.key: x for x in session.scalars(select(LibraryField)).all()}
return {x.key: x for x in session.scalars(select(ValueType)).all()}

def get_library_field(self, field_key: str) -> LibraryField:
def get_value_type(self, field_key: str) -> ValueType:
with Session(self.engine) as session:
field = session.scalar(
select(LibraryField).where(LibraryField.key == field_key)
)
field = session.scalar(select(ValueType).where(ValueType.key == field_key))
session.expunge(field)
return field

def add_entry_field_type(
self,
entry_ids: list[int] | int,
*,
field: LibraryField | None = None,
field: ValueType | None = None,
field_id: _FieldID | str | None = None,
value: str | datetime | list[str] | None = None,
) -> bool:
Expand All @@ -615,7 +625,7 @@ def add_entry_field_type(
if not field:
if isinstance(field_id, _FieldID):
field_id = field_id.name
field = self.get_library_field(field_id)
field = self.get_value_type(field_id)

field_model: TextField | DatetimeField | TagBoxField
if field.type in (FieldTypeEnum.TEXT_LINE, FieldTypeEnum.TEXT_BOX):
Expand Down
41 changes: 28 additions & 13 deletions tagstudio/src/core/library/alchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
FieldTypeEnum,
_FieldID,
BaseField,
BooleanField,
)
from .joins import TagSubtag
from ...constants import TAG_FAVORITE, TAG_ARCHIVED
Expand Down Expand Up @@ -139,7 +140,7 @@ def fields(self) -> list[BaseField]:
fields.extend(self.tag_box_fields)
fields.extend(self.text_fields)
fields.extend(self.datetime_fields)
fields = sorted(fields, key=lambda field: field.type.order)
fields = sorted(fields, key=lambda field: field.type.position)
return fields

@property
Expand Down Expand Up @@ -171,18 +172,11 @@ def __init__(
self,
path: Path,
folder: Folder,
fields: list[BaseField] | None = None,
fields: list[BaseField],
) -> None:
self.path = path
self.folder = folder

if fields is None:
fields = [
TagBoxField(type_key=_FieldID.TAGS_META.name, position=0),
TagBoxField(type_key=_FieldID.TAGS_CONTENT.name, position=0),
TextField(type_key=_FieldID.TITLE.name, position=0),
]

for field in fields:
if isinstance(field, TextField):
self.text_fields.append(field)
Expand Down Expand Up @@ -210,13 +204,15 @@ def remove_tag(self, tag: Tag, field: TagBoxField | None = None) -> None:
tag_box_field.tags.remove(tag)


class LibraryField(Base):
class ValueType(Base):
"""Define Field Types in the Library.
Example:
key: content_tags (this field is slugified `name`)
name: Content Tags (this field is human readable name)
kind: type of content (Text Line, Text Box, Tags, Datetime, Checkbox)
is_default: Should the field be present in new Entry?
order: position of the field widget in the Entry form
"""

Expand All @@ -225,7 +221,8 @@ class LibraryField(Base):
key: Mapped[str] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
type: Mapped[FieldTypeEnum] = mapped_column(default=FieldTypeEnum.TEXT_LINE)
order: Mapped[int]
is_default: Mapped[bool]
position: Mapped[int]

# add relations to other tables
text_fields: Mapped[list[TextField]] = relationship(
Expand All @@ -237,9 +234,27 @@ class LibraryField(Base):
tag_box_fields: Mapped[list[TagBoxField]] = relationship(
"TagBoxField", back_populates="type"
)
boolean_fields: Mapped[list[BooleanField]] = relationship(
"BooleanField", back_populates="type"
)


@event.listens_for(LibraryField, "before_insert")
@property
def as_field(self) -> BaseField:
FieldClass = {
FieldTypeEnum.TEXT_LINE: TextField,
FieldTypeEnum.TEXT_BOX: TextField,
FieldTypeEnum.TAGS: TagBoxField,
FieldTypeEnum.DATETIME: DatetimeField,
FieldTypeEnum.BOOLEAN: BooleanField,
}

return FieldClass[self.type](
type_key=self.key,
position=self.position,
)


@event.listens_for(ValueType, "before_insert")
def slugify_field_key(mapper, connection, target):
"""Slugify the field key before inserting into the database."""
if not target.key:
Expand Down
5 changes: 4 additions & 1 deletion tagstudio/src/core/library/json/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,10 @@ def add_new_files_as_entries(self) -> list[int]:
path = Path(file)
# print(os.path.split(file))
entry = Entry(
id=self._next_entry_id, filename=path.name, path=path.parent, fields=[]
id=self._next_entry_id,
filename=path.name,
path=path.parent,
fields=[],
)
self._next_entry_id += 1
self.add_entry_to_library(entry)
Expand Down
1 change: 1 addition & 0 deletions tagstudio/src/core/utils/refresh_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def save_new_files(self) -> Iterator[int]:
Entry(
path=entry_path,
folder=self.library.folder,
fields=self.library.default_fields,
)
]
)
Expand Down
4 changes: 2 additions & 2 deletions tagstudio/src/qt/widgets/preview_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ def update_widgets(self) -> bool:
mixed_fields.append(f)

self.common_fields = common_fields
self.mixed_fields = sorted(mixed_fields, key=lambda x: x.type.order)
self.mixed_fields = sorted(mixed_fields, key=lambda x: x.type.position)

self.selected = list(self.driver.selected)
logger.info(
Expand Down Expand Up @@ -946,7 +946,7 @@ def remove_field(self, field: BaseField):
self.driver.update_badges(self.selected)

def update_field(self, field: BaseField, content: str) -> None:
"""Remove a field from all selected Entries, given a field object."""
"""Update a field in all selected Entries, given a field object."""
assert isinstance(
field, (TextField, DatetimeField, TagBoxField)
), f"instance: {type(field)}"
Expand Down
3 changes: 2 additions & 1 deletion tagstudio/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,21 @@ def library(request):
entry = Entry(
folder=lib.folder,
path=pathlib.Path("foo.txt"),
fields=lib.default_fields,
)

entry.tag_box_fields = [
TagBoxField(type_key=_FieldID.TAGS.name, tags={tag}, position=0),
TagBoxField(
type_key=_FieldID.TAGS_META.name,
position=0,
# tags={tag2}
),
]

entry2 = Entry(
folder=lib.folder,
path=pathlib.Path("one/two/bar.md"),
fields=lib.default_fields,
)
entry2.tag_box_fields = [
TagBoxField(
Expand Down
3 changes: 3 additions & 0 deletions tagstudio/tests/macros/test_dupe_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ def test_refresh_dupe_files(library):
entry = Entry(
folder=library.folder,
path=pathlib.Path("bar/foo.txt"),
fields=library.default_fields,
)

entry2 = Entry(
folder=library.folder,
path=pathlib.Path("foo/foo.txt"),
fields=library.default_fields,
)

library.add_entries([entry, entry2])
Expand Down
6 changes: 5 additions & 1 deletion tagstudio/tests/qt/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@

def test_update_thumbs(qt_driver):
qt_driver.frame_content = [
Entry(folder=qt_driver.lib.folder, path=Path("/tmp/foo"))
Entry(
folder=qt_driver.lib.folder,
path=Path("/tmp/foo"),
fields=qt_driver.lib.default_fields,
)
]

qt_driver.item_thumbs = []
Expand Down
19 changes: 16 additions & 3 deletions tagstudio/tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ def test_library_add_file():
lib = Library()
lib.open_library(tmp_dir)

entry = Entry(path=file_path, folder=lib.folder)
entry = Entry(
path=file_path,
folder=lib.folder,
fields=lib.default_fields,
)

assert not lib.has_path_entry(entry.path)

Expand Down Expand Up @@ -94,7 +98,10 @@ def test_get_entry(library, entry_min):


def test_entries_count(library):
entries = [Entry(path=Path(f"{x}.txt"), folder=library.folder) for x in range(10)]
entries = [
Entry(path=Path(f"{x}.txt"), folder=library.folder, fields=[])
for x in range(10)
]
library.add_entries(entries)
matches, page = library.search_library(
FilterState(
Expand All @@ -111,6 +118,7 @@ def test_add_field_to_entry(library):
entry = Entry(
folder=library.folder,
path=Path("xxx"),
fields=library.default_fields,
)
# meta tags + content tags
assert len(entry.tag_box_fields) == 2
Expand Down Expand Up @@ -208,7 +216,12 @@ def test_preferences(library):

def test_save_windows_path(library, generate_tag):
# pretend we are on windows and create `Path`
entry = Entry(path=PureWindowsPath("foo\\bar.txt"), folder=library.folder)

entry = Entry(
path=PureWindowsPath("foo\\bar.txt"),
folder=library.folder,
fields=library.default_fields,
)
tag = generate_tag("win_path")
tag_name = tag.name

Expand Down

0 comments on commit 4e66377

Please sign in to comment.