Andrea Fazzi 5 жил өмнө
parent
commit
fb35b4fb81
100 өөрчлөгдсөн 3116 нэмэгдсэн , 3917 устгасан
  1. 1 0
      .gitignore
  2. 22 7
      Dockerfile
  3. 0 5
      Godeps/Readme
  4. 8 2
      Makefile
  5. 23 0
      client/client.go
  6. 3 3
      compose/karmen/docker-compose.yml
  7. 68 0
      compose/karmen/docker-compose_outside_docker.yml
  8. 2 2
      compose/karmen/ldap/Dockerfile
  9. 6 26
      compose/karmen/ldap/bootstrap/ldif/foo.org.ldif
  10. 0 4
      compose/karmen/sql/karmen_test.sql
  11. 50 0
      config/config_outside_docker.yaml
  12. 4 4
      cron/sync/sync.go
  13. 2 2
      cron/sync/test/sync_test.go
  14. 5 5
      generator/generators/department/department.go
  15. 8 8
      generator/generators/list/list.go
  16. 29 0
      go.mod
  17. 188 0
      go.sum
  18. 76 58
      handlers/handlers.go
  19. 0 0
      ldap/ldap.go
  20. 34 7
      main.go
  21. 126 121
      orm/activity.go
  22. 83 87
      orm/administrative.go
  23. 77 136
      orm/class.go
  24. 74 90
      orm/department.go
  25. 82 103
      orm/document.go
  26. 44 0
      orm/file.go
  27. 79 108
      orm/group.go
  28. 43 48
      orm/job.go
  29. 60 220
      orm/mappings.go
  30. 236 0
      orm/mappings.go.old
  31. 55 90
      orm/office.go
  32. 1 29
      orm/orm.go
  33. 2 2
      orm/orm_test.go
  34. 89 117
      orm/student.go
  35. 70 98
      orm/subject.go
  36. 58 81
      orm/teacher.go
  37. 230 0
      renderer/funcmap.go
  38. 8 23
      renderer/renderer.go
  39. 1 1
      template_generator/main.go
  40. 17 40
      templates/activities.html.tpl
  41. 43 151
      templates/activities_add_update.html.tpl
  42. 33 103
      templates/activities_show.html.tpl
  43. 16 32
      templates/administratives.html.tpl
  44. 35 94
      templates/administratives_add_update.html.tpl
  45. 16 61
      templates/administratives_show.html.tpl
  46. 27 39
      templates/classes.html.tpl
  47. 42 95
      templates/classes_add_update.html.tpl
  48. 28 79
      templates/classes_show.html.tpl
  49. 20 40
      templates/departments.html.tpl
  50. 21 60
      templates/departments_add_update.html.tpl
  51. 36 83
      templates/departments_show.html.tpl
  52. 17 28
      templates/documents.html.tpl
  53. 41 78
      templates/documents_add_update.html.tpl
  54. 23 67
      templates/documents_show.html.tpl
  55. 17 28
      templates/groups.html.tpl
  56. 23 65
      templates/groups_add_update.html.tpl
  57. 12 47
      templates/groups_show.html.tpl
  58. 19 22
      templates/jobs.html.tpl
  59. 18 47
      templates/jobs_show.html.tpl
  60. 13 0
      templates/layout/add_update_header.html.tpl
  61. 2 2
      templates/layout/base.html.tpl
  62. 17 0
      templates/layout/breadcrumb.html.tpl
  63. 11 0
      templates/layout/checkbox.html.tpl
  64. 6 0
      templates/layout/create_button.html.tpl
  65. 8 0
      templates/layout/delete_button.html.tpl
  66. 3 0
      templates/layout/display_no_elements.html.tpl
  67. 11 0
      templates/layout/input.html.tpl
  68. 14 0
      templates/layout/read_all_header.html.tpl
  69. 56 0
      templates/layout/relation_list.html.tpl
  70. 7 0
      templates/layout/search_input.html.tpl
  71. 25 0
      templates/layout/select.html.tpl
  72. 15 0
      templates/layout/show_header.html.tpl
  73. 13 0
      templates/layout/small.html.tpl
  74. 10 0
      templates/layout/submit_cancel_button.html.tpl
  75. 6 0
      templates/layout/update_button.html.tpl
  76. 18 34
      templates/offices.html.tpl
  77. 20 49
      templates/offices_add_update.html.tpl
  78. 24 63
      templates/offices_show.html.tpl
  79. 19 33
      templates/students.html.tpl
  80. 30 89
      templates/students_add_update.html.tpl
  81. 20 56
      templates/students_show.html.tpl
  82. 19 35
      templates/subjects.html.tpl
  83. 20 59
      templates/subjects_add_update.html.tpl
  84. 22 46
      templates/subjects_show.html.tpl
  85. 15 39
      templates/teachers.html.tpl
  86. 35 73
      templates/teachers_add_update.html.tpl
  87. 75 125
      templates/teachers_show.html.tpl
  88. 0 0
      util/fileutil/fileutil.go
  89. 22 0
      util/fileutil/fileutil_test.go
  90. 0 0
      util/libreoffice/libreoffice.go
  91. 35 0
      util/libreoffice/libreoffice_test.go
  92. 0 0
      util/pandoc/pandoc.go
  93. 28 0
      util/pandoc/pandoc_test.go
  94. 0 0
      util/template/html.go
  95. 0 0
      util/template/template.go
  96. 46 0
      util/template/template_test.go
  97. 0 21
      vendor/github.com/auth0/go-jwt-middleware/LICENSE
  98. 0 207
      vendor/github.com/auth0/go-jwt-middleware/README.md
  99. 0 236
      vendor/github.com/auth0/go-jwt-middleware/jwtmiddleware.go
  100. 0 4
      vendor/github.com/dgrijalva/jwt-go/.gitignore

+ 1 - 0
.gitignore

@@ -1,2 +1,3 @@
 **/*~
 core
+output

+ 22 - 7
Dockerfile

@@ -1,26 +1,41 @@
-FROM golang
+FROM golang:latest
 
 RUN apt-get update -qq && apt-get install -y netcat
 RUN apt-get install -y -q libsm6 libcups2 libcairo2 libdbus-1-3 libxinerama1
 
+
+## Download pandoc and LibreOffice
+
 WORKDIR /tmp
 RUN wget https://github.com/jgm/pandoc/releases/download/2.7.3/pandoc-2.7.3-1-amd64.deb
 RUN wget https://download.documentfoundation.org/libreoffice/stable/6.3.3/deb/x86_64/LibreOffice_6.3.3_Linux_x86-64_deb.tar.gz
 RUN tar xvzf LibreOffice_6.3.3_Linux_x86-64_deb.tar.gz
 
+
 WORKDIR LibreOffice_6.3.3.2_Linux_x86-64_deb/DEBS
+
+## Install LibreOffice
+
 RUN dpkg -i *.deb
 RUN ln -s /usr/local/bin/libreoffice6.2 /usr/local/bin/libreoffice
+
+## Install pandoc
+
 WORKDIR /tmp
 RUN dpkg -i ./pandoc-2.7.3-1-amd64.deb
     
-ENV PATH=$GOPATH/bin:$PATH
-RUN mkdir -p $GOPATH/src/gogs.carducci-dante.gov.it/karmen
-ADD . $GOPATH/src/gogs.carducci-dante.gov.it/karmen/core
-COPY config/config.yaml /go/src/gogs.carducci-dante.gov.it/karmen/core/config/
-WORKDIR $GOPATH/src/gogs.carducci-dante.gov.it/karmen/core
+ENV PATH=$PATH:/src/karmen/core
+RUN mkdir -p /src/karmen/core/
+ADD . /src/karmen/core
+
+COPY config/config.yaml /src/karmen/core/config/
+
+WORKDIR /src/karmen/core/
+
 RUN go build -o server *.go
-ENTRYPOINT ["/go/src/gogs.carducci-dante.gov.it/karmen/core/server"]
+ENTRYPOINT ["/src/karmen/core/server"]
+
 # VOLUME /go/src/gogs.carducci-dante.gov.it/karmen/core/config
 # VOLUME /go/src/gogs.carducci-dante.gov.it/karmen/core/documents
+
 EXPOSE 3000

+ 0 - 5
Godeps/Readme

@@ -1,5 +0,0 @@
-This directory tree is generated automatically by godep.
-
-Please do not edit.
-
-See https://github.com/tools/godep for more information.

+ 8 - 2
Makefile

@@ -1,6 +1,6 @@
 PHONY: all
 
-run:
+run_with_docker:
 	docker-compose -f compose/karmen/docker-compose.yml down
 	docker-compose -f compose/karmen/docker-compose.yml up --build -d
 	echo "Uploading files to the cloud..."
@@ -14,4 +14,10 @@ run:
 	curl -u admin:password http://localhost:8080/remote.php/webdav/Documents/Department/template.md -X PUT --data-binary @"./generator/generators/department/example_1/resources/template.md"
 	curl -u admin:password http://localhost:8080/remote.php/webdav/Documents/Department/reference.odt -X PUT --data-binary @"./generator/generators/department/example_1/resources/reference.odt"
 
-all: run
+run_outside_docker:
+	killall main || echo "Process was not running."
+	docker-compose -f compose/karmen/docker-compose_outside_docker.yml down
+	docker-compose -f compose/karmen/docker-compose_outside_docker.yml up -d db
+	go run main.go --config=config/config_outside_docker.yaml &
+
+all: run_with_docker

+ 23 - 0
vendor/gogs.carducci-dante.gov.it/karmen/client/client.go → client/client.go

@@ -588,3 +588,26 @@ func (c *Client) AddTeacher(teacher *orm.Teacher) error {
 	}
 	return nil
 }
+
+func (c *Client) GetGroups() ([]*orm.Group, error) {
+	var (
+		response renderer.JsonResponse
+		groups   []*orm.Group
+	)
+
+	data, err := c.SendRequest("GET", "/api/groups?format=json", nil)
+	if err != nil {
+		return nil, err
+	}
+	if err := json.Unmarshal(data, &response); err != nil {
+		return nil, err
+	}
+
+	if string(response.Error) != "" {
+		return nil, errors.New(string(response.Error))
+	}
+	if err := json.Unmarshal(response.Result, &groups); err != nil {
+		return nil, err
+	}
+	return groups, nil
+}

+ 3 - 3
compose/karmen/docker-compose.yml

@@ -25,9 +25,9 @@ services:
   ldap_server:
     restart: always
     build: ./ldap
-    # command: --loglevel debug
-    domainname: foo.org
-    hostname: foo.org
+    command: --loglevel debug
+    # domainname: foo.org
+    # hostname: foo.org
     ports:
       - 389:389
     environment:

+ 68 - 0
compose/karmen/docker-compose_outside_docker.yml

@@ -0,0 +1,68 @@
+version: "3.3"
+
+services:
+
+  app:
+    build: ../../
+    ports:
+      - 3000:3000
+    environment:
+      - DB_HOST=db
+      - DB_PORT=3306
+    volumes:
+      - /etc/localtime:/etc/localtime:ro
+
+  db:
+    image: mariadb
+    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
+    restart: always
+    volumes:
+      - db:/var/lib/mysql
+      - ./sql:/docker-entrypoint-initdb.d
+    env_file:
+      - db.env
+    ports:
+      - 3307:3306
+
+  ldap_server:
+    restart: always
+    build: ./ldap
+    # command: --loglevel debug
+    domainname: foo.org
+    hostname: foo.org
+    ports:
+      - 389:389
+    environment:
+      - LDAP_ORGANISATION=Foo Organization
+      - LDAP_DOMAIN=foo.org
+      - LDAP_ADMIN_PASSWORD=admin
+
+  ldap_web_client:
+    restart: always
+    image: osixia/phpldapadmin
+    environment:
+      - PHPLDAPADMIN_LDAP_HOSTS=ldap_server
+      - PHPLDAPADMIN_HTTPS=false
+    ports:
+      - 3001:80
+
+  nextcloud_server:
+    restart: always
+    image: nextcloud:17
+    container_name: nextcloud_server
+    environment:
+      - SQLITE_DATABASE=nextcloud
+      - NEXTCLOUD_ADMIN_USER=admin
+      - NEXTCLOUD_ADMIN_PASSWORD=password
+      - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud_server
+    # volumes:
+    #   - ./data:/var/www/html/data
+    #   - ./config:/var/www/html/config
+    ports:
+      - 8080:80
+
+volumes:
+  db:
+  
+
+      

+ 2 - 2
compose/karmen/ldap/Dockerfile

@@ -1,9 +1,9 @@
-FROM osixia/openldap:1.2.1
+FROM osixia/openldap:1.3.0
 MAINTAINER Andrea fazzi <andrea.fazzi@carducci-dante.gov.it>
 
 ADD bootstrap /container/service/slapd/assets/config/bootstrap
 RUN rm /container/service/slapd/assets/config/bootstrap/schema/mmc/mail.schema
 
 # ADD certs /container/service/slapd/assets/certs
-#ADD environment /container/environment/01-custom
+# ADD environment /container/environment/01-custom
 

+ 6 - 26
compose/karmen/ldap/bootstrap/ldif/foo.org.ldif

@@ -6,6 +6,12 @@ objectclass: organizationalUnit
 objectclass: top
 ou: Groups
 
+dn: ou=People,dc=foo,dc=org
+changetype: add
+objectclass: organizationalUnit
+objectclass: top
+ou: People
+
 ## Mailing lists
 
 dn: ou=Mailing Lists,ou=Groups,dc=foo,dc=org
@@ -85,26 +91,6 @@ gidNumber: 6001
 objectclass: posixGroup
 objectclass: top
 
-dn: ou=Segreterie,ou=Groups,dc=foo,dc=org
-changetype: add
-ou: Segreterie
-objectclass: organizationalUnit
-objectclass: top
-
-dn: cn=Segreteria del personale,ou=Segreterie,ou=Groups,dc=foo,dc=org
-changetype: add
-cn: Segreteria del personale
-gidNumber: 6015
-objectclass: posixGroup
-objectclass: top
-
-dn: cn=Segreteria didattica,ou=Segreterie,ou=Groups,dc=foo,dc=org
-changetype: add
-cn: Segreteria didattica
-gidNumber: 6016
-objectclass: posixGroup
-objectclass: top
-
 dn: ou=Dipartimenti,ou=Groups,dc=foo,dc=org
 changetype: add
 ou: Dipartimenti
@@ -127,12 +113,6 @@ objectclass: top
 
 ## People
 
-dn: ou=People,dc=foo,dc=org
-changetype: add
-objectclass: organizationalUnit
-objectclass: top
-ou: People
-
 dn: ou=Teachers,ou=People,dc=foo,dc=org
 changetype: add
 objectclass: organizationalUnit

+ 0 - 4
compose/karmen/sql/karmen_test.sql

@@ -283,8 +283,6 @@ INSERT INTO `files` VALUES (1,'2019-10-02 10:34:03','2019-10-02 10:34:03',NULL,'
 UNLOCK TABLES;
 
 --
-=======
->>>>>>> groups
 -- Table structure for table `group_teachers`
 --
 
@@ -307,8 +305,6 @@ LOCK TABLES `group_teachers` WRITE;
 
 INSERT INTO `group_teachers` VALUES (1,3),(1,6),(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),(3,9),(3,10),(3,11),(4,2),(4,3),(4,4),(4,7);
 
-INSERT INTO `group_teachers` VALUES (1,3),(1,6);
-
 /*!40000 ALTER TABLE `group_teachers` ENABLE KEYS */;
 UNLOCK TABLES;
 

+ 50 - 0
config/config_outside_docker.yaml

@@ -0,0 +1,50 @@
+url: "http://localhost:3000"
+domain: "foo.org"
+log_level: 2
+
+keys:
+  cookie_store_key: "something-very-secret"
+  jwt_signing_key: "secret"
+  
+orm:
+  connection: "karmen:karmen@tcp(localhost:3307)/karmen_dev"
+  options: "charset=utf8&parseTime=True&loc=Local"
+  automigrate: true
+  regenerate: false
+
+ldap:
+  host: "ldap_server:389"
+  admin_cn: "admin"
+  admin_password: "admin"
+  people_dn: "ou=People"
+  teachers_dn: "ou=People"
+  groups_dn: "ou=Groups"
+  maildir_base_parh: "/var/mail/foo.org"
+  mail_quota: "10240"
+  first_uid_number: "5000"
+  mail_gid_number: "5000"
+
+admin:
+  username: "admin"
+  password: "admin"
+
+sync:
+  safe_run: false
+  verbose: true
+  send_mail: false
+  schedule: "@every 10m"
+  teachers_search_base: "ou=Teachers,ou=People,dc=foo,dc=org"
+  administratives_search_base: "ou=Administratives,ou=People,dc=foo,dc=org"
+  teachers_group: "cn=Teachers"
+  administratives_group: "cn=Administratives"
+  teachers_ml: "cn=Teachers,ou=Mailing Lists"
+  departments_coordinators_group: "cn=Coordinatori Dipartimenti"
+  departments_coordinators_ml: "cn=Coordinatori Dipartimenti,ou=Mailing Lists"
+
+cloud:
+  username: admin
+  password: password
+  url: "http://nextcloud_server/remote.php/webdav/"
+  
+documents:
+  output_path: "output"

+ 4 - 4
cron/sync/sync.go

@@ -15,12 +15,12 @@ import (
 
 	"text/template"
 
-	karmen_client "gogs.carducci-dante.gov.it/karmen/client"
+	karmen_client "gogs.carducci-dante.gov.it/karmen/core/client"
 	"gogs.carducci-dante.gov.it/karmen/core/config"
+	karmen_ldap "gogs.carducci-dante.gov.it/karmen/core/ldap"
 	"gogs.carducci-dante.gov.it/karmen/core/orm"
-	karmen_ldap "gogs.carducci-dante.gov.it/karmen/ldap"
 
-	tpl_util "gogs.carducci-dante.gov.it/karmen/util/template"
+	tpl_util "gogs.carducci-dante.gov.it/karmen/core/util/template"
 )
 
 type entriesToDN []*ldap.Entry
