Merge pull request #18 in WFCC/cc34 from 16-notes-eleves-formation to master

* commit 'c93a2a6dbfa061f946c8736d8416384256f0c11d':
  Ajout de la note si deja voté dans noter et titre
  Ajout de la moyenne de la note à la page
  Ajout bouton noter
  Ajout de la possibilitée de noter une formation
  Ajout notes dans relation entre eleve et atelier
This commit is contained in:
Serra Aymeric 2023-02-09 21:15:05 +01:00
commit 85d1ef6e10
15 changed files with 379 additions and 48 deletions

View File

@ -5,7 +5,7 @@
<driver-ref>sqlite.xerial</driver-ref> <driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver> <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:\\wsl$\Ubuntu\home\ziani\ProjetCC\dev\cc34\var\data.db</jdbc-url> <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/var/data.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
<libraries> <libraries>
<library> <library>

7
.idea/sqldialects.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/migrations/Version20230209164915.php" dialect="GenericSQL" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

View File

@ -105,3 +105,10 @@ symfony console doctrine:fixtures:load
symfony console make:controller InstructeurController symfony console make:controller InstructeurController
symfony console make:controller ApprentiController symfony console make:controller ApprentiController
``` ```
### Question 17
```bash
symfony console make:entity FormationUser
symfony console make:migration
symfony console d:m:m
```

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230209164915 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE formation_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, atelier_id INTEGER NOT NULL, eleve_id INTEGER NOT NULL, note SMALLINT DEFAULT NULL, CONSTRAINT FK_DA4C330982E2CF35 FOREIGN KEY (atelier_id) REFERENCES atelier (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_DA4C3309A6CC7B2 FOREIGN KEY (eleve_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql(
"INSERT INTO formation_user(atelier_id, eleve_id, note) SELECT atelier_id, user_id, null FROM user_atelier"
);
$this->addSql('CREATE INDEX IDX_DA4C330982E2CF35 ON formation_user (atelier_id)');
$this->addSql('CREATE INDEX IDX_DA4C3309A6CC7B2 ON formation_user (eleve_id)');
$this->addSql('DROP TABLE user_atelier');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE user_atelier (user_id INTEGER NOT NULL, atelier_id INTEGER NOT NULL, PRIMARY KEY(user_id, atelier_id), CONSTRAINT FK_B9B60629A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_B9B6062982E2CF35 FOREIGN KEY (atelier_id) REFERENCES atelier (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('CREATE INDEX IDX_B9B6062982E2CF35 ON user_atelier (atelier_id)');
$this->addSql('CREATE INDEX IDX_B9B60629A76ED395 ON user_atelier (user_id)');
$this->addSql('DROP TABLE formation_user');
}
}

View File

@ -3,7 +3,10 @@
namespace App\Controller; namespace App\Controller;
use App\Entity\Atelier; use App\Entity\Atelier;
use App\Entity\FormationUser;
use App\Form\NoteType;
use App\Repository\AtelierRepository; use App\Repository\AtelierRepository;
use App\Repository\FormationUserRepository;
use App\Services\MarkdownAtelier; use App\Services\MarkdownAtelier;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
@ -20,24 +23,75 @@ class ApprentiController extends AbstractController
public function inscription(Request $request, Atelier $atelier, ManagerRegistry $doctrine): Response public function inscription(Request $request, Atelier $atelier, ManagerRegistry $doctrine): Response
{ {
$entityManager = $doctrine->getManager(); $entityManager = $doctrine->getManager();
$atelier->addEleve($this->getUser());
$relation = new FormationUser();
$relation
->setNote(null)
->setAtelier($atelier)
->setEleve($this->getUser());
$entityManager->persist($relation);
$atelier->addElevesSuivantFormation($relation);
$entityManager->flush(); $entityManager->flush();
return $this->redirectToRoute('app_atelier_show', ["id"=>$atelier->getId()], Response::HTTP_SEE_OTHER); return $this->redirectToRoute('app_atelier_show', ["id" => $atelier->getId()], Response::HTTP_SEE_OTHER);
} }
#[Route('/atelier/{id}/desinscrire', name: 'app_atelier_desinscrire', methods: ['POST'])] #[Route('/atelier/{id}/desinscrire', name: 'app_atelier_desinscrire', methods: ['POST'])]
public function desinscrire(Request $request, Atelier $atelier, ManagerRegistry $doctrine): Response public function desinscrire(Request $request, Atelier $atelier, ManagerRegistry $doctrine, FormationUserRepository $formationUserRepository): Response
{ {
$entityManager = $doctrine->getManager(); $entityManager = $doctrine->getManager();
$atelier->removeEleve($this->getUser());
$relation = $formationUserRepository->findOneBy(
[
'atelier' => $atelier,
'eleve' => $this->getUser()
]
);
$atelier->removeElevesSuivantFormation($relation);
$entityManager->flush(); $entityManager->flush();
return $this->redirectToRoute('app_atelier_show', ["id"=>$atelier->getId()], Response::HTTP_SEE_OTHER); return $this->redirectToRoute('app_atelier_show', ["id" => $atelier->getId()], Response::HTTP_SEE_OTHER);
}
#[Route('/atelier/{id}/noter', name: 'app_atelier_noter', methods: ['GET', 'POST'])]
public function noter(Request $request, Atelier $atelier, ManagerRegistry $doctrine, FormationUserRepository $formationUserRepository): Response
{
$form = $this->createForm(NoteType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$f = $formationUserRepository
->findOneBy([
'atelier' => $atelier,
'eleve' => $this->getUser()
])
->setNote($form->get('note')->getData());
$formationUserRepository->save($f, true);
return $this->redirectToRoute('app_atelier_show', ["id" => $atelier->getId()], Response::HTTP_SEE_OTHER);
}
$f = $formationUserRepository
->findOneBy([
'atelier' => $atelier,
'eleve' => $this->getUser()
]);
if ($f != null && !$form->isSubmitted()) {
$form->get('note')->setData($f->getNote());
}
return $this->renderForm('atelier/noter.html.twig', [
'atelier' => $atelier,
'form' => $form,
]);
} }
#[Route('/', name: 'app_atelier_inscrit', methods: ['GET'])] #[Route('/', name: 'app_atelier_inscrit', methods: ['GET'])]
public function index_inscrit(AtelierRepository $atelierRepository, MarkdownAtelier $markdown): Response public function index_inscrit(AtelierRepository $atelierRepository, MarkdownAtelier $markdown): Response
{ {
$ateliers = $this->getUser()->getFormationsSuivies()->toArray();
$ateliers = $atelierRepository->getFormationsSuivies($this->getUser())->toArray();
return $this->render('atelier/index_inscrit.html.twig', [ return $this->render('atelier/index_inscrit.html.twig', [
'ateliers' => $markdown->parseArray($ateliers), 'ateliers' => $markdown->parseArray($ateliers),
]); ]);

View File

@ -3,15 +3,13 @@
namespace App\Controller; namespace App\Controller;
use App\Entity\Atelier; use App\Entity\Atelier;
use App\Form\AtelierType;
use App\Repository\AtelierRepository; use App\Repository\AtelierRepository;
use App\Repository\FormationUserRepository;
use App\Repository\UserRepository;
use App\Services\MarkdownAtelier; use App\Services\MarkdownAtelier;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/atelier')] #[Route('/atelier')]
class AtelierController extends AbstractController class AtelierController extends AbstractController
@ -25,11 +23,15 @@ class AtelierController extends AbstractController
} }
#[Route('/{id}', name: 'app_atelier_show', methods: ['GET'])] #[Route('/{id}', name: 'app_atelier_show', methods: ['GET'])]
public function show(Atelier $atelier, MarkdownAtelier $markdownAtelier): Response public function show(Atelier $atelier, MarkdownAtelier $markdownAtelier, UserRepository $userRepository, FormationUserRepository $repository): Response
{ {
$eleves = $userRepository->getEleves($atelier);
$user = $this->getUser();
return $this->render('atelier/show.html.twig', [ return $this->render('atelier/show.html.twig', [
'atelier' => $markdownAtelier->parse($atelier), 'atelier' => $markdownAtelier->parse($atelier),
'inscrit' => $atelier->getEleves()->contains($this->getUser()), 'eleves' => $eleves,
'note' => $repository->getAvg($atelier),
'inscrit' => $user != null && $eleves->contains($this->getUser()),
]); ]);
} }
} }

