liquid_feedback_frontend
annotate model/member.lua @ 19:00d1004545f1
Dynamic interface using XMLHttpRequests, and many other changes
Bugfixes:
- Only allow voting on admitted initiatives
- Repaired issue search
- Don't display delegations for closed issues on member page
- Don't show revoke link in initiative, when issue is already half_frozen
- Localization for voting JavaScript
- Display author of suggestions
Disclosure of voting data after voting is finished:
- Possibility to inspect every ballot including preferences
- Show number of voters preferring one initiative to another initiative
Interface behaviour changes:
- Reversed default order of drafts
- Default order of suggestions changed
- Show new drafts of initiatives only once per day in timeline
Accessibility:
- Barrier-free voting implemented
- POST links are now accessible without JavaScript
- Changed gray for unsatisfied supporters in bar graph to a lighter gray
Other interface improvements:
- Optical enhancements
- Dynamic interface using XMLHttpRequests
- Show usage terms in about section
- Show own membership in area listing
- Show uninformed supporters greyed out and marked with yellow question mark
- Warning box in non-admitted initiatives
- When voted, don't display voting notice and change label of voting link
- Show object counts in more tabulator heads
- Enlarged member statement input field
Miscellaneous:
- Code cleanup
- Added README file containing installation instructions
- Use new WebMCP function ui.filters{...} instead of own ui.filter and ui.order functions
Bugfixes:
- Only allow voting on admitted initiatives
- Repaired issue search
- Don't display delegations for closed issues on member page
- Don't show revoke link in initiative, when issue is already half_frozen
- Localization for voting JavaScript
- Display author of suggestions
Disclosure of voting data after voting is finished:
- Possibility to inspect every ballot including preferences
- Show number of voters preferring one initiative to another initiative
Interface behaviour changes:
- Reversed default order of drafts
- Default order of suggestions changed
- Show new drafts of initiatives only once per day in timeline
Accessibility:
- Barrier-free voting implemented
- POST links are now accessible without JavaScript
- Changed gray for unsatisfied supporters in bar graph to a lighter gray
Other interface improvements:
- Optical enhancements
- Dynamic interface using XMLHttpRequests
- Show usage terms in about section
- Show own membership in area listing
- Show uninformed supporters greyed out and marked with yellow question mark
- Warning box in non-admitted initiatives
- When voted, don't display voting notice and change label of voting link
- Show object counts in more tabulator heads
- Enlarged member statement input field
Miscellaneous:
- Code cleanup
- Added README file containing installation instructions
- Use new WebMCP function ui.filters{...} instead of own ui.filter and ui.order functions
| author | bsw/jbe | 
|---|---|
| date | Sat Feb 20 22:10:31 2010 +0100 (2010-02-20) | 
| parents | 77d58efe99fd | 
| children | 733f65c0c0a0 | 
| rev | line source | 
|---|---|
| bsw/jbe@0 | 1 Member = mondelefant.new_class() | 
| bsw/jbe@0 | 2 Member.table = 'member' | 
| bsw/jbe@0 | 3 | 
| bsw/jbe@0 | 4 Member:add_reference{ | 
| bsw@9 | 5 mode = "1m", | 
| bsw@9 | 6 to = "MemberHistory", | 
| bsw@9 | 7 this_key = 'id', | 
| bsw@9 | 8 that_key = 'member_id', | 
| bsw@9 | 9 ref = 'history_entries', | 
| bsw@9 | 10 back_ref = 'member' | 
| bsw@9 | 11 } | 
| bsw@9 | 12 | 
| bsw@9 | 13 Member:add_reference{ | 
| bsw/jbe@4 | 14 mode = '1m', | 
| bsw@2 | 15 to = "MemberImage", | 
| bsw@2 | 16 this_key = 'id', | 
| bsw@2 | 17 that_key = 'member_id', | 
| bsw/jbe@4 | 18 ref = 'images', | 
| bsw@2 | 19 back_ref = 'member' | 
| bsw@2 | 20 } | 
| bsw@2 | 21 | 
| bsw@2 | 22 Member:add_reference{ | 
| bsw/jbe@0 | 23 mode = '1m', | 
| bsw/jbe@0 | 24 to = "Contact", | 
| bsw/jbe@0 | 25 this_key = 'id', | 
| bsw/jbe@0 | 26 that_key = 'member_id', | 
| bsw/jbe@0 | 27 ref = 'contacts', | 
| bsw/jbe@0 | 28 back_ref = 'member', | 
| bsw/jbe@0 | 29 default_order = '"other_member_id"' | 
| bsw/jbe@0 | 30 } | 
| bsw/jbe@0 | 31 | 
| bsw/jbe@0 | 32 Member:add_reference{ | 
| bsw/jbe@0 | 33 mode = '1m', | 
| bsw/jbe@0 | 34 to = "Contact", | 
| bsw/jbe@0 | 35 this_key = 'id', | 
| bsw/jbe@0 | 36 that_key = 'member_id', | 
| bsw/jbe@0 | 37 ref = 'foreign_contacts', | 
| bsw/jbe@0 | 38 back_ref = 'other_member', | 
| bsw/jbe@0 | 39 default_order = '"member_id"' | 
| bsw/jbe@0 | 40 } | 
| bsw/jbe@0 | 41 | 
| bsw/jbe@0 | 42 Member:add_reference{ | 
| bsw/jbe@0 | 43 mode = '1m', | 
| bsw/jbe@0 | 44 to = "Session", | 
| bsw/jbe@0 | 45 this_key = 'id', | 
| bsw/jbe@0 | 46 that_key = 'member_id', | 
| bsw/jbe@0 | 47 ref = 'sessions', | 
| bsw/jbe@0 | 48 back_ref = 'member', | 
| bsw/jbe@0 | 49 default_order = '"ident"' | 
| bsw/jbe@0 | 50 } | 
| bsw/jbe@0 | 51 | 
| bsw/jbe@0 | 52 Member:add_reference{ | 
| bsw/jbe@0 | 53 mode = '1m', | 
| bsw/jbe@0 | 54 to = "Draft", | 
| bsw/jbe@0 | 55 this_key = 'id', | 
| bsw/jbe@0 | 56 that_key = 'author_id', | 
| bsw/jbe@0 | 57 ref = 'drafts', | 
| bsw/jbe@0 | 58 back_ref = 'author', | 
| bsw/jbe@0 | 59 default_order = '"id"' | 
| bsw/jbe@0 | 60 } | 
| bsw/jbe@0 | 61 | 
| bsw/jbe@0 | 62 Member:add_reference{ | 
| bsw/jbe@0 | 63 mode = '1m', | 
| bsw/jbe@0 | 64 to = "Suggestion", | 
| bsw/jbe@0 | 65 this_key = 'id', | 
| bsw/jbe@0 | 66 that_key = 'author_id', | 
| bsw/jbe@0 | 67 ref = 'suggestions', | 
| bsw/jbe@0 | 68 back_ref = 'author', | 
| bsw/jbe@0 | 69 default_order = '"id"' | 
| bsw/jbe@0 | 70 } | 
| bsw/jbe@0 | 71 | 
| bsw/jbe@0 | 72 Member:add_reference{ | 
| bsw/jbe@0 | 73 mode = '1m', | 
| bsw/jbe@0 | 74 to = "Membership", | 
| bsw/jbe@0 | 75 this_key = 'id', | 
| bsw/jbe@0 | 76 that_key = 'member_id', | 
| bsw/jbe@0 | 77 ref = 'memberships', | 
| bsw/jbe@0 | 78 back_ref = 'member', | 
| bsw/jbe@0 | 79 default_order = '"area_id"' | 
| bsw/jbe@0 | 80 } | 
| bsw/jbe@0 | 81 | 
| bsw/jbe@0 | 82 Member:add_reference{ | 
| bsw/jbe@0 | 83 mode = '1m', | 
| bsw/jbe@0 | 84 to = "Interest", | 
| bsw/jbe@0 | 85 this_key = 'id', | 
| bsw/jbe@0 | 86 that_key = 'member_id', | 
| bsw/jbe@0 | 87 ref = 'interests', | 
| bsw/jbe@0 | 88 back_ref = 'member', | 
| bsw/jbe@0 | 89 default_order = '"id"' | 
| bsw/jbe@0 | 90 } | 
| bsw/jbe@0 | 91 | 
| bsw/jbe@0 | 92 Member:add_reference{ | 
| bsw/jbe@0 | 93 mode = '1m', | 
| bsw/jbe@0 | 94 to = "Initiator", | 
| bsw/jbe@0 | 95 this_key = 'id', | 
| bsw/jbe@0 | 96 that_key = 'member_id', | 
| bsw/jbe@0 | 97 ref = 'initiators', | 
| bsw@10 | 98 back_ref = 'member' | 
| bsw/jbe@0 | 99 } | 
| bsw/jbe@0 | 100 | 
| bsw/jbe@0 | 101 Member:add_reference{ | 
| bsw/jbe@0 | 102 mode = '1m', | 
| bsw/jbe@0 | 103 to = "Supporter", | 
| bsw/jbe@0 | 104 this_key = 'id', | 
| bsw/jbe@0 | 105 that_key = 'member_id', | 
| bsw/jbe@0 | 106 ref = 'supporters', | 
| bsw@2 | 107 back_ref = 'member' | 
| bsw/jbe@0 | 108 } | 
| bsw/jbe@0 | 109 | 
| bsw/jbe@0 | 110 Member:add_reference{ | 
| bsw/jbe@0 | 111 mode = '1m', | 
| bsw/jbe@0 | 112 to = "Opinion", | 
| bsw/jbe@0 | 113 this_key = 'id', | 
| bsw/jbe@0 | 114 that_key = 'member_id', | 
| bsw/jbe@0 | 115 ref = 'opinions', | 
| bsw/jbe@0 | 116 back_ref = 'member', | 
| bsw/jbe@0 | 117 default_order = '"id"' | 
| bsw/jbe@0 | 118 } | 
| bsw/jbe@0 | 119 | 
| bsw/jbe@0 | 120 Member:add_reference{ | 
| bsw/jbe@0 | 121 mode = '1m', | 
| bsw/jbe@0 | 122 to = "Delegation", | 
| bsw/jbe@0 | 123 this_key = 'id', | 
| bsw/jbe@0 | 124 that_key = 'truster_id', | 
| bsw/jbe@0 | 125 ref = 'outgoing_delegations', | 
| bsw/jbe@0 | 126 back_ref = 'truster', | 
| bsw/jbe@0 | 127 default_order = '"id"' | 
| bsw/jbe@0 | 128 } | 
| bsw/jbe@0 | 129 | 
| bsw/jbe@0 | 130 Member:add_reference{ | 
| bsw/jbe@0 | 131 mode = '1m', | 
| bsw/jbe@0 | 132 to = "Delegation", | 
| bsw/jbe@0 | 133 this_key = 'id', | 
| bsw/jbe@0 | 134 that_key = 'trustee_id', | 
| bsw/jbe@0 | 135 ref = 'incoming_delegations', | 
| bsw/jbe@0 | 136 back_ref = 'trustee', | 
| bsw/jbe@0 | 137 default_order = '"id"' | 
| bsw/jbe@0 | 138 } | 
| bsw/jbe@0 | 139 | 
| bsw/jbe@0 | 140 Member:add_reference{ | 
| bsw/jbe@0 | 141 mode = '1m', | 
| bsw/jbe@0 | 142 to = "DirectVoter", | 
| bsw/jbe@0 | 143 this_key = 'id', | 
| bsw/jbe@0 | 144 that_key = 'member_id', | 
| bsw/jbe@0 | 145 ref = 'direct_voter', | 
| bsw/jbe@0 | 146 back_ref = 'member', | 
| bsw/jbe@0 | 147 default_order = '"issue_id"' | 
| bsw/jbe@0 | 148 } | 
| bsw/jbe@0 | 149 | 
| bsw/jbe@0 | 150 Member:add_reference{ | 
| bsw/jbe@0 | 151 mode = '1m', | 
| bsw/jbe@0 | 152 to = "Vote", | 
| bsw/jbe@0 | 153 this_key = 'id', | 
| bsw/jbe@0 | 154 that_key = 'member_id', | 
| bsw/jbe@0 | 155 ref = 'vote', | 
| bsw/jbe@0 | 156 back_ref = 'member', | 
| bsw/jbe@0 | 157 default_order = '"issue_id", "initiative_id"' | 
| bsw/jbe@0 | 158 } | 
| bsw/jbe@0 | 159 | 
| bsw/jbe@0 | 160 Member:add_reference{ | 
| bsw/jbe@0 | 161 mode = 'mm', | 
| bsw/jbe@0 | 162 to = "Member", | 
| bsw/jbe@0 | 163 this_key = 'id', | 
| bsw/jbe@0 | 164 that_key = 'id', | 
| bsw/jbe@0 | 165 connected_by_table = 'contact', | 
| bsw/jbe@0 | 166 connected_by_this_key = 'member_id', | 
| bsw/jbe@0 | 167 connected_by_that_key = 'other_member_id', | 
| bsw/jbe@0 | 168 ref = 'saved_members', | 
| bsw/jbe@0 | 169 } | 
| bsw/jbe@0 | 170 | 
| bsw/jbe@0 | 171 Member:add_reference{ | 
| bsw/jbe@0 | 172 mode = 'mm', | 
| bsw/jbe@0 | 173 to = "Member", | 
| bsw/jbe@0 | 174 this_key = 'id', | 
| bsw/jbe@0 | 175 that_key = 'id', | 
| bsw/jbe@0 | 176 connected_by_table = 'contact', | 
| bsw/jbe@0 | 177 connected_by_this_key = 'other_member_id', | 
| bsw/jbe@0 | 178 connected_by_that_key = 'member_id', | 
| bsw/jbe@0 | 179 ref = 'saved_by_members', | 
| bsw/jbe@0 | 180 } | 
| bsw/jbe@0 | 181 | 
| bsw/jbe@0 | 182 Member:add_reference{ | 
| bsw/jbe@0 | 183 mode = 'mm', | 
| bsw/jbe@0 | 184 to = "Area", | 
| bsw/jbe@0 | 185 this_key = 'id', | 
| bsw/jbe@0 | 186 that_key = 'id', | 
| bsw/jbe@0 | 187 connected_by_table = 'membership', | 
| bsw/jbe@0 | 188 connected_by_this_key = 'member_id', | 
| bsw/jbe@0 | 189 connected_by_that_key = 'area_id', | 
| bsw/jbe@0 | 190 ref = 'areas' | 
| bsw/jbe@0 | 191 } | 
| bsw/jbe@0 | 192 | 
| bsw/jbe@0 | 193 Member:add_reference{ | 
| bsw/jbe@0 | 194 mode = 'mm', | 
| bsw/jbe@0 | 195 to = "Issue", | 
| bsw/jbe@0 | 196 this_key = 'id', | 
| bsw/jbe@0 | 197 that_key = 'id', | 
| bsw/jbe@0 | 198 connected_by_table = 'interest', | 
| bsw/jbe@0 | 199 connected_by_this_key = 'member_id', | 
| bsw/jbe@0 | 200 connected_by_that_key = 'issue_id', | 
| bsw/jbe@0 | 201 ref = 'issues' | 
| bsw/jbe@0 | 202 } | 
| bsw/jbe@0 | 203 | 
| bsw/jbe@0 | 204 Member:add_reference{ | 
| bsw/jbe@0 | 205 mode = 'mm', | 
| bsw/jbe@0 | 206 to = "Initiative", | 
| bsw/jbe@0 | 207 this_key = 'id', | 
| bsw/jbe@0 | 208 that_key = 'id', | 
| bsw/jbe@0 | 209 connected_by_table = 'initiator', | 
| bsw/jbe@0 | 210 connected_by_this_key = 'member_id', | 
| bsw/jbe@0 | 211 connected_by_that_key = 'initiative_id', | 
| bsw/jbe@0 | 212 ref = 'initiated_initiatives' | 
| bsw/jbe@0 | 213 } | 
| bsw/jbe@0 | 214 | 
| bsw/jbe@0 | 215 Member:add_reference{ | 
| bsw/jbe@0 | 216 mode = 'mm', | 
| bsw/jbe@0 | 217 to = "Initiative", | 
| bsw/jbe@0 | 218 this_key = 'id', | 
| bsw/jbe@0 | 219 that_key = 'id', | 
| bsw/jbe@0 | 220 connected_by_table = 'supporter', | 
| bsw/jbe@0 | 221 connected_by_this_key = 'member_id', | 
| bsw/jbe@0 | 222 connected_by_that_key = 'initiative_id', | 
| bsw/jbe@0 | 223 ref = 'supported_initiatives' | 
| bsw/jbe@0 | 224 } | 
| bsw/jbe@0 | 225 | 
| bsw/jbe@0 | 226 function Member.object:set_password(password) | 
| bsw/jbe@0 | 227 local hash = os.crypt( | 
| bsw/jbe@0 | 228 password, | 
| bsw/jbe@0 | 229 "$1$" .. multirand.string( | 
| bsw/jbe@0 | 230 8, | 
| bsw/jbe@0 | 231 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./" | 
| bsw/jbe@0 | 232 ) | 
| bsw/jbe@0 | 233 ) | 
| bsw/jbe@0 | 234 assert(hash, "os.crypt failed") | 
| bsw/jbe@0 | 235 self.password = hash | 
| bsw/jbe@0 | 236 end | 
| bsw/jbe@0 | 237 | 
| bsw/jbe@0 | 238 function Member.object:check_password(password) | 
| bsw/jbe@0 | 239 if type(password) == "string" and type(self.password) == "string" then | 
| bsw/jbe@0 | 240 return os.crypt(password, self.password) == self.password | 
| bsw/jbe@0 | 241 else | 
| bsw/jbe@0 | 242 return false | 
| bsw/jbe@0 | 243 end | 
| bsw/jbe@0 | 244 end | 
| bsw/jbe@0 | 245 | 
| bsw/jbe@0 | 246 function Member.object_get:published_contacts() | 
| bsw/jbe@0 | 247 return Member:new_selector() | 
| bsw/jbe@0 | 248 :join('"contact"', nil, '"contact"."other_member_id" = "member"."id"') | 
| bsw/jbe@0 | 249 :add_where{ '"contact"."member_id" = ?', self.id } | 
| bsw/jbe@0 | 250 :add_where("public") | 
| bsw/jbe@0 | 251 :exec() | 
| bsw/jbe@0 | 252 end | 
| bsw/jbe@0 | 253 | 
| bsw/jbe@0 | 254 function Member:by_login_and_password(login, password) | 
| bsw/jbe@0 | 255 local selector = self:new_selector() | 
| bsw/jbe@5 | 256 selector:add_where{'"login" = ?', login } | 
| bsw/jbe@0 | 257 selector:add_where('"active"') | 
| bsw/jbe@0 | 258 selector:optional_object_mode() | 
| bsw/jbe@0 | 259 local member = selector:exec() | 
| bsw/jbe@0 | 260 if member and member:check_password(password) then | 
| bsw/jbe@0 | 261 return member | 
| bsw/jbe@0 | 262 else | 
| bsw/jbe@0 | 263 return nil | 
| bsw/jbe@0 | 264 end | 
| bsw/jbe@0 | 265 end | 
| bsw/jbe@0 | 266 | 
| bsw/jbe@5 | 267 function Member:by_login(login) | 
| bsw/jbe@5 | 268 local selector = self:new_selector() | 
| bsw/jbe@5 | 269 selector:add_where{'"login" = ?', login } | 
| bsw/jbe@5 | 270 selector:optional_object_mode() | 
| bsw/jbe@5 | 271 return selector:exec() | 
| bsw/jbe@5 | 272 end | 
| bsw/jbe@5 | 273 | 
| bsw/jbe@5 | 274 function Member:by_name(name) | 
| bsw/jbe@5 | 275 local selector = self:new_selector() | 
| bsw/jbe@5 | 276 selector:add_where{'"name" = ?', name } | 
| bsw/jbe@5 | 277 selector:optional_object_mode() | 
| bsw/jbe@5 | 278 return selector:exec() | 
| bsw/jbe@5 | 279 end | 
| bsw/jbe@5 | 280 | 
| bsw@2 | 281 function Member:get_search_selector(search_string) | 
| bsw/jbe@0 | 282 return self:new_selector() | 
| bsw@2 | 283 :add_field( {'"highlight"("member"."name", ?)', search_string }, "name_highlighted") | 
| bsw@2 | 284 :add_where{ '"member"."text_search_data" @@ "text_search_query"(?)', search_string } | 
| bsw/jbe@0 | 285 :add_where("active") | 
| bsw/jbe@0 | 286 end | 
| bsw@2 | 287 | 
| bsw/jbe@6 | 288 function Member.object:set_notify_email(notify_email) | 
| bsw/jbe@6 | 289 local expiry = db:query("SELECT now() + '7 days'::interval as expiry", "object").expiry | 
| bsw/jbe@6 | 290 self.notify_email_unconfirmed = notify_email | 
| bsw/jbe@6 | 291 self.notify_email_secret = multirand.string( 24, "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" ) | 
| bsw/jbe@6 | 292 self.notify_email_secret_expiry = expiry | 
| bsw/jbe@6 | 293 local content = slot.use_temporary(function() | 
| bsw/jbe@6 | 294 slot.put(_"Hello " .. self.name .. ",\n\n") | 
| bsw/jbe@6 | 295 slot.put(_"Please confirm your email address by clicking the following link:\n\n") | 
| bsw/jbe@6 | 296 slot.put(config.absolute_base_url .. "index/confirm_notify_email.html?secret=" .. self.notify_email_secret .. "\n\n") | 
| bsw/jbe@6 | 297 slot.put(_"If this link is not working, please open following url in your web browser:\n\n") | 
| bsw/jbe@6 | 298 slot.put(config.absolute_base_url .. "index/confirm_notify_email.html\n\n") | 
| bsw/jbe@6 | 299 slot.put(_"On that page please enter the confirmation code:\n\n") | 
| bsw/jbe@6 | 300 slot.put(self.notify_email_secret .. "\n\n") | 
| bsw/jbe@6 | 301 end) | 
| bsw/jbe@6 | 302 local success = net.send_mail{ | 
| bsw/jbe@6 | 303 envelope_from = config.mail_envelope_from, | 
| bsw/jbe@6 | 304 from = config.mail_from, | 
| bsw/jbe@6 | 305 reply_to = config.mail_reply_to, | 
| bsw/jbe@6 | 306 to = self.notify_email_unconfirmed, | 
| bsw/jbe@6 | 307 subject = config.mail_subject_prefix .. _"Email confirmation request", | 
| bsw/jbe@6 | 308 content_type = "text/plain; charset=UTF-8", | 
| bsw/jbe@6 | 309 content = content | 
| bsw/jbe@6 | 310 } | 
| bsw/jbe@6 | 311 return success | 
| bsw/jbe@6 | 312 end | 
| bsw@11 | 313 | 
| bsw/jbe@19 | 314 function Member.object:get_setting(key) | 
| bsw/jbe@19 | 315 return Setting:by_pk(app.session.member.id, key) | 
| bsw/jbe@19 | 316 end | 
| bsw/jbe@19 | 317 | 
| bsw/jbe@19 | 318 function Member.object:get_setting_value(key) | 
| bsw/jbe@19 | 319 local setting = Setting:by_pk(app.session.member.id, key) | 
| bsw/jbe@19 | 320 if setting then | 
| bsw/jbe@19 | 321 return setting.value | 
| bsw/jbe@19 | 322 end | 
| bsw@11 | 323 end | 
| bsw@11 | 324 | 
| bsw@11 | 325 function Member.object:set_setting(key, value) | 
| bsw/jbe@19 | 326 local setting = self:get_setting(key) | 
| bsw/jbe@19 | 327 if not setting then | 
| bsw/jbe@19 | 328 setting = Setting:new() | 
| bsw/jbe@19 | 329 setting.member_id = app.session.member_id | 
| bsw/jbe@19 | 330 setting.key = key | 
| bsw/jbe@19 | 331 end | 
| bsw/jbe@19 | 332 setting.value = value | 
| bsw/jbe@19 | 333 setting:save() | 
| bsw@11 | 334 end | 
| bsw@11 | 335 | 
| bsw@11 | 336 function Member.object:get_setting_maps_by_key(key) | 
| bsw@11 | 337 return SettingMap:new_selector() | 
| bsw@11 | 338 :add_where{ "member_id = ?", self.id } | 
| bsw@11 | 339 :add_where{ "key = ?", key } | 
| bsw@11 | 340 :add_order_by("subkey") | 
| bsw@11 | 341 :exec() | 
| bsw@11 | 342 end | 
| bsw@11 | 343 | 
| bsw@11 | 344 function Member.object:get_setting_map_by_key_and_subkey(key, subkey) | 
| bsw@11 | 345 return SettingMap:new_selector() | 
| bsw@11 | 346 :add_where{ "member_id = ?", self.id } | 
| bsw@11 | 347 :add_where{ "key = ?", key } | 
| bsw@11 | 348 :add_where{ "subkey = ?", subkey } | 
| bsw@11 | 349 :add_order_by("subkey") | 
| bsw@11 | 350 :optional_object_mode() | 
| bsw@11 | 351 :exec() | 
| bsw@11 | 352 end | 
| bsw@11 | 353 | 
| bsw@11 | 354 function Member.object:set_setting_map(key, subkey, value) | 
| bsw@11 | 355 | 
| bsw@11 | 356 end |