One option would be to use Geopy to lookup the address on someone like Yahoo or Google Maps, which will then return the full address of the one(s) they match it with. You may have to watch for apartment numbers being truncated off in the returned address (e.g. "221 Amsterdam Av #330" becoming "221 AMSTERDAM AVENUE"). In addition, you will also get the city/state/country information, which the user may have also abbreviated or misspelled.
In the case that there is multiple matches, you could prompt the user for feedback on which is their address. In the case of no matches, you could also let the user know, and possibly allow the address save anyway, depending on how important a valid address is, and how much trust you put in the address-lookup-providers' validity.
Regarding doing this normalization in the form vs. model, I don't know what the preferred Django-way of doing things is, but my preference is in the form, for example:
def clean(self):
# check address via some self-defined helper function
matches = my_helper_address_matcher(address, city, state, zip)
if not matches:
raise forms.ValidationError("Your address couldn't be found...")
elif len(matches) > 1:
# add javascript into error so the user can select
# the address that matches? maybe there is a cleaner way to do this
raise forms.ValidationError('Did you mean...')
You could throw this lookup function in the model (or some helpers.py file) in case you want to reuse it in other areas