Commit 8f1cfce9e673bc0d4a4457f9d729b040fb3ddad5

Authored by Tássia Camões Araújo
1 parent 4ea9806a
Exists in master and in 1 other branch add_vagrant

Definition of forms as classes; Validation of input form data; New option of

recommendation by packages_list; Templates layout modifications.
src/recommender.py
@@ -49,7 +49,7 @@ class RecommendationResult: @@ -49,7 +49,7 @@ class RecommendationResult:
49 Return prediction based on recommendation size (number of items). 49 Return prediction based on recommendation size (number of items).
50 """ 50 """
51 sorted_result = sorted(self.item_score.items(), key=itemgetter(1)) 51 sorted_result = sorted(self.item_score.items(), key=itemgetter(1))
52 - return reversed(sorted_result[:size]) 52 + return reversed(sorted_result[-size:])
53 53
54 class Recommender: 54 class Recommender:
55 """ 55 """
src/strategy.py
@@ -20,6 +20,7 @@ __license__ = """ @@ -20,6 +20,7 @@ __license__ = """
20 along with this program. If not, see <http://www.gnu.org/licenses/>. 20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 """ 21 """
22 22
  23 +import string
23 import os, re 24 import os, re
24 import xapian 25 import xapian
25 from data import * 26 from data import *
@@ -181,7 +182,8 @@ class AxiContentBasedStrategy(RecommendationStrategy): @@ -181,7 +182,8 @@ class AxiContentBasedStrategy(RecommendationStrategy):
181 Perform recommendation strategy. 182 Perform recommendation strategy.
182 """ 183 """
183 profile = user.axi_tag_profile(rec.items_repository,50) 184 profile = user.axi_tag_profile(rec.items_repository,50)
184 - query = xapian.Query(xapian.Query.OP_OR,profile) 185 + #profile_str = string.join(list(profile),' ')
  186 + query = xapian.Query(xapian.Query.OP_OR,list(profile))
185 enquire = xapian.Enquire(rec.items_repository) 187 enquire = xapian.Enquire(rec.items_repository)
186 enquire.set_query(query) 188 enquire.set_query(query)
187 189
@@ -200,13 +202,17 @@ class CollaborativeStrategy(RecommendationStrategy): @@ -200,13 +202,17 @@ class CollaborativeStrategy(RecommendationStrategy):
200 """ 202 """
201 Colaborative recommendation strategy. 203 Colaborative recommendation strategy.
202 """ 204 """
  205 + def __init__(self):
  206 + self.description = "Collaborative"
  207 +
203 #def run(self,rec,user,similarity_measure): 208 #def run(self,rec,user,similarity_measure):
204 def run(self,rec,user): 209 def run(self,rec,user):
205 """ 210 """
206 Perform recommendation strategy. 211 Perform recommendation strategy.
207 """ 212 """
208 profile = user.maximal_pkg_profile() 213 profile = user.maximal_pkg_profile()
209 - query = xapian.Query(xapian.Query.OP_OR,profile) 214 + #profile_str = string.join(list(profile),' ')
  215 + query = xapian.Query(xapian.Query.OP_OR,list(profile))
210 enquire = xapian.Enquire(rec.users_repository) 216 enquire = xapian.Enquire(rec.users_repository)
211 enquire.set_query(query) 217 enquire.set_query(query)
212 218
@@ -236,6 +242,9 @@ class KnowledgeBasedStrategy(RecommendationStrategy): @@ -236,6 +242,9 @@ class KnowledgeBasedStrategy(RecommendationStrategy):
236 """ 242 """
237 Knowledge-based recommendation strategy. 243 Knowledge-based recommendation strategy.
238 """ 244 """
  245 + def __init__(self):
  246 + self.description = "Knowledge-based"
  247 +
239 def run(self,user,knowledge_repository): 248 def run(self,user,knowledge_repository):
240 """ 249 """
241 Perform recommendation strategy. 250 Perform recommendation strategy.
@@ -247,6 +256,9 @@ class DemographicStrategy(RecommendationStrategy): @@ -247,6 +256,9 @@ class DemographicStrategy(RecommendationStrategy):
247 """ 256 """
248 Recommendation strategy based on demographic data. 257 Recommendation strategy based on demographic data.
249 """ 258 """
  259 + def __init__(self):
  260 + self.description = "Demographic"
  261 +
