Recursion function for prime number check
Python and recursion are not a good mix, because Python doesn't do tail call optimization.  (There's a fun blog post here that explores a cheesy workaround that you probably don't want to do for this project: https://chrispenner.ca/posts/python-tail-recursion)

Leaving that aside, there are two opportunities I see for optimization:

if i + 1 == n:
    return True


The largest minimum factor of any given number is going to be its square root, so you don't need to look for factors all the way up to n.

return has_divisors(n, i + 1)


If n wasn't divisible by 2, it's not going to be divisible by any other even number, so this recursive call is a waste of time (and stack space) at least half the time.  Are there any other ways you might be able to know before you recurse that a given new value of i isn't going to be fruitful?