tallyfy 1.0.4__py3-none-any.whl → 1.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tallyfy might be problematic. Click here for more details.
- tallyfy/__init__.py +8 -4
- tallyfy/core.py +8 -8
- tallyfy/form_fields_management/__init__.py +70 -0
- tallyfy/form_fields_management/base.py +109 -0
- tallyfy/form_fields_management/crud_operations.py +234 -0
- tallyfy/form_fields_management/options_management.py +222 -0
- tallyfy/form_fields_management/suggestions.py +411 -0
- tallyfy/task_management/__init__.py +81 -0
- tallyfy/task_management/base.py +125 -0
- tallyfy/task_management/creation.py +221 -0
- tallyfy/task_management/retrieval.py +211 -0
- tallyfy/task_management/search.py +196 -0
- tallyfy/template_management/__init__.py +85 -0
- tallyfy/template_management/analysis.py +1093 -0
- tallyfy/template_management/automation.py +469 -0
- tallyfy/template_management/base.py +56 -0
- tallyfy/template_management/basic_operations.py +477 -0
- tallyfy/template_management/health_assessment.py +763 -0
- tallyfy/user_management/__init__.py +69 -0
- tallyfy/user_management/base.py +146 -0
- tallyfy/user_management/invitation.py +286 -0
- tallyfy/user_management/retrieval.py +339 -0
- {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/METADATA +120 -56
- tallyfy-1.0.5.dist-info/RECORD +28 -0
- tallyfy/BUILD.md +0 -5
- tallyfy/form_fields_management.py +0 -582
- tallyfy/task_management.py +0 -356
- tallyfy/template_management.py +0 -2607
- tallyfy/user_management.py +0 -235
- tallyfy-1.0.4.dist-info/RECORD +0 -13
- {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/WHEEL +0 -0
- {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {tallyfy-1.0.4.dist-info → tallyfy-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User Management Package
|
|
3
|
+
|
|
4
|
+
This package provides a refactored, modular approach to user management
|
|
5
|
+
functionality, breaking down the monolithic UserManagement class into
|
|
6
|
+
specialized components for better maintainability and separation of concerns.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
UserRetrieval: User and guest retrieval operations
|
|
10
|
+
UserInvitation: User invitation operations
|
|
11
|
+
UserManager: Unified interface combining all functionality
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .base import UserManagerBase
|
|
15
|
+
from .retrieval import UserRetrieval
|
|
16
|
+
from .invitation import UserInvitation
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UserManager:
|
|
20
|
+
"""
|
|
21
|
+
Unified interface for user management functionality.
|
|
22
|
+
|
|
23
|
+
This class provides access to all user management capabilities
|
|
24
|
+
through a single interface while maintaining the modular structure
|
|
25
|
+
underneath.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, sdk):
|
|
29
|
+
"""
|
|
30
|
+
Initialize user manager with SDK instance.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
sdk: Main SDK instance
|
|
34
|
+
"""
|
|
35
|
+
self.retrieval = UserRetrieval(sdk)
|
|
36
|
+
self.invitation = UserInvitation(sdk)
|
|
37
|
+
|
|
38
|
+
# For backward compatibility, expose common methods at the top level
|
|
39
|
+
|
|
40
|
+
# Retrieval methods
|
|
41
|
+
self.get_current_user_info = self.retrieval.get_current_user_info
|
|
42
|
+
self.get_organization_users = self.retrieval.get_organization_users
|
|
43
|
+
self.get_organization_users_list = self.retrieval.get_organization_users_list
|
|
44
|
+
self.get_organization_guests = self.retrieval.get_organization_guests
|
|
45
|
+
self.get_organization_guests_list = self.retrieval.get_organization_guests_list
|
|
46
|
+
self.get_all_organization_members = self.retrieval.get_all_organization_members
|
|
47
|
+
self.get_user_by_email = self.retrieval.get_user_by_email
|
|
48
|
+
self.get_guest_by_email = self.retrieval.get_guest_by_email
|
|
49
|
+
self.search_members_by_name = self.retrieval.search_members_by_name
|
|
50
|
+
|
|
51
|
+
# Invitation methods
|
|
52
|
+
self.invite_user_to_organization = self.invitation.invite_user_to_organization
|
|
53
|
+
self.invite_multiple_users = self.invitation.invite_multiple_users
|
|
54
|
+
self.resend_invitation = self.invitation.resend_invitation
|
|
55
|
+
self.invite_user_with_custom_role_permissions = self.invitation.invite_user_with_custom_role_permissions
|
|
56
|
+
self.get_invitation_template_message = self.invitation.get_invitation_template_message
|
|
57
|
+
self.validate_invitation_data = self.invitation.validate_invitation_data
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# For backward compatibility, create an alias
|
|
61
|
+
UserManagement = UserManager
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
'UserManagerBase',
|
|
65
|
+
'UserRetrieval',
|
|
66
|
+
'UserInvitation',
|
|
67
|
+
'UserManager',
|
|
68
|
+
'UserManagement' # Backward compatibility alias
|
|
69
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for user management operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, List, Dict, Any
|
|
6
|
+
from ..models import TallyfyError
|
|
7
|
+
from email_validator import validate_email, EmailNotValidError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UserManagerBase:
|
|
11
|
+
"""Base class providing common functionality for user management"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, sdk):
|
|
14
|
+
"""
|
|
15
|
+
Initialize base user manager.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
sdk: Main SDK instance
|
|
19
|
+
"""
|
|
20
|
+
self.sdk = sdk
|
|
21
|
+
|
|
22
|
+
def _validate_org_id(self, org_id: str) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Validate organization ID parameter.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
org_id: Organization ID to validate
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ValueError: If org_id is invalid
|
|
31
|
+
"""
|
|
32
|
+
if not org_id or not isinstance(org_id, str):
|
|
33
|
+
raise ValueError("Organization ID must be a non-empty string")
|
|
34
|
+
|
|
35
|
+
def _validate_email(self, email: str) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Validate email parameter.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
email: Email to validate
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: If email is invalid
|
|
44
|
+
"""
|
|
45
|
+
if not email or not isinstance(email, str):
|
|
46
|
+
raise ValueError("Email must be a non-empty string")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
validation = validate_email(email)
|
|
50
|
+
# The validated email address
|
|
51
|
+
email = validation.normalized
|
|
52
|
+
except EmailNotValidError as e:
|
|
53
|
+
raise ValueError(f"Invalid email address: {str(e)}")
|
|
54
|
+
# # Basic email validation
|
|
55
|
+
# import re
|
|
56
|
+
# email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
|
|
57
|
+
# if not re.match(email_pattern, email):
|
|
58
|
+
# raise ValueError(f"Invalid email address: {email}")
|
|
59
|
+
|
|
60
|
+
def _validate_name(self, name: str, field_name: str) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Validate name parameter.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
name: Name to validate
|
|
66
|
+
field_name: Name of the field for error messages
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If name is invalid
|
|
70
|
+
"""
|
|
71
|
+
if not name or not isinstance(name, str) or not name.strip():
|
|
72
|
+
raise ValueError(f"{field_name} must be a non-empty string")
|
|
73
|
+
|
|
74
|
+
def _validate_role(self, role: str) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Validate user role parameter.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
role: Role to validate
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If role is invalid
|
|
83
|
+
"""
|
|
84
|
+
valid_roles = ["light", "standard", "admin"]
|
|
85
|
+
if role not in valid_roles:
|
|
86
|
+
raise ValueError(f"Role must be one of: {', '.join(valid_roles)}")
|
|
87
|
+
|
|
88
|
+
def _extract_data(self, response_data, default_empty: bool = True) -> List[Dict[str, Any]]:
|
|
89
|
+
"""
|
|
90
|
+
Extract data from API response.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
response_data: Raw response from API
|
|
94
|
+
default_empty: Return empty list if no data found
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Extracted data list or single item, or empty list/None
|
|
98
|
+
"""
|
|
99
|
+
if isinstance(response_data, dict):
|
|
100
|
+
if 'data' in response_data:
|
|
101
|
+
return response_data['data']
|
|
102
|
+
return response_data if not default_empty else []
|
|
103
|
+
elif isinstance(response_data, list):
|
|
104
|
+
return response_data
|
|
105
|
+
return [] if default_empty else None
|
|
106
|
+
|
|
107
|
+
def _handle_api_error(self, error: Exception, operation: str, **context) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Handle API errors with context.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
error: The exception that occurred
|
|
113
|
+
operation: Description of the operation that failed
|
|
114
|
+
**context: Additional context for error logging
|
|
115
|
+
"""
|
|
116
|
+
context_str = ", ".join([f"{k}={v}" for k, v in context.items()])
|
|
117
|
+
error_msg = f"Failed to {operation}"
|
|
118
|
+
if context_str:
|
|
119
|
+
error_msg += f" ({context_str})"
|
|
120
|
+
error_msg += f": {error}"
|
|
121
|
+
|
|
122
|
+
self.sdk.logger.error(error_msg)
|
|
123
|
+
|
|
124
|
+
if isinstance(error, TallyfyError):
|
|
125
|
+
raise error
|
|
126
|
+
else:
|
|
127
|
+
raise TallyfyError(error_msg)
|
|
128
|
+
|
|
129
|
+
def _build_query_params(self, **kwargs) -> Dict[str, Any]:
|
|
130
|
+
"""
|
|
131
|
+
Build query parameters from keyword arguments, filtering out None values.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
**kwargs: Keyword arguments to convert to query parameters
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Dictionary of non-None parameters
|
|
138
|
+
"""
|
|
139
|
+
params = {}
|
|
140
|
+
for key, value in kwargs.items():
|
|
141
|
+
if value is not None:
|
|
142
|
+
if isinstance(value, bool):
|
|
143
|
+
params[key] = 'true' if value else 'false'
|
|
144
|
+
else:
|
|
145
|
+
params[key] = str(value)
|
|
146
|
+
return params
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
User invitation operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
from .base import UserManagerBase
|
|
7
|
+
from ..models import User, TallyfyError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UserInvitation(UserManagerBase):
|
|
11
|
+
"""Handles user invitation operations"""
|
|
12
|
+
|
|
13
|
+
def invite_user_to_organization(self, org_id: str, email: str, first_name: str, last_name: str,
|
|
14
|
+
role: str = "light", message: Optional[str] = None) -> Optional[User]:
|
|
15
|
+
"""
|
|
16
|
+
Invite a member to your organization.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
org_id: Organization ID
|
|
20
|
+
email: Email address of the user to invite
|
|
21
|
+
first_name: First name of the user (required)
|
|
22
|
+
last_name: Last name of the user (required)
|
|
23
|
+
role: User role - 'light', 'standard', or 'admin' (default: 'light')
|
|
24
|
+
message: Custom invitation message (optional)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
User object for the invited user
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
TallyfyError: If the request fails
|
|
31
|
+
ValueError: If parameters are invalid
|
|
32
|
+
"""
|
|
33
|
+
# Validate inputs
|
|
34
|
+
self._validate_org_id(org_id)
|
|
35
|
+
self._validate_email(email)
|
|
36
|
+
self._validate_name(first_name, "First name")
|
|
37
|
+
self._validate_name(last_name, "Last name")
|
|
38
|
+
self._validate_role(role)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
endpoint = f"organizations/{org_id}/users/invite"
|
|
42
|
+
|
|
43
|
+
invite_data = {
|
|
44
|
+
"email": email,
|
|
45
|
+
"first_name": first_name.strip(),
|
|
46
|
+
"last_name": last_name.strip(),
|
|
47
|
+
"role": role
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Add message if provided, otherwise use default
|
|
51
|
+
if message:
|
|
52
|
+
invite_data["message"] = message
|
|
53
|
+
else:
|
|
54
|
+
invite_data["message"] = "Please join Tallyfy - it's going to help us automate tasks between people."
|
|
55
|
+
|
|
56
|
+
response_data = self.sdk._make_request('POST', endpoint, data=invite_data)
|
|
57
|
+
|
|
58
|
+
user_data = self._extract_data(response_data, default_empty=False)
|
|
59
|
+
if user_data:
|
|
60
|
+
if isinstance(user_data, dict):
|
|
61
|
+
return User.from_dict(user_data)
|
|
62
|
+
elif isinstance(user_data, list) and user_data:
|
|
63
|
+
return User.from_dict(user_data[0])
|
|
64
|
+
|
|
65
|
+
self.sdk.logger.warning("Unexpected response format for user invitation")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
except TallyfyError:
|
|
69
|
+
raise
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self._handle_api_error(e, "invite user to organization", org_id=org_id, email=email)
|
|
72
|
+
|
|
73
|
+
def invite_multiple_users(self, org_id: str, invitations: List[dict],
|
|
74
|
+
default_role: str = "light", default_message: Optional[str] = None) -> List[Optional[User]]:
|
|
75
|
+
"""
|
|
76
|
+
Invite multiple users to the organization in batch.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
org_id: Organization ID
|
|
80
|
+
invitations: List of invitation dictionaries, each containing:
|
|
81
|
+
- email (required)
|
|
82
|
+
- first_name (required)
|
|
83
|
+
- last_name (required)
|
|
84
|
+
- role (optional, defaults to default_role)
|
|
85
|
+
- message (optional, defaults to default_message)
|
|
86
|
+
default_role: Default role for users where role is not specified
|
|
87
|
+
default_message: Default message for users where message is not specified
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
List of User objects for successfully invited users (None for failed invitations)
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
TallyfyError: If any invitation fails
|
|
94
|
+
ValueError: If parameters are invalid
|
|
95
|
+
"""
|
|
96
|
+
self._validate_org_id(org_id)
|
|
97
|
+
self._validate_role(default_role)
|
|
98
|
+
|
|
99
|
+
if not invitations or not isinstance(invitations, list):
|
|
100
|
+
raise ValueError("Invitations must be a non-empty list")
|
|
101
|
+
|
|
102
|
+
results = []
|
|
103
|
+
|
|
104
|
+
for i, invitation in enumerate(invitations):
|
|
105
|
+
if not isinstance(invitation, dict):
|
|
106
|
+
raise ValueError(f"Invitation {i} must be a dictionary")
|
|
107
|
+
|
|
108
|
+
# Validate required fields
|
|
109
|
+
required_fields = ['email', 'first_name', 'last_name']
|
|
110
|
+
for field in required_fields:
|
|
111
|
+
if field not in invitation:
|
|
112
|
+
raise ValueError(f"Invitation {i} missing required field: {field}")
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
# Use provided values or defaults
|
|
116
|
+
role = invitation.get('role', default_role)
|
|
117
|
+
message = invitation.get('message', default_message)
|
|
118
|
+
|
|
119
|
+
user = self.invite_user_to_organization(
|
|
120
|
+
org_id=org_id,
|
|
121
|
+
email=invitation['email'],
|
|
122
|
+
first_name=invitation['first_name'],
|
|
123
|
+
last_name=invitation['last_name'],
|
|
124
|
+
role=role,
|
|
125
|
+
message=message
|
|
126
|
+
)
|
|
127
|
+
results.append(user)
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.sdk.logger.error(f"Failed to invite user {invitation.get('email')}: {e}")
|
|
131
|
+
results.append(None)
|
|
132
|
+
# Continue with other invitations even if one fails
|
|
133
|
+
|
|
134
|
+
return results
|
|
135
|
+
|
|
136
|
+
def resend_invitation(self, org_id: str, email: str, message: Optional[str] = None) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Resend invitation to a user who was previously invited but hasn't joined.
|
|
139
|
+
|
|
140
|
+
Note: This is a convenience method that attempts to re-invite the user.
|
|
141
|
+
The API may handle this as a new invitation if the previous one expired.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
org_id: Organization ID
|
|
145
|
+
email: Email address of the user to re-invite
|
|
146
|
+
message: Custom invitation message (optional)
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
True if resend was successful
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
TallyfyError: If the request fails
|
|
153
|
+
ValueError: If parameters are invalid
|
|
154
|
+
"""
|
|
155
|
+
self._validate_org_id(org_id)
|
|
156
|
+
self._validate_email(email)
|
|
157
|
+
|
|
158
|
+
# Since there's no specific resend endpoint, we'll need to get user info first
|
|
159
|
+
# and then send a new invitation. This is a limitation of the current API.
|
|
160
|
+
|
|
161
|
+
# For now, we'll use a generic approach - this would need to be updated
|
|
162
|
+
# based on the actual API capabilities for resending invitations
|
|
163
|
+
try:
|
|
164
|
+
# Create a basic invitation message for resending
|
|
165
|
+
if not message:
|
|
166
|
+
message = "This is a reminder to join Tallyfy - please accept your invitation to help us automate tasks between people."
|
|
167
|
+
|
|
168
|
+
# Note: Without knowing the user's name, we'll use placeholder values
|
|
169
|
+
# In a real implementation, you might want to store invitation data
|
|
170
|
+
# or retrieve user info from a pending invitations endpoint
|
|
171
|
+
result = self.invite_user_to_organization(
|
|
172
|
+
org_id=org_id,
|
|
173
|
+
email=email,
|
|
174
|
+
first_name="User", # Placeholder - would need actual name
|
|
175
|
+
last_name="Invite", # Placeholder - would need actual name
|
|
176
|
+
role="light",
|
|
177
|
+
message=message
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return result is not None
|
|
181
|
+
|
|
182
|
+
except TallyfyError:
|
|
183
|
+
raise
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self._handle_api_error(e, "resend invitation", org_id=org_id, email=email)
|
|
186
|
+
|
|
187
|
+
def invite_user_with_custom_role_permissions(self, org_id: str, email: str, first_name: str,
|
|
188
|
+
last_name: str, role: str,
|
|
189
|
+
custom_permissions: Optional[dict] = None,
|
|
190
|
+
message: Optional[str] = None) -> Optional[User]:
|
|
191
|
+
"""
|
|
192
|
+
Invite a user with custom role and permissions (if supported by API).
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
org_id: Organization ID
|
|
196
|
+
email: Email address of the user to invite
|
|
197
|
+
first_name: First name of the user
|
|
198
|
+
last_name: Last name of the user
|
|
199
|
+
role: User role
|
|
200
|
+
custom_permissions: Custom permissions dictionary (if supported)
|
|
201
|
+
message: Custom invitation message
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
User object for the invited user
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
TallyfyError: If the request fails
|
|
208
|
+
ValueError: If parameters are invalid
|
|
209
|
+
"""
|
|
210
|
+
# For now, this is the same as regular invitation since the API
|
|
211
|
+
# doesn't appear to support custom permissions in the invite endpoint
|
|
212
|
+
# This method exists for future extensibility
|
|
213
|
+
|
|
214
|
+
if custom_permissions:
|
|
215
|
+
self.sdk.logger.warning("Custom permissions are not currently supported in invitations")
|
|
216
|
+
|
|
217
|
+
return self.invite_user_to_organization(
|
|
218
|
+
org_id=org_id,
|
|
219
|
+
email=email,
|
|
220
|
+
first_name=first_name,
|
|
221
|
+
last_name=last_name,
|
|
222
|
+
role=role,
|
|
223
|
+
message=message
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def get_invitation_template_message(self, org_name: Optional[str] = None,
|
|
227
|
+
custom_text: Optional[str] = None) -> str:
|
|
228
|
+
"""
|
|
229
|
+
Generate a customized invitation message template.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
org_name: Organization name to include in the message
|
|
233
|
+
custom_text: Additional custom text to include
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Formatted invitation message string
|
|
237
|
+
"""
|
|
238
|
+
base_message = "Please join Tallyfy - it's going to help us automate tasks between people."
|
|
239
|
+
|
|
240
|
+
if org_name:
|
|
241
|
+
base_message = f"Please join {org_name} on Tallyfy - it's going to help us automate tasks between people."
|
|
242
|
+
|
|
243
|
+
if custom_text:
|
|
244
|
+
base_message += f"\n\n{custom_text}"
|
|
245
|
+
|
|
246
|
+
return base_message
|
|
247
|
+
|
|
248
|
+
def validate_invitation_data(self, invitation_data: dict) -> dict:
|
|
249
|
+
"""
|
|
250
|
+
Validate and clean invitation data before sending.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
invitation_data: Dictionary containing invitation fields
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Validated and cleaned invitation data
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
ValueError: If validation fails
|
|
260
|
+
"""
|
|
261
|
+
required_fields = ['email', 'first_name', 'last_name']
|
|
262
|
+
|
|
263
|
+
# Check required fields
|
|
264
|
+
for field in required_fields:
|
|
265
|
+
if field not in invitation_data or not invitation_data[field]:
|
|
266
|
+
raise ValueError(f"Missing required field: {field}")
|
|
267
|
+
|
|
268
|
+
# Validate specific fields
|
|
269
|
+
self._validate_email(invitation_data['email'])
|
|
270
|
+
self._validate_name(invitation_data['first_name'], "First name")
|
|
271
|
+
self._validate_name(invitation_data['last_name'], "Last name")
|
|
272
|
+
|
|
273
|
+
# Validate role if provided
|
|
274
|
+
if 'role' in invitation_data:
|
|
275
|
+
self._validate_role(invitation_data['role'])
|
|
276
|
+
|
|
277
|
+
# Clean and return data
|
|
278
|
+
cleaned_data = {
|
|
279
|
+
'email': invitation_data['email'].strip().lower(),
|
|
280
|
+
'first_name': invitation_data['first_name'].strip(),
|
|
281
|
+
'last_name': invitation_data['last_name'].strip(),
|
|
282
|
+
'role': invitation_data.get('role', 'light'),
|
|
283
|
+
'message': invitation_data.get('message', self.get_invitation_template_message())
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return cleaned_data
|