diff --git a/project.clj b/project.clj index e4673d3..72439df 100644 --- a/project.clj +++ b/project.clj @@ -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"]]) diff --git a/resources/transforms/adl2views.xslt b/resources/transforms/adl2views.xslt index 53ace20..a067b55 100755 --- a/resources/transforms/adl2views.xslt +++ b/resources/transforms/adl2views.xslt @@ -1,7 +1,7 @@ (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 diff --git a/src/adl/to_selmer_templates.clj b/src/adl/to_selmer_templates.clj new file mode 100644 index 0000000..332ac33 --- /dev/null +++ b/src/adl/to_selmer_templates.clj @@ -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 " %}%{ 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)) + + diff --git a/src/adl/utils.clj b/src/adl/utils.clj index ed77ca5..5fc77cf 100644 --- a/src/adl/utils.clj +++ b/src/adl/utils.clj @@ -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") diff --git a/src/adl/validator.clj b/src/adl/validator.clj index 13a547f..9513542 100644 --- a/src/adl/validator.clj +++ b/src/adl/validator.clj @@ -652,3 +652,8 @@ entity-validations)]]}) +(defn valid-adl? [src] + (b/valid? src application-validations)) + +(defn validate-adl [src] + (b/validate src application-validations)) diff --git a/yyy.adl.clj b/yyy.adl.clj new file mode 100644 index 0000000..539687b --- /dev/null +++ b/yyy.adl.clj @@ -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"]}]}]}]}