250 def run(self,user,items_repository): 262 def run(self,user,items_repository):
251 """ 263 """
252 Perform recommendation strategy. 264 Perform recommendation strategy.
@@ -123,12 +123,17 @@ class User: @@ -123,12 +123,17 @@ class User:
123 cache = apt.Cache() 123 cache = apt.Cache()
124 old_profile_size = len(self.pkg_profile) 124 old_profile_size = len(self.pkg_profile)
125 for p in self.pkg_profile[:]: #iterate list copy 125 for p in self.pkg_profile[:]: #iterate list copy
126 - pkg = cache[p]  
127 - if pkg.candidate:  
128 - for dep in pkg.candidate.dependencies:  
129 - for or_dep in dep.or_dependencies:  
130 - if or_dep.name in self.pkg_profile:  
131 - self.pkg_profile.remove(or_dep.name) 126 + try:
  127 + if cache.has_key(p):
  128 + pkg = cache[p]
  129 + if pkg.candidate:
  130 + for dep in pkg.candidate.dependencies:
  131 + for or_dep in dep.or_dependencies:
  132 + if or_dep.name in self.pkg_profile:
  133 + self.pkg_profile.remove(or_dep.name)
  134 + except:
  135 + logging.debug("Disconsidering package not found in cache: %s"
  136 + % p)
132 profile_size = len(self.pkg_profile) 137 profile_size = len(self.pkg_profile)
133 logging.info("Reduced packages profile size from %d to %d." % 138 logging.info("Reduced packages profile size from %d to %d." %
134 (old_profile_size, profile_size)) 139 (old_profile_size, profile_size))
@@ -157,9 +162,14 @@ class LocalSystem(User): @@ -157,9 +162,14 @@ class LocalSystem(User):
157 cache = apt.Cache() 162 cache = apt.Cache()
158 old_profile_size = len(self.pkg_profile) 163 old_profile_size = len(self.pkg_profile)
159 for p in self.pkg_profile[:]: #iterate list copy 164 for p in self.pkg_profile[:]: #iterate list copy
160 - pkg = cache[p]  
161 - if pkg.is_auto_installed:  
162 - self.pkg_profile.remove(p) 165 + try:
  166 + if cache.has_key(p):
  167 + pkg = cache[p]
  168 + if pkg.is_auto_installed:
  169 + self.pkg_profile.remove(p)
  170 + except:
  171 + logging.debug("Disconsidering package not found in cache: %s"
  172 + % p)
163 profile_size = len(self.pkg_profile) 173 profile_size = len(self.pkg_profile)
164 logging.info("Reduced packages profile size from %d to %d." % 174 logging.info("Reduced packages profile size from %d to %d." %
165 (old_profile_size, profile_size)) 175 (old_profile_size, profile_size))
src/web/server.py
@@ -11,119 +11,117 @@ from config import * @@ -11,119 +11,117 @@ from config import *
11 from recommender import * 11 from recommender import *
12 from user import * 12 from user import *
13 13
14 -def add_global_hook():  
15 - g = web.storage({"counter": "1"})  
16 - def _wrapper(handler):  
17 - web.ctx.globals = g  
18 - return handler()  
19 - return _wrapper  
20 -  
21 -class Index:  
22 - def GET(self):  
23 - form_action = "/feedback"  
24 - form_method = "post"  
25 - form = self._form()  
26 - return render.index(form_action, form_method, form)  
27 -  
28 - def _form(self):  
29 - send_form = form.Form(  
30 - form.File("pkgs_file", description="Packages list"),  
31 - form.Dropdown('limit', [('5', '05'), ('10', '10'), ('20', '20')], 14 +class RequestForm(form.Form):
  15 + def __init__(self):
  16 + form.Form.__init__(self, \
  17 + form.File("pkgs_file", description="Upload file"),
  18 + form.Textarea("pkgs_list", description="Packages",
  19 + rows="4", cols="40"),
  20 + form.Dropdown('limit', [(5, '05'), (10, '10'), (20, '20')],
32 description = "Limit"), 21 description = "Limit"),
33 - form.Checkbox("strategy_cb", value="True", checked=False, 22 + form.Checkbox("strategy_cb", value=1, checked=False,
34 description="Content-based"), 23 description="Content-based"),
35 - form.Checkbox("strategy_col", value="True", checked=False, 24 + form.Checkbox("strategy_col", value=1, checked=False,
36 description="Collaborative"), 25 description="Collaborative"),
37 form.Checkbox("strategy_hybrid", value="True", checked=False, 26 form.Checkbox("strategy_hybrid", value="True", checked=False,
38 description="Hybrid"), 27 description="Hybrid"),
39 form.Checkbox("strategy_hybrid_plus", value="True", checked=False, 28 form.Checkbox("strategy_hybrid_plus", value="True", checked=False,
40 description="Hybrid plus"), 29 description="Hybrid plus"),
41 - validators = [form.Validator("You must select at least one strategy",  
42 - lambda i: i.strategy_cb | i.startegy_col |  
43 - i.strategy_hybrid | i.strategy_hybrid_plus)]  
44 - )  
45 - return send_form() 30 + validators = [form.Validator("No packages list provided.",
  31 + lambda f: f.has_key("pkgs_list") |
  32 + f.has_key("pkgs_file") ),
  33 + form.Validator("No strategy selected.",
  34 + lambda f: f.has_key("strategy_cb") |
  35 + f.has_key("startegy_col") |
  36 + f.has_key("strategy_hybrid") |
  37 + f.has_key("strategy_hybrid_plus")) ])
  38 +
  39 +class FeedbackForm(form.Form):
  40 + def __init__(self,strategies):
  41 + desc_dict = {"cta": "Content-based", "col": "Collaborative",
  42 + "hybrid": "Hybrib", "hybrid_plus": "Hybrid Plus"}
  43 + fields = []
  44 + for strategy in strategies:
  45 + fields.append(form.Radio(desc_dict[strategy],
  46 + [('1','1 '),('2','2 '),('3','3'),('4','4 '),('5','5')]))
  47 + form.Form.__init__(self, *fields, validators = [])
  48 +
  49 +class Index:
  50 + def GET(self):
  51 + return render.index("/apprec", "post", RequestForm())
  52 +
  53 +class Thanks:
  54 + def POST(self):
  55 + return render.thanks()
