Repository: ietf-tools/datatracker

Issue:
Failure to extract title causes NotNullException when validating a submission
### Describe the issue

A txt-format submission whose title could not be extracted caused the exception below. This prevents the `process_uploaded_submission_task()` to fail, leaving the submission in the "validating" state until the cleanup task cancels it as timed out.

```
[2024-03-02 07:58:17,928: INFO/MainProcess] Task ietf.submit.tasks.process_uploaded_submission_task[0e170b37-18f9-4beb-8864-9d204523b28d] received
[2024-03-02 07:58:18,085: ERROR/ForkPoolWorker-93] Task ietf.submit.tasks.process_uploaded_submission_task[0e170b37-18f9-4beb-8864-9d204523b28d] r>
Traceback (most recent call last):
  File "/workspace/ietf/submit/utils.py", line 1411, in process_uploaded_submission
    process_and_validate_submission(submission)
  File "/workspace/ietf/submit/utils.py", line 1291, in process_and_validate_submission
    raise SubmissionError("Could not determine the title of the draft")
ietf.submit.utils.SubmissionError: Could not determine the title of the draft
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.NotNullViolation: null value in column "title" of relation "submit_submission" violates not-null constraint
DETAIL:  Failing row contains (140758, , c2e2515f403fe71e96e6f963e3309fec, , draft-feng-nmop-network-incident-yang, null, , 01, null, null, [
    {
        "name": "Chong Feng",
        "email": "fengchon..., , , , .txt, null, null, 2024-03-02, null, , null, null, cancel).
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/celery/app/trace.py", line 477, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/celery/app/trace.py", line 760, in __protected_call__
    return self.run(*args, **kwargs)
  File "/workspace/ietf/submit/tasks.py", line 24, in process_uploaded_submission_task
    process_uploaded_submission(submission)
```

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:

Resolving patch:
diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py
--- a/ietf/submit/utils.py
+++ b/ietf/submit/utils.py
@@ -1280,11 +1280,11 @@ def process_and_validate_submission(submission):
         if xml_metadata is not None:
             # Items preferred / only available from XML
             submission.xml_version = xml_metadata["xml_version"]
-            submission.title = xml_metadata["title"]
+            submission.title = xml_metadata["title"] or ""
             submission.authors = xml_metadata["authors"]
         else:
             # Items to get from text only if XML not available
-            submission.title = text_metadata["title"]
+            submission.title = text_metadata["title"] or ""
             submission.authors = text_metadata["authors"]
 
         if not submission.title:
@@ -1371,6 +1371,7 @@ def process_and_accept_uploaded_submission(submission):
     try:
         process_and_validate_submission(submission)
     except SubmissionError as err:
+        submission.refresh_from_db()  # guard against incomplete changes in submission validation / processing
         cancel_submission(submission)  # changes Submission.state
         create_submission_event(None, submission, f"Submission rejected: {err}")
         return
@@ -1410,11 +1411,13 @@ def process_uploaded_submission(submission):
     try:
         process_and_validate_submission(submission)
     except InconsistentRevisionError as consistency_error:
+        submission.refresh_from_db()  # guard against incomplete changes in submission validation / processing
         submission.state_id = "manual"
         submission.save()
         create_submission_event(None, submission, desc="Uploaded submission (diverted to manual process)")
         send_manual_post_request(None, submission, errors=dict(consistency=str(consistency_error)))
     except SubmissionError as err:
+        submission.refresh_from_db()  # guard against incomplete changes in submission validation / processing
         cancel_submission(submission)  # changes Submission.state
         create_submission_event(None, submission, f"Submission rejected: {err}")
     else:


Test patch: