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