View File

@ -25,12 +25,12 @@ class Atelier
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
private ?User $instructeur = null; private ?User $instructeur = null;
#[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'formationsSuivies')] #[ORM\OneToMany(mappedBy: 'atelier', targetEntity: FormationUser::class, orphanRemoval: true)]
private Collection $eleves; private Collection $elevesSuivantFormations;
public function __construct() public function __construct()
{ {
$this->eleves = new ArrayCollection(); $this->elevesSuivantFormations = new ArrayCollection();
} }
public function getId(): ?int public function getId(): ?int
@ -75,27 +75,30 @@ class Atelier
} }
/** /**
* @return Collection<int, User> * @return Collection<int, FormationUser>
*/ */
public function getEleves(): Collection public function getElevesSuivantFormations(): Collection
{ {
return $this->eleves; return $this->elevesSuivantFormations;
} }
public function addEleve(User $eleve): self public function addElevesSuivantFormation(FormationUser $elevesSuivantFormation): self
{ {
if (!$this->eleves->contains($eleve)) { if (!$this->elevesSuivantFormations->contains($elevesSuivantFormation)) {
$this->eleves->add($eleve); $this->elevesSuivantFormations->add($elevesSuivantFormation);
$eleve->addFormationsSuivie($this); $elevesSuivantFormation->setAtelier($this);
} }
return $this; return $this;
} }
public function removeEleve(User $eleve): self public function removeElevesSuivantFormation(FormationUser $elevesSuivantFormation): self
{ {
if ($this->eleves->removeElement($eleve)) { if ($this->elevesSuivantFormations->removeElement($elevesSuivantFormation)) {
$eleve->removeFormationsSuivie($this); // set the owning side to null (unless already changed)
if ($elevesSuivantFormation->getAtelier() === $this) {
$elevesSuivantFormation->setAtelier(null);
}
} }
return $this; return $this;

View File

@ -0,0 +1,68 @@
<?php
namespace App\Entity;
use App\Repository\FormationUserRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: FormationUserRepository::class)]
class FormationUser
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::SMALLINT, nullable: true)]
private ?int $note = null;
#[ORM\ManyToOne(inversedBy: 'elevesSuivantFormations')]
#[ORM\JoinColumn(nullable: false)]
private ?Atelier $atelier = null;
#[ORM\ManyToOne(inversedBy: 'formationsInscrits')]
#[ORM\JoinColumn(nullable: false)]
private ?User $eleve = null;
public function getId(): ?int
{
return $this->id;
}
public function getNote(): ?int
{
return $this->note;
}
public function setNote(?int $note): self
{
$this->note = $note;
return $this;
}
public function getAtelier(): ?Atelier
{
return $this->atelier;
}
public function setAtelier(?Atelier $atelier): self
{
$this->atelier = $atelier;
return $this;
}
public function getEleve(): ?User
{
return $this->eleve;
}
public function setEleve(?User $eleve): self
{
$this->eleve = $eleve;
return $this;
}
}

