-
-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathmodels.py
More file actions
116 lines (102 loc) · 4.49 KB
/
Copy pathmodels.py
File metadata and controls
116 lines (102 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""Identity model for the CTJ platform.
Defines `CustomUser`, the project's `AUTH_USER_MODEL`. The
domain-table FKs that the user model holds (community of practice,
skills learned matrix) reference models that live in `ctj_api` -
keeping the user record on its own table while still tying it to
the domain taxonomy.
Stage 1 / Stage 2 boundary: `CustomUser` is locally owned in Stage
1 (email + password signup) and reconciles to a PeopleDepot user
record in Stage 2 via `people_depot_user_id`. See the class
docstring for the full lifecycle.
"""
import uuid
from django.contrib.auth.models import AbstractUser
from django.db import models
from ctj_api.models import CommunityOfPractice, SkillMatrix
class CustomUser(AbstractUser):
"""
Summary:
- CTJ's user / identity model (the project's `AUTH_USER_MODEL`).
Business workflow:
- Created at signup via email + password (Stage 1) or via Cognito
(Stage 2).
- Identity reconciles to a PeopleDepot user record in Stage 2 via
the `people_depot_user_id` field; Stage 1 self-registered users
carry a `local:<uuid>` placeholder until that reconciliation.
- `isProjectManager` gates the ability to post opportunities in
the CMS (see `OpportunityPermission`).
Current policy:
- Subclasses Django's `AbstractUser` rather than extending the
default `User` via a OneToOne profile model. The trade-off is
that all user-shaped fields - identity, practice area, skills,
availability - live on a single table; reads are simpler
(`request.user.name` works directly, no join required), at the
cost of locking in the choice from day 1.
- `username` (inherited from `AbstractUser`) is set to the user's
email at signup time. That's what lets Django's default
`ModelBackend` resolve `authenticate(username=email,
password=...)` without writing a custom auth backend. Stage 2
(Cognito) replaces the password-based auth path entirely, so
this coupling is temporary.
- `people_depot_user_id` is unique-not-null; the `local:<uuid>`
placeholder satisfies the constraint until Stage 2 reconciliation.
The cleaner long-term fix is making the field nullable; deferred
to a schema migration.
- `isProjectManager` is camelCase intentionally; renaming would
require a schema migration and isn't worth the disruption solo.
Lifecycle control:
- `user-managed` (self-registration in Stage 1).
Visibility:
- `auth-required` via `/api/users/<uuid>/`; restricted further by
`UserDetailPermission`.
"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
people_depot_user_id = models.CharField(max_length=255, unique=True)
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
community_of_practice = models.ForeignKey(
CommunityOfPractice,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="user",
)
skills_learned_matrix = models.OneToOneField(
SkillMatrix,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="user",
help_text="User's list of skills mapped to a mastery level (1-5).",
)
max_available_hours = models.IntegerField(
null=True, blank=True, help_text="User's available hours per week."
)
# JSON shape: list of objects with keys `day` (str), `start`
# ("HH:MM"), `end` ("HH:MM"). Example entry:
# `{"day": "Wed", "start": "17:00", "end": "21:00"}`.
# Parallels `Opportunity.meeting_times` shape but omits the
# `team` key (opportunity-side metadata, not user-side). The
# Availability filter on the browse surface keeps an opportunity
# if any of its `meeting_times` slots overlaps any of these
# windows. Not enforced by a JSONSchema validator yet.
meeting_availability = models.JSONField(
null=True,
blank=True,
help_text=(
"Availability windows the user can attend. JSON shape: "
'[{"day": "Wed", "start": "17:00", "end": "21:00"}, ...]'
),
)
isProjectManager = models.BooleanField(
default=False,
help_text="A user that is a PM can create and edit opportunities in the CMS.",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "users"
verbose_name = "User"
verbose_name_plural = "Users"
def __str__(self):
return self.name