Here's the Gurobi code to solve your optimization problem:

```python
import gurobipy as gp

# Create a new model
m = gp.Model("food_optimization")

# Create variables
chicken = m.addVar(vtype=gp.GRB.INTEGER, name="chicken")
potatoes = m.addVar(vtype=gp.GRB.INTEGER, name="potatoes")
tomatoes = m.addVar(vtype=gp.GRB.INTEGER, name="tomatoes")
milkshakes = m.addVar(vtype=gp.GRB.INTEGER, name="milkshakes")

# Set objective function
m.setObjective(3.29 * chicken + 7.22 * potatoes + 7.31 * tomatoes + 4.91 * milkshakes, gp.GRB.MINIMIZE)

# Add constraints
m.addConstr(1 * chicken + 8 * potatoes + 9 * tomatoes + 3 * milkshakes <= 120, "calcium_limit") # Total Calcium
m.addConstr(5 * chicken + 7 * potatoes + 6 * tomatoes + 4 * milkshakes <= 160, "iron_limit") # Total Iron
m.addConstr(3 * chicken + 1 * potatoes + 1 * tomatoes + 8 * milkshakes <= 165, "fiber_limit") # Total Fiber

m.addConstr(1 * chicken + 3 * milkshakes >= 28, "calcium_chicken_milkshakes")
m.addConstr(8 * potatoes + 9 * tomatoes >= 29, "calcium_potatoes_tomatoes")
m.addConstr(1 * chicken + 8 * potatoes + 9 * tomatoes + 3 * milkshakes >= 29, "calcium_total")

m.addConstr(5 * chicken + 6 * tomatoes >= 31, "iron_chicken_tomatoes")
m.addConstr(7 * potatoes + 4 * milkshakes >= 33, "iron_potatoes_milkshakes")
m.addConstr(7 * potatoes + 6 * tomatoes >= 26, "iron_potatoes_tomatoes")
m.addConstr(5 * chicken + 7 * potatoes + 6 * tomatoes + 4 * milkshakes >= 26, "iron_total")


m.addConstr(1 * potatoes + 1 * tomatoes >= 41, "fiber_potatoes_tomatoes")
m.addConstr(3 * chicken + 1 * tomatoes >= 38, "fiber_chicken_tomatoes")
m.addConstr(1 * tomatoes + 8 * milkshakes >= 18, "fiber_tomatoes_milkshakes")
m.addConstr(3 * chicken + 1 * tomatoes + 8 * milkshakes >= 39, "fiber_chicken_tomatoes_milkshakes")
m.addConstr(1 * potatoes + 1 * tomatoes + 8 * milkshakes >= 39, "fiber_potatoes_tomatoes_milkshakes")
m.addConstr(3 * chicken + 1 * tomatoes + 8 * milkshakes >= 22, "fiber_chicken_tomatoes_milkshakes_2")
m.addConstr(1 * potatoes + 1 * tomatoes + 8 * milkshakes >= 22, "fiber_potatoes_tomatoes_milkshakes_2")
m.addConstr(3 * chicken + 1 * potatoes + 1 * tomatoes + 8 * milkshakes >= 22, "fiber_all")

m.addConstr(5 * chicken + 7 * potatoes <= 139, "iron_chicken_potatoes_max")
m.addConstr(7 * potatoes + 4 * milkshakes <= 108, "iron_potatoes_milkshakes_max")
m.addConstr(5 * chicken + 4 * milkshakes <= 85, "iron_chicken_milkshakes_max")
m.addConstr(7 * potatoes + 6 * tomatoes <= 139, "iron_potatoes_tomatoes_max")
m.addConstr(5 * chicken + 6 * tomatoes + 4 * milkshakes <= 159, "iron_chicken_tomatoes_milkshakes_max")
m.addConstr(5 * chicken + 7 * potatoes + 4 * milkshakes <= 61, "iron_chicken_potatoes_milkshakes_max")
m.addConstr(5 * chicken + 7 * potatoes + 6 * tomatoes <= 54, "iron_chicken_potatoes_tomatoes_max")

m.addConstr(3 * chicken + 8 * milkshakes <= 126, "fiber_chicken_milkshakes_max")
m.addConstr(1 * potatoes + 8 * milkshakes <= 161, "fiber_potatoes_milkshakes_max")


# Optimize model
m.optimize()

# Print results
if m.status == gp.GRB.OPTIMAL:
    print('Obj: %g' % m.objVal)
    for v in m.getVars():
        print('%s %g' % (v.varName, v.x))
elif m.status == gp.GRB.INFEASIBLE:
    print("The model is infeasible.")
else:
    print("Optimization ended with status %d" % m.status)

```
