Optimize adding default reviewers to review requests.

Review Request #10361 — Created Jan. 11, 2019 and submitted — Latest diff uploaded

Review Board

The process of adding default reviewers was pretty expensive. For every
default reviewer that was a match for a review request, we'd iterate
through all groups and all active and inactive users that were
specified. We then performed a lookup of the existing users and groups
to see if the new users/groups already existed and would then add them
one-by-one (which resulted in Django performing a database query
per-item to do the existence check we already did). In total, if 2
default reviewers matched, each with 2 users and 2 groups, we'd have a
total of 17 SQL queries. This would only go up as more users, groups, or
default reviewers were involved.

This change introduces new logic that minimizes the number of SQL
queries. First off, we no longer even attempt these lookups if the
review request isn't part of a repository. We then make note of any
default reviewers that matched, and fetch all their active users and
group IDs in one go, preventing any overhead for multiple matches. From
there, we add the users and groups directly to the review request in one
go without performing existence checks, instead letting Django take care
of that.

We also minimize the file lookups, returning only the raw data we need
instead of returning all data and turning them into model instances. On
top of this, we request only the attributes needed in DefaultReviewer
to perform the checks.

The result is a worst-case scenario of 9 SQL queries, covering any
number of matched default reviewers, 7 if only users or only groups
are added, and 3 if no default reviewers match.

Unit tests pass.