Coverage for rfpy/model/misc.py: 100%
31 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
1import re
2from typing import TYPE_CHECKING
4from sqlalchemy import text, ForeignKey
5from sqlalchemy.dialects import mysql
6from sqlalchemy.orm import Mapped, mapped_column, relationship, DynamicMapped
8from rfpy.model.meta import Base
10if TYPE_CHECKING:
11 from rfpy.model.humans import Organisation
12 from rfpy.model.project import Project
15class Category(Base):
16 __tablename__ = "categories"
17 __table_args__ = ({"mysql_engine": "InnoDB", "mysql_charset": "utf8mb4"},)
19 public_attrs = ["id", "name", "description"]
21 name: Mapped[str] = mapped_column(
22 mysql.VARCHAR(length=50), nullable=False, server_default=text("''")
23 )
24 description: Mapped[str] = mapped_column(
25 mysql.VARCHAR(length=255), nullable=False, server_default=text("''")
26 )
27 org_id: Mapped[str] = mapped_column(
28 mysql.VARCHAR(length=150),
29 ForeignKey("organisations.id", ondelete="CASCADE", onupdate="CASCADE"),
30 nullable=False,
31 server_default=text("''"),
32 )
34 organisation: Mapped["Organisation"] = relationship(
35 "Organisation", back_populates="categories"
36 )
38 # TODO - does this expose security hole enabling org A to
39 # get a category and lookup projects for org B?
40 projects: DynamicMapped["Project"] = relationship(
41 "Project", secondary="project_categories", lazy="dynamic", viewonly=True
42 )
44 def __repr__(self):
45 return "(%s) %s" % (self.org_id, self.name)
48def clean_search_term(term):
49 if len(term) > 100:
50 raise ValueError("search term cannot be longer than 100 characters")
52 cleaned = []
53 tokens = term.split()
54 for token in tokens:
55 try:
56 word = re.match(r"\W*(\w+)", token).groups()[0]
57 if len(word) < 3:
58 continue
59 except Exception:
60 continue
62 token = token.lstrip("*")
63 token = token.rstrip("-+~")
65 cleaned.append(token)
67 return " ".join(cleaned)