View File

@ -40,13 +40,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\OneToMany(mappedBy: 'instructeur', targetEntity: Atelier::class, orphanRemoval: true)] #[ORM\OneToMany(mappedBy: 'instructeur', targetEntity: Atelier::class, orphanRemoval: true)]
private Collection $ateliersForm<72><6D><EFBFBD>es; private Collection $ateliersForm<72><6D><EFBFBD>es;
#[ORM\ManyToMany(targetEntity: atelier::class, inversedBy: 'eleves')] #[ORM\OneToMany(mappedBy: 'eleve', targetEntity: FormationUser::class, orphanRemoval: true)]
private Collection $formationsSuivies; private Collection $formationsInscrits;
public function __construct() public function __construct()
{ {
$this->ateliersForm<EFBFBD><EFBFBD><EFBFBD>es = new ArrayCollection(); $this->ateliersForm<EFBFBD><EFBFBD><EFBFBD>es = new ArrayCollection();
$this->formationsSuivies = new ArrayCollection(); $this->formationsInscrits = new ArrayCollection();
} }
public function getId(): ?int public function getId(): ?int
@ -73,7 +73,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
*/ */
public function getUserIdentifier(): string public function getUserIdentifier(): string
{ {
return (string) $this->email; return (string)$this->email;
} }
/** /**
@ -176,25 +176,31 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
} }
/** /**
* @return Collection<int, atelier> * @return Collection<int, FormationUser>
*/ */
public function getFormationsSuivies(): Collection public function getFormationsInscrits(): Collection
{ {
return $this->formationsSuivies; return $this->formationsInscrits;
} }
public function addFormationsSuivie(atelier $formationsSuivie): self public function addFormationsInscrit(FormationUser $formationsInscrit): self
{ {
if (!$this->formationsSuivies->contains($formationsSuivie)) { if (!$this->formationsInscrits->contains($formationsInscrit)) {
$this->formationsSuivies->add($formationsSuivie); $this->formationsInscrits->add($formationsInscrit);
$formationsInscrit->setEleve($this);
} }
return $this; return $this;
} }
public function removeFormationsSuivie(atelier $formationsSuivie): self public function removeFormationsInscrit(FormationUser $formationsInscrit): self
{ {
$this->formationsSuivies->removeElement($formationsSuivie); if ($this->formationsInscrits->removeElement($formationsInscrit)) {
// set the owning side to null (unless already changed)
if ($formationsInscrit->getEleve() === $this) {
$formationsInscrit->setEleve(null);
}
}
return $this; return $this;
} }