46 56
47 -class FeedbackForm: 57 +class AppRecommender:
48 def POST(self): 58 def POST(self):
49 - action = "/thanks"  
50 - method = "post"  
51 outputdir = tempfile.mkdtemp(prefix='',dir='./submissions/') 59 outputdir = tempfile.mkdtemp(prefix='',dir='./submissions/')
52 user_id = outputdir.lstrip('./submissions/') 60 user_id = outputdir.lstrip('./submissions/')
53 - x = web.input(pkgs_file={})  
54 - f = open(outputdir + "/packages_list", "wb")  
55 - #content = x['pkgs_file'].value  
56 - self.pkgs_list = []  
57 - for line in x['pkgs_file'].file:  
58 - self.pkgs_list.append(line.split()[0])  
59 - f.write(line)  
60 - # while 1:  
61 - # chunk = x['pkgs_file'].file.read(10000)  
62 - # if not chunk:  
63 - # break  
64 - # f.write(chunk)  
65 - f.close()  
66 -  
67 - strategies = []  
68 - if 'strategy_cb' in x: strategies.append("cta")  
69 - if 'strategy_col' in x: strategies.append("col")  
70 - if 'strategy_hybrid' in x: strategies.append("hybrid")  
71 - if 'strategy_hybrid+' in x: strategies.append("hybrid+")  
72 -  
73 - return render.feedbackForm(action, method,  
74 - self._recommends(user_id,strategies),  
75 - self._form())  
76 -  
77 - def _recommends(self,user_id,strategies):  
78 - user = User(dict.fromkeys(self.pkgs_list,1),user_id) 61 + request = RequestForm()
  62 + request_info = web.input(pkgs_file={})
  63 + if not request.validates(request_info):
  64 + return render.error(request)
  65 + else:
  66 + user_pkgs_list = []
  67 + if request_info.has_key('pkgs_list'):
  68 + user_pkgs_list = request_info['pkgs_list'].encode('utf8').split()
  69 + print user_pkgs_list
  70 +
  71 + if request_info.has_key('pkgs_file'):
  72 + f = open(outputdir + "/packages_list", "wb")
  73 + for line in request_info['pkgs_file'].file:
  74 + user_pkgs_list.append(line.split()[0])
  75 + f.write(line)
  76 + f.close()
  77 +
  78 + strategies = []
  79 + if request_info.has_key('strategy_cb'): strategies.append("cta")
  80 + ### Colaborative strategies can not go online yet
  81 + if request_info.has_key('strategy_col'): strategies.append("col")
  82 + if request_info.has_key('strategy_hybrid'):
  83 + strategies.append("hybrid")
  84 + if request_info.has_key('strategy_hybrid_plus'):
  85 + strategies.append("hybrid_plus")
  86 +
  87 + return render.apprec("/thanks", "post",
  88 + self._recommends(user_id,user_pkgs_list,
  89 + strategies, int(request_info['limit'])),
  90 + FeedbackForm(strategies))
  91 +
  92 + def _recommends(self,user_id,user_pkgs_list,strategies,limit):
  93 + user = User(dict.fromkeys(user_pkgs_list,1),user_id)
