class User(db.Model): """User Model """ __tablename__ = "doku_user" id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) username = db.Column(db.String(48), nullable=False, unique=True) email = db.Column(db.String(48), unique=True, nullable=False) password = db.Column(db.LargeBinary, nullable=False) def __init__(self, *args, **kwargs): if "password" in kwargs: self.set_password(kwargs.pop("password")) super().__init__(*args, **kwargs) @staticmethod def _hash_password(password): return generate_password_hash(password.encode(), salt_length=12).encode() def set_password(self, password): self.password = self._hash_password(password) def check_password(self, password): return check_password_hash(self.password.decode(), password) def authenticate(self, request): pass
class Variable(db.Model, DateMixin): """Variable Used to store the actual markdown content of a document. As we work with Jinja2 templates for out :class:`Template` class, they are basically passed as a context. """ AUTO_UPDATE = [ "name", "use_markdown", "css_class", "content", "document_id", # Used for empty children ] __tablename__ = "doku_variable" id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) name = db.Column(db.String(255), unique=False, nullable=False) use_markdown = db.Column(db.Boolean, default=True) css_class = db.Column(db.String(255), unique=False, nullable=True, default="") content = db.Column(db.UnicodeText, nullable=False, default="") compiled_content = db.Column(db.UnicodeText, nullable=False, default="") document_id = db.Column( db.Integer, db.ForeignKey("doku_document.id"), nullable=False ) parent_id = db.Column(db.Integer, db.ForeignKey("doku_variable.id"), nullable=True) children = db.relationship( "Variable", cascade="all,delete", backref=db.backref("parent", remote_side=[id]), order_by=func.lower(name), ) document = db.relationship("Document", back_populates="variables") @property def used(self) -> bool: if self.parent_id is not None: return True return self.name in self.document.template.available_fields @property def is_list(self) -> bool: return self.name.endswith("_list") @property def as_list(self) -> List[str]: if not self.is_list: raise ValueError(f"{self.name} is not a list") return [var.compiled_content for var in self.children]
class Resource(db.Model, DateMixin): """Resource Model """ __tablename__ = "doku_resource" id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) name = db.Column(db.String(255), unique=False, nullable=False) filename = db.Column(db.String(255), unique=True, nullable=False) @property def url(self): return url_for("resources.view", resource_id=self.id)
class Document(db.Model, DateMixin): """Document Model """ __tablename__ = "doku_document" id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) name = db.Column(db.String(255), unique=False, nullable=False) public = db.Column(db.Boolean) template_id = db.Column( db.Integer, db.ForeignKey("doku_template.id"), nullable=True ) template = db.relationship("Template", back_populates="documents") variables = db.relationship( "Variable", cascade="all,delete", back_populates="document" ) recent_variable = db.relationship( "Variable", order_by="desc(Variable.last_updated)", lazy="dynamic" ) def __str__(self): return self.name @property def html(self): return self.template.source @property def filename(self): return "_".join(self.name.split()).lower() def render(self): return self.template.render(self.variables)
class Stylesheet(db.Model, DateMixin): """Stylesheet Model This model is used in conjunction with the :class:`Template` model. While the Template model provides the Jinja2 (HTML) template, this model will serve the stylesheet. Separating both types will help us achieve a more modular templating engine with base styles and custom styles that can be changed globally. """ __tablename__ = "doku_stylesheet" id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) name = db.Column(db.String(255), unique=False, nullable=False) source = db.Column(db.UnicodeText, nullable=True) base_templates = db.relationship("Template", back_populates="base_style") templates = db.relationship("Template", secondary=template_stylesheet_relation, back_populates="styles") MAX_CONTENT_LENGTH = 125000 def __str__(self): return self.name @property def as_css(self) -> CSS: return CSS(string=self.source)
class Template(db.Model, DateMixin): """Template Model Used to store Jinja2 templates that can be populated with various variables. Styles are implemented using relations to the :class:`Stylesheet` model. For each template there is a :attr:`base_style`, which corresponds to a global stylesheet for multi-document presets. """ __tablename__ = "doku_template" id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False) name = db.Column(db.String(255), unique=False, nullable=False) base_style_id = db.Column(db.Integer, db.ForeignKey("doku_stylesheet.id"), nullable=True) source = db.Column(db.UnicodeText, nullable=True, default=DEFAULT_TEMPLATE) documents = db.relationship("Document", back_populates="template") base_style = db.relationship("Stylesheet", back_populates="base_templates") styles = db.relationship("Stylesheet", secondary=template_stylesheet_relation, back_populates="templates") def __str__(self): return self.name @property @functools.lru_cache(maxsize=512) def available_fields(self): env = Environment() content = env.parse(self.source) return meta.find_undeclared_variables(content) def render(self, variables): stylesheets = [ style.as_css for style in self.styles if style.source is not None ] if self.base_style.source is not None: stylesheets = [self.base_style.as_css] + stylesheets template = Jinja2Template(self.source) context: dict = { var.name: var.compiled_content for var in variables if not var.is_list or var.parent_id is not None } context.update( {var.name: var.as_list for var in variables if var.is_list}) source = template.render(**context) if "codehilite" in source: stylesheets.append(CSS(string=HtmlFormatter().get_style_defs())) html = HTML(string=source, base_url=".", url_fetcher=url_fetcher) return html.write_pdf(stylesheets=stylesheets)