feat(backend): CRUD variétés, plantations, tâches + tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,66 @@
|
|||||||
from fastapi import APIRouter
|
from datetime import datetime
|
||||||
router = APIRouter()
|
from typing import List
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from app.database import get_session
|
||||||
|
from app.models.planting import Planting, PlantingEvent
|
||||||
|
|
||||||
|
router = APIRouter(tags=["plantations"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/plantings", response_model=List[Planting])
|
||||||
|
def list_plantings(session: Session = Depends(get_session)):
|
||||||
|
return session.exec(select(Planting)).all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/plantings", response_model=Planting, status_code=status.HTTP_201_CREATED)
|
||||||
|
def create_planting(p: Planting, session: Session = Depends(get_session)):
|
||||||
|
session.add(p)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/plantings/{id}", response_model=Planting)
|
||||||
|
def get_planting(id: int, session: Session = Depends(get_session)):
|
||||||
|
p = session.get(Planting, id)
|
||||||
|
if not p:
|
||||||
|
raise HTTPException(status_code=404, detail="Plantation introuvable")
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/plantings/{id}", response_model=Planting)
|
||||||
|
def update_planting(id: int, data: Planting, session: Session = Depends(get_session)):
|
||||||
|
p = session.get(Planting, id)
|
||||||
|
if not p:
|
||||||
|
raise HTTPException(status_code=404, detail="Plantation introuvable")
|
||||||
|
for k, v in data.model_dump(exclude_unset=True, exclude={"id", "created_at"}).items():
|
||||||
|
setattr(p, k, v)
|
||||||
|
p.updated_at = datetime.utcnow()
|
||||||
|
session.add(p)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/plantings/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
def delete_planting(id: int, session: Session = Depends(get_session)):
|
||||||
|
p = session.get(Planting, id)
|
||||||
|
if not p:
|
||||||
|
raise HTTPException(status_code=404, detail="Plantation introuvable")
|
||||||
|
session.delete(p)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/plantings/{id}/events", response_model=List[PlantingEvent])
|
||||||
|
def list_events(id: int, session: Session = Depends(get_session)):
|
||||||
|
return session.exec(select(PlantingEvent).where(PlantingEvent.planting_id == id)).all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/plantings/{id}/events", response_model=PlantingEvent, status_code=status.HTTP_201_CREATED)
|
||||||
|
def create_event(id: int, e: PlantingEvent, session: Session = Depends(get_session)):
|
||||||
|
e.planting_id = id
|
||||||
|
session.add(e)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(e)
|
||||||
|
return e
|
||||||
|
|||||||
@@ -1,2 +1,61 @@
|
|||||||
from fastapi import APIRouter
|
from datetime import datetime
|
||||||
router = APIRouter()
|
from typing import List, Optional
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from app.database import get_session
|
||||||
|
from app.models.task import Task
|
||||||
|
|
||||||
|
router = APIRouter(tags=["tâches"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tasks", response_model=List[Task])
|
||||||
|
def list_tasks(
|
||||||
|
statut: Optional[str] = None,
|
||||||
|
garden_id: Optional[int] = None,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
):
|
||||||
|
q = select(Task)
|
||||||
|
if statut:
|
||||||
|
q = q.where(Task.statut == statut)
|
||||||
|
if garden_id:
|
||||||
|
q = q.where(Task.garden_id == garden_id)
|
||||||
|
return session.exec(q).all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/tasks", response_model=Task, status_code=status.HTTP_201_CREATED)
|
||||||
|
def create_task(t: Task, session: Session = Depends(get_session)):
|
||||||
|
session.add(t)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(t)
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tasks/{id}", response_model=Task)
|
||||||
|
def get_task(id: int, session: Session = Depends(get_session)):
|
||||||
|
t = session.get(Task, id)
|
||||||
|
if not t:
|
||||||
|
raise HTTPException(status_code=404, detail="Tâche introuvable")
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/tasks/{id}", response_model=Task)
|
||||||
|
def update_task(id: int, data: Task, session: Session = Depends(get_session)):
|
||||||
|
t = session.get(Task, id)
|
||||||
|
if not t:
|
||||||
|
raise HTTPException(status_code=404, detail="Tâche introuvable")
|
||||||
|
for k, v in data.model_dump(exclude_unset=True, exclude={"id", "created_at"}).items():
|
||||||
|
setattr(t, k, v)
|
||||||
|
t.updated_at = datetime.utcnow()
|
||||||
|
session.add(t)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(t)
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/tasks/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
def delete_task(id: int, session: Session = Depends(get_session)):
|
||||||
|
t = session.get(Task, id)
|
||||||
|
if not t:
|
||||||
|
raise HTTPException(status_code=404, detail="Tâche introuvable")
|
||||||
|
session.delete(t)
|
||||||
|
session.commit()
|
||||||
|
|||||||
@@ -1,2 +1,50 @@
|
|||||||
from fastapi import APIRouter
|
from typing import List
|
||||||
router = APIRouter()
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from app.database import get_session
|
||||||
|
from app.models.plant import PlantVariety
|
||||||
|
|
||||||
|
router = APIRouter(tags=["variétés"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/varieties", response_model=List[PlantVariety])
|
||||||
|
def list_varieties(session: Session = Depends(get_session)):
|
||||||
|
return session.exec(select(PlantVariety)).all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/varieties", response_model=PlantVariety, status_code=status.HTTP_201_CREATED)
|
||||||
|
def create_variety(v: PlantVariety, session: Session = Depends(get_session)):
|
||||||
|
session.add(v)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/varieties/{id}", response_model=PlantVariety)
|
||||||
|
def get_variety(id: int, session: Session = Depends(get_session)):
|
||||||
|
v = session.get(PlantVariety, id)
|
||||||
|
if not v:
|
||||||
|
raise HTTPException(status_code=404, detail="Variété introuvable")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/varieties/{id}", response_model=PlantVariety)
|
||||||
|
def update_variety(id: int, data: PlantVariety, session: Session = Depends(get_session)):
|
||||||
|
v = session.get(PlantVariety, id)
|
||||||
|
if not v:
|
||||||
|
raise HTTPException(status_code=404, detail="Variété introuvable")
|
||||||
|
for k, val in data.model_dump(exclude_unset=True, exclude={"id", "created_at"}).items():
|
||||||
|
setattr(v, k, val)
|
||||||
|
session.add(v)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/varieties/{id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
def delete_variety(id: int, session: Session = Depends(get_session)):
|
||||||
|
v = session.get(PlantVariety, id)
|
||||||
|
if not v:
|
||||||
|
raise HTTPException(status_code=404, detail="Variété introuvable")
|
||||||
|
session.delete(v)
|
||||||
|
session.commit()
|
||||||
|
|||||||
25
backend/tests/test_plantings.py
Normal file
25
backend/tests/test_plantings.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
def test_create_planting(client):
|
||||||
|
g = client.post("/api/gardens", json={"nom": "Potager", "type": "plein_air"}).json()
|
||||||
|
v = client.post("/api/varieties", json={"nom_commun": "Tomate"}).json()
|
||||||
|
r = client.post("/api/plantings", json={
|
||||||
|
"garden_id": g["id"], "variety_id": v["id"], "quantite": 3
|
||||||
|
})
|
||||||
|
assert r.status_code == 201
|
||||||
|
assert r.json()["statut"] == "prevu"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_plantings(client):
|
||||||
|
g = client.post("/api/gardens", json={"nom": "Potager", "type": "plein_air"}).json()
|
||||||
|
v = client.post("/api/varieties", json={"nom_commun": "Tomate"}).json()
|
||||||
|
client.post("/api/plantings", json={"garden_id": g["id"], "variety_id": v["id"]})
|
||||||
|
r = client.get("/api/plantings")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert len(r.json()) >= 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_planting_event(client):
|
||||||
|
g = client.post("/api/gardens", json={"nom": "Potager", "type": "plein_air"}).json()
|
||||||
|
v = client.post("/api/varieties", json={"nom_commun": "Tomate"}).json()
|
||||||
|
p = client.post("/api/plantings", json={"garden_id": g["id"], "variety_id": v["id"]}).json()
|
||||||
|
r = client.post(f"/api/plantings/{p['id']}/events", json={"type": "arrosage", "note": "Bien arrosé"})
|
||||||
|
assert r.status_code == 201
|
||||||
28
backend/tests/test_tasks.py
Normal file
28
backend/tests/test_tasks.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
def test_create_task(client):
|
||||||
|
r = client.post("/api/tasks", json={"titre": "Arroser les tomates", "priorite": "haute"})
|
||||||
|
assert r.status_code == 201
|
||||||
|
assert r.json()["statut"] == "a_faire"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_tasks(client):
|
||||||
|
client.post("/api/tasks", json={"titre": "Tâche 1"})
|
||||||
|
client.post("/api/tasks", json={"titre": "Tâche 2"})
|
||||||
|
r = client.get("/api/tasks")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert len(r.json()) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_tasks_by_statut(client):
|
||||||
|
client.post("/api/tasks", json={"titre": "À faire", "statut": "a_faire"})
|
||||||
|
client.post("/api/tasks", json={"titre": "Fait", "statut": "fait"})
|
||||||
|
r = client.get("/api/tasks?statut=a_faire")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert all(t["statut"] == "a_faire" for t in r.json())
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_task_statut(client):
|
||||||
|
r = client.post("/api/tasks", json={"titre": "À faire"})
|
||||||
|
id = r.json()["id"]
|
||||||
|
r2 = client.put(f"/api/tasks/{id}", json={"titre": "À faire", "statut": "fait"})
|
||||||
|
assert r2.status_code == 200
|
||||||
|
assert r2.json()["statut"] == "fait"
|
||||||
26
backend/tests/test_varieties.py
Normal file
26
backend/tests/test_varieties.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
def test_create_variety(client):
|
||||||
|
r = client.post("/api/varieties", json={"nom_commun": "Tomate", "famille": "Solanacées"})
|
||||||
|
assert r.status_code == 201
|
||||||
|
assert r.json()["nom_commun"] == "Tomate"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_varieties(client):
|
||||||
|
client.post("/api/varieties", json={"nom_commun": "Tomate"})
|
||||||
|
client.post("/api/varieties", json={"nom_commun": "Courgette"})
|
||||||
|
r = client.get("/api/varieties")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert len(r.json()) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_variety(client):
|
||||||
|
r = client.post("/api/varieties", json={"nom_commun": "Basilic"})
|
||||||
|
id = r.json()["id"]
|
||||||
|
r2 = client.get(f"/api/varieties/{id}")
|
||||||
|
assert r2.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_variety(client):
|
||||||
|
r = client.post("/api/varieties", json={"nom_commun": "Test"})
|
||||||
|
id = r.json()["id"]
|
||||||
|
r2 = client.delete(f"/api/varieties/{id}")
|
||||||
|
assert r2.status_code == 204
|
||||||
Reference in New Issue
Block a user