Once a database error is raised inside transaction.atomic(), the transaction is broken. Catching the exception and continuing produces TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block. on the next query.
This is not Django being strict for its own sake. On PostgreSQL the connection is genuinely in an aborted state and the server rejects everything until a rollback, so Django reports it clearly rather than letting the driver fail later.
The fix is to wrap only the failing statement in its own inner atomic() block, which creates a savepoint, and put the except IntegrityError around that inner block. The savepoint rolls back and the outer transaction survives. The pattern is exactly what get_or_create does internally. Two related notes: select_for_update() outside an atomic block raises a TransactionManagementError of its own, and in tests TestCase wraps each test in a transaction, so code that depends on this behaviour should be tested with TransactionTestCase to see real commit semantics.