Some checks failed
Test Suite / lint (pull_request) Has been cancelled
Test Suite / test (macos-latest, 3.10) (pull_request) Has been cancelled
Test Suite / test (macos-latest, 3.11) (pull_request) Has been cancelled
Test Suite / test (macos-latest, 3.12) (pull_request) Has been cancelled
Test Suite / test (macos-latest, 3.8) (pull_request) Has been cancelled
Test Suite / test (macos-latest, 3.9) (pull_request) Has been cancelled
Test Suite / test (ubuntu-latest, 3.10) (pull_request) Has been cancelled
Test Suite / test (ubuntu-latest, 3.11) (pull_request) Has been cancelled
Test Suite / test (ubuntu-latest, 3.12) (pull_request) Has been cancelled
Test Suite / test (ubuntu-latest, 3.8) (pull_request) Has been cancelled
Test Suite / test (ubuntu-latest, 3.9) (pull_request) Has been cancelled
Test Suite / package (pull_request) Has been cancelled
Test Suite / test (windows-latest, 3.10) (pull_request) Has been cancelled
Test Suite / test (windows-latest, 3.11) (pull_request) Has been cancelled
Test Suite / test (windows-latest, 3.12) (pull_request) Has been cancelled
Test Suite / test (windows-latest, 3.8) (pull_request) Has been cancelled
Test Suite / test (windows-latest, 3.9) (pull_request) Has been cancelled
Test Suite / security (pull_request) Has been cancelled
193 lines
6.0 KiB
Python
193 lines
6.0 KiB
Python
"""Page-related data models for wikijs-python-sdk."""
|
|
|
|
import re
|
|
from typing import List, Optional
|
|
|
|
from pydantic import Field, field_validator
|
|
|
|
from .base import BaseModel, TimestampedModel
|
|
|
|
|
|
class Page(TimestampedModel):
|
|
"""Represents a Wiki.js page.
|
|
|
|
This model contains all the data for a wiki page including
|
|
content, metadata, and computed properties.
|
|
"""
|
|
|
|
id: int = Field(..., description="Unique page identifier")
|
|
title: str = Field(..., description="Page title")
|
|
path: str = Field(..., description="Page path/slug")
|
|
content: Optional[str] = Field(None, description="Page content")
|
|
|
|
# Optional fields that may be present
|
|
description: Optional[str] = Field(None, description="Page description")
|
|
is_published: bool = Field(True, description="Whether page is published")
|
|
is_private: bool = Field(False, description="Whether page is private")
|
|
|
|
# Metadata
|
|
tags: List[str] = Field(default_factory=list, description="Page tags")
|
|
locale: str = Field("en", description="Page locale")
|
|
|
|
# Author information
|
|
author_id: Optional[int] = Field(None, alias="authorId")
|
|
author_name: Optional[str] = Field(None, alias="authorName")
|
|
author_email: Optional[str] = Field(None, alias="authorEmail")
|
|
|
|
# Editor information
|
|
editor: Optional[str] = Field(None, description="Editor used")
|
|
|
|
@field_validator("path")
|
|
@classmethod
|
|
def validate_path(cls, v: str) -> str:
|
|
"""Validate page path format."""
|
|
if not v:
|
|
raise ValueError("Path cannot be empty")
|
|
|
|
# Remove leading/trailing slashes and normalize
|
|
v = v.strip("/")
|
|
|
|
# Check for valid characters (letters, numbers, hyphens, underscores, slashes)
|
|
if not re.match(r"^[a-zA-Z0-9\-_/]+$", v):
|
|
raise ValueError("Path contains invalid characters")
|
|
|
|
return v
|
|
|
|
@field_validator("title")
|
|
@classmethod
|
|
def validate_title(cls, v: str) -> str:
|
|
"""Validate page title."""
|
|
if not v or not v.strip():
|
|
raise ValueError("Title cannot be empty")
|
|
|
|
# Limit title length
|
|
if len(v) > 255:
|
|
raise ValueError("Title cannot exceed 255 characters")
|
|
|
|
return v.strip()
|
|
|
|
@property
|
|
def word_count(self) -> int:
|
|
"""Calculate word count from content."""
|
|
if not self.content:
|
|
return 0
|
|
|
|
# Simple word count - split on whitespace
|
|
words = self.content.split()
|
|
return len(words)
|
|
|
|
@property
|
|
def reading_time(self) -> int:
|
|
"""Estimate reading time in minutes (assuming 200 words per minute)."""
|
|
return max(1, self.word_count // 200)
|
|
|
|
@property
|
|
def url_path(self) -> str:
|
|
"""Get the full URL path for this page."""
|
|
return f"/{self.path}"
|
|
|
|
def extract_headings(self) -> List[str]:
|
|
"""Extract markdown headings from content.
|
|
|
|
Returns:
|
|
List of heading text (without # markers)
|
|
"""
|
|
if not self.content:
|
|
return []
|
|
|
|
headings = []
|
|
for line in self.content.split("\n"):
|
|
line = line.strip()
|
|
if line.startswith("#"):
|
|
# Remove # markers and whitespace
|
|
heading = re.sub(r"^#+\s*", "", line).strip()
|
|
if heading:
|
|
headings.append(heading)
|
|
|
|
return headings
|
|
|
|
def has_tag(self, tag: str) -> bool:
|
|
"""Check if page has a specific tag.
|
|
|
|
Args:
|
|
tag: Tag to check for
|
|
|
|
Returns:
|
|
True if page has the tag
|
|
"""
|
|
return tag.lower() in [t.lower() for t in self.tags]
|
|
|
|
|
|
class PageCreate(BaseModel):
|
|
"""Data model for creating a new page."""
|
|
|
|
title: str = Field(..., description="Page title")
|
|
path: str = Field(..., description="Page path/slug")
|
|
content: str = Field(..., description="Page content")
|
|
|
|
# Optional fields
|
|
description: Optional[str] = Field(None, description="Page description")
|
|
is_published: bool = Field(True, description="Whether to publish immediately")
|
|
is_private: bool = Field(False, description="Whether page should be private")
|
|
|
|
tags: List[str] = Field(default_factory=list, description="Page tags")
|
|
locale: str = Field("en", description="Page locale")
|
|
editor: str = Field("markdown", description="Editor to use")
|
|
|
|
@field_validator("path")
|
|
@classmethod
|
|
def validate_path(cls, v: str) -> str:
|
|
"""Validate page path format."""
|
|
if not v:
|
|
raise ValueError("Path cannot be empty")
|
|
|
|
# Remove leading/trailing slashes and normalize
|
|
v = v.strip("/")
|
|
|
|
# Check for valid characters
|
|
if not re.match(r"^[a-zA-Z0-9\-_/]+$", v):
|
|
raise ValueError("Path contains invalid characters")
|
|
|
|
return v
|
|
|
|
@field_validator("title")
|
|
@classmethod
|
|
def validate_title(cls, v: str) -> str:
|
|
"""Validate page title."""
|
|
if not v or not v.strip():
|
|
raise ValueError("Title cannot be empty")
|
|
|
|
if len(v) > 255:
|
|
raise ValueError("Title cannot exceed 255 characters")
|
|
|
|
return v.strip()
|
|
|
|
|
|
class PageUpdate(BaseModel):
|
|
"""Data model for updating an existing page."""
|
|
|
|
# All fields optional for partial updates
|
|
title: Optional[str] = Field(None, description="Page title")
|
|
content: Optional[str] = Field(None, description="Page content")
|
|
description: Optional[str] = Field(None, description="Page description")
|
|
|
|
is_published: Optional[bool] = Field(None, description="Publication status")
|
|
is_private: Optional[bool] = Field(None, description="Privacy status")
|
|
|
|
tags: Optional[List[str]] = Field(None, description="Page tags")
|
|
|
|
@field_validator("title")
|
|
@classmethod
|
|
def validate_title(cls, v: Optional[str]) -> Optional[str]:
|
|
"""Validate page title if provided."""
|
|
if v is not None:
|
|
if not v.strip():
|
|
raise ValueError("Title cannot be empty")
|
|
|
|
if len(v) > 255:
|
|
raise ValueError("Title cannot exceed 255 characters")
|
|
|
|
return v.strip()
|
|
|
|
return v
|