vikunja-python 0.1.0__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.
@@ -0,0 +1,838 @@
1
+ """
2
+ User and Team Models for Vikunja API
3
+
4
+ Covers: user.User, v1.UserSettings, models.ProjectUser, models.Team, etc.
5
+ API endpoints: /user, /teams, /projects/{id}/users
6
+
7
+ User Management:
8
+ - User profiles with settings
9
+ - Authentication (login, register, password reset)
10
+ - TOTP 2FA setup and verification
11
+
12
+ Team Management:
13
+ - Team creation and membership
14
+ - Project-team associations with permissions
15
+ - Team-based access control
16
+
17
+ Permission Levels (3 levels):
18
+ - 0 (Read): Read-only access
19
+ - 1 (Write): Read + Create/Update/Delete own items
20
+ - 2 (Admin): Full access including member management
21
+
22
+ Usage Examples:
23
+ # Login and get token
24
+ login = LoginRequest(username="alice", password="secret", long_token=True)
25
+
26
+ # Create a team
27
+ team = TeamCreateRequest(
28
+ name="Engineering",
29
+ description="Core engineering team",
30
+ is_public=True
31
+ )
32
+
33
+ # Add user to project with write permission
34
+ project_user = ProjectUserCreateRequest(
35
+ username="bob",
36
+ permission=Permission.WRITE # or 1
37
+ )
38
+ """
39
+
40
+ from pydantic import Field, field_validator
41
+ from datetime import datetime
42
+ from typing import Optional, Dict, Any, Literal
43
+ from .base import VikunjaBaseModel
44
+
45
+
46
+ # ============================================================================
47
+ # Permission Enum (3 levels)
48
+ # ============================================================================
49
+
50
+ Permission = Literal[0, 1, 2]
51
+ """Permission level for project/team access.
52
+
53
+ API spec: models.Permission
54
+
55
+ Values:
56
+ 0 (Read): Read-only access
57
+ 1 (Write): Read + Create/Update/Delete own items
58
+ 2 (Admin): Full access including member management
59
+
60
+ Usage:
61
+ from models.user import Permission
62
+
63
+ # Grant write access to a user
64
+ project_user = ProjectUserCreateRequest(
65
+ username="bob",
66
+ permission=Permission.WRITE # or simply 1
67
+ )
68
+ """
69
+
70
+ # Named constants for clarity
71
+ Permission.READ: Permission = 0
72
+ Permission.WRITE: Permission = 1
73
+ Permission.ADMIN: Permission = 2
74
+
75
+ PERMISSION_DESCRIPTIONS: Dict[Permission, str] = {
76
+ 0: "Read-only access - can view but not modify",
77
+ 1: "Write access - can create, update, delete own items",
78
+ 2: "Admin access - full control including member management",
79
+ }
80
+
81
+
82
+ # ============================================================================
83
+ # Core User Models
84
+ # ============================================================================
85
+
86
+ class User(VikunjaBaseModel):
87
+ """
88
+ Basic user information.
89
+
90
+ API endpoint: Referenced in many endpoints (assignees, created_by, etc.)
91
+
92
+ This is the minimal user object returned in nested contexts like task
93
+ assignees or label creators. For full user profiles, use UserWithSettings.
94
+
95
+ Fields from API spec (user.User):
96
+ - id: Unique user identifier
97
+ - username: Unique username (1-250 chars)
98
+ - name: Full display name
99
+ - email: User's email address (max 250 chars)
100
+ - created: Account creation timestamp
101
+ - updated: Last update timestamp
102
+
103
+ Example from API response:
104
+ {
105
+ "id": 1,
106
+ "username": "alice",
107
+ "name": "Alice Johnson",
108
+ "email": "alice@example.com",
109
+ "created": "2024-01-15T10:30:00Z",
110
+ "updated": "2024-01-20T14:45:00Z"
111
+ }
112
+ """
113
+
114
+ # Core Identification (2 fields)
115
+ id: Optional[int] = Field(None, description="Unique user ID")
116
+ username: str = Field(
117
+ ...,
118
+ min_length=1,
119
+ max_length=250,
120
+ description="Unique username"
121
+ )
122
+
123
+ # Profile Information (2 fields)
124
+ name: Optional[str] = Field(None, description="Full display name")
125
+ email: Optional[str] = Field(
126
+ None,
127
+ max_length=250,
128
+ description="User's email address"
129
+ )
130
+
131
+ # Timestamps (2 fields - auto-managed)
132
+ created: Optional[datetime] = Field(None, description="Account creation timestamp")
133
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
134
+
135
+
136
+ class UserSettings(VikunjaBaseModel):
137
+ """
138
+ User settings and preferences.
139
+
140
+ API endpoint: Part of UserWithSettings response
141
+
142
+ Contains user-specific configuration for the Vikunja instance.
143
+
144
+ Fields from API spec (v1.UserSettings):
145
+ - default_project_id: Default project for new tasks
146
+ - language: User's language preference
147
+ - timezone: Timezone for reminders and displays
148
+ - week_start: Day when week starts (0=Sunday, 1=Monday)
149
+ - discoverable_by_email/name: Privacy settings
150
+ - email_reminders_enabled: Email notification toggle
151
+ - overdue_tasks_reminders_enabled/Time: Daily summary settings
152
+ - extra_settings_links: OpenID-provided links
153
+ - frontend_settings: Frontend-specific settings
154
+
155
+ Example from API response:
156
+ {
157
+ "default_project_id": 1,
158
+ "language": "en",
159
+ "timezone": "America/New_York",
160
+ "week_start": 1,
161
+ "discoverable_by_email": true,
162
+ "discoverable_by_name": true,
163
+ "email_reminders_enabled": true,
164
+ "overdue_tasks_reminders_enabled": true,
165
+ "overdue_tasks_reminders_time": "09:00"
166
+ }
167
+ """
168
+
169
+ # Project Settings (1 field)
170
+ default_project_id: Optional[int] = Field(
171
+ None,
172
+ description="Default project ID for new tasks without specified project"
173
+ )
174
+
175
+ # Localization (3 fields)
176
+ language: Optional[str] = Field(None, description="User's language preference")
177
+ timezone: Optional[str] = Field(None, description="Timezone for reminders/displays")
178
+ week_start: Optional[int] = Field(
179
+ None,
180
+ ge=0,
181
+ le=6,
182
+ description="Day when week starts (0=Sunday, 1=Monday, etc.)"
183
+ )
184
+
185
+ # Privacy Settings (2 fields)
186
+ discoverable_by_email: Optional[bool] = Field(
187
+ None,
188
+ description="Can be found by searching exact email"
189
+ )
190
+ discoverable_by_name: Optional[bool] = Field(
191
+ None,
192
+ description="Can be found by searching name/partial name"
193
+ )
194
+
195
+ # Notification Settings (3 fields)
196
+ email_reminders_enabled: Optional[bool] = Field(
197
+ None,
198
+ description="Enable email reminders for tasks"
199
+ )
200
+ overdue_tasks_reminders_enabled: Optional[bool] = Field(
201
+ None,
202
+ description="Enable daily overdue task summaries"
203
+ )
204
+ overdue_tasks_reminders_time: Optional[str] = Field(
205
+ None,
206
+ description="Time for daily overdue summary (e.g., '09:00')"
207
+ )
208
+
209
+ # Extended Settings (2 fields)
210
+ extra_settings_links: Optional[Dict[str, Any]] = Field(
211
+ None,
212
+ description="Additional settings links from OpenID"
213
+ )
214
+ frontend_settings: Optional[Dict[str, Any]] = Field(
215
+ None,
216
+ description="Frontend-specific settings"
217
+ )
218
+
219
+
220
+ class UserWithSettings(VikunjaBaseModel):
221
+ """
222
+ Full user profile with settings.
223
+
224
+ API endpoint: GET /user
225
+
226
+ Combines basic user info with their complete settings.
227
+
228
+ Fields from API spec (v1.UserWithSettings):
229
+ - All User fields (id, username, name, email, created, updated)
230
+ - settings: Complete UserSettings object
231
+ - auth_provider: Authentication provider type
232
+ - is_local_user: Whether user is locally authenticated
233
+ - deletion_scheduled_at: Account deletion timestamp (if scheduled)
234
+
235
+ Example from API response:
236
+ {
237
+ "id": 1,
238
+ "username": "alice",
239
+ "name": "Alice Johnson",
240
+ "email": "alice@example.com",
241
+ "created": "2024-01-15T10:30:00Z",
242
+ "updated": "2024-01-20T14:45:00Z",
243
+ "settings": {...},
244
+ "auth_provider": "local",
245
+ "is_local_user": true
246
+ }
247
+ """
248
+
249
+ # Core User Fields (6 fields)
250
+ id: Optional[int] = Field(None, description="Unique user ID")
251
+ username: str = Field(
252
+ ...,
253
+ min_length=1,
254
+ max_length=250,
255
+ description="Unique username"
256
+ )
257
+ name: Optional[str] = Field(None, description="Full display name")
258
+ email: Optional[str] = Field(
259
+ None,
260
+ max_length=250,
261
+ description="User's email address"
262
+ )
263
+ created: Optional[datetime] = Field(None, description="Account creation timestamp")
264
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
265
+
266
+ # Settings (1 field)
267
+ settings: Optional[UserSettings] = Field(None, description="User settings and preferences")
268
+
269
+ # Authentication Info (3 fields)
270
+ auth_provider: Optional[str] = Field(None, description="Authentication provider type")
271
+ is_local_user: Optional[bool] = Field(None, description="Whether user is locally authenticated")
272
+ deletion_scheduled_at: Optional[datetime] = Field(
273
+ None,
274
+ description="Account deletion timestamp (if scheduled)"
275
+ )
276
+
277
+
278
+ # ============================================================================
279
+ # Authentication Request Models
280
+ # ============================================================================
281
+
282
+ class LoginRequest(VikunjaBaseModel):
283
+ """
284
+ Request body for user login.
285
+
286
+ API endpoint: POST /user/login
287
+
288
+ Required fields: username, password
289
+ Optional fields: totp_passcode (if 2FA enabled), long_token
290
+
291
+ Example:
292
+ req = LoginRequest(
293
+ username="alice",
294
+ password="secret123",
295
+ totp_passcode="123456", # If 2FA enabled
296
+ long_token=True # "Remember me" style login
297
+ )
298
+ """
299
+ username: str = Field(..., description="Username for login")
300
+ password: str = Field(..., description="User's password")
301
+ totp_passcode: Optional[str] = Field(None, description="TOTP passcode if 2FA enabled")
302
+ long_token: Optional[bool] = Field(
303
+ None,
304
+ description="Long-lived token for 'remember me' style login"
305
+ )
306
+
307
+ def model_dump_for_api(self) -> dict:
308
+ """Convert to dict for API submission."""
309
+ return self.model_dump(exclude_none=True)
310
+
311
+
312
+ class RegisterRequest(VikunjaBaseModel):
313
+ """
314
+ Request body for user registration.
315
+
316
+ API endpoint: POST /user
317
+
318
+ Note: Full structure may vary based on Vikunja configuration.
319
+ This is a placeholder - verify with actual API response.
320
+
321
+ UNKNOWN: Full register request structure requires API testing
322
+ """
323
+ # Placeholder - needs verification from actual API
324
+ username: Optional[str] = Field(None, description="Desired username")
325
+ email: Optional[str] = Field(None, description="User's email address")
326
+ password: Optional[str] = Field(None, description="Desired password")
327
+
328
+
329
+ class EmailUpdateRequest(VikunjaBaseModel):
330
+ """
331
+ Request body for updating email address.
332
+
333
+ API endpoint: POST /user/email
334
+
335
+ Required fields: new_email, password (for confirmation)
336
+
337
+ Example:
338
+ req = EmailUpdateRequest(
339
+ new_email="alice@newdomain.com",
340
+ password="current_password"
341
+ )
342
+ """
343
+ new_email: str = Field(..., description="New email address (must be valid)")
344
+ password: str = Field(..., description="Current password for confirmation")
345
+
346
+ def model_dump_for_api(self) -> dict:
347
+ """Convert to dict for API submission."""
348
+ return self.model_dump(exclude_none=True)
349
+
350
+
351
+ class PasswordResetRequest(VikunjaBaseModel):
352
+ """
353
+ Request body for password reset.
354
+
355
+ API endpoint: POST /user/password/reset
356
+
357
+ Required fields: token, new_password
358
+
359
+ Password requirements: 8-72 characters
360
+
361
+ Example:
362
+ req = PasswordResetRequest(
363
+ token="reset_token_from_email",
364
+ new_password="new_secure_password123"
365
+ )
366
+ """
367
+ token: str = Field(..., description="Password reset token from email")
368
+ new_password: str = Field(
369
+ ...,
370
+ min_length=8,
371
+ max_length=72,
372
+ description="New password (8-72 characters)"
373
+ )
374
+
375
+ @field_validator('new_password')
376
+ @classmethod
377
+ def validate_password_strength(cls, v):
378
+ """Basic password validation."""
379
+ if len(v) < 8:
380
+ raise ValueError("Password must be at least 8 characters")
381
+ return v
382
+
383
+ def model_dump_for_api(self) -> dict:
384
+ """Convert to dict for API submission."""
385
+ return self.model_dump(exclude_none=True)
386
+
387
+
388
+ # ============================================================================
389
+ # TOTP (2FA) Models
390
+ # ============================================================================
391
+
392
+ class TOTPSetupResponse(VikunjaBaseModel):
393
+ """
394
+ Response for TOTP setup initiation.
395
+
396
+ API endpoint: POST /user/totp
397
+
398
+ Returns the secret and URL needed to configure TOTP authenticator app.
399
+
400
+ Fields from API spec (models.TOTP):
401
+ - secret: TOTP secret key
402
+ - url: OTPAuth URL for QR code generation
403
+ - enabled: Whether TOTP is enabled (false until verified)
404
+
405
+ Example from API response:
406
+ {
407
+ "secret": "JBSWY3DPEHPK3PXP",
408
+ "url": "otpauth://totp/Vikunja:alice?secret=JBSWY3DPEHPK3PXP...",
409
+ "enabled": false
410
+ }
411
+ """
412
+ secret: Optional[str] = Field(None, description="TOTP secret key")
413
+ url: Optional[str] = Field(None, description="OTPAuth URL for QR code")
414
+ enabled: Optional[bool] = Field(None, description="Whether TOTP is enabled")
415
+
416
+
417
+ class TOTPPasscodeRequest(VikunjaBaseModel):
418
+ """
419
+ Request body for TOTP passcode verification.
420
+
421
+ API endpoint: POST /user/totp/verify
422
+
423
+ Required fields: passcode
424
+
425
+ Example:
426
+ req = TOTPPasscodeRequest(passcode="123456")
427
+ """
428
+ passcode: str = Field(..., description="TOTP passcode from authenticator app")
429
+
430
+ def model_dump_for_api(self) -> dict:
431
+ """Convert to dict for API submission."""
432
+ return self.model_dump(exclude_none=True)
433
+
434
+
435
+ # ============================================================================
436
+ # Project User Models (Project Membership)
437
+ # ============================================================================
438
+
439
+ class ProjectUser(VikunjaBaseModel):
440
+ """
441
+ User's membership in a project with permission level.
442
+
443
+ API endpoint: GET /projects/{id}/users
444
+
445
+ Represents the relationship between a user and a project.
446
+
447
+ Fields from API spec (models.ProjectUser):
448
+ - id: Unique relation identifier
449
+ - username: Username of the member
450
+ - permission: Permission level (0=Read, 1=Write, 2=Admin)
451
+ - created: Relation creation timestamp
452
+ - updated: Last update timestamp
453
+
454
+ Example from API response:
455
+ {
456
+ "id": 5,
457
+ "username": "bob",
458
+ "permission": 1,
459
+ "created": "2024-01-15T10:30:00Z",
460
+ "updated": "2024-01-15T10:30:00Z"
461
+ }
462
+ """
463
+
464
+ # Core Identification (2 fields)
465
+ id: Optional[int] = Field(None, description="Unique relation ID")
466
+ username: str = Field(..., description="Username of the project member")
467
+
468
+ # Access Control (1 field)
469
+ permission: Optional[Permission] = Field(
470
+ 0,
471
+ description="Permission level: 0=Read, 1=Write, 2=Admin"
472
+ )
473
+
474
+ # Timestamps (2 fields - auto-managed)
475
+ created: Optional[datetime] = Field(None, description="Relation creation timestamp")
476
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
477
+
478
+
479
+ class ProjectUserCreateRequest(VikunjaBaseModel):
480
+ """
481
+ Request body for adding a user to a project.
482
+
483
+ API endpoint: POST /projects/{id}/users
484
+
485
+ Required fields: username
486
+ Optional fields: permission (defaults to 0 = Read)
487
+
488
+ Example:
489
+ req = ProjectUserCreateRequest(
490
+ username="bob",
491
+ permission=Permission.WRITE # or 1
492
+ )
493
+ """
494
+ username: str = Field(..., description="Username to add to project")
495
+ permission: Optional[Permission] = Field(
496
+ 0,
497
+ description="Permission level (default: 0=Read)"
498
+ )
499
+
500
+ def model_dump_for_api(self) -> dict:
501
+ """Convert to dict for API submission."""
502
+ return self.model_dump(exclude_none=True)
503
+
504
+
505
+ class ProjectUserUpdateRequest(VikunjaBaseModel):
506
+ """
507
+ Request body for updating project membership.
508
+
509
+ API endpoint: POST /projects/{id}/users/{username}
510
+
511
+ Only provided fields are updated.
512
+
513
+ Example:
514
+ req = ProjectUserUpdateRequest(
515
+ permission=Permission.ADMIN # Promote to admin
516
+ )
517
+ """
518
+ permission: Optional[Permission] = Field(None, description="New permission level")
519
+
520
+ def model_dump_for_api(self) -> dict:
521
+ """Convert to dict, excluding None values."""
522
+ return self.model_dump(exclude_none=True)
523
+
524
+
525
+ # ============================================================================
526
+ # Team Models
527
+ # ============================================================================
528
+
529
+ class TeamUser(VikunjaBaseModel):
530
+ """
531
+ User information within a team context.
532
+
533
+ API endpoint: Part of Team response (members array)
534
+
535
+ Similar to basic User but used specifically in team membership lists.
536
+
537
+ Fields from API spec (models.TeamUser):
538
+ - id: User ID
539
+ - username: Unique username
540
+ - name: Full display name
541
+ - email: User's email
542
+ - admin: Whether user is team admin
543
+ - created/updated: Timestamps
544
+
545
+ Example from API response:
546
+ {
547
+ "id": 1,
548
+ "username": "alice",
549
+ "name": "Alice Johnson",
550
+ "email": "alice@example.com",
551
+ "admin": true,
552
+ "created": "2024-01-15T10:30:00Z",
553
+ "updated": "2024-01-15T10:30:00Z"
554
+ }
555
+ """
556
+
557
+ # Core Identification (2 fields)
558
+ id: Optional[int] = Field(None, description="User ID")
559
+ username: str = Field(
560
+ ...,
561
+ min_length=1,
562
+ max_length=250,
563
+ description="Unique username"
564
+ )
565
+
566
+ # Profile Information (2 fields)
567
+ name: Optional[str] = Field(None, description="Full display name")
568
+ email: Optional[str] = Field(
569
+ None,
570
+ max_length=250,
571
+ description="User's email address"
572
+ )
573
+
574
+ # Team Role (1 field)
575
+ admin: Optional[bool] = Field(
576
+ None,
577
+ description="Whether member is team admin"
578
+ )
579
+
580
+ # Timestamps (2 fields - auto-managed)
581
+ created: Optional[datetime] = Field(None, description="Membership creation timestamp")
582
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
583
+
584
+
585
+ class TeamMember(VikunjaBaseModel):
586
+ """
587
+ Team membership relation.
588
+
589
+ API endpoint: GET /teams/{id}/members
590
+
591
+ Represents a user's membership in a team (without full user details).
592
+
593
+ Fields from API spec (models.TeamMember):
594
+ - id: Unique relation identifier
595
+ - username: Username of the member
596
+ - admin: Whether member is team admin
597
+ - created: Membership creation timestamp
598
+
599
+ Example from API response:
600
+ {
601
+ "id": 10,
602
+ "username": "bob",
603
+ "admin": false,
604
+ "created": "2024-01-15T10:30:00Z"
605
+ }
606
+ """
607
+
608
+ # Core Identification (2 fields)
609
+ id: Optional[int] = Field(None, description="Unique relation ID")
610
+ username: str = Field(
611
+ ...,
612
+ min_length=1,
613
+ max_length=250,
614
+ description="Username of the team member"
615
+ )
616
+
617
+ # Role (1 field)
618
+ admin: Optional[bool] = Field(None, description="Whether member is team admin")
619
+
620
+ # Timestamp (1 field - auto-managed)
621
+ created: Optional[datetime] = Field(None, description="Membership creation timestamp")
622
+
623
+
624
+ class Team(VikunjaBaseModel):
625
+ """
626
+ A team of users for collaborative access control.
627
+
628
+ API endpoint: GET /teams
629
+
630
+ Teams allow grouping users for shared project permissions.
631
+
632
+ Fields from API spec (models.Team):
633
+ - id: Unique team identifier
634
+ - name: Team name (1-250 chars)
635
+ - description: Optional team description
636
+ - is_public: Whether team is discoverable when sharing projects
637
+ - external_id: External ID from OpenID/LDAP provider
638
+ - members: Array of TeamUser objects
639
+ - created_by: User who created the team
640
+ - created/updated: Timestamps
641
+
642
+ Example from API response:
643
+ {
644
+ "id": 1,
645
+ "name": "Engineering",
646
+ "description": "Core engineering team",
647
+ "is_public": true,
648
+ "external_id": null,
649
+ "members": [...],
650
+ "created_by": {...},
651
+ "created": "2024-01-15T10:30:00Z",
652
+ "updated": "2024-01-20T14:45:00Z"
653
+ }
654
+ """
655
+
656
+ # Core Identification (2 fields)
657
+ id: Optional[int] = Field(None, description="Unique team ID")
658
+ name: str = Field(
659
+ ...,
660
+ min_length=1,
661
+ max_length=250,
662
+ description="Team name"
663
+ )
664
+
665
+ # Team Configuration (3 fields)
666
+ description: Optional[str] = Field(None, description="Team description")
667
+ is_public: Optional[bool] = Field(
668
+ None,
669
+ description="Whether team is discoverable when sharing projects"
670
+ )
671
+ external_id: Optional[str] = Field(
672
+ None,
673
+ max_length=250,
674
+ description="External ID from OpenID/LDAP provider"
675
+ )
676
+
677
+ # Members (1 field)
678
+ members: Optional[list[TeamUser]] = Field(
679
+ None,
680
+ description="Array of team members"
681
+ )
682
+
683
+ # Metadata (3 fields)
684
+ created_by: Optional[User] = Field(None, description="User who created this team")
685
+ created: Optional[datetime] = Field(None, description="Team creation timestamp")
686
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
687
+
688
+
689
+ class TeamCreateRequest(VikunjaBaseModel):
690
+ """
691
+ Request body for creating a team.
692
+
693
+ API endpoint: POST /teams
694
+
695
+ Required fields: name
696
+ Optional fields: description, is_public
697
+
698
+ Example:
699
+ req = TeamCreateRequest(
700
+ name="Engineering",
701
+ description="Core engineering team",
702
+ is_public=True
703
+ )
704
+ """
705
+ name: str = Field(
706
+ ...,
707
+ min_length=1,
708
+ max_length=250,
709
+ description="Team name (required)"
710
+ )
711
+ description: Optional[str] = Field(None, description="Team description")
712
+ is_public: Optional[bool] = Field(
713
+ None,
714
+ description="Whether team is discoverable when sharing projects"
715
+ )
716
+
717
+ def model_dump_for_api(self) -> dict:
718
+ """Convert to dict for API submission."""
719
+ return self.model_dump(exclude_none=True)
720
+
721
+
722
+ class TeamUpdateRequest(VikunjaBaseModel):
723
+ """
724
+ Request body for updating a team.
725
+
726
+ API endpoint: POST /teams/{id}
727
+
728
+ All fields are optional - only provided fields are updated.
729
+
730
+ Example:
731
+ req = TeamUpdateRequest(
732
+ description="Updated engineering team description"
733
+ )
734
+ """
735
+ name: Optional[str] = Field(None, min_length=1, max_length=250, description="New team name")
736
+ description: Optional[str] = Field(None, description="New team description")
737
+ is_public: Optional[bool] = Field(None, description="New public visibility setting")
738
+
739
+ def model_dump_for_api(self) -> dict:
740
+ """Convert to dict, excluding None values."""
741
+ return self.model_dump(exclude_none=True)
742
+
743
+
744
+ class TeamWithPermission(Team):
745
+ """
746
+ Team with an additional permission level.
747
+
748
+ API endpoint: Used in team-project contexts
749
+
750
+ Extends Team with a permission field for project-team relationships.
751
+
752
+ Fields from API spec (models.TeamWithPermission):
753
+ - All Team fields
754
+ - permission: Permission level for the context (0=Read, 1=Write, 2=Admin)
755
+ """
756
+ permission: Optional[Permission] = Field(
757
+ None,
758
+ description="Permission level in this context"
759
+ )
760
+
761
+
762
+ class TeamProject(VikunjaBaseModel):
763
+ """
764
+ Team's membership in a project with permission level.
765
+
766
+ API endpoint: GET /projects/{id}/teams
767
+
768
+ Represents the relationship between a team and a project.
769
+
770
+ Fields from API spec (models.TeamProject):
771
+ - id: Unique relation identifier
772
+ - team_id: Team ID
773
+ - permission: Permission level (0=Read, 1=Write, 2=Admin)
774
+ - created/updated: Timestamps
775
+
776
+ Example from API response:
777
+ {
778
+ "id": 15,
779
+ "team_id": 1,
780
+ "permission": 2,
781
+ "created": "2024-01-15T10:30:00Z",
782
+ "updated": "2024-01-15T10:30:00Z"
783
+ }
784
+ """
785
+
786
+ # Core Identification (2 fields)
787
+ id: Optional[int] = Field(None, description="Unique relation ID")
788
+ team_id: int = Field(..., description="Team ID")
789
+
790
+ # Access Control (1 field)
791
+ permission: Optional[Permission] = Field(
792
+ 0,
793
+ description="Permission level: 0=Read, 1=Write, 2=Admin"
794
+ )
795
+
796
+ # Timestamps (2 fields - auto-managed)
797
+ created: Optional[datetime] = Field(None, description="Relation creation timestamp")
798
+ updated: Optional[datetime] = Field(None, description="Last update timestamp")
799
+
800
+
801
+ # ============================================================================
802
+ # Response Models
803
+ # ============================================================================
804
+
805
+ class UserGetResponse(VikunjaBaseModel):
806
+ """Response for getting current user."""
807
+ success: bool = Field(True, description="Request succeeded")
808
+ user: Optional[UserWithSettings] = Field(None, description="Current user with settings")
809
+ error: Optional[str] = Field(None, description="Error message if failed")
810
+
811
+
812
+ class LoginResponse(VikunjaBaseModel):
813
+ """Response for login request."""
814
+ success: bool = Field(True, description="Request succeeded")
815
+ token: Optional[str] = Field(None, description="Authentication token")
816
+ user: Optional[User] = Field(None, description="Logged in user")
817
+ error: Optional[str] = Field(None, description="Error message if failed")
818
+
819
+
820
+ class TeamListResponse(VikunjaBaseModel):
821
+ """Response for listing teams."""
822
+ success: bool = Field(True, description="Request succeeded")
823
+ teams: list[Team] = Field(default_factory=list, description="List of teams")
824
+ error: Optional[str] = Field(None, description="Error message if failed")
825
+
826
+
827
+ class TeamGetResponse(VikunjaBaseModel):
828
+ """Response for getting a single team."""
829
+ success: bool = Field(True, description="Request succeeded")
830
+ team: Optional[Team] = Field(None, description="The requested team")
831
+ error: Optional[str] = Field(None, description="Error message if failed")
832
+
833
+
834
+ class ProjectUserListResponse(VikunjaBaseModel):
835
+ """Response for listing project users."""
836
+ success: bool = Field(True, description="Request succeeded")
837
+ users: list[ProjectUser] = Field(default_factory=list, description="List of project users")
838
+ error: Optional[str] = Field(None, description="Error message if failed")