79 user.maximal_pkg_profile() 94 user.maximal_pkg_profile()
80 cfg = Config() 95 cfg = Config()
81 rec = Recommender(cfg) 96 rec = Recommender(cfg)
82 results = dict() 97 results = dict()
83 for strategy in strategies: 98 for strategy in strategies:
84 - eval("rec."+strategy+"(cfg)")  
85 - prediction = rec.get_recommendation(user).get_prediction()  
86 - results[rec.strategy.description] = [result[0] for result in prediction]  
87 - # Colaborative strategies can not go online yet  
88 - results['Hybrid+'] = []  
89 - results['Hybrid'] = []  
90 - results['Collaborative'] = [] 99 + ### Colaborative strategies can not go online yet
  100 + #eval("rec."+strategy+"(cfg)")
  101 + rec.cta(cfg)
  102 + prediction = rec.get_recommendation(user).get_prediction(limit)
  103 + results[rec.strategy.description] = \
  104 + [result[0] for result in prediction]
91 return results 105 return results
92 106
93 - def _form(self):  
94 - send_form = form.Form(  
95 - form.Radio('Content-based',  
96 - [('1','1 '),('2','2 '),('3','3 '),('4','4 '),('5','5')]),  
97 - form.Radio('Collaborative',  
98 - [('1','1 '),('2','2 '),('3','3 '),('4','4 '),('5','5')]),  
99 - form.Radio('Hybrid',  
100 - [('1','1 '),('2','2 '),('3','3 '),('4','4 '),('5','5')]),  
101 - form.Radio('Hybrid+',  
102 - [('1','1 '),('2','2 '),('3','3 '),('4','4 '),('5','5')]),  
103 - form.Dropdown('expertise',  
104 - ["Newbie", "Intermediate", "Advanced", "Guru", "Chuck Norris"],  
105 - description ='Debian expertise'),  
106 - form.Textarea('comments', rows="7", cols="60",  
107 - description="Anything else to share?"),  
108 -  
109 - )  
110 - return send_form  
111 -  
112 -class FeedbackThanks:  
113 - def POST(self):  
114 - return render.feedbackThanks() 107 +def add_global_hook():
  108 + g = web.storage({"counter": "1"})
  109 + def _wrapper(handler):
  110 + web.ctx.globals = g
  111 + return handler()
  112 + return _wrapper
115 113
116 render = web.template.render('templates/', base='layout') 114 render = web.template.render('templates/', base='layout')
117 115
118 urls = ('/', 'Index', 116 urls = ('/', 'Index',
119 - '/feedback', 'FeedbackForm',  
120 - '/thanks', 'FeedbackThanks' 117 + '/apprec', 'AppRecommender',
  118 + '/thanks', 'Thanks'
121 ) 119 )
122 120
123 web.webapi.internalerror = web.debugerror 121 web.webapi.internalerror = web.debugerror
124 122
125 if __name__ == "__main__": 123 if __name__ == "__main__":
126 - app = web.application(urls, globals())  
127 - app.add_processor(add_global_hook())  
128 - app.run() 124 + apprec = web.application(urls, globals())
  125 + apprec.add_processor(add_global_hook())
  126 + apprec.run()
129 127
src/web/templates/apprec.html 0 → 100644
@@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
  1 +$def with (action, method, recommends, form)
  2 +$var title: Feedback
  3 +$var mod = 'feedback';
  4 +
  5 + <div class="index_include">
  6 +
  7 +
  8 +<center>
  9 + <h1>Recommendations</h1>
  10 +</center>
  11 +
  12 +<p>Results per strategy:</p>
  13 +
  14 +<ul class="toc">
  15 +$for strategy, result in recommends.items():
  16 + <li><a href="#$strategy">$strategy</a></li>
  17 +<li><a href="#feedback">Your feedback is appreciated!</a></li>
  18 +</ul>
  19 +
  20 +<form action="$action" method="$method">
  21 +$for strategy, result in recommends.items():
  22 + <h2><a name="$strategy" id="$strategy">$strategy</a></h2>
  23 + $for pkg in result:
  24 + <div class="line">
  25 + <div class="item col50">
  26 + <image src="http://screenshots.debian.net/screenshot/$pkg" width="300">
  27 + </div>
  28 + <div class="item col50 lastcol">
  29 + <script>loadPackage("$pkg");</script>
  30 + <b id="pack-$pkg"></b><div id="desc-$pkg"> </div>
  31 + <ul>
  32 + <li id="maint-$pkg"></li>
  33 + <li id="homepage-$pkg"></li>
  34 + </ul>
  35 + </div>
  36 + <div class="clear"></div>
  37 + </div>
  38 + <hr />
  39 +
  40 +<h2><a name="feedback" id="feedback">Feedback</a></h2>
  41 +
  42 +$:form.render()
  43 +<input type="submit" />
  44 +</form>
  45 +
  46 +
  47 +</div>
  48 +
src/web/templates/error.html 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +$def with (form)
  2 +$var title: Error
  3 +$var mod = 'error';
  4 +
  5 +<div class="index_include">
  6 +<center>
  7 + <h1>Welcome to AppRecommender</h1>
  8 +</center>
  9 +
  10 +<div class="align-right"><img alt="AppRecommender logo" src="/static/Pics/AppRecommender-logo.jpg" /></div>
  11 +
  12 +Your request could not be proccessed due to the following error(s):
  13 +
  14 +<ul>
  15 +$for v in form.validators:
  16 + <b><li>$v.msg</li></b>
  17 +</ul>
  18 +
  19 +<p><a href="/">Go back</a> and try again. </p>
  20 +<p>If you believe it is a bug, please report to <a
  21 +href="mailto:tassia@gamil.com">tassia@gmail.com</a>.</p>
  22 +
  23 +</div>
src/web/templates/feedbackForm.html
@@ -1,50 +0,0 @@ @@ -1,50 +0,0 @@
1 -$def with (action, method, recommends, form)  
2 -$var title: Feedback  
3 -$var mod = 'feedback';  
4 -  
5 - <div class="index_include">  
6 -  
7 -  
8 -<center>  
9 - <h1>Recommendations</h1>  
10 -</center>  
11 -  
12 -<p>Results per strategy:</p>  
13 -  
14 -<ul class="toc">  
15 -$for strategy, result in recommends.items():  
16 - <li><a href="#$strategy">$strategy</a></li>  
17 -<li><a href="#feedback">Your feedback is appreciated!</a></li>  
18 -</ul>  
19 -  
20 -<form action="$action" method="$method">  
21 -$for strategy, result in recommends.items():  
22 - <h2><a name="$strategy" id="$strategy">$strategy</a></h2>  
23 - $for pkg in result:  
24 - <div class="line">  
25 - <div class="item col50">  
26 - <image src="http://screenshots.debian.net/screenshot/$pkg" width="300">  
27 - </div>  
28 - <div class="item col50 lastcol">  
29 - <script>loadPackage("$pkg");</script>  
30 - <b id="pack-$pkg"></b><div id="desc-$pkg"> </div>  
31 - <ul>  
32 - <li id="maint-$pkg"></li>  
33 - <li id="homepage-$pkg"></li>  
34 - </ul>  
35 - </div>  
36 - <div class="clear"></div>  
37 - </div>  
38 - <hr />  
39 -  
40 -<h2><a name="feedback" id="feedback">Feedback</a></h2>  
41 -  
42 -$if not form.valid: <p>Try again, AmeriCAN:</p>  
43 -$:form.render()  
44 -<br />  
45 -<input type="submit" />  
46 -</form>  
47 -  
48 -  
49 -</div>  
50 -  
src/web/templates/feedbackThanks.html
@@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
1 -$var title: Feedback  
2 -$var mod = 'thanks';  
3 -  
4 - <div class="index_include">  
5 -<center>  
6 - <h1>Thanks!</h1>  
7 -</center>  
8 -  
9 -</div>  
src/web/templates/index.html
@@ -16,9 +16,7 @@ providing platform independent solutions, it should also follow this @@ -16,9 +16,7 @@ providing platform independent solutions, it should also follow this
16 principle.</p> 16 principle.</p>
17 17
18 <form action="$action" enctype="multipart/form-data" method="$method"> 18 <form action="$action" enctype="multipart/form-data" method="$method">
19 -$if not form.valid: <p>Try again, AmeriCAN:</p>  
20 $:form.render() 19 $:form.render()
21 -<p>&nbsp;</p>  
22 <input type="submit" /> 20 <input type="submit" />
23 </form> 21 </form>
24 22
src/web/templates/layout.html
@@ -40,7 +40,7 @@ function loadPackage(packagename) @@ -40,7 +40,7 @@ function loadPackage(packagename)
40 <div id="logo"> 40 <div id="logo">
41 <a href="http://www.debian.org/" title="Debian Home"><img src="/static/Pics/openlogo-50.png" alt="Debian" width="50" height="61" /></a> 41 <a href="http://www.debian.org/" title="Debian Home"><img src="/static/Pics/openlogo-50.png" alt="Debian" width="50" height="61" /></a>
42 </div> 42 </div>
43 - <p class="section">Recommender</p> 43 + <p class="section">APP_REC</p>
44 44
45 </div> 45 </div>
46 46
@@ -48,10 +48,13 @@ function loadPackage(packagename) @@ -48,10 +48,13 @@ function loadPackage(packagename)
48 48
49 <p class="hidecss"><a href="#inner">Skip Quicknav</a></p> 49 <p class="hidecss"><a href="#inner">Skip Quicknav</a></p>
50 <ul> 50 <ul>
51 - <li><a href="http://www.debian.org/intro/about">About Debian</a></li>  
52 - <li><a href="http://www.debian.org/distrib/">Getting Debian</a></li>  
53 - <li><a href="http://www.debian.org/support">Support</a></li>  
54 - <li><a href="http://www.debian.org/devel/">Developers'&nbsp;Corner</a></li> 51 + <li><a href="/">Home</a></li>
  52 + <li>
  53 + <a href="http://www.ime.usp.br/~tassia/apprec.html">About</a>
  54 + </li>
  55 + <li>
  56 + <a href="http://github.com/tassia/AppRecommender">Devel</a>
  57 + </li>