18
src/Form/NoteType.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
class NoteType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('note', IntegerType::class, [
'attr' => array('min' => 0, 'max' => 5,),
'required' => false
]);
}
}

View File

@ -3,7 +3,9 @@
namespace App\Repository; namespace App\Repository;
use App\Entity\Atelier; use App\Entity\Atelier;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
/** /**
@ -39,6 +41,16 @@ class AtelierRepository extends ServiceEntityRepository
} }
} }
public function getFormationsSuivies(User $user): ArrayCollection
{
return new ArrayCollection($this->createQueryBuilder('a')
->innerJoin('\App\Entity\FormationUser', 'fu', 'WITH', 'fu.atelier = a')
->where('fu.eleve = :eleve')
->setParameter('eleve', $user)
->getQuery()
->execute());
}
// /** // /**
// * @return Atelier[] Returns an array of Atelier objects // * @return Atelier[] Returns an array of Atelier objects
// */ // */

View File

@ -0,0 +1,78 @@
<?php
namespace App\Repository;
use App\Entity\Atelier;
use App\Entity\FormationUser;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<FormationUser>
*
* @method FormationUser|null find($id, $lockMode = null, $lockVersion = null)
* @method FormationUser|null findOneBy(array $criteria, array $orderBy = null)
* @method FormationUser[] findAll()
* @method FormationUser[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class FormationUserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, FormationUser::class);
}
public function save(FormationUser $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(FormationUser $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function getAvg(Atelier $atelier): ?float
{
return $this
->createQueryBuilder('fu')
->select('AVG(fu.note)')
->where('fu.atelier = :atelier and fu.note is not null')
->setParameter('atelier', $atelier)
->getQuery()
->getSingleScalarResult();
}
// /**
// * @return FormationUser[] Returns an array of FormationUser objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('f')
// ->andWhere('f.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('f.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?FormationUser
// {
// return $this->createQueryBuilder('f')
// ->andWhere('f.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -2,8 +2,11 @@
namespace App\Repository; namespace App\Repository;
use App\Entity\Atelier;
use App\Entity\User; use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
@ -24,15 +27,6 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
parent::__construct($registry, User::class); parent::__construct($registry, User::class);
} }
public function save(User $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(User $entity, bool $flush = false): void public function remove(User $entity, bool $flush = false): void
{ {
$this->getEntityManager()->remove($entity); $this->getEntityManager()->remove($entity);
@ -56,6 +50,25 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
$this->save($user, true); $this->save($user, true);
} }
public function save(User $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function getEleves(Atelier $atelier) : ArrayCollection
{
return new ArrayCollection($this->createQueryBuilder('e')
->innerJoin('\App\Entity\FormationUser', 'fu', 'WITH', 'fu.eleve = e')
->where('fu.atelier = :atelier')
->setParameter('atelier', $atelier)
->getQuery()
->execute());
}
// /** // /**
// * @return User[] Returns an array of User objects // * @return User[] Returns an array of User objects
// */ // */

View File

@ -0,0 +1,14 @@
{% extends 'base.html.twig' %}
{% block title %}Noter l'atelier{% endblock %}
{% block body %}
<h1>Noter l'atelier</h1>
{{ form_start(form) }}
{{ form_widget(form) }}
<div class="d-flex flex-row">
<button class="btn btn-outline-primary m-2">Enregistrer</button>
</div>
{{ form_end(form) }}
{% endblock %}

View File

@ -23,11 +23,17 @@
<th>Email instructeur</th> <th>Email instructeur</th>
<td>{{ atelier.instructeur.email }}</td> <td>{{ atelier.instructeur.email }}</td>
</tr> </tr>
<tr>
<th>Note</th>
<td>
{{ note }}
</td>
</tr>
<tr> <tr>
<th>Elèves inscrits</th> <th>Elèves inscrits</th>
<td> <td>
<ul> <ul>
{% for eleve in atelier.eleves %} {% for eleve in eleves %}
<li>{{ eleve.prenom }} {{ eleve.nom }} - {{ eleve.email }}</li> <li>{{ eleve.prenom }} {{ eleve.nom }} - {{ eleve.email }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -53,6 +59,9 @@
<form method="post" action="{{ path('app_atelier_desinscrire', {'id': atelier.id}) }}"> <form method="post" action="{{ path('app_atelier_desinscrire', {'id': atelier.id}) }}">
<button class="btn btn-outline-danger m-2">Se désinscrire</button> <button class="btn btn-outline-danger m-2">Se désinscrire</button>
</form> </form>
<a class="btn btn-outline-primary m-2"
href="{{ path('app_atelier_noter', {'id': atelier.id}) }}">Noter</a>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}