diff --git a/Gemfile b/Gemfile
index fd73aa125cefe41af4fcc3bd27c74a650abcec69..7342df7bc001dd45ae8116efe417f4f191626f6d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -36,7 +36,7 @@ gem 'front_matter_parser'
 gem 'two_factor_authentication', git: 'https://github.com/noesya/two_factor_authentication.git'
 # gem 'two_factor_authentication', path: '../two_factor_authentication'
 gem 'curation'#, path: '../../arnaudlevy/curation'
-gem 'nested_form'
+gem "cocoon", "~> 1.2"
 
 # Front
 gem 'jquery-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index d80a92781546013564092059c7bd22517b832c77..05191986e8cbaf751e56c071af2930d6bdb8f837 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -122,6 +122,7 @@ GEM
       regexp_parser (>= 1.5, < 3.0)
       xpath (~> 3.2)
     childprocess (4.1.0)
+    cocoon (1.2.15)
     concurrent-ruby (1.1.9)
     countries (4.2.1)
       i18n_data (~> 0.15.0)
@@ -267,7 +268,6 @@ GEM
     multipart-post (2.1.1)
     mustermann (1.1.1)
       ruby2_keywords (~> 0.0.1)
-    nested_form (0.3.2)
     nesty (1.0.2)
     nio4r (2.5.8)
     nokogiri (1.13.1)
@@ -426,6 +426,7 @@ DEPENDENCIES
   byebug
   cancancan
   capybara (>= 3.26)
+  cocoon (~> 1.2)
   country_select
   curation
   delayed_job_active_record
@@ -443,7 +444,6 @@ DEPENDENCIES
   kaminari
   listen (~> 3.3)
   mini_magick
-  nested_form
   octokit
   pg (~> 1.1)
   puma
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 8d5f8c6330f1170a5514e1c1f4bf4d810953a5fb..8325a2d7fb557ccbcf2813bec4b23fe348317df0 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -12,7 +12,7 @@
 //= require trix
 //= require sortablejs/Sortable
 //= require slug/slug
-//= require jquery_nested_form
+//= require cocoon
 //= require_self
 //= require_tree ./admin/commons
 //= require_tree ./admin/plugins
diff --git a/app/assets/javascripts/admin/plugins/sortable.js b/app/assets/javascripts/admin/plugins/sortable.js
index e34e6fbfd08c626604e59e291d180ac10a2ed92b..ab073755a18f930db9ac6dc3c088b4a04ee10e64 100644
--- a/app/assets/javascripts/admin/plugins/sortable.js
+++ b/app/assets/javascripts/admin/plugins/sortable.js
@@ -1,26 +1,87 @@
 /*global $, Sortable */
-$(function () {
-    'use strict';
-    // Re-order elements of a table. Needs a "table-sortable" class on the table, a "data-reorder-url" param on the tbody and a "data-id" param on each tr
-    var nestedSortables = [].slice.call(document.querySelectorAll('.table-sortable tbody')),
-        i;
-    for (i = 0; i < nestedSortables.length; i += 1) {
-        new Sortable(nestedSortables[i], {
+
+// Add [data-sortable] to container. Its direct children can be sortable.
+// You can pass a value to data-sortable. It can be "xhr" or "inputs". Defaults to "xhr".
+// With "xhr", you need to set [data-id="<id>"] on the children and [data-sort-url="<post url to call>"] on the container.
+// With "inputs", you need to set [data-sortable-input] on the input field of each child.
+window.sortableManager = {
+    init: function () {
+        'use strict';
+        var i;
+        this.containers = document.querySelectorAll('[data-sortable]');
+        this.instances = [];
+        for (i = 0; i < this.containers.length; i += 1) {
+            this.instances.push(this.createInstance(this.containers[i]));
+        }
+    },
+
+    createInstance: function (container) {
+        'use strict';
+        var sortableType = container.dataset.sortable;
+        if (sortableType === 'input') {
+            $(container).on('cocoon:after-add cocoon:after-remove', this.updateViaInputs);
+        }
+
+        return new Sortable(container, {
             handle: '.handle',
             group: 'nested',
             animation: 150,
             fallbackOnBody: true,
             swapThreshold: 0.65,
-            onEnd: function (evt) {
-                var to = evt.to,
-                    ids = [],
-                    url = $(to).attr('data-reorder-url');
-                // get list of ids
-                $('> tr', to).each(function () {
-                    ids.push($(this).attr('data-id'));
-                });
-                $.post(url, { ids: ids });
-            }
+            onEnd: sortableType === 'inputs' ? this.updateViaInputs : this.updateViaXhr
         });
+    },
+
+    updateViaXhr: function (event) {
+        'use strict';
+        var url = event.to.dataset.sortUrl,
+            children = event.to.children,
+            ids = [],
+            i;
+
+        if (!url) {
+            return;
+        }
+
+        for (i = 0; i < children.length; i += 1) {
+            ids.push(children[i].dataset.id);
+        }
+
+        $.post(url, { ids: ids });
+    },
+
+    updateViaInputs: function (event) {
+        'use strict';
+        var children = event.to.children,
+            newPosition = 0,
+            destroyInput,
+            targetInput,
+            i;
+
+        for (i = 0; i < children.length; i += 1) {
+            destroyInput = children[i].querySelector('input[name$="[_destroy]"]');
+            if (destroyInput !== null && destroyInput.value === '1') {
+                continue;
+            }
+
+            newPosition += 1;
+
+            targetInput = children[i].querySelector('[data-sortable-input]');
+            if (targetInput !== null) {
+                targetInput.value = newPosition;
+            }
+        }
+    },
+
+    invoke: function () {
+        'use strict';
+        return {
+            init: this.init.bind(this)
+        };
     }
+}.invoke();
+
+window.addEventListener('DOMContentLoaded', function () {
+    'use strict';
+    window.sortableManager.init();
 });
diff --git a/app/controllers/admin/education/program/role/people_controller.rb b/app/controllers/admin/education/program/role/people_controller.rb
index 0369f528d938061f02e333bce423a3d35b9856e1..ff0fe767c8b3f77faaa3a3871ec8e587c95468da 100644
--- a/app/controllers/admin/education/program/role/people_controller.rb
+++ b/app/controllers/admin/education/program/role/people_controller.rb
@@ -1,51 +1,68 @@
 class Admin::Education::Program::Role::PeopleController < Admin::Education::Program::ApplicationController
-  load_and_authorize_resource :role, class: Education::Program::Role, through: :program
-  load_and_authorize_resource class: Education::Program::Role::Person, through: :role
-
-  before_action :get_available_people, except: :destroy
+  load_and_authorize_resource :role, class: University::Role, through: :program, param: :role_id, through_association: :university_roles
+  load_and_authorize_resource :involvement, class: University::Person::Involvement, through: :role, parent: false
 
   include Admin::Reorderable
 
-  def reorder
-    super { |first_person| first_person.sync_program }
-  end
+  before_action :get_available_people, except: [:reorder, :destroy]
 
   def new
     breadcrumb
   end
 
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
   def create
-    if @person.save
-      redirect_to admin_education_program_role_path(@role), notice: t('admin.successfully_created_html', model: @person.to_s)
+    if @involvement.save
+      redirect_to admin_education_program_role_path(@role, { program_id: @program.id }), notice: t('admin.successfully_created_html', model: @person.to_s)
     else
       breadcrumb
       render :new, status: :unprocessable_entity
     end
   end
 
+  def update
+    if @involvement.update(involvement_params)
+      redirect_to admin_education_program_role_path(@role, { program_id: @program.id }), notice: t('admin.successfully_updated_html', model: @involvement.to_s)
+    else
+      breadcrumb
+      render :edit, status: :unprocessable_entity
+      add_breadcrumb t('edit')
+    end
+  end
+
   def destroy
-    @person.destroy
-    redirect_to admin_education_program_role_path(@role), notice: t('admin.successfully_destroyed_html', model: @person.to_s)
+    @involvement.destroy
+    redirect_to admin_education_program_role_path(@role, { program_id: @program.id }), notice: t('admin.successfully_destroyed_html', model: @person.to_s)
   end
 
   protected
 
   def get_available_people
-    used_person_ids = @role.people.where.not(id: @person.id).pluck(:person_id)
+    used_person_ids = @role.involvements.where.not(id: @involvement.id).pluck(:person_id)
     @available_people = current_university.people.where.not(id: used_person_ids).accessible_by(current_ability).ordered
   end
 
   def breadcrumb
     super
-    add_breadcrumb Education::Program::Role.model_name.human(count: 2)
-    breadcrumb_for @role
-    add_breadcrumb Education::Program::Role::Person.model_name.human(count: 2)
-    breadcrumb_for @person
+    add_breadcrumb University::Role.model_name.human(count: 2)
+    add_breadcrumb(@role, admin_education_program_role_path(@role, { program_id: @program.id }))
+    if @involvement
+      @involvement.persisted?  ? add_breadcrumb(@involvement, admin_education_program_role_person_path(@involvement, { program_id: @program.id, role_id: @role.id }))
+                               : add_breadcrumb(t('create'))
+    end
+  end
+
+  def involvement_params
+    params.require(:university_person_involvement)
+          .permit(:description, :position, :person_id)
+          .merge(university_id: @program.university_id, kind: :administrator)
   end
 
-  def person_params
-    params.require(:education_program_role_person)
-          .permit(:person_id)
-          .merge(role_id: @role.id)
+  def model
+    University::Person::Involvement
   end
 end
diff --git a/app/controllers/admin/education/program/roles_controller.rb b/app/controllers/admin/education/program/roles_controller.rb
index edf300d53dd07ea862b9ad2f4dd91d8c9918e47b..2d95bfaf5fd0af9ef72efed7a40312d38f54cb11 100644
--- a/app/controllers/admin/education/program/roles_controller.rb
+++ b/app/controllers/admin/education/program/roles_controller.rb
@@ -1,13 +1,17 @@
 class Admin::Education::Program::RolesController < Admin::Education::Program::ApplicationController
-  load_and_authorize_resource class: Education::Program::Role, through: :program
+  load_and_authorize_resource class: University::Role, through: :program, through_association: :university_roles
 
   include Admin::Reorderable
 
-  def reorder
-    super { |first_role| first_role.sync_program }
+  before_action :load_people, only: [:new, :edit, :create, :update]
+
+  def index
+    @roles = @roles.ordered
+    breadcrumb
   end
 
   def show
+    @involvements = @role.involvements.ordered
     breadcrumb
   end
 
@@ -48,13 +52,24 @@ class Admin::Education::Program::RolesController < Admin::Education::Program::Ap
 
   def breadcrumb
     super
-    add_breadcrumb Education::Program::Role.model_name.human(count: 2)
-    breadcrumb_for @role
+    add_breadcrumb University::Role.model_name.human(count: 2), admin_education_program_roles_path(@program)
+    if @role
+      @role.persisted?  ? add_breadcrumb(@role, admin_education_program_role_path(@role, { program_id: @program.id }))
+                        : add_breadcrumb(t('create'))
+    end
   end
 
   def role_params
-    params.require(:education_program_role)
-          .permit(:title)
-          .merge(program_id: @program.id, university_id: current_university.id)
+    params.require(:university_role)
+          .permit(:description, involvements_attributes: [:id, :person_id, :position, :_destroy])
+          .merge(target: @program, university_id: current_university.id)
+  end
+
+  def model
+    University::Role
+  end
+
+  def load_people
+    @people = current_university.people.accessible_by(current_ability).ordered
   end
 end
diff --git a/app/controllers/admin/education/program/teachers_controller.rb b/app/controllers/admin/education/program/teachers_controller.rb
index 52d7085d53b763e28311db97fbfe040db9a152cc..f469eecb8801d2ad5a09e442b854b615d26220f8 100644
--- a/app/controllers/admin/education/program/teachers_controller.rb
+++ b/app/controllers/admin/education/program/teachers_controller.rb
@@ -1,7 +1,18 @@
 class Admin::Education::Program::TeachersController < Admin::Education::Program::ApplicationController
-  load_and_authorize_resource class: Education::Program::Teacher, through: :program
+  load_and_authorize_resource :involvement,
+                              class: University::Person::Involvement,
+                              through: :program,
+                              through_association: :university_person_involvements,
+                              parent: false
 
-  before_action :get_teachers, except: :destroy
+  include Admin::Reorderable
+
+  before_action :get_available_people, except: [:index, :reorder, :destroy]
+
+  def index
+    @involvements = @involvements.ordered_by_name
+    breadcrumb
+  end
 
   def new
     breadcrumb
@@ -13,8 +24,8 @@ class Admin::Education::Program::TeachersController < Admin::Education::Program:
   end
 
   def create
-    if @teacher.save
-      redirect_to admin_education_program_path(@program), notice: t('admin.successfully_created_html', model: @teacher.to_s)
+    if @involvement.save
+      redirect_to admin_education_program_teachers_path(@program), notice: t('admin.successfully_created_html', model: @involvement.to_s)
     else
       breadcrumb
       render :new, status: :unprocessable_entity
@@ -22,8 +33,8 @@ class Admin::Education::Program::TeachersController < Admin::Education::Program:
   end
 
   def update
-    if @teacher.update(teacher_params)
-      redirect_to admin_education_program_path(@program), notice: t('admin.successfully_updated_html', model: @teacher.to_s)
+    if @involvement.update(involvement_params)
+      redirect_to admin_education_program_teachers_path(@program), notice: t('admin.successfully_updated_html', model: @involvement.to_s)
     else
       breadcrumb
       render :edit, status: :unprocessable_entity
@@ -32,26 +43,33 @@ class Admin::Education::Program::TeachersController < Admin::Education::Program:
   end
 
   def destroy
-    @teacher.destroy
-    redirect_to admin_education_program_path(@program), notice: t('admin.successfully_destroyed_html', model: @teacher.to_s)
+    @involvement.destroy
+    redirect_back fallback_location: admin_education_program_path(@program), notice: t('admin.successfully_quit_html', model: @involvement.to_s, target: @involvement.target.to_s)
   end
 
   protected
 
-  def get_teachers
-    used_teacher_ids = @program.teachers.where.not(id: @teacher.id).pluck(:person_id)
-    @teachers = current_university.people.teachers.where.not(id: used_teacher_ids).accessible_by(current_ability).ordered
+  def get_available_people
+    used_person_ids = @program.university_person_involvements.where.not(id: @involvement.id).pluck(:person_id)
+    @available_people = current_university.people.teachers.where.not(id: used_person_ids).accessible_by(current_ability).ordered
   end
 
   def breadcrumb
     super
-    add_breadcrumb Education::Program::Teacher.model_name.human(count: 2)
-    breadcrumb_for @teacher
+    add_breadcrumb Education::Program.human_attribute_name("teachers"), admin_education_program_teachers_path(@program)
+    if @involvement
+      @involvement.persisted?  ? add_breadcrumb(@involvement)
+                               : add_breadcrumb(t('create'))
+    end
+  end
+
+  def involvement_params
+    params.require(:university_person_involvement)
+          .permit(:description, :position, :person_id)
+          .merge(university_id: @program.university_id, kind: :teacher)
   end
 
-  def teacher_params
-    params.require(:education_program_teacher)
-          .permit(:description, :person_id)
-          .merge(program_id: @program.id)
+  def model
+    University::Person::Involvement
   end
 end
diff --git a/app/controllers/admin/education/programs_controller.rb b/app/controllers/admin/education/programs_controller.rb
index 3402156592d980c2c2c7d4f6afee0d49bfa528fe..980e088b8226826818179ce43fb832675bbe71aa 100644
--- a/app/controllers/admin/education/programs_controller.rb
+++ b/app/controllers/admin/education/programs_controller.rb
@@ -3,6 +3,8 @@ class Admin::Education::ProgramsController < Admin::Education::ApplicationContro
                               through: :current_university,
                               through_association: :education_programs
 
+  before_action :load_teacher_people, only: [:new, :edit, :create, :update]
+
   def index
     @programs = @programs.root.ordered
     breadcrumb
@@ -31,6 +33,8 @@ class Admin::Education::ProgramsController < Admin::Education::ApplicationContro
   end
 
   def show
+    @roles = @program.university_roles.ordered
+    @teacher_involvements = @program.university_person_involvements.includes(:person).ordered_by_name
     breadcrumb
   end
 
@@ -82,7 +86,11 @@ class Admin::Education::ProgramsController < Admin::Education::ApplicationContro
       :featured_image, :featured_image_delete, :featured_image_infos, :featured_image_alt,
       :prerequisites, :objectives, :duration, :registration, :pedagogy,
       :evaluation, :accessibility, :pricing, :contacts, :opportunities, :other,
-      :parent_id, school_ids: []
+      :parent_id, school_ids: [], university_person_involvements_attributes: [:id, :person_id, :description, :position, :_destroy]
     )
   end
+
+  def load_teacher_people
+    @teacher_people = current_university.people.teachers.accessible_by(current_ability).ordered
+  end
 end
diff --git a/app/controllers/admin/education/school/administrators_controller.rb b/app/controllers/admin/education/school/administrators_controller.rb
deleted file mode 100644
index d7788a8b49a0ce838ac66a389ae470bacf113151..0000000000000000000000000000000000000000
--- a/app/controllers/admin/education/school/administrators_controller.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-class Admin::Education::School::AdministratorsController < Admin::Education::School::ApplicationController
-  load_and_authorize_resource class: Education::School::Administrator, through: :school
-
-  def new
-    breadcrumb
-  end
-
-  def edit
-    breadcrumb
-    add_breadcrumb t('edit')
-  end
-
-  def create
-    if @administrator.save
-      redirect_to admin_education_school_path(@school), notice: t('admin.successfully_created_html', model: @administrator.to_s)
-    else
-      breadcrumb
-      render :new, status: :unprocessable_entity
-    end
-  end
-
-  def update
-    if @administrator.update(administrator_params)
-      redirect_to admin_education_school_path(@school), notice: t('admin.successfully_updated_html', model: @administrator.to_s)
-    else
-      breadcrumb
-      render :edit, status: :unprocessable_entity
-      add_breadcrumb t('edit')
-    end
-  end
-
-  def destroy
-    @administrator.destroy
-    redirect_to admin_education_school_path(@school), notice: t('admin.successfully_destroyed_html', model: @administrator.to_s)
-  end
-
-  protected
-
-  def breadcrumb
-    super
-    add_breadcrumb Education::School::Administrator.model_name.human(count: 2)
-    breadcrumb_for @administrator
-  end
-
-  def administrator_params
-    params.require(:education_school_administrator)
-          .permit(:description, :person_id)
-          .merge(school_id: @school.id)
-  end
-end
diff --git a/app/controllers/admin/education/school/role/people_controller.rb b/app/controllers/admin/education/school/role/people_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a1bd4624bdd02b8390da2cfbed647be8cdaa9ffb
--- /dev/null
+++ b/app/controllers/admin/education/school/role/people_controller.rb
@@ -0,0 +1,66 @@
+class Admin::Education::School::Role::PeopleController < Admin::Education::School::ApplicationController
+  load_and_authorize_resource :role, class: University::Role, through: :school, param: :role_id, through_association: :university_roles
+  load_and_authorize_resource :involvement, class: University::Person::Involvement, through: :role, parent: false
+
+  include Admin::Reorderable
+
+  before_action :get_available_people, except: [:reorder, :destroy]
+
+  def new
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    if @involvement.save
+      redirect_to admin_education_school_role_path(@role, { school_id: @school.id }), notice: t('admin.successfully_created_html', model: @involvement.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @involvement.update(involvement_params)
+      redirect_to admin_education_school_role_path(@role, { school_id: @school.id }), notice: t('admin.successfully_updated_html', model: @involvement.to_s)
+    else
+      breadcrumb
+      render :edit, status: :unprocessable_entity
+      add_breadcrumb t('edit')
+    end
+  end
+
+  def destroy
+    @involvement.destroy
+    redirect_to admin_education_school_role_path(@role, { school_id: @school.id }), notice: t('admin.successfully_destroyed_html', model: @involvement.to_s)
+  end
+
+  protected
+
+  def get_available_people
+    used_person_ids = @role.involvements.where.not(id: @involvement.id).pluck(:person_id)
+    @available_people = current_university.people.administration.where.not(id: used_person_ids).accessible_by(current_ability).ordered
+  end
+
+  def breadcrumb
+    super
+    add_breadcrumb University::Role.model_name.human(count: 2), admin_education_school_roles_path(@school)
+    add_breadcrumb(@role, admin_education_school_role_path(@role, { school_id: @school.id }))
+    if @involvement
+      @involvement.persisted?  ? add_breadcrumb(@involvement, admin_education_school_role_person_path(@involvement, { school_id: @school.id, role_id: @role.id }))
+                               : add_breadcrumb(t('create'))
+    end
+  end
+
+  def involvement_params
+    params.require(:university_person_involvement).permit(:position, :person_id)
+  end
+
+  def model
+    University::Person::Involvement
+  end
+end
diff --git a/app/controllers/admin/education/school/roles_controller.rb b/app/controllers/admin/education/school/roles_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7ab46b743b6a18ddeb7ae3c95e381842896a8173
--- /dev/null
+++ b/app/controllers/admin/education/school/roles_controller.rb
@@ -0,0 +1,75 @@
+class Admin::Education::School::RolesController < Admin::Education::School::ApplicationController
+  load_and_authorize_resource class: University::Role, through: :school, through_association: :university_roles
+
+  include Admin::Reorderable
+
+  before_action :load_administration_people, only: [:new, :edit, :create, :update]
+
+  def index
+    @roles = @roles.ordered
+    breadcrumb
+  end
+
+  def show
+    @involvements = @role.involvements.ordered
+    breadcrumb
+  end
+
+  def new
+    breadcrumb
+  end
+
+  def edit
+    breadcrumb
+    add_breadcrumb t('edit')
+  end
+
+  def create
+    if @role.save
+      redirect_to admin_education_school_role_path(@role), notice: t('admin.successfully_created_html', model: @role.to_s)
+    else
+      breadcrumb
+      render :new, status: :unprocessable_entity
+    end
+  end
+
+  def update
+    if @role.update(role_params)
+      redirect_to admin_education_school_role_path(@role), notice: t('admin.successfully_updated_html', model: @role.to_s)
+    else
+      breadcrumb
+      render :edit, status: :unprocessable_entity
+      add_breadcrumb t('edit')
+    end
+  end
+
+  def destroy
+    @role.destroy
+    redirect_to admin_education_school_roles_path(@school), notice: t('admin.successfully_destroyed_html', model: @role.to_s)
+  end
+
+  protected
+
+  def breadcrumb
+    super
+    add_breadcrumb Education::School.human_attribute_name('roles'), admin_education_school_roles_path(@school)
+    if @role
+      @role.persisted?  ? add_breadcrumb(@role, admin_education_school_role_path(@role, { school_id: @school.id }))
+                        : add_breadcrumb(t('create'))
+    end
+  end
+
+  def role_params
+    params.require(:university_role)
+          .permit(:description, involvements_attributes: [:id, :person_id, :position, :_destroy])
+          .merge(target: @school, university_id: @school.university_id)
+  end
+
+  def model
+    University::Role
+  end
+
+  def load_administration_people
+    @administration_people = current_university.people.administration.accessible_by(current_ability).ordered
+  end
+end
diff --git a/app/controllers/admin/education/schools_controller.rb b/app/controllers/admin/education/schools_controller.rb
index e1368257c8d1520570065c6c0a9817a75a720f91..7152bbd2748ae5a6622823a90566cf1fea61c082 100644
--- a/app/controllers/admin/education/schools_controller.rb
+++ b/app/controllers/admin/education/schools_controller.rb
@@ -8,6 +8,7 @@ class Admin::Education::SchoolsController < Admin::Education::ApplicationControl
   end
 
   def show
+    @roles = @school.university_roles.ordered
     breadcrumb
   end
 
diff --git a/app/controllers/admin/education/teachers_controller.rb b/app/controllers/admin/education/teachers_controller.rb
index 6aa8315db54177a5b0375306232d42218d9c01ec..93b626fd9a9a89e3889f33b120465588666abaaa 100644
--- a/app/controllers/admin/education/teachers_controller.rb
+++ b/app/controllers/admin/education/teachers_controller.rb
@@ -1,13 +1,29 @@
 class Admin::Education::TeachersController < Admin::Education::ApplicationController
+  before_action :load_teacher, only: [:show, :edit, :update]
+
   def index
     @teachers = current_university.people.teachers.accessible_by(current_ability).ordered.page(params[:page])
     breadcrumb
   end
 
   def show
-    @teacher = current_university.people.teachers.accessible_by(current_ability).find(params[:id])
+    @involvements = @teacher.involvements_as_teacher.includes(:target).order(:created_at).page(params[:page])
+    breadcrumb
+  end
+
+  def edit
     breadcrumb
-    @programs = @teacher.education_programs.ordered.page(params[:page])
+    add_breadcrumb t('edit')
+  end
+
+  def update
+    if @teacher.update(teacher_params)
+      redirect_to admin_education_teacher_path(@teacher), notice: t('admin.successfully_updated_html', model: @teacher.to_s)
+    else
+      render :edit
+      breadcrumb
+      add_breadcrumb t('edit')
+    end
   end
 
   protected
@@ -17,4 +33,14 @@ class Admin::Education::TeachersController < Admin::Education::ApplicationContro
     add_breadcrumb t('education.teachers', count: 2), admin_education_teachers_path
     add_breadcrumb @teacher, admin_education_teacher_path(@teacher) if @teacher
   end
+
+  def load_teacher
+    @teacher = current_university.people.teachers.accessible_by(current_ability).find(params[:id])
+  end
+
+  def teacher_params
+    params.require(:university_person).permit(
+      involvements_attributes: [:id, :target_id, :target_type, :description, :_destroy]
+    )
+  end
 end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 905dd2f6a0db3038439066dcc1c2fec1f76f2273..0a189db00a1f71e12f6c32180960d1b03550b4ca 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -32,10 +32,12 @@ class Ability
 
   def teacher
     can :manage, University::Person, user_id: @user.id
+    # can :read, University::Person, university_id: @user.university_id
+    cannot :create, University::Person
     can :read, Education::Program, university_id: @user.university_id
-    can :manage, Education::Program::Teacher, person_id: @user.person&.id
-    can :read, Education::Program::Role, university_id: @user.university_id
-    can :manage, Education::Program::Role::Person, person_id: @user.person&.id
+    can :read, University::Role, university_id: @user.university_id
+    can :manage, University::Person::Involvement, person_id: @user.person&.id
+    can :read, University::Person::Involvement, university_id: @user.university_id
   end
 
   def program_manager
@@ -57,15 +59,13 @@ class Ability
     can :manage, Communication::Website::Imported::Page, university_id: @user.university_id
     can :manage, Communication::Website::Imported::Post, university_id: @user.university_id
     can :manage, Education::School, university_id: @user.university_id
-    can :manage, Education::School::Administrator, university_id: @user.university_id
     can :manage, Education::Program, university_id: @user.university_id
-    can :manage, Education::Program::Teacher, university_id: @user.university_id
-    can :manage, Education::Program::Role, university_id: @user.university_id
-    can :manage, Education::Program::Role::Person, university_id: @user.university_id
     can :manage, Research::Journal, university_id: @user.university_id
     can :manage, Research::Journal::Article, university_id: @user.university_id
     can :manage, Research::Journal::Volume, university_id: @user.university_id
     can :manage, Research::Laboratory, university_id: @user.university_id
+    can :manage, University::Role, university_id: @user.university_id
+    can :manage, University::Person::Involvement, university_id: @user.university_id
     can :read, User, university_id: @user.university_id
     can :manage, User, university_id: @user.university_id, role: @user.managed_roles
   end
diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb
index 7549fdf462e06c88fe75fba7e750b66b95637c51..d583ecedbd4a216b8653318347c9cd3dc4490104 100644
--- a/app/models/communication/website.rb
+++ b/app/models/communication/website.rb
@@ -57,13 +57,18 @@ class Communication::Website < ApplicationRecord
       pages + pages.map(&:active_storage_blobs).flatten +
       posts + posts.map(&:active_storage_blobs).flatten +
       [home] + home.explicit_active_storage_blobs +
-      categories + menus + people + [about]
+      people_with_facets + people.map(&:active_storage_blobs).flatten +
+      categories + menus + [about]
     )
 
     if about.is_a? Education::School
       dependencies += about.programs
+      dependencies += about.programs.map(&:active_storage_blobs).flatten
     elsif about.is_a? Research::Journal
-      dependencies.concat [about.articles, about.volumes]
+      dependencies += about.articles
+      dependencies += about.articles.map(&:active_storage_blobs).flatten
+      dependencies += about.volumes
+      dependencies += about.volumes.map(&:active_storage_blobs).flatten
     end
 
     dependencies
diff --git a/app/models/communication/website/with_abouts.rb b/app/models/communication/website/with_abouts.rb
index 88ef5400cfd0a5fb59902f4fb2de8ca8f507e54a..83916b81544fc65b6e2c256bc20e4053ddcff20e 100644
--- a/app/models/communication/website/with_abouts.rb
+++ b/app/models/communication/website/with_abouts.rb
@@ -64,15 +64,31 @@ module Communication::Website::WithAbouts
 
   def people
     @people ||= begin
+      people = authors
+      if about_school?
+        people += about.university_people_through_role_involvements
+        people += about.university_people_through_program_involvements
+        people += about.university_people_through_program_role_involvements
+      elsif about_journal?
+        people += about.people
+      end
+      people.uniq.compact
+    end
+  end
+
+  def people_with_facets
+    @people_with_facets ||= begin
       people = authors + authors.compact.map(&:author)
       if about_school?
-        people += programs.collect(&:university_people_through_teachers).flatten
-        people += programs.collect(&:university_people_through_teachers).flatten.map(&:teacher)
-        people += about.university_people_through_administrators
-        people += about.university_people_through_administrators.map(&:administrator)
+        people += about.university_people_through_role_involvements
+        people += about.university_people_through_role_involvements.map(&:administrator)
+        people += about.university_people_through_program_involvements
+        people += about.university_people_through_program_involvements.map(&:teacher)
+        people += about.university_people_through_program_role_involvements
+        people += about.university_people_through_program_role_involvements.map(&:administrator)
       elsif about_journal?
-        people += research_articles.collect(&:people).flatten
-        people += research_articles.collect(&:people).flatten.map(&:researcher)
+        people += about.people
+        people += about.people.map(&:researcher)
       end
       people.uniq.compact
     end
diff --git a/app/models/communication/website/with_menu_items.rb b/app/models/communication/website/with_menu_items.rb
index b40ed1b5ba50f358da6acfebf8d9082dd68136f2..4a343d7dccf2dec0b13c2a8aff251e23dfe6eae3 100644
--- a/app/models/communication/website/with_menu_items.rb
+++ b/app/models/communication/website/with_menu_items.rb
@@ -53,11 +53,11 @@ module Communication::Website::WithMenuItems
   end
 
   def menu_item_kind_researchers?
-    research_articles.collect(&:people).flatten.any?
+    about_journal? && about.people.any?
   end
 
   def menu_item_kind_teachers?
-    programs.collect(&:university_people_through_teachers).flatten.any?
+    about_school? && about.university_people_through_program_involvements.any?
   end
 
   def menu_item_kind_research_volumes?
diff --git a/app/models/education/program.rb b/app/models/education/program.rb
index c4c75482b940290fbf10fc1dc4c503b05a43a4c2..37c7c1ff77a18c8f4ba8ce4971b1cb57211ab0b3 100644
--- a/app/models/education/program.rb
+++ b/app/models/education/program.rb
@@ -63,20 +63,23 @@ class Education::Program < ApplicationRecord
              class_name: 'Education::Program',
              foreign_key: :parent_id,
              dependent: :destroy
-  has_many   :teachers,
-             class_name: 'Education::Program::Teacher',
+  has_many   :university_roles,
+             class_name: 'University::Role',
+             as: :target,
              dependent: :destroy
-  has_many   :university_people_through_teachers,
-             through: :teachers,
+  has_many   :involvements_through_roles,
+             through: :university_roles,
+             source: :involvements
+  has_many   :university_people_through_role_involvements,
+             through: :involvements_through_roles,
              source: :person
-  has_many   :roles,
-             class_name: 'Education::Program::Role',
+  has_many   :university_person_involvements,
+             class_name: 'University::Person::Involvement',
+             as: :target,
+             inverse_of: :target,
              dependent: :destroy
-  has_many   :role_people,
-             through: :roles,
-             source: :people
-  has_many   :university_people_through_roles,
-             through: :role_people,
+  has_many   :university_people_through_involvements,
+             through: :university_person_involvements,
              source: :person
   has_many   :website_categories,
              class_name: 'Communication::Website::Category',
@@ -88,6 +91,8 @@ class Education::Program < ApplicationRecord
                           association_foreign_key: 'education_school_id'
   has_many :websites, -> { distinct }, through: :schools
 
+  accepts_nested_attributes_for :university_person_involvements, reject_if: :all_blank, allow_destroy: true
+
   enum level: {
     first_year: 100,
     second_year: 200,
@@ -122,10 +127,12 @@ class Education::Program < ApplicationRecord
   def git_dependencies(website)
     [self] +
     active_storage_blobs +
-    university_people_through_teachers +
-    university_people_through_teachers.map(&:teacher) +
-    university_people_through_roles
-    # TODO: les administrative via roles
+    university_people_through_involvements +
+    university_people_through_involvements.map(&:active_storage_blobs) +
+    university_people_through_involvements.map(&:teacher) +
+    university_people_through_role_involvements +
+    university_people_through_role_involvements.map(&:active_storage_blobs) +
+    university_people_through_role_involvements.map(&:administrator)
   end
 
   def git_destroy_dependencies(website)
diff --git a/app/models/education/program/role.rb b/app/models/education/program/role.rb
deleted file mode 100644
index d52e7447e3c4043422d87aaf474c03f9561149c7..0000000000000000000000000000000000000000
--- a/app/models/education/program/role.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# == Schema Information
-#
-# Table name: education_program_roles
-#
-#  id            :uuid             not null, primary key
-#  position      :integer
-#  title         :string
-#  created_at    :datetime         not null
-#  updated_at    :datetime         not null
-#  program_id    :uuid             not null
-#  university_id :uuid             not null
-#
-# Indexes
-#
-#  index_education_program_roles_on_program_id     (program_id)
-#  index_education_program_roles_on_university_id  (university_id)
-#
-# Foreign Keys
-#
-#  fk_rails_...  (program_id => education_programs.id)
-#  fk_rails_...  (university_id => universities.id)
-#
-class Education::Program::Role < ApplicationRecord
-  include WithPosition
-
-  belongs_to :university
-  belongs_to :program, class_name: 'Education::Program'
-  has_many :people, class_name: 'Education::Program::Role::Person', dependent: :destroy
-  has_many :university_people, through: :people, source: :person
-
-  after_commit :sync_program
-
-  def to_s
-    "#{title}"
-  end
-
-  def sync_program
-    program.sync_with_git
-  end
-
-  protected
-
-  def last_ordered_element
-    program.roles.ordered.last
-  end
-end
diff --git a/app/models/education/program/role/person.rb b/app/models/education/program/role/person.rb
deleted file mode 100644
index 87521175e302f56ef0abed9e39ee19f90ad281e5..0000000000000000000000000000000000000000
--- a/app/models/education/program/role/person.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# == Schema Information
-#
-# Table name: education_program_role_people
-#
-#  id         :uuid             not null, primary key
-#  position   :integer
-#  created_at :datetime         not null
-#  updated_at :datetime         not null
-#  person_id  :uuid             not null
-#  role_id    :uuid             not null
-#
-# Indexes
-#
-#  index_education_program_role_people_on_person_id  (person_id)
-#  index_education_program_role_people_on_role_id    (role_id)
-#
-# Foreign Keys
-#
-#  fk_rails_...  (person_id => university_people.id)
-#  fk_rails_...  (role_id => education_program_roles.id)
-#
-class Education::Program::Role::Person < ApplicationRecord
-  include WithPosition
-
-  belongs_to :person, class_name: 'University::Person'
-  belongs_to :role, class_name: 'Education::Program::Role'
-  delegate :program, to: :role
-
-  after_commit :sync_program
-
-  def to_s
-    person.to_s
-  end
-
-  def sync_program
-    program.sync_with_git
-  end
-
-  protected
-
-  def last_ordered_element
-    role.people.ordered.last
-  end
-end
diff --git a/app/models/education/program/teacher.rb b/app/models/education/program/teacher.rb
deleted file mode 100644
index ee27e125906adac518d821a451926a46c520ab5a..0000000000000000000000000000000000000000
--- a/app/models/education/program/teacher.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# == Schema Information
-#
-# Table name: education_program_teachers
-#
-#  id          :uuid             not null, primary key
-#  description :text
-#  created_at  :datetime         not null
-#  updated_at  :datetime         not null
-#  person_id   :uuid             not null
-#  program_id  :uuid             not null
-#
-# Indexes
-#
-#  index_education_program_teachers_on_person_id   (person_id)
-#  index_education_program_teachers_on_program_id  (program_id)
-#
-# Foreign Keys
-#
-#  fk_rails_...  (person_id => university_people.id)
-#  fk_rails_...  (program_id => education_programs.id)
-#
-class Education::Program::Teacher < ApplicationRecord
-  belongs_to :program, class_name: 'Education::Program'
-  belongs_to :person, class_name: 'University::Person'
-
-  validates :person_id, uniqueness: { scope: :program_id }
-
-  scope :ordered, -> { joins(:person).order('university_people.last_name, university_people.first_name') }
-
-  after_commit :sync_program
-
-  def to_s
-    person.to_s
-  end
-
-  def best_description
-    description.blank? ? person.description : description
-  end
-
-  protected
-
-  def sync_program
-    program.sync_with_git
-  end
-end
diff --git a/app/models/education/school.rb b/app/models/education/school.rb
index 6f79e2f3beb978ade47e0fe8d554ec8fcb5824c6..7af5c773bd9de1908bf2ecb106bd3b78a58502cd 100644
--- a/app/models/education/school.rb
+++ b/app/models/education/school.rb
@@ -28,16 +28,23 @@ class Education::School < ApplicationRecord
 
   belongs_to :university
   has_many  :websites, class_name: 'Communication::Website', as: :about, dependent: :nullify
-  has_many  :administrators, dependent: :destroy
-  has_many  :university_people_through_administrators,
-            through: :administrators,
-            source: :person
   has_and_belongs_to_many :programs,
                           class_name: 'Education::Program',
                           join_table: 'education_programs_schools',
                           foreign_key: 'education_school_id',
                           association_foreign_key: 'education_program_id'
-  has_many :teachers, -> { distinct }, through: :programs
+
+  has_many  :university_roles, class_name: 'University::Role', as: :target, dependent: :destroy
+  has_many  :involvements_through_roles, through: :university_roles, source: :involvements
+  has_many  :university_people_through_role_involvements,
+            through: :involvements_through_roles,
+            source: :person
+  has_many  :university_people_through_program_involvements,
+            through: :programs,
+            source: :university_people_through_involvements
+  has_many  :university_people_through_program_role_involvements,
+            through: :programs,
+            source: :university_people_through_role_involvements
 
   validates :name, :address, :city, :zipcode, :country, presence: true
 
@@ -53,7 +60,7 @@ class Education::School < ApplicationRecord
 
   def git_dependencies(website)
     [self] +
-    university_people_through_administrators +
-    university_people_through_administrators.map(&:administrator)
+    university_people_through_role_involvements +
+    university_people_through_role_involvements.map(&:administrator)
   end
 end
diff --git a/app/models/education/school/administrator.rb b/app/models/education/school/administrator.rb
deleted file mode 100644
index 6b9e5a69dcb76d99c0d9f55c30fd405b1102385d..0000000000000000000000000000000000000000
--- a/app/models/education/school/administrator.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# == Schema Information
-#
-# Table name: education_school_administrators
-#
-#  id          :uuid             not null, primary key
-#  description :text
-#  created_at  :datetime         not null
-#  updated_at  :datetime         not null
-#  person_id   :uuid             not null
-#  school_id   :uuid             not null
-#
-# Indexes
-#
-#  index_education_school_administrators_on_person_id  (person_id)
-#  index_education_school_administrators_on_school_id  (school_id)
-#
-# Foreign Keys
-#
-#  fk_rails_...  (person_id => university_people.id)
-#  fk_rails_...  (school_id => education_schools.id)
-#
-class Education::School::Administrator < ApplicationRecord
-  belongs_to :school
-  belongs_to :person, class_name: "University::Person"
-
-  validates :person_id, uniqueness: { scope: :school_id }
-
-  after_commit :sync_school
-
-  scope :ordered, -> { joins(:person).order('university_people.last_name, university_people.first_name') }
-
-  def to_s
-    person.to_s
-  end
-
-  def best_description
-    description.blank? ? person.description : description
-  end
-
-  protected
-
-  def sync_school
-    school.sync_with_git
-  end
-end
diff --git a/app/models/research/journal.rb b/app/models/research/journal.rb
index bc4be6eca437a294db96061f4629166f8fc11b20..f4f6fc15f97fa17618ec065c2abfd9d7a57110ff 100644
--- a/app/models/research/journal.rb
+++ b/app/models/research/journal.rb
@@ -27,7 +27,7 @@ class Research::Journal < ApplicationRecord
   has_many :websites, class_name: 'Communication::Website', as: :about, dependent: :nullify
   has_many :volumes, foreign_key: :research_journal_id, dependent: :destroy
   has_many :articles, foreign_key: :research_journal_id, dependent: :destroy
-  has_many :people, through: :articles
+  has_many :people, -> { distinct }, through: :articles
 
   scope :ordered, -> { order(:title) }
 
@@ -40,7 +40,7 @@ class Research::Journal < ApplicationRecord
   end
 
   def git_dependencies(website)
-    [self] + articles + volumes + people + people.map(&:researcher)
+    [self] + articles + volumes + people + people.map(&:active_storage_blobs).flatten + people.map(&:researcher)
   end
 
   def git_destroy_dependencies(website)
diff --git a/app/models/research/journal/article.rb b/app/models/research/journal/article.rb
index 5617202477cb2a9af6cf8e85dd15d144a276b35b..1d899b51a97105eefebe45a66a5650d1c0273489 100644
--- a/app/models/research/journal/article.rb
+++ b/app/models/research/journal/article.rb
@@ -66,6 +66,7 @@ class Research::Journal::Article < ApplicationRecord
     active_storage_blobs +
     other_articles_in_the_volume +
     people +
+    people.map(&:active_storage_blobs).flatten +
     people.map(&:researcher)
   end
 
diff --git a/app/models/research/journal/volume.rb b/app/models/research/journal/volume.rb
index 80c1fb01f42f15b0b67f75d08389b31b3064f273..9365b67740f1130f468ba8cd4b88f6b240bcca87 100644
--- a/app/models/research/journal/volume.rb
+++ b/app/models/research/journal/volume.rb
@@ -8,7 +8,7 @@
 #  keywords            :text
 #  number              :integer
 #  published           :boolean          default(FALSE)
-#  published_at        :date
+#  published_at        :datetime
 #  slug                :string
 #  title               :string
 #  created_at          :datetime         not null
@@ -47,11 +47,11 @@ class Research::Journal::Volume < ApplicationRecord
   end
 
   def git_path(website)
-    "content/volumes/#{published_at.year}/#{published_at.strftime "%Y-%m-%d"}-#{slug}.html" if published_at
+    "content/volumes/#{published_at.year}/#{slug}/_index.html" if published_at
   end
 
   def git_dependencies(website)
-    [self] + articles + people + people.map(&:researcher) + active_storage_blobs
+    [self] + articles + people + people.map(&:active_storage_blobs).flatten + people.map(&:researcher) + active_storage_blobs
   end
 
   def git_destroy_dependencies(website)
diff --git a/app/models/university/person.rb b/app/models/university/person.rb
index c23ba6098c2fd5f751b094e316035e8ea0acf018..392568a1cb285f87087fc15a7bd4fd83f68f207d 100644
--- a/app/models/university/person.rb
+++ b/app/models/university/person.rb
@@ -31,8 +31,10 @@
 #
 class University::Person < ApplicationRecord
   include WithGit
+  include WithBlobs
   include WithSlug
   include WithPicture
+  include WithEducation
 
   has_rich_text :biography
 
@@ -44,22 +46,6 @@ class University::Person < ApplicationRecord
                           join_table: :research_journal_articles_researchers,
                           foreign_key: :researcher_id
 
-  has_many                :education_program_teachers,
-                          class_name: 'Education::Program::Teacher',
-                          dependent: :destroy
-
-  has_many                :education_program_role_people,
-                          class_name: 'Education::Program::Role::Person',
-                          dependent: :destroy
-
-  has_many                :education_programs,
-                          through: :education_program_teachers,
-                          source: :program
-
-  has_many                :education_school_administrators,
-                          class_name: 'Education::School::Administrator',
-                          dependent: :destroy
-
   has_many                :communication_website_posts,
                           class_name: 'Communication::Website::Post',
                           foreign_key: :author_id,
@@ -70,6 +56,10 @@ class University::Person < ApplicationRecord
                           foreign_key: :author_id,
                           dependent: :destroy
 
+  has_many                :involvements,
+                          class_name: 'University::Person::Involvement',
+                          dependent: :destroy
+
   has_many                :author_websites,
                           -> { distinct },
                           through: :communication_website_posts,
@@ -85,6 +75,7 @@ class University::Person < ApplicationRecord
                           through: :education_programs,
                           source: :websites
 
+  accepts_nested_attributes_for :involvements
 
   validates_presence_of   :first_name, :last_name
   validates_uniqueness_of :email,
@@ -119,7 +110,7 @@ class University::Person < ApplicationRecord
     dependencies = []
     if for_website?(website)
       dependencies << self
-      dependencies << best_picture.blob
+      dependencies.concat active_storage_blobs
     end
     dependencies << administrator if administrator.for_website?(website)
     dependencies << author if author.for_website?(website)
@@ -153,6 +144,14 @@ class University::Person < ApplicationRecord
 
   protected
 
+  def explicit_blob_ids
+    [picture&.blob_id]
+  end
+
+  def inherited_blob_ids
+    [best_picture&.blob_id]
+  end
+
   def sanitize_email
     self.email = self.email.downcase.strip
   end
diff --git a/app/models/university/person/administrator.rb b/app/models/university/person/administrator.rb
index fc58865e3cace8c5bedd097b13181c71e4346e33..82976022e823969216acde098bb649c11b5dd955 100644
--- a/app/models/university/person/administrator.rb
+++ b/app/models/university/person/administrator.rb
@@ -39,6 +39,9 @@ class University::Person::Administrator < University::Person
   end
 
   def for_website?(website)
-    is_administration && website.about_school? && Education::School::Administrator.where(school_id: website.about_id, person_id: id).any?
+    is_administration && website.about_school? && (
+      website.about.university_people_through_role_involvements.find_by(id: id).present? ||
+      website.programs.published.joins(:involvements_through_roles).where(university_person_involvements: { person_id: id }).any?
+    )
   end
 end
diff --git a/app/models/university/person/involvement.rb b/app/models/university/person/involvement.rb
index 83c1b444a5dc914519773f8f35a3c593cf96ea18..8cc875a66c538f7a9ed09f04e97380021e56f6e3 100644
--- a/app/models/university/person/involvement.rb
+++ b/app/models/university/person/involvement.rb
@@ -27,15 +27,50 @@
 class University::Person::Involvement < ApplicationRecord
   include WithPosition
 
+  enum kind: { administrator: 10, researcher: 20, teacher: 30 }
+
   belongs_to :university
-  belongs_to :person
+  belongs_to :person, class_name: 'University::Person'
   belongs_to :target, polymorphic: true
 
-  enum kind: { administrator: 10, researcher: 20, teacher: 30 }
+  validates :person_id, uniqueness: { scope: [:target_id, :target_type] }
+  validates :target_id, uniqueness: { scope: [:person_id, :target_type] }
+
+  before_validation :set_kind, on: :create
+  before_validation :set_university_id, on: :create
+  after_commit :sync_with_git
+
+  scope :ordered_by_name, -> {
+    joins(:person).select('university_person_involvements.*')
+                  .order('university_people.last_name', 'university_people.first_name')
+  }
+
+  def to_s
+    "#{person}"
+  end
+
+  def sync_with_git
+    target.sync_with_git if target.respond_to? :sync_with_git
+  end
 
   protected
 
   def last_ordered_element
     self.class.unscoped.where(university_id: university_id, target: target).ordered.last
   end
+
+  def set_kind
+    case target_type
+    when "Education::Program"
+      self.kind = :teacher
+    when "Research::Laboratory"
+      self.kind = :researcher
+    else
+      self.kind = :administrator
+    end
+  end
+
+  def set_university_id
+    self.university_id = self.person.university_id
+  end
 end
diff --git a/app/models/university/person/teacher.rb b/app/models/university/person/teacher.rb
index 8f354959b118a2d4486826f5be6117b6ab8c9ee6..89e5a7d3441b0720af6edb0d7993ec93a27734af 100644
--- a/app/models/university/person/teacher.rb
+++ b/app/models/university/person/teacher.rb
@@ -41,8 +41,8 @@ class University::Person::Teacher < University::Person
   def for_website?(website)
     is_teacher && website.about_school? && website.programs
                                                   .published
-                                                  .joins(:teachers)
-                                                  .where(education_program_teachers: { person_id: id })
+                                                  .joins(:university_person_involvements)
+                                                  .where(university_person_involvements: { person_id: id })
                                                   .any?
   end
 end
diff --git a/app/models/university/person/with_education.rb b/app/models/university/person/with_education.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e350249f0676aa67435303ce7b9f29d5524c81ce
--- /dev/null
+++ b/app/models/university/person/with_education.rb
@@ -0,0 +1,21 @@
+module University::Person::WithEducation
+  extend ActiveSupport::Concern
+
+  included do
+    has_many  :involvements_as_teacher,
+              -> { where(kind: 'teacher') },
+              class_name: 'University::Person::Involvement'
+
+    has_many  :education_programs_as_teacher,
+              through: :involvements_as_teacher,
+              source: :target,
+              source_type: "Education::Program"
+  end
+
+  def education_programs_as_administrator
+    university.education_programs
+              .joins(:involvements_through_roles)
+              .where(university_person_involvements: { person_id: id })
+              .distinct
+  end
+end
diff --git a/app/models/university/role.rb b/app/models/university/role.rb
index d63eb7041925492af796c72c6b8585b4f7140ce6..7d6c29d6fa46ae38b9e2b5da3ecc6a13cb3b9371 100644
--- a/app/models/university/role.rb
+++ b/app/models/university/role.rb
@@ -25,6 +25,18 @@ class University::Role < ApplicationRecord
 
   belongs_to :university
   belongs_to :target, polymorphic: true, optional: true
+  has_many :involvements, class_name: 'University::Person::Involvement', as: :target, dependent: :destroy, inverse_of: :target
+  has_many :people, through: :involvements
+
+  accepts_nested_attributes_for :involvements, reject_if: :all_blank, allow_destroy: true
+
+  def to_s
+    "#{description}"
+  end
+
+  def sync_with_git
+    target.sync_with_git if target&.respond_to? :sync_with_git
+  end
 
   protected
 
diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb
index bfc04677d206700925559742c2e0515ee3048532..06139d6319f2d2cd80d83698d1b671bde6bd88dc 100644
--- a/app/views/admin/dashboard/index.html.erb
+++ b/app/views/admin/dashboard/index.html.erb
@@ -15,7 +15,7 @@
   </div>
 </div>
 
-<% if current_university.research_journals.any? %>
+<% if current_university.research_journals.any? && can?(:read, Research::Journal) %>
   <h2 class="h4 my-4"><%= Research::Journal.model_name.human(count: 2) %></h2>
   <div class="row">
     <% current_university.research_journals.each do |journal| %>
@@ -33,7 +33,7 @@
   </div>
 <% end %>
 
-<% if current_university.communication_websites.any? %>
+<% if current_university.communication_websites.any? && can?(:read, Communication::Website) %>
   <h2 class="h4 my-4"><%= Communication::Website.model_name.human(count: 2) %></h2>
   <div class="row">
     <% current_university.communication_websites.each do |website| %>
diff --git a/app/views/admin/education/program/role/people/_list.html.erb b/app/views/admin/education/program/role/people/_list.html.erb
deleted file mode 100644
index fc31ddd11a709929630337c2f87dde710821fc42..0000000000000000000000000000000000000000
--- a/app/views/admin/education/program/role/people/_list.html.erb
+++ /dev/null
@@ -1,30 +0,0 @@
-<% if people.any? %>
-  <table class="table table-sortable">
-    <thead>
-      <tr>
-        <th><%= Education::Program::Role::Person.model_name.human %></th>
-        <th></th>
-      </tr>
-    </thead>
-    <tbody data-reorder-url="<%= reorder_admin_education_program_role_people_path(program_id: @program.id, role_id: @role.id) %>">
-      <% people.each do |person| %>
-        <tr class="handle" data-id="<%= person.id %>">
-          <td>
-            <%= link_to_if  can?(:read, person.person),
-                            person.person,
-                            admin_university_person_path(person.person) %>
-          </td>
-          <td class="text-end pe-0">
-            <div class="btn-group" role="group">
-              <%= link_to t('delete'),
-                          admin_education_program_role_person_path(person, { role_id: @role.id }),
-                          method: :delete,
-                          data: { confirm: t('please_confirm') },
-                          class: button_classes_danger %>
-            </div>
-          </td>
-        </tr>
-      <% end %>
-    </tbody>
-  </table>
-<% end %>
diff --git a/app/views/admin/education/program/role/people/new.html.erb b/app/views/admin/education/program/role/people/new.html.erb
deleted file mode 100644
index d2a7ac69768b51844a88ea8619cf00ff449dd98e..0000000000000000000000000000000000000000
--- a/app/views/admin/education/program/role/people/new.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-<% content_for :title, Education::Program::Role::Person.model_name.human %>
-
-<%= simple_form_for [:admin, @person] do |f| %>
-  <div class="row">
-    <div class="col-md-8">
-      <div class="card flex-fill w-100">
-        <div class="card-header">
-          <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
-        </div>
-        <div class="card-body">
-          <%= f.association :person, collection: @available_people %>
-        </div>
-      </div>
-    </div>
-  </div>
-  <% content_for :action_bar_right do %>
-    <%= submit f %>
-  <% end %>
-<% end %>
diff --git a/app/views/admin/education/program/roles/_form.html.erb b/app/views/admin/education/program/roles/_form.html.erb
index 26ccb783e9660e9a9c21fdeab7c8088934d0a0c9..54d99730bb0f75ceb39daa2b09be65eb3fe51a9c 100644
--- a/app/views/admin/education/program/roles/_form.html.erb
+++ b/app/views/admin/education/program/roles/_form.html.erb
@@ -1,4 +1,6 @@
-<%= simple_form_for [:admin, role] do |f| %>
+<%= simple_form_for [:admin, role],
+                    url: role.new_record? ? admin_education_program_roles_path(@program)
+                                          : admin_education_program_role_path(role, { program_id: @program.id }) do |f| %>
   <div class="row">
     <div class="col-md-8">
       <div class="card flex-fill w-100">
@@ -6,11 +8,31 @@
           <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
         </div>
         <div class="card-body">
-          <%= f.input :title %>
+          <%= f.input :description %>
+        </div>
+      </div>
+    </div>
+    <div class="col-md-4">
+      <div class="card flex-fill w-100">
+        <div class="card-header">
+          <h5 class="card-title mb-0"><%= University::Person.model_name.human(count: 2) %></h5>
+        </div>
+        <div class="card-body">
+          <%= link_to_add_association t('add'), f, :involvements, class: "btn btn-primary mb-3", data: {
+                'association-insertion-method': 'append',
+                'association-insertion-node':   '#involvements'
+              } %>
+
+          <div class="mb-3" id="involvements" data-sortable="inputs">
+            <%= f.simple_fields_for :involvements, role.involvements.sort_by(&:position), include_id: false do |involvement_f| %>
+              <%= render 'admin/education/program/roles/involvement_fields', f: involvement_f, include_id: true %>
+            <% end %>
+          </div>
         </div>
       </div>
     </div>
   </div>
+
   <% content_for :action_bar_right do %>
     <%= submit f %>
   <% end %>
diff --git a/app/views/admin/education/program/roles/_involvement_fields.html.erb b/app/views/admin/education/program/roles/_involvement_fields.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..505d7ca9b103695623cc39c52ffe75af6058a0f1
--- /dev/null
+++ b/app/views/admin/education/program/roles/_involvement_fields.html.erb
@@ -0,0 +1,18 @@
+<% include_id ||= false %>
+<div class="card nested-fields mb-2">
+  <div class="card-body">
+    <div class="row align-items-center">
+      <div class="col-1">
+        <i class="fa fa-bars handle"></i>
+      </div>
+      <div class="col-9">
+        <%= f.association :person, collection: @people, label: false, include_blank: :translate, wrapper: false, required: true %>
+      </div>
+      <div class="col-2">
+        <%= link_to_remove_association '<i class="fas fa-trash"></i>'.html_safe, f, class: 'btn btn-sm btn-danger' %>
+      </div>
+    </div>
+  </div>
+  <%= f.hidden_field :position, data: { 'sortable-input': '' } %>
+  <%= f.hidden_field :id if include_id %>
+</div>
diff --git a/app/views/admin/education/program/roles/_list.html.erb b/app/views/admin/education/program/roles/_list.html.erb
index c4465b6573d625c4e5fee70a81ee2240a26b8f47..6362fc22b67d75f730ad6e9b29d8cce234696bef 100644
--- a/app/views/admin/education/program/roles/_list.html.erb
+++ b/app/views/admin/education/program/roles/_list.html.erb
@@ -1,21 +1,27 @@
 <% if roles.any? %>
-  <table class="table table-sortable">
+  <table class="table">
     <thead>
       <tr>
-        <th class="ps-0"><%= Education::Program::Role.model_name.human %></th>
-        <th><%= Education::Program::Role.human_attribute_name('people') %></th>
+        <% if can? :reorder, University::Role %>
+          <th width="20" class="ps-0">&nbsp;</th>
+        <% end %>
+        <th class="ps-0"><%= University::Role.model_name.human %></th>
+        <th><%= University::Role.human_attribute_name('people') %></th>
         <th></th>
       </tr>
     </thead>
-    <tbody data-reorder-url="<%= reorder_admin_education_program_roles_path(program_id: @program.id) %>">
+    <tbody data-sortable data-sort-url="<%= reorder_admin_education_program_roles_path(program_id: @program.id) %>">
       <% roles.each do |role| %>
-        <tr class="handle" data-id="<%= role.id %>">
+        <tr data-id="<%= role.id %>">
+          <% if can? :reorder, University::Role %>
+            <td><i class="fa fa-bars handle"></i></td>
+          <% end %>
           <td class="ps-0">
             <%= link_to_if  can?(:read, role),
                             role,
                             admin_education_program_role_path(role, { program_id: @program.id }) %>
           </td>
-          <td><%= role.people.includes(:person).ordered.map { |person| person.person.to_s }.to_sentence %></td>
+          <td><%= role.involvements.includes(:person).ordered.map { |involvement| involvement.person.to_s }.to_sentence %></td>
           <td class="text-end pe-0">
             <div class="btn-group" role="group">
               <%= link_to t('edit'),
diff --git a/app/views/admin/education/program/roles/index.html.erb b/app/views/admin/education/program/roles/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..fa4e8a5493acf50486e09e7e5389558adbfad9bf
--- /dev/null
+++ b/app/views/admin/education/program/roles/index.html.erb
@@ -0,0 +1,6 @@
+<% content_for :title, University::Role.model_name.human(count: 2) %>
+<%= render 'admin/education/program/roles/list', roles: @roles %>
+
+<% content_for :action_bar_right do %>
+  <%= link_to t('add'), new_admin_education_program_role_path(program_id: @program.id), class: button_classes if can? :create, University::Role %>
+<% end %>
diff --git a/app/views/admin/education/program/roles/new.html.erb b/app/views/admin/education/program/roles/new.html.erb
index 1bad6fa6b7171dc693ea7eeb1652b264eca17d07..1e11d9157b4247984613930c39a66a5f5287ec23 100644
--- a/app/views/admin/education/program/roles/new.html.erb
+++ b/app/views/admin/education/program/roles/new.html.erb
@@ -1,3 +1,3 @@
-<% content_for :title, Education::Program::Role.model_name.human %>
+<% content_for :title, University::Role.model_name.human %>
 
 <%= render 'form', role: @role %>
diff --git a/app/views/admin/education/program/roles/show.html.erb b/app/views/admin/education/program/roles/show.html.erb
index 09d1438bae5ebeb50909afb7c2d79e657036ada7..07f5ed11bf9bd060aaf62e01ae4852827611b5d0 100644
--- a/app/views/admin/education/program/roles/show.html.erb
+++ b/app/views/admin/education/program/roles/show.html.erb
@@ -1,32 +1,35 @@
 <% content_for :title, @role %>
 
-<div class="row">
-  <div class="col-md-4">
-    <div class="card flex-fill w-100">
-      <div class="card-header">
-        <h2 class="card-title mb-0 h5"><%= t('admin.infos') %></h2>
-      </div>
-      <div class="card-body">
-        <h3 class="h5"><%= Education::Program::Role.human_attribute_name('title') %></h3>
-        <%= @role.title %>
-      </div>
-    </div>
-  </div>
-  <div class="col-md-8">
-    <div class="card flex-fill w-100">
-      <div class="card-header">
-        <h2 class="card-title mb-0 h5"><%= Education::Program::Role.human_attribute_name('people') %></h2>
-      </div>
-      <div class="card-body">
-        <%= render 'admin/education/program/role/people/list', people: @role.people.includes(:person).ordered %>
-        <% if can? :create, Education::Program::Role::Person %>
-          <p><%= link_to t('create'), new_admin_education_program_role_person_path(role_id: @role.id), class: 'btn btn-primary' %></p>
+<% if @involvements.any? %>
+  <table class="table">
+    <thead>
+      <tr>
+        <% if can? :reorder, University::Role %>
+          <th width="20" class="ps-0">&nbsp;</th>
         <% end %>
-      </div>
-    </div>
-  </div>
-</div>
+        <th class="ps-0"><%= University::Person.model_name.human %></th>
+      </tr>
+    </thead>
+    <tbody data-sortable data-sort-url="<%= reorder_admin_education_program_role_people_path(@role, { program_id: @program.id }) %>">
+      <% @involvements.each do |involvement| %>
+        <tr data-id="<%= involvement.id %>">
+          <% if can? :reorder, University::Role %>
+            <td><i class="fa fa-bars handle"></i></td>
+          <% end %>
+          <td class="ps-0">
+            <%= link_to_if  can?(:read, involvement.person),
+                            involvement.person,
+                            [:admin, involvement.person] %>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
+
 
 <% content_for :action_bar_right do %>
-  <%= edit_link @role %>
+  <%= link_to t('edit'),
+              edit_admin_education_program_role_path(@role, { program_id: @program.id }),
+              class: button_classes if can?(:update, @role) %>
 <% end %>
diff --git a/app/views/admin/education/program/teachers/_form.html.erb b/app/views/admin/education/program/teachers/_form.html.erb
index 31950531cded62732c883007d24af5dd0f8a7685..b77505dfdb3e19597b8ccde8c8765dfaa5c4278a 100644
--- a/app/views/admin/education/program/teachers/_form.html.erb
+++ b/app/views/admin/education/program/teachers/_form.html.erb
@@ -1,4 +1,6 @@
-<%= simple_form_for [:admin, teacher] do |f| %>
+<%= simple_form_for [:admin, involvement],
+                    url: involvement.new_record? ? admin_education_program_teachers_path(@program)
+                                                 : admin_education_program_teacher_path(involvement, { program_id: @program.id }) do |f| %>
   <div class="card flex-fill w-100">
     <div class="card-header">
       <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
@@ -6,10 +8,10 @@
     <div class="card-body">
       <div class="row">
         <div class="col-md-6">
-          <%= f.association :person, collection: @teachers %>
+          <%= f.association :person, collection: @available_people %>
         </div>
         <div class="col-md-6">
-          <%= f.input :description, as: :string %>
+          <%= f.input :description %>
         </div>
       </div>
     </div>
diff --git a/app/views/admin/education/program/teachers/_list.html.erb b/app/views/admin/education/program/teachers/_list.html.erb
index cc7d9c17b4e0f0bd685a4fc0525771250f30610f..2d7acf1dcdb15f68b4a35541726a5d16986d75d8 100644
--- a/app/views/admin/education/program/teachers/_list.html.erb
+++ b/app/views/admin/education/program/teachers/_list.html.erb
@@ -1,31 +1,29 @@
-<% if teachers.any? %>
+<% if involvements.any? %>
   <table class="table">
     <thead>
       <tr>
-        <th class="ps-0"><%= Education::Program::Teacher.model_name.human %></th>
-        <th><%= Education::Program::Teacher.human_attribute_name('description') %></th>
+        <th class="ps-0"><%= University::Person.model_name.human %></th>
+        <th><%= University::Person::Involvement.human_attribute_name('description') %></th>
         <th></th>
       </tr>
     </thead>
     <tbody>
-      <% teachers.each do |teacher| %>
+      <% involvements.each do |involvement| %>
         <tr>
           <td class="ps-0">
-            <%= link_to_if  can?(:read, teacher.person),
-                            teacher.person,
-                            admin_education_teacher_path(teacher.person) %>
+            <%= link_to_if can?(:read, involvement.person), involvement.person.to_s, admin_university_person_path(involvement.person) %>
           </td>
-          <td><%= teacher.description %></td>
+          <td><%= involvement.description %></td>
           <td class="text-end pe-0">
             <div class="btn-group" role="group">
               <%= link_to t('edit'),
-                          edit_admin_education_program_teacher_path(teacher, { program_id: @program.id }),
-                          class: button_classes if can? :update, teacher.person %>
-              <%= link_to t('delete'),
-                          admin_education_program_teacher_path(teacher, { program_id: @program.id }),
+                          edit_admin_education_program_teacher_path(involvement, { program_id: @program.id }),
+                          class: button_classes if can?(:edit, involvement) %>
+              <%= link_to t('remove'),
+                          admin_education_program_teacher_path(involvement, { program_id: @program.id }),
                           method: :delete,
                           data: { confirm: t('please_confirm') },
-                          class: button_classes_danger if can? :update, teacher.person %>
+                          class: button_classes_danger if can?(:destroy, involvement) %>
             </div>
           </td>
         </tr>
diff --git a/app/views/admin/education/program/teachers/edit.html.erb b/app/views/admin/education/program/teachers/edit.html.erb
index b3f94ca300bb5361d5c000f96b377bd2bd76eb0e..da924cd052e14cfa55c1f75b9fe56ee8d15a1b8a 100644
--- a/app/views/admin/education/program/teachers/edit.html.erb
+++ b/app/views/admin/education/program/teachers/edit.html.erb
@@ -1,3 +1,3 @@
-<% content_for :title, @teacher %>
+<% content_for :title, @involvement %>
 
-<%= render 'form', teacher: @teacher %>
+<%= render 'form', involvement: @involvement %>
diff --git a/app/views/admin/education/program/teachers/index.html.erb b/app/views/admin/education/program/teachers/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f37281009002c9944136ec4e9d76b43ee96b9d5c
--- /dev/null
+++ b/app/views/admin/education/program/teachers/index.html.erb
@@ -0,0 +1,6 @@
+<% content_for :title, Education::Program.human_attribute_name("teachers") %>
+<%= render 'admin/education/program/teachers/list', involvements: @involvements %>
+
+<% content_for :action_bar_right do %>
+  <%= link_to t('add'), new_admin_education_program_teacher_path(program_id: @program.id), class: button_classes %>
+<% end %>
diff --git a/app/views/admin/education/program/teachers/new.html.erb b/app/views/admin/education/program/teachers/new.html.erb
index 1617fb343abe20477a921b1b7488d6ffaf546fa9..f07ff39c18ffb7d9e598688bb41a9361413d804f 100644
--- a/app/views/admin/education/program/teachers/new.html.erb
+++ b/app/views/admin/education/program/teachers/new.html.erb
@@ -1,3 +1,3 @@
-<% content_for :title, Education::Program::Teacher.model_name.human %>
+<% content_for :title, University::Person.model_name.human %>
 
-<%= render 'form', teacher: @teacher %>
+<%= render 'form', involvement: @involvement %>
diff --git a/app/views/admin/education/programs/_form.html.erb b/app/views/admin/education/programs/_form.html.erb
index f930ef9bfe4761f01d51c994ae8cd99c8d29ea66..461c20397edabcdfe9e4465e00516fa02e001774 100644
--- a/app/views/admin/education/programs/_form.html.erb
+++ b/app/views/admin/education/programs/_form.html.erb
@@ -1,4 +1,4 @@
-<%= simple_nested_form_for [:admin, program] do |f| %>
+<%= simple_form_for [:admin, program] do |f| %>
   <div class="row">
     <div class="col-md-3">
       <div class="card flex-fill w-100">
@@ -59,13 +59,33 @@
            <%= f.input :featured_image_alt %>
         </div>
       </div>
-      <div class="card flex-fill w-100">
-        <div class="card-header">
-          <h5 class="card-title mb-0"><%= t('activerecord.attributes.education/program.team') %></h5>
-        </div>
-        <div class="card-body">
-          <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :contacts %>
-        </div>
+    </div>
+  </div>
+  <div class="card flex-fill w-100">
+    <div class="card-header">
+      <h5 class="card-title mb-0"><%= t('activerecord.attributes.education/program.team') %></h5>
+    </div>
+    <div class="card-body">
+      <%= render 'admin/education/programs/forms/input_with_inheritance', f: f, property: :contacts %>
+
+      <p><%= Education::Program.human_attribute_name('teachers') %></p>
+      <%= link_to_add_association t('add'), f, :university_person_involvements,
+                                  class: "btn btn-primary mb-3",
+                                  partial: 'admin/education/programs/involvement_fields',
+                                  data: {
+                                    'association-insertion-method': 'append',
+                                    'association-insertion-node':   '#involvements',
+                                  } %>
+
+      <div class="row mb-3" id="involvements">
+        <%
+        sorted_involvements = program.university_person_involvements.sort_by { |involvement|
+          [involvement.person&.last_name, involvement.person&.first_name]
+        }
+        %>
+        <%= f.simple_fields_for :university_person_involvements, sorted_involvements, include_id: false do |involvement_f| %>
+          <%= render 'admin/education/programs/involvement_fields', f: involvement_f, include_id: true %>
+        <% end %>
       </div>
     </div>
   </div>
diff --git a/app/views/admin/education/programs/_involvement_fields.html.erb b/app/views/admin/education/programs/_involvement_fields.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..08a3e0acc0b5d3db6f4edf28e1e7b9d3c8473036
--- /dev/null
+++ b/app/views/admin/education/programs/_involvement_fields.html.erb
@@ -0,0 +1,17 @@
+<% include_id ||= false %>
+<div class="nested-fields col-md-3">
+  <div class="card mb-2">
+    <div class="card-body">
+      <div class="row align-items-center">
+        <div class="col-10">
+          <%= f.association :person, collection: @teacher_people, label: false, include_blank: :translate, required: true %>
+          <%= f.input :description, label: false, placeholder: University::Person::Involvement.human_attribute_name('description'), wrapper: false %>
+        </div>
+        <div class="col-2">
+          <%= link_to_remove_association '<i class="fas fa-trash"></i>'.html_safe, f, class: 'btn btn-sm btn-danger' %>
+        </div>
+      </div>
+    </div>
+    <%= f.hidden_field :id if include_id %>
+  </div>
+</div>
diff --git a/app/views/admin/education/programs/show.html.erb b/app/views/admin/education/programs/show.html.erb
index 4ebd03b90caa3aa1586b5ea5b0ac6d798ec67e58..d6ef2d1ab04c6a560963ca2bb57040cf0e72bb42 100644
--- a/app/views/admin/education/programs/show.html.erb
+++ b/app/views/admin/education/programs/show.html.erb
@@ -121,14 +121,10 @@
   <div class="card-body">
     <div class="row">
       <div class="col-md-6">
-        <h3 class="h5"><%= Education::Program.human_attribute_name('roles') %></h3>
-        <%= render 'admin/education/program/roles/list', roles: @program.roles.ordered %>
-        <%= link_to t('create'), new_admin_education_program_role_path(program_id: @program.id), class: button_classes if can?(:create, Education::Program::Role) %>
+        <%= render 'admin/education/programs/show/roles', roles: @roles %>
       </div>
       <div class="col-md-6">
-        <h3 class="h5"><%= Education::Program.human_attribute_name('teachers') %></h3>
-        <%= render 'admin/education/program/teachers/list', teachers: @program.teachers.includes(:person).ordered %>
-        <%= link_to t('create'), new_admin_education_program_teacher_path(program_id: @program.id), class: button_classes %>
+        <%= render 'admin/education/programs/show/teachers', involvements: @teacher_involvements %>
       </div>
     </div>
   </div>
diff --git a/app/views/admin/education/programs/show/_roles.html.erb b/app/views/admin/education/programs/show/_roles.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..22c9ea57aca1e14b34a744456828eaba189b1861
--- /dev/null
+++ b/app/views/admin/education/programs/show/_roles.html.erb
@@ -0,0 +1,28 @@
+<div class="float-end">
+  <%= link_to t('education.manage_roles'),
+              admin_education_program_roles_path(program_id: @program.id),
+              class: button_classes if can?(:update, University::Role) %>
+</div>
+<h3 class="h5"><%= Education::Program.human_attribute_name('roles') %></h3>
+<% if @roles.any? %>
+  <table class="table">
+    <thead>
+      <tr>
+        <th class="ps-0"><%= University::Role.model_name.human %></th>
+        <th><%= University::Role.human_attribute_name('people') %></th>
+      </tr>
+    </thead>
+    <tbody>
+      <% @roles.each do |role| %>
+        <tr>
+          <td class="ps-0">
+            <%= link_to_if  can?(:read, role),
+                            role,
+                            admin_education_program_role_path(role, { program_id: @program.id }) %>
+          </td>
+          <td><%= role.involvements.includes(:person).ordered.map { |involvement| link_to_if can?(:read, involvement.person), involvement.person.to_s, admin_university_person_path(involvement.person) }.to_sentence.html_safe %></td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
diff --git a/app/views/admin/education/programs/show/_teachers.html.erb b/app/views/admin/education/programs/show/_teachers.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f32470ae7d2ca53fc6b160892f5e2ecad922f548
--- /dev/null
+++ b/app/views/admin/education/programs/show/_teachers.html.erb
@@ -0,0 +1,26 @@
+<div class="float-end">
+  <%= link_to t('education.manage_teachers'),
+              admin_education_program_teachers_path(program_id: @program.id),
+              class: button_classes if can?(:update, University::Person::Involvement) %>
+</div>
+<h3 class="h5"><%= Education::Program.human_attribute_name('teachers') %></h3>
+<% if @teacher_involvements.any? %>
+  <table class="table">
+    <thead>
+      <tr>
+        <th class="ps-0"><%= University::Person.model_name.human %></th>
+        <th><%= University::Person::Involvement.human_attribute_name('description') %></th>
+      </tr>
+    </thead>
+    <tbody>
+      <% @teacher_involvements.each do |involvement| %>
+        <tr>
+          <td class="ps-0">
+            <%= link_to_if can?(:read, involvement.person), involvement.person.to_s, admin_university_person_path(involvement.person) %>
+          </td>
+          <td><%= involvement.description %></td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
diff --git a/app/views/admin/education/programs/static.html.erb b/app/views/admin/education/programs/static.html.erb
index 505c7a206bfa7aefb738391ca926a37e7660a1e7..4da9b86d49106a152435ff2b7316e0e022a3d806 100644
--- a/app/views/admin/education/programs/static.html.erb
+++ b/app/views/admin/education/programs/static.html.erb
@@ -1,4 +1,5 @@
 ---
+<% teacher_involvements = @program.university_person_involvements.includes(:person).ordered_by_name %>
 title: >
   <%= @program.name %>
 url: /<%= @website.static_pathname_programs %><%= @program.path %>/
@@ -11,23 +12,27 @@ image_alt: "<%= @program.featured_image_alt %>"
 <% end %>
 category: "<%= @website.categories.find_by(program_id: @program.id)&.path %>/"
 teachers:
-<% @program.teachers.includes(:person).ordered.each do |teacher| %>
-  - "<%= teacher.person.slug %>"
+<% teacher_involvements.each do |involvement| %>
+  - "<%= involvement.person.slug %>"
 <% end %>
 teachers_description:
-<% @program.teachers.includes(:person).ordered.each do |teacher| %>
-  "<%= teacher.person.slug %>": >
-    <%= teacher.best_description %>
+<% teacher_involvements.each do |involvement| %>
+  "<%= involvement.person.slug %>": >
+    <%= involvement.description %>
 <% end %>
+<% if @program.university_roles.any? %>
 roles:
-<% @program.roles.ordered.each do |role| %>
+<% @program.university_roles.ordered.each do |role| %>
   - title: >
       <%= role.to_s %>
     persons:
-    <% role.people.includes(:person).ordered.each do |role_person| %>
-      - "<%= role_person.person.slug %>"
+    <% role.involvements.includes(:person).ordered.each do |involvement| %>
+      - "<%= involvement.person.slug %>"
     <% end %>
 <% end %>
+<% else %>
+roles: []
+<% end %>
 continuing: <%= @program.continuing %>
 level: <%= @program.level %>
 ects: <%= @program.ects %>
diff --git a/app/views/admin/education/school/administrators/_form.html.erb b/app/views/admin/education/school/administrators/_form.html.erb
deleted file mode 100644
index 42c01254a796ec1fad06fe563dd7ed2a07adac26..0000000000000000000000000000000000000000
--- a/app/views/admin/education/school/administrators/_form.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-<%= simple_form_for [:admin, administrator] do |f| %>
-  <div class="card flex-fill w-100">
-    <div class="card-header">
-      <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
-    </div>
-    <div class="card-body">
-      <div class="row">
-        <div class="col-md-6">
-          <% used_administrator_ids = @school.administrators.where.not(id: administrator.id).pluck(:person_id) %>
-          <%= f.association :person, collection: current_university.people.administration.where.not(id: used_administrator_ids).ordered %>
-        </div>
-        <div class="col-md-6">
-          <%= f.input :description, as: :string %>
-        </div>
-      </div>
-    </div>
-  </div>
-  <% content_for :action_bar_right do %>
-    <%= submit f %>
-  <% end %>
-<% end %>
diff --git a/app/views/admin/education/school/administrators/_list.html.erb b/app/views/admin/education/school/administrators/_list.html.erb
deleted file mode 100644
index 569c595ec8484f23a561d1d16fb4ac9aea34466b..0000000000000000000000000000000000000000
--- a/app/views/admin/education/school/administrators/_list.html.erb
+++ /dev/null
@@ -1,35 +0,0 @@
-<% if administrators.any? %>
-  <table class="table">
-    <thead>
-      <tr>
-        <th class="ps-0"><%= Education::School::Administrator.model_name.human %></th>
-        <th><%= Education::School::Administrator.human_attribute_name('description') %></th>
-        <th></th>
-      </tr>
-    </thead>
-    <tbody>
-      <% administrators.each do |administrator| %>
-        <tr>
-          <td class="ps-0">
-            <%= link_to_if  can?(:read, administrator.person),
-                            administrator.person,
-                            admin_university_person_path(administrator.person) %>
-          </td>
-          <td><%= administrator.description %></td>
-          <td class="text-end pe-0">
-            <div class="btn-group" role="group">
-              <%= link_to t('edit'),
-                          edit_admin_education_school_administrator_path(administrator, { school_id: @school.id }),
-                          class: button_classes %>
-              <%= link_to t('delete'),
-                          admin_education_school_administrator_path(administrator, { school_id: @school.id }),
-                          method: :delete,
-                          data: { confirm: t('please_confirm') },
-                          class: button_classes_danger %>
-            </div>
-          </td>
-        </tr>
-      <% end %>
-    </tbody>
-  </table>
-<% end %>
diff --git a/app/views/admin/education/school/administrators/edit.html.erb b/app/views/admin/education/school/administrators/edit.html.erb
deleted file mode 100644
index d2a82994691505b7789cdf1252c47e02b635ad7c..0000000000000000000000000000000000000000
--- a/app/views/admin/education/school/administrators/edit.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<% content_for :title, @administrator %>
-
-<%= render 'form', administrator: @administrator %>
diff --git a/app/views/admin/education/school/administrators/new.html.erb b/app/views/admin/education/school/administrators/new.html.erb
deleted file mode 100644
index 491f71583cc0b5c2f5be4abc2e892f093ad25d9e..0000000000000000000000000000000000000000
--- a/app/views/admin/education/school/administrators/new.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<% content_for :title, Education::School::Administrator.model_name.human %>
-
-<%= render 'form', administrator: @administrator %>
diff --git a/app/views/admin/education/school/roles/_form.html.erb b/app/views/admin/education/school/roles/_form.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c5ef4a1c65b23b012228150fb597516195c798c5
--- /dev/null
+++ b/app/views/admin/education/school/roles/_form.html.erb
@@ -0,0 +1,40 @@
+<%= simple_form_for [:admin, role],
+                    url: role.new_record? ? admin_education_school_roles_path(@school)
+                                          : admin_education_school_role_path(role, { school_id: @school.id }) do |f| %>
+
+  <div class="row">
+    <div class="col-md-8">
+      <div class="card flex-fill w-100">
+        <div class="card-header">
+          <h5 class="card-title mb-0"><%= t('admin.infos') %></h5>
+        </div>
+        <div class="card-body">
+          <%= f.input :description %>
+        </div>
+      </div>
+    </div>
+    <div class="col-md-4">
+      <div class="card flex-fill w-100">
+        <div class="card-header">
+          <h5 class="card-title mb-0"><%= University::Person.model_name.human(count: 2) %></h5>
+        </div>
+        <div class="card-body">
+          <%= link_to_add_association t('add'), f, :involvements, class: "btn btn-primary mb-3", data: {
+                'association-insertion-method': 'append',
+                'association-insertion-node':   '#involvements'
+              } %>
+
+          <div class="mb-3" id="involvements" data-sortable="inputs">
+            <%= f.simple_fields_for :involvements, role.involvements.sort_by(&:position), include_id: false do |involvement_f| %>
+              <%= render 'admin/education/school/roles/involvement_fields', f: involvement_f, include_id: true %>
+            <% end %>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/education/school/roles/_involvement_fields.html.erb b/app/views/admin/education/school/roles/_involvement_fields.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..0ce4a6118749b46cf3abc74eab502d958da82191
--- /dev/null
+++ b/app/views/admin/education/school/roles/_involvement_fields.html.erb
@@ -0,0 +1,18 @@
+<% include_id ||= false %>
+<div class="card nested-fields mb-2">
+  <div class="card-body">
+    <div class="row align-items-center">
+      <div class="col-1">
+        <i class="fa fa-bars handle"></i>
+      </div>
+      <div class="col-9">
+        <%= f.association :person, collection: @administration_people, label: false, include_blank: :translate, wrapper: false, required: true %>
+      </div>
+      <div class="col-2">
+        <%= link_to_remove_association '<i class="fas fa-trash"></i>'.html_safe, f, class: 'btn btn-sm btn-danger' %>
+      </div>
+    </div>
+  </div>
+  <%= f.hidden_field :position, data: { 'sortable-input': '' } %>
+  <%= f.hidden_field :id if include_id %>
+</div>
diff --git a/app/views/admin/education/school/roles/_list.html.erb b/app/views/admin/education/school/roles/_list.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..5d694d93e0a80f5a2afa8c1e21fa0088ea619f47
--- /dev/null
+++ b/app/views/admin/education/school/roles/_list.html.erb
@@ -0,0 +1,41 @@
+<% if roles.any? %>
+  <table class="table">
+    <thead>
+      <tr>
+        <% if can? :reorder, University::Role %>
+          <th width="20" class="ps-0">&nbsp;</th>
+        <% end %>
+        <th class="ps-0"><%= University::Role.model_name.human %></th>
+        <th><%= University::Role.human_attribute_name('people') %></th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody data-sortable data-sort-url="<%= reorder_admin_education_school_roles_path(school_id: @school.id) %>">
+      <% roles.each do |role| %>
+        <tr data-id="<%= role.id %>">
+          <% if can? :reorder, University::Role %>
+            <td><i class="fa fa-bars handle"></i></td>
+          <% end %>
+          <td class="ps-0">
+            <%= link_to_if  can?(:read, role),
+                            role,
+                            admin_education_school_role_path(role, { school_id: @school.id }) %>
+          </td>
+          <td><%= role.involvements.includes(:person).ordered.map { |involvement| involvement.person.to_s }.to_sentence %></td>
+          <td class="text-end pe-0">
+            <div class="btn-group" role="group">
+              <%= link_to t('edit'),
+                          edit_admin_education_school_role_path(role, { school_id: @school.id }),
+                          class: button_classes if can?(:edit, role) %>
+              <%= link_to t('delete'),
+                          admin_education_school_role_path(role, { school_id: @school.id }),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger if can?(:destroy, role) %>
+            </div>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
diff --git a/app/views/admin/education/school/roles/edit.html.erb b/app/views/admin/education/school/roles/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..ff1ec73bd6f1465a96497606080d481b6fd6ca51
--- /dev/null
+++ b/app/views/admin/education/school/roles/edit.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, @role %>
+
+<%= render 'form', role: @role %>
diff --git a/app/views/admin/education/school/roles/index.html.erb b/app/views/admin/education/school/roles/index.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..169795ed215ab55d5484cbc72e0affb3662fd532
--- /dev/null
+++ b/app/views/admin/education/school/roles/index.html.erb
@@ -0,0 +1,6 @@
+<% content_for :title, Education::School.human_attribute_name('roles') %>
+<%= render 'admin/education/school/roles/list', roles: @roles %>
+
+<% content_for :action_bar_right do %>
+  <%= link_to t('add'), new_admin_education_school_role_path(school_id: @school.id), class: button_classes if can? :create, University::Role %>
+<% end %>
diff --git a/app/views/admin/education/school/roles/new.html.erb b/app/views/admin/education/school/roles/new.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..1e11d9157b4247984613930c39a66a5f5287ec23
--- /dev/null
+++ b/app/views/admin/education/school/roles/new.html.erb
@@ -0,0 +1,3 @@
+<% content_for :title, University::Role.model_name.human %>
+
+<%= render 'form', role: @role %>
diff --git a/app/views/admin/education/school/roles/show.html.erb b/app/views/admin/education/school/roles/show.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..05b170d15700dd54ece0ef3813a9a97763a0b3d3
--- /dev/null
+++ b/app/views/admin/education/school/roles/show.html.erb
@@ -0,0 +1,35 @@
+<% content_for :title, @role %>
+
+<% if @involvements.any? %>
+  <table class="table">
+    <thead>
+      <tr>
+        <% if can? :reorder, University::Person::Involvement %>
+          <th width="20" class="ps-0">&nbsp;</th>
+        <% end %>
+        <th class="ps-0"><%= University::Person.model_name.human %></th>
+      </tr>
+    </thead>
+    <tbody data-sortable data-sort-url="<%= reorder_admin_education_school_role_people_path(@role, { school_id: @school.id }) %>">
+      <% @involvements.each do |involvement| %>
+        <tr data-id="<%= involvement.id %>">
+          <% if can? :reorder, University::Person::Involvement %>
+            <td><i class="fa fa-bars handle"></i></td>
+          <% end %>
+          <td class="ps-0">
+            <%= link_to_if  can?(:read, involvement.person),
+                            involvement.person,
+                            [:admin, involvement.person] %>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
+  </table>
+<% end %>
+
+
+<% content_for :action_bar_right do %>
+  <%= link_to t('edit'),
+              edit_admin_education_school_role_path(@role, { school_id: @school.id }),
+              class: button_classes if can?(:update, @role) %>
+<% end %>
diff --git a/app/views/admin/education/schools/show.html.erb b/app/views/admin/education/schools/show.html.erb
index 5367e4558ab1221f8c894ae179fcc1a437f85a94..2bd93a35f76d5ae1e23836e4b513b10860d460f5 100644
--- a/app/views/admin/education/schools/show.html.erb
+++ b/app/views/admin/education/schools/show.html.erb
@@ -56,15 +56,7 @@
 
 </div>
 
-<div class="card flex-fill w-100">
-  <div class="card-header">
-    <h2 class="card-title mb-0 h5"><%= Education::School.human_attribute_name('administrators') %></h2>
-  </div>
-  <div class="card-body">
-    <%= render 'admin/education/school/administrators/list', administrators: @school.administrators.includes(:person).ordered %>
-    <%= link_to t('create'), new_admin_education_school_administrator_path(school_id: @school.id), class: button_classes %>
-  </div>
-</div>
+<%= render 'admin/education/schools/show/roles', roles: @roles %>
 
 <% content_for :action_bar_right do %>
   <%= edit_link @school %>
diff --git a/app/views/admin/education/schools/show/_roles.html.erb b/app/views/admin/education/schools/show/_roles.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..c76b6046d3d5ef9a71cbbf3b58749e0dd0640aa5
--- /dev/null
+++ b/app/views/admin/education/schools/show/_roles.html.erb
@@ -0,0 +1,40 @@
+<div class="card flex-fill w-100">
+  <div class="card-header">
+    <div class="float-end">
+      <%= link_to t('education.schools.manage_roles'),
+                  admin_education_school_roles_path(school_id: @school.id),
+                  class: button_classes if can?(:update, University::Role) %>
+    </div>
+    <h2 class="card-title mb-0 h5"><%= Education::School.human_attribute_name('roles') %></h2>
+  </div>
+  <div class="card-body">
+    <% if @roles.any? %>
+      <table class="table">
+        <thead>
+          <tr>
+            <th class="ps-0"><%= University::Role.model_name.human %></th>
+            <th><%= University::Role.human_attribute_name('people') %></th>
+          </tr>
+        </thead>
+        <tbody>
+          <% roles.each do |role| %>
+            <tr>
+              <td class="ps-0">
+                <%= link_to_if  can?(:read, role),
+                                role,
+                                edit_admin_education_school_role_path(role, { school_id: @school.id }) %>
+              </td>
+              <td>
+                <ul>
+                  <% role.involvements.includes(:person).ordered.each do |involvement| %>
+                    <li><%= link_to_if can?(:read, involvement.person), involvement.person.to_s, admin_university_person_path(involvement.person) %></li>
+                  <% end %>
+                </ul>
+              </td>
+            </tr>
+          <% end %>
+        </tbody>
+      </table>
+    <% end %>
+  </div>
+</div>
diff --git a/app/views/admin/education/schools/static.html.erb b/app/views/admin/education/schools/static.html.erb
index 800d8c5b1c974e925f1adff080b1b4a7a612fbbe..a0afec14d317ea794c1c1de5ea7e94dd25aa5d04 100644
--- a/app/views/admin/education/schools/static.html.erb
+++ b/app/views/admin/education/schools/static.html.erb
@@ -11,13 +11,17 @@ country: >
   <%= ISO3166::Country[@school.country].translations[@school.country.downcase] %>
 phone: >
   <%= @school.phone %>
-administrators:
-<% @school.administrators.includes(:person).ordered.each do |administrator| %>
-  - "<%= administrator.person.slug %>"
+<% if @school.university_roles.any? %>
+roles:
+<% @school.university_roles.ordered.each do |role| %>
+  - title: >
+      <%= role.to_s %>
+    persons:
+    <% role.involvements.includes(:person).ordered.each do |involvement| %>
+      - "<%= involvement.person.slug %>"
+    <% end %>
 <% end %>
-administrators_description:
-<% @school.administrators.includes(:person).ordered.each do |administrator| %>
-  "<%= administrator.person.slug %>": >
-    <%= administrator.best_description %>
+<% else %>
+roles: []
 <% end %>
 ---
diff --git a/app/views/admin/education/teachers/_involvement_fields.html.erb b/app/views/admin/education/teachers/_involvement_fields.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..b53edeec8ee8d608a5150c0934b3688ef3eba77a
--- /dev/null
+++ b/app/views/admin/education/teachers/_involvement_fields.html.erb
@@ -0,0 +1,24 @@
+<% include_id ||= false %>
+<div class="nested-fields col-md-4">
+  <div class="card mb-2">
+    <div class="card-body">
+      <div class="row align-items-center">
+        <div class="col-10">
+          <%= f.hidden_field :target_type, value: "Education::Program" %>
+          <%= f.association :target,
+                            collection: collection_tree(current_university.education_programs),
+                            label_method: ->(p) { sanitize p[:label] },
+                            value_method: ->(p) { p[:id] },
+                            label: false,
+                            include_blank: t('simple_form.include_blanks.defaults.program'),
+                            required: true %>
+          <%= f.input :description, label: false, placeholder: University::Person::Involvement.human_attribute_name('description'), wrapper: false %>
+        </div>
+        <div class="col-2">
+          <%= link_to_remove_association '<i class="fas fa-trash"></i>'.html_safe, f, class: 'btn btn-sm btn-danger' %>
+        </div>
+      </div>
+    </div>
+    <%= f.hidden_field :id if include_id %>
+  </div>
+</div>
diff --git a/app/views/admin/education/teachers/edit.html.erb b/app/views/admin/education/teachers/edit.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..769b9ea4c9bf132a798d30c94e4793a829f28f67
--- /dev/null
+++ b/app/views/admin/education/teachers/edit.html.erb
@@ -0,0 +1,28 @@
+<% content_for :title, @teacher %>
+
+<%= simple_form_for [:admin, @teacher], url: admin_education_teacher_path(@teacher) do |f| %>
+  <div class="card flex-fill w-100">
+    <div class="card-header">
+      <h5 class="card-title mb-0"><%= Education::Program.model_name.human(count: 2) %></h5>
+    </div>
+    <div class="card-body">
+      <%= link_to_add_association t('add'), f, :involvements,
+                                  class: "btn btn-primary mb-3",
+                                  data: {
+                                    'association-insertion-method': 'append',
+                                    'association-insertion-node':   '#involvements',
+                                  } %>
+
+      <div class="row mb-3" id="involvements">
+        <% sorted_teacher_involvements = @teacher.involvements.select(&:teacher?).sort_by { |involvement| involvement.created_at || Time.zone.now } %>
+        <%= f.simple_fields_for :involvements, sorted_teacher_involvements, include_id: false do |involvement_f| %>
+          <%= render 'admin/education/teachers/involvement_fields', f: involvement_f, include_id: true %>
+        <% end %>
+      </div>
+    </div>
+  </div>
+
+  <% content_for :action_bar_right do %>
+    <%= submit f %>
+  <% end %>
+<% end %>
diff --git a/app/views/admin/education/teachers/show.html.erb b/app/views/admin/education/teachers/show.html.erb
index cfb4ef8e703ce264403458eb27c93628101da575..6430d8c3dc9350b6fb602c64a68e51ab653fc321 100644
--- a/app/views/admin/education/teachers/show.html.erb
+++ b/app/views/admin/education/teachers/show.html.erb
@@ -2,21 +2,47 @@
 
 <%= render 'admin/university/people/main_infos', person: @teacher %>
 
-
-<% if @programs.total_count > 0 %>
+<% if @involvements.total_count > 0 %>
   <div class="card">
     <div class="card-header">
-      <h2 class="card-title mb-0 h5"><%= "#{Education::Program.model_name.human(count: 2)} (#{@programs.total_count})" %></h2>
+      <h2 class="card-title mb-0 h5"><%= "#{Education::Program.model_name.human(count: 2)} (#{@involvements.total_count})" %></h2>
     </div>
-    <%= render 'admin/education/programs/list', programs: @programs %>
-    <% if @programs.total_pages > 1 %>
+    <table class="table">
+      <thead>
+        <tr>
+          <th><%= Education::Program.model_name.human %></th>
+          <th><%= Education::Program.human_attribute_name('level') %></th>
+          <th><%= University::Person::Involvement.human_attribute_name('description') %></th>
+          <th></th>
+        </tr>
+      </thead>
+      <tbody>
+        <% @involvements.each do |involvement| %>
+          <% program = involvement.target %>
+          <tr>
+            <td><%= link_to_if can?(:read, program), program, [:admin, program] %></td>
+            <td><%= program.level_i18n %></td>
+            <td><%= involvement.description %></td>
+            <td class="text-end">
+              <%= link_to t('quit'),
+                          admin_education_program_teacher_path(involvement, { program_id: program.id }),
+                          method: :delete,
+                          data: { confirm: t('please_confirm') },
+                          class: button_classes_danger if can?(:destroy, involvement) %>
+            </td>
+          </tr>
+        <% end %>
+      </tbody>
+    </table>
+
+    <% if @involvements.total_pages > 1 %>
       <div class="card-footer">
-        <%= paginate @programs, theme: 'bootstrap-5' %>
+        <%= paginate @involvements, theme: 'bootstrap-5' %>
       </div>
     <% end %>
   </div>
 <% end %>
 
 <% content_for :action_bar_right do %>
-  <%#= link_to t('edit'), edit_admin_education_teacher_path(@teacher), class: button_classes if can?(:edit, Education::Program) %>
+  <%= link_to t('education.manage_programs'), edit_admin_education_teacher_path(@teacher), class: button_classes if can?(:manage, University::Person::Involvement) %>
 <% end %>
diff --git a/app/views/admin/research/journal/volumes/show.html.erb b/app/views/admin/research/journal/volumes/show.html.erb
index da93defb44dc656924bfa7525c335a8b5b387732..d485e48b49840e135c31b7378a6aaf68f10abe82 100644
--- a/app/views/admin/research/journal/volumes/show.html.erb
+++ b/app/views/admin/research/journal/volumes/show.html.erb
@@ -11,17 +11,23 @@
         <p><%= @volume.description %></p>
         <% if @articles.any? %>
           <h3 class="h5 mt-4"><%= Research::Journal::Volume.human_attribute_name('articles') %></h3>
-          <table class="table table-sortable">
+          <table class="table">
             <thead>
               <tr>
-                <th><%= Research::Journal::Article.model_name.human %></th>
+                <% if can? :reorder, Research::Journal::Article %>
+                  <th width="20" class="ps-0">&nbsp;</th>
+                <% end %>
+                <th class="ps-0"><%= Research::Journal::Article.model_name.human %></th>
                 <th><%= Research::Journal::Article.human_attribute_name('published_at') %></th>
                 <th></th>
               </tr>
             </thead>
-            <tbody data-reorder-url="<%= reorder_admin_research_journal_articles_path(journal_id: @journal.id) %>">
+            <tbody data-sortable data-sort-url="<%= reorder_admin_research_journal_articles_path(journal_id: @journal.id) %>">
               <% @articles.each do |article| %>
-                <tr class="handle" data-id="<%= article.id %>">
+                <tr data-id="<%= article.id %>">
+                  <% if can? :reorder, Research::Journal::Article %>
+                    <td><i class="fa fa-bars handle"></i></td>
+                  <% end %>
                   <td>
                     <%= link_to article,
                                 admin_research_journal_article_path(journal_id: article.journal, id: article),
diff --git a/app/views/admin/research/laboratory/axes/_list.html.erb b/app/views/admin/research/laboratory/axes/_list.html.erb
index baf2e27f8c1f5365eb765453cbe898a151f12ad9..f9df418ab25be326c44cc61fbb9fc7d6fd69f149 100644
--- a/app/views/admin/research/laboratory/axes/_list.html.erb
+++ b/app/views/admin/research/laboratory/axes/_list.html.erb
@@ -1,14 +1,20 @@
-<table class="table table-sortable">
+<table class="table">
   <thead>
     <tr>
+      <% if can? :reorder, Research::Laboratory::Axis %>
+        <th width="20" class="ps-0">&nbsp;</th>
+      <% end %>
       <th><%= Research::Laboratory::Axis.model_name.human %></th>
       <th><%= Research::Laboratory::Axis.human_attribute_name('short_name') %></th>
       <th></th>
     </tr>
   </thead>
-  <tbody data-reorder-url="<%= reorder_admin_research_laboratory_axes_path(laboratory_id: @laboratory.id) %>">
+  <tbody data-sortable data-sort-url="<%= reorder_admin_research_laboratory_axes_path(laboratory_id: @laboratory.id) %>">
     <% axes.each do |axis| %>
-      <tr class="handle" data-id="<%= axis.id %>">
+      <tr data-id="<%= axis.id %>">
+        <% if can? :reorder, Research::Laboratory::Axis %>
+          <td><i class="fa fa-bars handle"></i></td>
+        <% end %>
         <td>
           <%= link_to axis, admin_research_laboratory_axis_path(laboratory_id: axis.laboratory, id: axis) %>
         </td>
diff --git a/app/views/admin/university/people/_list.html.erb b/app/views/admin/university/people/_list.html.erb
index d7e02ab29da8cc88bb5114d27ac171594ccad2c1..6218535649f66a8a1b6d70ac749c2b2e49534087 100644
--- a/app/views/admin/university/people/_list.html.erb
+++ b/app/views/admin/university/people/_list.html.erb
@@ -15,12 +15,12 @@
           <div class="btn-group" role="group">
             <%= link_to t('edit'),
                       edit_admin_university_person_path(person),
-                      class: button_classes %>
+                      class: button_classes if can?(:update, person) %>
             <%= link_to t('delete'),
                       admin_university_person_path(person),
                       method: :delete,
                       data: { confirm: t('please_confirm') },
-                      class: button_classes_danger %>
+                      class: button_classes_danger if can?(:destroy, person) %>
           </div>
         </td>
       </tr>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
index 17a26328ee8d1cc5f44a9411d93506b626c158d0..f8850cffd8c2093a9464d6f321d53e2f027b90b3 100644
--- a/app/views/devise/registrations/new.html.erb
+++ b/app/views/devise/registrations/new.html.erb
@@ -18,7 +18,7 @@
       <%= f.association :language,
                         required: true,
                         label_method: lambda { |l| t("languages.#{l.iso_code.to_s}") },
-                        include_blank: t('select_language') %>
+                        include_blank: :translate %>
     </div>
     <div class="col-md-6">
       <%= f.input :password,
diff --git a/app/views/server/dashboard/index.html.erb b/app/views/server/dashboard/index.html.erb
index a537f930ac782da272e191983be28430d3b16851..7cda9c12f2349d1f56b17184ded3ecdb3e0a6133 100644
--- a/app/views/server/dashboard/index.html.erb
+++ b/app/views/server/dashboard/index.html.erb
@@ -1 +1 @@
-<% content_for :title, "Bonjour #{ current_user.first_name }!" %>
+<% content_for :title, t('hello', name: current_user.first_name) %>
diff --git a/config/locales/education/en.yml b/config/locales/education/en.yml
index 011a5cad49bc54f2f9701b567a679a3ce3584654..ae65263548c98696aba87606c29fe3ca777d5556 100644
--- a/config/locales/education/en.yml
+++ b/config/locales/education/en.yml
@@ -7,21 +7,9 @@ en:
       education/program:
         one: Program
         other: Programs
-      education/program/role:
-        one: Role
-        other: Roles
-      education/program/role/person:
-        one: Person
-        other: People
-      education/program/teacher:
-        one: Teacher
-        other: Teachers
       education/school:
         one: School
         other: Schools
-      education/school/administrator:
-        one: Administrator
-        other: Administrators
     attributes:
       education/program:
         accessibility: Accessibilité
@@ -48,14 +36,8 @@ en:
         schools: Schools with this formation
         teachers: Teachers
         team: Team
-      education/program/role:
-        people: People
-        title: Title
-      education/program/role/person:
-        person: Person
-      education/program/teacher:
-        description: Description
-        person: Person
+        content: Program's content
+        results: Results' indicator
       education/school:
         address: Address
         administrators: Administrators
@@ -64,18 +46,23 @@ en:
         name: Name
         phone: Phone
         programs: Programs provided
+        roles: Team Members
         websites: Associated websites
         zipcode: Zipcode
-      education/school/administrator:
-        description: Description
-        person: Person
   education:
+    manage_programs: Manage programs
+    manage_roles: Manage roles
     manage_teachers: Manage teachers
     number_of_programs: Number of programs
     program:
       educational_informations: Educational informations
       main_informations: Main informations
       useful_informations: Useful informations
+    roles:
+      one: Role
+      other: Roles
+    schools:
+      manage_roles: Manage the team
     teachers:
       one: Teacher
       other: Teachers
diff --git a/config/locales/education/fr.yml b/config/locales/education/fr.yml
index 334e2e464512a0ccc317e0c44689f3bc1a614446..132a500f74d4fad39917ed6aa0411102450e92bf 100644
--- a/config/locales/education/fr.yml
+++ b/config/locales/education/fr.yml
@@ -7,21 +7,9 @@ fr:
       education/program:
         one: Formation
         other: Formations
-      education/program/role:
-        one: Rôle
-        other: Rôles
-      education/program/role/person:
-        one: Personne
-        other: Personnes
-      education/program/teacher:
-        one: Enseignant·e
-        other: Enseignants·es
       education/school:
         one: École
         other: Écoles
-      education/school/administrator:
-        one: Équipe administrative
-        other: Équipe administrative
     attributes:
       education/program:
         accessibility: Accessibilité
@@ -50,14 +38,6 @@ fr:
         team: Équipe
         content: Contenus de la formation
         results: Indicateurs de résultats
-      education/program/role:
-        people: Personnes
-        title: Titre
-      education/program/role/person:
-        person: Personne
-      education/program/teacher:
-        description: Description
-        person: Personne
       education/school:
         address: Adresse
         administrators: Équipe administrative
@@ -66,13 +46,13 @@ fr:
         name: Nom
         phone: Téléphone
         programs: Formations dispensées
+        roles: Membres de l'équipe
         websites: Sites webs associés
         zipcode: Code postal
-      education/school/administrator:
-        description: Description
-        person: Personne
   education:
-    manage_teachers: Gérer les Enseignants·es
+    manage_programs: Gérer les formations
+    manage_roles: Gérer les rôles
+    manage_teachers: Gérer les enseignants·es
     number_of_programs: Nombre de formations
     program:
       educational_informations: Informations pédagogiques
@@ -81,6 +61,8 @@ fr:
     roles:
       one: Rôle
       other: Rôles
+    schools:
+      manage_roles: Gérer l'équipe
     teachers:
       one: Enseignant·e
       other: Enseignants·es
diff --git a/config/locales/en.yml b/config/locales/en.yml
index c692b52c30b57506cce0168e7d49cf8a83b99efe..06e4495933c11bbc3086c5105dda897b6d7d5e47 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -32,6 +32,7 @@ en:
       user:
         one: User
         other: Users
+  add: Add
   admin:
     attachment_not_available: Attachment not available
     dashboard: Dashboard
@@ -42,6 +43,7 @@ en:
     password_hint: Leave blank if you do not wish to change the password.
     successfully_created_html: "<i>%{model}</i> was successfully created."
     successfully_destroyed_html: "<i>%{model}</i> was successfully destroyed."
+    successfully_quit_html: "<i>%{model}</i> successfully quit <i>%{target}</i>."
     successfully_updated_html: "<i>%{model}</i> was successfully updated."
     users_alerts:
       not_locked_html: '<i>%{model}</i> was not locked.'
@@ -86,6 +88,7 @@ en:
   false: No
   gdpr:
     privacy_policy: https://osuny.org/politique-de-confidentialite
+  hello: "Hello %{name}!"
   home: Home
   languages:
     en: English
@@ -106,6 +109,8 @@ en:
   please_confirm_with_children: "WARNING: deleting this element will also remove every child. Are you sure?"
   privacy_policy: Privacy policy
   privacy_policy_url: https://osuny.org/politique-de-confidentialite
+  quit: Quit
+  remove: Remove
   save: Save
   select_language: Select language
   simple_form:
@@ -114,6 +119,11 @@ en:
     hints:
       user:
         mobile_phone: "International format (+XX). By filling this field, you accept to receive your two-factor authentication codes via SMS."
+    include_blanks:
+      defaults:
+        language: "Select a language"
+        person: "Select a person"
+        program: "Select a program"
   simple_form_password_with_hints:
       test_chars: "%{min_length} characters min."
   show: Show
@@ -127,3 +137,10 @@ en:
       date_with_hour: "%B %d, %Y %H:%M"
   true: Yes
   validate: Validate
+  views:
+    pagination:
+      first: "&laquo; First"
+      last: "Last &raquo;"
+      previous: "&lsaquo; Previous"
+      next: "Next &rsaquo;"
+      truncate: "&hellip;"
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 768f6224d7e927bcbf8074ed9592f72993c6cc91..a9e297ee7b7d1a3176669eac3eb90c09c82a983f 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -32,6 +32,7 @@ fr:
       user:
         one: Utilisateur·rice
         other: Utilisateur·rice·s
+  add: Ajouter
   admin:
     attachment_not_available: Impossible d'accéder à l'élément
     dashboard: Tableau de bord
@@ -42,6 +43,7 @@ fr:
     password_hint: Laissez vide si vous ne souhaitez pas modifier le mot de passe.
     successfully_created_html: "<i>%{model}</i> a bien été créé(e)."
     successfully_destroyed_html: "<i>%{model}</i> a bien été détruit(e)."
+    successfully_quit_html: "<i>%{model}</i> a bien quitté <i>%{target}</i>."
     successfully_updated_html: "<i>%{model}</i> a bien été mis(e) à jour."
     users_alerts:
       not_locked_html: "<i>%{model}</i> n'était pas verrouillé(e)."
@@ -86,6 +88,7 @@ fr:
   false: Non
   gdpr:
     privacy_policy: https://osuny.org/politique-de-confidentialite
+  hello: "Bonjour %{name} !"
   home: Accueil
   languages:
     en: Anglais
@@ -106,6 +109,8 @@ fr:
   please_confirm_with_children: "ATTENTION : effacer cet élément supprimera aussi tous ses enfants. Est-ce que vous confirmez ?"
   privacy_policy: Politique de confidentialité
   privacy_policy_url: https://osuny.org/politique-de-confidentialite
+  quit: Quitter
+  remove: Retirer
   save: Enregistrer
   select_language: Sélectionnez une langue
   simple_form:
@@ -114,6 +119,11 @@ fr:
     hints:
       user:
         mobile_phone: "Format international (+XX). En renseignant ce champ, vous acceptez de recevoir vos codes de double authentification par SMS."
+    include_blanks:
+      defaults:
+        language: "Sélectionnez une langue"
+        person: "Sélectionnez une personne"
+        program: "Sélectionnez une formation"
   simple_form_password_with_hints:
       test_chars: "%{min_length} caractères min."
   show: Voir
diff --git a/config/locales/university/en.yml b/config/locales/university/en.yml
index 8d690dbb687aa5dd1691327ed3ba5c9e9de97061..2a332ee980a066fc29d622f280313f73ae96769b 100644
--- a/config/locales/university/en.yml
+++ b/config/locales/university/en.yml
@@ -35,6 +35,12 @@ en:
         slug: Slug
         tenure: Have tenure?
         user: User
+      university/person/involvement:
+        person: Person
+        target_id: ''
+      university/role:
+        description: Description
+        people: People
     models:
       university:
         one: University
@@ -42,6 +48,12 @@ en:
       university/person:
         one: Person
         other: People
+      university/person/involvement:
+        one: Involvement
+        other: Involvements
+      university/role:
+        one: Role
+        other: Roles
   simple_form:
     hints:
       university:
diff --git a/config/locales/university/fr.yml b/config/locales/university/fr.yml
index d40b74e837c1e021fdc7e0d26fce0c6da9046248..5bb3fa4fee399807288e31ce3aaf81d4a5049756 100644
--- a/config/locales/university/fr.yml
+++ b/config/locales/university/fr.yml
@@ -35,6 +35,12 @@ fr:
         slug: Slug
         tenure: Titulaire ?
         user: Utilisateur
+      university/person/involvement:
+        person: Personne
+        target_id: ''
+      university/role:
+        description: Description
+        people: Personnes
     models:
       university:
         one: Université
@@ -42,6 +48,12 @@ fr:
       university/person:
         one: Personne
         other: Personnes
+      university/person/involvement:
+        one: Involvement
+        other: Involvements
+      university/role:
+        one: Rôle
+        other: Rôles
   simple_form:
     hints:
       university:
diff --git a/config/routes/admin/education.rb b/config/routes/admin/education.rb
index c3ac5899ec1bdaae385534f3d240cf8716955689..082e5da5115944aa3f89b82cf255f5a27108d410 100644
--- a/config/routes/admin/education.rb
+++ b/config/routes/admin/education.rb
@@ -1,21 +1,29 @@
 namespace :education do
-  resources :teachers, only: [:index, :show]
+  resources :teachers, only: [:index, :show, :edit, :update]
   resources :schools do
-    resources :administrators, controller: 'school/administrators', except: [:index, :show]
+    resources :roles, controller: 'school/roles' do
+      resources :people, controller: 'school/role/people', only: [] do
+        post :reorder, on: :collection
+      end
+      collection do
+        post :reorder
+      end
+    end
   end
   resources :programs do
-    resources :roles, controller: 'program/roles', except: :index do
-      resources :people, controller: 'program/role/people', except: [:index, :show, :edit, :update] do
-        collection do
-          post :reorder
-        end
+    resources :roles, controller: 'program/roles' do
+      resources :people, controller: 'program/role/people', only: [] do
+        post :reorder, on: :collection
       end
-
       collection do
         post :reorder
       end
     end
-    resources :teachers, controller: 'program/teachers', except: [:index, :show]
+    resources :teachers, controller: 'program/teachers', except: :show do
+      collection do
+        post :reorder
+      end
+    end
     collection do
       post :reorder
     end
diff --git a/db/migrate/20220128111528_remove_legacy_education_models.rb b/db/migrate/20220128111528_remove_legacy_education_models.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ba2a7369cd30e58793dfd3f96d972c314a74733
--- /dev/null
+++ b/db/migrate/20220128111528_remove_legacy_education_models.rb
@@ -0,0 +1,8 @@
+class RemoveLegacyEducationModels < ActiveRecord::Migration[6.1]
+  def change
+    drop_table :education_school_administrators
+    drop_table :education_program_teachers
+    drop_table :education_program_role_people
+    drop_table :education_program_roles
+  end
+end
diff --git a/db/migrate/20220131163458_change_research_journal_volume_published_type.rb b/db/migrate/20220131163458_change_research_journal_volume_published_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6ac77d3639f17b38a7d172c46b1fe2ee17b6fd2d
--- /dev/null
+++ b/db/migrate/20220131163458_change_research_journal_volume_published_type.rb
@@ -0,0 +1,5 @@
+class ChangeResearchJournalVolumePublishedType < ActiveRecord::Migration[6.1]
+  def change
+    change_column :research_journal_volumes, :published_at, :datetime
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fa449131e30109e0ca6ac24b1b24c9cfe0fd42d8..c2d823d76cade935dd6213bf06ead3cf8d8c8a28 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2022_01_24_165229) do
+ActiveRecord::Schema.define(version: 2022_01_31_163458) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
@@ -352,37 +352,6 @@ ActiveRecord::Schema.define(version: 2022_01_24_165229) do
     t.index ["priority", "run_at"], name: "delayed_jobs_priority"
   end
 
-  create_table "education_program_role_people", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
-    t.integer "position"
-    t.uuid "person_id", null: false
-    t.uuid "role_id", null: false
-    t.datetime "created_at", precision: 6, null: false
-    t.datetime "updated_at", precision: 6, null: false
-    t.index ["person_id"], name: "index_education_program_role_people_on_person_id"
-    t.index ["role_id"], name: "index_education_program_role_people_on_role_id"
-  end
-
-  create_table "education_program_roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
-    t.string "title"
-    t.integer "position"
-    t.uuid "program_id", null: false
-    t.uuid "university_id", null: false
-    t.datetime "created_at", precision: 6, null: false
-    t.datetime "updated_at", precision: 6, null: false
-    t.index ["program_id"], name: "index_education_program_roles_on_program_id"
-    t.index ["university_id"], name: "index_education_program_roles_on_university_id"
-  end
-
-  create_table "education_program_teachers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
-    t.text "description"
-    t.uuid "program_id", null: false
-    t.uuid "person_id", null: false
-    t.datetime "created_at", precision: 6, null: false
-    t.datetime "updated_at", precision: 6, null: false
-    t.index ["person_id"], name: "index_education_program_teachers_on_person_id"
-    t.index ["program_id"], name: "index_education_program_teachers_on_program_id"
-  end
-
   create_table "education_programs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
@@ -410,16 +379,6 @@ ActiveRecord::Schema.define(version: 2022_01_24_165229) do
     t.index ["education_school_id", "education_program_id"], name: "school_program"
   end
 
-  create_table "education_school_administrators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
-    t.text "description"
-    t.uuid "school_id", null: false
-    t.uuid "person_id", null: false
-    t.datetime "created_at", precision: 6, null: false
-    t.datetime "updated_at", precision: 6, null: false
-    t.index ["person_id"], name: "index_education_school_administrators_on_person_id"
-    t.index ["school_id"], name: "index_education_school_administrators_on_school_id"
-  end
-
   create_table "education_schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
@@ -476,7 +435,7 @@ ActiveRecord::Schema.define(version: 2022_01_24_165229) do
     t.uuid "research_journal_id", null: false
     t.string "title"
     t.integer "number"
-    t.date "published_at"
+    t.datetime "published_at"
     t.datetime "created_at", precision: 6, null: false
     t.datetime "updated_at", precision: 6, null: false
     t.text "description"
@@ -690,16 +649,8 @@ ActiveRecord::Schema.define(version: 2022_01_24_165229) do
   add_foreign_key "communication_website_posts", "universities"
   add_foreign_key "communication_website_posts", "university_people", column: "author_id"
   add_foreign_key "communication_websites", "universities"
-  add_foreign_key "education_program_role_people", "education_program_roles", column: "role_id"
-  add_foreign_key "education_program_role_people", "university_people", column: "person_id"
-  add_foreign_key "education_program_roles", "education_programs", column: "program_id"
-  add_foreign_key "education_program_roles", "universities"
-  add_foreign_key "education_program_teachers", "education_programs", column: "program_id"
-  add_foreign_key "education_program_teachers", "university_people", column: "person_id"
   add_foreign_key "education_programs", "education_programs", column: "parent_id"
   add_foreign_key "education_programs", "universities"
-  add_foreign_key "education_school_administrators", "education_schools", column: "school_id"
-  add_foreign_key "education_school_administrators", "university_people", column: "person_id"
   add_foreign_key "education_schools", "universities"
   add_foreign_key "research_journal_articles", "research_journal_volumes"
   add_foreign_key "research_journal_articles", "research_journals"
diff --git a/docs/communication/websites/export.md b/docs/communication/websites/export.md
index 5f0b61974f450f1637bce05eed426f9bb89897a1..bdabcff060960c773df6e5f0added753578385db 100644
--- a/docs/communication/websites/export.md
+++ b/docs/communication/websites/export.md
@@ -15,6 +15,27 @@ Les fichiers renommés doivent être déplacés sur git.
 Les fichiers supprimés ou dépubliés doivent être supprimés sur git.
 Il faut veiller à limiter le nombre de commits et à éviter les commits vides.
 
+## Setup
+
+### GitHub
+
+- Créer un repository à partir du template suivant : https://github.com/noesya/osuny-hugo-template
+- Une fois le repository créé, générer un personal access token ici : https://github.com/settings/tokens
+  - Permission à accorder : `repo`
+  - Durée : pour une bonne sécurité, il n'est pas recommandé de créer un token permanent, notez simplement qu'il faut le régénérer réguilièrement.
+- Copier le personnal access token
+- Dans le back-office d'Osuny, créer ou modifier un website et renseignez les 2 champs relatifs à Git :
+  - Repository : `username/repo`
+  - Access token : `ghp_xxxxxxxxxxxxxxxxxxxx`
+
+### Déploiement (Netlify)
+
+La récupération du thème se fait via SSH par défaut. Pour que le déploiement fonctionne correctement, vous pouvez :
+- Changer le remote du submodule pour qu'il utilise HTTPS.
+- Garder le SSH, cependant il faut :
+  - Générer et copier la deploy key du site sur Netlify (dans "Site settings", "Build & deploy" puis "Deploy key").
+  - L'ajouter dans la section "Deploy keys" du repository contenant le thème (ici : https://github.com/noesya/osuny-hugo-theme/settings/keys).
+
 ## Architecture
 
 Les git::providers permettent de dialoguer avec les services comme Github et Gitlab.
diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake
index 0e862f5d679e6e5f6ed78b930a645e03915d1316..7882b1afab2410acebdea1bb686e865848ba5751 100644
--- a/lib/tasks/app.rake
+++ b/lib/tasks/app.rake
@@ -40,50 +40,6 @@ namespace :app do
         imported_post.post&.update_column :published_at, imported_post.published_at
       end
     end
-
-    Education::Program::Teacher.find_each { |teacher|
-      involvement = University::Person::Involvement.where(
-        kind: 'teacher',
-        target: teacher.program,
-        person_id: teacher.person_id,
-        university_id: teacher.person.university_id
-      ).first_or_create
-      involvement.update_column(:description, teacher.description)
-    }
-
-    Education::Program::Role.find_each { |program_role|
-      university_role = University::Role.where(
-        description: program_role.description,
-        target: program_role.program,
-        position: program_role.position,
-        university_id: program_role.university_id
-      ).first_or_create
-
-      program_role.people.find_each { |role_person|
-        University::Person::Involvement.where(
-          kind: 'administrator',
-          target: university_role,
-          person_id: role_person.person_id,
-          position: role_person.position,
-          university_id: program_role.university_id
-        ).first_or_create
-      }
-    }
-
-    Education::School::Administrator.find_each { |administrator|
-      university_role = University::Role.where(
-        description: administrator.description,
-        target: administrator.school,
-        university_id: administrator.person.university_id
-      ).first_or_create
-
-      University::Person::Involvement.where(
-        kind: 'administrator',
-        target: university_role,
-        person_id: administrator.person_id,
-        university_id: administrator.person.university_id
-      ).first_or_create
-    }
   end
 
   namespace :db do