How is diagonalization a valid argument for the undecidability of the halting problem? All proofs for the undecidability of the halting problem seem to be based directly or indirectly on self-reference. 

def g():
    if halts(g):
        loop_forever()


My question is: how can the input of a TM contain the TM itself and its input? 

If the description of the TM has states and transitions, then it has more than its input therefore the input of the TM has infinite size because it contains both the TM and the input.
Try the following code instead:

def quote(s):
    return s.replace("'", "\\'")

def g():
    s = '''
def g():
    s = \'\'\'%s\'\'\'
    if halts(s %% quote(s)):
        while True:
            pass
'''
    if halts(s % quote(s)):
        while True:
            pass


If we add the following halting routine:

def halts(prog):
    print prog
    return False


then we can run g() and see that the input to halts is indeed the code for g():

&gt;&gt;&gt; g()

def g():
    s = '''
def g():
    s = \'\'\'%s\'\'\'
    if halts(s %% quote(s)):
        while True:
            pass
'''
    if halts(s % quote(s)):
        while True:
            pass


A program that prints itself is called a quine. The corresponding result in computability theory is the recursion theorem, which ensures that (in a sense) a program can have access to its own source code.