Implement Groups API with complete CRUD operations

Implementation:
- Group data models (wikijs/models/group.py)
  - Group, GroupCreate, GroupUpdate models
  - GroupPermission, GroupPageRule, GroupUser models
  - GroupAssignUser, GroupUnassignUser models
  - Field validation and normalization

- Sync GroupsEndpoint (wikijs/endpoints/groups.py)
  - list() - List all groups with users
  - get(group_id) - Get single group
  - create(group_data) - Create new group
  - update(group_id, group_data) - Update existing group
  - delete(group_id) - Delete group
  - assign_user(group_id, user_id) - Add user to group
  - unassign_user(group_id, user_id) - Remove user from group

- Async AsyncGroupsEndpoint (wikijs/aio/endpoints/groups.py)
  - Complete async implementation
  - Identical interface to sync version
  - All CRUD operations + user management

- Integration with clients
  - WikiJSClient.groups
  - AsyncWikiJSClient.groups

GraphQL operations for all group management features.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2025-10-22 20:27:22 +00:00
parent 5ad98e469e
commit fc96472d55
8 changed files with 1353 additions and 5 deletions

View File

@@ -1,11 +1,29 @@
"""Data models for wikijs-python-sdk."""
from .base import BaseModel
from .group import (
Group,
GroupAssignUser,
GroupCreate,
GroupPageRule,
GroupPermission,
GroupUnassignUser,
GroupUpdate,
GroupUser,
)
from .page import Page, PageCreate, PageUpdate
from .user import User, UserCreate, UserGroup, UserUpdate
__all__ = [
"BaseModel",
"Group",
"GroupAssignUser",
"GroupCreate",
"GroupPageRule",
"GroupPermission",
"GroupUnassignUser",
"GroupUpdate",
"GroupUser",
"Page",
"PageCreate",
"PageUpdate",

217
wikijs/models/group.py Normal file
View File

@@ -0,0 +1,217 @@
"""Data models for Wiki.js groups."""
from typing import List, Optional
from pydantic import Field, field_validator
from .base import BaseModel, TimestampedModel
class GroupPermission(BaseModel):
"""Group permission model."""
id: str = Field(..., description="Permission identifier")
name: Optional[str] = Field(None, description="Permission name")
class Config:
"""Pydantic configuration."""
populate_by_name = True
class GroupPageRule(BaseModel):
"""Group page access rule model."""
id: str = Field(..., description="Rule identifier")
path: str = Field(..., description="Page path pattern")
roles: List[str] = Field(default_factory=list, description="Allowed roles")
match: str = Field(default="START", description="Match type (START, EXACT, REGEX)")
deny: bool = Field(default=False, description="Whether this is a deny rule")
locales: List[str] = Field(default_factory=list, description="Allowed locales")
class Config:
"""Pydantic configuration."""
populate_by_name = True
class GroupUser(BaseModel):
"""User member of a group (minimal representation)."""
id: int = Field(..., description="User ID")
name: str = Field(..., description="User name")
email: str = Field(..., description="User email")
class Config:
"""Pydantic configuration."""
populate_by_name = True
class Group(TimestampedModel):
"""Wiki.js group model.
Represents a complete group with all fields.
Attributes:
id: Group ID
name: Group name
is_system: Whether this is a system group
redirect_on_login: Path to redirect to on login
permissions: List of group permissions
page_rules: List of page access rules
users: List of users in this group (only populated in get operations)
created_at: Creation timestamp
updated_at: Last update timestamp
"""
id: int = Field(..., description="Group ID")
name: str = Field(..., min_length=1, max_length=255, description="Group name")
is_system: bool = Field(
default=False, alias="isSystem", description="System group flag"
)
redirect_on_login: Optional[str] = Field(
None, alias="redirectOnLogin", description="Redirect path on login"
)
permissions: List[str] = Field(
default_factory=list, description="Permission identifiers"
)
page_rules: List[GroupPageRule] = Field(
default_factory=list, alias="pageRules", description="Page access rules"
)
users: List[GroupUser] = Field(
default_factory=list, description="Users in this group"
)
@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
"""Validate group name."""
if not v or not v.strip():
raise ValueError("Group name cannot be empty")
if len(v.strip()) < 1:
raise ValueError("Group name must be at least 1 character")
if len(v) > 255:
raise ValueError("Group name cannot exceed 255 characters")
return v.strip()
class Config:
"""Pydantic configuration."""
populate_by_name = True
class GroupCreate(BaseModel):
"""Model for creating a new group.
Attributes:
name: Group name (required)
redirect_on_login: Path to redirect to on login
permissions: List of permission identifiers
page_rules: List of page access rule configurations
"""
name: str = Field(..., min_length=1, max_length=255, description="Group name")
redirect_on_login: Optional[str] = Field(
None, alias="redirectOnLogin", description="Redirect path on login"
)
permissions: List[str] = Field(
default_factory=list, description="Permission identifiers"
)
page_rules: List[dict] = Field(
default_factory=list, alias="pageRules", description="Page access rules"
)
@field_validator("name")
@classmethod
def validate_name(cls, v: str) -> str:
"""Validate group name."""
if not v or not v.strip():
raise ValueError("Group name cannot be empty")
if len(v.strip()) < 1:
raise ValueError("Group name must be at least 1 character")
if len(v) > 255:
raise ValueError("Group name cannot exceed 255 characters")
return v.strip()
class Config:
"""Pydantic configuration."""
populate_by_name = True
class GroupUpdate(BaseModel):
"""Model for updating an existing group.
All fields are optional to support partial updates.
Attributes:
name: Updated group name
redirect_on_login: Updated redirect path
permissions: Updated permission list
page_rules: Updated page access rules
"""
name: Optional[str] = Field(
None, min_length=1, max_length=255, description="Group name"
)
redirect_on_login: Optional[str] = Field(
None, alias="redirectOnLogin", description="Redirect path on login"
)
permissions: Optional[List[str]] = Field(None, description="Permission identifiers")
page_rules: Optional[List[dict]] = Field(
None, alias="pageRules", description="Page access rules"
)
@field_validator("name")
@classmethod
def validate_name(cls, v: Optional[str]) -> Optional[str]:
"""Validate group name if provided."""
if v is None:
return v
if not v or not v.strip():
raise ValueError("Group name cannot be empty")
if len(v.strip()) < 1:
raise ValueError("Group name must be at least 1 character")
if len(v) > 255:
raise ValueError("Group name cannot exceed 255 characters")
return v.strip()
class Config:
"""Pydantic configuration."""
populate_by_name = True
class GroupAssignUser(BaseModel):
"""Model for assigning a user to a group.
Attributes:
group_id: Group ID
user_id: User ID
"""
group_id: int = Field(..., alias="groupId", description="Group ID")
user_id: int = Field(..., alias="userId", description="User ID")
class Config:
"""Pydantic configuration."""
populate_by_name = True
class GroupUnassignUser(BaseModel):
"""Model for removing a user from a group.
Attributes:
group_id: Group ID
user_id: User ID
"""
group_id: int = Field(..., alias="groupId", description="Group ID")
user_id: int = Field(..., alias="userId", description="User ID")
class Config:
"""Pydantic configuration."""
populate_by_name = True