mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-05 21:47:00 -04:00
refac
This commit is contained in:
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import errno
|
||||
from enum import Enum
|
||||
|
||||
|
||||
_ERRNO_MESSAGES = {
|
||||
errno.ENAMETOOLONG: 'File name is too long.',
|
||||
errno.ENOSPC: 'The server is out of storage space.',
|
||||
@@ -121,6 +120,7 @@ class ERROR_MESSAGES(str, Enum):
|
||||
AUTOMATION_COUNT_REQUIRES_DTSTART = (
|
||||
'RRULE with COUNT requires an explicit DTSTART line to anchor the occurrence window'
|
||||
)
|
||||
CALENDAR_RRULE_TOO_FREQUENT = 'Recurring events cannot repeat more often than daily'
|
||||
|
||||
FEATURE_DISABLED = lambda name='': f'{name} is disabled'
|
||||
INPUT_TOO_LONG = lambda size='': f'Input prompt exceeds maximum length of {size}'
|
||||
|
||||
@@ -4,10 +4,12 @@ from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from open_webui.internal.db import Base, get_async_db_context
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import User, UserModel, UserResponse
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from open_webui.utils.automations import rrule_interval_seconds
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
BigInteger,
|
||||
@@ -26,6 +28,7 @@ from sqlalchemy import (
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
MIN_CALENDAR_RRULE_INTERVAL_SECONDS = 24 * 60 * 60
|
||||
|
||||
|
||||
####################
|
||||
@@ -191,6 +194,20 @@ class CalendarEventForm(BaseModel):
|
||||
meta: Optional[dict] = None
|
||||
attendees: Optional[list[dict]] = None
|
||||
|
||||
@field_validator('rrule')
|
||||
@classmethod
|
||||
def reject_sub_daily_rrule(cls, value: Optional[str]) -> Optional[str]:
|
||||
if value:
|
||||
try:
|
||||
interval = rrule_interval_seconds(value)
|
||||
except ValueError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e))
|
||||
if interval is not None and interval < MIN_CALENDAR_RRULE_INTERVAL_SECONDS:
|
||||
raise ValueError(ERROR_MESSAGES.CALENDAR_RRULE_TOO_FREQUENT)
|
||||
return value
|
||||
|
||||
|
||||
class CalendarEventUpdateForm(BaseModel):
|
||||
calendar_id: Optional[str] = None
|
||||
@@ -207,6 +224,20 @@ class CalendarEventUpdateForm(BaseModel):
|
||||
is_cancelled: Optional[bool] = None
|
||||
attendees: Optional[list[dict]] = None
|
||||
|
||||
@field_validator('rrule')
|
||||
@classmethod
|
||||
def reject_sub_daily_rrule(cls, value: Optional[str]) -> Optional[str]:
|
||||
if value:
|
||||
try:
|
||||
interval = rrule_interval_seconds(value)
|
||||
except ValueError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e))
|
||||
if interval is not None and interval < MIN_CALENDAR_RRULE_INTERVAL_SECONDS:
|
||||
raise ValueError(ERROR_MESSAGES.CALENDAR_RRULE_TOO_FREQUENT)
|
||||
return value
|
||||
|
||||
|
||||
class RSVPForm(BaseModel):
|
||||
status: str # 'accepted' | 'declined' | 'tentative' | 'pending'
|
||||
|
||||
@@ -8,6 +8,7 @@ import datetime as dt
|
||||
import logging
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil.rrule import rrulestr
|
||||
from open_webui.utils.automations import _resolve_tz
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -25,18 +26,17 @@ def expand_recurring_event(
|
||||
Takes an event dict (from CalendarEventModel.model_dump()) and produces
|
||||
one dict per occurrence, with adjusted start_at / end_at.
|
||||
"""
|
||||
from dateutil.rrule import rrulestr
|
||||
|
||||
rrule_str = event_dict.get('rrule')
|
||||
if not rrule_str:
|
||||
return [event_dict]
|
||||
if 'EXRULE' in rrule_str.upper():
|
||||
log.warning(f'EXRULE is not supported for event {event_dict.get("id")}: {rrule_str}')
|
||||
return [event_dict]
|
||||
|
||||
user_timezone = _resolve_tz(tz)
|
||||
|
||||
def to_local_datetime(timestamp_ns: int) -> dt.datetime:
|
||||
if user_timezone:
|
||||
return dt.datetime.fromtimestamp(timestamp_ns / 1_000_000_000, tz=user_timezone).replace(tzinfo=None)
|
||||
return dt.datetime.fromtimestamp(timestamp_ns / 1_000_000_000)
|
||||
return dt.datetime.fromtimestamp(timestamp_ns / 1_000_000_000, tz=user_timezone).replace(tzinfo=None)
|
||||
|
||||
range_start = to_local_datetime(range_start_ns)
|
||||
range_end = to_local_datetime(range_end_ns)
|
||||
@@ -44,10 +44,11 @@ def expand_recurring_event(
|
||||
|
||||
original_start_ns = event_dict['start_at']
|
||||
original_start = to_local_datetime(original_start_ns)
|
||||
rule_str = '\n'.join(line for line in rrule_str.splitlines() if not line.upper().startswith('DTSTART')) or rrule_str
|
||||
|
||||
try:
|
||||
# Anchor to the event's real start so day-of-week / day-of-month are correct
|
||||
rule = rrulestr(rrule_str, dtstart=original_start, ignoretz=True)
|
||||
rule = rrulestr(rule_str, dtstart=original_start, ignoretz=True)
|
||||
except Exception:
|
||||
log.warning(f'Failed to parse RRULE for event {event_dict.get("id")}: {rrule_str}')
|
||||
return [event_dict]
|
||||
@@ -56,13 +57,13 @@ def expand_recurring_event(
|
||||
duration_ns = (original_end_ns - original_start_ns) if original_end_ns else None
|
||||
|
||||
instances = []
|
||||
occurrence_start = rule.after(scan_start, inc=True)
|
||||
previous_start = None
|
||||
for occurrence_start in rule.xafter(scan_start, count=max_instances, inc=True):
|
||||
if occurrence_start >= range_end or occurrence_start == previous_start:
|
||||
break
|
||||
previous_start = occurrence_start
|
||||
|
||||
while occurrence_start and occurrence_start < range_end and len(instances) < max_instances:
|
||||
if user_timezone:
|
||||
instance_start_ns = int(occurrence_start.replace(tzinfo=user_timezone).timestamp() * 1_000_000_000)
|
||||
else:
|
||||
instance_start_ns = int(occurrence_start.timestamp() * 1_000_000_000)
|
||||
instance_start_ns = int(occurrence_start.replace(tzinfo=user_timezone).timestamp() * 1_000_000_000)
|
||||
|
||||
if instance_start_ns >= range_start_ns:
|
||||
instance = {
|
||||
@@ -73,8 +74,6 @@ def expand_recurring_event(
|
||||
}
|
||||
instances.append(instance)
|
||||
|
||||
occurrence_start = rule.after(occurrence_start)
|
||||
|
||||
return instances
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user