Really substantial progress in generating Selmer templates.

This commit is contained in:
Simon Brooke 2018-05-11 18:56:58 +01:00
parent 4d6bad7c2a
commit fd9e65b7c7
7 changed files with 1015 additions and 25 deletions

View file

@ -5,4 +5,5 @@
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/math.combinatorics "0.1.4"]
[bouncer "1.0.1"]])
[bouncer "1.0.1"]
[hiccup "1.0.5"]])

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns="http://libs.cygnets.co.uk/adl/1.4/"
xmlns:adl="http://libs.cygnets.co.uk/adl/1.4/"
xmlns="http://bowyer.journeyman.cc/adl/1.4/"
xmlns:adl="http://bowyer.journeyman.cc/adl/1.4/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:exsl="urn:schemas-microsoft-com:xslt"

View file

@ -6,7 +6,7 @@
[clojure.string :as s]
[clj-time.core :as t]
[clj-time.format :as f]
[adl.utils :refer [singularise is-link-table?]]))
[adl.utils :refer [has-non-key-properties? has-primary-key? is-link-table? key-names singularise]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@ -31,23 +31,6 @@
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn key-names [entity-map]
(remove
nil?
(map
#(:name (:attrs %))
(vals (:content (:key (:content entity-map)))))))
(defn has-primary-key? [entity-map]
(> (count (key-names entity-map)) 0))
(defn has-non-key-properties? [entity-map]
(>
(count (vals (:properties (:content entity-map))))
(count (key-names entity-map))))
(defn where-clause [entity-map]
(let

View file

@ -0,0 +1,307 @@
(ns ^{:doc "Application Description Language: generate RING routes for REST requests."
:author "Simon Brooke"}
adl.to-selmer-templates
(:require [adl.utils :refer :all]
[clojure.java.io :refer [file]]
[clojure.math.combinatorics :refer [combinations]]
[clojure.string :as s]
[clojure.xml :as x]
[clj-time.core :as t]
[clj-time.format :as f]
[hiccup.core :as h]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
;;;; adl.to-json-routes: generate RING routes for REST requests.
;;;;
;;;; This program is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU General Public License
;;;; as published by the Free Software Foundation; either version 2
;;;; of the License, or (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program; if not, write to the Free Software
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
;;;; USA.
;;;;
;;;; Copyright (C) 2018 Simon Brooke
;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:dynamic *locale* "en-GB")
(defn file-header []
"{% extends \"templates/base.html\" %}\n{% block content %}")
(defn file-footer []
"{% endblock %}")
(defn prompt
"Return an appropriate prompt for the given `field-or-property` taken from this
`form` of this `entity` of this `application`, in the context of the current
binding of `*locale*`. TODO: something more sophisticated about i18n"
[field-or-property form entity application]
(or
(first
(children
field-or-property
#(and
(= (:tag %) :prompt)
(= (:locale :attrs %) *locale*))))
(:name (:attrs field-or-property))))
(defn permission
[property form entity application]
(or
(children property #(= (:tag %) :permission))
(children entity :permission)))
(defn visible?
"Return `true` if this property is not `system`-distinct, and is readable
to the `public` group; else return a list of groups to which it is readable,
given these `permissions`."
[property permissions]
(let [attributes (attributes property)]
(if
(not
(and
;; if it's immutable and system distinct, the user should not need to see it.
(= (:immutable attributes) "true")
(= (:distinct attributes) "system")))
(map
#(if
(some #{"read" "insert" "noedit" "edit" "all"} (:permission (:attrs %)))
(:group (:attrs %)))
permissions))))
(defn csrf-widget
"For the present, just return the standard cross site scripting protection field statement"
[]
"{% csrf-field %}")
(defn save-widget
"Return an appropriate 'save' widget for this `form` operating on this `entity` taken
from this `application`."
[form entity application]
{:tag :p
:attrs {:class "widget action-safe"}
:content [{:tag :label
:attrs {:for "save-button" :class "action-safe"}
:content [(str "To save this " (:name (:attrs entity)) " record")]}
{:tag :input
:attrs {:id "save-button"
:name "save-button"
:class "action-safe"
:type :submit
:value (str "Save!")}}]})
(defn delete-widget
"Return an appropriate 'save' widget for this `form` operating on this `entity` taken
from this `application`."
[form entity application]
{:tag :p
:attrs {:class "widget action-dangerous"}
:content [{:tag :label
:attrs {:for "delete-button" :class "action-dangerous"}
:content [(str "To delete this " (:name (:attrs entity)) " record")]}
{:tag :input
:attrs {:id "delete-button"
:name "delete-button"
:class "action-dangerous"
:type :submit
:value (str "Delete!")}}]})
(defn get-options
"Produce template code to get options for this `property` of this `entity` taken from
this `application`."
[property form entity application]
(let
[type (:type (:attrs property))
farname (:entity (:attrs property))
farside (application farname)
farkey (or
(:farkey (:attrs property))
(:name (:attrs (first (children (children farside #(= (:tag %) :key))))))
"id")]
(str "{% for record in " farname " %}<option value='record." farkey "'>"
(s/join
" "
(map
#(str "{{record." (:name (:attrs %)) "}}")
(children farside #(some #{"user" "all"} (:distinct %))))))
"</option>%{ endfor %}"))
(defn widget
"Generate a widget for this `field-or-property` of this `form` for this `entity`
taken from within this `application`."
[field-or-property form entity application]
(let
[name (:name (:attrs field-or-property))
property (if
(= (:tag field-or-property) :property)
field-or-property
(first
(children
entity
#(and
(= (:tag %) :property)
(= (:name (:attrs %)) (:property (:attrs field-or-property)))))))
permissions (permission property form entity application)
show? (visible? property permissions)
is-select? false]
;; TODO: deal with disabling/hiding if no permission
(if
show?
{:tag :p
:attrs {:class "widget"}
:content [{:tag :label
:attrs {:for name}
:content [(prompt field-or-property form entity application)]}
(if
is-select?
{:tag :select
:attrs {:id name
:name name}
:content (get-options property form entity application)}
{:tag :input
:attrs {:id name
:name name
:type :text ;; TODO - or other things
:value (str "{{record." name "}}")}})]}
{:tag :input
:attrs {:id name
:name name
:type :hidden
:value (str "{{record." name "}}")}})))
(defn form-to-template
"Generate a template as specified by this `form` element for this `entity`,
taken from this `application`. If `form` is nill, generate a default form
template for the entity."
[form entity application]
(let
[name (str (if form (:name (:attrs form)) "edit") "-" (:name (:attrs entity)))
keyfields (children
;; there should only be one key; its keys are properties
(first (children entity #(= (:tag %) :key))))
fields (if
(and form (= "listed" (:properties (:attrs form))))
;; if we've got a form, collect its fields, fieldgroups and verbs
(flatten
(map #(if (some #{:field :fieldgroup :verb} (:tag %)) %)
(children form)))
(children entity #(= (:key %) :property)))]
{:tag :div
:attrs {:id "content" :class "edit"}
:content
[{:tag :form
:attrs {:action (str "{{servlet-context}}/" name)
:method "POST"}
:content (flatten
(list
(csrf-widget)
(map
#(widget % form entity application)
(flatten
(list keyfields fields)))
(save-widget form entity application)
(delete-widget form entity application)))}]}))
(defn page-to-template
"Generate a template as specified by this `page` element for this `entity`,
taken from this `application`. If `page` is nill, generate a default page
template for the entity."
[page entity application]
)
(defn list-to-template
"Generate a template as specified by this `list` element for this `entity`,
taken from this `application`. If `list` is nill, generate a default list
template for the entity."
[list entity application]
)
(defn entity-to-templates
"Generate one or more templates for editing instances of this
`entity` in this `application`"
[entity application]
(let
[forms (children entity #(= (:tag %) :form))
pages (children entity #(= (:tag %) :page))
lists (children entity #(= (:tag %) :list))]
(if
(and
(= (:tag entity) :entity) ;; it seems to be an ADL entity
(not (link-table? entity)))
(merge
(if
forms
(apply merge (map #(assoc {} (keyword (str "form-" (:name (:attrs entity)) "-" (:name (:attrs %))))
(form-to-template % entity application))
forms))
{(keyword (str "form-" (:name (:attrs entity))))
(form-to-template nil entity application)})
(if
pages
(apply merge (map #(assoc {} (keyword (str "page-" (:name (:attrs entity)) "-" (:name (:attrs %))))
(page-to-template % entity application))
pages))
{(keyword (str "page-" (:name (:attrs entity))))
(page-to-template nil entity application)})
(if
lists
(apply merge (map #(assoc {} (keyword (str "list-" (:name (:attrs entity)) "-" (:name (:attrs %))))
(list-to-template % entity application))
lists))
{(keyword (str "list-" (:name (:attrs entity))))
(form-to-template nil entity application)})))))
(defn write-template-file
[filename template]
(spit
filename
(s/join
"\n"
(list
(file-header)
(with-out-str (x/emit-element template))
(file-footer)))))
(defn to-selmer-templates
[application]
(let
[templates-map (reduce
merge
{}
(map
#(entity-to-templates % application)
(children application #(= (:tag %) :entity))))]
(doall
(map
#(if
(templates-map %)
(write-template-file (str (name %) ".html") (templates-map %)))
(keys templates-map)))
templates-map))

View file

@ -1,11 +1,75 @@
(ns adl.utils
(:require [clojure.string :as s]))
(:require [clojure.string :as s]
[clojure.xml :as x]
[adl.validator :refer [valid-adl? validate-adl]]))
(defn children
"Return the children of this `element`; if `predicate` is passed, return only those
children satisfying the predicate."
([element]
(if
(keyword? (:tag element)) ;; it has a tag; it seems to be an XML element
(:content element)))
([element predicate]
(remove ;; there's a more idionatic way of doing remove-nil-map, but for the moment I can't recall it.
nil?
(map
#(if (predicate %) %)
(children element)))))
(defn singularise [string]
(s/replace (s/replace (s/replace string #"_" "-") #"s$" "") #"ie$" "y"))
(defn is-link-table?
[entity-map]
(let [properties (-> entity-map :content :properties vals)
(defn link-table?
"Return true if this `entity` represents a link table."
[entity]
(let [properties (children entity #(= (:tag %) :property))
links (filter #(-> % :attrs :entity) properties)]
(= (count properties) (count links))))
(defn read-adl [url]
(let [adl (x/parse url)
valid? (valid-adl? adl)]
adl))
;; (if valid? adl
;; (throw (Exception. (str (validate-adl adl)))))))
(defn key-names [entity-map]
(remove
nil?
(map
#(:name (:attrs %))
(vals (:content (:key (:content entity-map)))))))
(defn has-primary-key? [entity-map]
(> (count (key-names entity-map)) 0))
(defn has-non-key-properties? [entity-map]
(>
(count (vals (:properties (:content entity-map))))
(count (key-names entity-map))))
(defn attributes
"Return the attributes of this `element`; if `predicate` is passed, return only those
attributes satisfying the predicate."
([element]
(if
(keyword? (:tag element)) ;; it has a tag; it seems to be an XML element
(:attrs element)))
([element predicate]
(remove ;; there's a more idionatic way of doing remove-nil-map, but for the moment I can't recall it.
nil?
(map
#(if (predicate %) %)
(:attrs element)))))
;; (read-adl "../youyesyet/stripped.adl.xml")

View file

@ -652,3 +652,8 @@
entity-validations)]]})
(defn valid-adl? [src]
(b/valid? src application-validations))
(defn validate-adl [src]
(b/validate src application-validations))

630
yyy.adl.clj Normal file
View file

@ -0,0 +1,630 @@
{:tag :application,
:attrs {:version "0.1.1", :name "youyesyet"},
:content
[{:tag :entity,
:attrs {:name "electors"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:column "name", :name "name", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "name"},
:content ["\nname\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "addresses",
:column "address_id",
:name "address_id",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "address_id"},
:content ["\naddress_id\n"]}]}
{:tag :property,
:attrs {:column "phone", :name "phone", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "phone"},
:content ["\nphone\n"]}]}
{:tag :property,
:attrs {:column "email", :name "email", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "email"},
:content ["\nemail\n"]}]}]}
{:tag :entity,
:attrs {:name "addresses"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:column "address",
:name "address",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "address"},
:content ["\naddress\n"]}]}
{:tag :property,
:attrs {:column "postcode", :name "postcode", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "postcode"},
:content ["\npostcode\n"]}]}
{:tag :property,
:attrs {:column "phone", :name "phone", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "phone"},
:content ["\nphone\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "districts",
:column "district_id",
:name "district_id",
:type "entity"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "district_id"},
:content ["\ndistrict_id\n"]}]}
{:tag :property,
:attrs {:column "latitude", :name "latitude", :type "real"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "latitude"},
:content ["\nlatitude\n"]}]}
{:tag :property,
:attrs {:column "longitude", :name "longitude", :type "real"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "longitude"},
:content ["\nlongitude\n"]}]}]}
{:tag :entity,
:attrs {:name "visits"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "addresses",
:column "address_id",
:name "address_id",
:type "integer",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "address_id"},
:content ["\naddress_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "canvassers",
:column "canvasser_id",
:name "canvasser_id",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "canvasser_id"},
:content ["\ncanvasser_id\n"]}]}
{:tag :property,
:attrs
{:column "date",
:name "date",
:type "timestamp",
:default "",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "date"},
:content ["\ndate\n"]}]}]}
{:tag :entity,
:attrs {:name "authorities"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}]}
{:tag :entity,
:attrs {:name "issues"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs {:column "url", :name "url", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "url"},
:content ["\nurl\n"]}]}]}
{:tag :entity,
:attrs {:name "schema_migrations"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}]}
{:tag :entity,
:attrs {:name "intentions"},
:content
[{:tag :property,
:attrs
{:column "visit_id",
:name "visit_id",
:farkey "id",
:entity "visits",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "visit_id"},
:content ["\nvisit_id\n"]}]}
{:tag :property,
:attrs
{:column "elector_id",
:name "elector_id",
:farkey "id",
:entity "electors",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "elector_id"},
:content ["\nelector_id\n"]}]}
{:tag :property,
:attrs
{:column "option_id",
:name "option_id",
:farkey "id",
:entity "options",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "option_id"},
:content ["\noption_id\n"]}]}]}
{:tag :entity,
:attrs {:name "canvassers"},
:content
[{:tag :property,
:attrs {:column "id", :name "id", :type "integer"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:column "username",
:name "username",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "username"},
:content ["\nusername\n"]}]}
{:tag :property,
:attrs
{:column "fullname",
:name "fullname",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "fullname"},
:content ["\nfullname\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "electors",
:column "elector_id",
:name "elector_id",
:type "entity"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "elector_id"},
:content ["\nelector_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "addresses",
:column "address_id",
:name "address_id",
:type "integer",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "address_id"},
:content ["\naddress_id\n"]}]}
{:tag :property,
:attrs {:column "phone", :name "phone", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "phone"},
:content ["\nphone\n"]}]}
{:tag :property,
:attrs {:column "email", :name "email", :type "string"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "email"},
:content ["\nemail\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "authorities",
:column "authority_id",
:name "authority_id",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "authority_id"},
:content ["\nauthority_id\n"]}]}
{:tag :property,
:attrs
{:column "authorised", :name "authorised", :type "boolean"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "authorised"},
:content ["\nauthorised\n"]}]}]}
{:tag :entity,
:attrs {:name "followuprequests"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "electors",
:column "elector_id",
:name "elector_id",
:type "integer",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "elector_id"},
:content ["\nelector_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "visits",
:column "visit_id",
:name "visit_id",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "visit_id"},
:content ["\nvisit_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "issues",
:column "issue_id",
:name "issue_id",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "issue_id"},
:content ["\nissue_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "followupmethods",
:column "method_id",
:name "method_id",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "method_id"},
:content ["\nmethod_id\n"]}]}]}
{:tag :entity,
:attrs {:name "rolememberships"},
:content
[{:tag :property,
:attrs
{:column "role_id",
:name "role_id",
:farkey "id",
:entity "roles",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "role_id"},
:content ["\nrole_id\n"]}]}
{:tag :property,
:attrs
{:column "canvasser_id",
:name "canvasser_id",
:farkey "id",
:entity "canvassers",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "canvasser_id"},
:content ["\ncanvasser_id\n"]}]}]}
{:tag :entity,
:attrs {:name "roles"},
:content
[{:tag :property,
:attrs {:column "id", :name "id", :type "integer"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:column "name", :name "name", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "name"},
:content ["\nname\n"]}]}]}
{:tag :entity,
:attrs {:name "teams"},
:content
[{:tag :property,
:attrs {:column "id", :name "id", :type "integer"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:column "name", :name "name", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "name"},
:content ["\nname\n"]}]}
{:tag :property,
:attrs
{:column "district_id",
:name "district_id",
:farkey "id",
:entity "districts",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "district_id"},
:content ["\ndistrict_id\n"]}]}
{:tag :property,
:attrs {:column "latitude", :name "latitude", :type "real"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "latitude"},
:content ["\nlatitude\n"]}]}
{:tag :property,
:attrs {:column "longitude", :name "longitude", :type "real"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "longitude"},
:content ["\nlongitude\n"]}]}]}
{:tag :entity,
:attrs {:name "districts"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:column "name", :name "name", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "name"},
:content ["\nname\n"]}]}]}
{:tag :entity,
:attrs {:name "teamorganiserships"},
:content
[{:tag :property,
:attrs
{:column "team_id",
:name "team_id",
:farkey "id",
:entity "teams",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "team_id"},
:content ["\nteam_id\n"]}]}
{:tag :property,
:attrs
{:column "canvasser_id",
:name "canvasser_id",
:farkey "id",
:entity "canvassers",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "canvasser_id"},
:content ["\ncanvasser_id\n"]}]}]}
{:tag :entity,
:attrs {:name "followupactions"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "integer", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "followuprequests",
:column "request_id",
:name "request_id",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "request_id"},
:content ["\nrequest_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "canvassers",
:column "actor",
:name "actor",
:type "integer",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "actor"},
:content ["\nactor\n"]}]}
{:tag :property,
:attrs
{:column "date",
:name "date",
:type "timestamp",
:default "",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "date"},
:content ["\ndate\n"]}]}
{:tag :property,
:attrs {:column "notes", :name "notes", :type "text"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "notes"},
:content ["\nnotes\n"]}]}
{:tag :property,
:attrs {:column "closed", :name "closed", :type "boolean"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "closed"},
:content ["\nclosed\n"]}]}]}
{:tag :entity,
:attrs {:name "issueexpertise"},
:content
[{:tag :property,
:attrs
{:farkey "id",
:entity "canvassers",
:column "canvasser_id",
:name "canvasser_id",
:type "integer",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "canvasser_id"},
:content ["\ncanvasser_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "issues",
:column "issue_id",
:name "issue_id",
:type "string",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "issue_id"},
:content ["\nissue_id\n"]}]}
{:tag :property,
:attrs
{:farkey "id",
:entity "followupmethods",
:column "method_id",
:name "method_id",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "method_id"},
:content ["\nmethod_id\n"]}]}]}
{:tag :entity,
:attrs {:name "options"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}]}
{:tag :entity,
:attrs {:name "teammemberships"},
:content
[{:tag :property,
:attrs
{:column "team_id",
:name "team_id",
:farkey "id",
:entity "teams",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "team_id"},
:content ["\nteam_id\n"]}]}
{:tag :property,
:attrs
{:column "canvasser_id",
:name "canvasser_id",
:farkey "id",
:entity "canvassers",
:type "entity",
:required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "canvasser_id"},
:content ["\ncanvasser_id\n"]}]}]}
{:tag :entity,
:attrs {:name "followupmethods"},
:content
[{:tag :property,
:attrs
{:column "id", :name "id", :type "string", :required "true"},
:content
[{:tag :prompt,
:attrs {:locale "en-GB", :prompt "id"},
:content ["\nid\n"]}]}]}]}