Merge lp:~pratyush-nigam/sahana-eden/savesearch into lp:sahana-eden

Proposed by Pratyush Nigam
Status: Merged
Merged at revision: 2692
Proposed branch: lp:~pratyush-nigam/sahana-eden/savesearch
Merge into: lp:sahana-eden
Diff against target: 1083 lines (+471/-232)
10 files modified
controllers/msg.py (+131/-2)
controllers/pr.py (+69/-69)
deployment-templates/cron/crontab (+7/-0)
deployment-templates/models/000_config.py (+3/-0)
models/04_pr.py (+79/-72)
models/msg.py (+60/-24)
models/zz_last.py (+1/-0)
modules/s3/s3aaa.py (+1/-1)
modules/s3/s3cfg.py (+7/-2)
modules/s3/s3search.py (+113/-62)
To merge this branch: bzr merge lp:~pratyush-nigam/sahana-eden/savesearch
Reviewer Review Type Date Requested Status
Michael Howden (community) Needs Fixing
Review via email: mp+69918@code.launchpad.net
To post a comment you must log in.
2384. By Pratyush Nigam

removed the 80 characters in a single line

2385. By Pratyush Nigam

Merging

2386. By Pratyush Nigam

Merging

2387. By Pratyush Nigam

Removed some of the persisting errors

2388. By Pratyush Nigam

Started work on getting updates for the saved searches

2389. By Pratyush Nigam

Merging

2390. By Pratyush Nigam

Changes to find updates for the user who are subscribed to the saved searches
Work in Progress

2391. By Pratyush Nigam

Merging

2392. By Pratyush Nigam

Got the subscription update part working to some extent.

2393. By Pratyush Nigam

Merging

2394. By Pratyush Nigam

Changes made to implement the subscription sending feature

2395. By Pratyush Nigam

Merging

2396. By Pratyush Nigam

Made some changes with respect to what gets displayed in the Search Criteria Column

2397. By Pratyush Nigam

Changes made to send subscription emails, email formatting and cronjob settings left

2398. By Pratyush Nigam

Email Formatting Improved and Cron jobs are set

2399. By Pratyush Nigam

Merging and Customized the saved searches tab

2400. By Pratyush Nigam

Merging

Revision history for this message
Michael Howden (michael-howden) :
review: Approve
2401. By Pratyush Nigam

Code Documenting

2402. By Pratyush Nigam

Conflict removed and Merging

Revision history for this message
Michael Howden (michael-howden) :
review: Needs Fixing
2403. By Pratyush Nigam

Implemented the suggested changes

2404. By Pratyush Nigam

Merging

2405. By Pratyush Nigam

Playing with crud string

2406. By Pratyush Nigam

Made changes to rectify the errors in search criteria

2407. By Pratyush Nigam

Merging

2408. By Pratyush Nigam

Made changes to format email
---------- This line and the following will be ignored --------------

modified:
  controllers/msg.py
  controllers/pr.py

2409. By Pratyush Nigam

Merging

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'controllers/msg.py'
2--- controllers/msg.py 2011-08-06 18:24:53 +0000
3+++ controllers/msg.py 2011-08-20 19:52:33 +0000
4@@ -850,6 +850,136 @@
5 return items
6
7
8+# -----------------------------------------------------------------------------
9+def subscription():
10+ resourcename = "subscription"
11+ #form for the subscrption preferences of the user
12+ return s3_rest_controller(module, resourcename)
13+
14+
15+# -----------------------------------------------------------------------------
16+def load_search(id):
17+ var = {}
18+ var["load"] = id
19+ s3mgr.load("pr_save_search")
20+ rows = db(db.pr_save_search.id == id).select(db.pr_save_search.ALL)
21+ import cPickle
22+ for row in rows:
23+ search_vars = cPickle.loads(row.search_vars)
24+ prefix = str(search_vars["prefix"])
25+ function = str(search_vars["function"])
26+ date = str(row.modified_on)
27+ break
28+ field = "%s.modified_on__gt" %(function)
29+ date = date.replace(" ","T")
30+ date = date + "Z"
31+ var[field] = date
32+ #var["transform"] = "eden/static/formats/xml/import.xsl"
33+ r = current.manager.parse_request(prefix,
34+ function,
35+ args=["search"],
36+ #extension="xml",
37+ get_vars=Storage(var)
38+ )
39+ #redirect(URL(r=request, c=prefix, f=function, args=["search"],vars=var))
40+ response.s3.no_sspag=True
41+ output = r()
42+ #extract the updates
43+ return output
44+
45+
46+# -----------------------------------------------------------------------------
47+def get_criteria(id):
48+ s = ""
49+ try:
50+ id = id.replace("'","'")
51+ search_vars = cPickle.loads(id)
52+ s = "<p>"
53+ pat = '_'
54+ for var in search_vars.iterkeys():
55+ if var == "criteria" :
56+ c_dict = search_vars[var]
57+ #s = s + crud_string("pr_save_search", "Search Criteria")
58+ for j in c_dict.iterkeys():
59+ if not re.match(pat,j):
60+ st = str(j)
61+ st = st.replace("_search_"," ")
62+ st = st.replace("_advanced","")
63+ st = st.replace("_simple","")
64+ st = st.replace("text","text matching")
65+ """st = st.replace(search_vars["function"],"")
66+ st = st.replace(search_vars["prefix"],"")"""
67+ st = st.replace("_"," ")
68+ s = "%s <b> %s </b>: %s <br />" %(s, st.capitalize(), str(c_dict[j]))
69+ elif var == "simple" or var == "advanced":
70+ continue
71+ else:
72+ if var == "function":
73+ v1 = "Resource Name"
74+ elif var == "prefix":
75+ v1 = "Module"
76+ s = "%s<b>%s</b>: %s<br />" %(s, v1, str(search_vars[var]))
77+ s = s + "</p>"
78+ return s
79+ except:
80+ return s
81+
82+
83+# -----------------------------------------------------------------------------
84+def check_updates(user_id):
85+ #Check Updates for all the Saved Searches Subscribed by the User
86+ message = "<h2>Saved Searches' Update</h2>"
87+ flag = 0
88+ s3mgr.load("pr_save_search")
89+ rows = db(db.pr_save_search.user_id == user_id).select(db.pr_save_search.ALL)
90+ for row in rows :
91+ if row.subscribed:
92+ records = load_search(row.id)
93+ #message = message + "<b>" + get_criteria(row.id) + "</b>"
94+ if str(records["items"]) != "No Matching Records":
95+ message = message + str(records["items"]) + "<br />" #Include the Saved Search details
96+ flag = 1
97+ db.pr_save_search[row.id] = dict(modified_on = request.utcnow)
98+ if flag == 0:
99+ return
100+ else:
101+ return XML(message)
102+
103+
104+# -----------------------------------------------------------------------------
105+def subscription_messages():
106+
107+ subs = None
108+ if request.args[0] == "daily":
109+ subs = db(db.msg_subscription.subscription_frequency == "daily").select(
110+ db.msg_subscription.ALL)
111+ if request.args[0] == "weekly":
112+ subs = db(db.msg_subscription.subscription_frequency=="weekly").select(
113+ db.msg_subscription.ALL)
114+ if request.args[0] == "monthly":
115+ subs = db(db.msg_subscription.subscription_frequency=="monthly").select(
116+ db.msg_subscription.ALL)
117+ if subs:
118+ for sub in subs:
119+ #check if the message is not empty
120+ message = check_updates(sub.user_id)
121+ if message == None:
122+ return
123+ person_id = auth.s3_user_to_person(sub.user_id)
124+ rows = db(db.pr_person.id == person_id).select(db.pr_person.ALL)
125+ for row in rows:
126+ pe_id = row.pe_id
127+ break
128+ msg.send_by_pe_id(pe_id,
129+ subject="Subscription Updates",
130+ message=message,
131+ sender_pe_id = None,
132+ pr_message_method = "EMAIL",
133+ sender="noreply@sahana.com",
134+ fromaddress="sahana@sahana.com")
135+ return
136+
137+
138 # =============================================================================
139 # Enabled only for testing:
140 #
141@@ -870,5 +1000,4 @@
142 return s3_rest_controller(module, resourcename)
143
144
145-# END =========================================================================
146-
147+# END ================================================================================
148
149=== modified file 'controllers/pr.py'
150--- controllers/pr.py 2011-08-16 16:10:39 +0000
151+++ controllers/pr.py 2011-08-20 19:52:33 +0000
152@@ -4,6 +4,7 @@
153 VITA Person Registry, Controllers
154
155 @author: nursix
156+ @author: Pratyush Nigam <pratyush.nigam@gmail.com>
157 @see: U{http://eden.sahanafoundation.org/wiki/BluePrintVITA}
158 """
159
160@@ -108,6 +109,8 @@
161
162 # Load Model
163 s3mgr.load("pr_address")
164+ s3mgr.load("pr_save_search")
165+ s3mgr.load("msg_subscription")
166
167 # Handle Personalised Map Configs
168 gis_config_form_setup()
169@@ -146,8 +149,67 @@
170 r.table.volunteer.writable = True
171
172 return True
173-
174+
175+ def postp(r, output):
176+ if r.component_name == "save_search":
177+ # Handle Subscribe/Unsubscribe requests
178+ if "subscribe" in r.get_vars:
179+ save_search_id = r.get_vars.get("subscribe", None)
180+ db.pr_save_search[save_search_id] = dict(subscribed = True)
181+ if "unsubscribe" in r.get_vars:
182+ save_search_id = r.get_vars.get("unsubscribe", None)
183+ db.pr_save_search[save_search_id] = dict(subscribed = False)
184+
185+ s3_action_buttons(r)
186+ rows = db(db.pr_save_search.subscribed == False).select(
187+ db.pr_save_search.id)
188+ restrict_s = [str(row.id) for row in rows]
189+ rows = db(db.pr_save_search.subscribed == True).select(
190+ db.pr_save_search.id)
191+ restrict_u = [str(row.id) for row in rows]
192+ response.s3.actions = \
193+ response.s3.actions + [
194+ dict(label=str(T("Load Search")), _class="action-btn",
195+ url=str(URL(r=request,
196+ f="load_search",
197+ args=["[id]"]
198+ ),
199+ )
200+ )
201+ ]
202+ vars = {}
203+ #vars["person.uid"] = r.uid
204+ vars["subscribe"] = "[id]"
205+ response.s3.actions.append(
206+ dict(label=str(T("Subscribe")), _class="action-btn",
207+ url = str( URL(r = request,
208+ f = "person",
209+ args = [s3_logged_in_person(),
210+ "save_search"
211+ ],
212+ vars = vars)),
213+ restrict = restrict_s
214+ )
215+ )
216+ var = {}
217+ #var["person.uid"] = r.uid
218+ var["unsubscribe"] = "[id]"
219+ response.s3.actions.append(
220+ dict(label=str(T("Unsubscribe")), _class="action-btn",
221+ url = str( URL(r = request,
222+ f = "person",
223+ args = [s3_logged_in_person(),
224+ "save_search",
225+ ],
226+ vars = var)),
227+ restrict = restrict_u
228+ )
229+ )
230+
231+ return output
232+
233 response.s3.prep = prep
234+ response.s3.postp = postp
235
236 s3mgr.configure("pr_group_membership",
237 list_fields=["id",
238@@ -179,10 +241,10 @@
239 s3mgr.model.add_component("hrm_training",
240 pr_person="person_id")
241 tabs.append((T("Training"), "training"))
242-
243 # Configuration tabs
244 tabs = tabs + [(T("Subscriptions"), "pe_subscription"),
245 (T("Saved Searches"), "save_search"),
246+ (T("Subscription Details"), "subscription"),
247 (T("Map Settings"), "config")]
248
249 s3mgr.configure("pr_person", listadd=False, insertable=True)
250@@ -539,67 +601,6 @@
251 form2 = SQLFORM(persons, record, _id="form2", _action=("%s/%s" % (myUrl, perID2)))
252 return dict(form1=form1, form2=form2, perID1=perID1, perID2=perID2)
253
254-#------------------------------------------------------------------------------
255-#UI for Saved Searches
256-#
257-def save_search():
258-
259- if not auth.is_logged_in():
260- redirect(URL(f="index") )
261- else :
262- module = "pr"
263- resourcename = "save_search"
264- tablename = "%s_%s" % (module, resourcename)
265- s3mgr.load(tablename)
266- table = db[tablename]
267-
268- if "subscribe" in request.args:
269- rows = db(db.pr_save_search.id == request.args[0]).select(
270- db.pr_save_search.ALL)
271- for row in rows:
272- #process subscribe/un-subscribe action
273- if str(row.id) in request.args:
274- if row.subscribed:
275- db.pr_save_search[row.id] = dict(subscribed = False)
276- else:
277- db.pr_save_search[row.id] = dict(subscribed = True)
278- redirect(URL(f="save_search"))
279-
280- def prep(r):
281- if r.interactive :
282- s3mgr.configure(tablename,
283- insertable = False,
284- editable = False,
285- listadd = False,
286- deletable = True,
287- list_fields=["search_vars","subscribed"])
288- return True
289-
290- def postp(r, output):
291- s3_action_buttons(r)
292- values = [dict(col=2, key="True", display=str(T("Yes"))),
293- dict(col=2, key="False", display=str(T("No")))
294- ]
295- response.s3.dataTableDisplay = values
296- response.s3.actions = \
297- response.s3.actions + [
298- dict(label=str(T("Load Search")), _class="action-btn",
299- url=URL(f="load_search",
300- args=["[id]"])
301- )
302- ]
303- response.s3.actions.append(
304- dict(label=str(T("Subscribe/Unsubscribe")), _class="action-btn",
305- url = str( URL(f="save_search",
306- args = ["[id]", "subscribe"]))
307- )
308- )
309- return output
310-
311- response.s3.prep = prep
312- response.s3.postp = postp
313- return s3_rest_controller(module, resourcename)
314-
315
316 #------------------------------------------------------------------------------
317 #Function to redirect for loading the search
318@@ -607,17 +608,16 @@
319 def load_search():
320 var = {}
321 var["load"] = request.args[0]
322- rows = db(db.pr_save_search.id == request.args[0]).select(
323- db.pr_save_search.ALL)
324+ s3mgr.load("pr_save_search")
325+ rows = db(db.pr_save_search.id == request.args[0]).select(db.pr_save_search.ALL)
326+ import cPickle
327 for row in rows:
328 search_vars = cPickle.loads(row.search_vars)
329 prefix = str(search_vars["prefix"])
330 function = str(search_vars["function"])
331 break
332- redirect(URL(prefix,function, args=["search"],
333- vars=var))
334+ redirect(URL(r=request, c=prefix, f=function, args=["search"],vars=var))
335 return
336
337
338-# END =========================================================================
339-
340+# END =========================================================================
341\ No newline at end of file
342
343=== modified file 'deployment-templates/cron/crontab'
344--- deployment-templates/cron/crontab 2011-07-08 22:55:36 +0000
345+++ deployment-templates/cron/crontab 2011-08-20 19:52:33 +0000
346@@ -28,3 +28,10 @@
347 # Poll RMS Feeds (Haiti-specific)
348 #*/5 * * * * web2py *applications/eden/cron/rms_sms2record.py
349 #*/5 * * * * web2py *applications/eden/cron/rms_tweet2request.py
350+# Send Subscription Messages
351+# Daily
352+# 0 0 * * * web2py *msg/subscription_updates/daily
353+# Weekly
354+# 0 0 * * 1 web2py *msg/subscription_updates/weekly
355+# Monthly
356+# 0 0 1 * * web2py *msg/subscription_updates/monthly
357
358=== modified file 'deployment-templates/models/000_config.py'
359--- deployment-templates/models/000_config.py 2011-08-19 04:23:49 +0000
360+++ deployment-templates/models/000_config.py 2011-08-20 19:52:33 +0000
361@@ -402,6 +402,9 @@
362 # Human Resource Management
363 #deployment_settings.hrm.email_required = False
364
365+# Save Search Widget
366+deployment_settings.save_search.widget = True
367+
368 # Terms of Service to be able to Register on the system
369 #deployment_settings.options.terms_of_service = T("Terms of Service\n\nYou have to be eighteen or over to register as a volunteer.")
370 # Should we use internal Support Requests?
371
372=== modified file 'models/04_pr.py'
373--- models/04_pr.py 2011-08-14 13:15:49 +0000
374+++ models/04_pr.py 2011-08-20 19:52:33 +0000
375@@ -4,6 +4,7 @@
376 Person Registry
377
378 @author: nursix
379+ @author: Pratyush Nigam <pratyush.nigam@gmail.com>
380 @see: U{http://eden.sahanafoundation.org/wiki/BluePrintVITA}
381
382 """
383@@ -853,78 +854,84 @@
384 # =============================================================================
385 # Saved Searches
386 #
387-# @ToDo: Conditional Model Load
388-tablename = "pr_save_search"
389-table = db.define_table(tablename,
390- Field("user_id", "integer",
391- default = auth.user_id),
392- Field("search_vars", "string",
393- label = T("Search Criteria")),
394- Field("subscribed", "boolean",
395- default=False),
396- person_id(),
397- *s3_timestamp())
398-table.person_id.default = s3_logged_in_person()
399-table.user_id.readable = False
400-table.user_id.writable = False
401-
402-# @ToDo: If this can't be moved inside the conditional model load, then
403-# move to 00_db.py with the other imports
404-import cPickle
405-
406-def get_criteria(id):
407- s = ""
408- try:
409- id = id.replace("&apos;", "'")
410- search_vars = cPickle.loads(id)
411- s = "<p>"
412- pat = '_'
413- for var in search_vars.iterkeys():
414- if var == "criteria" :
415- c_dict = search_vars[var]
416- for j in c_dict.iterkeys():
417- if not re.match(pat,j):
418- s = "%s<b>%s</b>: %s<br />" % (s,
419- str(j).capitalize(),
420- str(c_dict[j]))
421- elif var == "simple" or var == "advanced":
422- continue
423- else:
424- s = "%s<b>%s</b>: %s<br />" % (s,
425- str(var).capitalize(),
426- str(search_vars[var]))
427- s = "%s</p>" % s
428- return XML(s)
429- except:
430- return XML(s)
431-
432-table.search_vars.represent = lambda id: get_criteria(id = id)
433-
434-s3mgr.configure(tablename,
435- insertable = False,
436- editable = False,
437- listadd = False,
438- deletable = True,
439- list_fields=["search_vars",
440- "subscribed"])
441-
442-s3mgr.model.add_component(tablename, pr_person="person_id")
443-
444-s3.crud_strings[tablename] = Storage(
445- title_create = T("Save Search"),
446- title_display = T("Saved Search Details"),
447- title_list = T("Saved Searches"),
448- title_update = T("Edit Saved Search"),
449- title_search = T("Search Saved Searches"),
450- subtitle_create = T("Add Saved Search"),
451- subtitle_list = T("Saved Searches"),
452- label_list_button = T("List Saved Searches"),
453- label_create_button = T("Save Search"),
454- label_delete_button = T("Delete Saved Search"),
455- msg_record_created = T("Saved Search added"),
456- msg_record_modified = T("Saved Search updated"),
457- msg_record_deleted = T("Saved Search deleted"),
458- msg_list_empty = T("No Search saved"))
459+def saved_search_tables():
460+ tablename = "pr_save_search"
461+ table = db.define_table(tablename,
462+ Field("user_id","integer", default = auth.user_id),
463+ Field("search_vars","string", label = T("Search Criteria")),
464+ Field("subscribed","boolean", default=False),
465+ person_id(label = T("Person")),
466+ migrate=migrate, *s3_timestamp())
467+ db.pr_save_search.person_id.default = s3_logged_in_person()
468+ db.pr_save_search.user_id.readable = False
469+ db.pr_save_search.user_id.writable = False
470+
471+ import cPickle
472+ def get_criteria(id):
473+ s = ""
474+ try:
475+ id = id.replace("&apos;","'")
476+ search_vars = cPickle.loads(id)
477+ s = "<p>"
478+ pat = '_'
479+ for var in search_vars.iterkeys():
480+ if var == "criteria" :
481+ c_dict = search_vars[var]
482+ #s = s + crud_string("pr_save_search", "Search Criteria")
483+ for j in c_dict.iterkeys():
484+ if not re.match(pat,j):
485+ st = str(j)
486+ st = st.replace("_search_"," ")
487+ st = st.replace("_advanced","")
488+ st = st.replace("_simple","")
489+ st = st.replace("text","text matching")
490+ """st = st.replace(search_vars["function"],"")
491+ st = st.replace(search_vars["prefix"],"")"""
492+ st = st.replace("_"," ")
493+ s = "%s <b> %s </b>: %s <br />" %(s, st.capitalize(), str(c_dict[j]))
494+ elif var == "simple" or var == "advanced":
495+ continue
496+ else:
497+ if var == "function":
498+ v1 = "Resource Name"
499+ elif var == "prefix":
500+ v1 = "Module"
501+ s = "%s<b>%s</b>: %s<br />" %(s, v1, str(search_vars[var]))
502+ s = s + "</p>"
503+ return XML(s)
504+ except:
505+ return XML(s)
506+
507+ db.pr_save_search.search_vars.represent = lambda id : get_criteria(id = id)
508+
509+ s3mgr.configure(tablename,
510+ insertable = False,
511+ editable = False,
512+ listadd = False,
513+ deletable = True,
514+ list_fields=["search_vars"])
515+
516+ s3mgr.model.add_component(table, pr_person="person_id")
517+
518+ s3.crud_strings[tablename] = Storage(
519+ title_create = T("Save Search"),
520+ title_display = T("Saved Search Details"),
521+ title_list = T("Saved Searches"),
522+ title_update = T("Edit Saved Search"),
523+ title_search = T("Search Saved Searches"),
524+ subtitle_create = T("Add Saved Search"),
525+ subtitle_list = T("Saved Searches"),
526+ label_list_button = T("List Saved Searches"),
527+ label_create_button = T("Save Search"),
528+ label_delete_button = T("Delete Saved Search"),
529+ msg_record_created = T("Saved Search added"),
530+ msg_record_modified = T("Saved Search updated"),
531+ msg_record_deleted = T("Saved Search deleted"),
532+ msg_list_empty = T("No Search saved"))
533+
534+# Provide a handle to this load function
535+s3mgr.model.loader(saved_search_tables,
536+ "pr_save_search")
537
538
539 # =============================================================================
540
541=== modified file 'models/msg.py' (properties changed: -x to +x)
542--- models/msg.py 2011-08-06 18:24:53 +0000
543+++ models/msg.py 2011-08-20 19:52:33 +0000
544@@ -40,7 +40,7 @@
545 represent = lambda direction: \
546 (direction and ["In"] or ["Out"])[0],
547 label = T("Direction")),
548-
549+ migrate=migrate,
550 *(s3_timestamp() + s3_uid() + s3_deletion_status()))
551
552 s3mgr.configure(tablename,
553@@ -77,7 +77,7 @@
554 Field("record_uuid", # null in this field implies subscription to the entire resource
555 type=s3uuid,
556 length=128),
557-
558+ migrate=migrate,
559 *(s3_timestamp() + s3_uid() + s3_deletion_status()))
560
561 s3mgr.configure(tablename,
562@@ -105,7 +105,7 @@
563 # - as easy for admin to edit source in 000_config.py as to edit DB (although an admin panel can be nice)
564 #Field("outbound_mail_server"),
565 #Field("outbound_mail_from"),
566- *s3_timestamp())
567+ migrate=migrate, *s3_timestamp())
568
569 # ---------------------------------------------------------------------
570 # SMS
571@@ -118,7 +118,7 @@
572 zero=None)),
573 # Moved to deployment_settings
574 #Field("default_country_code", "integer", default=44),
575- *s3_timestamp())
576+ migrate=migrate, *s3_timestamp())
577
578 # ---------------------------------------------------------------------
579 tablename = "msg_modem_settings"
580@@ -128,7 +128,7 @@
581 Field("modem_baud", "integer", default = 115200),
582 Field("enabled", "boolean", default = True),
583 #Field("preference", "integer", default = 5), To be used later
584- *s3_timestamp())
585+ migrate=migrate, *s3_timestamp())
586
587 # ---------------------------------------------------------------------
588 tablename = "msg_api_settings"
589@@ -141,7 +141,7 @@
590 Field("to_variable", "string", default = "to"),
591 Field("enabled", "boolean", default = True),
592 #Field("preference", "integer", default = 5), To be used later
593- *s3_timestamp())
594+ migrate=migrate, *s3_timestamp())
595
596 # ---------------------------------------------------------------------
597 tablename = "msg_smtp_to_sms_settings"
598@@ -151,14 +151,14 @@
599 Field("subject", length=64),
600 Field("enabled", "boolean", default = True),
601 #Field("preference", "integer", default = 5), To be used later
602- *s3_timestamp())
603+ migrate=migrate, *s3_timestamp())
604
605 # ---------------------------------------------------------------------
606 tablename = "msg_tropo_settings"
607 table = db.define_table(tablename,
608 Field("token_messaging"),
609 #Field("token_voice"),
610- *s3_timestamp())
611+ migrate=migrate, *s3_timestamp())
612
613 # ---------------------------------------------------------------------
614 # Outbound Messages
615@@ -200,7 +200,7 @@
616 opt_msg_status,
617 Field("system_generated", "boolean", default = False),
618 Field("log"),
619-
620+ migrate=migrate,
621 *(s3_timestamp() + s3_uid() + s3_deletion_status()))
622
623 s3mgr.model.add_component(table, msg_log="message_id")
624@@ -222,7 +222,7 @@
625 Field("recipient"),
626 Field("message"),
627 Field("network"),
628- )
629+ migrate=migrate)
630
631 # ---------------------------------------------------------------------
632 # Inbound Messages
633@@ -237,7 +237,7 @@
634 zero=None),
635 default = "EMAIL"),
636 Field("log"),
637-
638+ migrate=migrate,
639 *(s3_timestamp() + s3_uid() + s3_deletion_status()))
640
641 # ---------------------------------------------------------------------
642@@ -245,7 +245,7 @@
643 tablename = "msg_email_inbound_status"
644 table = db.define_table(tablename,
645 Field("status"),
646- )
647+ migrate=migrate)
648
649 # ---------------------------------------------------------------------
650 # SMS store for persistence and scratch pad for combining incoming xform chunks
651@@ -256,7 +256,7 @@
652 Field("totalno", "integer"),
653 Field("partno", "integer"),
654 Field("message", "string", length = 160),
655- )
656+ migrate=migrate)
657
658 # =====================================================================
659 def msg_compose( redirect_module = "msg",
660@@ -278,8 +278,8 @@
661 if auth.is_logged_in() or auth.basic():
662 pass
663 else:
664- redirect(URL(c="default", f="user", args="login",
665- vars={"_next":URL(redirect_module,redirect_function, vars=redirect_vars)}))
666+ redirect(URL(r=request, c="default", f="user", args="login",
667+ vars={"_next":URL(r=request, c=redirect_module, f=redirect_function, vars=redirect_vars)}))
668
669 # Model options
670 table1.sender.writable = table1.sender.readable = False
671@@ -305,7 +305,7 @@
672 """ Set the sender and use msg.send_by_pe_id to route the message """
673 if not request.vars.pe_id:
674 session.error = T("Please enter the recipient")
675- redirect(URL(redirect_module,redirect_function,
676+ redirect(URL(r=request, c=redirect_module, f=redirect_function,
677 vars=redirect_vars))
678 table = db.pr_person
679 query = (table.uuid == auth.user.person_uuid)
680@@ -319,11 +319,11 @@
681 # Trigger a Process Outbox
682 msg.process_outbox(contact_method = request.vars.pr_message_method)
683 session.flash = T("Check outbox for the message status")
684- redirect(URL(redirect_module,redirect_function,
685+ redirect(URL(r=request, c=redirect_module, f=redirect_function,
686 vars=redirect_vars))
687 else:
688 session.error = T("Error in message")
689- redirect(URL(redirect_module,redirect_function,
690+ redirect(URL(r=request, c=redirect_module, f=redirect_function,
691 vars=redirect_vars))
692
693 logform = crud.create(table1,
694@@ -345,7 +345,7 @@
695 Field("oauth_secret",
696 readable = False, writable = False),
697 Field("twitter_account", writable = False),
698- *s3_timestamp())
699+ migrate=migrate, *s3_timestamp())
700
701 def twitter_settings_onvalidation(form):
702 """ Complete oauth: take tokens from session + pin from form, and do the 2nd API call to Twitter """
703@@ -382,7 +382,7 @@
704 tablename = "msg_twitter_search"
705 table = db.define_table(tablename,
706 Field("search_query", length = 140),
707- )
708+ migrate = migrate)
709 # ---------------------------------------------------------------------
710 resourcename = "twitter_search_results"
711 tablename = "msg_twitter_search_results"
712@@ -391,7 +391,7 @@
713 Field("posted_by"),
714 Field("posted_at"),
715 Field("twitter_search", db.msg_twitter_search),
716- )
717+ migrate = migrate)
718 #table.twitter_search.requires = IS_ONE_OF(db, "twitter_search.search_query")
719 #table.twitter_search.represent = lambda id: db(db.msg_twitter_search.id == id).select(db.msg_twitter_search.search_query, limitby = (0,1)).first().search_query
720
721@@ -404,7 +404,7 @@
722 "posted_at",
723 "twitter_search",
724 ])
725-
726+
727 # ---------------------------------------------------------------------
728 # Pass variables back to global scope (response.s3.*)
729 return dict(msg_compose=msg_compose,
730@@ -422,6 +422,43 @@
731 "msg_tag",
732 "msg_channel",
733 "msg_outbox")
734+
735+ # ---------------------------------------------------------------------
736+ msg_subscription_mode_opts = {
737+ 1:T("Email"),
738+ #2:T("SMS"),
739+ #3:T("Email and SMS")
740+ }
741+ """msg_subscription_frequency_opts = {
742+ 1:T("Email"),
743+ 2:T("SMS"),
744+ 3:T("Email and SMS")
745+ }"""
746+ tablename = "msg_subscription"
747+ table = db.define_table(tablename,
748+ Field("user_id","integer",
749+ default = auth.user_id,
750+ ),
751+ Field("subscribe_mode","integer",
752+ default = 1,
753+ readable = False,
754+ requires = IS_IN_SET(msg_subscription_mode_opts,
755+ zero=None)
756+ ),
757+ Field("subscription_frequency",
758+ requires = IS_IN_SET(["daily","weekly","monthly"]),
759+ default = "daily",
760+ ),
761+ person_id(label = T("Person")),
762+ migrate=migrate, *s3_timestamp())
763+ db.msg_subscription.person_id.default = s3_logged_in_person()
764+ db.msg_subscription.user_id.writable = False
765+ db.msg_subscription.user_id.readable = False
766+ db.msg_subscription.user_id.requires = IS_NOT_IN_DB(db, 'msg_subscription.user_id')
767+ s3mgr.configure("msg_subscription",
768+ list_fields=["subscribe_mode",
769+ "subscription_frequency"])
770+ s3mgr.model.add_component("msg_subscription", pr_person="person_id")
771
772 # =========================================================================
773 # CAP: Common Alerting Protocol
774@@ -488,7 +525,7 @@
775 location_id(),
776 Field("image", "upload", autodelete = True),
777 Field("url", requires=IS_NULL_OR(IS_URL())),
778-
779+ migrate=migrate,
780 *(s3_timestamp() + s3_uid() + s3_deletion_status()))
781 # Pass variables back to global scope (response.s3.*)
782 return dict()
783@@ -498,4 +535,3 @@
784 "msg_report")
785
786 # END =========================================================================
787-
788
789=== modified file 'models/zz_last.py'
790--- models/zz_last.py 2011-08-10 07:39:32 +0000
791+++ models/zz_last.py 2011-08-20 19:52:33 +0000
792@@ -5,6 +5,7 @@
793 # Pass Tasks to Scheduler instance
794 # http://www.vimeo.com/27478796
795 # NB Avoid passing state into the async call as state may change before the message is executed (race condition)
796+import sys
797 try:
798 from gluon.scheduler import Scheduler
799 except:
800
801=== modified file 'modules/s3/s3aaa.py'
802--- modules/s3/s3aaa.py 2011-08-17 23:44:42 +0000
803+++ modules/s3/s3aaa.py 2011-08-20 19:52:33 +0000
804@@ -1636,7 +1636,7 @@
805 utable = db.pr_person
806 ptable = self.settings.table_user
807 query = (utable.id == user_id) & \
808- (utable.person_uuid == ptable.uuid)
809+ (ptable.person_uuid == utable.uuid)
810 record = db(query).select(ptable.id,
811 limitby=(0, 1)).first()
812 if record:
813
814=== modified file 'modules/s3/s3cfg.py'
815--- modules/s3/s3cfg.py 2011-08-18 18:20:37 +0000
816+++ modules/s3/s3cfg.py 2011-08-20 19:52:33 +0000
817@@ -63,9 +63,10 @@
818 self.req = Storage()
819 self.inv = Storage()
820 self.hrm = Storage()
821-
822+ self.save_search = Storage()
823+
824 T = current.T
825-
826+
827 # These are copied from modules/s3/s3aaa.py
828 self.aaa.acl = Storage(CREATE = 0x0001,
829 READ = 0x0002,
830@@ -446,6 +447,10 @@
831 def get_hrm_email_required(self):
832 return self.hrm.get("email_required", True)
833
834+ # Save Search and Subscription
835+ def get_save_search_widget(self):
836+ return self.save_search.get("widget", True)
837+
838 # -----------------------------------------------------------------------------
839 # Active modules list
840 def has_module(self, module_name):
841
842=== modified file 'modules/s3/s3search.py'
843--- modules/s3/s3search.py 2011-08-08 19:47:35 +0000
844+++ modules/s3/s3search.py 2011-08-20 19:52:33 +0000
845@@ -5,6 +5,7 @@
846
847 @author: Fran Boon <fran[at]aidiq.com>
848 @author: Dominic König <dominic[at]aidiq.com>
849+ @author: Pratyush Nigam <pratyush.nigam@gmail.com>
850
851 @requires: U{B{I{gluon}} <http://web2py.com>}
852
853@@ -36,6 +37,7 @@
854 import re
855 import sys
856 import gluon.contrib.simplejson as jsonlib
857+import cPickle
858
859 try:
860 import cPickle
861@@ -1068,6 +1070,100 @@
862 query = query & q
863 return (query, errors)
864 # -------------------------------------------------------------------------
865+ def save_search_widget(self, r, search_vars, **attr):
866+ request = self.request
867+ user_id = current.session.auth.user.id
868+ save_search_btn_id = "save_my_filter_btn_%s" % str(request.utcnow.microsecond)
869+ save_search_processing_id = "save_search_processing_%s" % str(request.utcnow.microsecond)
870+ save_search_a_id = "save_search_a_%s" % str(request.utcnow.microsecond)
871+ arg = str(user_id) + "/save_search"
872+ save_search_a = DIV("View and Subscribe to Saved Searches ",
873+ A("Here",
874+ _href = URL(r=request, c="pr", f="person", args=[arg]),
875+ _target = "_blank"
876+ ),
877+ ".",
878+ _id = save_search_a_id,
879+ _class = "save_search_a"
880+ )
881+ search_vars["prefix"] = r.prefix
882+ search_vars["function"] = r.function
883+ s3mgr = self.manager
884+ s3mgr.load("pr_save_search")
885+ db = current.db
886+ if len (db(db.pr_save_search.user_id == user_id).select(db.pr_save_search.id,
887+ limitby = (0,1))):
888+ rows = db(db.pr_save_search.user_id == user_id).select(db.pr_save_search.ALL)
889+ for row in rows:
890+ pat = '_'
891+ s_v = cPickle.loads(row.search_vars)
892+ if ((search_vars["prefix"] == s_v["prefix"]) and \
893+ (search_vars["function"] == s_v["function"])):
894+ s_dict = s_v["criteria"]
895+ if "criteria" in search_vars:
896+ c_dict = search_vars["criteria"]
897+ else:
898+ break
899+ diff = [ k for k in c_dict if k not in s_dict ]
900+ if not len(diff):
901+ flag = 1
902+ for j in s_dict.iterkeys():
903+ if not re.match(pat,j):
904+ if c_dict[j] != s_dict[j]:
905+ flag = 0
906+ break
907+ if flag == 1:
908+ return DIV(save_search_a,
909+ _style = "font-size:12px; padding:5px 0px 5px 90px;",
910+ _id = "save_search"
911+ )
912+
913+ save_search_btn = A("Save Search",
914+ _class = "save_search_btn",
915+ _id = save_search_btn_id,
916+ _href = "#",
917+ _title = "Save this search")
918+ save_search_a["_style"] = "display:none;"
919+ save_search_processing = IMG(_src = "/" + request.application + "/static/img/ajax-loader.gif",
920+ _id = save_search_processing_id,
921+ _class = "save_search_processing_id",
922+ _style = "display:none;"
923+ )
924+ s_var = {}
925+ s_var["save"] = True
926+ jurl = URL(r=request, c=r.prefix, f=r.function, args=["search"], vars = s_var)
927+ save_search_script = SCRIPT("".join("""
928+ $("#%s").live( 'click', function () {
929+ $("#%s").show();
930+ $("#%s").hide();
931+ $.ajax({
932+ url: '%s',
933+ data: '%s',
934+ success: function(data) {
935+ $("#%s").show();
936+ $("#%s").hide();
937+ },
938+ type: 'POST'
939+ });
940+ return false;
941+ });
942+ """%
943+ (save_search_btn_id,
944+ save_search_processing_id,
945+ save_search_btn_id,
946+ jurl,
947+ jsonlib.dumps(search_vars),
948+ save_search_a_id,
949+ save_search_processing_id)))
950+ save_search = DIV(save_search_processing,
951+ save_search_a,
952+ save_search_btn,
953+ save_search_script,
954+ _style = "font-size:12px; padding:5px 0px 5px 90px;",
955+ _id = "save_search"
956+ )
957+ return save_search
958+ # -------------------------------------------------------------------------
959 def search_interactive(self, r, **attr):
960 """
961 Interactive search
962@@ -1084,9 +1180,11 @@
963 db = current.db
964 table = self.table
965 tablename = self.tablename
966+ s3mgr = self.manager
967+ gis = s3mgr.gis
968 gis = current.gis
969 T = current.T
970-
971+
972 vars = request.get_vars
973
974 # Get representation
975@@ -1191,6 +1289,7 @@
976 if not search_id:
977 r.error(400, self.manager.ERROR.BAD_RECORD)
978 r.post_vars = r.vars
979+ s3mgr.load("pr_save_search")
980 search_table = current.db.pr_save_search
981 record = current.db(search_table.id == search_id).select(limitby=(0,1)).first()
982 if not record:
983@@ -1237,60 +1336,12 @@
984 errors = simple and self.__simple and simple_form.errors or \
985 self.__advanced and advanced_form.errors or \
986 None
987-
988- #Append the Save Search Widget
989- if r.http == "POST" and session.auth :
990- save_search_btn_id = "save_my_filter_btn_%s" % str(request.utcnow.microsecond)
991- save_search_processing_id = "save_search_processing_%s" % str(request.utcnow.microsecond)
992- save_search_a_id = "save_search_a_%s" % str(request.utcnow.microsecond)
993- save_search_a = DIV(T("View and Subscribe to Saved Searches "),
994- A("Here",
995- _href = URL(c='pr',f='save_search'),
996- _target = "_blank"
997- ),
998- ".",
999- _id = save_search_a_id,
1000- _class = "save_search_a"
1001- )
1002- save_search_btn = A("Save Search",
1003- _class = "save_search_btn",
1004- _id = save_search_btn_id,
1005- _href = "#",
1006- _title = T("Save this search"))
1007- save_search_a["_style"] = "display:none;"
1008- save_search_processing = IMG(_src = "/" + request.application + "/static/img/ajax-loader.gif",
1009- _id = save_search_processing_id,
1010- _class = "save_search_processing_id",
1011- _style = "display:none;"
1012- )
1013- search_vars["prefix"] = r.prefix
1014- search_vars["function"] = r.function
1015- s_var = {}
1016- s_var["save"] = True
1017- save_search_script = SCRIPT( """
1018- $("#""" + save_search_btn_id + """").live( 'click', function () {
1019- $("#""" + save_search_processing_id+ """").show();
1020- $("#""" + save_search_btn_id + """").hide();
1021- $.ajax({
1022- url: '""" + URL(r.prefix,r.function,args=["search"],
1023- vars = s_var) + """',
1024- data: '""" + jsonlib.dumps(search_vars) + """',
1025- success: function(data) {
1026- $("#""" + save_search_a_id + """").show();
1027- $("#""" + save_search_processing_id+ """").hide();
1028- },
1029- type: 'POST'
1030- });
1031- return false;
1032- });
1033- """)
1034- save_search = DIV(save_search_processing,
1035- save_search_a,
1036- save_search_btn,
1037- save_search_script,
1038- _style = "font-size:12px; padding:5px 0px 5px 90px;",
1039- _id = "save_search"
1040- )
1041+
1042+ settings = resource.manager.deployment_settings
1043+
1044+ # Append the Save Search Widget
1045+ if r.http == "POST" and session.auth and settings.get_save_search_widget :
1046+ save_search = self.save_search_widget(r, search_vars, **attr)
1047 else:
1048 save_search = DIV()
1049 if self.__simple:
1050@@ -1738,6 +1789,7 @@
1051 # r contains the resource name:
1052 tablename = r.tablename
1053 component = r.component_name
1054+ s3mgr = self.manager
1055 db = current.db
1056 session = current.session
1057 auth = current.auth
1058@@ -1756,14 +1808,14 @@
1059 key = str(i)
1060 s_vars[key] = str(search_vars[i])
1061 search_str = cPickle.dumps(s_vars)
1062- table = db.pr_save_search
1063- query = (table.user_id == user_id) & \
1064- (table.search_vars == search_str)
1065- if len (db(query).select()) == 0:
1066+ s3mgr.load("pr_save_search")
1067+ if len (db( (db.pr_save_search.user_id == user_id) & \
1068+ (db.pr_save_search.search_vars == search_str)).select()) == 0:
1069 new_search = {}
1070 new_search["search_vars"] = search_str
1071- _id = table.insert(**new_search)
1072- msg = "success"
1073+ search_table = db.pr_save_search
1074+ _id = search_table.insert(**new_search)
1075+ msg = "success"
1076 return msg
1077
1078
1079@@ -2232,4 +2284,3 @@
1080 return output
1081
1082 # =============================================================================
1083-