To solve this optimization problem, we first need to define the decision variables and the objective function. Let's denote the number of lattes as \(L\) and the number of cappuccinos as \(C\). The profit per latte is $2, and the profit per cappuccino is $1. So, our objective function to maximize profit (\(P\)) can be written as:

\[ P = 2L + C \]

We have constraints based on the availability of milk and coffee:
- Each latte requires 4 units of milk, and each cappuccino requires 2 units of milk. The cafe has a total of 80 units of milk available. This gives us the constraint:
\[ 4L + 2C \leq 80 \]
- Each latte needs 1 unit of coffee, and each cappuccino requires 2 units of coffee. With 50 units of coffee available, we have:
\[ L + 2C \leq 50 \]

Additionally, \(L\) and \(C\) must be non-negative since they represent quantities of products.

Now, let's translate this problem into Gurobi code in Python:

```python
from gurobipy import *

# Create a new model
m = Model("Cafe_Optimization")

# Define the decision variables
lattes = m.addVar(lb=0, vtype=GRB.INTEGER, name="Lattes")
cappuccinos = m.addVar(lb=0, vtype=GRB.INTEGER, name="Cappuccinos")

# Set the objective function
m.setObjective(2*lattes + cappuccinos, GRB.MAXIMIZE)

# Add constraints
m.addConstr(4*lattes + 2*cappuccinos <= 80, "Milk_Constraint")
m.addConstr(lattes + 2*cappuccinos <= 50, "Coffee_Constraint")

# Optimize the model
m.optimize()

# Print the results
if m.status == GRB.OPTIMAL:
    print("Optimal solution found:")
    print(f"Lattes: {lattes.x}")
    print(f"Cappuccinos: {cappuccinos.x}")
    print(f"Maximum Profit: {m.objVal}")
else:
    print("No optimal solution found")
```