4848 "DATETIME_AUTO" ,
4949 "DatetimeOrAuto" ,
5050 "UserID" ,
51+ "Author" ,
5152 "AUTHOR_AUTO" ,
5253 "AuthorOrAuto" ,
5354 "get_learning_package" ,
120121"""
121122
122123
123- type AuthorOrAuto = (
124- # Attribute to a specific user:
125- UserID | AbstractUser
126- # Attribue to nobody:
127- | None | AnonymousUser
128- # Attribute to the same user as the enclosing draft_changes_for:
129- | Literal ["AUTHOR_AUTO" ]
130- )
124+ type Author = UserID | AbstractUser | AnonymousUser | None
131125"""
132- How to attribute an operation (creation, edit, deletion, change) to a user .
126+ A user attribution for a content operation (creation, edit, deletion, change).
133127
134128For attributable changes, this could be a User, or its database ID.
135129
136130For changes without any attributable author (e.g. backfills), this could be None
137131or an AnonymousUser, both of which map to NULL in the database. This should be
138132reserved for special cases--most changes have a concrete author!
133+ """
134+
135+
136+ type AuthorOrAuto = Author | Literal ["AUTHOR_AUTO" ]
137+ """
138+ Either an Author (which could be None) or AUTHOR_AUTO.
139139
140- Finally, this could be AUTHOR_AUTO, which means "use the same author (or lack
141- thereof) that the draft change context is using." Most functions default to AUTHOR_AUTO.
140+ Most functions default to AUTHOR_AUTO. Please note that `None` is not an
141+ appopriate default author--only certain special operations are author-less;
142+ by default, users should supply an author user or use AUTHOR_AUTO in order to
143+ inherit the context's author user.
142144"""
143145
144146
@@ -147,7 +149,7 @@ def resolve_datetime(
147149 dt : DatetimeOrAuto ,
148150) -> datetime :
149151 """
150- Convert a Timestamp specifier into a concerete datetime to be saved to the DB.
152+ Convert a Timestamp specifier into a concrete datetime to be saved to the DB.
151153
152154 If `timestamp is DATETIME_AUTO`, then return either the datetime of the
153155 enclosing `draft_changes_for` context, or `datetime.now` if there is
@@ -168,7 +170,7 @@ def resolve_author(
168170 author : AuthorOrAuto ,
169171) -> UserID | None :
170172 """
171- Convert a Author specifier into a concerete user ID (or None) to be saved to the DB.
173+ Convert a Author specifier into a concrete user ID (or None) to be saved to the DB.
172174
173175 If `author is AUTHOR_AUTO`, then return either the author of the enclosing
174176 `draft_changes_for` context. Raises `ValueError` if there is no
@@ -184,7 +186,14 @@ def resolve_author(
184186 "An author (other than AUTHOR_AUTO) must be specified when changing content "
185187 "outside of a `draft_changes_for` context."
186188 )
187- elif isinstance (author , AnonymousUser ):
189+ return _normalize_author_to_user_id (author ) # type: ignore[arg-type]
190+
191+
192+ def _normalize_author_to_user_id (author : Author ) -> UserID | None :
193+ """
194+ Given a user (object or ID) or lack thereof (AnonymousUser or None), return the ID or None.
195+ """
196+ if isinstance (author , AnonymousUser ):
188197 return None
189198 elif isinstance (author , AbstractUser ):
190199 assert isinstance (author .pk , int )
@@ -318,7 +327,7 @@ def create_publishable_entity(
318327 You'd typically want to call this right before creating your own content
319328 model that points to it.
320329
321- Must be called inside `with draft_changes_for(...):`
330+ You must specify `created_by=` unless you're inside a ` draft_changes_for` context.
322331 """
323332
324333 return PublishableEntity .objects .create (
@@ -520,9 +529,10 @@ def get_entities_with_unpublished_deletes(learning_package_id: LearningPackage.I
520529def publish_all_drafts (
521530 learning_package_id : LearningPackage .ID ,
522531 / ,
532+ published_by : Author ,
533+ * ,
523534 message = "" ,
524535 published_at : DatetimeOrAuto = DATETIME_AUTO ,
525- published_by : AuthorOrAuto = AUTHOR_AUTO ,
526536) -> PublishLog :
527537 """
528538 Publish everything that is a Draft and is not already published.
@@ -533,7 +543,11 @@ def publish_all_drafts(
533543 .with_unpublished_changes ()
534544 )
535545 return publish_from_drafts (
536- learning_package_id , draft_qset , message , published_at , published_by
546+ learning_package_id ,
547+ draft_qset ,
548+ published_by = published_by ,
549+ message = message ,
550+ published_at = published_at ,
537551 )
538552
539553
@@ -574,10 +588,10 @@ def publish_from_drafts(
574588 learning_package_id : LearningPackage .ID ,
575589 / ,
576590 draft_qset : QuerySet [Draft ],
591+ published_by : Author ,
592+ * ,
577593 message : str = "" ,
578594 published_at : DatetimeOrAuto = DATETIME_AUTO ,
579- published_by : AuthorOrAuto = AUTHOR_AUTO ,
580- * ,
581595 publish_dependencies : bool = True ,
582596) -> PublishLog :
583597 """
@@ -589,7 +603,7 @@ def publish_from_drafts(
589603 if DraftChangeLogContext .get_active_draft_change_log (learning_package_id ) is not None :
590604 raise ValidationError ("Cannot publish while in draft_changes_for()." )
591605 published_at = resolve_datetime (learning_package_id , published_at )
592- published_by = resolve_author ( learning_package_id , published_by )
606+ published_by = _normalize_author_to_user_id ( published_by )
593607 with atomic ():
594608 if publish_dependencies :
595609 dependency_drafts_qsets = _get_dependencies_with_unpublished_changes (draft_qset )
@@ -1015,13 +1029,6 @@ def set_draft_version(
10151029 Calling this function attaches a new DraftChangeLogRecord and attaches it to
10161030 a DraftChangeLog.
10171031
1018- This function will create DraftSideEffect entries and properly add any
1019- containers that may have been affected by this draft update, UNLESS it is
1020- called from within a draft_changes_for block. If it is called from
1021- inside a draft_changes_for block, it will not add side-effects for
1022- containers, as draft_changes_for will automatically do that when the
1023- block exits. @@TODO update this docstring
1024-
10251032 Must be called inside `with draft_changes_for(...):`
10261033 """
10271034 with atomic (savepoint = False ):
@@ -1797,7 +1804,7 @@ def get_published_version_as_of(
17971804
17981805def draft_changes_for (
17991806 learning_package_id : LearningPackage .ID ,
1800- changed_by : UserID | AbstractUser | AnonymousUser | None ,
1807+ changed_by : Author ,
18011808 changed_at : DatetimeOrAuto = DATETIME_AUTO ,
18021809) -> DraftChangeLogContext :
18031810 """
@@ -1824,15 +1831,7 @@ def draft_changes_for(
18241831 else :
18251832 assert isinstance (changed_at , datetime )
18261833 changed_at_dt = changed_at
1827- changed_by_id : UserID | None
1828- if isinstance (changed_by , AnonymousUser ):
1829- changed_by_id = None
1830- elif isinstance (changed_by , AbstractUser ):
1831- assert isinstance (changed_by .pk , int )
1832- changed_by_id = changed_by .pk
1833- elif changed_by :
1834- assert isinstance (changed_by , int )
1835- changed_by_id = changed_by
1834+ changed_by_id = _normalize_author_to_user_id (changed_by )
18361835 return DraftChangeLogContext (
18371836 learning_package_id ,
18381837 changed_at = changed_at_dt ,
0 commit comments