Tactical commit before moving to Fletcher

I *think* I'm on the right lines with this, but illuminator is too slow
and the REPL server is timing out...
This commit is contained in:
Simon Brooke 2018-07-21 16:47:52 +01:00
parent d53c633527
commit 5ad384745f
10 changed files with 1034 additions and 939 deletions

View file

@ -54,7 +54,7 @@
:main ^:skip-aot youyesyet.core :main ^:skip-aot youyesyet.core
:migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")} :migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")}
:plugins [[lein-adl ["0.1.1"]] :plugins [;;[lein-adl ["0.1.1"]]
[lein-cljsbuild "1.1.4"] [lein-cljsbuild "1.1.4"]
[lein-codox "0.10.3"] [lein-codox "0.10.3"]
[lein-cprop "1.0.1"] [lein-cprop "1.0.1"]

View file

@ -5,7 +5,7 @@
-- --
-- auto-generated by [Application Description Language framework] -- auto-generated by [Application Description Language framework]
-- --
-- (https://github.com/simon-brooke/adl) at 20180721T111020.637Z -- (https://github.com/simon-brooke/adl) at 20180721T133846.002Z
-- --
-- A web-app intended to be used by canvassers -- A web-app intended to be used by canvassers
-- campaigning for a 'Yes' vote in the second independence -- campaigning for a 'Yes' vote in the second independence
@ -72,7 +72,7 @@ CREATE TABLE addresses
( (
id SERIAL NOT NULL PRIMARY KEY, id SERIAL NOT NULL PRIMARY KEY,
address VARCHAR(256) NOT NULL, address VARCHAR(256) NOT NULL,
postcode VARCHAR(16) CONSTRAINT pattern_7470 CHECK (postcode ~* '^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$'), postcode VARCHAR(16) CONSTRAINT pattern_1 CHECK (postcode ~* '^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$'),
phone VARCHAR(16), phone VARCHAR(16),
district_id INTEGER, district_id INTEGER,
latitude DOUBLE PRECISION, latitude DOUBLE PRECISION,
@ -985,31 +985,33 @@ ALTER TABLE ln_roles_canvassers_roles ADD CONSTRAINT ri_ln_roles_canvassers_role
ON DELETE NO ACTION ; ON DELETE NO ACTION ;
------------------------------------------------------------------------ ------------------------------------------------------------------------
-- link table joining roles with canvassers -- link table joining events with teams
------------------------------------------------------------------------ ------------------------------------------------------------------------
CREATE TABLE ln_members_roles_canvassers CREATE TABLE ln_teams_events_teams
( (
role_id INTEGER, event_id INTEGER,
canvasser_id INTEGER team_id INTEGER
); );
GRANT SELECT ON ln_members_roles_canvassers TO admin, GRANT SELECT ON ln_teams_events_teams TO admin,
analysts, analysts,
canvassers, canvassers,
issueeditors, issueeditors,
issueexperts, issueexperts,
teamorganisers ; teamorganisers ;
GRANT INSERT ON ln_members_roles_canvassers TO admin ; GRANT INSERT ON ln_teams_events_teams TO admin,
GRANT UPDATE ON ln_members_roles_canvassers TO admin ; teamorganisers ;
GRANT DELETE ON ln_members_roles_canvassers TO admin ; GRANT UPDATE ON ln_teams_events_teams TO admin,
teamorganisers ;
GRANT DELETE ON ln_teams_events_teams TO admin ;
ALTER TABLE ln_members_roles_canvassers ADD CONSTRAINT ri_ln_members_roles_canvassers_canvassers_canvasser_id ALTER TABLE ln_teams_events_teams ADD CONSTRAINT ri_ln_teams_events_teams_events_event_id
FOREIGN KEY( canvasser_id ) FOREIGN KEY( event_id )
REFERENCES canvassers(id) REFERENCES events(id)
ON DELETE NO ACTION ; ON DELETE NO ACTION ;
ALTER TABLE ln_members_roles_canvassers ADD CONSTRAINT ri_ln_members_roles_canvassers_roles_role_id ALTER TABLE ln_teams_events_teams ADD CONSTRAINT ri_ln_teams_events_teams_teams_team_id
FOREIGN KEY( role_id ) FOREIGN KEY( team_id )
REFERENCES roles(id) REFERENCES teams(id)
ON DELETE NO ACTION ; ON DELETE NO ACTION ;
------------------------------------------------------------------------ ------------------------------------------------------------------------

View file

@ -63,6 +63,9 @@
<!-- content: put your main page content into this block --> <!-- content: put your main page content into this block -->
{% endblock %} {% endblock %}
</div> </div>
<p>
User: {{user}}
</p>
<br clear="both"/> <br clear="both"/>
</div> </div>
{% block foot %} {% block foot %}

View file

@ -39,13 +39,18 @@
(declare ^:dynamic *app-context*) (declare ^:dynamic *app-context*)
(def ^:dynamic *user* nil)
(parser/set-resource-path! (clojure.java.io/resource "templates")) (parser/set-resource-path! (clojure.java.io/resource "templates"))
(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))) (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
(filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)])) (filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
(tags/add-tags) (tags/add-tags)
(defn raw-get-user-roles [user] ;; role assignments change only rarely.
"Return, as a set, the names of the roles of which this user is a member." (def get-user-roles
"Return, as a set, the names of the roles of which this `user` is a member."
(memoize
(fn [user]
(if (if
user user
(do (do
@ -53,14 +58,10 @@
(let [roles (let [roles
(set (map #(lower-case (:name %)) (db/list-roles-by-canvasser db/*db* user)))] (set (map #(lower-case (:name %)) (db/list-roles-by-canvasser db/*db* user)))]
(log/debug (str "found roles " roles " for user " user)) (log/debug (str "found roles " roles " for user " user))
roles)))) roles))))))
;; role assignments change only rarely. (defn render-with-session
(def get-user-roles (memoize raw-get-user-roles))
(defn render
"renders the HTML `template` located relative to resources/templates in "renders the HTML `template` located relative to resources/templates in
the context of this session and with these parameters." the context of this session and with these parameters."
;; TODO: I'm passing `session` through into render. The default luminus ;; TODO: I'm passing `session` through into render. The default luminus
@ -68,7 +69,7 @@
;; than me so there's almost certainly a reason it doesn't. ;; than me so there's almost certainly a reason it doesn't.
[template session & [params]] [template session & [params]]
(let [user (:user session)] (let [user (:user session)]
(log/debug (str "layout/render: template: '" template "'")) (log/debug (str "layout/render-with-session: template: '" template "'"))
(content-type (content-type
(ok (ok
(parser/render-file (parser/render-file
@ -83,6 +84,26 @@
"text/html; charset=utf-8"))) "text/html; charset=utf-8")))
(defn render
"renders the HTML `template` located relative to resources/templates in
the context of this session and with these parameters."
[template & [params]]
(log/debug (str "layout/render: template: '" template "'"))
(content-type
(ok
(parser/render-file
template
(merge params
{:page template
:csrf-token *anti-forgery-token*
:user *user*
:user-roles (get-user-roles *user*)
:site-title (:site-title env)
:version (System/getProperty "youyesyet.version")})))
"text/html; charset=utf-8"))
(defn error-page (defn error-page
"error-details should be a map containing the following keys: "error-details should be a map containing the following keys:
:status - error status :status - error status

View file

@ -8,7 +8,7 @@
[ring-ttl-session.core :refer [ttl-memory-store]] [ring-ttl-session.core :refer [ttl-memory-store]]
[youyesyet.env :refer [defaults]] [youyesyet.env :refer [defaults]]
[youyesyet.config :refer [env]] [youyesyet.config :refer [env]]
[youyesyet.layout :refer [*app-context* error-page]]) [youyesyet.layout :refer [*app-context* *user* error-page]])
(:import [javax.servlet ServletContext])) (:import [javax.servlet ServletContext]))
@ -63,6 +63,17 @@
((if (:websocket? request) handler wrapped) request)))) ((if (:websocket? request) handler wrapped) request))))
(defn wrap-user
"Dynamically bind *user* to the user in the session, if any, so that it
is available in layout/render, q.v."
[handler]
(fn [request]
(if-let [user (-> request :session :user)]
(binding [*user* user]
(handler request))
(handler request))))
(defn wrap-base [handler] (defn wrap-base [handler]
(-> ((:middleware defaults) handler) (-> ((:middleware defaults) handler)
wrap-webjars wrap-webjars
@ -71,4 +82,6 @@
(assoc-in [:security :anti-forgery] false) (assoc-in [:security :anti-forgery] false)
(assoc-in [:session :store] (ttl-memory-store (* 60 30))))) (assoc-in [:session :store] (ttl-memory-store (* 60 30)))))
wrap-context wrap-context
wrap-internal-error)) wrap-internal-error
wrap-user))