55 58
56 </ul> 59 </ul>
57 </div> 60 </div>
@@ -69,11 +72,9 @@ $:content @@ -69,11 +72,9 @@ $:content
69 <div id="footer"> 72 <div id="footer">
70 <div id="pageinfo"> 73 <div id="pageinfo">
71 74
72 -<p>Back to the <a href="http://www.debian.org/../">Debian Project homepage</a>.</p>  
73 -<hr/>  
74 <div id="footermap"> 75 <div id="footermap">
75 <!--UdmComment--> 76 <!--UdmComment-->
76 -<p><strong><a href="http://www.debian.org">Home</a></strong></p> 77 +<p><strong><a href="http://www.debian.org">Debian Project homepage</a></strong></p>
77 <ul id="footermap-cola"> 78 <ul id="footermap-cola">
78 <li><a href="http://www.debian.org/intro/about">About</a> 79 <li><a href="http://www.debian.org/intro/about">About</a>
79 <ul> 80 <ul>
@@ -141,11 +142,13 @@ $:content @@ -141,11 +142,13 @@ $:content
141 </ul> 142 </ul>
142 <!--/UdmComment--> 143 <!--/UdmComment-->
143 </div> <!-- end footermap --> 144 </div> <!-- end footermap -->
  145 +
  146 +<hr />
144 <div id="fineprint"> 147 <div id="fineprint">
145 <p>To report a problem with the web site, e-mail <a 148 <p>To report a problem with the web site, e-mail <a
146 href="mailto:tassia@gmail.com">tassia@gmail.com</a>.</p> 149 href="mailto:tassia@gmail.com">tassia@gmail.com</a>.</p>
147 <p> 150 <p>
148 -Last Modified: <span class="date">Mon 09 May 2011 04:08:40 AM UTC</span> 151 +Last Modified: <span class="date">Mon 24 June 2011 21:00:40 UTC</span>
149 <br /> 152 <br />
150 Copyright &copy; 2011 153 Copyright &copy; 2011
151 <a href="$url_base">AppRecommender</a>.<br /> 154 <a href="$url_base">AppRecommender</a>.<br />
src/web/templates/thanks.html 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +$var title: Feedback
  2 +$var mod = 'thanks';
  3 +
  4 + <div class="index_include">
  5 +<center>
  6 + <h1>Thanks!</h1>
  7 +</center>
  8 +
  9 +</div>