@@ -399,7 +399,7 @@ func (syncJob *SyncJob) Run() {
 
 	for _, department := range departments {
 
-		departmentsCoordinators = append(departmentsCoordinators, &department.Coordinator)
+		departmentsCoordinators = append(departmentsCoordinators, department.Coordinator)
 
 		users := make([]orm.User, 0)
 		for _, teacher := range department.Teachers {

+ 2 - 2
cron/sync/test/sync_test.go

@@ -7,11 +7,11 @@ import (
 	"time"
 
 	"github.com/remogatto/prettytest"
-	karmen_client "gogs.carducci-dante.gov.it/karmen/client"
+	karmen_client "gogs.carducci-dante.gov.it/karmen/core/client"
 	"gogs.carducci-dante.gov.it/karmen/core/config"
 	"gogs.carducci-dante.gov.it/karmen/core/cron/sync"
+	karmen_ldap "gogs.carducci-dante.gov.it/karmen/core/ldap"
 	"gogs.carducci-dante.gov.it/karmen/core/orm"
-	karmen_ldap "gogs.carducci-dante.gov.it/karmen/ldap"
 )
 
 type testSuite struct {

+ 5 - 5
generator/generators/department/department.go

@@ -12,15 +12,15 @@ import (
 
 	"github.com/remogatto/cloud"
 
-	karmen_client "gogs.carducci-dante.gov.it/karmen/client"
+	karmen_client "gogs.carducci-dante.gov.it/karmen/core/client"
 	"gogs.carducci-dante.gov.it/karmen/core/generator"
 	"gogs.carducci-dante.gov.it/karmen/core/generator/funcmap"
 	"gogs.carducci-dante.gov.it/karmen/core/generator/logger"
 	"gogs.carducci-dante.gov.it/karmen/core/orm"
-	"gogs.carducci-dante.gov.it/karmen/util/fileutil"
-	"gogs.carducci-dante.gov.it/karmen/util/libreoffice"
-	"gogs.carducci-dante.gov.it/karmen/util/pandoc"
-	tpl_util "gogs.carducci-dante.gov.it/karmen/util/template"
+	"gogs.carducci-dante.gov.it/karmen/core/util/fileutil"
+	"gogs.carducci-dante.gov.it/karmen/core/util/libreoffice"
+	"gogs.carducci-dante.gov.it/karmen/core/util/pandoc"
+	tpl_util "gogs.carducci-dante.gov.it/karmen/core/util/template"
 )
 
 type Data struct {

+ 8 - 8
generator/generators/list/list.go

@@ -1,25 +1,25 @@
 package list
 
 import (
-	"fmt"
 	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
+	"strconv"
 	"sync"
 	"text/template"
 	"time"
 
 	"github.com/remogatto/cloud"
 
-	karmen_client "gogs.carducci-dante.gov.it/karmen/client"
+	karmen_client "gogs.carducci-dante.gov.it/karmen/core/client"
 	"gogs.carducci-dante.gov.it/karmen/core/config"
 	"gogs.carducci-dante.gov.it/karmen/core/generator/funcmap"
 	"gogs.carducci-dante.gov.it/karmen/core/orm"
-	"gogs.carducci-dante.gov.it/karmen/util/fileutil"
-	"gogs.carducci-dante.gov.it/karmen/util/libreoffice"
-	"gogs.carducci-dante.gov.it/karmen/util/pandoc"
-	tpl_util "gogs.carducci-dante.gov.it/karmen/util/template"
+	"gogs.carducci-dante.gov.it/karmen/core/util/fileutil"
+	"gogs.carducci-dante.gov.it/karmen/core/util/libreoffice"
+	"gogs.carducci-dante.gov.it/karmen/core/util/pandoc"
+	tpl_util "gogs.carducci-dante.gov.it/karmen/core/util/template"
 )
 
 type Data struct {
@@ -185,7 +185,7 @@ func (generator *ListGenerator) Run() {
 	)
 
 	if generator.JobID > 0 {
-		outputPath = filepath.Join(generator.Config.Documents.OutputPath, fmt.Sprintf("%d/%d", job.DocumentID, job.ID))
+		outputPath = filepath.Join(generator.Config.Documents.OutputPath, strconv.Itoa(int(job.ID)))
 		cloudPath = job.Document.CloudPath
 	} else {
 		outputPath = "output"
@@ -201,7 +201,7 @@ func (generator *ListGenerator) Run() {
 	if generator.JobID > 0 {
 		job.End = time.Now()
 
-		job.Files = append(job.Files, &orm.File{Path: "elenco_docenti.pdf"})
+		job.Files = append(job.Files, &orm.File{Path: filepath.Join(outputPath, "elenco_docenti.pdf")})
 
 		err = generator.kaClient.UpdateJob(job)
 		if err != nil {

+ 29 - 0
go.mod

@@ -0,0 +1,29 @@
+module gogs.carducci-dante.gov.it/karmen/core
+
+go 1.13
+
+require (
+	github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/gobwas/glob v0.2.3
+	github.com/gocarina/gocsv v0.0.0-20190927101021-3ecffd272576
+	github.com/gorilla/handlers v1.4.2
+	github.com/gorilla/mux v1.7.3
+	github.com/gorilla/schema v1.1.0
+	github.com/gorilla/sessions v1.2.0
+	github.com/jinzhu/gorm v1.9.11
+	github.com/jinzhu/inflection v1.0.0
+	github.com/kr/pretty v0.1.0 // indirect
+	github.com/remogatto/cloud v0.0.0-20191009090237-dcf2638e6bc7
+	github.com/remogatto/prettytest v0.0.0-20161014102941-8b5d7bfe964e
+	github.com/remogatto/slicediff v0.0.0-20190903082355-d19e1ebfb964
+	github.com/robfig/cron v1.2.0
+	github.com/sethvargo/go-password v0.1.3
+	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
+	github.com/urfave/negroni v1.0.0 // indirect
+	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
+	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
+	gopkg.in/ldap.v2 v2.5.1
+	gopkg.in/yaml.v2 v2.2.4
+)

+ 188 - 0
go.sum

@@ -0,0 +1,188 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
+cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGmxWl5kONC5icxwqV899dkf4VjOScbxLpllEnw=
+github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gocarina/gocsv v0.0.0-20190927101021-3ecffd272576 h1:nDMaZgjBNKhsVJEWN1wAnqUJWwWGxFdDlt8U17KqMQ8=
+github.com/gocarina/gocsv v0.0.0-20190927101021-3ecffd272576/go.mod h1:/oj50ZdPq/cUjA02lMZhijk5kR31SEydKyqah1OgBuo=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
+github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
+github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
+github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
+github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/remogatto/cloud v0.0.0-20191009090237-dcf2638e6bc7 h1:0d+RhTaGBAwgRVW/FDCsYTg2dUMvIZtn8KNhZk2crZg=
+github.com/remogatto/cloud v0.0.0-20191009090237-dcf2638e6bc7/go.mod h1:84Ak6wMU0H33hglQdkwyFJob+r0lRiQhtBiekhNrgL8=
+github.com/remogatto/prettytest v0.0.0-20161014102941-8b5d7bfe964e h1:uQy6ob81XHCWTGzVTbVHnMDjc4jRKaIXuaNSsX5thCU=
+github.com/remogatto/prettytest v0.0.0-20161014102941-8b5d7bfe964e/go.mod h1:jOEnp79oIHy5cvQSHeLcgVJk1GHOOHJHQWps/d1N5Yo=
+github.com/remogatto/slicediff v0.0.0-20190903082355-d19e1ebfb964 h1:nr+J2/lD7hrKuUU37aaW5GMjnTN9SWcPOZm8bOhw7lk=
+github.com/remogatto/slicediff v0.0.0-20190903082355-d19e1ebfb964/go.mod h1:hYhX1i+/93J3gTMZFUL96Mf/45LmfRZTunoLYPwaHTA=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/sethvargo/go-password v0.1.3 h1:18KkbGDkw8SuzeohAbWqBLNSfRQblVwEHOLbPa0PvWM=
+github.com/sethvargo/go-password v0.1.3/go.mod h1:2tyaaoHK/AlXwh5WWQDYjqQbHcq4cjPj5qb/ciYvu/Q=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
+github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
+gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 76 - 58
handlers/handlers.go

@@ -6,9 +6,12 @@ import (
 	"io/ioutil"
 	"log"
 	"net/http"
+	"path"
 	"path/filepath"
+	"reflect"
 	"runtime/debug"
 	"strconv"
+	"strings"
 
 	"gogs.carducci-dante.gov.it/karmen/core/config"
 	"gogs.carducci-dante.gov.it/karmen/core/orm"
@@ -18,6 +21,7 @@ import (
 	jwt "github.com/dgrijalva/jwt-go"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/sessions"
+	"github.com/jinzhu/inflection"
 )
 
 type User struct {
@@ -50,20 +54,6 @@ var (
 		},
 		SigningMethod: jwt.SigningMethodHS256,
 	})
-
-	models = []string{
-		"teachers",
-		"classes",
-		"subjects",
-		"departments",
-		"activities",
-		"students",
-		"offices",
-		"administratives",
-		"documents",
-		"jobs",
-		"groups",
-	}
 )
 
 func (pp PathPattern) RedirectPath(model string, id ...uint) string {
@@ -77,56 +67,63 @@ func (pp PathPattern) Path(model string) string {
 	return fmt.Sprintf(pp.PathPattern, model)
 }
 
-// Generate CRUD handlers
-func generateHandler(r *mux.Router, model string) {
+func modelName(s interface{}) string {
+	if t := reflect.TypeOf(s); t.Kind() == reflect.Ptr {
+		return t.Elem().Name()
+	} else {
+		return t.Name()
+	}
+}
+
+func pluralizedModelName(s interface{}) string {
+	return inflection.Plural(strings.ToLower(modelName(s)))
+}
+
+// Generate CRUD handlers for models
+func generateHandler(r *mux.Router, model interface{}) {
 	var (
 		patterns []PathPattern = []PathPattern{
 			PathPattern{"/%s", "", []string{"GET"}},
 			PathPattern{"/%s/{id}", "", []string{"GET"}},
-			PathPattern{"/%s/add/", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
+			PathPattern{"/%s/create/", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
 			PathPattern{"/%s/{id}/update", "/%s/%d?format=html&tpl_layout=base&tpl_content=%s_show", []string{"GET", "POST"}},
 			PathPattern{"/%s/{id}/delete", "/%s?format=html&tpl_layout=base&tpl_content=%s", []string{"DELETE"}},
 		}
 
-		apiPatterns []PathPattern = []PathPattern{
-			PathPattern{"/api/%s", "", []string{"GET"}},
-			PathPattern{"/api/%s/{id}", "", []string{"GET"}},
-			PathPattern{"/api/%s/add/", "", []string{"GET", "POST"}},
-			PathPattern{"/api/%s/{id}/update", "", []string{"GET", "POST"}},
-			PathPattern{"/api/%s/{id}/delete", "", []string{"DELETE"}},
-		}
-
-		executePatterns []PathPattern = []PathPattern{
-			PathPattern{"/%s/{id}/execute", "", []string{"GET"}},
-		}
+		apiPatterns []PathPattern
 
 		filePatterns []PathPattern = []PathPattern{
 			PathPattern{"/%s/{id}/files/{filename}", "", []string{"GET"}},
 		}
 	)
-	for _, pattern := range patterns {
-		r.Handle(pattern.Path(model), jwtCookie.Handler(recoverHandler(modelHandler(model, pattern)))).Methods(pattern.Methods...)
+
+	// Generate API patterns prefixing "api" path
+
+	for _, p := range patterns {
+		apiPatterns = append(apiPatterns, PathPattern{path.Join("/", "api", p.PathPattern), "", p.Methods})
 	}
 
-	for _, pattern := range apiPatterns {
-		r.Handle(pattern.Path(model), jwtHeader.Handler(recoverHandler(modelHandler(model, pattern)))).Methods(pattern.Methods...)
+	// Install standard paths
+
+	for _, pattern := range patterns {
+		r.Handle(pattern.Path(pluralizedModelName(model)), jwtCookie.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
 	}
 
-	if model == "documents" {
-		for _, pattern := range executePatterns {
-			r.Handle(pattern.Path(model), jwtCookie.Handler(recoverHandler(modelHandler(model, pattern)))).Methods(pattern.Methods...)
-		}
+	// Install API paths
+
+	for _, pattern := range apiPatterns {
+		r.Handle(pattern.Path(pluralizedModelName(model)), jwtHeader.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
 	}
 
-	if model == "jobs" {
+	if modelName(model) == "Job" {
 		for _, pattern := range filePatterns {
-			r.Handle(pattern.Path(model), jwtCookie.Handler(recoverHandler(modelHandler(model, pattern)))).Methods(pattern.Methods...)
+			r.Handle(pattern.Path(pluralizedModelName(model)), jwtCookie.Handler(recoverHandler(modelHandler(pluralizedModelName(model), pattern)))).Methods(pattern.Methods...)
 		}
 	}
 
 }
 
-func Handlers() *mux.Router {
+func Handlers(models []interface{}) *mux.Router {
 	r := mux.NewRouter()
 
 	// Authentication
@@ -138,16 +135,20 @@ func Handlers() *mux.Router {
 
 	r.Handle("/", jwtCookie.Handler(recoverHandler(homeHandler())))
 
-	// Generate model handlers
+	// Generate CRUD handlers
 
 	for _, model := range models {
 		generateHandler(r, model)
 	}
 
+	r.Handle("/documents/{id}/execute", jwtCookie.Handler(recoverHandler(executeHandler()))).Methods("GET")
+
 	// Token handling
+
 	r.Handle("/get_token", tokenHandler())
 
 	// Static file server
+
 	r.PathPrefix("/").Handler(http.FileServer(http.Dir("./dist/")))
 
 	return r
@@ -198,11 +199,11 @@ func recoverHandler(next http.Handler) http.Handler {
 func get(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
 	format := r.URL.Query().Get("format")
 	getFn, err := orm.GetFunc(pattern.Path(model))
-
 	if err != nil {
+		log.Println("Error:", err)
 		respondWithError(w, r, err)
 	} else {
-		data, err := getFn(mux.Vars(r))
+		data, err := getFn(mux.Vars(r), r)
 		if err != nil {
 			renderer.Render[format](w, r, err)
 		} else {
@@ -212,20 +213,14 @@ func get(w http.ResponseWriter, r *http.Request, model string, pattern PathPatte
 
 }
 
-func respondWithError(w http.ResponseWriter, r *http.Request, err error) {
-	respFormat := renderer.GetContentFormat(r)
-	w.WriteHeader(http.StatusInternalServerError)
-	renderer.Render[respFormat](w, r, err)
-}
-
 func post(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
 	var (
-		data orm.IDer
+		data interface{}
 		err  error
 	)
 
 	respFormat := renderer.GetContentFormat(r)
-	postFn, err := orm.PostFunc(pattern.Path(model))
+	postFn, err := orm.GetFunc(pattern.Path(model))
 
 	if err != nil {
 		respondWithError(w, r, err)
@@ -238,10 +233,10 @@ func post(w http.ResponseWriter, r *http.Request, model string, pattern PathPatt
 				modelId, _ := strconv.Atoi(id)
 				http.Redirect(w, r, pattern.RedirectPath(model, uint(modelId)), http.StatusSeeOther)
 			} else {
-				http.Redirect(w, r, pattern.RedirectPath(model, data.GetID()), http.StatusSeeOther)
+				http.Redirect(w, r, pattern.RedirectPath(model, data.(orm.IDer).GetID()), http.StatusSeeOther)
 			}
 		} else {
-			renderer.Render[respFormat](w, r, data.GetID())
+			renderer.Render[respFormat](w, r, data.(orm.IDer).GetID())
 		}
 
 	}
@@ -250,15 +245,15 @@ func post(w http.ResponseWriter, r *http.Request, model string, pattern PathPatt
 
 func delete(w http.ResponseWriter, r *http.Request, model string, pattern PathPattern) {
 	var (
-		data orm.IDer
+		data interface{}
 		err  error
 	)
 
 	respFormat := renderer.GetContentFormat(r)
 
-	postFn, ok := orm.Post[pattern.Path(model)]
-	if !ok {
-		renderer.Render[r.URL.Query().Get("format")](w, r, fmt.Errorf("Can't find ORM function for path %s!", pattern.PathPattern))
+	postFn, err := orm.GetFunc(pattern.Path(model))
+	if err != nil {
+		renderer.Render[r.URL.Query().Get("format")](w, r, err)
 	}
 	data, err = postFn(mux.Vars(r), r)
 	if err != nil {
@@ -272,12 +267,22 @@ func delete(w http.ResponseWriter, r *http.Request, model string, pattern PathPa
 		w.Header().Set("Content-Type", "application/json")
 		json.NewEncoder(w).Encode(data)
 	} else {
-		renderer.Render[respFormat](w, r, data.GetID())
+		renderer.Render[respFormat](w, r, data.(orm.IDer).GetID())
 	}
 }
 
+func respondWithError(w http.ResponseWriter, r *http.Request, err error) {
+	respFormat := renderer.GetContentFormat(r)
+	w.WriteHeader(http.StatusInternalServerError)
+	renderer.Render[respFormat](w, r, err)
+}
+
 func modelHandler(model string, pattern PathPattern) http.Handler {
 	fn := func(w http.ResponseWriter, r *http.Request) {
+
+		// Replace "api" prefix
+		pattern.PathPattern = strings.Replace(pattern.PathPattern, "/api", "", -1)
+
 		switch r.Method {
 		case "GET":
 			get(w, r, model, pattern)
@@ -287,13 +292,26 @@ func modelHandler(model string, pattern PathPattern) http.Handler {
 
 		case "DELETE":
 			delete(w, r, model, pattern)
-			break
 		}
 	}
 
 	return http.HandlerFunc(fn)
 }
 
+func executeHandler() http.Handler {
+	fn := func(w http.ResponseWriter, r *http.Request) {
+		format := r.URL.Query().Get("format")
+		data, err := orm.GetDocumentExecute(mux.Vars(r), r)
+		if err != nil {
+			renderer.Render[format](w, r, err)
+		} else {
+			renderer.Render[format](w, r, data, r.URL.Query())
+		}
+
+	}
+	return http.HandlerFunc(fn)
+}
+
 func homeHandler() http.Handler {
 	fn := func(w http.ResponseWriter, r *http.Request) {
 		http.Redirect(w, r, "/teachers?format=html&tpl_layout=teachers&tpl_content=teachers", http.StatusSeeOther)

+ 0 - 0
vendor/gogs.carducci-dante.gov.it/karmen/ldap/ldap.go → ldap/ldap.go


+ 34 - 7
main.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"flag"
 	"fmt"
 	"log"
 	"net/http"
@@ -26,15 +27,32 @@ const (
 	RetryTimeInterval = 5
 )
 
-func main() {
+var (
+	db  *gorm.DB
+	err error
+
+	models = []interface{}{
+		&orm.Teacher{},
+		&orm.Department{},
+		&orm.Subject{},
+		&orm.Class{},
+		&orm.Student{},
+		&orm.Activity{},
+		&orm.Office{},
+		&orm.Administrative{},
+		&orm.Group{},
+		&orm.Document{},
+		&orm.Job{},
+		&orm.File{},
+	}
+)
 
-	var (
-		db  *gorm.DB
-		err error
-	)
+func main() {
+	configFile := flag.String("config", "config/config.yaml", "Load the given config file")
+	flag.Parse()
 
 	log.Println("Loading config file...")
-	err = config.ReadFile("config/config.yaml", config.Config)
+	err = config.ReadFile(*configFile, config.Config)
 	if err != nil {
 		panic(err)
 	}
@@ -63,6 +81,11 @@ func main() {
 		orm.AutoMigrate()
 	}
 
+	log.Println("Map models <-> handlers")
+	if err := orm.MapHandlers(models); err != nil {
+		panic(err)
+	}
+
 	generator.Generators = make(map[string]generator.Generator)
 
 	log.Println("Initialize ListGenerator")
@@ -72,6 +95,10 @@ func main() {
 	generator.Generators["department"] = department.NewDepartmentGenerator(&generator.Config{ConfigT: *config.Config})
 
 	files, err := filepath.Glob("generator/generators/*")
+	if err != nil {
+		panic(err)
+	}
+
 	log.Println("Found the following generators...", files)
 	gTypes := make([]*orm.GeneratorType, 0)
 	for _, g := range files {
@@ -118,7 +145,7 @@ func main() {
 	c.Start()
 
 	log.Println("karmen is listening to port 3000...")
-	if err := http.ListenAndServe(":3000", handlers.LoggingHandler(os.Stdout, karmen_handlers.Handlers())); err != nil {
+	if err := http.ListenAndServe(":3000", handlers.LoggingHandler(os.Stdout, karmen_handlers.Handlers(models))); err != nil {
 		panic(err)
 	}
 

+ 126 - 121
orm/activity.go

@@ -1,7 +1,9 @@
 package orm
 
 import (
+	"fmt"
 	"net/http"
+	"strconv"
 
 	"github.com/jinzhu/gorm"
 	"gogs.carducci-dante.gov.it/karmen/core/renderer"
@@ -29,42 +31,75 @@ type Activity struct {
 
 	Hours int
 
-	prevTeacherID uint
+	AllClasses  []*Class   `gorm:"-"`
+	AllSubjects []*Subject `gorm:"-"`
+	AllTeachers []*Teacher `gorm:"-"`
+	AllStudents []*Student `gorm:"-"`
+
+	SelectedStudent       map[uint]string `gorm:"-"`
+	SelectedTeacher       map[uint]string `gorm:"-"`
+	SelectedSupplyTeacher map[uint]string `gorm:"-"`
+	SelectedSubject       map[uint]string `gorm:"-"`
+	SelectedClass         map[uint]string `gorm:"-"`
 }
 
-type ActivityForUpdate struct {
-	Activity Activity
+func (a *Activity) GetID() uint { return a.ID }
 
-	AllClasses  []*Class
-	AllSubjects []*Subject
-	AllTeachers []*Teacher
-	AllStudents []*Student
+func (a *Activity) String() string {
+	var result string
 
-	SelectedStudent       map[uint]string
-	SelectedTeacher       map[uint]string
-	SelectedSupplyTeacher map[uint]string
-	SelectedSubject       map[uint]string
-	SelectedClass         map[uint]string
-}
+	result = a.Subject.Name
+
+	if a.Class != nil {
+		result += " " + a.Class.Name
+	}
 
-type ActivityForAdd struct {
-	AllClasses  []*Class
-	AllSubjects []*Subject
-	AllTeachers []*Teacher
-	AllStudents []*Student
+	if a.SupplyTeacher != nil {
+		result += fmt.Sprintf(" (SUPPL %s)", a.SupplyTeacher.String())
+	}
+
+	result += " " + strconv.Itoa(a.Hours) + "h"
+
+	return result
 }
 
-func (a *Activity) GetID() uint { return a.ID }
+func (a *Activity) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		activity := new(Activity)
 
-func GetActivity(args map[string]string) (interface{}, error) {
-	var activity Activity
-	if err := DB().First(&activity, args["id"]).Error; err != nil {
-		return nil, err
+		if err := DB().Find(&activity.AllClasses).Error; err != nil {
+			return nil, err
+		}
+
+		if err := DB().Find(&activity.AllSubjects).Error; err != nil {
+			return nil, err
+		}
+
+		if err := DB().Find(&activity.AllTeachers).Error; err != nil {
+			return nil, err
+		}
+
+		if err := DB().Find(&activity.AllStudents).Error; err != nil {
+			return nil, err
+		}
+
+		return activity, nil
+	} else {
+		activity := new(Activity)
+		err := renderer.Decode(activity, r)
+		if err != nil {
+			return nil, err
+		}
+		activity, err = CreateActivity(activity)
+		if err != nil {
+			return nil, err
+		}
+
+		return activity, nil
 	}
-	return &activity, nil
 }
 
-func GetActivityAll(args map[string]string) (interface{}, error) {
+func (a *Activity) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var activity Activity
 
 	id := args["id"]
@@ -72,124 +107,105 @@ func GetActivityAll(args map[string]string) (interface{}, error) {
 	if err := DB().Preload("Student").Preload("Teacher").Preload("Subject").Preload("Class").First(&activity, id).Error; err != nil {
 		return nil, err
 	}
+
 	return &activity, nil
 }
 
-func GetActivityForUpdate(args map[string]string) (interface{}, error) {
-	var data ActivityForUpdate
-
-	id := args["id"]
-
-	if err := DB().Preload("SupplyTeacher").Preload("Teacher").Preload("Subject").Preload("Class").First(&data.Activity, id).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllClasses).Error; err != nil {
-		return nil, err
-	}
+func (a *Activity) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
+	var activities []*Activity
 
-	if err := DB().Find(&data.AllSubjects).Error; err != nil {
+	if err := DB().Preload("Teacher").Preload("Subject").Preload("Class").Order("name").Find(&activities).Error; err != nil {
 		return nil, err
 	}
 
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
-		return nil, err
-	}
+	return activities, nil
+}
 
-	if err := DB().Find(&data.AllStudents).Error; err != nil {
-		return nil, err
-	}
+func (a *Activity) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := a.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-	data.SelectedTeacher = make(map[uint]string)
-	data.SelectedTeacher[data.Activity.TeacherID] = "selected"
+		activity := result.(*Activity)
 
-	data.SelectedSupplyTeacher = make(map[uint]string)
-	data.SelectedSupplyTeacher[data.Activity.SupplyTeacherID] = "selected"
+		if err := DB().Find(&activity.AllClasses).Error; err != nil {
+			return nil, err
+		}
 
-	data.SelectedStudent = make(map[uint]string)
-	data.SelectedStudent[data.Activity.StudentID] = "selected"
+		if err := DB().Find(&activity.AllSubjects).Error; err != nil {
+			return nil, err
+		}
 
-	data.SelectedClass = make(map[uint]string)
-	data.SelectedClass[data.Activity.ClassID] = "selected"
+		if err := DB().Find(&activity.AllTeachers).Error; err != nil {
+			return nil, err
+		}
 
-	data.SelectedSubject = make(map[uint]string)
-	data.SelectedSubject[data.Activity.SubjectID] = "selected"
-	return data, nil
-}
+		if err := DB().Find(&activity.AllStudents).Error; err != nil {
+			return nil, err
+		}
 
-func GetActivityForAdd(args map[string]string) (interface{}, error) {
-	var data ActivityForAdd
+		activity.SelectedTeacher = make(map[uint]string)
+		activity.SelectedTeacher[activity.TeacherID] = "selected"
 
-	if err := DB().Find(&data.AllClasses).Error; err != nil {
-		return nil, err
-	}
+		activity.SelectedSupplyTeacher = make(map[uint]string)
+		activity.SelectedSupplyTeacher[activity.SupplyTeacherID] = "selected"
 
-	if err := DB().Find(&data.AllSubjects).Error; err != nil {
-		return nil, err
-	}
+		activity.SelectedStudent = make(map[uint]string)
+		activity.SelectedStudent[activity.StudentID] = "selected"
 
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
-		return nil, err
-	}
+		activity.SelectedClass = make(map[uint]string)
+		activity.SelectedClass[activity.ClassID] = "selected"
 
-	if err := DB().Find(&data.AllStudents).Error; err != nil {
-		return nil, err
-	}
+		activity.SelectedSubject = make(map[uint]string)
+		activity.SelectedSubject[activity.SubjectID] = "selected"
 
-	return data, nil
-}
+		return activity, nil
+	} else {
+		activity, err := a.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func GetActivitiesAll(args map[string]string) (interface{}, error) {
-	var activities []*Activity
-	if err := DB().Preload("Teacher").Preload("Subject").Preload("Class").Order("name").Find(&activities).Error; err != nil {
-		return nil, err
-	}
-	return activities, nil
-}
+		activity.(*Activity).GroupActivity = false
 
-func GetActivities(args map[string]string) (interface{}, error) {
-	var activities []*Activity
-	if err := DB().Order("surname,name").Find(&activities).Error; err != nil {
-		return nil, err
-	}
-	return activities, nil
-}
+		err = renderer.Decode(activity, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveActivity(activity)
+		if err != nil {
+			return nil, err
+		}
+		activity, err = a.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func SaveActivity(activity interface{}) (interface{}, error) {
-	if err := DB().Save(activity).Error; err != nil {
-		return nil, err
+		return activity.(*Activity), nil
 	}
-	return activity, nil
 }
 
-func UpdateActivity(args map[string]string, r *http.Request) (IDer, error) {
-	activity, err := GetActivity(args)
-	if err != nil {
-		return nil, err
-	}
-	err = renderer.Decode(activity, r)
+func (a *Activity) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	activity, err := a.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
-	_, err = SaveActivity(activity)
-	if err != nil {
-		return nil, err
-	}
-	activity, err = GetActivity(args)
-	if err != nil {
+	if err := DB().Unscoped().Delete(activity.(*Activity)).Error; err != nil {
 		return nil, err
 	}
 	return activity.(*Activity), nil
 }
 
-func AddActivity(args map[string]string, r *http.Request) (IDer, error) {
-	activity := new(Activity)
-	err := renderer.Decode(activity, r)
-	if err != nil {
-		return nil, err
-	}
-	activity, err = CreateActivity(activity)
-	if err != nil {
+func SaveActivity(activity interface{}) (interface{}, error) {
+	if err := DB().Omit(
+		"Class",
+		"Teacher",
+		"Subject",
+		"SupplyTeacher",
+		"Student",
+	).Save(activity).Error; err != nil {
 		return nil, err
 	}
 	return activity, nil
@@ -201,14 +217,3 @@ func CreateActivity(activity *Activity) (*Activity, error) {
 	}
 	return activity, nil
 }
-
-func DeleteActivity(args map[string]string, r *http.Request) (IDer, error) {
-	activity, err := GetActivity(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(activity.(*Activity)).Error; err != nil {
-		return nil, err
-	}
-	return activity.(*Activity), nil
-}

+ 83 - 87
orm/administrative.go

@@ -3,6 +3,7 @@ package orm
 import (
 	"fmt"
 	"net/http"
+	"time"
 
 	"gogs.carducci-dante.gov.it/karmen/core/renderer"
 )
@@ -13,30 +14,37 @@ type Administrative struct {
 	Office *Office
 
 	OfficeID uint `schema:"office_id"`
-}
 
-type AdministrativeForUpdate struct {
-	Administrative Administrative
 	AllOffices     []*Office
-
-	SelectedOffice map[uint]string
+	SelectedOffice map[uint]string `gorm:"-"`
 }
 
-type AdministrativeForAdd struct {
-	AllOffices []*Office
-}
+func (s *Administrative) GetID() uint    { return s.ID }
+func (a *Administrative) String() string { return a.CompleteName() }
 
-func (s *Administrative) GetID() uint { return s.ID }
+func (a *Administrative) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		administrative := new(Administrative)
+		if err := DB().Find(&administrative.AllOffices).Error; err != nil {
+			return nil, err
+		}
+		return administrative, nil
+	} else {
+		administrative := new(Administrative)
+		err := renderer.Decode(administrative, r)
+		if err != nil {
+			return nil, err
+		}
+		administrative, err = CreateAdministrative(administrative)
+		if err != nil {
+			return nil, err
+		}
 
-func GetAdministrative(args map[string]string) (interface{}, error) {
-	var administrative Administrative
-	if err := DB().First(&administrative, args["id"]).Error; err != nil {
-		return nil, err
+		return administrative, nil
 	}
-	return &administrative, nil
 }
 
-func GetAdministrativeAll(args map[string]string) (interface{}, error) {
+func (a *Administrative) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var administrative Administrative
 
 	id := args["id"]
@@ -48,74 +56,77 @@ func GetAdministrativeAll(args map[string]string) (interface{}, error) {
 	return &administrative, nil
 }
 
-func GetAdministratives(args map[string]string) (interface{}, error) {
+func (a *Administrative) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var administratives []*Administrative
-	if err := DB().Order("surname,name").Find(&administratives).Error; err != nil {
-		return nil, err
-	}
-	return administratives, nil
-}
 
-func GetAdministrativesAll(args map[string]string) (interface{}, error) {
-	var administratives []*Administrative
 	if err := DB().Preload("Office").Order("surname,name").Find(&administratives).Error; err != nil {
 		return nil, err
 	}
+
 	return administratives, nil
 }
 
-func SaveAdministrative(administrative interface{}) (interface{}, error) {
-	if err := DB().Omit("Office").Save(administrative).Error; err != nil {
-		return nil, err
-	}
-	return administrative, nil
-}
+func (a *Administrative) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := a.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func UpdateAdministrative(args map[string]string, r *http.Request) (IDer, error) {
-	administrative, err := GetAdministrative(args)
-	if err != nil {
-		return nil, err
-	}
+		administrative := result.(*Administrative)
 
-	err = renderer.Decode(administrative, r)
-	if err != nil {
-		return nil, err
-	}
+		if err := DB().Find(&administrative.AllOffices).Error; err != nil {
+			return nil, err
+		}
 
-	_, err = SaveAdministrative(administrative)
-	if err != nil {
-		return nil, err
-	}
-	administrative, err = GetAdministrative(args)
-	if err != nil {
-		return nil, err
-	}
-	return administrative.(*Administrative), nil
-}
+		administrative.SelectedOffice = make(map[uint]string)
+		administrative.SelectedOffice[administrative.OfficeID] = "selected"
 
-func AddAdministrative(args map[string]string, r *http.Request) (IDer, error) {
-	administrative := new(Administrative)
-	err := renderer.Decode(administrative, r)
-	if err != nil {
-		return nil, err
-	}
-	administrative, err = CreateAdministrative(administrative)
-	if err != nil {
-		return nil, err
-	}
+		return administrative, nil
+	} else {
+		administrative, err := a.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-	return administrative, nil
-}
+		// FIXME: Should not be hard set.
+		administrative.(*Administrative).Regenerate = false
 
-func CreateAdministrative(administrative *Administrative) (*Administrative, error) {
-	if err := DB().Create(administrative).Error; err != nil {
-		return nil, err
+		// FIXME: Should not be hard set.
+		administrative.(*Administrative).Exclude = false
+
+		// FIXME: Should not be hard set.
+		administrative.(*Administrative).NotInvited = false
+
+		if len(args["DateFrom"]) == 0 {
+			// FIXME: Should not be hard set.
+			administrative.(*Administrative).DateFrom = time.Time{}
+		}
+
+		if len(args["DateTo"]) == 0 {
+			// FIXME: Should not be hard set.
+			administrative.(*Administrative).DateTo = time.Time{}
+		}
+
+		err = renderer.Decode(administrative, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveAdministrative(administrative)
+		if err != nil {
+			return nil, err
+		}
+		administrative, err = a.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		return administrative.(*Administrative), nil
 	}
-	return administrative, nil
 }
 
-func DeleteAdministrative(args map[string]string, r *http.Request) (IDer, error) {
-	administrative, err := GetAdministrative(args)
+func (a *Administrative) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	administrative, err := a.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
@@ -125,33 +136,18 @@ func DeleteAdministrative(args map[string]string, r *http.Request) (IDer, error)
 	return administrative.(*Administrative), nil
 }
 
-func GetAdministrativeForUpdate(args map[string]string) (interface{}, error) {
-	var data AdministrativeForUpdate
-
-	id := args["id"]
-
-	if err := DB().Preload("Office").First(&data.Administrative, id).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllOffices).Error; err != nil {
+func SaveAdministrative(administrative interface{}) (interface{}, error) {
+	if err := DB().Omit("Office").Save(administrative).Error; err != nil {
 		return nil, err
 	}
-
-	data.SelectedOffice = make(map[uint]string)
-	data.SelectedOffice[data.Administrative.OfficeID] = "selected"
-
-	return data, nil
+	return administrative, nil
 }
 
-func GetAdministrativeForAdd(args map[string]string) (interface{}, error) {
-	var data AdministrativeForAdd
-
-	if err := DB().Find(&data.AllOffices).Error; err != nil {
+func CreateAdministrative(administrative *Administrative) (*Administrative, error) {
+	if err := DB().Create(administrative).Error; err != nil {
 		return nil, err
 	}
-
-	return data, nil
+	return administrative, nil
 }
 
 func (s *Administrative) CompleteName() string {

+ 77 - 136
orm/class.go

@@ -31,41 +31,49 @@ type Class struct {
 	CoordinatorID uint `schema:"coordinator_id"`
 	MinuterID     uint `schema:"minuter_id"`
 
-	Coordinator Teacher
-	Minuter     Teacher
+	Coordinator *Teacher
+	Minuter     *Teacher
 
 	Teachers   []*Teacher
 	Activities []*Activity
 	Students   []*Student
-}
 
-type ClassForUpdate struct {
-	Class               Class
-	AllTeachers         []*Teacher
-	SelectedCoordinator map[uint]string
-	SelectedMinuter     map[uint]string
+	AllTeachers         []*Teacher      `gorm:"-"`
+	SelectedCoordinator map[uint]string `gorm:"-"`
+	SelectedMinuter     map[uint]string `gorm:"-"`
 }
 
-type ClassForAdd struct {
-	AllTeachers []*Teacher
-}
+func (t *Class) GetID() uint    { return t.ID }
+func (c *Class) String() string { return c.Name }
 
-func (t *Class) GetID() uint { return t.ID }
+func (c *Class) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		class := new(Class)
+		if err := DB().Find(&class.AllTeachers).Error; err != nil {
+			return nil, err
+		}
+		return class, nil
+	} else {
+		class := new(Class)
+		err := renderer.Decode(class, r)
+		if err != nil {
+			return nil, err
+		}
+		class, err = CreateClass(class)
+		if err != nil {
+			return nil, err
+		}
 
-func GetClass(args map[string]string) (interface{}, error) {
-	var class Class
-	if err := DB().First(&class, args["id"]).Error; err != nil {
-		return nil, err
+		return class, nil
 	}
-	return &class, nil
 }
 
-func GetClassAll(args map[string]string) (interface{}, error) {
+func (c *Class) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var class Class
 
 	id := args["id"]
 
-	if err := DB().First(&class, id).Error; err != nil {
+	if err := DB().Preload("Coordinator").Preload("Minuter").Where("id = ?", id).Find(&class).Error; err != nil {
 		return nil, err
 	}
 
@@ -77,39 +85,15 @@ func GetClassAll(args map[string]string) (interface{}, error) {
 		return nil, err
 	}
 
-	if class.CoordinatorID != 0 {
-		if !DB().First(&class.Coordinator, class.CoordinatorID).RecordNotFound() {
-			if err := DB().First(&class.Coordinator, class.CoordinatorID).Error; err != nil {
-				return nil, err
-			}
-		}
-	}
-
-	if class.MinuterID != 0 {
-		if !DB().First(&class.Minuter, class.MinuterID).RecordNotFound() {
-			if err := DB().First(&class.Minuter, class.MinuterID).Error; err != nil {
-				return nil, err
-			}
-		}
-	}
-
 	return &class, nil
 }
 
-func GetClasses(args map[string]string) (interface{}, error) {
+func (c *Class) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var classes []*Class
-	if err := DB().Order("name").Find(&classes).Error; err != nil {
+	if err := DB().Preload("Coordinator").Preload("Minuter").Order("name").Find(&classs).Error; err != nil {
 		return nil, err
 	}
-	return classes, nil
-}
-
-func GetClassesAll(args map[string]string) (interface{}, error) {
-	var classes []*Class
-	if err := DB().Order("name").Find(&classes).Error; err != nil {
-		return nil, err
-	}
-	for _, class := range classes {
+	for _, class := range classs {
 		class.GetTeachers()
 	}
 
@@ -121,93 +105,68 @@ func GetClassesAll(args map[string]string) (interface{}, error) {
 		class.GetActivities()
 	}
 
-	for _, class := range classes {
-		if class.CoordinatorID != 0 {
-			if !DB().First(&class.Coordinator, class.CoordinatorID).RecordNotFound() {
-				if err := DB().First(&class.Coordinator, class.CoordinatorID).Error; err != nil {
-					return nil, err
-				}
-			}
+	return classes, nil
+}
+
+func (c *Class) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := c.Read(args, r)
+		if err != nil {
+			return nil, err
 		}
 
-		if class.MinuterID != 0 {
-			if !DB().First(&class.Minuter, class.MinuterID).RecordNotFound() {
-				if err := DB().First(&class.Minuter, class.MinuterID).Error; err != nil {
-					return nil, err
-				}
-			}
+		class := result.(*Class)
+
+		if err := DB().Find(&class.AllTeachers).Error; err != nil {
+			return nil, err
 		}
 
-	}
+		class.SelectedMinuter = make(map[uint]string)
+		class.SelectedMinuter[class.MinuterID] = "selected"
 
-	return classes, nil
-}
+		class.SelectedCoordinator = make(map[uint]string)
+		class.SelectedCoordinator[class.CoordinatorID] = "selected"
 
-func SaveClass(class interface{}) (interface{}, error) {
-	if err := DB().Save(class).Error; err != nil {
-		return nil, err
+		return class, nil
+	} else {
+		class, err := c.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		err = renderer.Decode(class, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveClass(class)
+		if err != nil {
+			return nil, err
+		}
+		class, err = c.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		return class.(*Class), nil
 	}
-	return class, nil
 }
 
-func UpdateClass(args map[string]string, r *http.Request) (IDer, error) {
-	class, err := GetClass(args)
-	if err != nil {
-		return nil, err
-	}
-	err = renderer.Decode(class, r)
+func (c *Class) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	class, err := c.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
-	_, err = SaveClass(class)
-	if err != nil {
-		return nil, err
-	}
-	class, err = GetClass(args)
-	if err != nil {
+	if err := DB().Unscoped().Delete(class.(*Class)).Error; err != nil {
 		return nil, err
 	}
 	return class.(*Class), nil
 }
 
-func AddClass(args map[string]string, r *http.Request) (IDer, error) {
-	class := new(Class)
-	err := renderer.Decode(class, r)
-	if err != nil {
-		return nil, err
-	}
-	class, err = CreateClass(class)
-	if err != nil {
-		return nil, err
-	}
-	return class, nil
-}
-
-func CreateClass(class *Class) (*Class, error) {
-	var classes []*Class
-	if err := DB().Where("name=?", class.Name).Find(&classes).Error; err != nil {
-		return nil, err
-	}
-	if len(classes) > 0 {
-		return nil, fmt.Errorf("Class %s already exists!", class.Name)
-	}
-	if err := DB().Create(class).Error; err != nil {
+func SaveClass(class interface{}) (interface{}, error) {
+	if err := DB().Debug().Omit("Coordinator", "Minuter", "Teachers", "Activities", "Students").Save(class).Error; err != nil {
 		return nil, err
 	}
 	return class, nil
 }
 
-func DeleteClass(args map[string]string, r *http.Request) (IDer, error) {
-	class, err := GetClass(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(class.(*Class)).Error; err != nil {
-		return nil, err
-	}
-	return class.(*Class), nil
-}
-
 func (c *Class) GetTeachers() ([]*Teacher, error) {
 	if err := DB().Raw(selectUniqueClassTeachers, c.ID).Scan(&c.Teachers).Error; err != nil {
 		return nil, err
@@ -229,34 +188,16 @@ func (c *Class) GetActivities() ([]*Activity, error) {
 	return c.Activities, nil
 }
 
-func GetClassForUpdate(args map[string]string) (interface{}, error) {
-	var data ClassForUpdate
-
-	id := args["id"]
-
-	if err := DB().Preload("Coordinator").Preload("Minuter").First(&data.Class, id).Error; err != nil {
+func CreateClass(class *Class) (*Class, error) {
+	var classes []*Class
+	if err := DB().Where("name=?", class.Name).Find(&classes).Error; err != nil {
 		return nil, err
 	}
-
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
-		return nil, err
+	if len(classes) > 0 {
+		return nil, fmt.Errorf("Class %s already exists!", class.Name)
 	}
-
-	data.SelectedMinuter = make(map[uint]string)
-	data.SelectedMinuter[data.Class.MinuterID] = "selected"
-
-	data.SelectedCoordinator = make(map[uint]string)
-	data.SelectedCoordinator[data.Class.CoordinatorID] = "selected"
-
-	return data, nil
-}
-
-func GetClassForAdd(args map[string]string) (interface{}, error) {
-	var data ClassForAdd
-
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
+	if err := DB().Create(class).Error; err != nil {
 		return nil, err
 	}
-
-	return data, nil
+	return class, nil
 }

+ 74 - 90
orm/department.go

@@ -12,21 +12,13 @@ type Department struct {
 	Name string
 
 	CoordinatorID uint `schema:"coordinator_id"`
-	Coordinator   Teacher
+	Coordinator   *Teacher
 
 	Subjects []*Subject
 	Teachers []*Teacher
-}
-
-type DepartmentForUpdate struct {
-	Department          Department
-	AllTeachers         []*Teacher
-	SelectedCoordinator map[uint]string
-	SelectedMinuter     map[uint]string
-}
 
-type DepartmentForAdd struct {
-	AllTeachers []*Teacher
+	AllTeachers         []*Teacher      `gorm:"-"`
+	SelectedCoordinator map[uint]string `gorm:"-"`
 }
 
 var (
@@ -43,20 +35,38 @@ WHERE department_id=?`
 
 func (d *Department) GetID() uint { return d.ID }
 
-func GetDepartment(args map[string]string) (interface{}, error) {
-	var department Department
-	if err := DB().First(&department, args["id"]).Error; err != nil {
-		return nil, err
+func (d *Department) String() string {
+	return d.Name
+}
+
+func (d *Department) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		department := new(Department)
+		if err := DB().Find(&department.AllTeachers).Error; err != nil {
+			return nil, err
+		}
+		return department, nil
+	} else {
+		department := new(Department)
+		err := renderer.Decode(department, r)
+		if err != nil {
+			return nil, err
+		}
+		department, err = CreateDepartment(department)
+		if err != nil {
+			return nil, err
+		}
+
+		return department, nil
 	}
-	return &department, nil
 }
 
-func GetDepartmentAll(args map[string]string) (interface{}, error) {
+func (d *Department) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var department Department
 
 	id := args["id"]
 
-	if err := DB().Where("id = ?", id).Find(&department).Error; err != nil {
+	if err := DB().Preload("Coordinator").Where("id = ?", id).Find(&department).Error; err != nil {
 		return nil, err
 	}
 
@@ -68,26 +78,10 @@ func GetDepartmentAll(args map[string]string) (interface{}, error) {
 		return nil, err
 	}
 
-	if department.CoordinatorID != 0 {
-		if !DB().First(&department.Coordinator, department.CoordinatorID).RecordNotFound() {
-			if err := DB().First(&department.Coordinator, department.CoordinatorID).Error; err != nil {
-				return nil, err
-			}
-		}
-	}
-
 	return &department, nil
 }
 
-func GetDepartments(args map[string]string) (interface{}, error) {
-	var departments []*Department
-	if err := DB().Order("name").Find(&departments).Error; err != nil {
-		return nil, err
-	}
-	return departments, nil
-}
-
-func GetDepartmentsAll(args map[string]string) (interface{}, error) {
+func (d *Department) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var departments []*Department
 	if err := DB().Preload("Coordinator").Preload("Subjects").Order("name").Find(&departments).Error; err != nil {
 		return nil, err
@@ -98,31 +92,61 @@ func GetDepartmentsAll(args map[string]string) (interface{}, error) {
 	return departments, nil
 }
 
-func SaveDepartment(department interface{}) (interface{}, error) {
-	if err := DB().Omit("Teachers", "Subjects").Save(department).Error; err != nil {
-		return nil, err
+func (d *Department) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := d.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		department := result.(*Department)
+
+		if err := DB().Find(&department.AllTeachers).Error; err != nil {
+			return nil, err
+		}
+
+		department.SelectedCoordinator = make(map[uint]string)
+		department.SelectedCoordinator[department.CoordinatorID] = "selected"
+
+		return department, nil
+	} else {
+		department, err := d.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		err = renderer.Decode(department, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveDepartment(department)
+		if err != nil {
+			return nil, err
+		}
+		department, err = d.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		return department.(*Department), nil
 	}
-	return department, nil
 }
 
-func UpdateDepartment(args map[string]string, r *http.Request) (IDer, error) {
-	department, err := GetDepartment(args)
-	if err != nil {
-		return nil, err
-	}
-	err = renderer.Decode(department, r)
+func (d *Department) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	department, err := d.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
-	_, err = SaveDepartment(department)
-	if err != nil {
+	if err := DB().Unscoped().Delete(department.(*Department)).Error; err != nil {
 		return nil, err
 	}
-	department, err = GetDepartment(args)
-	if err != nil {
+	return department.(*Department), nil
+}
+
+func SaveDepartment(department interface{}) (interface{}, error) {
+	if err := DB().Debug().Omit("Coordinator", "Teachers", "Subjects").Save(department).Error; err != nil {
 		return nil, err
 	}
-	return department.(*Department), nil
+	return department, nil
 }
 
 func AddDepartment(args map[string]string, r *http.Request) (IDer, error) {
@@ -146,46 +170,6 @@ func CreateDepartment(department *Department) (*Department, error) {
 	return department, nil
 }
 
-func DeleteDepartment(args map[string]string, r *http.Request) (IDer, error) {
-	department, err := GetDepartment(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(department.(*Department)).Error; err != nil {
-		return nil, err
-	}
-	return department.(*Department), nil
-}
-
-func GetDepartmentForAdd(args map[string]string) (interface{}, error) {
-	var data DepartmentForAdd
-
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
-		return nil, err
-	}
-
-	return data, nil
-}
-
-func GetDepartmentForUpdate(args map[string]string) (interface{}, error) {
-	var data DepartmentForUpdate
-
-	id := args["id"]
-
-	if err := DB().Preload("Coordinator").First(&data.Department, id).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
-		return nil, err
-	}
-
-	data.SelectedCoordinator = make(map[uint]string)
-	data.SelectedCoordinator[data.Department.CoordinatorID] = "selected"
-
-	return data, nil
-}
-
 func (t *Department) GetTeachers() ([]*Teacher, error) {
 	if err := DB().Raw(selectUniqueDepartmentTeachers, t.ID).Scan(&t.Teachers).Error; err != nil {
 		return nil, err

+ 82 - 103
orm/document.go

@@ -1,6 +1,8 @@
 package orm
 
 import (
+	"errors"
+	"fmt"
 	"log"
 	"net/http"
 	"time"
@@ -17,6 +19,8 @@ type GeneratorType struct {
 	Name string
 }
 
+func (g *GeneratorType) String() string { return g.Name }
+
 type Document struct {
 	gorm.Model
 
@@ -25,7 +29,7 @@ type Document struct {
 	OutputPath string
 	CloudPath  string
 
-	GeneratorTypeID uint
+	GeneratorTypeID uint `schema:"generator_type_id"`
 	GeneratorType   *GeneratorType
 
 	KeepArtifacts bool
@@ -36,33 +40,37 @@ type Document struct {
 	CompletedJobs []*Job
 
 	LastJob *Job `gorm:"-"`
-}
-
-type DocumentForUpdate struct {
-	Document
 
-	AllGeneratorTypes []*GeneratorType
-
-	SelectedGeneratorType map[uint]string
+	AllGeneratorTypes     []*GeneratorType
+	SelectedGeneratorType map[uint]string `gorm:"-"`
 }
 
-type DocumentForAdd struct {
-	Document
+func (s *Document) GetID() uint    { return s.ID }
+func (d *Document) String() string { return d.Name }
 
-	AllGeneratorTypes []*GeneratorType
-}
+func (d *Document) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		document := new(Document)
+		if err := DB().Find(&document.AllGeneratorTypes).Error; err != nil {
+			return nil, err
+		}
+		return document, nil
+	} else {
+		document := new(Document)
+		err := renderer.Decode(document, r)
+		if err != nil {
+			return nil, err
+		}
+		document, err = CreateDocument(document)
+		if err != nil {
+			return nil, err
+		}
 
-func (s *Document) GetID() uint { return s.ID }
-
-func GetDocument(args map[string]string) (interface{}, error) {
-	var document Document
-	if err := DB().First(&document, args["id"]).Error; err != nil {
-		return nil, err
+		return document, nil
 	}
-	return &document, nil
 }
 
-func GetDocumentAll(args map[string]string) (interface{}, error) {
+func (d *Document) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var document Document
 
 	id := args["id"]
@@ -80,79 +88,60 @@ func GetDocumentAll(args map[string]string) (interface{}, error) {
 	return &document, nil
 }
 
-func GetDocuments(args map[string]string) (interface{}, error) {
+func (d *Document) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var documents []*Document
-	if err := DB().Order("name").Find(&documents).Error; err != nil {
-		return nil, err
-	}
-	return documents, nil
-}
 
-func GetDocumentsAll(args map[string]string) (interface{}, error) {
-	var documents []*Document
 	if err := DB().Preload("GeneratorType").Order("name").Find(&documents).Error; err != nil {
 		return nil, err
 	}
 	return documents, nil
 }
 
-func SaveDocument(document interface{}) (interface{}, error) {
-	if err := DB().Save(document).Error; err != nil {
-		return nil, err
-	}
-	return document, nil
-}
-
-func UpdateDocument(args map[string]string, r *http.Request) (IDer, error) {
-	document, err := GetDocument(args)
-	if err != nil {
-		return nil, err
-	}
+func (d *Document) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := d.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-	// FIXME: Should not be hard set.
-	document.(*Document).KeepArtifacts = false
+		document := result.(*Document)
 
-	err = renderer.Decode(document, r)
-	if err != nil {
-		return nil, err
-	}
-
-	_, err = SaveDocument(document)
-	if err != nil {
-		return nil, err
-	}
+		if err := DB().Find(&document.AllGeneratorTypes).Error; err != nil {
+			return nil, err
+		}
 
-	document, err = GetDocument(args)
-	if err != nil {
-		return nil, err
-	}
+		document.SelectedGeneratorType = make(map[uint]string)
+		document.SelectedGeneratorType[document.GeneratorTypeID] = "selected"
 
-	return document.(*Document), nil
-}
+		return document, nil
+	} else {
+		document, err := d.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func AddDocument(args map[string]string, r *http.Request) (IDer, error) {
-	document := new(Document)
-	err := renderer.Decode(document, r)
-	if err != nil {
-		return nil, err
-	}
-	document, err = CreateDocument(document)
-	if err != nil {
-		return nil, err
-	}
+		// FIXME: Should not be hard set.
+		document.(*Document).KeepArtifacts = false
 
-	return document, nil
-}
+		err = renderer.Decode(document, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveDocument(document)
+		if err != nil {
+			return nil, err
+		}
+		document, err = d.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func CreateDocument(document *Document) (*Document, error) {
-	if err := DB().Create(document).Error; err != nil {
-		return nil, err
+		return document.(*Document), nil
 	}
-	return document, nil
 }
 
-func DeleteDocument(args map[string]string, r *http.Request) (IDer, error) {
-	document, err := GetDocument(args)
+func (d *Document) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	document, err := d.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
@@ -162,32 +151,21 @@ func DeleteDocument(args map[string]string, r *http.Request) (IDer, error) {
 	return document.(*Document), nil
 }
 
-func GetDocumentForUpdate(args map[string]string) (interface{}, error) {
-	var data DocumentForUpdate
-
-	id := args["id"]
-
-	if err := DB().First(&data.Document, id).Error; err != nil {
+func SaveDocument(document interface{}) (interface{}, error) {
+	if err := DB().Save(document).Error; err != nil {
 		return nil, err
 	}
+	return document, nil
+}
 
-	if err := DB().Find(&data.AllGeneratorTypes).Error; err != nil {
+func CreateDocument(document *Document) (*Document, error) {
+	if err := DB().Create(document).Error; err != nil {
 		return nil, err
 	}
-
-	data.SelectedGeneratorType = make(map[uint]string)
-	data.SelectedGeneratorType[data.GeneratorTypeID] = "selected"
-
-	return data, nil
-}
-
-func GetDocumentForAdd(args map[string]string) (interface{}, error) {
-	var data DocumentForAdd
-
-	return data, nil
+	return document, nil
 }
 
-func GetDocumentExecute(args map[string]string) (interface{}, error) {
+func GetDocumentExecute(args map[string]string, r *http.Request) (interface{}, error) {
 	var document Document
 	if err := DB().Preload("GeneratorType").First(&document, args["id"]).Error; err != nil {
 		return nil, err
@@ -205,15 +183,16 @@ func GetDocumentExecute(args map[string]string) (interface{}, error) {
 
 	log.Printf("Start concurrent job %d for generator %s", job.ID, document.Name)
 
-	gen := generator.Generators[document.GeneratorType.Name]
-
-	gen.SetJobId(job.ID)
-
-	go gen.Run()
+	if document.GeneratorType != nil {
+		gen, ok := generator.Generators[document.GeneratorType.Name]
+		if !ok {
+			return nil, fmt.Errorf("Generator type %s not found!", document.GeneratorType.Name)
+		}
+		gen.SetJobId(job.ID)
+		go gen.Run()
+	} else {
+		return nil, errors.New("Generator type not defined!")
+	}
 
 	return &document, nil
 }
-
-func (t *Document) RestAPIPath() string {
-	return "documents"
-}

+ 44 - 0
orm/file.go

@@ -0,0 +1,44 @@
+package orm
+
+import (
+	"fmt"
+	"net/http"
+	"path/filepath"
+
+	"github.com/jinzhu/gorm"
+)
+
+type File struct {
+	gorm.Model
+
+	Path string
+
+	JobID uint
+}
+
+func (f *File) String() string { return filepath.Base(f.Path) }
+
+func (f *File) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
+
+func (f *File) Read(args map[string]string, r *http.Request) (interface{}, error) {
+	var file File
+	if err := DB().First(&file, args["id"]).Error; err != nil {
+		return nil, err
+	}
+
+	return map[string]string{"filename": file.Path}, nil
+}
+
+func (f *File) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
+
+func (f *File) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
+
+func (f *File) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	return nil, fmt.Errorf("Not implemented")
+}

+ 79 - 108
orm/group.go

@@ -20,47 +20,37 @@ type Group struct {
 	TeacherIDs []uint `schema:"teacher_ids" gorm:"-"`
 
 	Teachers []*Teacher `gorm:"many2many:group_teachers"`
-}
-
-type GroupForAdd struct {
-	AllTeachers []*Teacher
-}
-
-type GroupForUpdate struct {
-	Group Group
-
-	AllTeachers []*Teacher
-
-	SelectedTeacher map[uint]string
-}
 
-func (g *Group) GetID() uint { return g.ID }
-
-func GetGroups(args map[string]string) (interface{}, error) {
-	return GetGroupsAll(args)
+	AllTeachers     []*Teacher
+	SelectedTeacher map[uint]string `gorm:"-"`
 }
 
-func GetGroupsAll(args map[string]string) (interface{}, error) {
-	var groups []*Group
+func (g *Group) GetID() uint    { return g.ID }
+func (g *Group) String() string { return g.Name }
 
-	if err := DB().Preload("Teachers").Order("name").Find(&groups).Error; err != nil {
-		return nil, err
-	}
-
-	for _, group := range groups {
-		if group.Query != "" {
-			err := group.updateTeachersAssociation()
-			if err != nil {
-				return nil, err
-			}
+func (g *Group) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		group := new(Group)
+		if err := DB().Find(&group.AllTeachers).Error; err != nil {
+			return nil, err
+		}
+		return group, nil
+	} else {
+		group := new(Group)
+		err := renderer.Decode(group, r)
+		if err != nil {
+			return nil, err
+		}
+		group, err = CreateGroup(group)
+		if err != nil {
+			return nil, err
 		}
 
+		return group, nil
 	}
-
-	return groups, nil
 }
 
-func GetGroupAll(args map[string]string) (interface{}, error) {
+func (g *Group) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var group Group
 
 	id := args["id"]
@@ -76,95 +66,86 @@ func GetGroupAll(args map[string]string) (interface{}, error) {
 		}
 	}
 
-	return group, nil
+	return &group, nil
 }
 
-func GetGroupForUpdate(args map[string]string) (interface{}, error) {
-	var data GroupForUpdate
-
-	id := args["id"]
-
-	if err := DB().Preload("Teachers").First(&data.Group, id).Error; err != nil {
-		return nil, err
-	}
+func (g *Group) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
+	var groups []*Group
 
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
+	if err := DB().Preload("Teachers").Order("name").Find(&groups).Error; err != nil {
 		return nil, err
 	}
 
-	data.SelectedTeacher = make(map[uint]string)
-	for _, t := range data.Group.Teachers {
-		data.SelectedTeacher[t.ID] = "selected"
-	}
-
-	return data, nil
-}
-
-func GetGroupForAdd(args map[string]string) (interface{}, error) {
-	var data GroupForAdd
+	for _, group := range groups {
+		if group.Query != "" {
+			err := group.updateTeachersAssociation()
+			if err != nil {
+				return nil, err
+			}
+		}
 
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
-		return nil, err
 	}
 
-	return data, nil
+	return groups, nil
 }
 
-func GetGroup(args map[string]string) (interface{}, error) {
-	var group Group
+func (g *Group) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := g.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-	id := args["id"]
+		group := result.(*Group)
 
-	if err := DB().Preload("Teachers").Where("id = ?", id).Find(&group).Error; err != nil {
-		return nil, err
-	}
+		if err := DB().Find(&group.AllTeachers).Error; err != nil {
+			return nil, err
+		}
 
-	return &group, nil
+		group.SelectedTeacher = make(map[uint]string)
+		for _, t := range group.Teachers {
+			group.SelectedTeacher[t.ID] = "selected"
+		}
 
-}
+		return group, nil
+	} else {
+		group, err := g.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		err = renderer.Decode(group, r)
+		if err != nil {
+			return nil, err
+		}
 
-func UpdateGroup(args map[string]string, r *http.Request) (IDer, error) {
-	group, err := GetGroup(args)
-	if err != nil {
-		return nil, err
-	}
-	err = renderer.Decode(group, r)
-	if err != nil {
-		return nil, err
-	}
+		if err := DB().Where(group.(*Group).TeacherIDs).Find(&group.(*Group).Teachers).Error; err != nil {
+			return nil, err
+		}
 
-	if err := DB().Where(group.(*Group).TeacherIDs).Find(&group.(*Group).Teachers).Error; err != nil {
-		return nil, err
-	}
+		teachers, err := group.(*Group).executeTeacherQuery()
 
-	teachers, err := group.(*Group).executeTeacherQuery()
+		group.(*Group).Teachers = append(group.(*Group).Teachers, teachers...)
 
-	group.(*Group).Teachers = append(group.(*Group).Teachers, teachers...)
+		_, err = SaveGroup(group)
+		if err != nil {
+			return nil, err
+		}
 
-	_, err = SaveGroup(group)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Model(group).Association("Teachers").Replace(group.(*Group).Teachers).Error; err != nil {
-		return nil, err
-	}
+		if err := DB().Model(group).Association("Teachers").Replace(group.(*Group).Teachers).Error; err != nil {
+			return nil, err
+		}
 
-	group, err = GetGroup(args)
-	if err != nil {
-		return nil, err
-	}
-	return group.(*Group), nil
-}
+		group, err = g.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func SaveGroup(group interface{}) (interface{}, error) {
-	if err := DB().Save(group).Error; err != nil {
-		return nil, err
+		return group.(*Group), nil
 	}
-	return group, nil
 }
 
-func DeleteGroup(args map[string]string, r *http.Request) (IDer, error) {
-	group, err := GetGroup(args)
+func (g *Group) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	group, err := g.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
@@ -174,14 +155,8 @@ func DeleteGroup(args map[string]string, r *http.Request) (IDer, error) {
 	return group.(*Group), nil
 }
 
-func AddGroup(args map[string]string, r *http.Request) (IDer, error) {
-	group := new(Group)
-	err := renderer.Decode(group, r)
-	if err != nil {
-		return nil, err
-	}
-	group, err = CreateGroup(group)
-	if err != nil {
+func SaveGroup(group interface{}) (interface{}, error) {
+	if err := DB().Save(group).Error; err != nil {
 		return nil, err
 	}
 	return group, nil
@@ -211,10 +186,6 @@ func CreateGroup(group *Group) (*Group, error) {
 	return group, nil
 }
 
-func (group *Group) RestAPIPath() string {
-	return "groupes"
-}
-
 func (group *Group) updateTeachersAssociation() error {
 	teachers, err := group.executeTeacherQuery()
 	if err != nil {

+ 43 - 48
orm/job.go

@@ -1,22 +1,14 @@
 package orm
 
 import (
+	"fmt"
 	"net/http"
-	"strconv"
 	"time"
 
 	"github.com/jinzhu/gorm"
 	"gogs.carducci-dante.gov.it/karmen/core/renderer"
 )
 
-type File struct {
-	gorm.Model
-
-	Path string
-
-	JobID uint
-}
-
 type Log struct {
 	gorm.Model
 
@@ -41,21 +33,22 @@ type Job struct {
 	End   time.Time
 }
 
-func (s *Job) GetID() uint { return s.ID }
+func (s *Job) GetID() uint    { return s.ID }
+func (j *Job) String() string { return fmt.Sprintf("Lavoro ID %d", j.ID) }
+
+func (j *Job) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
 
-func GetFile(args map[string]string) (interface{}, error) {
+func (j *Job) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var job Job
-	if err := DB().Preload("Document").Preload("Files").First(&job, args["id"]).Error; err != nil {
+	if err := DB().Preload("Document").Preload("Files").Preload("Logs").First(&job, args["id"]).Error; err != nil {
 		return nil, err
 	}
-	return map[string]string{
-		"filename":    args["filename"],
-		"document_id": strconv.Itoa(int(job.DocumentID)),
-		"id":          strconv.Itoa(int(job.ID)),
-	}, nil
+	return &job, nil
 }
 
-func GetJobs(args map[string]string) (interface{}, error) {
+func (j *Job) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var jobs []*Job
 	if err := DB().Preload("Document").Order("start DESC").Find(&jobs).Error; err != nil {
 		return nil, err
@@ -63,31 +56,44 @@ func GetJobs(args map[string]string) (interface{}, error) {
 	return jobs, nil
 }
 
-func GetJob(args map[string]string) (interface{}, error) {
-	var job Job
-	if err := DB().Preload("Document").Preload("Files").Preload("Logs").First(&job, args["id"]).Error; err != nil {
-		return nil, err
+func (j *Job) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := j.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		job := result.(*Job)
+
+		return job, nil
+	} else {
+		job, err := j.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		err = renderer.Decode(job, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveJob(job)
+		if err != nil {
+			return nil, err
+		}
+		job, err = j.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		return job.(*Job), nil
 	}
-	return &job, nil
 }
 
-func UpdateJob(args map[string]string, r *http.Request) (IDer, error) {
-	job, err := GetJob(args)
+func (j *Job) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	job, err := j.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
-
-	err = renderer.Decode(job, r)
-	if err != nil {
-		return nil, err
-	}
-
-	_, err = SaveJob(job)
-	if err != nil {
-		return nil, err
-	}
-	job, err = GetJob(args)
-	if err != nil {
+	if err := DB().Unscoped().Delete(job.(*Job)).Error; err != nil {
 		return nil, err
 	}
 	return job.(*Job), nil
@@ -99,14 +105,3 @@ func SaveJob(job interface{}) (interface{}, error) {
 	}
 	return job, nil
 }
-
-func DeleteJob(args map[string]string, r *http.Request) (IDer, error) {
-	job, err := GetJob(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(job.(*Job)).Error; err != nil {
-		return nil, err
-	}
-	return job.(*Job), nil
-}

+ 60 - 220
orm/mappings.go

@@ -1,229 +1,69 @@
 package orm
 
-var (
-	Get map[string]GetFn = map[string]GetFn{
-		// Teacher
-		"/teachers":             GetTeachersFast,
-		"/teachers/{id}":        GetTeacherAll,
-		"/teachers/{id}/update": GetTeacher,
-		"/teachers/add/":        GetNothing,
-
-		"/api/teachers":             GetTeachersAll,
-		"/api/teachers/{id}":        GetTeacherAll,
-		"/api/teachers/{id}/update": GetTeacher,
-		"/api/teachers/add/":        GetNothing,
-
-		// Student
-		"/students":             GetStudentsFast,
-		"/students/{id}":        GetStudentAll,
-		"/students/{id}/update": GetStudentForUpdate,
-		"/students/add/":        GetStudentForAdd,
-
-		"/api/students":             GetStudentsAll,
-		"/api/students/{id}":        GetStudentAll,
-		"/api/students/{id}/update": GetStudentForUpdate,
-		"/api/students/add/":        GetStudentForAdd,
-
-		// Classes
-		"/classes":             GetClassesAll,
-		"/classes/{id}":        GetClassAll,
-		"/classes/{id}/update": GetClassForUpdate,
-		"/classes/add/":        GetClassForAdd,
-
-		"/api/classes":             GetClassesAll,
-		"/api/classes/{id}":        GetClassAll,
-		"/api/classes/{id}/update": GetClassForUpdate,
-		"/api/classes/add/":        GetClassForAdd,
-
-		// Subjects
-		"/subjects":             GetSubjectsAll,
-		"/subjects/{id}":        GetSubjectAll,
-		"/subjects/{id}/update": GetSubjectForUpdate,
-		"/subjects/add/":        GetSubjectForAdd,
-
-		"/api/subjects":             GetSubjectsAll,
-		"/api/subjects/{id}":        GetSubjectAll,
-		"/api/subjects/{id}/update": GetSubjectForUpdate,
-		"/api/subjects/add/":        GetSubjectForAdd,
-
-		// Departments
-		"/departments":             GetDepartmentsAll,
-		"/departments/{id}":        GetDepartmentAll,
-		"/departments/{id}/update": GetDepartmentForUpdate,
-		"/departments/add/":        GetDepartmentForAdd,
-
-		"/api/departments":             GetDepartmentsAll,
-		"/api/departments/{id}":        GetDepartmentAll,
-		"/api/departments/{id}/update": GetDepartment,
-		"/api/departments/add/":        GetNothing,
-
-		// Activity
-		"/activities":             GetActivitiesAll,
-		"/activities/{id}":        GetActivityAll,
-		"/activities/{id}/update": GetActivityForUpdate,
-		"/activities/add/":        GetActivityForAdd,
-
-		"/api/activities":             GetActivitiesAll,
-		"/api/activities/{id}":        GetActivityAll,
-		"/api/activities/{id}/update": GetActivityForUpdate,
-		"/api/activities/add/":        GetActivityForAdd,
-
-		// Offices
-		"/offices":             GetOfficesAll,
-		"/offices/{id}":        GetOfficeAll,
-		"/offices/{id}/update": GetOfficeForUpdate,
-		"/offices/add/":        GetOfficeForAdd,
-
-		"/api/offices":             GetOfficesAll,
-		"/api/offices/{id}":        GetOfficeAll,
-		"/api/offices/{id}/update": GetOffice,
-		"/api/offices/add/":        GetNothing,
-
-		// Administratives
-		"/administratives":             GetAdministrativesAll,
-		"/administratives/{id}":        GetAdministrativeAll,
-		"/administratives/{id}/update": GetAdministrativeForUpdate,
-		"/administratives/add/":        GetAdministrativeForAdd,
-
-		"/api/administratives":             GetAdministrativesAll,
-		"/api/administratives/{id}":        GetAdministrativeAll,
-		"/api/administratives/{id}/update": GetAdministrative,
-		"/api/administratives/add/":        GetNothing,
-
-		// Documents
-
-		"/documents":              GetDocumentsAll,
-		"/documents/{id}":         GetDocumentAll,
-		"/documents/{id}/update":  GetDocumentForUpdate,
-		"/documents/{id}/execute": GetDocumentExecute,
-		"/documents/add/":         GetDocumentForAdd,
-
-		"/api/documents":             GetDocumentsAll,
-		"/api/documents/{id}":        GetDocumentAll,
-		"/api/documents/{id}/update": GetDocument,
-		"/api/documents/add/":        GetNothing,
-
-		// Jobs
-
-		"/jobs":                       GetJobs,
-		"/jobs/{id}":                  GetJob,
-		"/jobs/{id}/files/{filename}": GetFile,
-
-		"/api/jobs/{id}": GetJob,
-		"/api/jobs/add/": GetNothing,
+import (
+	"fmt"
+	"net/http"
+	"path"
+	"reflect"
+	"strings"
+
+	"github.com/jinzhu/inflection"
+)
 
-		// Groups
+var (
+	fns map[string]func(map[string]string, *http.Request) (interface{}, error)
+)
 
-		"/groups":             GetGroupsAll,
-		"/groups/{id}":        GetGroupAll,
-		"/groups/{id}/update": GetGroupForUpdate,
-		"/groups/add/":        GetGroupForAdd,
+func init() {
+	fns = make(map[string]func(map[string]string, *http.Request) (interface{}, error), 0)
+}
+
+func MapHandlers(models []interface{}) error {
+	for _, model := range models {
+		name := inflection.Plural(strings.ToLower(modelName(model)))
+		for p, action := range map[string]string{
+			"":            "ReadAll",
+			"create/":     "Create",
+			"{id}":        "Read",
+			"{id}/update": "Update",
+			"{id}/delete": "Delete",
+		} {
+			method := reflect.ValueOf(model).MethodByName(action)
+			if !method.IsValid() {
+				return fmt.Errorf("Action %s is not defined for model %s", action, name)
+			}
+			joinedPath := path.Join("/", name, p)
+			if strings.HasSuffix(p, "/") {
+				joinedPath += "/"
+			}
+			fns[joinedPath] = method.Interface().(func(map[string]string, *http.Request) (interface{}, error))
+		}
 
-		"/api/groups":             GetGroupsAll,
-		"/api/groups/{id}":        GetGroupAll,
-		"/api/groups/{id}/update": GetGroup,
-		"/api/groups/add/":        GetNothing,
 	}
 
-	Post map[string]PostFn = map[string]PostFn{
-		// Teacher
-		"/teachers/{id}/update": UpdateTeacher,
-		"/teachers/{id}/delete": DeleteTeacher,
-		"/teachers/add/":        AddTeacher,
+	return nil
+}
 
-		"/api/teachers/add":         AddTeacher,
-		"/api/teachers/{id}/update": UpdateTeacher,
-		"/api/teachers/{id}/delete": DeleteTeacher,
-		"/api/teachers/add/":        AddTeacher,
-
-		// Student
-		"/students/{id}/update": UpdateStudent,
-		"/students/{id}/delete": DeleteStudent,
-		"/students/add/":        AddStudent,
-
-		"/api/students/add":         AddStudent,
-		"/api/students/{id}/update": UpdateStudent,
-		"/api/students/{id}/delete": DeleteStudent,
-		"/api/students/add/":        AddStudent,
-
-		// Classes
-		"/classes/{id}/update": UpdateClass,
-		"/classes/{id}/delete": DeleteClass,
-		"/classes/add/":        AddClass,
-
-		"/api/classes/{id}/update": UpdateClass,
-		"/api/classes/{id}/delete": DeleteClass,
-		"/api/classes/add/":        AddClass,
-
-		// Subjects
-		"/subjects/{id}/update": UpdateSubject,
-		"/subjects/{id}/delete": DeleteSubject,
-		"/subjects/add/":        AddSubject,
-
-		"/api/subjects/{id}/update": UpdateSubject,
-		"/api/subjects/{id}/delete": DeleteSubject,
-		"/api/subjects/add/":        AddSubject,
-
-		// Departments
-		"/departments/{id}/update": UpdateDepartment,
-		"/departments/{id}/delete": DeleteDepartment,
-		"/departments/add/":        AddDepartment,
-
-		"/api/departments/{id}/update": UpdateDepartment,
-		"/api/departments/{id}/delete": DeleteDepartment,
-		"/api/departments/add/":        AddDepartment,
-
-		// Activity
-		"/activities/{id}/update": UpdateActivity,
-		"/activities/{id}/delete": DeleteActivity,
-		"/activities/add/":        AddActivity,
-
-		"/api/activities/{id}/update": UpdateActivity,
-		"/api/activities/{id}/delete": DeleteActivity,
-		"/api/activities/add/":        AddActivity,
-
-		// Offices
-		"/offices/{id}/update": UpdateOffice,
-		"/offices/{id}/delete": DeleteOffice,
-		"/offices/add/":        AddOffice,
-
-		"/api/offices/{id}/update": UpdateOffice,
-		"/api/offices/{id}/delete": DeleteOffice,
-		"/api/offices/add/":        AddOffice,
-
-		// Administratives
-		"/administratives/{id}/update": UpdateAdministrative,
-		"/administratives/{id}/delete": DeleteAdministrative,
-		"/administratives/add/":        AddAdministrative,
-
-		"/api/administratives/{id}/update": UpdateAdministrative,
-		"/api/administratives/{id}/delete": DeleteAdministrative,
-		"/api/administratives/add/":        AddAdministrative,
-
-		// Documents
-		"/documents/{id}/update": UpdateDocument,
-		"/documents/{id}/delete": DeleteDocument,
-		"/documents/add/":        AddDocument,
-
-		"/api/documents/{id}/update": UpdateDocument,
-		"/api/documents/{id}/delete": DeleteDocument,
-		"/api/documents/add/":        AddDocument,
-
-		// Jobs
-
-		"/jobs/{id}/delete": DeleteJob,
-
-		"/api/jobs/{id}/update": UpdateJob,
-		"/api/jobs/{id}/delete": DeleteJob,
-
-		// Groups
-
-		"/groups/{id}/update": UpdateGroup,
-		"/groups/{id}/delete": DeleteGroup,
-		"/groups/add/":        AddGroup,
-
-		"/api/groups/{id}/update": UpdateGroup,
-		"/api/groups/{id}/delete": DeleteGroup,
-		"/api/groups/add/":        AddGroup,
+func GetFunc(path string) (GetFn, error) {
+	fn, ok := fns[path]
+	if !ok {
+		return nil, fmt.Errorf("Can't map path %s to any model methods.", path)
 	}
-)
+	return fn, nil
+}
+
+func GetNothing(args map[string]string) (interface{}, error) {
+	return nil, nil
+}
+
+func PostNothing(args map[string]string, r *http.Request) (IDer, error) {
+	return nil, nil
+}
+
+func modelName(s interface{}) string {
+	if t := reflect.TypeOf(s); t.Kind() == reflect.Ptr {
+		return t.Elem().Name()
+	} else {
+		return t.Name()
+	}
+}

+ 236 - 0
orm/mappings.go.old

@@ -0,0 +1,236 @@
+package orm
+
+import (
+	"path"
+	"reflect"
+	"strings"
+
+	"github.com/jinzhu/inflection"
+)
+
+var (
+	Get map[string]GetFn = map[string]GetFn{
+		// Teacher
+		"/teachers":             GetTeachersFast,
+		"/teachers/{id}":        GetTeacherAll,
+		"/teachers/{id}/update": GetTeacher,
+		"/teachers/add/":        GetNothing,
+
+		"/api/teachers":             GetTeachersAll,
+		"/api/teachers/{id}":        GetTeacherAll,
+		"/api/teachers/{id}/update": GetTeacher,
+		"/api/teachers/add/":        GetNothing,
+
+		// Student
+		"/students":             GetStudentsFast,
+		"/students/{id}":        GetStudentAll,
+		"/students/{id}/update": GetStudentForUpdate,
+		"/students/add/":        GetStudentForAdd,
+
+		"/api/students":             GetStudentsAll,
+		"/api/students/{id}":        GetStudentAll,
+		"/api/students/{id}/update": GetStudentForUpdate,
+		"/api/students/add/":        GetStudentForAdd,
+
+		// Classes
+		"/classes":             GetClassesAll,
+		"/classes/{id}":        GetClassAll,
+		"/classes/{id}/update": GetClassForUpdate,
+		"/classes/add/":        GetClassForAdd,
+
+		"/api/classes":             GetClassesAll,
+		"/api/classes/{id}":        GetClassAll,
+		"/api/classes/{id}/update": GetClassForUpdate,
+		"/api/classes/add/":        GetClassForAdd,
+
+		// Subjects
+		"/subjects":             GetSubjectsAll,
+		"/subjects/{id}":        GetSubjectAll,
+		"/subjects/{id}/update": GetSubjectForUpdate,
+		"/subjects/add/":        GetSubjectForAdd,
+
+		"/api/subjects":             GetSubjectsAll,
+		"/api/subjects/{id}":        GetSubjectAll,
+		"/api/subjects/{id}/update": GetSubjectForUpdate,
+		"/api/subjects/add/":        GetSubjectForAdd,
+
+		// Departments
+		"/departments":             GetDepartmentsAll,
+		"/departments/{id}":        GetDepartmentAll,
+		"/departments/{id}/update": GetDepartmentForUpdate,
+		"/departments/add/":        GetDepartmentForAdd,
+
+		"/api/departments":             GetDepartmentsAll,
+		"/api/departments/{id}":        GetDepartmentAll,
+		"/api/departments/{id}/update": GetDepartment,
+		"/api/departments/add/":        GetNothing,
+
+		// Activity
+		"/activities":             GetActivitiesAll,
+		"/activities/{id}":        GetActivityAll,
+		"/activities/{id}/update": GetActivityForUpdate,
+		"/activities/add/":        GetActivityForAdd,
+
+		"/api/activities":             GetActivitiesAll,
+		"/api/activities/{id}":        GetActivityAll,
+		"/api/activities/{id}/update": GetActivityForUpdate,
+		"/api/activities/add/":        GetActivityForAdd,
+
+		// Offices
+		"/offices":             GetOfficesAll,
+		"/offices/{id}":        GetOfficeAll,
+		"/offices/{id}/update": GetOfficeForUpdate,
+		"/offices/add/":        GetOfficeForAdd,
+
+		"/api/offices":             GetOfficesAll,
+		"/api/offices/{id}":        GetOfficeAll,
+		"/api/offices/{id}/update": GetOffice,
+		"/api/offices/add/":        GetNothing,
+
+		// Administratives
+		"/administratives":             GetAdministrativesAll,
+		"/administratives/{id}":        GetAdministrativeAll,
+		"/administratives/{id}/update": GetAdministrativeForUpdate,
+		"/administratives/add/":        GetAdministrativeForAdd,
+
+		"/api/administratives":             GetAdministrativesAll,
+		"/api/administratives/{id}":        GetAdministrativeAll,
+		"/api/administratives/{id}/update": GetAdministrative,
+		"/api/administratives/add/":        GetNothing,
+
+		// Documents
+		"/documents":              GetDocumentsAll,
+		"/documents/{id}":         GetDocumentAll,
+		"/documents/{id}/update":  GetDocumentForUpdate,
+		"/documents/{id}/execute": GetDocumentExecute,
+		"/documents/add/":         GetDocumentForAdd,
+
+		"/api/documents":             GetDocumentsAll,
+		"/api/documents/{id}":        GetDocumentAll,
+		"/api/documents/{id}/update": GetDocument,
+		"/api/documents/add/":        GetNothing,
+
+		// Jobs
+
+		"/jobs":                       GetJobs,
+		"/jobs/{id}":                  GetJob,
+		"/jobs/{id}/files/{filename}": GetFile,
+
+		"/api/jobs/{id}": GetJob,
+		"/api/jobs/add/": GetNothing,
+	}
+
+	Post map[string]PostFn = map[string]PostFn{
+		// Teacher
+		"/teachers/{id}/update": UpdateTeacher,
+		"/teachers/{id}/delete": DeleteTeacher,
+		"/teachers/add/":        AddTeacher,
+
+		"/api/teachers/add":         AddTeacher,
+		"/api/teachers/{id}/update": UpdateTeacher,
+		"/api/teachers/{id}/delete": DeleteTeacher,
+		"/api/teachers/add/":        AddTeacher,
+
+		// Student
+		"/students/{id}/update": UpdateStudent,
+		"/students/{id}/delete": DeleteStudent,
+		"/students/add/":        AddStudent,
+
+		"/api/students/add":         AddStudent,
+		"/api/students/{id}/update": UpdateStudent,
+		"/api/students/{id}/delete": DeleteStudent,
+		"/api/students/add/":        AddStudent,
+
+		// Classes
+		"/classes/{id}/update": UpdateClass,
+		"/classes/{id}/delete": DeleteClass,
+		"/classes/add/":        AddClass,
+
+		"/api/classes/{id}/update": UpdateClass,
+		"/api/classes/{id}/delete": DeleteClass,
+		"/api/classes/add/":        AddClass,
+
+		// Subjects
+		"/subjects/{id}/update": UpdateSubject,
+		"/subjects/{id}/delete": DeleteSubject,
+		"/subjects/add/":        AddSubject,
+
+		"/api/subjects/{id}/update": UpdateSubject,
+		"/api/subjects/{id}/delete": DeleteSubject,
+		"/api/subjects/add/":        AddSubject,
+
+		// Departments
+		"/departments/{id}/update": UpdateDepartment,
+		"/departments/{id}/delete": DeleteDepartment,
+		"/departments/add/":        AddDepartment,
+
+		"/api/departments/{id}/update": UpdateDepartment,
+		"/api/departments/{id}/delete": DeleteDepartment,
+		"/api/departments/add/":        AddDepartment,
+
+		// Activity
+		"/activities/{id}/update": UpdateActivity,
+		"/activities/{id}/delete": DeleteActivity,
+		"/activities/add/":        AddActivity,
+
+		"/api/activities/{id}/update": UpdateActivity,
+		"/api/activities/{id}/delete": DeleteActivity,
+		"/api/activities/add/":        AddActivity,
+
+		// Offices
+		"/offices/{id}/update": UpdateOffice,
+		"/offices/{id}/delete": DeleteOffice,
+		"/offices/add/":        AddOffice,
+
+		"/api/offices/{id}/update": UpdateOffice,
+		"/api/offices/{id}/delete": DeleteOffice,
+		"/api/offices/add/":        AddOffice,
+
+		// Administratives
+		"/administratives/{id}/update": UpdateAdministrative,
+		"/administratives/{id}/delete": DeleteAdministrative,
+		"/administratives/add/":        AddAdministrative,
+
+		"/api/administratives/{id}/update": UpdateAdministrative,
+		"/api/administratives/{id}/delete": DeleteAdministrative,
+		"/api/administratives/add/":        AddAdministrative,
+
+		// Documents
+		"/documents/{id}/update": UpdateDocument,
+		"/documents/{id}/delete": DeleteDocument,
+		"/documents/add/":        AddDocument,
+
+		"/api/documents/{id}/update": UpdateDocument,
+		"/api/documents/{id}/delete": DeleteDocument,
+		"/api/documents/add/":        AddDocument,
+
+		// Jobs
+
+		"/jobs/{id}/delete": DeleteJob,
+
+		"/api/jobs/{id}/update": UpdateJob,
+		"/api/jobs/{id}/delete": DeleteJob,
+	}
+)
+
+func MapModels(models []interface{}, handlers map[string]string) {
+	for _, model := range models {
+		name := inflection.Plural(strings.ToLower(modelName(model)))
+		for p, action := range map[string]string{
+			"":            "GetAll",
+			"create":      "Create",
+			"{id}":        "Get",
+			"{id}/udpate": "Update",
+		} {
+			getFns[path.Join("/", name, p)] = reflect.ValueOf(model).MethodByName(action).Interface().(func(map[string]string) (interface{}, error))
+		}
+	}
+}
+
+func modelName(s interface{}) string {
+	if t := reflect.TypeOf(s); t.Kind() == reflect.Ptr {
+		return t.Elem().Name()
+	} else {
+		return t.Name()
+	}
+}

+ 55 - 90
orm/office.go

@@ -16,23 +16,31 @@ type Office struct {
 	Administratives []*Administrative
 }
 
-type OfficeForUpdate struct {
-	Office Office
-}
-
-type OfficeForAdd struct{}
-
 func (d *Office) GetID() uint { return d.ID }
 
-func GetOffice(args map[string]string) (interface{}, error) {
-	var office Office
-	if err := DB().First(&office, args["id"]).Error; err != nil {
-		return nil, err
+func (o *Office) String() string {
+	return o.Name
+}
+
+func (o *Office) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		return nil, nil
+	} else {
+		office := new(Office)
+		err := renderer.Decode(office, r)
+		if err != nil {
+			return nil, err
+		}
+		office, err = CreateOffice(office)
+		if err != nil {
+			return nil, err
+		}
+
+		return office, nil
 	}
-	return &office, nil
 }
 
-func GetOfficeAll(args map[string]string) (interface{}, error) {
+func (o *Office) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var office Office
 
 	id := args["id"]
@@ -40,67 +48,64 @@ func GetOfficeAll(args map[string]string) (interface{}, error) {
 	if err := DB().Preload("Administratives").Where("id = ?", id).Find(&office).Error; err != nil {
 		return nil, err
 	}
-
 	return &office, nil
 }
 
-func GetOffices(args map[string]string) (interface{}, error) {
-	var offices []*Office
-	if err := DB().Order("name").Find(&offices).Error; err != nil {
-		return nil, err
-	}
-	return offices, nil
-}
-
-func GetOfficesAll(args map[string]string) (interface{}, error) {
+func (o *Office) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var offices []*Office
 	if err := DB().Preload("Administratives").Order("name").Find(&offices).Error; err != nil {
 		return nil, err
 	}
-	// for _, office := range offices {
-	// 	office.GetAdministratives()
-	// }
 	return offices, nil
 }
 
-func SaveOffice(office interface{}) (interface{}, error) {
-	if err := DB().Omit("Administratives").Save(office).Error; err != nil {
-		return nil, err
+func (o *Office) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := o.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		office := result.(*Office)
+
+		return office, nil
+	} else {
+		office, err := o.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		err = renderer.Decode(office, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveOffice(office)
+		if err != nil {
+			return nil, err
+		}
+		office, err = o.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		return office.(*Office), nil
 	}
-	return office, nil
 }
 
-func UpdateOffice(args map[string]string, r *http.Request) (IDer, error) {
-	office, err := GetOffice(args)
-	if err != nil {
-		return nil, err
-	}
-	err = renderer.Decode(office, r)
-	if err != nil {
-		return nil, err
-	}
-	_, err = SaveOffice(office)
+func (o *Office) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	office, err := o.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
-	office, err = GetOffice(args)
-	if err != nil {
+	if err := DB().Unscoped().Delete(office.(*Office)).Error; err != nil {
 		return nil, err
 	}
 	return office.(*Office), nil
 }
 
-func AddOffice(args map[string]string, r *http.Request) (IDer, error) {
-	office := new(Office)
-	err := renderer.Decode(office, r)
-	if err != nil {
-		return nil, err
-	}
-	office, err = CreateOffice(office)
-	if err != nil {
+func SaveOffice(office interface{}) (interface{}, error) {
+	if err := DB().Omit("Administratives").Save(office).Error; err != nil {
 		return nil, err
 	}
-
 	return office, nil
 }
 
@@ -110,43 +115,3 @@ func CreateOffice(office *Office) (*Office, error) {
 	}
 	return office, nil
 }
-
-func DeleteOffice(args map[string]string, r *http.Request) (IDer, error) {
-	office, err := GetOffice(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(office.(*Office)).Error; err != nil {
-		return nil, err
-	}
-	return office.(*Office), nil
-}
-
-func GetOfficeForAdd(args map[string]string) (interface{}, error) {
-	var data OfficeForAdd
-
-	// if err := DB().Find(&data.AllTeachers).Error; err != nil {
-	// 	return nil, err
-	// }
-
-	return data, nil
-}
-
-func GetOfficeForUpdate(args map[string]string) (interface{}, error) {
-	var data OfficeForUpdate
-
-	id := args["id"]
-
-	if err := DB().First(&data.Office, id).Error; err != nil {
-		return nil, err
-	}
-
-	return data, nil
-}
-
-// func (t *Office) GetAdministratives() ([]*Administrative, error) {
-// 	if err := DB().Raw(selectUniqueOfficeTeachers, t.ID).Scan(&t.Teachers).Error; err != nil {
-// 		return nil, err
-// 	}
-// 	return t.Teachers, nil
-// }

+ 1 - 29
orm/orm.go

@@ -1,7 +1,6 @@
 package orm
 
 import (
-	"fmt"
 	"net/http"
 
 	"github.com/jinzhu/gorm"
@@ -45,8 +44,7 @@ type Desk struct {
 	Teacher  *Teacher
 }
 
-type GetFn func(map[string]string) (interface{}, error)
-type PostFn func(map[string]string, *http.Request) (IDer, error)
+type GetFn func(map[string]string, *http.Request) (interface{}, error)
 
 var currDB *gorm.DB
 
@@ -88,29 +86,3 @@ func AutoMigrate() {
 	}
 
 }
-
-func GetFunc(path string) (GetFn, error) {
-	fn, ok := Get[path]
-	if !ok {
-		return nil, fmt.Errorf("Can't map %s!", path)
-	}
-
-	return fn, nil
-}
-
-func PostFunc(path string) (PostFn, error) {
-	fn, ok := Post[path]
-	if !ok {
-		return nil, fmt.Errorf("Can't map %s!", path)
-	}
-
-	return fn, nil
-}
-
-func GetNothing(args map[string]string) (interface{}, error) {
-	return nil, nil
-}
-
-func PostNothing(args map[string]string, r *http.Request) (IDer, error) {
-	return nil, nil
-}

+ 2 - 2
orm/orm_test.go

@@ -114,7 +114,7 @@ func (t *testSuite) TestDeleteActivity() {
 }
 
 func (t *testSuite) TestGetTeacher() {
-	teacher, err := GetTeacher(map[string]string{"id": "1"})
+	teacher, err := new(Teacher).Read(map[string]string{"id": "1"})
 	t.Nil(err)
 	if !t.Failed() {
 		t.Equal("Amilcare", teacher.(*Teacher).Name)
@@ -161,7 +161,7 @@ func (t *testSuite) TestGetStudentAll() {
 }
 
 func (t *testSuite) TestGetTeachers() {
-	teachers, err := GetTeachers(map[string]string{})
+	teachers, err := new(Teacher).ReadAll(map[string]string{})
 	t.Nil(err)
 	t.Equal(11, len(teachers.([]*Teacher)))
 

+ 89 - 117
orm/student.go

@@ -10,9 +10,13 @@ import (
 type Student struct {
 	Credential
 
-	Handicap bool `schema:"Handicap" sql:"default: false"`
-	DSA      bool `schema:"DSA" sql:"default: false"`
-	BES      bool `schema:"BES" sql:"default: false"`
+	// Handicap bool `schema:"Handicap" sql:"default: false"`
+	// DSA      bool `schema:"DSA" sql:"default: false"`
+	// BES      bool `schema:"BES" sql:"default: false"`
+
+	Handicap bool
+	DSA      bool
+	BES      bool
 
 	TutorID uint `schema:"tutor_id"`
 	ClassID uint `schema:"class_id"`
@@ -21,20 +25,12 @@ type Student struct {
 	Tutor      *Teacher
 	Teachers   []*Teacher
 	Activities []*Activity
-}
 
-type StudentForUpdate struct {
-	Student     Student
-	AllClasses  []*Class
-	AllTeachers []*Teacher
+	AllClasses  []*Class   `gorm:"-"`
+	AllTeachers []*Teacher `gorm:"-"`
 
-	SelectedClass   map[uint]string
-	SelectedTeacher map[uint]string
-}
-
-type StudentForAdd struct {
-	AllClasses  []*Class
-	AllTeachers []*Teacher
+	SelectedClass   map[uint]string `gorm:"-"`
+	SelectedTeacher map[uint]string `gorm:"-"`
 }
 
 var (
@@ -45,17 +41,36 @@ WHERE student_id=?
 `
 )
 
-func (s *Student) GetID() uint { return s.ID }
+func (s *Student) GetID() uint    { return s.ID }
+func (s *Student) String() string { return s.CompleteName() }
 
-func GetStudent(args map[string]string) (interface{}, error) {
-	var student Student
-	if err := DB().First(&student, args["id"]).Error; err != nil {
-		return nil, err
+func (s *Student) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		student := new(Student)
+		if err := DB().Find(&student.AllTeachers).Error; err != nil {
+			return nil, err
+		}
+		if err := DB().Find(&student.AllClasses).Error; err != nil {
+			return nil, err
+		}
+
+		return student, nil
+	} else {
+		student := new(Student)
+		err := renderer.Decode(student, r)
+		if err != nil {
+			return nil, err
+		}
+		student, err = CreateStudent(student)
+		if err != nil {
+			return nil, err
+		}
+
+		return student, nil
 	}
-	return &student, nil
 }
 
-func GetStudentAll(args map[string]string) (interface{}, error) {
+func (s *Student) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var student Student
 
 	id := args["id"]
@@ -71,24 +86,9 @@ func GetStudentAll(args map[string]string) (interface{}, error) {
 	return &student, nil
 }
 
-func GetStudents(args map[string]string) (interface{}, error) {
-	var students []*Student
-	if err := DB().Order("surname,name").Find(&students).Error; err != nil {
-		return nil, err
-	}
-	return students, nil
-}
-
-func GetStudentsFast(args map[string]string) (interface{}, error) {
+func (s *Student) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var students []*Student
-	if err := DB().Preload("Class").Order("surname,name").Find(&students).Error; err != nil {
-		return nil, err
-	}
-	return students, nil
-}
 
-func GetStudentsAll(args map[string]string) (interface{}, error) {
-	var students []*Student
 	if err := DB().Preload("Class").Preload("Class.Coordinator").Preload("Tutor").Order("surname,name").Find(&students).Error; err != nil {
 		return nil, err
 	}
@@ -98,66 +98,64 @@ func GetStudentsAll(args map[string]string) (interface{}, error) {
 	for _, student := range students {
 		student.GetActivities()
 	}
+
 	return students, nil
 }
 
-func SaveStudent(student interface{}) (interface{}, error) {
-	if err := DB().Omit("Class", "Tutor", "Teachers", "Activities").Save(student).Error; err != nil {
-		return nil, err
-	}
-	return student, nil
-}
+func (s *Student) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := s.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func UpdateStudent(args map[string]string, r *http.Request) (IDer, error) {
-	student, err := GetStudent(args)
-	if err != nil {
-		return nil, err
-	}
+		student := result.(*Student)
 
-	// FIXME: Should not be hard set.
-	student.(*Student).Handicap = false
-	student.(*Student).DSA = false
-	student.(*Student).BES = false
+		if err := DB().Find(&student.AllTeachers).Error; err != nil {
+			return nil, err
+		}
 
-	err = renderer.Decode(student, r)
-	if err != nil {
-		return nil, err
-	}
+		if err := DB().Find(&student.AllClasses).Error; err != nil {
+			return nil, err
+		}
 
-	_, err = SaveStudent(student)
-	if err != nil {
-		return nil, err
-	}
-	student, err = GetStudent(args)
-	if err != nil {
-		return nil, err
-	}
-	return student.(*Student), nil
-}
+		student.SelectedTeacher = make(map[uint]string)
+		student.SelectedTeacher[student.TutorID] = "selected"
 
-func AddStudent(args map[string]string, r *http.Request) (IDer, error) {
-	student := new(Student)
-	err := renderer.Decode(student, r)
-	if err != nil {
-		return nil, err
-	}
-	student, err = CreateStudent(student)
-	if err != nil {
-		return nil, err
-	}
+		student.SelectedClass = make(map[uint]string)
+		student.SelectedClass[student.ClassID] = "selected"
 
-	return student, nil
-}
+		return student, nil
+	} else {
+		student, err := s.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
 
-func CreateStudent(student *Student) (*Student, error) {
-	if err := DB().Create(student).Error; err != nil {
-		return nil, err
+		// FIXME: Should not be hard set.
+		student.(*Student).Handicap = false
+		student.(*Student).DSA = false
+		student.(*Student).BES = false
+
+		err = renderer.Decode(student, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveStudent(student)
+		if err != nil {
+			return nil, err
+		}
+		student, err = s.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		return student.(*Student), nil
 	}
-	return student, nil
 }
 
-func DeleteStudent(args map[string]string, r *http.Request) (IDer, error) {
-	student, err := GetStudent(args)
+func (s *Student) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	student, err := s.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
@@ -167,44 +165,18 @@ func DeleteStudent(args map[string]string, r *http.Request) (IDer, error) {
 	return student.(*Student), nil
 }
 
-func GetStudentForUpdate(args map[string]string) (interface{}, error) {
-	var data StudentForUpdate
-
-	id := args["id"]
-
-	if err := DB().Preload("Class").First(&data.Student, id).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllClasses).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
+func SaveStudent(student interface{}) (interface{}, error) {
+	if err := DB().Omit("Class", "Tutor", "Teachers", "Activities").Save(student).Error; err != nil {
 		return nil, err
 	}
-
-	data.SelectedClass = make(map[uint]string)
-	data.SelectedClass[data.Student.ClassID] = "selected"
-
-	data.SelectedTeacher = make(map[uint]string)
-	data.SelectedTeacher[data.Student.TutorID] = "selected"
-
-	return data, nil
+	return student, nil
 }
 
-func GetStudentForAdd(args map[string]string) (interface{}, error) {
-	var data StudentForAdd
-
-	if err := DB().Find(&data.AllClasses).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllTeachers).Error; err != nil {
+func CreateStudent(student *Student) (*Student, error) {
+	if err := DB().Create(student).Error; err != nil {
 		return nil, err
 	}
-
-	return data, nil
+	return student, nil
 }
 
 func (s *Student) GetTeachers() ([]*Teacher, error) {

+ 70 - 98
orm/subject.go

@@ -21,38 +21,46 @@ type Subject struct {
 	Alias string
 
 	DepartmentID uint `schema:"department_id"`
+	Department   *Department
 
 	Teachers   []*Teacher
 	Activities []*Activity
-}
-
-type SubjectForUpdate struct {
-	Subject Subject
-
-	SelectedDepartment map[uint]string
-	AllDepartments     []*Department
-}
 
-type SubjectForAdd struct {
-	AllDepartments []*Department
+	AllDepartments []*Department   `gorm:"-"`
+	Selected       map[uint]string `gorm:"-"`
 }
 
-func (s *Subject) GetID() uint { return s.ID }
-
-func GetSubject(args map[string]string) (interface{}, error) {
-	var subject Subject
-	if err := DB().First(&subject, args["id"]).Error; err != nil {
-		return nil, err
+func (s *Subject) GetID() uint    { return s.ID }
+func (s *Subject) String() string { return s.Name }
+
+func (s *Subject) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		subject := new(Subject)
+		if err := DB().Find(&subject.AllDepartments).Error; err != nil {
+			return nil, err
+		}
+		return subject, nil
+	} else {
+		subject := new(Subject)
+		err := renderer.Decode(subject, r)
+		if err != nil {
+			return nil, err
+		}
+		subject, err = CreateSubject(subject)
+		if err != nil {
+			return nil, err
+		}
+
+		return subject, nil
 	}
-	return &subject, nil
 }
 
-func GetSubjectAll(args map[string]string) (interface{}, error) {
+func (s *Subject) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var subject Subject
 
 	id := args["id"]
 
-	if err := DB().First(&subject, id).Error; err != nil {
+	if err := DB().Preload("Department").Where("id = ?", id).Find(&subject).Error; err != nil {
 		return nil, err
 	}
 
@@ -60,68 +68,72 @@ func GetSubjectAll(args map[string]string) (interface{}, error) {
 		return nil, err
 	}
 
-	if err := DB().Preload("Teacher").Preload("Subject").Preload("Class").Where("subject_id=?", id).Find(&subject.Activities).Error; err != nil {
-		return nil, err
-	}
-
 	return &subject, nil
 }
 
-func GetSubjects(args map[string]string) (interface{}, error) {
+func (s *Subject) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var subjects []*Subject
-	if err := DB().Order("name").Find(&subjects).Error; err != nil {
-		return nil, err
-	}
-	return subjects, nil
-}
-
-func GetSubjectsAll(args map[string]string) (interface{}, error) {
-	var subjects []*Subject
-	if err := DB().Order("name").Find(&subjects).Error; err != nil {
+	if err := DB().Preload("Department").Order("name").Find(&subjects).Error; err != nil {
 		return nil, err
 	}
 	for _, subject := range subjects {
 		subject.GetTeachers()
 	}
-
 	return subjects, nil
 }
 
-func SaveSubject(subject interface{}) (interface{}, error) {
-	if err := DB().Save(subject).Error; err != nil {
-		return nil, err
+func (s *Subject) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		result, err := s.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		subject := result.(*Subject)
+
+		if err := DB().Find(&subject.AllDepartments).Error; err != nil {
+			return nil, err
+		}
+
+		subject.Selected = make(map[uint]string)
+		subject.Selected[subject.DepartmentID] = "selected"
+
+		return subject, nil
+	} else {
+		subject, err := s.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+		err = renderer.Decode(subject, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveSubject(subject)
+		if err != nil {
+			return nil, err
+		}
+		subject, err = s.Read(args, r)
+		if err != nil {
+			return nil, err
+		}
+
+		return subject.(*Subject), nil
 	}
-	return subject, nil
 }
 
-func UpdateSubject(args map[string]string, r *http.Request) (IDer, error) {
-	subject, err := GetSubject(args)
-	if err != nil {
-		return nil, err
-	}
-	err = renderer.Decode(subject, r)
+func (s *Subject) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	subject, err := s.Read(args, r)
 	if err != nil {
 		return nil, err
 	}
-	_, err = SaveSubject(subject)
-	if err != nil {
-		return nil, err
-	}
-	subject, err = GetSubject(args)
-	if err != nil {
+	if err := DB().Unscoped().Delete(subject.(*Subject)).Error; err != nil {
 		return nil, err
 	}
 	return subject.(*Subject), nil
 }
 
-func AddSubject(args map[string]string, r *http.Request) (IDer, error) {
-	subject := new(Subject)
-	err := renderer.Decode(subject, r)
-	if err != nil {
-		return nil, err
-	}
-	subject, err = CreateSubject(subject)
-	if err != nil {
+func SaveSubject(subject interface{}) (interface{}, error) {
+	if err := DB().Omit("Department").Save(subject).Error; err != nil {
 		return nil, err
 	}
 	return subject, nil
@@ -142,49 +154,9 @@ func CreateSubject(subject *Subject) (*Subject, error) {
 	return subject, nil
 }
 
-func DeleteSubject(args map[string]string, r *http.Request) (IDer, error) {
-	subject, err := GetSubject(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(subject.(*Subject)).Error; err != nil {
-		return nil, err
-	}
-	return subject.(*Subject), nil
-}
-
 func (c *Subject) GetTeachers() ([]*Teacher, error) {
 	if err := DB().Raw(selectUniqueSubjectTeachers, c.ID).Scan(&c.Teachers).Error; err != nil {
 		return nil, err
 	}
 	return c.Teachers, nil
 }
-
-func GetSubjectForUpdate(args map[string]string) (interface{}, error) {
-	var data SubjectForUpdate
-
-	id := args["id"]
-
-	if err := DB().First(&data.Subject, id).Error; err != nil {
-		return nil, err
-	}
-
-	if err := DB().Find(&data.AllDepartments).Error; err != nil {
-		return nil, err
-	}
-
-	data.SelectedDepartment = make(map[uint]string)
-	data.SelectedDepartment[data.Subject.DepartmentID] = "selected"
-
-	return data, nil
-}
-
-func GetSubjectForAdd(args map[string]string) (interface{}, error) {
-	var data SubjectForAdd
-
-	if err := DB().Find(&data.AllDepartments).Error; err != nil {
-		return nil, err
-	}
-
-	return data, nil
-}

+ 58 - 81
orm/teacher.go

@@ -70,15 +70,25 @@ func (t *Teacher) String() string {
 	return fmt.Sprintf("%s %s", t.Surname, t.Name)
 }
 
-func GetTeacher(args map[string]string) (interface{}, error) {
-	var teacher Teacher
-	if err := DB().First(&teacher, args["id"]).Error; err != nil {
-		return nil, err
+func (t *Teacher) Create(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		return nil, nil
+	} else {
+		teacher := new(Teacher)
+		err := renderer.Decode(teacher, r)
+		if err != nil {
+			return nil, err
+		}
+		teacher, err = CreateTeacher(teacher)
+		if err != nil {
+			return nil, err
+		}
+
+		return teacher, nil
 	}
-	return &teacher, nil
 }
 
-func GetTeacherAll(args map[string]string) (interface{}, error) {
+func (t *Teacher) Read(args map[string]string, r *http.Request) (interface{}, error) {
 	var teacher Teacher
 
 	id := args["id"]
@@ -128,19 +138,12 @@ func GetTeacherAll(args map[string]string) (interface{}, error) {
 	return &teacher, nil
 }
 
-func GetTeachers(args map[string]string) (interface{}, error) {
+func (t *Teacher) ReadAll(args map[string]string, r *http.Request) (interface{}, error) {
 	var teachers []*Teacher
 	if err := DB().Order("surname,name").Find(&teachers).Error; err != nil {
 		return nil, err
 	}
-	return teachers, nil
-}
 
-func GetTeachersAll(args map[string]string) (interface{}, error) {
-	var teachers []*Teacher
-	if err := DB().Order("surname,name").Find(&teachers).Error; err != nil {
-		return nil, err
-	}
 	for _, teacher := range teachers {
 		teacher.GetClasses()
 	}
@@ -180,80 +183,65 @@ func GetTeachersAll(args map[string]string) (interface{}, error) {
 	return teachers, nil
 }
 
-func GetTeachersFast(args map[string]string) (interface{}, error) {
-	var teachers []*Teacher
-
-	if err := DB().Order("surname,name").Find(&teachers).Error; err != nil {
-		return nil, err
-	}
-	for _, teacher := range teachers {
-		teacher.GetClasses()
-	}
-	for _, teacher := range teachers {
-		teacher.GetSubjects()
-	}
-
-	return teachers, nil
-}
-
-func SaveTeacher(teacher interface{}) (interface{}, error) {
-	if err := DB().Omit("Classes", "Subjects", "Activities").Save(teacher).Error; err != nil {
-		return nil, err
-	}
-	return teacher, nil
-}
+func (t *Teacher) Update(args map[string]string, r *http.Request) (interface{}, error) {
+	if r.Method == "GET" {
+		return t.Read(args, r)
+	} else {
+		teacher, err := t.Read(args, nil)
+		if err != nil {
+			return nil, err
+		}
 
-func UpdateTeacher(args map[string]string, r *http.Request) (IDer, error) {
-	teacher, err := GetTeacher(args)
-	if err != nil {
-		return nil, err
-	}
+		// FIXME: Should not be hard set.
+		teacher.(*Teacher).Regenerate = false
 
-	// FIXME: Should not be hard set.
-	teacher.(*Teacher).Regenerate = false
+		// FIXME: Should not be hard set.
+		teacher.(*Teacher).Exclude = false
 
-	// FIXME: Should not be hard set.
-	teacher.(*Teacher).Exclude = false
+		// FIXME: Should not be hard set.
+		teacher.(*Teacher).NotInvited = false
 
-	// FIXME: Should not be hard set.
-	teacher.(*Teacher).NotInvited = false
+		if len(args["DateFrom"]) == 0 {
+			// FIXME: Should not be hard set.
+			teacher.(*Teacher).DateFrom = time.Time{}
+		}
 
-	if len(args["DateFrom"]) == 0 {
-		// FIXME: Should not be hard set.
-		teacher.(*Teacher).DateFrom = time.Time{}
-	}
+		if len(args["DateTo"]) == 0 {
+			// FIXME: Should not be hard set.
+			teacher.(*Teacher).DateTo = time.Time{}
+		}
 
-	if len(args["DateTo"]) == 0 {
-		// FIXME: Should not be hard set.
-		teacher.(*Teacher).DateTo = time.Time{}
+		err = renderer.Decode(teacher, r)
+		if err != nil {
+			return nil, err
+		}
+		_, err = SaveTeacher(teacher)
+		if err != nil {
+			return nil, err
+		}
+		teacher, err = t.Read(args, nil)
+		if err != nil {
+			return nil, err
+		}
+		return teacher.(*Teacher), nil
 	}
+}
 
-	err = renderer.Decode(teacher, r)
+func (t *Teacher) Delete(args map[string]string, r *http.Request) (interface{}, error) {
+	teacher, err := t.Read(args, nil)
 	if err != nil {
 		return nil, err
 	}
-	_, err = SaveTeacher(teacher)
-	if err != nil {
-		return nil, err
-	}
-	teacher, err = GetTeacher(args)
-	if err != nil {
+	if err := DB().Unscoped().Delete(teacher.(*Teacher)).Error; err != nil {
 		return nil, err
 	}
 	return teacher.(*Teacher), nil
 }
 
-func AddTeacher(args map[string]string, r *http.Request) (IDer, error) {
-	teacher := new(Teacher)
-	err := renderer.Decode(teacher, r)
-	if err != nil {
-		return nil, err
-	}
-	teacher, err = CreateTeacher(teacher)
-	if err != nil {
+func SaveTeacher(teacher interface{}) (interface{}, error) {
+	if err := DB().Omit("Classes", "Subjects", "Activities").Save(teacher).Error; err != nil {
 		return nil, err
 	}
-
 	return teacher, nil
 }
 
@@ -271,17 +259,6 @@ func CreateTeacher(teacher *Teacher) (*Teacher, error) {
 	return teacher, nil
 }
 
-func DeleteTeacher(args map[string]string, r *http.Request) (IDer, error) {
-	teacher, err := GetTeacher(args)
-	if err != nil {
-		return nil, err
-	}
-	if err := DB().Unscoped().Delete(teacher.(*Teacher)).Error; err != nil {
-		return nil, err
-	}
-	return teacher.(*Teacher), nil
-}
-
 func (t *Teacher) GetClasses() ([]*Class, error) {
 	if err := DB().Raw(selectUniqueTeacherClasses, t.ID, t.ID).Scan(&t.Classes).Error; err != nil {
 		return nil, err

+ 230 - 0
renderer/funcmap.go

@@ -0,0 +1,230 @@
+package renderer
+
+import (
+	"errors"
+	"fmt"
+	"html/template"
+	"net/url"
+	"reflect"
+	"strings"
+	"time"
+
+	"github.com/jinzhu/inflection"
+	yml "gopkg.in/yaml.v2"
+)
+
+var (
+	funcMap = template.FuncMap{
+		"query":       query,
+		"convertDate": convertDate,
+		"modelPath":   modelPath,
+		"dict":        dict,
+		"yaml":        yaml,
+		"create":      create,
+		"update":      update,
+		"delete":      delete,
+		"show":        show,
+		"all":         all,
+		"execute":     execute,
+		"isSlice":     isSlice,
+		"toSlice":     toSlice,
+		"string":      callString,
+		"incr":        incr,
+		"mod2":        mod2,
+		"toLower":     toLower,
+		"anchor":      anchor,
+		"html":        html,
+		"field":       field,
+	}
+)
+
+func field(name string, value interface{}) interface{} {
+	if value != nil {
+		s := reflect.ValueOf(value).Elem()
+		return s.FieldByName(name).Interface()
+	} else {
+		return nil
+	}
+}
+
+func html(content string) template.HTML {
+	return template.HTML(content)
+}
+
+func anchor(text, url string) template.HTML {
+	return template.HTML(fmt.Sprintf("<a href=\"%s\">%s</a>", url, text))
+}
+
+func toLower(text string) string {
+	return strings.ToLower(text)
+}
+
+func mod2(value int) bool {
+	return value%2 == 0
+}
+
+func incr(value int) int {
+	return value + 1
+}
+
+func callString(value interface{}) string {
+	if value != nil {
+		return reflect.ValueOf(value).MethodByName("String").Interface().(func() string)()
+	} else {
+		return ""
+	}
+}
+
+func isSlice(value interface{}) bool {
+	return reflect.TypeOf(value).Kind() == reflect.Slice
+}
+
+func yaml(content string) (interface{}, error) {
+	var result interface{}
+	err := yml.Unmarshal([]byte(content), &result)
+	if err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+func dict(values ...interface{}) (map[string]interface{}, error) {
+	if len(values)%2 != 0 {
+		return nil, errors.New("invalid dict call")
+	}
+	dict := make(map[string]interface{}, len(values)/2)
+	for i := 0; i < len(values); i += 2 {
+		key, ok := values[i].(string)
+		if !ok {
+			return nil, errors.New("dict keys must be strings")
+		}
+		dict[key] = values[i+1]
+	}
+	return dict, nil
+}
+
+func toSlice(values ...string) interface{} {
+	var result []string
+	result = append(result, values...)
+	return result
+}
+
+func getType(myvar interface{}) (res string) {
+	t := reflect.TypeOf(myvar)
+	for t.Kind() == reflect.Ptr {
+		t = t.Elem()
+		res += "*"
+	}
+	return res + t.Name()
+}
+
+func query(values ...string) template.URL {
+	var (
+		urlValues url.Values
+		format    bool
+	)
+
+	urlValues = make(url.Values)
+
+	for i := 0; i < len(values); i += 2 {
+		if values[i] == "format" {
+			format = true
+		}
+		urlValues.Add(values[i], values[i+1])
+	}
+
+	if !format {
+		urlValues.Set("format", "html")
+	}
+
+	return template.URL(urlValues.Encode())
+}
+
+func convertDate(value interface{}) string {
+	t, ok := value.(time.Time)
+	if !ok {
+		return ""
+	}
+	return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day())
+}
+
+func modelPath(model string, action string, id uint) string {
+	var q template.URL
+
+	action = strings.ToLower(action)
+	plural := inflection.Plural(strings.ToLower(model))
+
+	if action != "" {
+		switch action {
+		case "show":
+			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
+			return fmt.Sprintf("/%s/%d?%s", plural, id, q)
+		case "update":
+			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural), "update", "true")
+			return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
+		case "create":
+			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural))
+			return fmt.Sprintf("/%s/%s/?%s", plural, action, q)
+		case "delete":
+			q = query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
+			return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
+		}
+	}
+	q = query("tpl_layout", "base", "tpl_content", plural)
+	return fmt.Sprintf("/%s?%s", plural, q)
+}
+
+func all(model string) string {
+	plural := inflection.Plural(strings.ToLower(model))
+	q := query("tpl_layout", "base", "tpl_content", plural)
+
+	return fmt.Sprintf("/%s?%s", plural, q)
+}
+
+func create(model string) string {
+	action := "create"
+	plural := inflection.Plural(strings.ToLower(model))
+	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural))
+
+	return fmt.Sprintf("/%s/%s/?%s", plural, action, q)
+}
+
+func show(model string, id uint, format ...string) string {
+	action := "show"
+	plural := inflection.Plural(strings.ToLower(model))
+
+	args := []string{"tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action)}
+
+	if len(format) > 0 {
+		args = append(args, []string{"format", format[0]}...)
+	}
+
+	// q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
+	q := query(args...)
+
+	return fmt.Sprintf("/%s/%d?%s", plural, id, q)
+}
+
+func execute(model string, id uint) string {
+	action := "execute"
+	plural := inflection.Plural(strings.ToLower(model))
+	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
+
+	return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
+}
+
+func update(model string, id uint) string {
+	action := "update"
+	plural := inflection.Plural(strings.ToLower(model))
+	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_add_update", plural), "update", "true")
+
+	return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
+}
+
+func delete(model string, id uint) string {
+	action := "delete"
+	plural := inflection.Plural(strings.ToLower(model))
+	q := query("tpl_layout", "base", "tpl_content", fmt.Sprintf("%s_%s", plural, action))
+
+	return fmt.Sprintf("/%s/%d/%s?%s", plural, id, action, q)
+}

+ 8 - 23
renderer/renderer.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"html/template"
 	"io"
+
 	"mime"
 	"net/http"
 	"net/url"
@@ -42,11 +43,6 @@ var (
 	currRenderer Renderer
 	Render       map[string]func(http.ResponseWriter, *http.Request, interface{}, ...url.Values)
 
-	funcMap = template.FuncMap{
-		"query":       query,
-		"convertDate": convertDate,
-	}
-
 	contentTypeToFormat = map[string]string{
 		"application/x-www-form-urlencoded": "html",
 		"application/json":                  "json",
@@ -100,22 +96,6 @@ func Query(values ...string) template.URL {
 	return query(values...)
 }
 
-func query(values ...string) template.URL {
-	var urlValues url.Values
-
-	urlValues = make(url.Values)
-	urlValues.Set("format", "html")
-
-	for i := 0; i < len(values); i += 2 {
-		urlValues.Add(values[i], values[i+1])
-	}
-	return template.URL(urlValues.Encode())
-}
-
-func convertDate(t time.Time) string {
-	return fmt.Sprintf("%d-%02d-%02d", t.Year(), t.Month(), t.Day())
-}
-
 // FIXME: Not safe. We should check if it responds to the error interface instead.
 func isErrorType(data interface{}) bool {
 	return strings.Contains(strings.ToLower(reflect.TypeOf(data).String()), "error")
@@ -183,12 +163,17 @@ func NewPDFRenderer() (*PDFRenderer, error) {
 }
 
 func (rend *PDFRenderer) Render(w http.ResponseWriter, r *http.Request, data interface{}, options ...url.Values) error {
+	fileInfo := data.(map[string]string)
+	filename := fileInfo["filename"]
+
 	w.Header().Set("Content-Type", "application/pdf")
-	fileMap := data.(map[string]string)
-	f, err := os.Open(filepath.Join("output", fileMap["document_id"], fileMap["id"], fileMap["filename"]))
+	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(filename)))
+
+	f, err := os.Open(filename)
 	if err != nil {
 		panic(err)
 	}
+
 	reader := bufio.NewReader(f)
 	io.Copy(w, reader)
 

+ 1 - 1
template_generator/main.go

@@ -10,7 +10,7 @@ import (
 	"text/template"
 
 	"github.com/jinzhu/inflection"
-	tpl_util "gogs.carducci-dante.gov.it/karmen/util/template"
+	tpl_util "gogs.carducci-dante.gov.it/karmen/core/util/template"
 )
 
 var (

+ 17 - 40
templates/activities.html.tpl

@@ -1,55 +1,32 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Attività ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/activities/add/?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}" class="btn btn-primary float-right">
-	  <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	  Crea nuova attività
-	</a>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Attività"
+  buttonTitle: "Crea nuova attività"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "activity")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $activity := .Data}}
-    <a class="list-group-item list-group-item-action" href="/activities/{{$activity.ID}}?{{query "tpl_layout" "base" "tpl_content" "activities_show"}}">
-      <span class="fas fa-business-time"></span>
-      {{if not $activity.Subject}}
-      <small>no materia</small>
-      {{else}}
-      {{$activity.Subject.Name}}
-      {{end}}
+    <a class="list-group-item list-group-item-action" href={{$activity.ID|show "activity"}}>
+      <span class="fa fa-business-time"></span>
+      {{$activity|string}}
       <div class="text-right">
-	{{if not $activity.Teacher}}
-	<small>no docente</small>
-	{{else}}
-	<small>{{$activity.Teacher.Surname}}</small>
-	{{end}}
-	{{if not $activity.Class}}
-	<small>no classe</small>
-	{{else}}
-	<small>{{$activity.Class.Name}}</small>
-	{{end}}
-	<small>{{$activity.Hours}}h</small>
+        {{$options := `noElements: "no docente"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $activity.Teacher}}
       </div>
     </a>
     {{end}}
     {{end}}
   </div>
+
 </div>
-  
+
 {{ end }}

+ 43 - 151
templates/activities_add_update.html.tpl

@@ -2,171 +2,63 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/activities?{{query "tpl_layout" "base" "tpl_content" "activities"}}">Attività</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna l'attività</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/activities?{{query "tpl_layout" "base" "tpl_content" "activities"}}">Attività</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna l'attività</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/activities/{{.Data.Activity.ID}}/delete"
-	   data-url="/activities/{{.Data.Activity.ID}}/delete"
-	   class="btn btn-danger karmen-ajax-delete float-right">
-	  <span class="fa fa-trash" aria-hidden="true"></span>
-	  Elimina
-	</a>
-      </div>
-    </div>
-  </div>
-  
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Attività" (all "Activity") (.Data|string) (.Data.ID|show "Activity") "Aggiorna" "current"}}
   {{else}}
-  <h1 class="karmen-info-header">Crea una nuova attività</h1>
+  {{template "breadcrumb" toSlice "Attività" (all "Activity") "Aggiungi" "current"}}
   {{end}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_activities_add_update" action="/activities/{{.Data.Activity.ID}}/update" method="POST" role="form" class="needs-validation">
-  {{else}}
-  <form id="form_activities_add_update" action="/activities/add/" method="POST" role="form" class="needs-validation">
-  {{end}}
-   
-    <div class="form-group">
-      <label class="control-label" for="subject_id">Materia</label>
-      <select name="subject_id" class="form-control selectpicker" id="subject_id" placeholder="Materia" data-live-search="true" form="form_activities_add_update" title="Seleziona il nome della materia" data-dropup-auto="false" required>
-	<option value="0"></option>
-  	{{range $subject := .Data.AllSubjects}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$subject.ID}}"
-	   {{index $.Data.SelectedSubject $subject.ID}}>{{$subject.Name}}
-	</option>
-	{{else}}
-  	<option value="{{$subject.ID}}">{{$subject.Name}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
-    
-    <div class="form-group">
-      <label class="control-label" for="class_id">Classe</label>
-      <select name="class_id" class="form-control selectpicker" id="class_id" placeholder="Classe" data-live-search="true" form="form_activities_add_update" title="Seleziona la classe" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $class := .Data.AllClasses}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$class.ID}}"
-	   {{index $.Data.SelectedClass $class.ID}}>{{$class.Name}}
-	</option>
-	{{else}}
-  	<option value="{{$class.ID}}">{{$class.Name}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
+  {{$updateTitle := ""}}
+  {{if $update}}{{$updateTitle = (printf "Aggiorna attività %s" (.Data|string))}}{{end}}
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuova attività" "updateTitle" $updateTitle}}
 
-    <div class="form-group">
-      <label class="control-label" for="student_id">Studente</label>
-      <select name="student_id" class="form-control selectpicker" id="student_id" placeholder="Docente" data-live-search="true" form="form_activities_add_update" title="Seleziona il nome del docente" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $student := .Data.AllStudents}}
-	{{if $.Options.Get "update"}}
-  	<option
-	  value="{{$student.ID}}"
-	  {{index $.Data.SelectedStudent $student.ID}}>{{$student.Name}} {{$student.Surname}}
-	</option>
-	{{else}}
-  	<option value="{{$student.ID}}">{{$student.Name}} {{$student.Surname}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
+  {{$form := "form_activities_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Activity"}}{{else}}{{create "Activity"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
 
-    <div class="form-group">
-      <label class="control-label" for="teacher_id">Docente</label>
-      <select name="teacher_id" class="form-control selectpicker" id="teacher_id" placeholder="Docente" data-live-search="true" form="form_activities_add_update" title="Seleziona il nome del docente" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $teacher := .Data.AllTeachers}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$teacher.ID}}"
-	   {{index $.Data.SelectedTeacher $teacher.ID}}>{{$teacher.Name}} {{$teacher.Surname}}
-	</option>
-	{{else}}
-  	<option value="{{$teacher.ID}}">{{$teacher.Name}} {{$teacher.Surname}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
+    {{$options := ` { name: "subject_id", id: "subject_id", label: "Materia", title: "Seleziona la materia"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllSubjects") "selected" (.Data|field "SelectedSubject") "update" $update "form" $form}}
 
-    <div class="form-group">
-      <label class="control-label" for="supply_teacher_id">Docente supplente</label>
-      <select name="supply_teacher_id" class="form-control selectpicker" id="supply_teacher_id" placeholder="Docente supplente" data-live-search="true" form="form_activities_add_update" title="Seleziona il nome del docente supplente" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $supplyTeacher := .Data.AllTeachers}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$supplyTeacher.ID}}"
-	   {{index $.Data.SelectedSupplyTeacher $supplyTeacher.ID}}>{{$supplyTeacher.Name}} {{$supplyTeacher.Surname}}
-	</option>
-	{{else}}
-  	<option value="{{$supplyTeacher.ID}}">{{$supplyTeacher.Name}} {{$supplyTeacher.Surname}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
+    {{$options := ` { name: "class_id", id: "class_id", label: "Classe", title: "Seleziona la classe"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllClasses") "selected" (.Data|field "SelectedClass") "update" $update "form" $form}}
 
+    {{$options := ` { name: "teacher_id", id: "teacher_id", label: "Docente", title: "Seleziona il docente"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedTeacher") "update" $update "form" $form}}
 
-    <div class="form-group">
-      <label class="control-label" for="hours">Numero di ore</label>
-      <input class="form-control" min="0" type="number" name="hours" id="hours" {{if .Options.Get "update"}}value="{{.Data.Activity.Hours}}"{{end}} required>
-    </div>
+    {{$options := ` { name: "supply_teacher_id", id: "supply_teacher_id", label: "Docente supplente", title: "Seleziona il docente"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedSupplyTeacher") "update" $update "form" $form}}
 
-    
-    <div class="form-group">
-      <div class="form-check">
-        <input type="checkbox" name="GroupActivity" class="form-check-input" id="activity_group_activity" {{if .Options.Get "update"}}{{if .Data.Activity.GroupActivity}}checked{{end}}{{end}}>
-        <label class="form-check-label" for="activity_group_activity">Attività di gruppo</label>
-        <small id="activity_group_help_box" class="form-text text-muted">
-          Spuntare la casella sopra nel caso di attività che coinvolgono più studenti contemporaneamente.
-        </small>
-      </div>
-    </div>
+    {{$options := ` { name: "Hours",id: "hours",label: "Numero di ore",type: "number",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Hours") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="group_activity_hours">Numero di ore dell'attività di gruppo</label>
-      <input class="form-control" min="0" type="number" name="group_activity_hours" id="group_activity_hours" {{if .Options.Get "update"}}value="{{.Data.Activity.GroupActivityHours}}"{{end}}>
-      <small id="activity_group_hours_help_box" class="form-text text-muted">
-        Indicare il numero totale di ore che il docente spende nell'attività di gruppo.
-      </small>        
-    </div>
+    {{$options := `
+    name: "GroupActivity"
+    id: "group_activity"
+    label: "Attività di gruppo"
+    formClass: "form-group form-check"
+    help: "Spuntare la casella sopra nel caso di attività che coinvolgono più studenti contemporaneamente."
+    `}}
+    {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "GroupActivity") "update" $update}}
 
+    {{$options := `
+    name: "group_activity_hours"
+    id: "group_activity_hours"
+    label: "Numero di ore dell'attività di gruppo"
+    type: "number"
+    help: "Indicare il numero totale di ore che il docente spende nell'attività di gruppo."
+    `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Hours") "update" $update}}
 
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/activities/{{.Data.Activity.ID}}?{{query "tpl_layout" "base" "tpl_content" "activities_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/activities?{{query "tpl_layout" "base" "tpl_content" "activities"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
-    
+    {{$options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Activity"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}  
   </form>
-
+  
 </div>
 
 {{ end }}

+ 33 - 103
templates/activities_show.html.tpl

@@ -1,131 +1,61 @@
 {{ define "content" }}
 
 <div class="container">
-
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/activities?{{query "tpl_layout" "base" "tpl_content" "activities"}}">Attività</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{if .Data.Subject}}{{.Data.Subject.Name}}{{else}}no materia{{end}} {{if .Data.Class}}{{.Data.Class.Name}}{{else}}no classe{{end}} {{.Data.Hours}}h</a></li>
-    </ol>
-  </nav>
   
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{if .Data.Subject}}{{.Data.Subject.Name}}{{else}}no materia{{end}} {{if .Data.Class}}{{.Data.Class.Name}}{{else}}no classe{{end}} {{.Data.Hours}}h</h1>
-      </div>
-      <div class="col-md-4">
-
-	<div class="btn-group float-right" role="group">
-	  <a href="/activities/add/?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}" class="btn btn-success">
-	    <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	    Crea
-	  </a>
-
-	  <a href="/activities/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "activities_add_update" "update" "true"}}" class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/activities/{{.Data.ID}}/delete"
-		  data-url="/activities/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-
-      </div>
-    </div>
-  </div>
+  {{template "breadcrumb" toSlice "Attività" (all "Activity") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Activity") "deletePath" (.Data.ID|delete "Activity")}}
 
   <div class="row">
     <div class="col-md-12">
-      
-      <h2 class="karmen-relation-header">Materia</h2>
-      {{if .Data.Subject}}
-      <div class="list-group" id="materie_list_group">
-    	<a href="/subjects/{{.Data.Subject.ID}}?{{query "tpl_layout" "base" "tpl_content" "subjects_show"}}" class="list-group-item list-group-item-action">
-    	  <span class="fa fa-book"></span>
-    	  {{.Data.Subject.Name}}
-    	</a>
-      </div>
-      {{else}}
-      <p>All'attività non è associata alcuna materia
-    	didattica. Clicca <a href="/activities/update?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per
-    	modificare questa attività didattica.</p>
-      {{end}}
+      {{$options := `
+      title: "Materia"
+      model: "Subject"
+      icon: "fa fa-book"
+      `}}
+      {{$noElements := "Nessuna materia associata all'attività"}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Subject "noElements" $noElements}}
     </div>
-    
   </div>
 
   <div class="row">
     <div class="col-md-12">
-      
-      <h2 class="karmen-relation-header">Classe</h2>
-      {{if .Data.Class}}
-      <div class="list-group" id="classes_list_group">
-
-	<a href="/classes/{{.Data.Class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-users"></span>
-	  {{.Data.Class.Name}}
-	</a>
-	
-      </div>
-      {{else}}
-      
-      <p>All'attività non è associata alcuna classe
-    	didattica. Clicca <a href="/activities/update?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per
-    	modificare questa attività didattica.</p>
-      
-      {{end}}
+      {{$options := `
+      title: "Docente"
+      model: "Teacher"
+      icon: "fa fa-user"
+      `}}
+      {{$noElements := "Nessun docente associato all'attività"}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Teacher "noElements" $noElements}}
     </div>
-    
   </div>
 
   <div class="row">
     <div class="col-md-12">
-      
-      <h2 class="karmen-relation-header">Docente</h2>
-      {{if .Data.Teacher}}
-      <div class="list-group" id="classes_list_group">
-	<a href="/teachers/{{.Data.Teacher.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-user"></span>
-	  {{.Data.Teacher.Surname}} {{.Data.Teacher.Name}}
-	</a>
-      </div>
-            
-      {{else}}
-
-      <p>All'attività non è associato alcun docente
-    	didattica. Clicca <a href="/activities/update?{{query "base" "activities_add_update"}}">qui</a> per
-    	modificare questa attività didattica.</p>
-	    
-      {{end}}
+      {{$options := `
+      title: "Classe"
+      model: "Class"
+      icon: "fa fa-users"
+      `}}
+      {{$noElements := "Nessuna classe associata all'attività"}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Class "noElements" $noElements}}
     </div>
   </div>
 
   <div class="row">
     <div class="col-md-12">
-      
-      <h2 class="karmen-relation-header">Studente</h2>
-      {{if .Data.Student}}
-      <div class="list-group" id="classes_list_group">
-	<a href="/teachers/{{.Data.Student.ID}}?{{query "tpl_layout" "base" "tpl_content" "students_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-user"></span>
-	  {{.Data.Student.Surname}} {{.Data.Student.Name}}
-	</a>
-      </div>
-      
-      {{else}}
-
-      <p>All'attività non è associato alcuno studente. Clicca <a href="/activities/update?{{query "base" "activities_add_update"}}">qui</a> per modificare questa attività didattica.</p>
-     
-      {{end}}
-      
+      {{$options := `
+      title: "Studente"
+      model: "Student"
+      icon: "fa fa-user"
+      `}}
+      {{$noElements := "Nessuno studente associato all'attività"}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Student "noElements" $noElements}}
     </div>
   </div>
 
-
 </div>    
 
 {{ end }}
+
+
+

+ 16 - 32
templates/administratives.html.tpl

@@ -2,47 +2,31 @@
 
 <div class="container">
   
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Amministrativi ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/administratives/add/?{{query "tpl_layout" "base" "tpl_content" "administratives_add_update"}}" class="btn btn-primary float-right">
-	  <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	  Crea nuovo amministrativo
-	</a>
-      </div>
-    </div>
-  </div>
-
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Personale ATA"
+  buttonTitle: "Crea nuovo ATA"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "Administrative")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $administrative := .Data}}
-    <a class="list-group-item list-group-item-action" href="/administratives/{{$administrative.ID}}?{{query "tpl_layout" "base" "tpl_content" "administratives_show"}}">
+    <a class="list-group-item list-group-item-action" href={{$administrative.ID|show "Administrative"}}>
       <span class="fa fa-user"></span>
-      {{$administrative.Surname}} {{$administrative.Name}}
+      {{$administrative|string}}
       <div class="text-right">
-	{{if $administrative.Office}}
-	<small>{{$administrative.Office.Name}}</small>
-	{{else}}
-	<small>nessun ufficio</small>
-	{{end}}
-        {{if $administrative.InternalTelephoneNumber}}
-	<small>Interno {{$administrative.InternalTelephoneNumber}}</small>
-	{{end}}
+        {{$options := `noElements: "nessun ufficio"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $administrative.Office}}
       </div>
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}

+ 35 - 94
templates/administratives_add_update.html.tpl

@@ -2,125 +2,66 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/administratives?{{query "tpl_layout" "base" "tpl_content" "administratives"}}">Amministrativi</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna amministrativo</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/administratives?{{query "tpl_layout" "base" "tpl_content" "administratives"}}">Amministrativi</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
- {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna amministrativo</h1>
-      </div>
-    </div>
-  </div>
-  
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Personale ATA" (all "Administrative") (.Data|string) (.Data.ID|show "Administrative") "Aggiorna" "current"}}
   {{else}}
-  <h1 class="karmen-info-header">Crea nuovo amministrativo</h1>
+  {{template "breadcrumb" toSlice "Personale ATA" (all "Administrative") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo ATA" "updateTitle" (printf "Aggiorna ATA %s" (.Data|string))}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_administratives_add_update" action="/administratives/{{.Data.Administrative.ID}}/update" method="POST" role="form" class="needs-validation">
-  {{else}}
-  <form id="form_administratives_add_update" action="/administratives/add/" method="POST" role="form" class="needs-validation">
-  {{end}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Administrative"}}{{else}}{{create "Administrative"}}{{end}}" method="POST" role="form">
 
-    <div class="form-group">
-      <label class="control-label" for="administrative_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="administrative_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Administrative.Name}}" {{end}} required>
-    </div>
+    {{$options := ` { name: "Name",id: "administrative_name",label: "Nome",placeholder: "Nome",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
+    
+    {{$options := ` { name: "Surname",id: "administrative_surname",label: "Cognome",placeholder: "Cognome",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Surname") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="administrative_surname">Cognome</label>
-      <input type="text" name="Surname" class="form-control" id="administrative_surname" placeholder="Cognome" {{if .Options.Get "update"}} value="{{.Data.Administrative.Surname}}" {{end}} required>
-    </div>
+    {{$options := ` { name: "InternalTelephoneNumber",id: "administrative_internal_telephone_number",label: "Numero di telefono interno",placeholder: "",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "InternalTelephoneNumber") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="administrative_internal_telephone">Telefono (interno)</label>
-      <input type="text" name="InternalTelephoneNumber" class="form-control" id="administrative_internal_telephone_number" placeholder="Numero di telefono (interno)" {{if .Options.Get "update"}} value="{{.Data.Administrative.InternalTelephoneNumber}}" {{end}}>
-    </div>
+    {{$options := ` { name: "AltEmail",id: "administrative_altemail",label: "Email alternativa",placeholder: "Inserire indirizzo email",type: "email" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "AltEmail") "update" $update}}
 
     <div class="form-row">
       <div class="col">
-	<input type="email" name="AltEmail" class="form-control" id="administrative_altemail" placeholder="Email alternativa" {{if .Options.Get "update"}} value="{{.Data.Administrative.AltEmail}}" {{end}}>
+        {{$options := ` { name: "Regenerate",id: "administrative_regenerate",label: "Rigenerare/inviare le credenziali",formClass: "form-check form-check-inline" } `}}
+        {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "Regenerate") "update" $update}}
       </div>
       <div class="col">
-	<div class="form-check">
-	  <input type="checkbox" name="Regenerate" class="form-check-input" id="administrative_regenerate" {{if .Options.Get "update"}}{{if .Data.Administrative.Regenerate}}checked{{end}}{{end}}>
-	  <label class="form-check-label has-feedback" for="administrative_regenerate">Rigenerare/inviare le credenziali</label>
-	</div>
+        {{$options := ` { name: "Exclude",id: "administrative_exclude",label: "Escludere dalle utenze",formClass: "form-check form-check-inline" } `}}
+        {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "Exclude") "update" $update}}
       </div>
       <div class="col">
-	<div class="form-check">
-	  <input type="checkbox" name="Exclude" class="form-check-input" id="administrative_exclude" {{if .Options.Get "update"}}{{if .Data.Administrative.Exclude}}checked{{end}}{{end}}>
-	  <label class="form-check-label has-feedback" for="administrative_exclude">Escludere dalle utenze</label>
-	</div>
-      </div>
-    </div>
-    <div class="col">
-      <div class="form-check">
-	<input type="checkbox" name="NotInvited" class="form-check-input" id="administrative_not_invited" {{if .Options.Get "update"}}{{if .Data.Administrative.NotInvited}}checked{{end}}{{end}}>
-	<label class="form-check-label has-feedback" for="administrative_not_invited">Escludere dagli inviti</label>
+        {{$options := ` { name: "NotInvited",id: "administrative_not_invited",label: "Escludere dagli inviti",formClass: "form-check form-check-inline" } `}}
+        {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "NotInvited") "update" $update}}
       </div>
     </div>
 
-    
     <div class="form-row">
       <div class="col">
-	<label class="control-label" for="administrative_date_from">In servizio dal</label>
-	<input type="date" name="DateFrom" class="form-control" id="administrative_date_from" {{if .Options.Get "update"}} value="{{convertDate .Data.Administrative.DateFrom}}" {{end}}>
+        {{$options := ` { name: "DateFrom",id: "administrative_date_from",label: "In servizio dal",type: "date" } `}}
+        {{template "input" dict "options" ($options|yaml) "value" (.Data|field "DateFrom"|convertDate) "update" $update}}
       </div>
       <div class="col">
-	<label class="control-label" for="administrative_date_to">In servizio fino al</label>
-	<input type="date" name="DateTo" class="form-control" id="administrative_date_to" {{if .Options.Get "update"}} value="{{convertDate .Data.Administrative.DateTo}}" {{end}}>
+        {{$options := ` { name: "DateTo",id: "administrative_date_to",label: "In servizio fino al",type: "date" } `}}
+        {{template "input" dict "options" ($options|yaml) "value" (.Data|field "DateTo"|convertDate) "update" $update}}
       </div>
     </div>
-
-    <div class="form-group">
-      <label class="control-label" for="office_id">Associato all'ufficio</label>
-      <select name="office_id" class="form-control selectpicker" id="office_id" placeholder="Seleziona l'ufficio" data-live-search="true" form="form_administratives_add_update" title="Seleziona l'ufficio" data-dropup-auto="false">
-    	<option value="0"></option>
-    	{{range $office := .Data.AllOffices}}
-    	{{if $.Options.Get "update"}}
-    	<option
-    	  value="{{$office.ID}}"
-    	  {{index $.Data.SelectedOffice $office.ID}}>{{$office.Name}}
-    	</option>
-    	{{else}}
-    	<option value="{{$office.ID}}">{{$office.Name}}</option>
-    	{{end}}
-    	{{end}}
-      </select>
-    </div>
-
-    <div class="form-group">
-      <label class="control-label has-feedback" for="administrative_ldapdnfmt">Stringa DN per LDAP</label>
-      <input type="text" name="LdapDNFmt" class="form-control" id="administrative_ldapdnfmt" placeholder="cn=%s %s,ou=People,dc=foo,dc=org" {{if .Options.Get "update"}}value="{{.Data.Administrative.LdapDNFmt}}"{{end}}>
-    </div>
-
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/administratives/{{.Data.Administrative.ID}}?{{query "tpl_layout" "base" "tpl_content" "administratives_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/administratives?{{query "tpl_layout" "base" "tpl_content" "administratives"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
     
-  </form>
+    {{$options := ` { name: "LdapDNFmt",id: "administrative_ldapfnfmt",label: "Stringa DN per LDAP",placeholder: "cn=%s %s,ou=People,dc=foo,dc=org",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "LdapDNFmt") "update" $update}}
 
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Administrative"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
+  
+  </form>
+  
 </div>
 
 {{ end }}

+ 16 - 61
templates/administratives_show.html.tpl

@@ -2,67 +2,22 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/administratives?{{query "tpl_layout" "base" "tpl_content" "administratives"}}">Amministrativi</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Surname}} {{.Data.Name}}</a></li>
-    </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Surname}} {{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-
-	<div class="btn-group float-right" role="group">
-	  <a href="/administratives/add/?{{query "tpl_layout" "base" "tpl_content" "administratives_add_update"}}" class="btn btn-success">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Crea
-	  </a>
-
-	  <a href="/administratives/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "administratives_add_update" "update" "true"}}"  class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/administratives/{{.Data.ID}}/delete"
-		  data-url="/administratives/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-
-      </div>
-    </div>
-  </div>
-
-  <div class="row">
-    <div class="col-md-12">
-      <h2 class="karmen-relation-header">Informazioni sull'amministrativo</h2>
-      {{if .Data.Office}}
-      <p>
-        L'amministrativo è associato con l'ufficio <a href="/offices/{{.Data.Office.ID}}?{{query "tpl_layout" "base" "tpl_content" "offices_show"}}">{{.Data.Office.Name}}</a>.
-      </p>
-      {{else}}
-      <p>Nessun ufficio associato all'amministrativo</p>
-      {{end}}
-
-      {{if .Data.InternalTelephoneNumber}}
-      <p>Il numero di telefono dell'amministrativo è {{.Data.InternalTelephoneNumber}}.</p>
-      {{else}}
-      <p>Non è stato associato alcun numero di telefono all'amministrativo.</p>
-      {{end}}
-
-      {{if .Data.AltEmail}}
-      <p>La mail alternativa dell'amministrativo è <a href="mailto:{{.Data.AltEmail}}">{{.Data.AltEmail}}</a>.</p>
-      {{else}}
-      <p>Non è stato associata alcuna mail alternativa.</p>
-      {{end}}
-
-    </div>
-  </div>
+  {{template "breadcrumb" toSlice "Personale ATA" (all "Administrative") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Administrative") "deletePath" (.Data.ID|delete "Administrative")}}
+
+  <h2 class="karmen-relation-header">Informazioni</h2>
+  
+  {{if .Data.Office}}
+  <p>L'amministrativo è associato con l'ufficio di {{(.Data.Office.ID|show "Office")|anchor (.Data.Office|string)}}.</p>
+  {{else}}
+  <p>L'amministrativo non è associato con alcun ufficio.</p>
+  {{end}}
+
+  {{if .Data.InternalTelephoneNumber}}
+  <p>L'amministrativo risponde al numero di telefono interno {{.Data.InternalTelephoneNumber}}.</p>
+  {{else}}
+  <p>L'amministrativo non ha un numero di telefono interno assegnato.</p>
+  {{end}}
 
 </div>    
 

+ 27 - 39
templates/classes.html.tpl

@@ -1,45 +1,33 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Classi ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<div class="btn-group float-right">
+<div class="container">  
 
-	  <a href="/classes/add/?{{query "tpl_layout" "base" "tpl_content" "classes_add_update"}}" class="btn btn-primary">
-	    <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	    Crea nuova classe
-	  </a>
-	  
-	</div>
+  {{$options := `
+  title: "Classi"
+  buttonTitle: "Crea nuova classe"
+  `}}
+  
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "class")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
+  {{else}}
+  <div class="list-group" id="myUL">
+    {{range $class := .Data}}
+    <a class="list-group-item list-group-item-action" href={{$class.ID|show "class"}}>
+      <span class="fa fa-user"></span>
+      {{$class.Name}}
+      <div class="text-right">
+        {{$options := `noElements: "no docenti"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $class.Teachers}}
       </div>
-    </div>
-
-    <div class="input-group" style="margin-bottom: 20px">
-      <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-      <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-    </div>
-    {{if not .Data}}
-    <p>Non c'è alcun elemento da visualizzare.</p>
-    {{else}}
-    <div class="list-group" id="myUL">
-      {{range $class := .Data}}
-      <a class="list-group-item list-group-item-action list-group-item-action" href="/classes/{{$class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes_show"}}">
-	<span class="fa fa-users"></span>
-	{{$class.Name}}
-	<div class="text-right">
- 	  {{range $teacher := $class.Teachers}}
-	  <small>{{$teacher.Surname}}</small>
-	  {{end}}
-	</div>
-      </a>
-      {{end}}
-    </div>
+    </a>
+    {{end}}
     {{end}}
   </div>
-  
-  {{ end }}
+
+</div>
+
+{{ end }}
+

+ 42 - 95
templates/classes_add_update.html.tpl

@@ -2,106 +2,53 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/classes?{{query "tpl_layout" "base" "tpl_content" "teachers"}}">Classi</a></li>
-      <li class="breadcrumb-item"><a href="/classes/{{.Data.Class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes"}}">{{.Data.Class.Name}}</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna la classe</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/classes/?{{query "tpl_layout" "base" "tpl_content" "classes"}}">Classi</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <h1 class="karmen-info-header">Aggiorna la classe "{{.Data.Class.Name}}"</h1>
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Classi" (all "Class") (.Data|string) (.Data.ID|show "Class") "Aggiorna" "current"}}
   {{else}}
-  <h1 class="karmen-info-header">Crea una nuova classe</h1>
+  {{template "breadcrumb" toSlice "Classi" (all "Class") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuova classe" "updateTitle" (printf "Aggiorna la classe %s" (.Data|string))}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_classes_add_update" action="/classes/{{.Data.Class.ID}}/update" method="POST" role="form" class="needs-validation">
-  {{else}}
-  <form id="form_classes_add_update" action="/classes/add/" method="POST" role="form" class="needs-validation">
-  {{end}}  
-    
-    <div class="form-group">
-      <label class="control-label" for="class_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="class_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Class.Name}}" {{end}} required>
-    </div>
-
-    <div class="form-group">
-      <label class="control-label" for="class_year">Anno</label>
-      <input type="number" min="1" name="Year" class="form-control" id="class_year" placeholder="Annualità" {{if .Options.Get "update"}} value="{{.Data.Class.Year}}" {{end}} required>
-    </div>
-    
-    <div class="form-group">
-      <label class="control-label" for="class_name">Sezione</label>
-      <input type="text" name="Section" class="form-control" id="class_section" placeholder="Sezione" {{if .Options.Get "update"}} value="{{.Data.Class.Section}}" {{end}} required>
-    </div>
-
-    <div class="form-group">
-      <label class="control-label" for="class_field">Indirizzo</label>
-      <input type="text" name="Field" class="form-control" id="class_field" placeholder="Indirizzo" {{if .Options.Get "update"}} value="{{.Data.Class.Field}}" {{end}} required>
-    </div>
-
-    <div class="form-group">
-      <label class="control-label" for="class_alias">Nome alternativo</label>
-      <input type="text" name="Alias" class="form-control" id="class_alias" placeholder="Nome alternativo" {{if .Options.Get "update"}} value="{{.Data.Class.Alias}}" {{end}}>
-    </div>
-
-    <div class="form-group">
-      <label class="control-label" for="coordinator_id">Coordinatore</label>
-      <select name="coordinator_id" class="form-control selectpicker" id="coordinator_id" placeholder="Coordinatore" data-live-search="true" form="form_classes_add_update" title="Seleziona il nome del coordinatore" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $teacher := .Data.AllTeachers}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$teacher.ID}}"
-	   {{index $.Data.SelectedCoordinator $teacher.ID}}>{{$teacher.Name}} {{$teacher.Surname}}
-	</option>
-	{{else}}
-  	<option value="{{$teacher.ID}}">{{$teacher.Name}} {{$teacher.Surname}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
-
-    
-    <div class="form-group">
-      <label class="control-label" for="minuter_id">Verbalizzante</label>
-      <select name="minuter_id" class="form-control selectpicker" id="minuter_id" placeholder="Verbalizzante" data-live-search="true" form="form_classes_add_update" title="Seleziona il nome del verbalizzante" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $teacher := .Data.AllTeachers}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$teacher.ID}}"
-	   {{index $.Data.SelectedMinuter $teacher.ID}}>{{$teacher.Name}} {{$teacher.Surname}}
-	</option>
-	{{else}}
-  	<option value="{{$teacher.ID}}">{{$teacher.Name}} {{$teacher.Surname}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
-
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/classes/{{.Data.Class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/classes/?{{query "tpl_layout" "base" "tpl_content" "classes"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
-    
-  </form>
+  {{$form := "form_classes_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Class"}}{{else}}{{create "Class"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
+
+    {{$options := ` { name: "Name",id: "class_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
+
+    {{$options := ` { name: "Alias",id: "class_alias",label: "Nome alternativo",placeholder: "Nome alternativo",type: "text"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Alias") "update" $update}}
+
+    {{$options := ` { name: "Year",id: "class_year",label: "Anno",placeholder: "",type: "number",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Year") "update" $update}}
+
+    {{$options := ` { name: "Section",id: "class_section",label: "Sezione",placeholder: "Sezione",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Section") "update" $update}}
 
+    {{$options := ` { name: "Field",id: "class_field",label: "Indirizzo",placeholder: "Indirizzo",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Field") "update" $update}}
+
+    {{$options := ` { name: "coordinator_id", id: "coordinator_id", label: "Coordinatore", title: "Seleziona il coordinatore"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedCoordinator") "update" $update "form" $form}}
+
+    {{$options := ` { name: "minuter_id", id: "minuter_id", label: "Verbalizzante", title: "Seleziona il verbalizzante"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedMinuter") "update" $update "form" $form}}
+
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Class"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
+  
+  </form>
+  
 </div>
 
 {{ end }}
+
+
+

+ 28 - 79
templates/classes_show.html.tpl

@@ -2,91 +2,40 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/classes?{{query "tpl_content" "classes" "tpl_layout" "base"}}">Classi</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
-    </ol>
-  </nav>
+  {{template "breadcrumb" toSlice "Classi" (all "Class") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Class") "deletePath" (.Data.ID|delete "Class")}}
 
+  <h2 class="karmen-relation-header">Informazioni generali</h2>
+  
+  <p>Questa classe appartiene all'indirizzo <strong>{{.Data.Field}}</strong>.</p>
 
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-	<div class="btn-group float-right" role="group">
-	  <a href="/classes/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "classes_add_update" "update" "true"}}" class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/classes/{{.Data.ID}}/delete"
-		  data-url="/classes/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-      </div>
-    </div>
-  </div>
-
-  <h2 class="karmen-relation-header">Attività della {{.Data.Name}}</h2>
-  <div class="list-group" id="classes_list_group">
-    {{if not .Data.Teachers}}
-    <p>Alla classe non è associata alcuna attività
-	didattica. Clicca <a href="/activities/add">qui</a> per creare
-	una nuova attività didattica da associare alla classe.</p>
-    {{else}}
-    {{range $activity := .Data.Activities}}
-      <a href="/activities/{{$activity.ID}}?{{query "tpl_layout" "base" "tpl_content" "activities_show"}}" class="list-group-item list-group-item-action">
-	<span class="fa fa-user"></span>
-	{{if $activity.Teacher}}{{$activity.Teacher.Surname}} {{$activity.Teacher.Name}}{{else}}no docente{{end}}
-	<div class="text-right">
-	  <small>{{if $activity.Subject}}{{$activity.Subject.Name}}{{else}}no materia{{end}} {{$activity.Hours}}h</small>
-	</div>
-      </a>
-    {{end}}
-    {{end}}
-  </div>
-
-  <h2 class="karmen-relation-header">Coordinatore e verbalizzante</h2>
-  <div class="list-group" id="classes_list_group">
-    {{if not .Data.Teachers}}
-    <p>Alla classe non è associata alcuna attività
-	didattica. Clicca <a href="/activities/add">qui</a> per creare
-	una nuova attività didattica da associare alla classe.</p>
+  {{if .Data.Coordinator}}
+  <p>Il <strong>coordinatore</strong> è il docente {{(.Data.Coordinator.ID|show "Teacher")|anchor (.Data.Coordinator|string)}}.
     {{else}}
+  <p>Alla classe non è associato nessun coordinatore. Clicca {{(.Data.ID|update "Class")|anchor "qui"}} per associare un coordinatore alla classe.</p>
+  {{end}}
 
-    {{if ne .Data.Coordinator.ID 0}}
-    <a href="/teachers/{{.Data.Coordinator.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="list-group-item list-group-item-action">
-      <span class="fa fa-user"></span>
-      {{.Data.Coordinator.Name}} {{.Data.Coordinator.Surname}}
-      <div class="text-right">
-	<small>Coordinatore</small>
-      </div>
-    </a>
+  {{if .Data.Minuter}}
+  <p>Il <strong>verbalizzante</strong> è il docente {{(.Data.Minuter.ID|show "Teacher")|anchor (.Data.Minuter|string)}}.
     {{else}}
-    <span class="list-group-item list-group-item-action">Nessun coordinatore</span>
-    {{end}}
-
-    {{if ne .Data.Minuter.ID 0}}
-    <a href="/teachers/{{.Data.Minuter.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="list-group-item list-group-item-action">
-      <span class="fa fa-user"></span>
-      {{.Data.Minuter.Name}} {{.Data.Minuter.Surname}}
-      <div class="text-right">
-	<small>Verbalizzante</small>
-      </div>
-    </a>
-    {{else}}
-    <span class="list-group-item list-group-item-action">Nessun verbalizzante</span>
-    {{end}}
- 
-    {{end}}
+  <p>Alla classe non è associato nessun verbalizzante. Clicca {{(.Data.ID|update "Class")|anchor "qui"}} per associare un verbalizzante alla classe.</p>
+  {{end}}
+
+  <div class="row">
+    <div class="col-md-12">
+
+      {{$options := `
+      title: "Attività associate alla classe"
+      model: "Activity"
+      icon: "fa fa-business-time"
+      `}}
+      
+      {{$noElements := "Nessuna attività associata alla classe."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Activities "noElements" $noElements "small" (toSlice "Teacher")}}
+    
+    </div>
   </div>
-
-
+  
 </div>    
 
 {{ end }}

+ 20 - 40
templates/departments.html.tpl

@@ -1,55 +1,35 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Dipartimenti ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/departments/add/?{{query "tpl_layout" "base" "tpl_content" "departments_add_update"}}" class="btn btn-primary float-right">
-	  <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	  Crea nuovo dipartimento
-	</a>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Dipartimenti"
+  buttonTitle: "Crea nuovo dipartimento"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "department")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $department := .Data}}
-    <a class="list-group-item list-group-item-action" href="/departments/{{$department.ID}}?{{query "tpl_layout" "base" "tpl_content" "departments_show"}}">
-      <span class="fa fa-layer-group"></span>
+    <a class="list-group-item list-group-item-action" href={{$department.ID|show "department"}}>
+      <span class="fa fa-user"></span>
       {{$department.Name}}
-
       <div class="text-right">
-	
-	{{if $department.Subjects}}
-	{{range $subject := $department.Subjects}}
-	<small>{{$subject.Name}}</small>
-	{{end}}
-	{{end}}
-
-	{{if $department.Teachers}}
-	{{range $teacher := $department.Teachers}}
-	<small>{{$teacher.Surname}}</small>
-	{{end}}
-	{{end}}
-
+        {{$options := `noElements: "no coordinatore"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $department.Coordinator}}
+        {{$options := `noElements: "no materie"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $department.Subjects}}
       </div>
-
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}
+

+ 21 - 60
templates/departments_add_update.html.tpl

@@ -2,74 +2,35 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/departments?{{query "tpl_layout" "base" "tpl_content" "departments"}}">Dipartimento</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna il dipartimento</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/departments?{{query "tpl_layout" "base" "tpl_content" "departments"}}">Dipartimento</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
-
- {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna il dipartimento</h1>
-      </div>
-    </div>
-  </div>
-  
-  {{else}}
-  <h1 class="karmen-info-header">Crea nuovo dipartimento</h1>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_departments_add_update" action="/departments/{{.Data.Department.ID}}/update" method="POST" role="form" class="needs-validation">
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Dipartimenti" (all "Department") (.Data|string) (.Data.ID|show "Department") "Aggiorna" "current"}}
   {{else}}
-  <form id="form_departments_add_update" action="/departments/add/" method="POST" role="form" class="needs-validation">
+  {{template "breadcrumb" toSlice "Dipartimenti" (all "Department") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo dipartimento" "updateTitle" (printf "Aggiorna dipartimento %s" (.Data|string))}}
 
-    <div class="form-group">
-      <label class="control-label" for="department_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="department_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Department.Name}}" {{end}} required>
-    </div>
+  {{$form := "form_departments_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Department"}}{{else}}{{create "Department"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
 
-    <div class="form-group">
-      <label class="control-label" for="coordinator_id">Coordinatore</label>
-      <select name="coordinator_id" class="form-control selectpicker" id="coordinator_id" placeholder="Coordinatore" data-live-search="true" form="form_departments_add_update" title="Seleziona il nome del coordinatore" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $teacher := .Data.AllTeachers}}
-	{{if $.Options.Get "update"}}
-  	<option
-	  value="{{$teacher.ID}}"
-	  {{index $.Data.SelectedCoordinator $teacher.ID}}>{{$teacher.Name}} {{$teacher.Surname}}
-	</option>
-	{{else}}
-  	<option value="{{$teacher.ID}}">{{$teacher.Name}} {{$teacher.Surname}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
+    {{$options := ` { name: "Name",id: "department_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
 
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/departments/{{.Data.Department.ID}}?{{query "tpl_layout" "base" "tpl_content" "departments_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/departments/?{{query "tpl_layout" "base" "tpl_content" "departments"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
+    {{$options := ` { name: "coordinator_id", id: "coordinator_id", label: "Coordinatore", title: "Seleziona il coordinatore"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedCoordinator") "update" $update "form" $form}}
     
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Department"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
+  
   </form>
-
+  
 </div>
 
 {{ end }}

+ 36 - 83
templates/departments_show.html.tpl

@@ -2,95 +2,48 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/departments?{{query "tpl_layout" "base" "tpl_content" "departments"}}">Dipartimenti</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
-    </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Dipartimento di {{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-
-	<div class="btn-group float-right" role="group">
-	  <a href="/departments/add/?{{query "tpl_layout" "base" "tpl_content" "departments_add_update"}}" class="btn btn-success">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Crea
-	  </a>
-
-	  <a href="/departments/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "departments_add_update" "update" "true"}}"  class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/departments/{{.Data.ID}}/delete"
-		  data-url="/departments/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
+  {{template "breadcrumb" toSlice "Dipartimenti" (all "Department") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Department") "deletePath" (.Data.ID|delete "Department")}}
+
+  <h2 class="karmen-relation-header">Informazioni</h2>
+  {{if .Data.Coordinator}}
+  <p>Il coordinatore di questo dipartimento è {{(.Data.Coordinator.ID|show "Teacher")|anchor (.Data.Coordinator|string)}}.</p>
+  {{else}}
+  <p>Il dipartimento non ha coordinatore. Clicca {{(.Data.ID|update "Department")|anchor "qui"}} per aggiungere un coordinatore.</p>
+  {{end}}
+  
+  <div class="row">
+    <div class="col-md-12">
 
-      </div>
+      {{$options := `
+      title: "Materie afferenti al dipartimento"
+      model: "Subject"
+      icon: "fa fa-book"
+      `}}
+      
+      {{$noElements := "Nessuna materia associata al dipartimento."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Subjects "noElements" $noElements}}
+    
     </div>
   </div>
-
+  
   <div class="row">
-
     <div class="col-md-12">
-      <h2 class="karmen-relation-header">Coordinatore del dipartimento</h2>
-      <div class="list-group" id="departments_list_group">
-	{{if ne .Data.Coordinator.ID 0}}
-	<a href="/teachers/{{.Data.Coordinator.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-user"></span>
-	  {{.Data.Coordinator.Surname}} {{.Data.Coordinator.Name}}
-	  <div class="text-right">
-	    <small>Coordinatore</small>
-	  </div>
-	</a>
-	{{else}}
-	<span class="list-group-item list-group-item-action">Nessun coordinatore</span>
-	{{end}}
-      </div>
+
+      {{$options := `
+      title: "Docenti"
+      model: "Teacher"
+      icon: "fa fa-users"
+      `}}
+      
+      {{$noElements := "Nessun docente associato al dipartimento."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Teachers "noElements" $noElements}}
+
     </div>
     
-    <div class="col-md-12">
-      <h2 class="karmen-relation-header">Docenti del dipartimento</h2>
-      {{if not .Data.Teachers}}
-      <p>Nel dipartimento non è presente alcun docente
-	didattica. Clicca <a href="/activities/add/?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per creare
-	una nuova attività didattica.</p>
-      {{else}}
-      <div class="list-group" id="teachers_list_group">
-	{{range $teacher := .Data.Teachers}}
-	<a class="list-group-item list-group-item-action" href="/teachers/{{$teacher.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}">
-	  <span class="fa fa-user"></span>
-	  {{$teacher.Surname}} {{$teacher.Name}}
-	</a>
-	{{end}}
-      </div>
-      {{end}}
-    </div>    
+  </div>
 
-    <div class="col-md-12">
-      <h2 class="karmen-relation-header">Materie afferenti al dipartimento</h2>
-      {{if not .Data.Teachers}}
-      <p>Nel dipartimento non è presente alcuna materia
-	didattica. Clicca <a href="/subjects?{{query "tpl_layout" "base" "tpl_content" "subjects"}}">qui</a> per visualizzare le materie.</p>
-      {{else}}
-      <div class="list-group" id="subjects_list_group">
-	{{range $subject := .Data.Subjects}}
-	<a class="list-group-item list-group-item-action" href="/subjects/{{$subject.ID}}?{{query "tpl_layout" "base" "tpl_content" "subjects_show"}}">
-	  <span class="fa fa-book"></span>
-	  {{$subject.Name}}
-	</a>
-	{{end}}
-	
-      </div>
-      {{end}}
-    </div>    
+  
+</div>    
 
-    {{ end }}
+{{ end }}

+ 17 - 28
templates/documents.html.tpl

@@ -1,43 +1,32 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Documenti ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/documents/add/?{{query "tpl_layout" "base" "tpl_content" "documents_add_update"}}" class="btn btn-primary float-right">
-          <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	  Crea nuovo documento
-	</a>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Documenti"
+  buttonTitle: "Crea nuovo documento"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "Document")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $document := .Data}}
-    <a class="list-group-item list-group-item-action" href="/documents/{{$document.ID}}?{{query "tpl_layout" "base" "tpl_content" "documents_show"}}">
-      <span class="fa fa-file"></span>
+    <a class="list-group-item list-group-item-action" href={{$document.ID|show "Document"}}>
+      <span class="fa fa-file-alt"></span>
       {{$document.Name}}
-      {{if $document.GeneratorType}}
       <div class="text-right">
-	<small>{{$document.GeneratorType.Name}}</small>
+        {{$options := `noElements: "nessun tipo"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $document.GeneratorType}}
       </div>
-      {{end}}
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}

+ 41 - 78
templates/documents_add_update.html.tpl

@@ -2,89 +2,52 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-  {{if .Options.Get "update"}}
-  <ol class="breadcrumb">
-    <li class="breadcrumb-item"><a href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documento</a></li>
-    <li class="breadcrumb-item active"><a href="#">Aggiorna documento</a></li>
-  </ol>
-  </nav>
-  
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documento</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
-
- {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna documento</h1>
-      </div>
-    </div>
-  </div>
-  
-  {{else}}
-  <h1 class="karmen-info-header">Crea nuovo documento</h1>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_documents_add_update" action="/documents/{{.Data.ID}}/update" method="POST" role="form" data-toggle="validator">
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Documenti" (all "Document") (.Data|string) (.Data.ID|show "Document") "Aggiorna" "current"}}
   {{else}}
-  <form id="form_documents_add_update" action="/documents/add/" method="POST" role="form" data-toggle="validator">
+  {{template "breadcrumb" toSlice "Documenti" (all "Document") "Aggiungi" "current"}}
   {{end}}
-
-    <div class="form-group has-feedback">
-      <label class="control-label" for="document_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="document_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Name}}" {{end}} required>
-    </div>
-
-    <div class="form-group has-feedback">
-      <label class="control-label" for="document_cloud_path">Cartella di lavoro remota (cloud)</label>
-      <input type="text" name="CloudPath" class="form-control" id="document_cloud_path" placeholder="Documents/" {{if .Options.Get "update"}} value="{{.Data.CloudPath}}" {{end}} required>
-    </div>
-
-    <div class="form-check">
-      <input type="checkbox" name="KeepArtifacts" class="form-check-input" id="keep_artifacts" {{if .Options.Get "update"}}{{if .Data.KeepArtifacts}}checked{{end}}{{end}}>
-      <label class="form-check-label" for="exampleCheck1">Mantieni gli artefatti</label>
-      <small id="activity_group_hours_help_box" class="form-text text-muted">
-        Se selezionato carica sulla cartella remota gli artefatti derivati dalla produzione del documento (es: file di Markdown).
-      </small>        
-    </div>
-
-        
-    <div class="form-group">
-      <label class="control-label" for="generator_type_id">Tipo di generatore</label>
-      <select name="GeneratorTypeID" class="form-control selectpicker" id="generator_type_id" placeholder="Tipo di generatore" data-live-search="true" form="form_documents_add_update" title="Seleziona il tipo di generatore" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $generatorType := .Data.AllGeneratorTypes}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$generatorType.ID}}"
-	   {{index $.Data.SelectedGeneratorType $generatorType.ID}}>{{$generatorType.Name}}
-	</option>
-	{{else}}
-  	<option value="{{$generatorType.ID}}">{{$generatorType.Name}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
-
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/documents/{{.Data.ID}}?{{query "tpl_layout" "base" "tpl_content" "documents_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo documento" "updateTitle" (printf "Aggiorna documento %s" (.Data|string))}}
+
+  {{$form := "form_documents_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Document"}}{{else}}{{create "Document"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
+
+    {{$options := ` { name: "Name",id: "document_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
+
+    {{$options := ` { name: "CloudPath",id: "document_cloud_path",label: "Cartella di lavoro remota",placeholder: "Documents/",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "CloudPath") "update" $update}}
+
+    {{$options := `
+    name: "KeepArtifacts"
+    id: "document_keep_artifacts"
+    label: "Mantieni gli artefatti"
+    formClass: "form-group form-check"
+    help: "Se selezionato carica sulla cartella remota gli artefatti derivati dalla produzione del documento (es: markdown, odt, etc.). "
+    `}}
+    {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "KeepArtifacts") "update" $update}}
+
+    {{$options := `
+    name: "generator_type_id"
+    id: "generator_type_id"
+    label: "Tipo di generatore"
+    title: "Seleziona il tipo di generatore"
+    `}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllGeneratorTypes") "selected" (.Data|field "SelectedGeneratorType") "update" $update "form" $form}}
     
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Document"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
+  
   </form>
-
+  
 </div>
 
 {{ end }}

+ 23 - 67
templates/documents_show.html.tpl

@@ -2,84 +2,40 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documenti</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
-    </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-        
-	<div class="btn-group float-right" role="group">
-          
-	  <a href="/documents/add/?{{query "tpl_layout" "base" "tpl_content" "documents_add_update"}}" class="btn btn-success">
-            <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	    Crea
-	  </a>
-
-	  <a href="/documents/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "documents_add_update" "update" "true"}}"  class="btn btn-primary">
-            <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/documents/{{.Data.ID}}/delete"
-		  data-url="/documents/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-            <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-
-      </div>
-    </div>
-  </div>
-
+  {{template "breadcrumb" toSlice "Documenti" (all "Document") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Document") "deletePath" (.Data.ID|delete "Document")}}
+  
   <div class="row">
     <div class="col-md-12">
+
+      {{$options := `
+      title: "Lavori in esecuzione"
+      model: "Job"
+      icon: "fa fa-file-alt"
+      `}}
       
-      <h2 class="karmen-relation-header">Lavori in esecuzione</h2>
-      {{if .Data.RunningJobs}}
-      <div class="list-group" id="jobs_list_group">
-    	{{range $job := .Data.RunningJobs}}
-    	<a href="/jobs/{{$job.ID}}?{{query "tpl_layout" "base" "tpl_content" "jobs_show"}}" class="list-group-item list-group-item-action">
-    	  <span class="fa fa-file-alt"></span>
-    	  Il lavoro con ID {{$job.ID}} è in esecuzione.
-    	  {{end}}
-    	</a>
-      </div>
-      {{else}}
-      <p>Nessun lavoro è attualmente in esecuzione. Clicca <a href="/documents/{{.Data.ID}}/execute?{{query "tpl_layout" "base" "tpl_content" "documents_execute"}}">qui</a> per avviare l'esecuzione di un nuovo lavoro.</p>
-      {{end}}
-    </div>
+      {{$noElements := (printf "Nessun lavoro in esecuzione. Clicca %s per eseguire un nuovo lavoro." ((.Data.ID|execute "Document")|anchor "qui"))|html}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.RunningJobs "noElements" $noElements}}
     
+    </div>
   </div>
 
-  <div class="row">
+    <div class="row">
     <div class="col-md-12">
+
+      {{$options := `
+      title: "Lavori completati di recente"
+      model: "Job"
+      icon: "fa fa-file-alt"
+      `}}
       
-      <h2 class="karmen-relation-header">Lavori terminati di recente</h2>
-      {{if .Data.CompletedJobs}}
-      <div class="list-group" id="jobs_list_group">
-    	{{range $job := .Data.CompletedJobs}}
-    	<a href="/jobs/{{$job.ID}}?{{query "tpl_layout" "base" "tpl_content" "jobs_show"}}" class="list-group-item list-group-item-action">
-    	  <span class="fa fa-file-alt"></span>
-    	  Il lavoro con ID {{$job.ID}} è stato completato in data {{$job.End}}.
-    	  {{end}}
-    	</a>
-      </div>
-      {{else}}
-      <p>Nessun lavoro è stato terminato di recente.</p>
-      {{end}}
-    </div>
+      {{$noElements := "Nessun lavoro è stato completato di recente."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.CompletedJobs "noElements" $noElements}}
     
+    </div>
   </div>
-  
 
+  
 </div>    
 
 {{ end }}

+ 17 - 28
templates/groups.html.tpl

@@ -1,43 +1,32 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Gruppi ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/groups/add/?{{query "tpl_layout" "base" "tpl_content" "groups_add_update"}}" class="btn btn-primary float-right">
-	  <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
-	  Crea nuovo gruppo
-	</a>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Gruppi"
+  buttonTitle: "Crea nuovo gruppo"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "Group")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $group := .Data}}
-    <a class="list-group-item list-group-item-action" href="/groups/{{$group.ID}}?{{query "tpl_layout" "base" "tpl_content" "groups_show"}}">
+    <a class="list-group-item list-group-item-action" href={{$group.ID|show "Group"}}>
       <span class="fa fa-users"></span>
-      {{$group.Name}}
+      {{$group|string}}
       <div class="text-right">
- 	{{range $teacher := $group.Teachers}}
-	<small>{{$teacher.Surname}}</small>
-	{{end}}
+        {{$options := `noElements: "nessun elemento"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $group.Teachers}}
       </div>
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}

+ 23 - 65
templates/groups_add_update.html.tpl

@@ -1,80 +1,38 @@
 {{ define "content" }}
 
 <div class="container">
+  
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna gruppo</a></li>
-    </ol>
-  </nav>
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Gruppi" (all "Group") (.Data|string) (.Data.ID|show "Group") "Aggiorna" "current"}}
   {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
+  {{template "breadcrumb" toSlice "Gruppi" (all "Group") "Aggiungi" "current"}}
   {{end}}
-
-  {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna gruppo</h1>
-      </div>
-    </div>
-  </div>
   
-  {{else}}
-  <h1 class="karmen-info-header">Crea nuovo gruppo</h1>
-  {{end}}
-
-  {{if .Options.Get "update"}}
-  <form id="form_groups_add_update" action="/groups/{{.Data.Group.ID}}/update" method="POST" role="form" data-toggle="validator">
-    {{else}}
-    <form id="form_groups_add_update" action="/groups/add/" method="POST" role="form" data-toggle="validator">
-      {{end}}
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo gruppo" "updateTitle" (printf "Aggiorna gruppo %s" (.Data|string))}}
 
-      <div class="form-group has-feedback">
-        <label class="control-label" for="group_name">Nome</label>
-        <input type="text" name="Name" class="form-control" id="group_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Group.Name}}" {{end}} required>
-      </div>
+  {{$form := "form_groups_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Group"}}{{else}}{{create "Group"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
 
-      <div class="form-group has-feedback">
-        <label class="control-label" for="group_query">Stringa di popolamento</label>
-        <input type="text" name="Query" class="form-control" id="group_query" placeholder="surname:*" {{if .Options.Get "update"}} value="{{.Data.Group.Query}}" {{end}}>
-      </div>
+    {{$options := ` { name: "Name",id: "group_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
 
-      <div class="form-group">
-        <label class="control-label" for="teacher_id">Docenti</label>
-        <select name="teacher_ids" class="form-control selectpicker" id="teacher_ids" placeholder="Docenti" data-live-search="true" form="form_groups_add_update" title="Seleziona i nomi dei docenti" data-dropup-auto="false" multiple>
-          <option value="0"></option>
-          {{range $teacher := .Data.AllTeachers}}
-          {{if $.Options.Get "update"}}
-          <option
-	    value="{{$teacher.ID}}"
-	    {{index $.Data.SelectedTeacher $teacher.ID}}>{{$teacher.CompleteName}}
-          </option>
-          {{else}}
-          <option value="{{$teacher.ID}}">{{$teacher.CompleteName}}</option>
-          {{end}}
-          {{end}}
-        </select>
-      </div>
+    {{$options := ` { name: "Query",id: "group_query",label: "Stringa di popolamento",placeholder: "surname:*",type: "text"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Query") "update" $update}}
 
-      <div class="form-group">
-        <button type="submit" class="btn btn-primary">Salva</button>
-        {{if .Options.Get "update"}}
-        <a href="/groups/{{.Data.Group.ID}}?{{query "tpl_layout" "base" "tpl_content" "groups_show"}}" class="btn btn-default">Annulla</a>
-        {{else}}
-        <a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}" class="btn btn-default">Annulla</a>
-        {{end}}
-      </div>
-    
-    </form>
+    {{$options := ` { name: "teacher_ids", id: "teacher_ids", label: "Docenti", title: "Seleziona i docenti", multiple: "true"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedTeacher") "update" $update "form" $form}}
 
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Group"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}  
+  </form>
+  
 </div>
 
 {{ end }}

+ 12 - 47
templates/groups_show.html.tpl

@@ -2,59 +2,24 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-  <ol class="breadcrumb">
-    <li class="breadcrumb-item"><a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a></li>
-    <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
-  </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-	<div class="btn-group float-right" role="group">
-	  <a href="/groups/add/?{{query "tpl_layout" "base" "tpl_content" "groups_add_update"}}" class="btn btn-success">
-	    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
-	    Crea
-	  </a>
-	  <a href="/groups/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "groups_add_update" "update" "true"}}"  class="btn btn-primary">
-	    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/groups/{{.Data.ID}}/delete"
-		  data-url="/groups/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-      </div>
-    </div>
-  </div>
+  {{template "breadcrumb" toSlice "Gruppi" (all "Group") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Group") "deletePath" (.Data.ID|delete "Group")}}
   
   <div class="row">
     <div class="col-md-12">
+
+      {{$options := `
+      title: "Docenti appartenenti al gruppo"
+      model: "Teacher"
+      icon: "fa fa-user"
+      `}}
       
-      <h2 class="karmen-relation-header">Docenti associati al gruppo</h2>
-      {{if .Data.Teachers}}
-      <div class="list-group" id="students_list_group">
-	{{range $teacher := .Data.Teachers}}
-	<a href="/teachers/{{$teacher.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-user"></span>
-	  {{$teacher.CompleteName}}
-	  {{end}}
-	  </a>
-      </div>
-      {{else}}
-      <p>Al gruppo non è associato alcun docente. Clicca <a href="/groups/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "groups_add_update"}}">qui</a> per modificare questo gruppo.</p>
-      {{end}}
-    </div>
+      {{$noElements := "Al gruppo non è associato nessun docente."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Teachers "noElements" $noElements}}
     
+    </div>
   </div>
-
+  
 </div>    
 
 {{ end }}

+ 19 - 22
templates/jobs.html.tpl

@@ -1,35 +1,32 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-12">
-	<h1>Lavori ({{len .Data}})</h1>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Lavori"
+  buttonTitle: "Esegui nuovo lavoro"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (all "Document")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $job := .Data}}
-    <a class="list-group-item list-group-item-action" href="/jobs/{{$job.ID}}?{{query "tpl_layout" "base" "tpl_content" "jobs_show"}}">
-      <span class="fa fa-file"></span>
-      Lavoro ID {{$job.ID}} relativo al document {{.Document.Name}}
+    <a class="list-group-item list-group-item-action" href={{$job.ID|show "Job"}}>
+      <span class="fa fa-user"></span>
+      {{$job|string}}
       <div class="text-right">
-	<small>optional tag goes here</small>
+        {{$options := `noElements: "nessun documento"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $job.Document}}
       </div>
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
-{{ end }}
+
+{{end}}

+ 18 - 47
templates/jobs_show.html.tpl

@@ -2,56 +2,27 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documenti</a></li>
-      <li class="breadcrumb-item"><a href="/documents/{{.Data.DocumentID}}?{{query "tpl_layout" "base" "tpl_content" "documents_show"}}">{{.Data.Document.Name}}</a></li>
-      <li class="breadcrumb-item active"><a href="#">Lavoro ID {{.Data.ID}}</a></li>
-    </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Lavoro ID {{.Data.ID}}</h1>
-      </div>
-      <div class="col-md-4">
-        
-	<div class="btn-group float-right" role="group">
-	  <button href="/jobs/{{.Data.ID}}/delete"
-		  data-url="/jobs/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-            <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-
-      </div>
-    </div>
-  </div>
+  {{template "breadcrumb" toSlice "Lavori" (all "Job") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Job") "deletePath" (.Data.ID|delete "Job")}}
   
   <div class="row">
     <div class="col-md-12">
+            
+      {{$options := `
+      title: "File prodotti"
+      model: "File"
+      icon: "fa fa-file-alt"
+      `}}
       
-      <h2 class="karmen-relation-header">Documenti prodotti</h2>
-      {{if .Data.Files}}
-      <div class="list-group" id="files_list_group">
-    	{{range $file := .Data.Files}}
-    	<a href="/jobs/{{$file.JobID}}/files/{{$file.Path}}?format=pdf" class="list-group-item list-group-item-action">
-    	  <span class="fa fa-file-alt"></span>
-    	  {{$file.Path}}
-    	  {{end}}
-    	</a>
-      </div>
-      {{else}}
-      <p>Non è stato prodotto nessun file..</p>
-      {{end}}
-    </div>
+      {{$noElements := "Questo lavoro non ha ancora prodotto alcun file."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Files "format" "pdf" "noElements" $noElements}}
     
+    </div>
   </div>
-  
-  <div class="row">
-    <div class="col-md-12">    
+
+    <div class="row">
+    <div class="col-md-12">
+
       <h2 class="karmen-relation-header">Messaggi dal generatore</h2>
       {{if .Data.Logs}}
       <pre class="terminal pre-scrollable">
@@ -62,13 +33,13 @@
         </code>
       </pre>
       {{else}}
-      <p>Non è stato prodotto nessun file..</p>
+      <p>Il generatore non ha prodotto alcun messaggio.</p>
       {{end}}
-    </div>
     
+    </div>
   </div>
 
-
+  
 </div>    
 
 {{ end }}

+ 13 - 0
templates/layout/add_update_header.html.tpl

@@ -0,0 +1,13 @@
+{{define "add_update_header"}}
+<div class="karmen-info-header">
+  <div class="row">
+    <div class="col-md-12">
+      {{if .update}}
+      <h1>{{.updateTitle}}</h1>
+      {{else}}
+      <h1>{{.addTitle}}</h1>
+      {{end}}
+    </div>
+  </div>
+</div>
+{{end}}

+ 2 - 2
templates/layout/base.html.tpl

@@ -43,10 +43,10 @@
 	     <a class="nav-item nav-link" href="/administratives?{{query "tpl_layout" "base" "tpl_content" "administratives"}}">ATA</a>
 	   </li>
            <li class="nav-item">
-	     <a class="nav-item nav-link" href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi<span class="badge badge-danger experimental">Sperimentale</span></a>
+	     <a class="nav-item nav-link" href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi<span class="badge badge-danger experimental">EXP</span></a>
 	   </li>           
            <li class="nav-item">
-	     <a class="nav-item nav-link" href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documenti<span class="badge badge-danger experimental">Sperimentale</span></a>
+	     <a class="nav-item nav-link" href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documenti<span class="badge badge-danger experimental">EXP</span></a>
 	   </li>
 	  </ul>
 

+ 17 - 0
templates/layout/breadcrumb.html.tpl

@@ -0,0 +1,17 @@
+{{define "breadcrumb"}}
+<nav aria-label="breadcrumb">
+  <ol class="breadcrumb">
+    {{$data:=.}}
+    {{range $i,$el := .}}
+    {{if ($i|mod2)}}
+    {{$url:=index $data ($i|incr)}}
+    {{if eq $url "current"}}
+    <li class="breadcrumb-item active" aria-current="page">{{$el}}</li>
+    {{else}}
+    <li class="breadcrumb-item"><a href="{{$url}}">{{$el}}</a></li>
+    {{end}}
+    {{end}}
+    {{end}}
+  </ol>
+</nav>
+{{end}}

+ 11 - 0
templates/layout/checkbox.html.tpl

@@ -0,0 +1,11 @@
+{{define "checkbox"}}
+<div class="{{if .options.formClass}}{{.options.formClass}}{{else}}form-group{{end}}">
+  <input type="checkbox"
+         name="{{.options.name}}"
+         class="form-check-input"
+         id="{{.options.id}}"
+         {{if .update}}{{if eq true .value}}checked{{end}}{{end}} {{.options.required}}>
+  <label class="form-check-label" for="{{.options.id}}">{{.options.label}}</label>
+  {{if .options.help}}<small class="form-text text-muted">{{.options.help}}</small>{{end}}
+</div>
+{{end}}

+ 6 - 0
templates/layout/create_button.html.tpl

@@ -0,0 +1,6 @@
+{{define "create_button"}}
+<a href="{{.modelPath}}" class="btn btn-primary">
+  <span class="fa fa-plus-circle" aria-hidden="true"></span>
+  {{.buttonTitle}}
+</a>
+{{end}}

+ 8 - 0
templates/layout/delete_button.html.tpl

@@ -0,0 +1,8 @@
+{{define "delete_button"}}
+<button href="{{.modelPath}}"
+	data-url="{{.modelPath}}"
+	class="btn btn-danger karmen-ajax-delete">
+  <span class="fa fa-trash" aria-hidden="true"></span>
+  Elimina
+</button>
+{{end}}

+ 3 - 0
templates/layout/display_no_elements.html.tpl

@@ -0,0 +1,3 @@
+{{define "display_no_elements"}}
+<p>Non c'è alcun elemento da visualizzare</p>
+{{end}}

+ 11 - 0
templates/layout/input.html.tpl

@@ -0,0 +1,11 @@
+{{define "input"}}
+<div class="{{if .options.formClass}}{{.options.formClass}}{{else}}form-group{{end}}">
+  <label class="control-label" for="{{.options.id}}">{{.options.label}}</label>
+  <input type="{{.options.type}}"
+         name="{{.options.name}}"
+         class="{{if .options.inputClass}}{{.options.inputClass}}{{else}}form-control{{end}}"
+         id="{{.options.id}}"
+         placeholder="{{.options.placeholder}}" {{if .update}}value="{{.value}}"{{end}} {{if .options.required}}required{{end}}>
+  {{if .options.help}}<small class="form-text text-muted">{{.options.help}}</small>{{end}}
+</div>
+{{end}}

+ 14 - 0
templates/layout/read_all_header.html.tpl

@@ -0,0 +1,14 @@
+{{define "read_all_header"}}
+<div class="karmen-info-header">
+  <div class="row">
+    <div class="col-md-8">
+      <h1>{{.options.title}} ({{.lengthData}})</h1>
+    </div>
+    <div class="col-md-4">
+      <div class="btn-group float-right">
+        {{template "create_button" dict "buttonTitle" .options.buttonTitle "modelPath" .modelPath}}
+      </div>
+    </div>
+  </div>
+</div>
+{{end}}

+ 56 - 0
templates/layout/relation_list.html.tpl

@@ -0,0 +1,56 @@
+{{define "relation_list"}}
+
+{{$format := "html"}}
+{{if .format}}
+{{$format = .format}}
+{{end}}
+
+<h2 class="karmen-relation-header">{{$title := .options.title}}{{if .title}}{{$title = .title}}{{end}}{{$title}}</h2>
+{{if (.data|isSlice)}}
+
+{{if gt (len .data) 0}}
+<div class="list-group" id="{{$title|toLower}}_list_group">
+  {{range $el := .data}}
+  <a href="{{show $.options.model $el.ID $format}}" class="list-group-item list-group-item-action">
+    <span class="{{$.options.icon}}"></span>
+    {{$el | string}}
+    {{if $.small}}
+    {{range $s := $.small}}
+    <div class="text-right">
+      {{$options := `noElements: "nessun elemento"`}}
+      {{template "small" dict "options" ($options | yaml) "data" ($el|field $s)}}
+    </div>
+    {{end}}
+    {{end}}
+    {{end}}
+  </a>
+</div>
+{{else}}
+<p>{{.noElements}}</p>
+{{end}}
+
+{{else}}
+
+{{if .data}}
+<div class="list-group" id="{{$title|toLower}}_list_group">
+
+  <a href="{{.data.ID | show $.options.model}}" class="list-group-item list-group-item-action">
+    <span class="{{$.options.icon}}"></span>
+    {{.data | string}}
+    {{if .small}}
+    {{range $s := .small}}
+    <div class="text-right">
+      {{$options := `noElements: "nessun elemento"`}}
+      {{template "small" dict "options" ($options | yaml) "data" (.data|field $s)}}
+    </div>
+    {{end}}
+    {{end}}
+  </a>
+</div>
+
+{{else}}
+<p>{{.noElements}}</p>
+{{end}}
+
+{{end}}
+{{end}}

+ 7 - 0
templates/layout/search_input.html.tpl

@@ -0,0 +1,7 @@
+{{define "search_input"}}
+<div class="input-group" style="margin-bottom: 20px">
+  <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
+  <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
+</div>
+{{end}}
+

+ 25 - 0
templates/layout/select.html.tpl

@@ -0,0 +1,25 @@
+{{define "select"}}
+<div class="form-group">
+  <label class="control-label" for="{{.options.id}}">{{.options.label}}</label>
+  <select name="{{.options.name}}"
+          class="form-control selectpicker"
+          id="{{.options.id}}"
+          placeholder="{{.options.placeholder}}"
+          data-live-search="true"
+          form="{{.form}}"
+          title="{{.options.title}}"
+          data-dropup-auto="false" {{if .options.multiple}}multiple{{end}}>
+    <option value="0"></option>
+    {{range $el := .data}}
+    {{if $.update}}
+    <option
+      value="{{$el.ID}}"
+      {{index $.selected $el.ID}}>{{$el|string}}
+    </option>
+    {{else}}
+    <option value="{{$el.ID}}">{{$el|string}}</option>
+    {{end}}
+    {{end}}
+  </select>
+</div>
+{{end}}

+ 15 - 0
templates/layout/show_header.html.tpl

@@ -0,0 +1,15 @@
+{{define "show_header"}}
+<div class="karmen-info-header">
+  <div class="row">
+    <div class="col-md-8">
+      <h1>{{.title}}</h1>
+    </div>
+    <div class="col-md-4">
+      <div class="btn-group float-right" role="group">
+        {{template "update_button" dict "modelPath" .updatePath}}
+        {{template "delete_button" dict "modelPath" .deletePath}}
+      </div>
+    </div>
+  </div>
+</div>
+{{end}}

+ 13 - 0
templates/layout/small.html.tpl

@@ -0,0 +1,13 @@
+{{define "small"}}
+{{if .data}}
+{{if .data | isSlice}}
+{{range $el := .data}}
+<small>{{$el|string}}</small>
+{{end}}
+{{else}}
+<small>{{.data|string}}</small>
+{{end}}
+{{else}}
+<small>{{.options.noElements}}</small>
+{{end}}
+{{end}}

+ 10 - 0
templates/layout/submit_cancel_button.html.tpl

@@ -0,0 +1,10 @@
+{{define "submit_cancel_buttons"}}
+<div class="form-group">
+  <button type="submit" class="btn btn-primary">{{.options.saveTitle}}</button>
+  {{if .options.update}}
+  <a href="{{.id|show .options.model}}" class="btn btn-default">Annulla</a>
+  {{else}}
+  <a href="{{all .options.model}}" class="btn btn-default">Annulla</a>
+  {{end}}
+</div>
+{{end}}

+ 6 - 0
templates/layout/update_button.html.tpl

@@ -0,0 +1,6 @@
+{{define "update_button"}}
+<a href="{{.modelPath}}" class="btn btn-primary">
+  <span class="fa fa-edit" aria-hidden="true"></span>
+  Modifica
+</a>
+{{end}}

+ 18 - 34
templates/offices.html.tpl

@@ -1,49 +1,33 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Uffici ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/offices/add/?{{query "tpl_layout" "base" "tpl_content" "offices_add_update"}}" class="btn btn-primary float-right">
-	  <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	  Crea nuovo ufficio
-	</a>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Uffici"
+  buttonTitle: "Crea nuovo ufficio"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "Office")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $office := .Data}}
-    <a class="list-group-item list-group-item-action" href="/offices/{{$office.ID}}?{{query "tpl_layout" "base" "tpl_content" "offices_show"}}">
-      <span class="fa fa-layer-group"></span>
+    <a class="list-group-item list-group-item-action" href={{$office.ID|show "Office"}}>
+      <span class="fa fa-user"></span>
       {{$office.Name}}
-
       <div class="text-right">
-	
-	{{if $office.Administratives}}
-	{{range $administrative := $office.Administratives}}
-	<small>{{$administrative.Surname}}</small>
-	{{end}}
-	{{end}}
-        
+        {{$options := `noElements: "nessun personale amministrativo"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $office.Administratives}}
       </div>
-
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}
+

+ 20 - 49
templates/offices_add_update.html.tpl

@@ -1,64 +1,35 @@
 {{ define "content" }}
 
 <div class="container">
-
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/offices?{{query "tpl_layout" "base" "tpl_content" "offices"}}">Segreteria</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna la segreteria</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/offices?{{query "tpl_layout" "base" "tpl_content" "offices"}}">Segreteria</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
-
- {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna la segreteria</h1>
-      </div>
-    </div>
-  </div>
   
-  {{else}}
-  <h1 class="karmen-info-header">Crea nuova segreteria</h1>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_offices_add_update" action="/offices/{{.Data.Office.ID}}/update" method="POST" role="form" class="needs-validation">
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Uffici" (all "Office") (.Data|string) (.Data.ID|show "Office") "Aggiorna" "current"}}
   {{else}}
-  <form id="form_offices_add_update" action="/offices/add/" method="POST" role="form" class="needs-validation">
+  {{template "breadcrumb" toSlice "Uffici" (all "Office") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo ufficio" "updateTitle" (printf "Aggiorna ufficio %s" (.Data|string))}}
 
-    <div class="form-group">
-      <label class="control-label" for="office_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="office_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Office.Name}}" {{end}} required>
-    </div>
+  {{$form := "form_offices_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Office"}}{{else}}{{create "Office"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
 
-    <div class="form-group">
-      <label class="control-label" for="office_name">Email</label>
-      <input type="text" name="Email" class="form-control" id="office_email" placeholder="Email dell'ufficio" {{if .Options.Get "update"}} value="{{.Data.Office.Email}}" {{end}} required>
-    </div>
+    {{$options := ` { name: "Name",id: "office_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
 
+    {{$options := ` { name: "Email",id: "office_email",label: "Email",placeholder: "Inserire l'indirizzo di posta elettronica",type: "email"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Email") "update" $update}}
 
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/offices/{{.Data.Office.ID}}?{{query "tpl_layout" "base" "tpl_content" "offices_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/offices?{{query "tpl_layout" "base" "tpl_content" "offices"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
-    
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Office"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}  
   </form>
-
+  
 </div>
 
 {{ end }}

+ 24 - 63
templates/offices_show.html.tpl

@@ -2,70 +2,31 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/offices?{{query "tpl_layout" "base" "tpl_content" "offices"}}">Uffici</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
-    </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Ufficio di {{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-
-	<div class="btn-group float-right" role="group">
-	  <a href="/offices/add/?{{query "tpl_layout" "base" "tpl_content" "offices_add_update"}}" class="btn btn-success">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Crea
-	  </a>
-
-	  <a href="/offices/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "offices_add_update" "update" "true"}}"  class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/offices/{{.Data.ID}}/delete"
-		  data-url="/offices/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-
-      </div>
-    </div>
-  </div>
-
+  {{template "breadcrumb" toSlice "Uffici" (all "Office") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Office") "deletePath" (.Data.ID|delete "Office")}}
+
+  <h2 class="karmen-relation-header">Informazioni</h2>
+  {{if .Data.Email}}
+  <p>L'indirizzo di posta elettronica dell'ufficio è <a href="mailto:{{.Data.Email}}">{{.Data.Email}}</a></p>
+  {{else}}
+  <p>Nessuna informazione</p>
+  {{end}}
+  
   <div class="row">
-
     <div class="col-md-12">
-      <h2 class="karmen-relation-header">Informazioni sull'ufficio</h2>
-      {{if .Data.Email}}
-      <span class="fa fa-envelope"></span>
-      <a href="mailto:{{.Data.Email}}">{{.Data.Email}}</a>
-      {{else}}
-      <span class="list-group-item list-group-item-action">Nessun coordinatore</span>
-      {{end}}
-    </div>
+
+      {{$options := `
+      title: "Personale ATA afferente all'ufficio"
+      model: "Administrative"
+      icon: "fa fa-user"
+      `}}
+      
+      {{$noElements := "All'ufficio non è associato personale ATA"}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Administratives "noElements" $noElements}}
     
-    <div class="col-md-12">
-      <h2 class="karmen-relation-header">Personale amministrativo presente nell'ufficio</h2>
-      {{if not .Data.Administratives}}
-      <p>All'ufficio non è associato nessun personale amministrativo
-	didattica. Clicca <a href="/administratives/add/?{{query "tpl_layout" "base" "tpl_content" "administratives_add_update"}}">qui</a> per creare
-	una nuova utenza amministrativa.</p>
-      {{else}}
-      <div class="list-group" id="administratives_list_group">
-	{{range $administrative := .Data.Administratives}}
-	<a class="list-group-item list-group-item-action" href="/administratives/{{$administrative.ID}}?{{query "tpl_layout" "base" "tpl_content" "administratives_show"}}">
-	  <span class="fa fa-user"></span>
-	  {{$administrative.Surname}} {{$administrative.Name}}
-	</a>
-	{{end}}
-      </div>
-      {{end}}
-    </div>    
+    </div>
+  </div>
+  
+</div>    
 
-    {{ end }}
+{{ end }}

+ 19 - 33
templates/students.html.tpl

@@ -1,48 +1,34 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Studenti ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<a href="/students/add/?{{query "tpl_layout" "base" "tpl_content" "students_add_update"}}" class="btn btn-primary float-right">
-	  <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	  Crea nuovo studente
-	</a>
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Studenti"
+  buttonTitle: "Crea nuovo studente"
+  `}}
   
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "Student")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $student := .Data}}
-    <a class="list-group-item list-group-item-action" href="/students/{{$student.ID}}?{{query "tpl_layout" "base" "tpl_content" "students_show"}}">
+    <a class="list-group-item list-group-item-action" href={{$student.ID|show "Student"}}>
       <span class="fa fa-user"></span>
-      {{$student.Surname}} {{$student.Name}}
-      {{if $student.Class}}
-      <div class="text-right">
-	<small>{{$student.Class.Name}}</small>
-      </div>
-      {{else}}
+      {{$student|string}}
       <div class="text-right">
-	<small>no classe</small>
+        {{$options := `noElements: "no classe"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $student.Class}}
       </div>
-
-      {{end}}
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}
+
+

+ 30 - 89
templates/students_add_update.html.tpl

@@ -2,108 +2,49 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <ol class="breadcrumb">
-    <li><a href="/students?{{query "tpl_layout" "base" "tpl_content" "students"}}">Studente</a></li>
-    <li class="active"><a href="#">Aggiorna studente</a></li>
-  </ol>  
-  {{else}}
-  <ol class="breadcrumb">
-    <li><a href="/students?{{query "tpl_layout" "base" "tpl_content" "students"}}">Studente</a></li>
-    <li class="active"><a href="#">Aggiungi</a></li>
-  </ol>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
- {{if .Options.Get "update"}}
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Aggiorna studente</h1>
-      </div>
-    </div>
-  </div>
-  
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Studenti" (all "Student") (.Data|string) (.Data.ID|show "Student") "Aggiorna" "current"}}
   {{else}}
-  <h1 class="karmen-info-header">Crea nuovo studente</h1>
+  {{template "breadcrumb" toSlice "Studenti" (all "Student") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo studente" "updateTitle" (printf "Aggiorna studente %s" (.Data|string))}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_students_add_update" action="/students/{{.Data.Student.ID}}/update" method="POST" role="form" class="needs-validation">
-  {{else}}
-  <form id="form_students_add_update" action="/students/add/" method="POST" role="form" class="needs-validation">
-  {{end}}
+  {{$form := "form_students_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Student"}}{{else}}{{create "Student"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
 
-    <div class="form-group">
-      <label class="control-label" for="student_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="student_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Student.Name}}" {{end}} required>
-    </div>
+    {{$options := ` { name: "Name",id: "student_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="student_surname">Cognome</label>
-      <input type="text" name="Surname" class="form-control" id="student_surname" placeholder="Cognome" {{if .Options.Get "update"}} value="{{.Data.Student.Surname}}" {{end}} required>
-    </div>
+    {{$options := ` { name: "Surname",id: "student_surname",label: "Cognome",placeholder: "Cognome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Surname") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="class_id">Classe</label>
-      <select name="class_id" class="form-control selectpicker" id="class_id" placeholder="Classe" data-live-search="true" form="form_students_add_update" title="Seleziona la classe" data-dropup-auto="false" required>
-    	<option value="0"></option>
-    	{{range $class := .Data.AllClasses}}
-    	{{if $.Options.Get "update"}}
-    	<option
-    	   value="{{$class.ID}}"
-    	   {{index $.Data.SelectedClass $class.ID}}>{{$class.Name}}
-    	</option>
-    	{{else}}
-    	<option value="{{$class.ID}}">{{$class.Name}}</option>
-    	{{end}}
-    	{{end}}
-      </select>
-    </div>
+    {{$options := ` { name: "class_id", id: "class_id", label: "Classe", title: "Seleziona la classe"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllClasses") "selected" (.Data|field "SelectedClass") "update" $update "form" $form}}
 
-    <div class="form-group form-check">
-      <input type="checkbox" name="Handicap" class="form-check-input" id="student_handicap" {{if .Options.Get "update"}}{{if .Data.Student.Handicap}}checked{{end}}{{end}}>
-      <label class="form-check-label has-feedback" for="student_handicap">Handicap (L. 104/92)</label>
-    </div>
+    {{$options := ` { name: "tutor_id", id: "tutor_id", label: "Docente tutor", title: "Seleziona il docente tutor"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllTeachers") "selected" (.Data|field "SelectedTeacher") "update" $update "form" $form}}
 
-    <div class="form-group form-check">
-      <input type="checkbox" name="DSA" class="form-check-input" id="student_dsa" {{if .Options.Get "update"}}{{if .Data.Student.DSA}}checked{{end}}{{end}}>
-      <label class="form-check-label has-feedback" for="student_dsa">DSA (L. 170/10)</label>
-    </div>
+    {{$options := ` { name: "Handicap",id: "student_handicap",label: "Handicap (L. 104/92)",formClass: "form-group form-check" } `}}
+    {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "Handicap") "update" $update}}
 
-    <div class="form-group form-check">
-      <input type="checkbox" name="BES" class="form-check-input" id="student_bes" {{if .Options.Get "update"}}{{if .Data.Student.BES}}checked{{end}}{{end}}>
-      <label class="form-check-label has-feedback" for="student_bes">BES (L. 170/10)</label>
-    </div>
+    {{$options := ` { name: "DSA",id: "student_dsa",label: "DSA (L. 170/10)",formClass: "form-group form-check" } `}}
+    {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "DSA") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="class_id">Docente tutor</label>
-      <select name="tutor_id" class="form-control selectpicker" id="tutor_id" placeholder="Docente tutor" data-live-search="true" form="form_students_add_update" title="Seleziona il docente tutor" data-dropup-auto="false">
-    	<option value="0"></option>
-    	{{range $teacher := .Data.AllTeachers}}
-    	{{if $.Options.Get "update"}}
-    	<option
-    	   value="{{$teacher.ID}}"
-    	   {{index $.Data.SelectedTeacher $teacher.ID}}>{{$teacher.Surname}} {{$teacher.Name}}
-    	</option>
-    	{{else}}
-    	<option value="{{$teacher.ID}}">{{$teacher.Surname}} {{$teacher.Name}}</option>
-    	{{end}}
-    	{{end}}
-      </select>
-    </div>
+    {{$options := ` { name: "BES",id: "student_bes",label: "BES (L. 170/10)",formClass: "form-group form-check" } `}}
+    {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "BES") "update" $update}}
 
-    
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/students/{{.Data.Student.ID}}?{{query "tpl_layout" "base" "tpl_content" "students_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/students?{{query "tpl_layout" "base" "tpl_content" "students"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
-    
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Student"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}  
   </form>
-
+  
 </div>
 
 {{ end }}

+ 20 - 56
templates/students_show.html.tpl

@@ -2,67 +2,31 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="active"><a href="/students?{{query "tpl_layout" "base" "tpl_content" "students"}}">Studenti</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Surname}} {{.Data.Name}}</a></li>
-    </ol>
-  </nav>
-
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Surname}} {{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-
-	<div class="btn-group float-right" role="group">
-	  <a href="/students/add/?{{query "tpl_layout" "base" "tpl_content" "students_add_update"}}" class="btn btn-success">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Crea
-	  </a>
-
-	  <a href="/students/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "students_add_update" "update" "true"}}"  class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/students/{{.Data.ID}}/delete"
-		  data-url="/students/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-
-      </div>
-    </div>
-  </div>
-
+  {{template "breadcrumb" toSlice "Studenti" (all "Student") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Student") "deletePath" (.Data.ID|delete "Student")}}
+
+  <h2 class="karmen-relation-header">Informazioni generali</h2>
+  {{if .Data.Class}}
+  <p>Lo studente appartiene alla classe {{(.Data.Class.ID|show "Class")|anchor (.Data.Class|string)}}.</p>
+  {{else}}
+  <p>Questo studente non appartiene a nessuna classe. Clicca {{(.Data.ID|update "Student")|anchor "qui"}} per associare una classe allo studente.</p>
+  {{end}}
+  
   <div class="row">
     <div class="col-md-12">
 
-
-      <h2 class="karmen-relation-header">Attività associate all'allievo</h2>
-      <div class="list-group" id="classes_list_group">
-	{{if not .Data.Activities}}
-	<p>All'allievo non è associata alcuna attività
-	  didattica. Clicca <a href="/activities/add?{{query "tpl_content" "activities_add_show" "tpl_layout" "base"}}">qui</a> per creare
-	  una nuova attività didattica da associare all'allievo.</p>
-	{{else}}
-	{{range $activity := .Data.Activities}}
-	<a href="/activities/{{$activity.ID}}?{{query "tpl_layout" "base" "tpl_content" "activities_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-user"></span>
-	  {{if $activity.Teacher}}{{$activity.Teacher.Surname}} {{$activity.Teacher.Name}}{{else}}no docente{{end}}
-	  <div class="text-right">
-	    <small>{{if $activity.Class}}{{$activity.Class.Name}}{{else}}no classe{{end}}  {{if $activity.Subject}}{{$activity.Subject.Name}}{{else}}no materia{{end}} {{$activity.Hours}}h</small>
-	  </div>
-	</a>
-	{{end}}
-	{{end}}
-      </div>
+      {{$options := `
+      title: "Attività associate allo studente"
+      model: "Activity"
+      icon: "fa fa-business-time"
+      `}}
+      
+      {{$noElements := "Nessuna attività associata allo studente."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Activities "noElements" $noElements "small" (toSlice "Teacher")}}
     
+    </div>
   </div>
-
+  
 </div>    
 
 {{ end }}

+ 19 - 35
templates/subjects.html.tpl

@@ -1,49 +1,33 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Materie ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	<div class="btn-group float-right">
-
-	  <a href="/subjects/add/?{{query "tpl_layout" "base" "tpl_content" "subjects_add_update"}}" class="btn btn-primary">
-	    <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	    Crea nuova materia
-	  </a>
-	
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
-  {{if not .Data}}
-  <p>Non c'è alcun elemento da visualizzare.</p>
+  {{$options := `
+  title: "Materie"
+  buttonTitle: "Crea nuova materia"
+  `}}
+  
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "subject")}}
+  {{template "search_input"}}
+    
+  {{if not .}}
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $subject := .Data}}
-    <a class="list-group-item list-group-item-action" href="/subjects/{{$subject.ID}}?{{query "tpl_layout" "base" "tpl_content" "subjects_show"}}">
-      <span class="fa fa-book"></span>
+    <a class="list-group-item list-group-item-action" href={{$subject.ID|show "subject"}}>
+      <span class="fa fa-user"></span>
       {{$subject.Name}}
       <div class="text-right">
-	{{if $subject.Teachers}}
-	{{range $teacher := $subject.Teachers}}
-	<small>{{$teacher.Surname}}</small>
-	{{end}}
-	{{else}}
-	<small>no docente</small>
-	{{end}}
+        {{$options := `noElements: "no docenti"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $subject.Teachers}}
       </div>
     </a>
     {{end}}
+    {{end}}
   </div>
-  {{end}}
+
 </div>
-  
+
 {{ end }}
+

+ 20 - 59
templates/subjects_add_update.html.tpl

@@ -2,73 +2,34 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/subjects">Materie</a></li>
-      <li class="breadcrumb-item"><a href="/subjects/{{.Data.Subject.ID}}">{{.Data.Subject.Name}}</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna materia</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/subjects">Materie</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <h1 class="karmen-info-header">Aggiorna la materia "{{.Data.Subject.Name}}"</h1>
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Materie" (all "Subject") (.Data|string) (.Data.ID|show "Subject") "Aggiorna" "current"}}
   {{else}}
-  <h1 class="karmen-info-header">Crea una nuova materia</h1>
+  {{template "breadcrumb" toSlice "Materie" (all "Subject") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuova materia" "updateTitle" (printf "Aggiorna materia %s" (.Data|string))}}
 
-  {{if .Options.Get "update"}}
-  <form id="form_subjects_add_update" action="/subjects/{{.Data.Subject.ID}}/update" method="POST" role="form" class="needs-validation">
-  {{else}}
-  <form id="form_subjects_add_update" action="/subjects/add/" method="POST" role="form" class="needs-validation">
-  {{end}}  
-    
-    <div class="form-group">
-      <label class="control-label" for="subject_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="subject_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Subject.Name}}" {{end}} required>
-    </div>
+  {{$form := "form_subjects_add_update"}}
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Subject"}}{{else}}{{create "Subject"}}{{end}}"
+    method="POST"
+    role="form"
+    id={{$form}}>
 
-    <div class="form-group">
-      <label class="control-label" for="subject_alias">Nome alternativo</label>
-      <input type="text" name="Alias" class="form-control" id="subject_alias" placeholder="Nome alternativo" {{if .Options.Get "update"}} value="{{.Data.Subject.Alias}}" {{end}}>
-    </div>
+    {{$options := ` { name: "Name",id: "subject_name",label: "Nome",placeholder: "Nome",type: "text",required: "true"} `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label" for="department_id">Dipartimento</label>
-      <select name="department_id" class="form-control selectpicker" id="department_id" placeholder="Dipartimento" data-live-search="true" form="form_subjects_add_update" title="Seleziona il nome del dipartimento" data-dropup-auto="false">
-	<option value="0"></option>
-  	{{range $department := .Data.AllDepartments}}
-	{{if $.Options.Get "update"}}
-  	<option
-	   value="{{$department.ID}}"
-	   {{index $.Data.SelectedDepartment $department.ID}}>{{$department.Name}}
-	</option>
-	{{else}}
-  	<option value="{{$department.ID}}">{{$department.Name}}</option>
-	{{end}}
-  	{{end}}
-      </select>
-    </div>
-	
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/subjects/{{.Data.Subject.ID}}?{{query "tpl_layout" "base" "tpl_content" "subjects_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/subjects?{{query "tpl_layout" "base" "tpl_content" "subjects"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
+    {{$options := ` { name: "department_id", id: "department_id", label: "Dipartimento", title: "Seleziona il dipartimento"}`}}
+    {{template "select" dict "options" ($options|yaml) "data" (.Data|field "AllDepartments") "selected" (.Data|field "Selected") "update" $update "form" $form}}
     
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Subject"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
+  
   </form>
-
   
 </div>
 

+ 22 - 46
templates/subjects_show.html.tpl

@@ -2,55 +2,31 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/subjects?{{query "tpl_layout" "base" "tpl_content" "subjects"}}">Materie</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
-    </ol>
-  </nav>
+  {{template "breadcrumb" toSlice "Materie" (all "Subject") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Subject") "deletePath" (.Data.ID|delete "Subject")}}
+
+  <h2 class="karmen-relation-header">Informazioni generali</h2>
+  {{if .Data.Department}}
+  <p>Questa materia afferisce al dipartimento di {{(.Data.Department.ID|show "Department")|anchor (.Data.Department|string)}}.</p>
+  {{else}}
+  <p>Questa materia non afferisce a nessun dipartimento. Clicca {{(.Data.ID|update "Subject")|anchor "qui"}} per associare un dipartimento alla materia.</p>
+  {{end}}
   
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Name}}</h1>
-      </div>
-      <div class="col-md-4">
-	<div class="btn-group float-right" role="group">
-	  <a href="/subjects/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "subjects_add_update" "update" "true"}}" class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/subjects/{{.Data.ID}}/delete"
-		  data-url="/subjects/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-      </div>
-    </div>
-  </div>
+  <div class="row">
+    <div class="col-md-12">
 
-  <h2 class="karmen-relation-header">Docenti di {{.Data.Name}}</h2>
-  <div class="list-group" id="subjects_list_group">
-    {{if not .Data.Teachers}}
-    <p>Alla materia non è associata alcuna attività
-      didattica. Clicca <a href="/activities/add?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per creare
-      una nuova attività didattica da associare alla materia.</p>
-    {{else}}
-    {{range $activity := .Data.Activities}}
-    <a href="/activities/{{$activity.ID}}?{{query "tpl_layout" "base" "tpl_content" "activities_show"}}" class="list-group-item list-group-item-action">
-      <span class="fa fa-user"></span>
-      {{$activity.Teacher.Surname}} {{$activity.Teacher.Name}}
-      <div class="text-right">
-	<small>{{$activity.Subject.Name}} {{$activity.Hours}}h</small>
-      </div>
-    </a>
-    {{end}}
-    {{end}}
+      {{$options := `
+      title: "Docenti della materia"
+      model: "Teacher"
+      icon: "fa fa-user"
+      `}}
+      
+      {{$noElements := "Nessun docente associato alla materia."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Teachers "noElements" $noElements}}
+    
+    </div>
   </div>
-
-
+  
 </div>    
 
 {{ end }}

+ 15 - 39
templates/teachers.html.tpl

@@ -1,53 +1,28 @@
 {{ define "content" }}
 
-<div class="container">
-  
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>Docenti ({{len .Data}})</h1>
-      </div>
-      <div class="col-md-4">
-	
-	<div class="btn-group float-right">
-	  
-	  <a href="/teachers/add/?{{query "tpl_layout" "base" "tpl_content" "teachers_add_update"}}" class="btn btn-primary">
-	    <span class="fa fa-plus-circle" aria-hidden="true"></span>
-	    Crea nuovo docente
-	  </a>
-	   	
-      </div>
-    </div>
-  </div>
+<div class="container">  
 
-  <div class="input-group" style="margin-bottom: 20px">
-    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
-    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
-  </div>
+  {{$options := `
+  title: "Docenti"
+  buttonTitle: "Crea nuovo docente"
+  `}}
   
+  {{template "read_all_header" dict  "options" ($options | yaml) "lengthData" (len .Data) "modelPath" (create "teacher")}}
+  {{template "search_input"}}
+    
   {{if not .}}
-  <p>Non c'è alcun elemento da visualizzare</p>
+  {{template "display_no_elements"}}
   {{else}}
   <div class="list-group" id="myUL">
     {{range $teacher := .Data}}
-    <a class="list-group-item list-group-item-action" href="/teachers/{{$teacher.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}">
+    <a class="list-group-item list-group-item-action" href={{$teacher.ID|show "teacher"}}>
       <span class="fa fa-user"></span>
       {{$teacher.Surname}} {{$teacher.Name}}
       <div class="text-right">
-	{{if $teacher.Classes}}
-	{{range $class := $teacher.Classes}}
-	<small>{{$class.Name}}</small>
-	{{end}}
-	{{else}}
-	<small>no classi</small>
-	{{end}}
-	{{if $teacher.Subjects}}
-	{{range $subject := $teacher.Subjects}}
-	<small>{{$subject.Name}}</small>
-	{{end}}
-	{{else}}
-	<small>no materie</small>
-        {{end}}
+        {{$options := `noElements: "no classi"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $teacher.Classes}}
+        {{$options := `noElements: "no materie"`}}
+        {{template "small" dict "options" ($options | yaml) "data" $teacher.Subjects}}
       </div>
     </a>
     {{end}}
@@ -55,4 +30,5 @@
   </div>
 
 </div>
+
 {{ end }}

+ 35 - 73
templates/teachers_add_update.html.tpl

@@ -2,103 +2,65 @@
 
 <div class="container">
 
-  {{if .Options.Get "update"}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/teachers?{{query "tpl_layout" "base" "tpl_content" "teachers"}}">Docenti</a></li>
-      <li class="breadcrumb-item"><a href="/teachers/{{.Data.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}">{{.Data.Name}} {{.Data.Surname}}</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiorna</a></li>
-    </ol>
-  </nav>
-  {{else}}
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/teachers/?{{query "tpl_layout" "base" "tpl_content" "teachers"}}">Docenti</a></li>
-      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
-    </ol>
-  </nav>
-  {{end}}
+  {{$update := .Options.Get "update"}}
 
-  {{if .Options.Get "update"}}
-  <h1 class="karmen-info-header">Aggiorna il docente "{{.Data.Name}} {{.Data.Surname}}"</h1>
+  {{if $update}}
+  {{template "breadcrumb" toSlice "Docenti" (all "Teacher") (.Data|string) (.Data.ID|show "Teacher") "Aggiorna" "current"}}
   {{else}}
-  <h1 class="karmen-info-header">Crea un nuovo docente</h1>
+  {{template "breadcrumb" toSlice "Docenti" (all "Teacher") "Aggiungi" "current"}}
   {{end}}
+  
+  {{template "add_update_header" dict "update" $update "addTitle" "Crea nuovo docente" "updateTitle" (printf "Aggiorna docente %s" (.Data|string))}}
 
-  {{if .Options.Get "update"}}
-  <form class="needs-validation" action="/teachers/{{.Data.ID}}/update" method="POST" role="form">
-  {{else}}
-  <form action="/teachers/add/" method="POST" role="form" class="needs-validation">
-  {{end}}  
-    <div class="form-group">
-      <label class="control-label" for="teacher_name">Nome</label>
-      <input type="text" name="Name" class="form-control" id="teacher_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Name}}" {{end}} required>
-    </div>
+  <form
+    class="needs-validation"
+    action="{{if $update}}{{.Data.ID|update "Teacher"}}{{else}}{{create "Teacher"}}{{end}}" method="POST" role="form">
+
+    {{$options := ` { name: "Name",id: "teacher_name",label: "Nome",placeholder: "Nome",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Name") "update" $update}}
     
-    <div class="form-group">
-      <label class="control-label" for="teacher_surname">Cognome</label>
-      <input type="text" name="Surname" class="form-control" id="teacher_surname" placeholder="Cognome" {{if .Options.Get "update"}} value="{{.Data.Surname}}" {{end}} required>
-    </div>
+    {{$options := ` { name: "Surname",id: "teacher_surname",label: "Cognome",placeholder: "Cognome",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Surname") "update" $update}}
 
-    <div class="form-group">
-      <label class="control-label has-feedback" for="teacher_hours">Numero di ore</label>
-      <input type="number" name="Hours" class="form-control" id="teacher_hours" {{if .Options.Get "update"}}value="{{.Data.Hours}}"{{else}}value="18"{{end}} required>
-    </div>
+    {{$options := ` { name: "Hours",id: "teacher_hours",label: "Numero di ore",placeholder: "",type: "number" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "Hours") "update" $update}}
+
+    {{$options := ` { name: "AltEmail",id: "teacher_altemail",label: "Email alternativa",placeholder: "Inserire indirizzo email",type: "email" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "AltEmail") "update" $update}}
 
     <div class="form-row">
-      
-      <div class="col">
-	<input type="email" name="AltEmail" class="form-control" id="teacher_altemail" placeholder="Email alternativa" {{if .Options.Get "update"}} value="{{.Data.AltEmail}}" {{end}}>
-      </div>
       <div class="col">
-	<div class="form-check">
-	  <input type="checkbox" name="Regenerate" class="form-check-input" id="teacher_regenerate" {{if .Options.Get "update"}}{{if .Data.Regenerate}}checked{{end}}{{end}}>
-	  <label class="form-check-label has-feedback" for="teacher_regenerate">Rigenerare/inviare le credenziali</label>
-	</div>
+        {{$options := ` { name: "Regenerate",id: "teacher_regenerate",label: "Rigenerare/inviare le credenziali",formClass: "form-check form-check-inline" } `}}
+        {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "Regenerate") "update" $update}}
       </div>
       <div class="col">
-	<div class="form-check">
-	  <input type="checkbox" name="Exclude" class="form-check-input" id="teacher_exclude" {{if .Options.Get "update"}}{{if .Data.Exclude}}checked{{end}}{{end}}>
-	  <label class="form-check-label has-feedback" for="teacher_exclude">Escludere dalle utenze</label>
-	</div>
+        {{$options := ` { name: "Exclude",id: "teacher_exclude",label: "Escludere dalle utenze",formClass: "form-check form-check-inline" } `}}
+        {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "Exclude") "update" $update}}
       </div>
       <div class="col">
-	<div class="form-check">
-	  <input type="checkbox" name="NotInvited" class="form-check-input" id="teacher_not_invited" {{if .Options.Get "update"}}{{if .Data.NotInvited}}checked{{end}}{{end}}>
-	  <label class="form-check-label has-feedback" for="teacher_not_invited">Escludere dagli inviti</label>
-	</div>
+        {{$options := ` { name: "NotInvited",id: "teacher_not_invited",label: "Escludere dagli inviti",formClass: "form-check form-check-inline" } `}}
+        {{template "checkbox" dict "options" ($options|yaml) "value" (.Data|field "NotInvited") "update" $update}}
       </div>
-
-
     </div>
 
     <div class="form-row">
       <div class="col">
-	<label class="control-label" for="teacher_date_from">In servizio dal</label>
-	<input type="date" name="DateFrom" class="form-control" id="teacher_date_from" {{if .Options.Get "update"}} value="{{convertDate .Data.DateFrom}}" {{end}}>
+        {{$options := ` { name: "DateFrom",id: "teacher_date_from",label: "In servizio dal",type: "date" } `}}
+        {{template "input" dict "options" ($options|yaml) "value" (.Data|field "DateFrom"|convertDate) "update" $update}}
       </div>
       <div class="col">
-	<label class="control-label" for="teacher_date_to">In servizio fino al</label>
-	<input type="date" name="DateTo" class="form-control" id="teacher_date_to" {{if .Options.Get "update"}} value="{{convertDate .Data.DateTo}}" {{end}}>
+        {{$options := ` { name: "DateTo",id: "teacher_date_to",label: "In servizio fino al",type: "date" } `}}
+        {{template "input" dict "options" ($options|yaml) "value" (.Data|field "DateTo"|convertDate) "update" $update}}
       </div>
     </div>
-
-    <div class="form-group">
-      <label class="control-label has-feedback" for="teacher_ldapdnfmt">Stringa DN per LDAP</label>
-      <input type="text" name="LdapDNFmt" class="form-control" id="teacher_ldapdnfmt" placeholder="cn=%s %s,ou=People,dc=foo,dc=org" {{if .Options.Get "update"}}value="{{.Data.LdapDNFmt}}"{{end}}>
-    </div>
-
-    <div class="form-group">
-      <button type="submit" class="btn btn-primary">Salva</button>
-      {{if .Options.Get "update"}}
-      <a href="/teachers/{{.Data.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="btn btn-default">Annulla</a>
-      {{else}}
-      <a href="/teachers/?{{query "tpl_layout" "base" "tpl_content" "teachers"}}" class="btn btn-default">Annulla</a>
-      {{end}}
-    </div>
     
-  </form>
+    {{$options := ` { name: "LdapDNFmt",id: "teacher_ldapfnfmt",label: "Stringa DN per LDAP",placeholder: "cn=%s %s,ou=People,dc=foo,dc=org",type: "text" } `}}
+    {{template "input" dict "options" ($options|yaml) "value" (.Data|field "LdapDNFmt") "update" $update}}
 
+    {{ $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Teacher"} ` }}
+    {{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
+  
+  </form>
   
 </div>
 

+ 75 - 125
templates/teachers_show.html.tpl

@@ -2,53 +2,36 @@
 
 <div class="container">
 
-  <nav aria-label="breadcrumb">
-    <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/teachers?{{query "tpl_layout" "base" "tpl_content" "teachers"}}">Docenti</a></li>
-      <li class="breadcrumb-item active"><a href="#">{{.Data.Name}} {{.Data.Surname}}</a></li>
-    </ol>
-  </nav>
+  {{template "breadcrumb" toSlice "Docenti" (all "Teacher") (.Data|string) "current"}}
+  {{template "show_header" dict "title" (.Data|string) "updatePath" (.Data.ID|update "Teacher") "deletePath" (.Data.ID|delete "Teacher")}}
 
-  <div class="karmen-info-header">
-    <div class="row">
-      <div class="col-md-8">
-	<h1>{{.Data.Name}} {{.Data.Surname}}</h1>
-      </div>
-      <div class="col-md-4">
-	<div class="btn-group float-right" role="group">
-	  <a href="/teachers/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "teachers_add_update" "update" "true"}}" class="btn btn-primary">
-	    <span class="fa fa-edit" aria-hidden="true"></span>
-	    Modifica
-	  </a>
-	  <button href="/teachers/{{.Data.ID}}/delete"
-		  data-url="/teachers/{{.Data.ID}}/delete"
-		  class="btn btn-danger karmen-ajax-delete">
-	    <span class="fa fa-trash" aria-hidden="true"></span>
-	    Elimina
-	  </button>
-	</div>
-      </div>
+  <div class="row">
+    <div class="col-md-12">
+
+      {{$options := `
+      title: "Materie"
+      model: "Subject"
+      icon: "fa fa-book"
+      `}}
+      
+      {{$noElements := "Nessuna materia associata."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Subjects "noElements" $noElements}}
+    
     </div>
   </div>
-
+  
   <div class="row">
     <div class="col-md-12">
+
+      {{$options := `
+      title: "Classi"
+      model: "Class"
+      icon: "fa fa-users"
+      `}}
       
-      <h2 class="karmen-relation-header">Materie</h2>
-      {{if .Data.Subjects}}
-      <div class="list-group" id="materie_list_group">
-    	{{range $subject := .Data.Subjects}}
-    	<a href="/subjects/{{$subject.ID}}?{{query "tpl_layout" "base" "tpl_content" "subjects_show"}}" class="list-group-item list-group-item-action">
-    	  <span class="fa fa-book"></span>
-    	  {{$subject.Name}}
-    	  {{end}}
-    	</a>
-      </div>
-      {{else}}
-      <p>Al docente non è associata alcuna attività
-    	didattica. Clicca <a href="/activities/add/?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per
-    	creare una nuova attività didattica da associare al docente.</p>
-      {{end}}
+      {{$noElements := "Nessuna classe associata."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Classes "noElements" $noElements}}
+
     </div>
     
   </div>
@@ -56,43 +39,31 @@
   <div class="row">
     <div class="col-md-12">
       
-      <h2 class="karmen-relation-header">Classi</h2>
-      {{if .Data.Classes}}
-      <div class="list-group" id="classes_list_group">
-	{{range $class := .Data.Classes}}
-	<a href="/classes/{{$class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-users"></span>
-	  {{$class.Name}}
-	  {{end}}
-	</a>
-      </div>
-      {{else}}
-      <p>Al docente non è associata alcuna attività
-	didattica. Clicca <a href="/activities/add/?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per
-	creare una nuova attività didattica da associare al docente.</p>
-      {{end}}
+      {{$options := `
+      title: "Studenti"
+      model: "Student"
+      icon: "fa fa-user"
+      `}}
+      
+      {{$noElements := (printf "Nessuna attività associata. Clicca %s per creare una nuova attività." (create "Activity"|anchor "qui"))|html}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Students "noElements" $noElements}}
+      
     </div>
     
   </div>
 
   <div class="row">
     <div class="col-md-12">
+
+      {{$options := `
+      title: "Gruppi a cui il docente appartiene"
+      model: "Group"
+      icon: "fa fa-users"
+      `}}
+      
+      {{$noElements := (printf "Nessun gruppo associato. Clicca %s per associare il docente ad un gruppo." (all "Group"|anchor "qui"))|html}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Groups "noElements" $noElements}}
       
-      <h2 class="karmen-relation-header">Studenti</h2>
-      {{if .Data.Students}}
-      <div class="list-group" id="students_list_group">
-	{{range $student := .Data.Students}}
-	<a href="/students/{{$student.ID}}?{{query "tpl_layout" "base" "tpl_content" "students_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-user"></span>
-	  {{$student.Surname}} {{$student.Name}}
-	  {{end}}
-	</a>
-      </div>
-      {{else}}
-      <p>Al docente non è associata alcuna attività
-	didattica. Clicca <a href="/activities/add/?{{query "tpl_layout" "base" "tpl_content" "activities_add_update"}}">qui</a> per
-	creare una nuova attività didattica da associare al docente.</p>
-      {{end}}
     </div>
     
   </div>
@@ -100,70 +71,49 @@
   <div class="row">
     <div class="col-md-12">
       
-      <h2 class="karmen-relation-header">Gruppi a cui il docente appartiene</h2>
-      {{if .Data.Groups}}
-      <div class="list-group" id="groups_list_group">
-    	{{range $group := .Data.Groups}}
-    	<a href="/groups/{{$group.ID}}?{{query "tpl_layout" "base" "tpl_content" "groups_show"}}" class="list-group-item list-group-item-action">
-    	  <span class="fa fa-book"></span>
-    	  {{$group.Name}}
-    	  {{end}}
-    	</a>
-      </div>
-      {{else}}
-      <p>Il docente non è associato ad alcun gruppo. Clicca <a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">qui</a> per creare un nuovo gruppo.</p>{{end}}
+      {{$options := `
+      model: "Activity"
+      icon: "fa fa-business-time"
+      `}}
+
+      {{$noElements := (printf "Nessuna attività associata. Clicca %s per creare una nuova attività." (create "Activity"|anchor "qui"))|html}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.Activities "noElements" $noElements "title" (printf "Attività del docente (%dh)" .Data.ActualHours)}}
+
     </div>
-    
   </div>
 
+  {{if $classes := .Data.CoordinatorClasses}}
   <div class="row">
     <div class="col-md-12">
-   
-      <h2 class="karmen-relation-header">Attività del docente ({{.Data.ActualHours}}h)</h2>
-      {{if .Data.Activities}}
-      <div class="list-group" id="classes_list_group">
-	{{range $activity := .Data.Activities}}
-	<a href="/activities/{{$activity.ID}}?{{query "tpl_layout" "base" "tpl_content" "activities_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-business-time"></span>
-	  {{$activity.Subject.Name}} {{if $activity.Class}}{{$activity.Class.Name}}{{else}}no classe{{end}} {{$activity.Hours}}h{{if $activity.SupplyTeacher}}{{if ne $.Data.Surname $activity.SupplyTeacher.Surname}} (sostituito da {{$activity.SupplyTeacher.Surname}}){{else}} (sostituisce {{$activity.Teacher.Surname}}){{end}}{{end}}
-	  {{end}}
-	</a>
-      </div>
       
-      {{if $classes := .Data.CoordinatorClasses}}
-      <p>Il docente è <strong>coordinatore</strong> nelle seguenti classi</p>
-      <div class="list-group" id="coord_classes_list_group">
-	{{range $class := $classes}}
-	<a href="/classes/{{$class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-users"></span>
-	  {{$class.Name}}
-	  {{end}}
-	</a>
-      </div>
-      {{end}}
-
-      {{if $classes := .Data.MinuterClasses}}
-      <p>Il docente è <strong>verbalizzante</strong> nelle seguenti classi</p>
-      <div class="list-group" id="coord_classes_list_group">
-	{{range $class := $classes}}
-	<a href="/classes/{{$class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes_show"}}" class="list-group-item list-group-item-action">
-	  <span class="fa fa-users"></span>
-	  {{$class.Name}}
-	  {{end}}
-	</a>
-      </div>
-      {{end}}
+      {{$options := `
+      title: "Coordinatore nelle seguenti classi"
+      model: "Class"
+      icon: "fa fa-users"
+      `}}
+  
+      {{$noElements := "Nessuna classe associata."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.CoordinatorClasses "noElements" $noElements}}
+    </div>
+  </div>
+  {{end}}
 
+  {{if $classes := .Data.MinuterClasses}}
+  <div class="row">
+    <div class="col-md-12">
       
-      {{else}}
-      <p>Al docente non è associata alcuna attività
-	didattica. Clicca <a href="/activities/add/?{{query "base" "activities_add_update"}}">qui</a> per
-	creare una nuova attività didattica da associare al docente.</p>
-      {{end}}
+      {{$options := `
+      title: "Verbalizzante nelle seguenti classi"
+      model: "Class"
+      icon: "fa fa-users"
+      `}}
+  
+      {{$noElements := "Nessuna classe associata."}}
+      {{template "relation_list" dict "options" ($options|yaml) "data" .Data.MinuterClasses "noElements" $noElements}}
     </div>
   </div>
-
-
+  {{end}}
+  
 </div>    
 
 {{ end }}

+ 0 - 0
vendor/gogs.carducci-dante.gov.it/karmen/util/fileutil/fileutil.go → util/fileutil/fileutil.go


+ 22 - 0
util/fileutil/fileutil_test.go

@@ -0,0 +1,22 @@
+package fileutil
+
+import (
+	"testing"
+
+	"github.com/remogatto/prettytest"
+)
+
+type testSuite struct {
+	prettytest.Suite
+}
+
+func TestRunner(t *testing.T) {
+	prettytest.Run(
+		t,
+		new(testSuite),
+	)
+}
+
+func (t *testSuite) TestReplaceExt() {
+	t.Equal("foo.odt", ReplaceExt("foo.md", "odt"))
+}

+ 0 - 0
vendor/gogs.carducci-dante.gov.it/karmen/util/libreoffice/libreoffice.go → util/libreoffice/libreoffice.go


+ 35 - 0
util/libreoffice/libreoffice_test.go

@@ -0,0 +1,35 @@
+package libreoffice
+
+import (
+	"os"
+	"testing"
+
+	"github.com/remogatto/prettytest"
+)
+
+type testSuite struct {
+	prettytest.Suite
+}
+
+func TestRunner(t *testing.T) {
+	prettytest.Run(
+		t,
+		new(testSuite),
+	)
+}
+
+func (t *testSuite) TestConvert() {
+	err := Convert("testdata/test.odt", "pdf", "--outdir", "testdata")
+	t.Nil(err)
+	_, err = os.Stat("testdata/test.pdf")
+	t.Nil(err)
+	err = os.Remove("testdata/test.pdf")
+	t.Nil(err)
+
+	err = Convert("testdata/test.csv", "ods", "--outdir", "testdata")
+	t.Nil(err)
+	_, err = os.Stat("testdata/test.ods")
+	t.Nil(err)
+	err = os.Remove("testdata/test.ods")
+	t.Nil(err)
+}

+ 0 - 0
vendor/gogs.carducci-dante.gov.it/karmen/util/pandoc/pandoc.go → util/pandoc/pandoc.go


+ 28 - 0
util/pandoc/pandoc_test.go

@@ -0,0 +1,28 @@
+package pandoc
+
+import (
+	"os"
+	"testing"
+
+	"github.com/remogatto/prettytest"
+)
+
+type testSuite struct {
+	prettytest.Suite
+}
+
+func TestRunner(t *testing.T) {
+	prettytest.Run(
+		t,
+		new(testSuite),
+	)
+}
+
+func (t *testSuite) TestConvert() {
+	err := Convert("testdata/test.md", "testdata/test.odt")
+	t.Nil(err)
+	_, err = os.Stat("testdata/test.odt")
+	t.Nil(err)
+	err = os.Remove("testdata/test.odt")
+	t.Nil(err)
+}

+ 0 - 0
vendor/gogs.carducci-dante.gov.it/karmen/util/template/html.go → util/template/html.go


+ 0 - 0
vendor/gogs.carducci-dante.gov.it/karmen/util/template/template.go → util/template/template.go


+ 46 - 0
util/template/template_test.go

@@ -0,0 +1,46 @@
+package template
+
+import (
+	"strings"
+	"testing"
+	"text/template"
+
+	"github.com/remogatto/prettytest"
+)
+
+var funcMap = template.FuncMap{
+	"toUpper": toUpper,
+}
+
+type testSuite struct {
+	prettytest.Suite
+}
+
+func toUpper(s string) string {
+	return strings.ToUpper(s)
+}
+
+func TestRunner(t *testing.T) {
+	prettytest.Run(
+		t,
+		new(testSuite),
+	)
+}
+
+func (t *testSuite) TestLoadTXTTemplate() {
+	tpl, err := LoadTextTemplate("testdata/test.tpl")
+	t.Nil(err)
+	t.Not(t.Nil(tpl))
+}
+
+func (t *testSuite) TestLoadTXTTemplateWithFuncs() {
+	tpl, err := LoadTextTemplate("testdata/test.tpl", funcMap)
+	t.Nil(err)
+	t.Not(t.Nil(tpl))
+}
+
+func (t *testSuite) TestLoadTemplateFromString() {
+	tpl, err := LoadTextTemplateFromString("{{.}}")
+	t.Nil(err)
+	t.Not(t.Nil(tpl))
+}

+ 0 - 21
vendor/github.com/auth0/go-jwt-middleware/LICENSE

@@ -1,21 +0,0 @@
-The MIT License (MIT)
- 
-Copyright (c) 2015 Auth0, Inc. <support@auth0.com> (http://auth0.com)
- 
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
- 
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
- 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.

+ 0 - 207
vendor/github.com/auth0/go-jwt-middleware/README.md

@@ -1,207 +0,0 @@
-# GO JWT Middleware
-
-A middleware that will check that a [JWT](http://jwt.io/) is sent on the `Authorization` header and will then set the content of the JWT into the `user` variable of the request.
-
-This module lets you authenticate HTTP requests using JWT tokens in your Go Programming Language applications. JWTs are typically used to protect API endpoints, and are often issued using OpenID Connect.
-
-## Key Features
-
-* Ability to **check the `Authorization` header for a JWT**
-* **Decode the JWT** and set the content of it to the request context
-
-## Installing
-
-````bash
-go get github.com/auth0/go-jwt-middleware
-````
-
-## Using it
-
-You can use `jwtmiddleware` with default `net/http` as follows.
-
-````go
-// main.go
-package main
-
-import (
-  "fmt"
-  "net/http"
-
-  "github.com/auth0/go-jwt-middleware"
-  "github.com/dgrijalva/jwt-go"
-  "github.com/gorilla/context"
-)
-
-var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-  user := context.Get(r, "user")
-  fmt.Fprintf(w, "This is an authenticated request")
-  fmt.Fprintf(w, "Claim content:\n")
-  for k, v := range user.(*jwt.Token).Claims {
-    fmt.Fprintf(w, "%s :\t%#v\n", k, v)
-  }
-})
-
-func main() {
-  jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
-    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
-      return []byte("My Secret"), nil
-    },
-    // When set, the middleware verifies that tokens are signed with the specific signing algorithm
-    // If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks
-    // Important to avoid security issues described here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
-    SigningMethod: jwt.SigningMethodHS256,
-  })
-
-  app := jwtMiddleware.Handler(myHandler)
-  http.ListenAndServe("0.0.0.0:3000", app)
-}
-````
-
-You can also use it with Negroni as follows:
-
-````go
-// main.go
-package main
-
-import (
-  "context"
-  "fmt"
-  "net/http"
-
-  "github.com/auth0/go-jwt-middleware"
-  "github.com/codegangsta/negroni"
-  "github.com/dgrijalva/jwt-go"
-  "github.com/gorilla/mux"
-)
-
-var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-  user := r.Context().Value("user");
-  fmt.Fprintf(w, "This is an authenticated request")
-  fmt.Fprintf(w, "Claim content:\n")
-  for k, v := range user.(*jwt.Token).Claims {
-    fmt.Fprintf(w, "%s :\t%#v\n", k, v)
-  }
-})
-
-func main() {
-  r := mux.NewRouter()
-
-  jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{
-    ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
-      return []byte("My Secret"), nil
-    },
-    // When set, the middleware verifies that tokens are signed with the specific signing algorithm
-    // If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks
-    // Important to avoid security issues described here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
-    SigningMethod: jwt.SigningMethodHS256,
-  })
-
-  r.Handle("/ping", negroni.New(
-    negroni.HandlerFunc(jwtMiddleware.HandlerWithNext),
-    negroni.Wrap(myHandler),
-  ))
-  http.Handle("/", r)
-  http.ListenAndServe(":3001", nil)
-}
-````
-
-## Options
-
-````go
-type Options struct {
-  // The function that will return the Key to validate the JWT.
-  // It can be either a shared secret or a public key.
-  // Default value: nil
-  ValidationKeyGetter jwt.Keyfunc
-  // The name of the property in the request where the user information
-  // from the JWT will be stored.
-  // Default value: "user"
-  UserProperty string
-  // The function that will be called when there's an error validating the token
-  // Default value: https://github.com/auth0/go-jwt-middleware/blob/master/jwtmiddleware.go#L35
-  ErrorHandler errorHandler
-  // A boolean indicating if the credentials are required or not
-  // Default value: false
-  CredentialsOptional bool
-  // A function that extracts the token from the request
-  // Default: FromAuthHeader (i.e., from Authorization header as bearer token)
-  Extractor TokenExtractor
-  // Debug flag turns on debugging output
-  // Default: false  
-  Debug bool
-  // When set, all requests with the OPTIONS method will use authentication
-  // Default: false
-  EnableAuthOnOptions bool,
-  // When set, the middelware verifies that tokens are signed with the specific signing algorithm
-  // If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks
-  // Important to avoid security issues described here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
-  // Default: nil
-  SigningMethod jwt.SigningMethod
-}
-````
-
-### Token Extraction
-
-The default value for the `Extractor` option is the `FromAuthHeader`
-function which assumes that the JWT will be provided as a bearer token
-in an `Authorization` header, i.e.,
-
-```
-Authorization: bearer {token}
-```
-
-To extract the token from a query string parameter, you can use the
-`FromParameter` function, e.g.,
-
-```go
-jwtmiddleware.New(jwtmiddleware.Options{
-  Extractor: jwtmiddleware.FromParameter("auth_code"),
-})
-```
-
-In this case, the `FromParameter` function will look for a JWT in the
-`auth_code` query parameter.
-
-Or, if you want to allow both, you can use the `FromFirst` function to
-try and extract the token first in one way and then in one or more
-other ways, e.g.,
-
-```go
-jwtmiddleware.New(jwtmiddleware.Options{
-  Extractor: jwtmiddleware.FromFirst(jwtmiddleware.FromAuthHeader,
-                                     jwtmiddleware.FromParameter("auth_code")),
-})
-```
-
-## Examples
-
-You can check out working examples in the [examples folder](https://github.com/auth0/go-jwt-middleware/tree/master/examples)
-
-
-## What is Auth0?
-
-Auth0 helps you to:
-
-* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, amont others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**.
-* Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**.
-* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user.
-* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely.
-* Analytics of how, when and where users are logging in.
-* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules).
-
-## Create a free Auth0 Account
-
-1. Go to [Auth0](https://auth0.com) and click Sign Up.
-2. Use Google, GitHub or Microsoft Account to login.
-
-## Issue Reporting
-
-If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
-
-## Author
-
-[Auth0](auth0.com)
-
-## License
-
-This project is licensed under the MIT license. See the [LICENSE](LICENSE.txt) file for more info.

+ 0 - 236
vendor/github.com/auth0/go-jwt-middleware/jwtmiddleware.go

@@ -1,236 +0,0 @@
-package jwtmiddleware
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"github.com/dgrijalva/jwt-go"
-	"log"
-	"net/http"
-	"strings"
-)
-
-// A function called whenever an error is encountered
-type errorHandler func(w http.ResponseWriter, r *http.Request, err string)
-
-// TokenExtractor is a function that takes a request as input and returns
-// either a token or an error.  An error should only be returned if an attempt
-// to specify a token was found, but the information was somehow incorrectly
-// formed.  In the case where a token is simply not present, this should not
-// be treated as an error.  An empty string should be returned in that case.
-type TokenExtractor func(r *http.Request) (string, error)
-
-// Options is a struct for specifying configuration options for the middleware.
-type Options struct {
-	// The function that will return the Key to validate the JWT.
-	// It can be either a shared secret or a public key.
-	// Default value: nil
-	ValidationKeyGetter jwt.Keyfunc
-	// The name of the property in the request where the user information
-	// from the JWT will be stored.
-	// Default value: "user"
-	UserProperty string
-	// The function that will be called when there's an error validating the token
-	// Default value:
-	ErrorHandler errorHandler
-	// A boolean indicating if the credentials are required or not
-	// Default value: false
-	CredentialsOptional bool
-	// A function that extracts the token from the request
-	// Default: FromAuthHeader (i.e., from Authorization header as bearer token)
-	Extractor TokenExtractor
-	// Debug flag turns on debugging output
-	// Default: false
-	Debug bool
-	// When set, all requests with the OPTIONS method will use authentication
-	// Default: false
-	EnableAuthOnOptions bool
-	// When set, the middelware verifies that tokens are signed with the specific signing algorithm
-	// If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks
-	// Important to avoid security issues described here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
-	// Default: nil
-	SigningMethod jwt.SigningMethod
-}
-
-type JWTMiddleware struct {
-	Options Options
-}
-
-func OnError(w http.ResponseWriter, r *http.Request, err string) {
-	http.Error(w, err, http.StatusUnauthorized)
-}
-
-// New constructs a new Secure instance with supplied options.
-func New(options ...Options) *JWTMiddleware {
-
-	var opts Options
-	if len(options) == 0 {
-		opts = Options{}
-	} else {
-		opts = options[0]
-	}
-
-	if opts.UserProperty == "" {
-		opts.UserProperty = "user"
-	}
-
-	if opts.ErrorHandler == nil {
-		opts.ErrorHandler = OnError
-	}
-
-	if opts.Extractor == nil {
-		opts.Extractor = FromAuthHeader
-	}
-
-	return &JWTMiddleware{
-		Options: opts,
-	}
-}
-
-func (m *JWTMiddleware) logf(format string, args ...interface{}) {
-	if m.Options.Debug {
-		log.Printf(format, args...)
-	}
-}
-
-// Special implementation for Negroni, but could be used elsewhere.
-func (m *JWTMiddleware) HandlerWithNext(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
-	err := m.CheckJWT(w, r)
-
-	// If there was an error, do not call next.
-	if err == nil && next != nil {
-		next(w, r)
-	}
-}
-
-func (m *JWTMiddleware) Handler(h http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		// Let secure process the request. If it returns an error,
-		// that indicates the request should not continue.
-		err := m.CheckJWT(w, r)
-
-		// If there was an error, do not continue.
-		if err != nil {
-			return
-		}
-
-		h.ServeHTTP(w, r)
-	})
-}
-
-// FromAuthHeader is a "TokenExtractor" that takes a give request and extracts
-// the JWT token from the Authorization header.
-func FromAuthHeader(r *http.Request) (string, error) {
-	authHeader := r.Header.Get("Authorization")
-	if authHeader == "" {
-		return "", nil // No error, just no token
-	}
-
-	// TODO: Make this a bit more robust, parsing-wise
-	authHeaderParts := strings.Split(authHeader, " ")
-	if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
-		return "", errors.New("Authorization header format must be Bearer {token}")
-	}
-
-	return authHeaderParts[1], nil
-}
-
-// FromParameter returns a function that extracts the token from the specified
-// query string parameter
-func FromParameter(param string) TokenExtractor {
-	return func(r *http.Request) (string, error) {
-		return r.URL.Query().Get(param), nil
-	}
-}
-
-// FromFirst returns a function that runs multiple token extractors and takes the
-// first token it finds
-func FromFirst(extractors ...TokenExtractor) TokenExtractor {
-	return func(r *http.Request) (string, error) {
-		for _, ex := range extractors {
-			token, err := ex(r)
-			if err != nil {
-				return "", err
-			}
-			if token != "" {
-				return token, nil
-			}
-		}
-		return "", nil
-	}
-}
-
-func (m *JWTMiddleware) CheckJWT(w http.ResponseWriter, r *http.Request) error {
-	if !m.Options.EnableAuthOnOptions {
-		if r.Method == "OPTIONS" {
-			return nil
-		}
-	}
-
-	// Use the specified token extractor to extract a token from the request
-	token, err := m.Options.Extractor(r)
-
-	// If debugging is turned on, log the outcome
-	if err != nil {
-		m.logf("Error extracting JWT: %v", err)
-	} else {
-		m.logf("Token extracted: %s", token)
-	}
-
-	// If an error occurs, call the error handler and return an error
-	if err != nil {
-		m.Options.ErrorHandler(w, r, err.Error())
-		return fmt.Errorf("Error extracting token: %v", err)
-	}
-
-	// If the token is empty...
-	if token == "" {
-		// Check if it was required
-		if m.Options.CredentialsOptional {
-			m.logf("  No credentials found (CredentialsOptional=true)")
-			// No error, just no token (and that is ok given that CredentialsOptional is true)
-			return nil
-		}
-
-		// If we get here, the required token is missing
-		errorMsg := "Required authorization token not found"
-		m.Options.ErrorHandler(w, r, errorMsg)
-		m.logf("  Error: No credentials found (CredentialsOptional=false)")
-		return fmt.Errorf(errorMsg)
-	}
-
-	// Now parse the token
-	parsedToken, err := jwt.Parse(token, m.Options.ValidationKeyGetter)
-
-	// Check if there was an error in parsing...
-	if err != nil {
-		m.logf("Error parsing token: %v", err)
-		m.Options.ErrorHandler(w, r, err.Error())
-		return fmt.Errorf("Error parsing token: %v", err)
-	}
-
-	if m.Options.SigningMethod != nil && m.Options.SigningMethod.Alg() != parsedToken.Header["alg"] {
-		message := fmt.Sprintf("Expected %s signing method but token specified %s",
-			m.Options.SigningMethod.Alg(),
-			parsedToken.Header["alg"])
-		m.logf("Error validating token algorithm: %s", message)
-		m.Options.ErrorHandler(w, r, errors.New(message).Error())
-		return fmt.Errorf("Error validating token algorithm: %s", message)
-	}
-
-	// Check if the parsed token is valid...
-	if !parsedToken.Valid {
-		m.logf("Token is invalid")
-		m.Options.ErrorHandler(w, r, "The token isn't valid")
-		return errors.New("Token is invalid")
-	}
-
-	m.logf("JWT: %v", parsedToken)
-
-	// If we get here, everything worked and we can set the
-	// user property in context.
-	newRequest := r.WithContext(context.WithValue(r.Context(), m.Options.UserProperty, parsedToken))
-	// Update the current request with the new context information.
-	*r = *newRequest
-	return nil
-}

+ 0 - 4
vendor/github.com/dgrijalva/jwt-go/.gitignore

@@ -1,4 +0,0 @@
-.DS_Store
-bin
-
-

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно