Explorar el Código

Add initial support for administratives and offices

Andrea Fazzi hace 5 años
padre
commit
26d45c467f

+ 1 - 1
cron/sync/sync.go

@@ -94,7 +94,7 @@ func (syncJob *SyncJob) sendMail(teacher *orm.Teacher, tpl *template.Template) e
 	}
 
 	if !syncJob.conf.Sync.SafeRun {
-		log.Printf("Invio le credenziali a %s", teacher.AltEmail)
+		log.Printf("SEND user credential to %s...", teacher.AltEmail)
 		m := gomail.NewMessage()
 		m.SetHeader("From", syncJob.conf.Smtp.From)
 		m.SetHeader("To", teacher.AltEmail)

+ 2 - 0
handlers/handlers.go

@@ -58,6 +58,8 @@ var (
 		"departments",
 		"activities",
 		"students",
+		"offices",
+		"administratives",
 	}
 )
 

+ 161 - 0
orm/administrative.go

@@ -0,0 +1,161 @@
+package orm
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/jinzhu/gorm"
+	"gogs.carducci-dante.gov.it/karmen/core/renderer"
+)
+
+type Administrative struct {
+	gorm.Model
+	Credential
+
+	Office *Office
+
+	OfficeID uint `schema:"office_id"`
+}
+
+type AdministrativeForUpdate struct {
+	Administrative Administrative
+	AllOffices     []*Office
+
+	SelectedOffice map[uint]string
+}
+
+type AdministrativeForAdd struct {
+	AllOffices []*Office
+}
+
+func (s *Administrative) GetID() uint { return s.ID }
+
+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
+}
+
+func GetAdministrativeAll(args map[string]string) (interface{}, error) {
+	var administrative Administrative
+
+	id := args["id"]
+
+	if err := DB().Preload("Office").Where("id = ?", id).Find(&administrative).Error; err != nil {
+		return nil, err
+	}
+
+	return &administrative, nil
+}
+
+func GetAdministratives(args map[string]string) (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 UpdateAdministrative(args map[string]string, r *http.Request) (IDer, error) {
+	administrative, err := GetAdministrative(args)
+	if err != nil {
+		return nil, err
+	}
+
+	err = renderer.Decode(administrative, r)
+	if 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
+}
+
+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
+}
+
+func CreateAdministrative(administrative *Administrative) (*Administrative, error) {
+	if err := DB().Create(administrative).Error; err != nil {
+		return nil, err
+	}
+	return administrative, nil
+}
+
+func DeleteAdministrative(args map[string]string, r *http.Request) (IDer, error) {
+	administrative, err := GetAdministrative(args)
+	if err != nil {
+		return nil, err
+	}
+	if err := DB().Unscoped().Delete(administrative.(*Administrative)).Error; err != nil {
+		return nil, err
+	}
+	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 {
+		return nil, err
+	}
+
+	data.SelectedOffice = make(map[uint]string)
+	data.SelectedOffice[data.Administrative.OfficeID] = "selected"
+
+	return data, nil
+}
+
+func GetAdministrativeForAdd(args map[string]string) (interface{}, error) {
+	var data AdministrativeForAdd
+
+	if err := DB().Find(&data.AllOffices).Error; err != nil {
+		return nil, err
+	}
+
+	return data, nil
+}
+
+func (s *Administrative) CompleteName() string {
+	return fmt.Sprintf("%s %s", s.Name, s.Surname)
+}

+ 11 - 9
orm/credential.go

@@ -13,15 +13,17 @@ import (
 type Checkbox bool
 
 type Credential struct {
-	Name            string `csv:"firstname"`
-	Surname         string `csv:"lastname"`
-	Password        string
-	PlainPassword   string
-	Email           string `csv:"email"`
-	AltEmail        string
-	DateFrom        time.Time
-	DateTo          time.Time
-	TelephoneNumber string
+	Name          string `csv:"firstname"`
+	Surname       string `csv:"lastname"`
+	Password      string
+	PlainPassword string
+	Email         string `csv:"email"`
+	AltEmail      string
+	DateFrom      time.Time
+	DateTo        time.Time
+
+	TelephoneNumber         string
+	InternalTelephoneNumber string
 
 	Regenerate bool `schema:"Regenerate" sql:"default: false"`
 	Exclude    bool

+ 40 - 0
orm/mappings.go

@@ -67,6 +67,28 @@ var (
 		"/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,
 	}
 
 	Post map[string]PostFn = map[string]PostFn{
@@ -125,5 +147,23 @@ var (
 		"/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,
 	}
 )

+ 152 - 0
orm/office.go

@@ -0,0 +1,152 @@
+package orm
+
+import (
+	"net/http"
+
+	"github.com/jinzhu/gorm"
+	"gogs.carducci-dante.gov.it/karmen/core/renderer"
+)
+
+type Office struct {
+	gorm.Model
+
+	Name  string
+	Email string
+
+	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
+	}
+	return &office, nil
+}
+
+func GetOfficeAll(args map[string]string) (interface{}, error) {
+	var office Office
+
+	id := args["id"]
+
+	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) {
+	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
+	}
+	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)
+	if err != nil {
+		return nil, err
+	}
+	office, err = GetOffice(args)
+	if 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 {
+		return nil, err
+	}
+
+	return office, nil
+}
+
+func CreateOffice(office *Office) (*Office, error) {
+	if err := DB().Create(office).Error; err != nil {
+		return nil, err
+	}
+	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
+// }

+ 2 - 0
orm/orm.go

@@ -75,6 +75,8 @@ func AutoMigrate() {
 		&Activity{},
 		&Department{},
 		&Student{},
+		&Office{},
+		&Administrative{},
 	).Error; err != nil {
 		panic(err)
 	}

+ 48 - 0
templates/administratives.html.tpl

@@ -0,0 +1,48 @@
+{{ define "content" }}
+
+<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>
+  
+  {{if not .Data}}
+  <p>Non c'è alcun elemento da visualizzare</p>
+  {{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"}}">
+      <span class="fa fa-user"></span>
+      {{$administrative.Surname}} {{$administrative.Name}}
+      <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}}
+      </div>
+    </a>
+    {{end}}
+  </div>
+  {{end}}
+</div>
+  
+{{ end }}

+ 85 - 0
templates/administratives_add_update.html.tpl

@@ -0,0 +1,85 @@
+{{ define "content" }}
+
+<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}}
+
+ {{if .Options.Get "update"}}
+  <div class="karmen-info-header">
+    <div class="row">
+      <div class="col-md-8">
+	<h1>Aggiorna amministrativo</h1>
+      </div>
+    </div>
+  </div>
+  
+  {{else}}
+  <h1 class="karmen-info-header">Crea nuovo amministrativo</h1>
+  {{end}}
+
+  {{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}}
+
+    <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>
+
+    <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>
+
+    <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}} required>
+    </div>
+
+    <div class="form-group">
+      <label class="control-label" for="office_id">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" required>
+    	<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">
+      <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>
+
+</div>
+
+{{ end }}

+ 63 - 0
templates/administratives_show.html.tpl

@@ -0,0 +1,63 @@
+{{ define "content" }}
+
+<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}}
+
+    </div>
+  </div>
+
+</div>    
+
+{{ end }}

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

@@ -36,6 +36,14 @@
 	   <li class="nav-item">
 	     <a class="nav-item nav-link" href="/activities?{{query "tpl_layout" "base" "tpl_content" "activities"}}">Attività</a>
 	   </li>
+           <li class="nav-item">
+	     <a class="nav-item nav-link" href="/offices?{{query "tpl_layout" "base" "tpl_content" "offices"}}">Uffici</a>
+	   </li>
+           <li class="nav-item">
+	     <a class="nav-item nav-link" href="/administratives?{{query "tpl_layout" "base" "tpl_content" "administratives"}}">Amministrativi</a>
+	   </li>
+
+
 	  </ul>
 
 	  <ul class="nav navbar-nav navbar-right">

+ 49 - 0
templates/offices.html.tpl

@@ -0,0 +1,49 @@
+{{ 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="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 $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>
+      {{$office.Name}}
+
+      <div class="text-right">
+	
+	{{if $office.Administratives}}
+	{{range $administrative := $office.Administratives}}
+	<small>{{$administrative.Surname}}</small>
+	{{end}}
+	{{end}}
+        
+      </div>
+
+    </a>
+    {{end}}
+  </div>
+  {{end}}
+</div>
+  
+{{ end }}

+ 64 - 0
templates/offices_add_update.html.tpl

@@ -0,0 +1,64 @@
+{{ 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}}
+
+  {{if .Options.Get "update"}}
+  <form id="form_offices_add_update" action="/offices/{{.Data.Office.ID}}/update" method="POST" role="form" class="needs-validation">
+  {{else}}
+  <form id="form_offices_add_update" action="/offices/add/" method="POST" role="form" class="needs-validation">
+  {{end}}
+
+    <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>
+
+    <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>
+
+
+    <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>
+    
+  </form>
+
+</div>
+
+{{ end }}

+ 71 - 0
templates/offices_show.html.tpl

@@ -0,0 +1,71 @@
+{{ define "content" }}
+
+<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>
+
+  <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>
+    
+    <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>    
+
+    {{ end }}