diff --git a/src/assets/js/map-util.js b/src/assets/js/map-util.js
index 7b9c606cd682670d038a9f99ec707ea84071f360..2bb0f78e4a4cc104f50751ca06cd87412feaf234 100644
--- a/src/assets/js/map-util.js
+++ b/src/assets/js/map-util.js
@@ -3,6 +3,13 @@ import "leaflet/dist/leaflet.css";
 import flip from '@turf/flip'
 import axios from "axios"
 
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+  var re = new RegExp(name + "=([^;]+)");
+  var value = re.exec(document.cookie);
+  return (value != null) ? unescape(value[1]) : null;
+})('csrftoken');
+
+
 let map;
 let dictLayersToLeaflet = {};
 
diff --git a/src/components/ImportTask.vue b/src/components/ImportTask.vue
index fb642ab722c761cc8c80988e6413576d0fd1f9e7..d7e008942c59ba2405acd96ee2611f5d5f0e4b58 100644
--- a/src/components/ImportTask.vue
+++ b/src/components/ImportTask.vue
@@ -23,7 +23,7 @@
           <td>
             <span
               v-if="importFile.infos"
-              :data-tooltip="dataTooltipMsg(importFile.infos)"
+              :data-tooltip="importFile.infos"
               class="ui icon"
             >
               <i
diff --git a/src/components/feature/FeatureAttachmentForm.vue b/src/components/feature/FeatureAttachmentForm.vue
index 7e9ef3a1d2aa912bb6292cd6e6c8c7c82ddec024..c75905f2db595f7cee272a5d245ede78a522024f 100644
--- a/src/components/feature/FeatureAttachmentForm.vue
+++ b/src/components/feature/FeatureAttachmentForm.vue
@@ -1,21 +1,17 @@
 <template>
   <div>
-    <!--  <span v-for="hidden in form.hidden_fields" :key="hidden">
-      {{ hidden }}
-    </span> -->
-
     <div class="ui teal segment">
       <h4>
         Pièce jointe
         <button
-          @click="remove_attachment_formset(form.dataKey)"
+          @click="removeAttachmentFormset(form.dataKey)"
           class="ui small compact right floated icon button remove-formset"
           type="button"
         >
           <i class="ui times icon"></i>
         </button>
       </h4>
-      {{ form.errors }}
+      <!-- {{ form.errors }} -->
       <div class="visible-fields">
         <div class="two fields">
           <div class="required field">
@@ -27,35 +23,38 @@
               :name="form.title.html_name"
               :id="form.title.id_for_label"
               v-model="form.title.value"
-              @blur="updateStore"
+              @change="updateStore"
             />
-            {{ form.title.errors }}
+            <ul :id="form.title.id_for_error" class="errorlist">
+              <li v-for="error in form.title.errors" :key="error">
+                {{ error }}
+              </li>
+            </ul>
           </div>
           <div class="required field">
             <label>Fichier (PDF, PNG, JPEG)</label>
-            <!-- // todo : mettre en place la sélection de fichier -->
             <label
-              @click="selectFile"
               class="ui icon button"
-              :for="form.attachment_file.id_for_label"
+              :for="'attachment_file' + attachmentForm.dataKey"
             >
               <i class="file icon"></i>
               <span v-if="form.attachment_file.value" class="label">{{
                 form.attachment_file.value
               }}</span>
-              <span v-else class="label">Sélectionner un fichier ...</span>
+              <span v-else class="label">Sélectionner un fichier ... </span>
             </label>
-            <!-- // todo: récupérer la valeur :accept="IMAGE_FORMAT" -->
-            <!-- @change="processImgData" -->
             <input
+              @change="onFileChange"
               type="file"
               style="display: none"
               :name="form.attachment_file.html_name"
-              class="image_file"
-              :id="form.attachment_file.id_for_label"
-              @blur="updateStore"
+              :id="'attachment_file' + attachmentForm.dataKey"
             />
-            {{ form.attachment_file.errors }}
+            <ul :id="form.attachment_file.id_for_error" class="errorlist">
+              <li v-for="error in form.attachment_file.errors" :key="error">
+                {{ error }}
+              </li>
+            </ul>
           </div>
         </div>
         <div class="field">
@@ -64,9 +63,9 @@
             name="form.info.html_name"
             rows="5"
             v-model="form.info.value"
-            @blur="updateStore"
+            @change="updateStore"
           ></textarea>
-          {{ form.info.errors }}
+          <!-- {{ form.info.errors }} -->
         </div>
       </div>
     </div>
@@ -81,9 +80,11 @@ export default {
 
   data() {
     return {
+      fileToImport: null,
       form: {
         title: {
-          errors: null,
+          errors: [],
+          id_for_error: `errorlist-title-${this.attachmentForm.dataKey}`,
           id_for_label: "titre",
           field: {
             max_length: 30, // todo : vérifier dans django
@@ -93,11 +94,11 @@ export default {
           value: "",
         },
         attachment_file: {
-          errors: null,
-          id_for_label: "titre",
+          errors: [],
+          id_for_error: `errorlist-file-${this.attachmentForm.dataKey}`,
           html_name: "titre",
           label: "Titre",
-          value: "",
+          value: null,
         },
         info: {
           value: "",
@@ -107,22 +108,76 @@ export default {
       },
     };
   },
+
+  watch: {
+    attachmentForm(newValue) {
+      this.initForm(newValue);
+    },
+  },
+
   methods: {
-    remove_attachment_formset() {
+    initForm(attachmentForm) {
+      for (let el in attachmentForm) {
+        if (el && this.form[el]) {
+          if (el === "attachment_file" && attachmentForm[el]) {
+            this.form[el].value = attachmentForm[el].split("/").pop(); //* keep only the file name, not the path
+          } else {
+            this.form[el].value = attachmentForm[el];
+          }
+        }
+      }
+    },
+
+    removeAttachmentFormset() {
       this.$store.commit(
         "feature/REMOVE_ATTACHMENT_FORM",
         this.attachmentForm.dataKey
       );
     },
-    selectFile() {},
+
     updateStore() {
       this.$store.commit("feature/UPDATE_ATTACHMENT_FORM", {
         dataKey: this.attachmentForm.dataKey,
         title: this.form.title.value,
         attachment_file: this.form.attachment_file.value,
         info: this.form.info.value,
+        fileToImport: this.fileToImport,
       });
     },
+
+    onFileChange(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files.length) return;
+      this.fileToImport = files[0]; //* store file to import
+      this.form.attachment_file.value = files[0].name; //* add name to the form for display, in order to match format return from API
+      this.updateStore();
+    },
+
+    checkForm() {
+      let isValid = true;
+      if (this.form.title.value === "") {
+        this.form.title.errors = ["Veuillez compléter ce champ."];
+        document
+          .getElementById(this.form.title.id_for_error)
+          .scrollIntoView({ block: "start", inline: "nearest" });
+        isValid = false;
+      } else if (this.form.attachment_file.value === null) {
+        this.form.attachment_file.errors = ["Veuillez compléter ce champ."];
+        this.form.title.errors = [];
+        document
+          .getElementById(this.form.attachment_file.id_for_error)
+          .scrollIntoView({ block: "start", inline: "nearest" });
+        isValid = false;
+      } else {
+        this.form.title.errors = [];
+        this.form.attachment_file.errors = [];
+      }
+      return isValid;
+    },
+  },
+
+  mounted() {
+    this.initForm(this.attachmentForm);
   },
 };
 </script>
\ No newline at end of file
diff --git a/src/components/feature/FeatureLinkedForm.vue b/src/components/feature/FeatureLinkedForm.vue
index 960f63371b388c965bfe623add3ba30b190030e4..80f10d776a4754d01cdcfb0d1568a566964d5e8b 100644
--- a/src/components/feature/FeatureLinkedForm.vue
+++ b/src/components/feature/FeatureLinkedForm.vue
@@ -10,7 +10,9 @@
         <i class="ui times icon"></i>
       </button>
     </h4>
-    {{ form.errors }}
+    <ul id="errorlist-links" class="errorlist">
+      <li v-for="error in form.errors" :key="error" v-html="error"></li>
+    </ul>
     <div class="visible-fields">
       <div class="two fields">
         <div class="required field">
@@ -53,8 +55,10 @@ export default {
   },
 
   computed: {
-    featureOptions: function() {
-      return this.features.map(el=> `${el.title} (${el.display_creator} - ${el.created_on})`) 
+    featureOptions: function () {
+      return this.features.map(
+        (el) => `${el.title} (${el.display_creator} - ${el.created_on})`
+      );
     },
     selected_relation_type: {
       // getter
@@ -92,7 +96,7 @@ export default {
           },
           html_name: "relation_type",
           label: "Type de liaison",
-          value: "",
+          value: "Doublon",
         },
         feature_to: {
           errors: null,
@@ -118,6 +122,19 @@ export default {
         feature_to: this.form.feature_to.value,
       });
     },
+    checkForm() {
+      if (this.form.feature_to.value === "") {
+        this.form.errors = [
+          "<strong>Choisir un signalement lié</strong><br/> Pourriez-vous choisir un signalement pour la nouvelle liaison ?",
+        ];
+        document
+          .getElementById("errorlist-links")
+          .scrollIntoView({ block: "start", inline: "nearest" });
+        return false;
+      }
+      this.form.errors = [];
+      return true;
+    },
   },
 };
 </script>
\ No newline at end of file
diff --git a/src/components/feature_type/FeatureTypeCustomForm.vue b/src/components/feature_type/FeatureTypeCustomForm.vue
index fa70cbfd986c21ef88b08497b2e583f80e48c683..140a62b0765e06417bb45fb7e03a1283aebac863 100644
--- a/src/components/feature_type/FeatureTypeCustomForm.vue
+++ b/src/components/feature_type/FeatureTypeCustomForm.vue
@@ -24,7 +24,11 @@
             @blur="updateStore"
           />
           <small>{{ form.label.help_text }}</small>
-          {{ form.label.errors }}
+          <ul id="errorlist" class="errorlist">
+            <li v-for="error in form.label.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
 
         <div class="required field">
@@ -39,9 +43,14 @@
             @blur="updateStore"
           />
           <small>{{ form.name.help_text }}</small>
-          {{ form.name.errors }}
+          <ul id="errorlist" class="errorlist">
+            <li v-for="error in form.name.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
       </div>
+
       <div class="three fields">
         <div class="required field">
           <label :for="form.position.id_for_label">{{
@@ -58,8 +67,13 @@
             />
           </div>
           <small>{{ form.position.help_text }}</small>
-          {{ form.position.errors }}
+          <ul id="errorlist" class="errorlist">
+            <li v-for="error in form.position.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
+
         <div class="required field">
           <label :for="form.field_type.id_for_label">{{
             form.field_type.label
@@ -70,7 +84,13 @@
             :selected="selectedFieldType"
             :selection.sync="selectedFieldType"
           />
+          <ul id="errorlist" class="errorlist">
+            <li v-for="error in form.field_type.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
+
         <div
           v-if="selectedFieldType === 'Liste de valeurs'"
           class="field field-list-options required field"
@@ -87,7 +107,11 @@
             class="options-field"
           />
           <small>{{ form.help_text }}</small>
-          {{ form.options.errors }}
+          <ul id="errorlist" class="errorlist">
+            <li v-for="error in form.options.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
       </div>
     </div>
@@ -120,7 +144,7 @@ export default {
       form: {
         dataKey: 0,
         label: {
-          errors: null,
+          errors: [],
           id_for_label: "label",
           label: "Label",
           help_text: "Nom en language naturel du champ",
@@ -131,7 +155,7 @@ export default {
           value: null,
         },
         name: {
-          errors: null,
+          errors: [],
           id_for_label: "name",
           label: "Nom",
           html_name: "name",
@@ -143,7 +167,7 @@ export default {
           value: null,
         },
         position: {
-          errors: null,
+          errors: [],
           id_for_label: "position",
           label: "Position",
           min_value: 0, // ! check if good values (not found)
@@ -153,10 +177,10 @@ export default {
           field: {
             max_length: 128, // ! check if good values (not found)
           },
-          value: 0,
+          value: this.customForm.dataKey - 1,
         },
         field_type: {
-          errors: null,
+          errors: [],
           id_for_label: "field_type",
           label: "Type de champ",
           html_name: "field_type",
@@ -164,10 +188,10 @@ export default {
           field: {
             max_length: 50,
           },
-          value: null, //* field to send to the backend
+          value: "boolean",
         },
         options: {
-          errors: null,
+          errors: [],
           id_for_label: "options",
           label: "Options",
           html_name: "options",
@@ -246,6 +270,19 @@ export default {
       // TODO : supprimer les espaces pour chaque option au début et à la fin QUE à la validation
       return string.replace(/\s*,\s*/gi, ",");
     },
+    checkCustomForm() {
+      if (this.form.label.value === null) {
+        this.form.label.errors = ["Veuillez compléter ce champ."];
+        return false;
+      } else if (this.form.name.value === null) {
+        this.form.name.errors = ["Veuillez compléter ce champ."];
+        this.form.label.errors = [];
+        return false;
+      }
+      this.form.label.errors = [];
+      this.form.name.errors = [];
+      return true;
+    },
   },
 
   beforeDestroy() {
diff --git a/src/services/project-api.js b/src/services/project-api.js
index 14019cb19f74c0b76d568761b5008b4bc24692ae..fd46c39241a72d1928e49af49f081730d497da9c 100644
--- a/src/services/project-api.js
+++ b/src/services/project-api.js
@@ -1,6 +1,14 @@
 import axios from 'axios';
 import store from '../store'
 
+
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+  var re = new RegExp(name + "=([^;]+)");
+  var value = re.exec(document.cookie);
+  return (value != null) ? unescape(value[1]) : null;
+})('csrftoken');
+
+
 const baseUrl = store.state.configuration.VUE_APP_DJANGO_API_BASE;
 
 const projectAPI = {
diff --git a/src/store/index.js b/src/store/index.js
index faebe1b6157cbcb4a4d572b2edc5be1135af5d9c..cb00de2006643cd9aeaa14a981a9b12726994edb 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -8,6 +8,12 @@ import feature_type from "./modules/feature_type"
 import feature from "./modules/feature"
 import map from "./modules/map"
 
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+  var re = new RegExp(name + "=([^;]+)");
+  var value = re.exec(document.cookie);
+  return (value != null) ? unescape(value[1]) : null;
+})('csrftoken');
+
 
 Vue.use(Vuex);
 
@@ -30,6 +36,7 @@ export default new Vuex.Store({
     map
   },
   state: {
+    error: null,
     logged: false,
     user: false,
     configuration:null,
@@ -43,6 +50,9 @@ export default new Vuex.Store({
   },
 
   mutations: {
+    error(state, data) {
+      return state.error = data
+    },
     SET_PROJECTS(state, projects) {
       state.projects = projects;
     },
@@ -125,15 +135,23 @@ export default new Vuex.Store({
             password: payload.password,
           })
           .then((response) => {
+            commit('error', null)
             if (response && response.status === 200) {
               // * use stored previous route to go back after login if page not open on login at first
-              const routerHistory = router.options.routerHistory[0].name !== "login" ? router.options.routerHistory : "/"
+              let routerHistory = ''
+              if (router.options.routerHistory[0] != undefined){
+                routerHistory = router.options.routerHistory[0].name !== "login" ? router.options.routerHistory : "/"
+              } else {
+                routerHistory = "/"
+              }
               commit("SET_USER", response.data.user);
               router.push(routerHistory[routerHistory.length - 1] || "/")
               dispatch("GET_USER_LEVEL_PROJECTS");
             }
           })
-          .catch(() => {
+          .catch((error) => {
+            if (error.response.status === 403)
+            commit('error', error.response.data.detail)
             commit("SET_USER", false);
           });
       }
diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js
index 07c33eef2583583f5aa911af5b58dc63bef7d752..f6f7f42e364c7e2ba603110d8f9ee261e068074c 100644
--- a/src/store/modules/feature.js
+++ b/src/store/modules/feature.js
@@ -1,5 +1,11 @@
 const axios = require("axios");
-//import router from '../../router'
+import router from '../../router'
+
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+  var re = new RegExp(name + "=([^;]+)");
+  var value = re.exec(document.cookie);
+  return (value != null) ? unescape(value[1]) : null;
+})('csrftoken');
 
 
 const feature = {
@@ -27,8 +33,8 @@ const feature = {
     SET_EXTRA_FORM(state, extra_form) {
       state.extra_form = extra_form;
     },
-    ADD_ATTACHMENT_FORM(state, dataKey) {
-      state.attachmentFormset = [...state.attachmentFormset, { dataKey }];
+    ADD_ATTACHMENT_FORM(state, attachmentFormset) {
+      state.attachmentFormset = [...state.attachmentFormset, attachmentFormset];
     },
     UPDATE_ATTACHMENT_FORM(state, payload) {
       const index = state.attachmentFormset.findIndex((el) => el.dataKey === payload.dataKey);
@@ -37,6 +43,9 @@ const feature = {
     REMOVE_ATTACHMENT_FORM(state, payload) {
       state.attachmentFormset = state.attachmentFormset.filter(form => form.dataKey !== payload);
     },
+    CLEAR_ATTACHMENT_FORM(state) {
+      state.attachmentFormset = [];
+    },
     ADD_LINKED_FORM(state, dataKey) {
       state.linkedFormset = [...state.linkedFormset, { dataKey }];
     },
@@ -55,19 +64,21 @@ const feature = {
       axios
         .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/feature/`)
         .then((response) => {
-          const features = response.data.features;
-          commit("SET_FEATURES", features);
-          //dispatch("map/ADD_FEATURES", null, { root: true }); //todo: should check if map was initiated
+          if (response.status === 200 && response.data) {
+            const features = response.data.features;
+            commit("SET_FEATURES", features);
+            //dispatch("map/ADD_FEATURES", null, { root: true }); //todo: should check if map was initiated
+          }
         })
         .catch((error) => {
           throw error;
         });
     },
 
-    POST_FEATURE({ state, rootState }, routeName) {
-      let extraFormOject = {};
+    SEND_FEATURE({ state, rootState, dispatch }, routeName) {
+      let extraFormObject = {}; //* prepare an object to be flatten in properties of geojson
       for (const field of state.extra_form) {
-        extraFormOject[field.name] = field.value;
+        extraFormObject[field.name] = field.value;
       }
       const geojson = {
         "id": state.form.feature_id,
@@ -79,7 +90,7 @@ const feature = {
           "status": state.form.status.value,
           "project": rootState.project_slug,
           "feature_type": rootState.feature_type.current_feature_type_slug,
-          ...extraFormOject
+          ...extraFormObject
         }
       }
 
@@ -87,37 +98,65 @@ const feature = {
         axios
           .put(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson)
           .then((response) => {
-            console.log(response, response.data)
+            if (response.status === 200 && response.data) {
+              router.push({
+                name: "project_detail",
+                params: {
+                  slug: rootState.project_slug,
+                  message: "Le signalement a été mis à jour",
+                },
+              });
+            }
           })
           .catch((error) => {
             throw error;
           });
-      } else {
-        axios
+        } else {
+          axios
           .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/`, geojson)
           .then((response) => {
-            console.log(response, response.data)
+            if (response.status === 201 && response.data) {
+              dispatch("SEND_ATTACHMENTS", response.data.id)
+              router.push({
+                name: "project_detail",
+                params: {
+                  slug: rootState.project_slug,
+                  message: "Le signalement a été crée",
+                },
+              });
+            }
           })
           .catch((error) => {
             throw error;
           });
       }
     },
+
+    SEND_ATTACHMENTS({ state }, featureId) {
+      for (let attacht of state.attachmentFormset) {
+        let formdata = new FormData();
+        formdata.append("file", attacht.fileToImport, attacht.fileToImport.name);
+        const data = {
+          title: attacht.title,
+          info: attacht.info,
+        }
+        formdata.append("data", JSON.stringify(data));
+        axios
+          .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${featureId}/attachments/`, formdata)
+          .then((response) => {
+            if (response.status === 200 && response.data) {
+              console.log(response, response.data)
+              return "La pièce jointe a bien été ajouté"
+            }
+          })
+          .catch((error) => {
+            throw error;
+          });
+      }
+    }
     //DELETE_FEATURE({ state }, feature_slug) {
     //console.log("Deleting feature:", feature_slug, state)
 
-    /* axios
-      .post(`${DJANGO_API_BASE}feature_type/`, data)
-      .then((response) => {
-        const routerHistory = router.options.routerHistory 
-        commit("SET_USER", response.data.user);
-        router.push(routerHistory[routerHistory.length - 1] || "/")
-        dispatch("GET_USER_LEVEL_PROJECTS");
-      })
-      .catch(() => {
-        commit("SET_USER", false)
-      }); */
-    // },
     // POST_COMMENT({ state }, data) {
     //console.log("post comment", data, state)
 
diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.js
index b1c65d094d405450babbad5a0a782fca292f318f..f4148c1783d7d4c6ecc97ea7186b0bb418d12296 100644
--- a/src/store/modules/feature_type.js
+++ b/src/store/modules/feature_type.js
@@ -1,5 +1,10 @@
 import axios from "axios"
 
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+  var re = new RegExp(name + "=([^;]+)");
+  var value = re.exec(document.cookie);
+  return (value != null) ? unescape(value[1]) : null;
+})('csrftoken');
 
 
 const feature_type = {
@@ -77,6 +82,7 @@ const feature_type = {
     async SEND_FEATURE_TYPE({ state, getters, rootGetters }, requestType) {
       const data = {
         'title': state.form.title.value,
+        'title_optional': state.form.title_optional.value,
         'geom_type': state.form.geom_type.value,
         'color': state.form.color.value,
         'colors_style': state.form.colors_style.value,
@@ -122,7 +128,7 @@ const feature_type = {
       }
     },
 
-    POST_FEATURES_FROM_GEOJSON({ state, dispatch }, payload) {
+    SEND_FEATURES_FROM_GEOJSON({ state, dispatch }, payload) {
       const { feature_type_slug } = payload
 
       if (state.fileToImport.size > 0) {
diff --git a/src/store/modules/map.js b/src/store/modules/map.js
index 207d3ebc9696cd3741160b7165027a6d9d9573c1..f29ef9f4d128729e027c6364005edcad240fce6c 100644
--- a/src/store/modules/map.js
+++ b/src/store/modules/map.js
@@ -1,6 +1,12 @@
 const axios = require("axios");
 import { mapUtil } from "@/assets/js/map-util.js";
 
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+  var re = new RegExp(name + "=([^;]+)");
+  var value = re.exec(document.cookie);
+  return (value != null) ? unescape(value[1]) : null;
+})('csrftoken');
+
 
 const map = {
   namespaced: true,
diff --git a/src/views/feature/Feature_detail.vue b/src/views/feature/Feature_detail.vue
index b894f3b31c48bb6c94459cddffe805fae4eadadf..d6743cf7508fad89316f35932fc8ae655fb66f6f 100644
--- a/src/views/feature/Feature_detail.vue
+++ b/src/views/feature/Feature_detail.vue
@@ -176,25 +176,26 @@
       <div class="seven wide column">
         <h2 class="ui header">Pièces jointes</h2>
 
-        <div v-for="pj in attachments" :key="pj.title" class="ui divided items">
+        <div v-for="pj in attachments" :key="pj.id" class="ui divided items">
           <div class="item">
             <a
               class="ui tiny image"
               target="_blank"
-              :href="pj.attachment_file.url"
+              :href="DJANGO_BASE_URL + pj.attachment_file"
             >
               <img
-                v-if="pj.extension === '.pdf'"
-                src="{% static 'geocontrib/img/pdf.png' %}"
+                :src="
+                  pj.extension === '.pdf'
+                    ? require('@/assets/img/pdf.png')
+                    : DJANGO_BASE_URL + pj.attachment_file
+                "
               />
-              <!-- // ? que faire ? -->
-              <img v-else :src="pj.attachment_file.url" />
             </a>
             <div class="middle aligned content">
               <a
                 class="header"
                 target="_blank"
-                :href="pj.attachment_file.url"
+                :href="DJANGO_BASE_URL + pj.attachment_file"
                 >{{ pj.title }}</a
               >
               <div class="description">
@@ -377,6 +378,7 @@
 import frag from "vue-frag";
 import { mapState } from "vuex";
 import { mapUtil } from "@/assets/js/map-util.js";
+import featureAPI from "@/services/feature-api";
 const axios = require("axios");
 
 export default {
@@ -402,18 +404,8 @@ export default {
           },
         }, */
       ],
-      attachments: [
-        // TODO : Récupérer depuis l'api
-        /*  {
-          attachment_file: {
-            url: "http://localhost:8000/media/user_1/albinoscom.jpg",
-          },
-          extension: "jpg",
-          title: "albinos",
-          info: "Drôle de bête",
-        }, */
-      ],
-      // TODO : Récupérer depuis l'api
+      attachments: [],
+      // TODO : Récupérer events depuis l'api
       events: [],
       comment_form: {
         attachment_file: {
@@ -564,6 +556,9 @@ export default {
       "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
       this.$route.params.slug_type_signal
     );
+    featureAPI
+      .getFeatureAttachments(this.$route.params.slug_signal)
+      .then((data) => (this.attachments = data));
   },
 
   mounted() {
diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue
index aa803f96591f2cdddc4758b2485a18985b6652c1..2b60ca875aa7c1750313258fa4296d3ad3cbd172 100644
--- a/src/views/feature/Feature_edit.vue
+++ b/src/views/feature/Feature_edit.vue
@@ -19,7 +19,7 @@
       >
         <!-- Feature Fields -->
         <div class="two fields">
-          <div class="required field">
+          <div :class="field_title">
             <label :for="form.title.id_for_label">{{ form.title.label }}</label>
             <input
               type="text"
@@ -30,7 +30,11 @@
               v-model="form.title.value"
               @blur="updateStore"
             />
-            {{ form.title.errors }}
+            <ul id="errorlist-title" class="errorlist">
+              <li v-for="error in form.title.errors" :key="error">
+                {{ error }}
+              </li>
+            </ul>
           </div>
           <div class="required field">
             <label :for="form.status.id_for_label">{{
@@ -41,8 +45,6 @@
               :selected="selected_status.name"
               :selection.sync="selected_status"
             />
-
-            {{ form.status.errors }}
           </div>
         </div>
         <div class="field">
@@ -55,7 +57,6 @@
             v-model="form.description.value"
             @blur="updateStore"
           ></textarea>
-          {{ form.description.errors }}
         </div>
 
         <!-- Geom Field -->
@@ -65,7 +66,8 @@
           <!-- Import GeoImage -->
           <div v-frag v-if="feature_type && feature_type.geom_type === 'point'">
             <p>
-              <button @click="showGeoRef=true;"
+              <button
+                @click="showGeoRef = true"
                 id="add-geo-image"
                 type="button"
                 class="ui compact button"
@@ -75,25 +77,44 @@
               Vous pouvez utiliser une image géoréférencée pour localiser le
               signalement.
             </p>
-            <div v-if="showGeoRef"> 
-                <p>Attention, si vous avez déjà saisi une géométrie, celle issue de l'image importée l'écrasera.</p>
-                <div class="field">
-                  <label>Image (png ou jpeg)</label>
-                  <label class="ui icon button" for="image_file">
-                    <i class="file icon"></i>
-                    <span class="label">Sélectionner une image ...</span>
-                  </label>
-                  <input type="file" accept="image/jpeg, image/png" style="display:none;" ref="file" v-on:change="handleFileUpload()"
-                    name="image_file" class="image_file" id="image_file" >
-                  <p class="error-message" style="color:red;">{{ erreurUploadMessage }}</p>
-                </div>
-                <button @click="georeferencement()" id="get-geom-from-image-file" type='button' class="ui positive right labeled icon button">
-                  Importer
-                  <i class="checkmark icon"></i>
-                </button>
+            <div v-if="showGeoRef">
+              <p>
+                Attention, si vous avez déjà saisi une géométrie, celle issue de
+                l'image importée l'écrasera.
+              </p>
+              <div class="field">
+                <label>Image (png ou jpeg)</label>
+                <label class="ui icon button" for="image_file">
+                  <i class="file icon"></i>
+                  <span class="label">Sélectionner une image ...</span>
+                </label>
+                <input
+                  type="file"
+                  accept="image/jpeg, image/png"
+                  style="display: none"
+                  ref="file"
+                  v-on:change="handleFileUpload()"
+                  name="image_file"
+                  class="image_file"
+                  id="image_file"
+                />
+                <p class="error-message" style="color: red">
+                  {{ erreurUploadMessage }}
+                </p>
+              </div>
+              <button
+                @click="georeferencement()"
+                id="get-geom-from-image-file"
+                type="button"
+                class="ui positive right labeled icon button"
+              >
+                Importer
+                <i class="checkmark icon"></i>
+              </button>
             </div>
             <p v-if="showGeoPositionBtn">
-              <button @click="create_point_geoposition()"
+              <button
+                @click="create_point_geoposition()"
                 id="create-point-geoposition"
                 type="button"
                 class="ui compact button"
@@ -102,19 +123,27 @@
                 signalement à partir de votre géolocalisation
               </button>
             </p>
-            <span id="erreur-geolocalisation" v-if="erreurGeolocalisationMessage">
+            <span
+              id="erreur-geolocalisation"
+              v-if="erreurGeolocalisationMessage"
+            >
               <div class="ui negative message">
                 <div class="header">
                   Une erreur est survenue avec la fonctionnalité de
                   géolocalisation
                 </div>
-                <p id="erreur-geolocalisation-message">{{ erreurGeolocalisationMessage }}</p>
+                <p id="erreur-geolocalisation-message">
+                  {{ erreurGeolocalisationMessage }}
+                </p>
               </div>
               <br />
             </span>
           </div>
-
-          {{ form.geom.errors }}
+          <ul id="errorlist-geom" class="errorlist">
+            <li v-for="error in form.geom.errors" :key="error">
+              {{ error }}
+            </li>
+          </ul>
           <!-- Map -->
           <input
             type="hidden"
@@ -130,7 +159,7 @@
             "geocontrib/map-layers/sidebar-layers.html" with
             basemaps=serialized_base_maps layers=serialized_layers
             project=project.slug%} {% endif %} -->
-            <SidebarLayers v-if="baseMaps && map" />
+            <SidebarLayers v-if="basemaps && map" />
           </div>
         </div>
 
@@ -147,14 +176,12 @@
 
         <!-- Pièces jointes -->
         <div class="ui horizontal divider">PIÈCES JOINTES</div>
-        <!-- {{ attachment_formset.non_form_errors }} -->
-
         <div id="formsets-attachment">
-          <!-- {{ attachment_formset.management_form }} -->
           <FeatureAttachmentForm
             v-for="form in attachmentFormset"
             :key="form.dataKey"
             :attachmentForm="form"
+            ref="attachementForm"
           />
         </div>
 
@@ -169,15 +196,13 @@
 
         <!-- Signalements liés -->
         <div class="ui horizontal divider">SIGNALEMENTS LIÉS</div>
-        <!-- {{ linked_formset.non_form_errors }} -->
         <div id="formsets-link">
-          <!-- {{ linked_formset.management_form }} -->
-
           <FeatureLinkedForm
             v-for="form in linkedFormset"
             :key="form.dataKey"
             :linkedForm="form"
             :features="features"
+            ref="linkedForm"
           />
         </div>
         <button
@@ -207,6 +232,7 @@ import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm";
 import FeatureExtraForm from "@/components/feature/FeatureExtraForm";
 import Dropdown from "@/components/Dropdown.vue";
 import SidebarLayers from "@/components/map-layers/SidebarLayers";
+import featureAPI from "@/services/feature-api";
 
 import L from "leaflet";
 import "leaflet-draw";
@@ -214,6 +240,13 @@ import { mapUtil } from "@/assets/js/map-util.js";
 const axios = require("axios");
 import flip from "@turf/flip";
 
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+    var re = new RegExp(name + "=([^;]+)");
+    var value = re.exec(document.cookie);
+    return (value != null) ? unescape(value[1]) : null;
+  })('csrftoken');
+
+
 export default {
   name: "Feature_edit",
 
@@ -231,12 +264,12 @@ export default {
 
   data() {
     return {
-      map:null,
-      file:null,
-      showGeoRef:false,
-      showGeoPositionBtn:true,
-      erreurGeolocalisationMessage:null,
-      erreurUploadMessage:null,
+      map: null,
+      file: null,
+      showGeoRef: false,
+      showGeoPositionBtn: true,
+      erreurGeolocalisationMessage: null,
+      erreurUploadMessage: null,
       attachmentDataKey: 0,
       linkedDataKey: 0,
       statusChoices: [
@@ -249,7 +282,7 @@ export default {
       ],
       form: {
         title: {
-          errors: null,
+          errors: [],
           id_for_label: "name",
           field: {
             max_length: 30,
@@ -259,20 +292,23 @@ export default {
           value: "",
         },
         status: {
-          errors: null,
           id_for_label: "status",
           html_name: "status",
           label: "Statut",
-          value: "Brouillon",
+          value: {
+            value: "draft",
+            name: "Brouillon",
+          },
         },
         description: {
-          errors: null,
+          errors: [],
           id_for_label: "description",
           html_name: "description",
           label: "Description",
           value: "",
         },
         geom: {
+          errors: [],
           label: "Localisation",
           value: null,
         },
@@ -282,6 +318,7 @@ export default {
 
   computed: {
     ...mapState(["project"]),
+    ...mapState("map", ["basemaps"]),
     ...mapState("feature", [
       "attachmentFormset",
       "linkedFormset",
@@ -290,6 +327,15 @@ export default {
     ]),
     ...mapGetters("feature_type", ["feature_type"]),
 
+    field_title() {
+      if (this.feature_type) {
+        if (this.feature_type.title_optional) {
+          return "field";
+        }
+      }
+      return "required field";
+    },
+
     currentRouteName() {
       return this.$route.name;
     },
@@ -328,15 +374,15 @@ export default {
   methods: {
     initForm() {
       if (this.currentRouteName === "editer-signalement") {
-        for (let el in this.feature) {
-          if (el && this.form[el]) {
-            if (el === "status") {
-              const value = this.feature[el];
-              this.form[el].value = this.statusChoices.find(
-                (el) => el.value === value
+        for (let key in this.feature) {
+          if (key && this.form[key]) {
+            if (key === "status") {
+              const value = this.feature[key];
+              this.form[key].value = this.statusChoices.find(
+                (key) => key.value === value
               );
             } else {
-              this.form[el].value = this.feature[el];
+              this.form[key].value = this.feature[key];
             }
           }
         }
@@ -344,51 +390,52 @@ export default {
       }
     },
     create_point_geoposition() {
-
       function success(position) {
-        const latitude  = position.coords.latitude
-        const longitude = position.coords.longitude
+        const latitude = position.coords.latitude;
+        const longitude = position.coords.longitude;
 
-        var layer = L.circleMarker([latitude, longitude])
-        this.add_layer_call_back(layer)
+        var layer = L.circleMarker([latitude, longitude]);
+        this.add_layer_call_back(layer);
         this.map.setView([latitude, longitude]);
       }
 
       function error(err) {
-        this.erreurGeolocalisationMessage=err.message;
-
+        this.erreurGeolocalisationMessage = err.message;
       }
-      this.erreurGeolocalisationMessage=null;
+      this.erreurGeolocalisationMessage = null;
       if (!navigator.geolocation) {
-        this.erreurGeolocalisationMessage="La géolocalisation n'est pas supportée par votre navigateur.";
+        this.erreurGeolocalisationMessage =
+          "La géolocalisation n'est pas supportée par votre navigateur.";
       } else {
-        navigator.geolocation.getCurrentPosition(success.bind(this), error.bind(this));
+        navigator.geolocation.getCurrentPosition(
+          success.bind(this),
+          error.bind(this)
+        );
       }
-
     },
     handleFileUpload() {
       this.file = this.$refs.file.files[0];
-      console.log('>>>> 1st element in files array >>>> ', this.file);
+      console.log(">>>> 1st element in files array >>>> ", this.file);
     },
-    georeferencement(){
+    georeferencement() {
       console.log("georeferencement");
       const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}exif-geom-reader/`;
       let formData = new FormData();
-      formData.append('file', this.file);
-      console.log('>> formData >> ', formData);
-      let self=this;
-      axios.post(url,
-          formData, {
-            headers: {
-              'Content-Type': 'multipart/form-data'
-            }
-          }
-        ).then(function () {
-          console.log('SUCCESS!!');
+      formData.append("file", this.file);
+      console.log(">> formData >> ", formData);
+      let self = this;
+      axios
+        .post(url, formData, {
+          headers: {
+            "Content-Type": "multipart/form-data",
+          },
+        })
+        .then(function () {
+          console.log("SUCCESS!!");
         })
         .catch(function () {
-          console.log('FAILURE!!');
-          self.erreurUploadMessage='FAILURE!!';
+          console.log("FAILURE!!");
+          self.erreurUploadMessage = "FAILURE!!";
         });
     },
 
@@ -409,10 +456,26 @@ export default {
     },
 
     add_attachement_formset() {
-      this.$store.commit("feature/ADD_ATTACHMENT_FORM", this.attachmentDataKey); // * create an object with the counter in store
+      this.$store.commit("feature/ADD_ATTACHMENT_FORM", {
+        dataKey: this.attachmentDataKey,
+      }); // * create an object with the counter in store
       this.attachmentDataKey += 1; // * increment counter for key in v-for
     },
 
+    addExistingAttachementFormset(attachementFormset) {
+      for (const attachment of attachementFormset) {
+        console.log("attachment", attachment);
+        this.$store.commit("feature/ADD_ATTACHMENT_FORM", {
+          dataKey: this.attachmentDataKey,
+          title: attachment.title,
+          attachment_file: attachment.attachment_file,
+          info: attachment.info,
+          id: attachment.id,
+        });
+        this.attachmentDataKey += 1;
+      }
+    },
+
     add_linked_formset() {
       this.$store.commit("feature/ADD_LINKED_FORM", this.linkedDataKey); // * create an object with the counter in store
       this.linkedDataKey += 1; // * increment counter for key in v-for
@@ -428,22 +491,89 @@ export default {
       });
     },
 
-    postForm() {
+    checkFormTitle() {
       if (this.form.title.value) {
-        this.form.title.errors = null;
-        this.$store.dispatch("feature/POST_FEATURE", this.currentRouteName);
+        this.form.title.errors = [];
+        return true;
+      } else if (
+        !this.form.title.errors.includes("Veuillez compléter ce champ.")
+      ) {
+        this.form.title.errors.push("Veuillez compléter ce champ.");
+        document
+          .getElementById("errorlist-title")
+          .scrollIntoView({ block: "end", inline: "nearest" });
+      }
+      return false;
+    },
+
+    checkFormGeom() {
+      if (this.form.geom.value) {
+        this.form.geom.errors = [];
+        return true;
+      } else if (
+        !this.form.geom.errors.includes("Valeur géométrique non valide.")
+      ) {
+        this.form.geom.errors.push("Valeur géométrique non valide.");
+        document
+          .getElementById("errorlist-geom")
+          .scrollIntoView({ block: "end", inline: "nearest" });
+      }
+      return false;
+    },
+
+    checkAddedForm() {
+      let isValid = true; //* fallback if all customForms returned true
+      if (this.$refs.attachementForm) {
+        for (const attachementForm of this.$refs.attachementForm) {
+          if (attachementForm.checkForm() === false) {
+            isValid = false;
+          }
+        }
+      }
+      if (this.$refs.linkedForm) {
+        for (const linkedForm of this.$refs.linkedForm) {
+          if (linkedForm.checkForm() === false) {
+            isValid = false;
+          }
+        }
+      }
+      return isValid;
+    },
+
+    goBackToProject(message) {
+      this.$router.push({
+        name: "project_detail",
+        params: {
+          slug: this.$store.state.project_slug,
+          message,
+        },
+      });
+    },
+
+    postForm() {
+      let is_valid = true;
+      if (!this.feature_type.title_optional) {
+        is_valid =
+          this.checkFormTitle() &&
+          this.checkFormGeom() &&
+          this.checkAddedForm();
       } else {
-        this.form.title.errors = "Veuillez compléter ce champ.";
+        is_valid = this.checkFormGeom() && this.checkAddedForm();
+      }
+
+      if (is_valid) {
+        this.$store.dispatch("feature/SEND_FEATURE", this.currentRouteName);
       }
     },
 
+    //* ************* MAP *************** *//
+
     onFeatureTypeLoaded() {
       var geomLeaflet = {
         point: "circlemarker",
         linestring: "polyline",
         polygon: "polygon",
       };
-      // console.log(this.feature_type)
       var geomType = this.feature_type.geom_type;
       var drawConfig = {
         polygon: false,
@@ -614,8 +744,8 @@ export default {
           this.drawControlFull.addTo(this.map);
           this.updateGeomField("");
           if (geomType === "point") {
-              this.showGeoPositionBtn=true;
-              this.erreurGeolocalisationMessage="";
+            this.showGeoPositionBtn = true;
+            this.erreurGeolocalisationMessage = "";
           }
         }.bind(this)
       );
@@ -687,15 +817,11 @@ export default {
           throw error;
         });
 
-
-
-
       document.addEventListener("change-layers-order", (event) => {
         // Reverse is done because the first layer in order has to be added in the map in last.
         // Slice is done because reverse() changes the original array, so we make a copy first
         mapUtil.updateOrder(event.detail.layers.slice().reverse());
       });
-
     },
 
     add_layer_call_back(layer) {
@@ -706,8 +832,8 @@ export default {
       //this.updateGeomField(wellknown.stringify(layer.toGeoJSON()))
       this.updateGeomField(layer.toGeoJSON());
       if (this.feature_type.geomType === "point") {
-          this.showGeoPositionBtn=false;
-          this.erreurGeolocalisationMessage="";
+        this.showGeoPositionBtn = false;
+        this.erreurGeolocalisationMessage = "";
       }
     },
   },
@@ -720,6 +846,16 @@ export default {
       "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
       this.$route.params.slug_type_signal
     );
+
+    // todo : mutualize in store with feature_detail.vue
+    if (this.$route.params.slug_signal) {
+      featureAPI
+        .getFeatureAttachments(this.$route.params.slug_signal)
+        .then((data) => this.addExistingAttachementFormset(data));
+    } else {
+      //* be sure that previous attachemntFormset has been cleared for creation
+      this.$store.commit("feature/CLEAR_ATTACHMENT_FORM");
+    }
   },
 
   mounted() {
@@ -727,8 +863,6 @@ export default {
     this.initMap();
   },
 };
-
-// TODO : add script from django and convert:
 </script>
 
 <style>
diff --git a/src/views/feature/Feature_list.vue b/src/views/feature/Feature_list.vue
index 6c347c7620e0ca65e43b352dd5c0b178f29cd8f8..8a2764cc2e69f5e25edc37309d966c198239711d 100644
--- a/src/views/feature/Feature_list.vue
+++ b/src/views/feature/Feature_list.vue
@@ -69,6 +69,29 @@
                 </div>
               </div>
             </div>
+            
+            <div v-if="project && feature_types" class="item right">
+            <div
+              
+              v-if="checkedFeatures.length"
+              class="
+                ui
+                top
+                center
+                pointing
+                compact
+                button button-hover-red
+              "
+              data-tooltip="Effacer tous les types de signalements sélectionnés"
+              data-position="left center"
+              data-variation="mini"
+              >
+              <i
+                class="grey trash icon"
+                @click="modalAllDelete()"
+                ></i>
+            </div>
+            </div>
           </div>
         </div>
       </div>
@@ -127,6 +150,10 @@
       <table id="table-features" class="ui compact table">
         <thead>
           <tr>
+            <th class="center">
+              
+            </th>
+
             <th class="center">Statut <i :class="{ down: isSortedAsc('statut'),up:isSortedDesc('statut') }" class="icon sort" @click="changeSort('statut')"/></th>
             <th class="center">Type <i :class="{ down: isSortedAsc('type'),up:isSortedDesc('type') }" class="icon sort" @click="changeSort('type')"/></th>
             <th class="center">Nom <i :class="{ down: isSortedAsc('nom'),up:isSortedDesc('nom') }" class="icon sort" @click="changeSort('nom')"/></th>
@@ -139,6 +166,19 @@
             v-for="(feature, index) in getPaginatedFeatures()"
             :key="index"
           >
+            <td class="center">
+                <div class="ui checkbox">
+                  <input 
+                    type="checkbox" 
+                    :id="feature.id" 
+                    :value="feature.id"  
+                    v-model="checkedFeatures"
+                    :checked="checkedFeatures[feature.id]"
+                    >
+                    <label></label>
+                  </div>
+              </td>
+
             <td class="center">
               <div v-if="feature.properties.status.value == 'archived'" data-tooltip="Archivé">
                 <i class="grey archive icon"></i>
@@ -242,6 +282,41 @@
       
       </div> 
     </div>
+    <!-- MODAL ALL DELETE FEATURE TYPE -->
+    <div
+      v-if="modalAllDeleteOpen"
+      class="ui dimmer modals page transition visible active"
+      style="display: flex !important"
+      >
+      <div
+        :class="[
+          'ui mini modal subscription',
+          { 'active visible': modalAllDeleteOpen },
+        ]"
+      >
+        <i @click="modalAllDeleteOpen = false" class="close icon"></i>
+        <div class="ui icon header">
+          <i class="trash alternate icon"></i>
+          Êtes-vous sûr de vouloir effacer 
+          <span v-if="checkedFeatures.length == 1"> 
+            un signalement ?
+          </span>
+          <span v-else> 
+            ces {{checkedFeatures.length}} signalements ?
+          </span>
+        </div>
+        <div class="actions">
+          
+            <button
+              @click="deleteAllFeatureSelection()"
+              type="button"
+              class="ui red compact fluid button"
+            >
+              Confirmer la suppression
+            </button>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -264,6 +339,8 @@ export default {
 
   data() {
     return {
+      modalAllDeleteOpen: false,
+      checkedFeatures: [],
       form: {
         type: {
           selected: null,
@@ -329,6 +406,31 @@ export default {
     
   },
   methods: {
+    modalAllDelete(){
+      return (this.modalAllDeleteOpen = !this.modalAllDeleteOpen);
+    },
+    deleteFeature(feature){
+      const url=`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${feature.feature_id}`;
+      axios
+        .delete(url, {
+        })
+        .then(() => {
+          if(!this.modalAllDeleteOpen){
+            this.$router.go();
+          }
+        })
+        .catch(() => {
+          return false;
+        });
+    },
+    deleteAllFeatureSelection(){
+      let feature = {}
+      this.checkedFeatures.forEach(feature_id => {
+        feature = {'feature_id': feature_id};
+        this.deleteFeature(feature);
+      });
+      this.modalAllDelete();
+    },
     getFeatureDisplayName(feature){
       return feature.properties.title || feature.id;
     },
@@ -372,7 +474,7 @@ export default {
             
           })
       }
-        
+
       return filterdFeatures.slice(
         this.pagination.start,
         this.pagination.end
diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue
index a595e6ea9760b71202e6fd9ad3ad5227858cc54f..cd18f5ec2fa0f784d0cf5452bbaef8081649ba32 100644
--- a/src/views/feature_type/Feature_type_detail.vue
+++ b/src/views/feature_type/Feature_type_detail.vue
@@ -242,7 +242,7 @@ export default {
     },
 
     importGeoJson() {
-      this.$store.dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", {
+      this.$store.dispatch("feature_type/SEND_FEATURES_FROM_GEOJSON", {
         slug: this.$route.params.slug,
         feature_type_slug: this.$route.params.feature_type_slug,
         fileToImport: this.fileToImport,
diff --git a/src/views/feature_type/Feature_type_edit.vue b/src/views/feature_type/Feature_type_edit.vue
index 7b198c7f03a260048a4fbe23e2d451ec315cc14b..e3ccfb670d45e981eff5795891acf54a13d70cb1 100644
--- a/src/views/feature_type/Feature_type_edit.vue
+++ b/src/views/feature_type/Feature_type_edit.vue
@@ -25,6 +25,7 @@
         <p v-if="action === 'create'">
           Ces champs par défaut existent pour tous les types de signalement:
         </p>
+
         <div class="two fields">
           <div class="required field">
             <label :for="form.title.id_for_label">{{ form.title.label }}</label>
@@ -43,6 +44,7 @@
               </li>
             </ul>
           </div>
+
           <div class="required field">
             <label :for="form.geom_type.id_for_label">{{
               form.geom_type.label
@@ -69,6 +71,16 @@
             <!-- {{ form.color.errors }} -->
           </div>
         </div>
+        <div class="field">
+          <div class="ui checkbox">
+            <input
+              :name="form.title_optional.html_name"
+              v-model="form.title_optional.value"
+              type="checkbox"
+            />
+            <label>{{form.title_optional.label}}</label>
+          </div>
+        </div>
 
         <!--  //* s'affiche après sélection d'option de type liste dans type de champ -->
         <div
@@ -109,6 +121,7 @@
             :key="form.dataKey"
             :dataKey="form.dataKey"
             :customForm="form"
+            ref="customForms"
           />
         </div>
 
@@ -204,6 +217,13 @@ export default {
           html_name: "title",
           value: null,
         },
+        title_optional: {
+          errors: null,
+          id_for_label: "title_optional",
+          html_name: "title_optional",
+          label: "Titre du signalement optionnel",
+          value: false,
+        },
         geom_type: {
           id_for_label: "geom_type",
           label: "Type de géométrie",
@@ -211,7 +231,7 @@ export default {
             max_length: 128, // ! Vérifier la valeur dans django
           },
           html_name: "geom_type",
-          value: "Point",
+          value: "point",
         },
       },
       reservedKeywords: [
@@ -305,7 +325,7 @@ export default {
         // * find feature_type and fill form values
         if (this.form[el]) this.form[el].value = formData[el];
       }
-      //! add custom fields using ONLY this function, incrementing dataKey for Vue updating correctly components
+      //! add custom fields using ONLY this function, incrementing dataKey for Vue to correctly update components
       formData.customfield_set.forEach((el) => this.addCustomForm(el));
       this.updateStore();
     },
@@ -319,17 +339,28 @@ export default {
       this.$store.commit("feature_type/UPDATE_FORM", {
         color: this.form.color,
         title: this.form.title,
+        title_optional: this.form.title_optional,
         geom_type: this.form.geom_type,
         colors_style: this.form.colors_style,
       });
     },
 
-    checkForm() {
+    checkCustomForms() {
+      if (this.$refs.customForms)
+        for (const customForm of this.$refs.customForms) {
+          if (customForm.checkCustomForm() === false) {
+            return false;
+          }
+        }
+      return true; //* fallback if all customForms returned true
+    },
+
+    checkForms() {
       if (this.form.title.value) {
         this.form.title.errors = [];
-        return true;
+        return this.checkCustomForms(); //* if customForms are ok, validate, if get out function
       } else if (
-        !this.form.title.errors.includes("Veuillez compléter ce champ.") // TODO : Gérer les autres champs
+        !this.form.title.errors.includes("Veuillez compléter ce champ.")
       ) {
         this.form.title.errors.push("Veuillez compléter ce champ.");
         document
@@ -352,7 +383,7 @@ export default {
     sendFeatureType() {
       // * si édition d'une feature_type déja existante, faire un put
       const requestType = this.action === "edit" ? "put" : "post";
-      if (this.checkForm()) {
+      if (this.checkForms()) {
         this.$store
           .dispatch("feature_type/SEND_FEATURE_TYPE", requestType)
           .then(({ status }) => {
@@ -371,7 +402,7 @@ export default {
 
     postFeatures(feature_type_slug) {
       this.$store
-        .dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", {
+        .dispatch("feature_type/SEND_FEATURES_FROM_GEOJSON", {
           slug: this.$route.params.slug,
           feature_type_slug,
         })
@@ -389,7 +420,7 @@ export default {
 
     async postFeatureTypeThenFeatures() {
       const requestType = this.action === "edit" ? "put" : "post";
-      if (this.checkForm()) {
+      if (this.checkForms()) {
         await this.$store
           .dispatch("feature_type/SEND_FEATURE_TYPE", requestType)
           .then(({ feature_type_slug }) => {
@@ -489,10 +520,10 @@ export default {
       }
       if (this.action === "duplicate") {
         //* replace original name with new default title
-        this.form.title.value += ` (Copie ${new Date()
+        this.form.title.value += ` (Copie-${new Date()
           .toLocaleString()
           .slice(0, -3)
-          .replace(",", "")} )`;
+          .replace(",", "")})`;
         this.updateStore(); // * initialize form in store in case this.form would not be modified
       }
     }
diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue
index 4e8055af6c185ec594437934e0089ab258fb1bba..8bff78d9e0d9dbb71fe8672f0eeb4899fb0d4414 100644
--- a/src/views/project/Project_detail.vue
+++ b/src/views/project/Project_detail.vue
@@ -74,7 +74,6 @@
 
       <div class="row">
         <div class="seven wide column">
-          <h3 class="ui header">Types de signalements</h3>
           <!--  // todo : Create endpoints for feature_types -->
           <div class="ui middle aligned divided list">
             <div
@@ -83,7 +82,7 @@
               class="item"
             >
               <div class="middle aligned content">
-                <router-link
+               <router-link
                   :to="{
                     name: 'details-type-signalement',
                     params: { feature_type_slug: type.slug },
@@ -429,6 +428,7 @@
         </div>
       </div>
     </div>
+
   </div>
 </template>
 
@@ -495,7 +495,6 @@ export default {
     refreshId() {
       return "?ver=" + Math.random();
     },
-
     toNewFeatureType() {
       this.$router.push({
         name: "ajouter-type-signalement",
@@ -534,7 +533,6 @@ export default {
         .then((data) => (this.is_suscriber = data.is_suscriber));
     },
   },
-
   created() {
     this.$store.dispatch("GET_PROJECT_INFO", this.slug);
     projectAPI
diff --git a/src/views/project/Project_edit.vue b/src/views/project/Project_edit.vue
index c2b1cbdd1cefa4ff350b0b177ed8315d0a753e05..72a6344c09054bf111b1fd2f44b4a4fe9b5e596c 100644
--- a/src/views/project/Project_edit.vue
+++ b/src/views/project/Project_edit.vue
@@ -13,8 +13,6 @@
       <div class="two fields">
         <div class="required field">
           <label for="title">Titre</label>
-          <!-- <small>{{ form.title.help_text }}</small
-          > --><!-- | safe  // ? utile ?  -->
           <input
             type="text"
             required
@@ -23,7 +21,11 @@
             id="title"
             v-model="form.title"
           />
-          <!-- {{ form.title.errors }} -->
+          <ul id="errorlist-title" class="errorlist">
+            <li v-for="error in errors.title" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
         <div class="field">
           <label>Illustration du projet</label>
@@ -73,6 +75,7 @@
             <input
               type="number"
               min="0"
+              oninput="validity.valid||(value='');"
               style="padding: 1px 2px"
               name="archive_feature"
               id="archive_feature"
@@ -80,7 +83,6 @@
             />
             <div class="ui label">jour(s)</div>
           </div>
-          <!-- {{ form.archive_feature.errors }} -->
         </div>
         <div class="field">
           <label for="delete_feature">Délai avant suppression</label>
@@ -88,6 +90,7 @@
             <input
               type="number"
               min="0"
+              oninput="validity.valid||(value='');"
               style="padding: 1px 2px"
               name="delete_feature"
               id="delete_feature"
@@ -95,7 +98,6 @@
             />
             <div class="ui label">jour(s)</div>
           </div>
-          <!-- {{ form.delete_feature.errors }} -->
         </div>
         <div class="required field">
           <label for="access_level_pub_feature"
@@ -106,6 +108,11 @@
             :selected="form.access_level_pub_feature.name"
             :selection.sync="form.access_level_pub_feature"
           />
+          <ul id="errorlist-access_level_pub_feature" class="errorlist">
+            <li v-for="error in errors.access_level_pub_feature" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
         <div class="required field">
           <label for="access_level_arch_feature">
@@ -116,6 +123,11 @@
             :selected="form.access_level_arch_feature.name"
             :selection.sync="form.access_level_arch_feature"
           />
+          <ul id="errorlist-access_level_arch_feature" class="errorlist">
+            <li v-for="error in errors.access_level_arch_feature" :key="error">
+              {{ error }}
+            </li>
+          </ul>
         </div>
       </div>
 
@@ -160,6 +172,13 @@ import Dropdown from "@/components/Dropdown.vue";
 
 import { mapGetters } from "vuex";
 
+axios.defaults.headers.common['X-CSRFToken'] = (name => {
+    var re = new RegExp(name + "=([^;]+)");
+    var value = re.exec(document.cookie);
+    return (value != null) ? unescape(value[1]) : null;
+  })('csrftoken');
+
+
 export default {
   name: "Project_edit",
 
@@ -179,6 +198,11 @@ export default {
         name: "Sélectionner une image ...",
         size: 0,
       },
+      errors: {
+        title: [],
+        access_level_pub_feature: [],
+        access_level_arch_feature: [],
+      },
       form: {
         title: "",
         slug: "",
@@ -206,7 +230,7 @@ export default {
 
   computed: {
     ...mapGetters(["project"]),
-    DJANGO_BASE_URL:function () {
+    DJANGO_BASE_URL: function () {
       return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
     },
   },
@@ -222,8 +246,8 @@ export default {
       }
     },
     truncate(n, len) {
-      var ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase();
-      var filename = n.replace("." + ext, "");
+      let ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase();
+      let filename = n.replace("." + ext, "");
       if (filename.length <= len) {
         return n;
       }
@@ -286,7 +310,27 @@ export default {
       }
     },
 
+    checkForm() {
+      for (const key in this.errors) {
+        if ((key === "title" && this.form[key]) || this.form[key].value) {
+          this.errors[key] = [];
+        } else if (!this.errors[key].length) {
+          this.errors[key].push(
+            key === "title"
+              ? "Veuillez compléter ce champ."
+              : "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles."
+          );
+          document
+            .getElementById(`errorlist-${key}`)
+            .scrollIntoView({ block: "end", inline: "nearest" });
+          return false;
+        }
+      }
+      return true;
+    },
+
     async postForm() {
+      if (!this.checkForm()) return;
       // todo: check form
       //let url = `${configuration.VUE_APP_DJANGO_API_BASE}projects/`;
       const projectData = {
@@ -302,14 +346,18 @@ export default {
 
       if (this.action === "create" || this.action === "duplicate") {
         await axios
-          .post(`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`, projectData)
+          .post(
+            `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`,
+            projectData
+          )
           .then((response) => {
             if (response && response.status === 201 && response.data) {
               //* send thumbnail after feature_type was created
-              if (this.fileToImport)
+              if (this.fileToImport.size > 0) {
                 this.postProjectThumbnail(response.data.slug);
-            } else {
-              this.goBackNrefresh(response.data.slug);
+              } else {
+                this.goBackNrefresh(response.data.slug);
+              }
             }
           })
           .catch((error) => {
@@ -339,15 +387,23 @@ export default {
 
   created() {
     this.definePageType();
+    console.log(this.action);
     if (this.action === "create") {
       this.thumbnailFileSrc = require("@/assets/img/default.png");
-    } else if (this.action === "edit") {
+    } else if (this.action === "edit" || this.action === "create_from") {
       if (!this.project) {
         this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
       }
-      this.form = this.project;
-      /* this.form.thumbnail = //* add api base to display image src
-        configuration.VUE_APP_DJANGO_BASE + this.form.thumbnail; */
+      this.form = { ...this.project }; //* create a new object to avoid modifying original one
+      if (this.action === "create_from") {
+        this.form.title =
+          this.project.title +
+          ` (Copie-${new Date()
+            .toLocaleString()
+            .slice(0, -3)
+            .replace(",", "")})`;
+        this.form.is_project_type = false;
+      }
       //* transform string values to objects for dropdowns display (could be in a computed)
       this.form.access_level_pub_feature = {
         name: this.project.access_level_pub_feature,
diff --git a/src/views/project/Project_type_list.vue b/src/views/project/Project_type_list.vue
index 3909eab7ac7c1f54d274d63744467eda05df91a0..1af8b5d584bdd52d5995c7d18c5824b4da86128d 100644
--- a/src/views/project/Project_type_list.vue
+++ b/src/views/project/Project_type_list.vue
@@ -21,7 +21,7 @@
                 :to="{
                   name: 'project_create_from',
                   params: {
-                    slug: project.title,
+                    slug: project.slug,
                   },
                 }"
                 >{{ project.title }}</router-link
diff --git a/src/views/registration/Login.vue b/src/views/registration/Login.vue
index 05d38bd0338eef467e39ed8b6647911635d3684f..470d00f3be470f1e4d7b8e989439019d89ba2b92 100644
--- a/src/views/registration/Login.vue
+++ b/src/views/registration/Login.vue
@@ -62,6 +62,7 @@
 
 <script>
 
+import { mapState } from "vuex";
 
 export default {
   name: "Login",
@@ -76,6 +77,7 @@ export default {
     };
   },
   computed: {
+    ...mapState(["error"]),
     LOGO_PATH:function () {
       return this.$store.state.configuration.VUE_APP_LOGO_PATH;
     },
@@ -87,11 +89,19 @@ export default {
     },
   },
   methods: {
-    login() {
+    async login() {
       this.$store.dispatch("LOGIN", {
         username: this.username_value,
         password: this.password_value,
-      });
+      })
+      .then(() => {
+        if (this.error != null){
+          this.form.errors = "Les informations d'identification sont incorrectes.";
+        }
+      })
+       .catch(() => {
+          this.form.errors = this.error
+        });
     },
 
   },