diff --git a/handlers/login.go b/handlers/login.go index 27d1773a..bd1b334c 100644 --- a/handlers/login.go +++ b/handlers/login.go @@ -15,11 +15,11 @@ import ( "github.com/gorilla/sessions" ) -type User struct { - Name string - Admin bool - Role string - UserID string +type UserToken struct { + Username string + Admin bool + Role string + UserID string } var ( @@ -88,17 +88,17 @@ func loginHandler() http.Handler { // FIXME: This is an hack for fast prototyping: users should have // their own table on DB. -func checkCredential(username string, password string) (*User, error) { +func checkCredential(username string, password string) (*UserToken, error) { var participant orm.Participant if username == config.Config.Admin.Username && password == config.Config.Admin.Password { - return &User{username, true, "administrator", "0"}, nil + return &UserToken{username, true, "administrator", "0"}, nil } if err := orm.DB().Where("username = ? AND password = ?", username, password).First(&participant).Error; err != nil { return nil, errors.New("Authentication failed!") } else { - return &User{username, false, "participant", strconv.Itoa(int(participant.ID))}, nil + return &UserToken{username, false, "participant", strconv.Itoa(int(participant.ID))}, nil } } @@ -112,7 +112,7 @@ func getToken(username string, password string) ([]byte, error) { /* Set token claims */ claims := make(map[string]interface{}) claims["admin"] = user.Admin - claims["name"] = user.Name + claims["username"] = user.Username claims["role"] = user.Role claims["user_id"] = user.UserID claims["exp"] = time.Now().Add(time.Hour * 24).Unix() @@ -141,7 +141,7 @@ func tokenHandler() http.Handler { /* Set token claims */ claims := make(map[string]interface{}) claims["admin"] = true - claims["name"] = user.Name + claims["name"] = user.Username claims["exp"] = time.Now().Add(time.Hour * 24).Unix() /* Create the token */ @@ -154,7 +154,7 @@ func tokenHandler() http.Handler { } w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Write([]byte(fmt.Sprintf("{\"Token\":\"%s\",\"User\":\"%s\"}", tokenString, user.Name))) + w.Write([]byte(fmt.Sprintf("{\"Token\":\"%s\",\"User\":\"%s\"}", tokenString, user.Username))) } return http.HandlerFunc(fn) } diff --git a/main.go b/main.go index 37aa8b96..b35c5dec 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ var ( &orm.Contest{}, &orm.Participant{}, &orm.Response{}, + &orm.User{}, } ) diff --git a/oef_dev.sql b/oef_dev.sql new file mode 100644 index 00000000..f8afeccb --- /dev/null +++ b/oef_dev.sql @@ -0,0 +1,201 @@ +-- MariaDB dump 10.17 Distrib 10.4.8-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: oef_test +-- ------------------------------------------------------ +-- Server version 10.4.8-MariaDB-1:10.4.8+maria~bionic + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `answers` +-- + +DROP TABLE IF EXISTS `answers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `answers` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `text` varchar(255) DEFAULT NULL, + `correct` tinyint(1) DEFAULT NULL, + `question_id` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_answers_deleted_at` (`deleted_at`) +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `answers` +-- + +LOCK TABLES `answers` WRITE; +/*!40000 ALTER TABLE `answers` DISABLE KEYS */; +INSERT INTO `answers` VALUES (1,'2019-11-13 15:44:39','2019-11-13 15:44:39',NULL,'la quantità di moneta che viene richiesta dalle imprese sotto forma di prestiti richiesti al sistema bancario',0,1),(2,'2019-11-14 11:48:06','2019-11-14 11:48:06',NULL,'la quantità di moneta richiesta dalla Banca Centrale quando mette in vendita dei titoli per ridurre la moneta in circolazione',0,1),(3,'2019-11-14 11:48:28','2019-11-14 11:48:28',NULL,'la quantità di moneta richiesta dalle famiglie per mantenere in forma liquida i loro risparmi',0,1),(4,'2019-11-14 11:49:05','2019-11-14 12:21:09',NULL,'la quantità di moneta richiesta dai soggetti del sistema economico per transazioni, per ragioni speculative o prudenziali o per altri motivi',1,1),(5,'2019-11-15 10:17:49','2019-11-15 10:17:49',NULL,'elevata differenziazione dei prodotti offerti',0,2),(6,'2019-11-15 10:18:14','2019-11-15 10:18:53',NULL,'trasparenza delle informazioni',1,2),(7,'2019-11-15 10:18:29','2019-11-15 10:18:29',NULL,'presenza di un solo consumatore',0,2),(8,'2019-11-15 10:18:44','2019-11-15 10:18:44',NULL,'presenza di un numero limitato di grandi produttori',0,2),(9,'2019-11-15 10:23:11','2019-11-15 10:23:11',NULL,'un ciclo economico',0,3),(10,'2019-11-15 10:23:24','2019-11-15 10:23:35',NULL,'l\'attività di trasformazione materiale di beni e servizi (input) in altri (output) al fine di accrescerne l\'utilità',1,3),(11,'2019-11-15 10:23:47','2019-11-15 10:23:47',NULL,'l\'insieme dei beni di produzione',0,3),(12,'2019-11-15 10:23:59','2019-11-15 10:23:59',NULL,'il risultato del lavoro dei dipendenti dell\'impresa',0,3); +/*!40000 ALTER TABLE `answers` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `contests` +-- + +DROP TABLE IF EXISTS `contests`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `contests` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `category` varchar(255) DEFAULT NULL, + `start_date` timestamp NULL DEFAULT NULL, + `end_date` timestamp NULL DEFAULT NULL, + `start_time` timestamp NULL DEFAULT NULL, + `end_time` timestamp NULL DEFAULT NULL, + `date` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_contests_deleted_at` (`deleted_at`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `contests` +-- + +LOCK TABLES `contests` WRITE; +/*!40000 ALTER TABLE `contests` DISABLE KEYS */; +INSERT INTO `contests` VALUES (1,'2019-11-14 10:02:17','2019-11-18 15:57:14',NULL,'Regionale JUNIOR','','0000-00-00 00:00:00','0000-00-00 00:00:00','2020-04-01 10:00:00','2020-04-01 11:00:00','2020-04-01 10:00:00'),(2,'2019-11-15 10:15:57','2019-11-18 15:58:55',NULL,'Test Diagnostico','',NULL,NULL,'2019-11-15 13:00:00','2019-11-15 14:00:00','2019-11-15 13:00:00'); +/*!40000 ALTER TABLE `contests` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `participants` +-- + +DROP TABLE IF EXISTS `participants`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `participants` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `firstname` varchar(255) DEFAULT NULL, + `lastname` varchar(255) DEFAULT NULL, + `username` varchar(255) DEFAULT NULL, + `password` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_participants_deleted_at` (`deleted_at`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `participants` +-- + +LOCK TABLES `participants` WRITE; +/*!40000 ALTER TABLE `participants` DISABLE KEYS */; +INSERT INTO `participants` VALUES (1,'2019-11-15 10:02:46','2019-11-18 15:58:55',NULL,'Mario','Rossi','mario.rossi','EqAs1z7M'),(2,'2019-11-18 12:00:07','2019-11-18 15:57:14',NULL,'Luigi','BIANCHI','luigi.bianchi','FpWJj89n'),(3,'2019-11-18 12:01:55','2019-11-18 12:12:26',NULL,'Francesco','VERDI','francesco.verdi','MiJ9Ig4L'),(4,'2019-11-18 15:57:36','2019-11-18 15:57:36',NULL,'Franco','neri','franco.neri','YtGGU28p'); +/*!40000 ALTER TABLE `participants` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `questions` +-- + +DROP TABLE IF EXISTS `questions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `questions` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + `text` varchar(255) DEFAULT NULL, + `contest_id` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_questions_deleted_at` (`deleted_at`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `questions` +-- + +LOCK TABLES `questions` WRITE; +/*!40000 ALTER TABLE `questions` DISABLE KEYS */; +INSERT INTO `questions` VALUES (1,'2019-11-13 14:45:17','2019-11-14 12:21:09',NULL,'Cosa si intende per domanda di moneta?',1),(2,'2019-11-15 10:17:24','2019-11-15 10:18:53',NULL,'È una caratteristica della concorrenza perfetta',2),(3,'2019-11-15 10:21:14','2019-11-15 10:23:35',NULL,'La produzione è',2); +/*!40000 ALTER TABLE `questions` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `responses` +-- + +DROP TABLE IF EXISTS `responses`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `responses` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NULL DEFAULT NULL, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_responses_deleted_at` (`deleted_at`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `responses` +-- + +LOCK TABLES `responses` WRITE; +/*!40000 ALTER TABLE `responses` DISABLE KEYS */; +/*!40000 ALTER TABLE `responses` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `subscriptions` +-- + +DROP TABLE IF EXISTS `subscriptions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `subscriptions` ( + `participant_id` int(10) unsigned NOT NULL, + `contest_id` int(10) unsigned NOT NULL, + PRIMARY KEY (`participant_id`,`contest_id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `subscriptions` +-- + +LOCK TABLES `subscriptions` WRITE; +/*!40000 ALTER TABLE `subscriptions` DISABLE KEYS */; +INSERT INTO `subscriptions` VALUES (1,2),(2,1),(2,2),(3,2); +/*!40000 ALTER TABLE `subscriptions` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2019-11-18 16:05:30 diff --git a/orm/participant.go b/orm/participant.go index e22178c0..bb0e8997 100644 --- a/orm/participant.go +++ b/orm/participant.go @@ -8,17 +8,19 @@ import ( "git.andreafazzi.eu/andrea/oef/renderer" "github.com/jinzhu/gorm" - "github.com/sethvargo/go-password/password" ) type Participant struct { gorm.Model + UserID uint + Firstname string Lastname string - Username string - Password string + FiscalCode string + + User *User Responses []*Response @@ -35,19 +37,8 @@ func (model *Participant) sanitize(s string) string { return r.Replace(lower) } -func (model *Participant) genUsername() error { - model.Username = strings.Join([]string{model.sanitize(model.Firstname), model.sanitize(model.Lastname)}, ".") - return nil -} - -func (model *Participant) genPassword() error { - password, err := password.Generate(8, 2, 0, false, true) - if err != nil { - return err - } - model.Password = password - return nil - +func (model *Participant) username() string { + return strings.Join([]string{model.sanitize(model.Firstname), model.sanitize(model.Lastname)}, ".") } func (model *Participant) GetID() uint { return model.ID } @@ -57,14 +48,11 @@ func (model *Participant) String() string { } func (model *Participant) BeforeSave(tx *gorm.DB) error { - if err := model.genUsername(); err != nil { + var user User + if err := tx.FirstOrCreate(&user, &User{Username: model.username()}).Error; err != nil { return err } - if model.Password == "" { - if err := model.genPassword(); err != nil { - return err - } - } + model.UserID = user.ID return nil } @@ -84,6 +72,13 @@ func (model *Participant) AfterSave(tx *gorm.DB) error { return nil } +func (model *Participant) AfterDelete(tx *gorm.DB) error { + if err := tx.Unscoped().Delete(model.User).Error; err != nil { + return err + } + return nil +} + func (model *Participant) Create(args map[string]string, r *http.Request) (interface{}, error) { if r.Method == "GET" { participant := new(Participant) @@ -110,7 +105,7 @@ func (model *Participant) Read(args map[string]string, r *http.Request) (interfa id := args["id"] - if err := DB().Preload("Responses").Preload("Contests").First(&participant, id).Error; err != nil { + if err := DB().Preload("User").Preload("Responses").Preload("Contests").First(&participant, id).Error; err != nil { return nil, err } @@ -187,6 +182,9 @@ func (model *Participant) Delete(args map[string]string, r *http.Request) (inter } func CreateParticipant(participant *Participant) (*Participant, error) { + if err := DB().Where(participant.ContestIDs).Find(&participant.Contests).Error; err != nil { + return nil, err + } if err := DB().Create(participant).Error; err != nil { return nil, err } diff --git a/orm/user.go b/orm/user.go new file mode 100644 index 00000000..83f36cfa --- /dev/null +++ b/orm/user.go @@ -0,0 +1,147 @@ +package orm + +import ( + "net/http" + + "git.andreafazzi.eu/andrea/oef/renderer" + "github.com/jinzhu/gorm" + + "github.com/sethvargo/go-password/password" +) + +type User struct { + gorm.Model + + Username string + Password string + Role string +} + +func (model *User) GetID() uint { return model.ID } + +func (model *User) String() string { + return "" // Please implement this. +} + +func genPassword() (string, error) { + password, err := password.Generate(8, 2, 0, false, true) + if err != nil { + return "", err + } + return password, nil +} + +func (model *User) BeforeSave(tx *gorm.DB) error { + if model.Password == "" { + password, err := genPassword() + if err != nil { + return err + } + model.Password = password + } + return nil +} + +func (model *User) Create(args map[string]string, r *http.Request) (interface{}, error) { + if r.Method == "GET" { + user := new(User) + // if err := DB().Find(&user.AllContests).Error; err != nil { + // return nil, err + // } + return user, nil + } else { + user := new(User) + err := renderer.Decode(user, r) + if err != nil { + return nil, err + } + user, err = CreateUser(user) + if err != nil { + return nil, err + } + return user, nil + } +} + +func (model *User) Read(args map[string]string, r *http.Request) (interface{}, error) { + var user User + + id := args["id"] + + if err := DB(). /*.Preload("Something")*/ First(&user, id).Error; err != nil { + return nil, err + } + + return &user, nil +} + +func (model *User) ReadAll(args map[string]string, r *http.Request) (interface{}, error) { + var users []*User + if err := DB(). /*.Preload("Something")*/ Order("created_at").Find(&users).Error; err != nil { + return nil, err + } + return users, nil +} + +func (model *User) Update(args map[string]string, r *http.Request) (interface{}, error) { + if r.Method == "GET" { + result, err := model.Read(args, r) + if err != nil { + return nil, err + } + + user := result.(*User) + + // if err := DB().Find(&user.AllElements).Error; err != nil { + // return nil, err + // } + + // user.SelectedElement = make(map[uint]string) + // user.SelectedElement[user.ElementID] = "selected" + + return user, nil + } else { + user, err := model.Read(args, nil) + if err != nil { + return nil, err + } + err = renderer.Decode(user, r) + if err != nil { + return nil, err + } + _, err = SaveUser(user) + if err != nil { + return nil, err + } + user, err = model.Read(args, nil) + if err != nil { + return nil, err + } + return user.(*User), nil + } +} + +func (model *User) Delete(args map[string]string, r *http.Request) (interface{}, error) { + user, err := model.Read(args, nil) + if err != nil { + return nil, err + } + if err := DB().Unscoped().Delete(user.(*User)).Error; err != nil { + return nil, err + } + return user.(*User), nil +} + +func CreateUser(user *User) (*User, error) { + if err := DB().Create(user).Error; err != nil { + return nil, err + } + return user, nil +} + +func SaveUser(user interface{}) (interface{}, error) { + if err := DB(). /*.Omit("Something")*/ Save(user).Error; err != nil { + return nil, err + } + return user, nil +} diff --git a/templates/participants_add_update.html.tpl b/templates/participants_add_update.html.tpl index 11098d1b..7d4c860e 100644 --- a/templates/participants_add_update.html.tpl +++ b/templates/participants_add_update.html.tpl @@ -29,7 +29,10 @@ {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Lastname") "update" $update}} - + + {{$options := ` { name: "FiscalCode",id: "participant_fiscalcode",label: "Codice fiscale del partecipante",placeholder: "Inserire il codice fiscale",type: "text",required: "true"} `}} + {{template "input" dict "options" ($options|yaml) "value" (.Data|field "FiscalCode") "update" $update}} + {{$options := ` { name: "contest_ids", id: "contest_ids", label: "Gare a cui il partecipante è iscritto", title: "Seleziona le gare", multiple: "true"}`}} {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllContests") "selected" (.Data|field "SelectedContest") "update" $update "form" $form}} diff --git a/templates/participants_show.html.tpl b/templates/participants_show.html.tpl index eb0a38e8..92507f37 100644 --- a/templates/participants_show.html.tpl +++ b/templates/participants_show.html.tpl @@ -7,10 +7,10 @@

Informazioni generali

- Il nome utente del partecipante è {{.Data.Username}} + Il nome utente del partecipante è {{if .Data.User}}{{.Data.User.Username}}{{end}}

- La sua password è {{.Data.Password}} + La sua password è {{if .Data.User}}{{.Data.User.Password}}{{end}}

diff --git a/templates/users.html.tpl b/templates/users.html.tpl new file mode 100644 index 00000000..fd10a6a4 --- /dev/null +++ b/templates/users.html.tpl @@ -0,0 +1,33 @@ +{{ define "content" }} + + +
+{{$options := ` + title: "Users" + buttonTitle: "Crea nuovo User" + `}} + + {{template "read_all_header" dict "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "User")}} + {{template "search_input"}} + + {{if not .}} + {{template "display_no_elements"}} + {{else}} + + +
+ + +{{ end }} diff --git a/templates/users_add_update.html.tpl b/templates/users_add_update.html.tpl new file mode 100644 index 00000000..299c7616 --- /dev/null +++ b/templates/users_add_update.html.tpl @@ -0,0 +1,28 @@ +{{ define "content" }} +
+ +{{$update := .Options.Get "update"}} + +{{if $update}} + +{{template "breadcrumb" toSlice "Users" (all "User") (.Data|string) (.Data.ID|show "User") "Aggiorna" "current"}} +{{else}} +{{template "breadcrumb" toSlice "Users" (all "User") "Aggiungi" "current"}} +{{end}} + +{{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo ELEMENTO" "updateTitle" (printf "Aggiorna ELEMENTO %s" (.Data|string))}} +{{$form := "form_add_update"}} +
+ + {{$options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "User" } `}} + {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}} + +
+ +
+{{ end }} diff --git a/templates/users_show.html.tpl b/templates/users_show.html.tpl new file mode 100644 index 00000000..1c15b65a --- /dev/null +++ b/templates/users_show.html.tpl @@ -0,0 +1,27 @@ +{{ define "content" }} + +
+ + {{template "breadcrumb" toSlice "ELEMENTS" (all "User") (.Data|string) "current"}} + {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "User") "deletePath" (.Data.ID|delete "User")}} + +

GENERAL SECTION

+ +
+
+ + {{$options := ` + title: "RELATIONS" + model: "MODEL" + icon: "ICON_CLASS" + `}} + + {{$noElements := "NO ELEMENTS"}} + {{template "relation_list" dict "options" ($options|yaml) "data" .Data.RELATIONS "noElements" $noElements}} + +
+
+ +
+ +{{ end }}