View file

@ -49,7 +49,7 @@
(defn about-page [] (defn about-page []
(layout/render "about.html" {} {:title (layout/render "about.html" {:title
(str "About " (:site-title env)) (str "About " (:site-title env))
:motd (md-to-html-string (slurp (io/resource "about.md")))})) :motd (md-to-html-string (slurp (io/resource "about.md")))}))
@ -67,7 +67,7 @@
(defn home-page [] (defn home-page []
(layout/render "home.html" {} {:title "You yes yet?" (layout/render "home.html" {:title "You yes yet?"
:motd (md-to-html-string (motd))})) :motd (md-to-html-string (motd))}))
@ -100,13 +100,12 @@
(assoc (assoc
(response/found redirect-to) (response/found redirect-to)
:session :session
(assoc session :user user :roles roles))) (assoc session :user (assoc user :roles roles))))
;; if we've got a username but either no user object or else ;; if we've got a username but either no user object or else
;; the password doesn't match ;; the password doesn't match
username username
(layout/render (layout/render
"login.html" "login.html"
session
{:title (str "User " username " is unknown") {:title (str "User " username " is unknown")
:redirect-to redirect-to :redirect-to redirect-to
:warnings ["Your user name was not recognised or your password did not match"]}) :warnings ["Your user name was not recognised or your password did not match"]})
@ -114,7 +113,6 @@
true true
(layout/render (layout/render
"login.html" "login.html"
session
{:title "Please log in" {:title "Please log in"
:redirect-to redirect-to :redirect-to redirect-to
:authorities (db-core/list-authorities db-core/*db*)})))) :authorities (db-core/list-authorities db-core/*db*)}))))

View file

@ -42,7 +42,7 @@
(defn list-page [request] (defn list-page [request]
(layout/render (layout/render
"issue-expert/list.html" "issue-expert/list.html"
(:session request) ;; (:session request)
(let [user (:user (:session request))] (let [user (:user (:session request))]
{:title "Open requests" {:title "Open requests"
:user user :user user
@ -69,7 +69,7 @@
db/*db* {:id (:visit_id record)})))] db/*db* {:id (:visit_id record)})))]
(layout/render (layout/render
"issue-expert/request.html" "issue-expert/request.html"
(:session request) ;; (:session request)
{:title (str "Request from " (:name elector) " at " (:date visit)) {:title (str "Request from " (:name elector) " at " (:date visit))
:user (:user (:session request)) :user (:user (:session request))
:visit visit :visit visit

View file

@ -42,7 +42,6 @@
(defn app-page [request] (defn app-page [request]
(layout/render "app.html" (layout/render "app.html"
(:session request)
{:title "Canvasser app"})) {:title "Canvasser app"}))
@ -51,7 +50,7 @@
(let [record (-> request :session :user)] (let [record (-> request :session :user)]
(layout/render (layout/render
"auto/form-canvassers-Canvasser.html" "auto/form-canvassers-Canvasser.html"
(:session request) ;; (:session request)
{:title (str "Profile for " (-> request :session :user :fullname)) {:title (str "Profile for " (-> request :session :user :fullname))
:record record :record record
:elector_id :elector_id

View file

@ -19,14 +19,14 @@
"Render the routing page for the roles the currently logged in user is member of." "Render the routing page for the roles the currently logged in user is member of."
(let (let
[session (:session request) [session (:session request)
user (:user session) user (-> request :session :user)
roles (if roles (if
user user
(db-core/list-roles-by-canvasser db-core/*db* {:id (:id user)}))] (db-core/list-roles-by-canvasser db-core/*db* {:id (:id user)}))]
(log/info (str "Roles routing page; user is " user "; roles are " roles)) (log/info (str "Roles routing page; user is " user "; roles are " roles))
(cond (cond
roles (layout/render "roles.html" roles (layout/render "roles.html"
(:session request) ;; ;; (:session request)
{:title (str "Welcome " (:fullname user) ", what do you want to do?") {:title (str "Welcome " (:fullname user) ", what do you want to do?")
:user user :user user
:roles (map #(assoc % :link (safe-name (:name %) :sql)) roles)}) :roles (map #(assoc % :link (safe-name (:name %) :sql)) roles)})
@ -38,7 +38,7 @@
[request] [request]
(layout/render (layout/render
(support/resolve-template "application-index.html") (support/resolve-template "application-index.html")
(:session request) ;; (:session request)
{:title "Administrative menu"})) {:title "Administrative menu"}))
@ -49,18 +49,24 @@
[request] [request]
(layout/render (layout/render
(support/resolve-template "application-index.html") (support/resolve-template "application-index.html")
(:session request) ;; (:session request)
{:title "Administrative menu"})) {:title "Administrative menu"}))
(defn canvassers-page (defn canvassers-page
[request] [request]
(layout/render "roles/canvasser.html" (:session request) {})) (layout/render
"roles/canvasser.html"
;; (:session request)
{}))
(defn team-organisers-page (defn team-organisers-page
[request] [request]
(layout/render "roles/team-orgenisers.html" request {})) (layout/render
"roles/team-orgenisers.html"
;; request
{}))
(defroutes roles-routes (defroutes roles-routes

View file

@ -14,8 +14,7 @@
* Generated using adl2canonical.xslt 1.10 $ * Generated using adl2canonical.xslt 1.10 $
* *
*************************************************************************** ***************************************************************************
--> --><documentation>A web-app intended to be used by canvassers
<documentation>A web-app intended to be used by canvassers
campaigning for a 'Yes' vote in the second independence campaigning for a 'Yes' vote in the second independence
referendum. The web-app will be delivered to canvassers out referendum. The web-app will be delivered to canvassers out
knocking doors primarily through an HTML5/React single-page app knocking doors primarily through an HTML5/React single-page app
@ -26,7 +25,9 @@
set the system up and authorise canvassers, and a 'followup' set the system up and authorise canvassers, and a 'followup'
interface through which issue-expert specialist canvassers can interface through which issue-expert specialist canvassers can
address particular electors' queries.</documentation> address particular electors' queries.</documentation>
<typedef name="postcode" type="string" pattern="^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$" size="16"> <typedef name="postcode" type="string"
pattern="^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$"
size="16">
<documentation>See <documentation>See
https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/488478/Bulk_Data_Transfer_-_additional_validation_valid_from_12_November_2015.pdf, https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/488478/Bulk_Data_Transfer_-_additional_validation_valid_from_12_November_2015.pdf,
section 3</documentation> section 3</documentation>
@ -72,20 +73,23 @@
</group> </group>
<!-- <!--
entity electors already has a key - not generating one entity electors already has a key - not generating one
--> --><entity table="electors" name="electors" magnitude="6" volatility="5">
<entity table="electors" name="electors" magnitude="6" volatility="5">
<documentation>All electors known to the system; electors are <documentation>All electors known to the system; electors are
people believed to be entitled to vote in the current people believed to be entitled to vote in the current
campaign.</documentation> campaign.</documentation>
<key> <key>
<property required="true" type="integer" name="id" column="id" immutable="true" distinct="system"> <property required="true" type="integer" name="id" column="id" immutable="true"
distinct="system">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property size="64" required="true" type="string" name="name" column="name" distinct="user"> <property size="64" required="true" type="string" name="name" column="name"
distinct="user">
<prompt prompt="Name" locale="en_GB.UTF-8"/> <prompt prompt="Name" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="dwelling_id" column="dwelling_id" entity="dwellings" farkey="id"> <property required="true" type="entity" name="dwelling_id" column="dwelling_id"
entity="dwellings"
farkey="id">
<prompt prompt="Home" locale="en_GB.UTF-8"/> <prompt prompt="Home" locale="en_GB.UTF-8"/>
</property> </property>
<property size="16" type="string" name="phone" column="phone"> <property size="16" type="string" name="phone" column="phone">
@ -94,7 +98,8 @@
<property size="128" type="string" name="email" column="email"> <property size="128" type="string" name="email" column="email">
<prompt prompt="Email" locale="en_GB.UTF-8"/> <prompt prompt="Email" locale="en_GB.UTF-8"/>
</property> </property>
<property name="gender" type="entity" column="gender" entity="genders" farkey="id" default="Unknown"> <property name="gender" type="entity" column="gender" entity="genders" farkey="id"
default="Unknown">
<prompt prompt="Gender" locale="en_GB.UTF-8"/> <prompt prompt="Gender" locale="en_GB.UTF-8"/>
</property> </property>
<property name="signature" type="text"> <property name="signature" type="text">
@ -139,8 +144,7 @@
</entity> </entity>
<!-- <!--
entity genders already has a key - not generating one entity genders already has a key - not generating one
--> --><entity table="genders" name="genders" magnitude="1" volatility="6">
<entity table="genders" name="genders" magnitude="1" volatility="6">
<documentation>All genders which may be assigned to <documentation>All genders which may be assigned to
electors.</documentation> electors.</documentation>
<key> <key>
@ -167,8 +171,7 @@
</entity> </entity>
<!-- <!--
entity dwellings already has a key - not generating one entity dwellings already has a key - not generating one
--> --><entity table="dwellings" name="dwellings" magnitude="6" volatility="6">
<entity table="dwellings" name="dwellings" magnitude="6" volatility="6">
<documentation>All dwellings within addresses in the system; a <documentation>All dwellings within addresses in the system; a
dwelling is a house, flat or appartment in which electors live. dwelling is a house, flat or appartment in which electors live.
Every address should have at least one dwelling; essentially, Every address should have at least one dwelling; essentially,
@ -176,11 +179,15 @@
what's behind that door. So a tenement or a block of flats what's behind that door. So a tenement or a block of flats
would be one address with many dwellings.</documentation> would be one address with many dwellings.</documentation>
<key> <key>
<property required="true" type="integer" name="id" column="id" immutable="true" distinct="system"> <property required="true" type="integer" name="id" column="id" immutable="true"
distinct="system">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="entity" name="address_id" column="address_id" entity="addresses" farkey="id" distinct="user"> <property required="true" type="entity" name="address_id" column="address_id"
entity="addresses"
farkey="id"
distinct="user">
<prompt prompt="Building Address" locale="en_GB.UTF-8"/> <prompt prompt="Building Address" locale="en_GB.UTF-8"/>
</property> </property>
<property required="false" type="string" size="32" name="sub-address" distinct="user"> <property required="false" type="string" size="32" name="sub-address" distinct="user">
@ -215,25 +222,28 @@
</entity> </entity>
<!-- <!--
entity addresses already has a key - not generating one entity addresses already has a key - not generating one
--> --><entity table="addresses" name="addresses" magnitude="6" volatility="6">
<entity table="addresses" name="addresses" magnitude="6" volatility="6">
<documentation>Addresses of all buildings which contain <documentation>Addresses of all buildings which contain
dwellings.</documentation> dwellings.</documentation>
<key> <key>
<property required="true" type="integer" name="id" column="id" immutable="true" distinct="system"> <property required="true" type="integer" name="id" column="id" immutable="true"
distinct="system">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="string" name="address" column="address" size="256" distinct="user"> <property required="true" type="string" name="address" column="address" size="256"
distinct="user">
<prompt prompt="Address" locale="en_GB.UTF-8"/> <prompt prompt="Address" locale="en_GB.UTF-8"/>
</property> </property>
<property type="defined" typedef="postcode" name="postcode" column="postcode" size="16" distinct="user"> <property type="defined" typedef="postcode" name="postcode" column="postcode" size="16"
distinct="user">
<prompt prompt="Postcode" locale="en_GB.UTF-8"/> <prompt prompt="Postcode" locale="en_GB.UTF-8"/>
</property> </property>
<property type="string" name="phone" column="phone" size="16"> <property type="string" name="phone" column="phone" size="16">
<prompt prompt="Phone" locale="en_GB.UTF-8"/> <prompt prompt="Phone" locale="en_GB.UTF-8"/>
</property> </property>
<property type="entity" name="district_id" column="district_id" entity="districts" farkey="id"> <property type="entity" name="district_id" column="district_id" entity="districts"
farkey="id">
<prompt prompt="District" locale="en_GB.UTF-8"/> <prompt prompt="District" locale="en_GB.UTF-8"/>
</property> </property>
<property type="real" name="latitude" column="latitude"> <property type="real" name="latitude" column="latitude">
@ -301,8 +311,7 @@
</entity> </entity>
<!-- <!--
entity visits already has a key - not generating one entity visits already has a key - not generating one
--> --><entity table="visits" name="visits" magnitude="7" volatility="2">
<entity table="visits" name="visits" magnitude="7" volatility="2">
<documentation>All visits made by canvassers to dwellings in <documentation>All visits made by canvassers to dwellings in
which opinions were recorded.</documentation> which opinions were recorded.</documentation>
<key> <key>
@ -311,13 +320,19 @@
</property> </property>
</key> </key>
<!-- TODO: should visit be keyed against dwelling rather than address? --> <!-- TODO: should visit be keyed against dwelling rather than address? -->
<property required="true" type="entity" name="address_id" column="address_id" entity="addresses" farkey="id" distinct="user"> <property required="true" type="entity" name="address_id" column="address_id"
entity="addresses"
farkey="id"
distinct="user">
<prompt prompt="Address" locale="en_GB.UTF-8"/> <prompt prompt="Address" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="canvasser_id" column="canvasser_id" entity="canvassers" farkey="id"> <property required="true" type="entity" name="canvasser_id" column="canvasser_id"
entity="canvassers"
farkey="id">
<prompt prompt="Canvasser" locale="en_GB.UTF-8"/> <prompt prompt="Canvasser" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="timestamp" name="date" column="date" default="now()" distinct="user"> <property required="true" type="timestamp" name="date" column="date" default="now()"
distinct="user">
<prompt prompt="Date" locale="en_GB.UTF-8"/> <prompt prompt="Date" locale="en_GB.UTF-8"/>
</property> </property>
<permission group="canvassers" permission="noedit"> <permission group="canvassers" permission="noedit">
@ -364,12 +379,12 @@
</entity> </entity>
<!-- <!--
entity authorities already has a key - not generating one entity authorities already has a key - not generating one
--> --><entity table="authorities" name="authorities" magnitude="2" volatility="7">
<entity table="authorities" name="authorities" magnitude="2" volatility="7">
<documentation>Authorities which may authenticate canvassers to <documentation>Authorities which may authenticate canvassers to
the system.</documentation> the system.</documentation>
<key> <key>
<property required="true" type="string" name="id" column="id" size="32" distinct="all" immutable="true"> <property required="true" type="string" name="id" column="id" size="32" distinct="all"
immutable="true">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
@ -408,12 +423,12 @@
</entity> </entity>
<!-- <!--
entity issues already has a key - not generating one entity issues already has a key - not generating one
--> --><entity table="issues" name="issues" magnitude="1" volatility="3">
<entity table="issues" name="issues" magnitude="1" volatility="3">
<documentation>Issues believed to be of interest to electors, <documentation>Issues believed to be of interest to electors,
about which they may have questions.</documentation> about which they may have questions.</documentation>
<key> <key>
<property required="true" type="string" name="id" column="id" size="32" immutable="true" distinct="all"> <property required="true" type="string" name="id" column="id" size="32" immutable="true"
distinct="all">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
@ -464,8 +479,7 @@
</entity> </entity>
<!-- <!--
entity intentions already has a key - not generating one entity intentions already has a key - not generating one
--> --><entity table="intentions" name="intentions" magnitude="6" volatility="2">
<entity table="intentions" name="intentions" magnitude="6" volatility="2">
<documentation>Intentions of electors to vote for options <documentation>Intentions of electors to vote for options
elicited in visits.</documentation> elicited in visits.</documentation>
<key> <key>
@ -473,13 +487,16 @@
<generator action="native"/> <generator action="native"/>
</property> </property>
</key> </key>
<property required="true" type="entity" entity="visits" farkey="id" name="visit_id" column="visit_id"> <property required="true" type="entity" entity="visits" farkey="id" name="visit_id"
column="visit_id">
<prompt prompt="visit_id" locale="en_GB.UTF-8"/> <prompt prompt="visit_id" locale="en_GB.UTF-8"/>
</property> </property>
<property type="entity" entity="electors" farkey="id" name="elector_id" column="elector_id"> <property type="entity" entity="electors" farkey="id" name="elector_id"
column="elector_id">
<prompt prompt="elector_id" locale="en_GB.UTF-8"/> <prompt prompt="elector_id" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" entity="options" farkey="id" name="option_id" column="option_id"> <property required="true" type="entity" entity="options" farkey="id" name="option_id"
column="option_id">
<prompt prompt="option_id" locale="en_GB.UTF-8"/> <prompt prompt="option_id" locale="en_GB.UTF-8"/>
</property> </property>
<property name="locality" type="integer" required="true"> <property name="locality" type="integer" required="true">
@ -532,8 +549,7 @@
</entity> </entity>
<!-- <!--
entity canvassers already has a key - not generating one entity canvassers already has a key - not generating one
--> --><entity table="canvassers" name="canvassers" magnitude="5" volatility="4">
<entity table="canvassers" name="canvassers" magnitude="5" volatility="4">
<documentation>Primary users of the system: those actually <documentation>Primary users of the system: those actually
interviewing electors.</documentation> interviewing electors.</documentation>
<key> <key>
@ -541,10 +557,12 @@
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="string" name="username" column="username" size="32" distinct="all"> <property required="true" type="string" name="username" column="username" size="32"
distinct="all">
<prompt prompt="username" locale="en_GB.UTF-8"/> <prompt prompt="username" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="string" name="fullname" column="fullname" size="64" distinct="user"> <property required="true" type="string" name="fullname" column="fullname" size="64"
distinct="user">
<prompt prompt="fullname" locale="en_GB.UTF-8"/> <prompt prompt="fullname" locale="en_GB.UTF-8"/>
</property> </property>
<property name="avatar" type="image"> <property name="avatar" type="image">
@ -556,10 +574,14 @@
<documentation>Information the canvasser supplies about themselves; an introduction.</documentation> <documentation>Information the canvasser supplies about themselves; an introduction.</documentation>
<prompt prompt="Bio" locale="en_GB.UTF-8"/> <prompt prompt="Bio" locale="en_GB.UTF-8"/>
</property> </property>
<property type="entity" name="elector_id" column="elector_id" entity="electors" farkey="id"> <property type="entity" name="elector_id" column="elector_id" entity="electors"
farkey="id">
<prompt prompt="Electoral roll entry" locale="en_GB.UTF-8"/> <prompt prompt="Electoral roll entry" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="address_id" column="address_id" entity="addresses" farkey="id" distinct="user"> <property required="true" type="entity" name="address_id" column="address_id"
entity="addresses"
farkey="id"
distinct="user">
<prompt prompt="address" locale="en_GB.UTF-8"/> <prompt prompt="address" locale="en_GB.UTF-8"/>
</property> </property>
<property type="string" name="phone" column="phone" size="16" distinct="user"> <property type="string" name="phone" column="phone" size="16" distinct="user">
@ -568,7 +590,8 @@
<property type="string" name="email" column="email" size="128" distinct="user"> <property type="string" name="email" column="email" size="128" distinct="user">
<prompt prompt="email" locale="en_GB.UTF-8"/> <prompt prompt="email" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="authority_id" entity="authorities" farkey="id"> <property required="true" type="entity" name="authority_id" entity="authorities"
farkey="id">
<prompt prompt="authority_id" locale="en_GB.UTF-8"/> <prompt prompt="authority_id" locale="en_GB.UTF-8"/>
</property> </property>
<property type="boolean" name="authorised" column="authorised"> <property type="boolean" name="authorised" column="authorised">
@ -651,25 +674,34 @@
</entity> </entity>
<!-- <!--
entity followuprequests already has a key - not generating one entity followuprequests already has a key - not generating one
--> --><entity table="followuprequests" name="followuprequests" magnitude="7" volatility="2">
<entity table="followuprequests" name="followuprequests" magnitude="7" volatility="2">
<documentation>Requests for a followup with an issue <documentation>Requests for a followup with an issue
expert</documentation> expert</documentation>
<key> <key>
<property required="true" type="integer" name="id" column="id" distinct="system" immutable="true"> <property required="true" type="integer" name="id" column="id" distinct="system"
immutable="true">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="entity" name="elector_id" column="elector_id" entity="electors" farkey="id" distinct="user"> <property required="true" type="entity" name="elector_id" column="elector_id"
entity="electors"
farkey="id"
distinct="user">
<prompt prompt="elector_id" locale="en_GB.UTF-8"/> <prompt prompt="elector_id" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="visit_id" column="visit_id" entity="visits" farkey="id" distinct="user"> <property required="true" type="entity" name="visit_id" column="visit_id" entity="visits"
farkey="id"
distinct="user">
<prompt prompt="visit_id" locale="en_GB.UTF-8"/> <prompt prompt="visit_id" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="issue_id" column="issue_id" entity="issues" farkey="id" distinct="user"> <property required="true" type="entity" name="issue_id" column="issue_id" entity="issues"
farkey="id"
distinct="user">
<prompt prompt="issue_id" locale="en_GB.UTF-8"/> <prompt prompt="issue_id" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="method_id" column="method_id" entity="followupmethods" farkey="id"> <property required="true" type="entity" name="method_id" column="method_id"
entity="followupmethods"
farkey="id">
<prompt prompt="method_id" locale="en_GB.UTF-8"/> <prompt prompt="method_id" locale="en_GB.UTF-8"/>
</property> </property>
<list properties="listed" name="Followuprequests"> <list properties="listed" name="Followuprequests">
@ -712,8 +744,7 @@
</entity> </entity>
<!-- <!--
entity roles already has a key - not generating one entity roles already has a key - not generating one
--> --><entity table="roles" name="roles" magnitude="1" volatility="7">
<entity table="roles" name="roles" magnitude="1" volatility="7">
<documentation>A role (essentially, the same as a group, but <documentation>A role (essentially, the same as a group, but
application layer rather than database layer) of which a user application layer rather than database layer) of which a user
may be a member.</documentation> may be a member.</documentation>
@ -722,7 +753,9 @@
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="string" name="name" column="name" size="64" distinct="all" immutable="true"> <property required="true" type="string" name="name" column="name" size="64"
distinct="all"
immutable="true">
<prompt prompt="name" locale="en_GB.UTF-8"/> <prompt prompt="name" locale="en_GB.UTF-8"/>
</property> </property>
<property type="link" entity="canvassers" name="members"> <property type="link" entity="canvassers" name="members">
@ -753,17 +786,18 @@
</entity> </entity>
<!-- <!--
entity teams already has a key - not generating one entity teams already has a key - not generating one
--> --><entity table="teams" name="teams" magnitude="4" volatility="4">
<entity table="teams" name="teams" magnitude="4" volatility="4">
<key> <key>
<property type="integer" name="id" column="id" distinct="system" immutable="true"> <property type="integer" name="id" column="id" distinct="system" immutable="true">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="string" name="name" column="name" size="64" distinct="user"> <property required="true" type="string" name="name" column="name" size="64"
distinct="user">
<prompt prompt="name" locale="en_GB.UTF-8"/> <prompt prompt="name" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" entity="districts" farkey="id" name="district_id" column="district_id"> <property required="true" type="entity" entity="districts" farkey="id" name="district_id"
column="district_id">
<prompt prompt="district_id" locale="en_GB.UTF-8"/> <prompt prompt="district_id" locale="en_GB.UTF-8"/>
</property> </property>
<property type="real" name="latitude" column="latitude"> <property type="real" name="latitude" column="latitude">
@ -859,8 +893,7 @@
</entity> </entity>
<!-- <!--
entity events already has a key - not generating one entity events already has a key - not generating one
--> --><entity volatility="0" name="events" table="events" magnitude="5">
<entity volatility="0" name="events" table="events" magnitude="5">
<documentation> <documentation>
An event to which a team or teams are invited. Typically created by the team organiser(s). An event to which a team or teams are invited. Typically created by the team organiser(s).
May be a training event, a social event or a canvassing session. May be a training event, a social event or a canvassing session.
@ -870,7 +903,8 @@
<generator action="native"/> <generator action="native"/>
</property> </property>
</key> </key>
<property required="true" type="string" name="name" column="name" size="64" distinct="user"> <property required="true" type="string" name="name" column="name" size="64"
distinct="user">
<prompt prompt="name" locale="en_GB.UTF-8"/> <prompt prompt="name" locale="en_GB.UTF-8"/>
</property> </property>
<property name="teams" type="link" entity="teams"/> <property name="teams" type="link" entity="teams"/>
@ -885,9 +919,23 @@
<permission permission="read" group="analysts"/> <permission permission="read" group="analysts"/>
<permission permission="read" group="issueeditors"/> <permission permission="read" group="issueeditors"/>
<permission permission="all" group="admin"/> <permission permission="all" group="admin"/>
<form properties="listed" name="Events"><field property="id"><generator action="native"/></field><field property="name"><prompt prompt="name" locale="en_GB.UTF-8"/></field><field property="teams"/><field property="date"/><field property="time"/><field property="decription"/><field property="cancelled"/></form> <form properties="listed" name="Events">
<field property="id">
<generator action="native"/>
</field>
<field property="name">
<prompt prompt="name" locale="en_GB.UTF-8"/>
</field>
<field property="teams"/>
<field property="date"/>
<field property="time"/>
<field property="decription"/>
<field property="cancelled"/>
</form>
<list properties="listed" name="Events"> <list properties="listed" name="Events">
<field property="name"><prompt prompt="name" locale="en_GB.UTF-8"/></field> <field property="name">
<prompt prompt="name" locale="en_GB.UTF-8"/>
</field>
<field property="date"/> <field property="date"/>
<field property="time"/> <field property="time"/>
<field property="cancelled"/> <field property="cancelled"/>
@ -895,17 +943,18 @@
</entity> </entity>
<!-- <!--
entity districts already has a key - not generating one entity districts already has a key - not generating one
--> --><entity table="districts" name="districts" magnitude="4" volatility="7">
<entity table="districts" name="districts" magnitude="4" volatility="7">
<documentation>Electoral districts: TODO: Shape (polygon) <documentation>Electoral districts: TODO: Shape (polygon)
information will need to be added, for use in information will need to be added, for use in
maps.</documentation> maps.</documentation>
<key> <key>
<property required="true" type="integer" name="id" column="id" immutable="true" distinct="system"> <property required="true" type="integer" name="id" column="id" immutable="true"
distinct="system">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="string" name="name" column="name" size="64" distinct="user"> <property required="true" type="string" name="name" column="name" size="64"
distinct="user">
<prompt prompt="name" locale="en_GB.UTF-8"/> <prompt prompt="name" locale="en_GB.UTF-8"/>
</property> </property>
<permission group="public" permission="read"/> <permission group="public" permission="read"/>
@ -929,22 +978,26 @@
</entity> </entity>
<!-- <!--
entity followupactions already has a key - not generating one entity followupactions already has a key - not generating one
--> --><entity table="followupactions" name="followupactions" magnitude="7" volatility="0">
<entity table="followupactions" name="followupactions" magnitude="7" volatility="0">
<documentation>Actions taken on followup <documentation>Actions taken on followup
requests.</documentation> requests.</documentation>
<key> <key>
<property required="true" type="integer" name="id" column="id" distinct="system" immutable="true"> <property required="true" type="integer" name="id" column="id" distinct="system"
immutable="true">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
<property required="true" type="entity" name="request_id" column="request_id" entity="followuprequests" farkey="id"> <property required="true" type="entity" name="request_id" column="request_id"
entity="followuprequests"
farkey="id">
<prompt prompt="request_id" locale="en_GB.UTF-8"/> <prompt prompt="request_id" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" type="entity" name="actor" column="actor" entity="canvassers" farkey="id"> <property required="true" type="entity" name="actor" column="actor" entity="canvassers"
farkey="id">
<prompt prompt="actor" locale="en_GB.UTF-8"/> <prompt prompt="actor" locale="en_GB.UTF-8"/>
</property> </property>
<property required="true" default="now()" type="timestamp" name="date" column="date" distinct="user"> <property required="true" default="now()" type="timestamp" name="date" column="date"
distinct="user">
<prompt prompt="date" locale="en_GB.UTF-8"/> <prompt prompt="date" locale="en_GB.UTF-8"/>
</property> </property>
<property type="text" name="notes" column="notes" distinct="user"> <property type="text" name="notes" column="notes" distinct="user">
@ -999,12 +1052,12 @@
</entity> </entity>
<!-- <!--
entity options already has a key - not generating one entity options already has a key - not generating one
--> --><entity table="options" name="options" magnitude="1" volatility="7">
<entity table="options" name="options" magnitude="1" volatility="7">
<documentation>Options in the election or referendum being <documentation>Options in the election or referendum being
canvassed on</documentation> canvassed on</documentation>
<key> <key>
<property required="true" type="string" name="id" column="id" size="32" distinct="all" immutable="true"> <property required="true" type="string" name="id" column="id" size="32" distinct="all"
immutable="true">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>
@ -1027,11 +1080,11 @@
</entity> </entity>
<!-- <!--
entity followupmethods already has a key - not generating one entity followupmethods already has a key - not generating one
--> --><entity table="followupmethods" name="followupmethods" magnitude="1" volatility="7">
<entity table="followupmethods" name="followupmethods" magnitude="1" volatility="7">
<documentation>Methods which may be used to follow up a followup request. Reference data.</documentation> <documentation>Methods which may be used to follow up a followup request. Reference data.</documentation>
<key> <key>
<property required="true" type="string" size="32" name="id" column="id" immutable="true" distinct="all"> <property required="true" type="string" size="32" name="id" column="id" immutable="true"
distinct="all">
<prompt prompt="id" locale="en_GB.UTF-8"/> <prompt prompt="id" locale="en_GB.UTF-8"/>
</property> </property>
</key> </key>