When making spatial queries, its good practice to pass a geometry that has a specified spatial reference system (srid). Like this, GeoDjango will automatically convert the input query geometry to the coordinate system of your table (the coordinate systems of your city model in your case).
In the first code example you gave, you do not specify an srid on the geometry, so pnt = 'POINT(%d %d)'%(lon, lat)
does not have an srid. In this case, GeoDjango will assume the srid is the same for the input and the model data table. Which is not the case in your example, and that is why you dont get any matches.
So you will need to create you point with the correct SRID. If you get the coordinates from OSM, most likely the coordinates are in the Web Mercator projection, which has the srid 3857
. This projection is often used in web mapping.
For this, you can use the EWKT format (which is essentially SRID + WKT) like so:
pnt = 'SRID=4326;POINT(-96.876369 29.90532)'
Or if you have the coordinates in Web Mercator Projection, the following should work:
pnt = 'SRID=3857;POINT(-8243997.66798 517864.86656)'
zonas = ciudad.zona_set.filter(polygon__contains=pnt)
Just for reference, here are a few examples on how to go back an forth between EWKT and GEOSGeometries:
So this (normal WKT, with srid specified on creation of geometry)
GEOSGeometry('POINT(-8243997.66798 517864.86656)', srid=3857)
is equivalent to this (srid contained in EWKT string):
GEOSGeometry('SRID=3857;POINT(-8243997.66798 517864.86656)')