Commit 896fcc0700b027acebed531bcf18efa7ec2a1c02
1 parent
a7a750fa
Exists in
master
and in
1 other branch
Integrated recommender and new web form; changed section about; changes frontpage layout;
Showing
6 changed files
with
184 additions
and
124 deletions
Show diff stats
src/web/server.py
| ... | ... | @@ -6,6 +6,7 @@ import tempfile |
| 6 | 6 | import sys |
| 7 | 7 | import simplejson as json |
| 8 | 8 | import apt |
| 9 | +import re | |
| 9 | 10 | |
| 10 | 11 | sys.path.insert(0,"../") |
| 11 | 12 | |
| ... | ... | @@ -15,44 +16,24 @@ from user import * |
| 15 | 16 | |
| 16 | 17 | import urllib |
| 17 | 18 | |
| 18 | -class RequestForm(form.Form): | |
| 19 | - def __init__(self): | |
| 20 | - form.Form.__init__(self, \ | |
| 21 | - form.File("pkgs_file", size=35, description="Upload file"), | |
| 22 | - form.Textarea("pkgs_list", description="Packages", | |
| 23 | - rows="4", cols="52"), | |
| 24 | - form.Dropdown('limit', [(5, '05'), (10, '10'), (20, '20')], | |
| 25 | - description = "Limit"), | |
| 26 | - form.Checkbox("strategy_cb", value=1, checked=False, | |
| 27 | - description="Content-based"), | |
| 28 | - form.Checkbox("strategy_col", value=1, checked=False, | |
| 29 | - description="Collaborative"), | |
| 30 | - form.Checkbox("strategy_hybrid", value="True", checked=False, | |
| 31 | - description="Hybrid"), | |
| 32 | - form.Checkbox("strategy_hybrid_plus", value="True", checked=False, | |
| 33 | - description="Hybrid plus"), | |
| 34 | - validators = [form.Validator("No packages list provided.", | |
| 35 | - lambda f: f.has_key("pkgs_list") | | |
| 36 | - f.has_key("pkgs_file") ), | |
| 37 | - form.Validator("No strategy selected.", | |
| 38 | - lambda f: f.has_key("strategy_cb") | | |
| 39 | - f.has_key("startegy_col") | | |
| 40 | - f.has_key("strategy_hybrid") | | |
| 41 | - f.has_key("strategy_hybrid_plus")) ]) | |
| 42 | - | |
| 43 | 19 | class FeedbackForm(form.Form): |
| 44 | - def __init__(self,strategies): | |
| 45 | - desc_dict = {"cta": "Content-based", "col": "Collaborative", | |
| 46 | - "hybrid": "Hybrib", "hybrid_plus": "Hybrid Plus"} | |
| 20 | + def __init__(self,selected_strategies): | |
| 21 | + desc_dict = {"cb": "Content-based", "cbt": "Content-based", | |
| 22 | + "cbd": "Content-based", "col": "Collaborative", | |
| 23 | + "hybrid": "Hybrib"} | |
| 47 | 24 | fields = [] |
| 48 | - for strategy in strategies: | |
| 25 | + for strategy in selected_strategies: | |
| 49 | 26 | fields.append(form.Radio(desc_dict[strategy], |
| 50 | 27 | [('1','1 '),('2','2 '),('3','3'),('4','4 '),('5','5')])) |
| 51 | 28 | form.Form.__init__(self, *fields, validators = []) |
| 52 | 29 | |
| 53 | 30 | class Index: |
| 54 | 31 | def GET(self): |
| 55 | - return render.index("/apprec", "post", RequestForm()) | |
| 32 | + return render.index() | |
| 33 | + | |
| 34 | +class About: | |
| 35 | + def GET(self): | |
| 36 | + return render.about() | |
| 56 | 37 | |
| 57 | 38 | class Thanks: |
| 58 | 39 | def POST(self): |
| ... | ... | @@ -95,52 +76,108 @@ class Package: |
| 95 | 76 | subtags = [] |
| 96 | 77 | return debtags |
| 97 | 78 | |
| 79 | +class Request: | |
| 80 | + def __init__(self,web_input,user_id): | |
| 81 | + self.user_id = user_id | |
| 82 | + self.storage = web_input | |
| 83 | + | |
| 84 | + self.pkgs_list = [] | |
| 85 | + if web_input.has_key('pkgs_list'): | |
| 86 | + self.pkgs_list = web_input['pkgs_list'].encode('utf8').split() | |
| 87 | + print self.pkgs_list | |
| 88 | + print web_input['pkgs_file'] | |
| 89 | + if web_input['pkgs_file']: | |
| 90 | + f = open(outputdir + "/packages_list", "wb") | |
| 91 | + lines = web_input['pkgs_file'].file.readlines() | |
| 92 | + print lines | |
| 93 | + if lines[0].startswith('POPULARITY-CONTEST'): | |
| 94 | + del lines[0] | |
| 95 | + del lines[-1] | |
| 96 | + package_name_field = 2 | |
| 97 | + else: | |
| 98 | + package_name_field = 0 | |
| 99 | + for line in lines: | |
| 100 | + self.pkgs_list.append(line.split()[package_name_field]) | |
| 101 | + f.write(line) | |
| 102 | + f.close() | |
| 103 | + | |
| 104 | + if web_input.has_key('limit'): | |
| 105 | + self.limit = int(web_input['limit']) | |
| 106 | + if web_input.has_key('profile_size'): | |
| 107 | + self.profile_size = int(web_input['profile_size']) | |
| 108 | + if web_input.has_key('weight'): | |
| 109 | + self.weight = web_input['weight'].encode('utf8') | |
| 110 | + | |
| 111 | + self.selected_strategies = [] | |
| 112 | + if web_input.has_key('strategy'): | |
| 113 | + if web_input['strategy'].encode('utf8') == "content": | |
| 114 | + if (web_input.has_key('tag') and web_input.has_key('desc')): | |
| 115 | + self.selected_strategies.append("cb") | |
| 116 | + elif web_input.has_key('desc'): | |
| 117 | + self.selected_strategies.append("cbd") | |
| 118 | + else: | |
| 119 | + self.selected_strategies.append("cbt") | |
| 120 | + if web_input['strategy'].encode('utf8') == "collab": | |
| 121 | + self.selected_strategies.append("col") | |
| 122 | + if web_input['strategy'].encode('utf8') == "hybrid": | |
| 123 | + self.selected_strategies.append("hybrid") | |
| 124 | + | |
| 125 | + if web_input.has_key('cluster'): | |
| 126 | + self.cluster = bool(web_input.has_key['cluster'].encode('utf8')) | |
| 127 | + | |
| 128 | + if web_input.has_key('neighbours'): | |
| 129 | + self.neighbours = int(web_input['neighbours']) | |
| 130 | + | |
| 131 | + self.profiles_set = set() | |
| 132 | + if web_input.has_key('profile_desktop'): | |
| 133 | + self.profiles = self.profiles.add("desktop") | |
| 134 | + if web_input.has_key('profile_admin'): | |
| 135 | + self.profiles = self.profiles.add("admin") | |
| 136 | + if web_input.has_key('profile_devel'): | |
| 137 | + self.profiles = self.profiles.add("devel") | |
| 138 | + if web_input.has_key('profile_science'): | |
| 139 | + self.profiles = self.profiles.add("science") | |
| 140 | + if web_input.has_key('profile_arts'): | |
| 141 | + self.profiles = self.profiles.add("arts") | |
| 142 | + | |
| 143 | + def __str__(self): | |
| 144 | + return self.storage | |
| 145 | + | |
| 146 | + def validates(self): | |
| 147 | + self.errors = [] | |
| 148 | + if (not self.pkgs_list or not self.selected_strategies): | |
| 149 | + self.errors.append("No upload file or packages list was provided.") | |
| 150 | + return False | |
| 151 | + if not self.selected_strategies: | |
| 152 | + self.errors.append("No strategy was selected.") | |
| 153 | + return False | |
| 154 | + return True | |
| 155 | + | |
| 156 | + def get_strategy_details(self): | |
| 157 | + return self.selected_strategies[0] | |
| 98 | 158 | |
| 99 | 159 | class AppRecommender: |
| 100 | 160 | def POST(self): |
| 161 | + print "post",web.input() | |
| 101 | 162 | outputdir = tempfile.mkdtemp(prefix='',dir='./submissions/') |
| 102 | 163 | user_id = outputdir.lstrip('./submissions/') |
| 103 | - request = RequestForm() | |
| 104 | - request_info = web.input(pkgs_file={}) | |
| 105 | - if not request.validates(request_info): | |
| 106 | - return render.error(request) | |
| 164 | + print web.input(pkgs_file={}) | |
| 165 | + request = Request(web.input(pkgs_file={}),user_id) | |
| 166 | + if not request.validates(): | |
| 167 | + return render.error(request.errors) | |
| 107 | 168 | else: |
| 108 | - user_pkgs_list = [] | |
| 109 | - if request_info.has_key('pkgs_list'): | |
| 110 | - user_pkgs_list = request_info['pkgs_list'].encode('utf8').split() | |
| 111 | - print user_pkgs_list | |
| 112 | - | |
| 113 | - if request_info['pkgs_file'].value: | |
| 114 | - f = open(outputdir + "/packages_list", "wb") | |
| 115 | - lines = request_info['pkgs_file'].file.readlines() | |
| 116 | - print lines | |
| 117 | - if lines[0].startswith('POPULARITY-CONTEST'): | |
| 118 | - del lines[0] | |
| 119 | - del lines[-1] | |
| 120 | - package_name_field = 2 | |
| 121 | - else: | |
| 122 | - package_name_field = 0 | |
| 123 | - for line in lines: | |
| 124 | - user_pkgs_list.append(line.split()[package_name_field]) | |
| 125 | - f.write(line) | |
| 126 | - f.close() | |
| 127 | - | |
| 128 | - strategies = [] | |
| 129 | - if request_info.has_key('strategy_cb'): strategies.append("cta") | |
| 130 | - ### Colaborative strategies can not go online yet | |
| 131 | - if request_info.has_key('strategy_col'): strategies.append("col") | |
| 132 | - if request_info.has_key('strategy_hybrid'): | |
| 133 | - strategies.append("hybrid") | |
| 134 | - if request_info.has_key('strategy_hybrid_plus'): | |
| 135 | - strategies.append("hybrid_plus") | |
| 136 | - recommends = self._recommends(user_id,user_pkgs_list,strategies, int(request_info['limit'])) | |
| 169 | + recommendation = self._recommends(request) | |
| 137 | 170 | ### Getting package summary (short description) ### |
| 138 | 171 | pkg_summaries = {} |
| 139 | 172 | cache = apt.Cache() |
| 140 | - for strategy, result in recommends.items(): | |
| 173 | + for strategy, result in recommendation.items(): | |
| 141 | 174 | for pkg in result: |
| 142 | - pkg_summaries[pkg] = cache[pkg].candidate.summary | |
| 143 | - return render.apprec(recommends, pkg_summaries, FeedbackForm(strategies)) | |
| 175 | + try: | |
| 176 | + pkg_summaries[pkg] = cache[pkg].candidate.summary | |
| 177 | + except: | |
| 178 | + pkg_summaries[pkg] = "" | |
| 179 | + return render.apprec(recommendation, pkg_summaries, | |
| 180 | + FeedbackForm(request.selected_strategies),request) | |
| 144 | 181 | |
| 145 | 182 | # parsing json from screenshots - can be usefull in the future... |
| 146 | 183 | # def _packages_attrs(self, recommends): #recommends is result of _recommends() |
| ... | ... | @@ -156,18 +193,16 @@ class AppRecommender: |
| 156 | 193 | # recommended_pkgs_attrs[pkg_attrs_dict['name']] = pkg_attrs_dict |
| 157 | 194 | # return recommended_pkgs_attrs |
| 158 | 195 | |
| 159 | - def _recommends(self,user_id,user_pkgs_list,strategies,limit): | |
| 160 | - user = User(dict.fromkeys(user_pkgs_list,1),user_id) | |
| 196 | + def _recommends(self,request): | |
| 197 | + user = User(dict.fromkeys(request.pkgs_list,1),request.user_id) | |
| 161 | 198 | user.maximal_pkg_profile() |
| 162 | 199 | cfg = Config() |
| 163 | 200 | rec = Recommender(cfg) |
| 164 | 201 | results = dict() |
| 165 | - for strategy in strategies: | |
| 166 | - ### Colaborative strategies can not go online yet | |
| 167 | - #eval("rec."+strategy+"(cfg)") | |
| 168 | - rec.cta(cfg) | |
| 169 | - prediction = rec.get_recommendation(user).get_prediction(limit) | |
| 170 | - results[rec.strategy.description] = \ | |
| 202 | + for strategy_str in request.selected_strategies: | |
| 203 | + rec.set_strategy(strategy_str) | |
| 204 | + prediction = rec.get_recommendation(user).get_prediction(request.limit) | |
| 205 | + results[strategy_str] = \ | |
| 171 | 206 | [result[0] for result in prediction] |
| 172 | 207 | return results |
| 173 | 208 | |
| ... | ... | @@ -181,9 +216,10 @@ def add_global_hook(): |
| 181 | 216 | render = web.template.render('templates/', base='layout') |
| 182 | 217 | render_plain = web.template.render('templates/') |
| 183 | 218 | |
| 184 | -urls = ('/', 'Index', | |
| 185 | - '/apprec', 'AppRecommender', | |
| 219 | +urls = ('/', 'Index', | |
| 220 | + '/apprec', 'AppRecommender', | |
| 186 | 221 | '/thanks', 'Thanks', |
| 222 | + '/about', 'About', | |
| 187 | 223 | '/package/(.*)', 'Package' |
| 188 | 224 | ) |
| 189 | 225 | ... | ... |
| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | +$var title: About | |
| 2 | +$var mod = 'about'; | |
| 3 | + | |
| 4 | +<div class="graybox"> | |
| 5 | + <h1>What is this?</h1> | |
| 6 | +<p>AppRecommender is a project in development that aims to provide solutions | |
| 7 | +for application recommendation at the GNU/Linux world. It was initially thought | |
| 8 | +as a Debian package recommender, but considering the multi-distro effort in | |
| 9 | +providing platform independent solutions, it should also follow this | |
| 10 | +principle.</p> | |
| 11 | +</div> | |
| 12 | + | |
| 13 | +<div class="align-right"><img alt="AppRecommender logo" src="/static/images/AppRecommender-logo.jpg" width="320" /></div> | |
| 14 | + | ... | ... |
src/web/templates/apprec.html
| 1 | -$def with (recommends, pkg_summaries, form) | |
| 1 | +$def with (recommends, pkg_summaries, form, request) | |
| 2 | 2 | $var title: Feedback |
| 3 | 3 | $var mod = 'feedback'; |
| 4 | 4 | |
| 5 | - | |
| 6 | 5 | <script type="application/x-javascript"> |
| 7 | 6 | $$(document).ready(function() { |
| 8 | 7 | inithandlers(); |
| ... | ... | @@ -12,18 +11,19 @@ $$(document).ready(function() { |
| 12 | 11 | </script> |
| 13 | 12 | |
| 14 | 13 | <div class="graybox"> |
| 15 | - <h1>Results per strategy</h1> | |
| 16 | -<ul class="toc"> | |
| 14 | + <h1>Details of recommendation strategy</h1> | |
| 15 | + $request.get_strategy_details() | |
| 16 | +<!--<ul class="toc"> | |
| 17 | 17 | $for strategy, result in recommends.items(): |
| 18 | 18 | <li><a href="#$strategy">$strategy</a></li> |
| 19 | 19 | </ul> |
| 20 | -</div> | |
| 20 | +</div>--> | |
| 21 | 21 | |
| 22 | -<form action="/thanks" method="post"> | |
| 22 | +<form action="/thanks" method="post" enctype="multipart/form-data"> | |
| 23 | 23 | <table> |
| 24 | 24 | <tbody> |
| 25 | 25 | $for strategy, result in recommends.items(): |
| 26 | - <h2><a name="$strategy" id="$strategy">$strategy</a></h2> | |
| 26 | + <!--<h2><a name="$strategy" id="$strategy">$strategy</a></h2>--> | |
| 27 | 27 | <tr> |
| 28 | 28 | $ count = 0 |
| 29 | 29 | $for pkg in result: | ... | ... |
src/web/templates/error.html
| 1 | -$def with (form) | |
| 1 | +$def with (error_msgs) | |
| 2 | 2 | $var title: Error |
| 3 | 3 | $var mod = 'error'; |
| 4 | 4 | |
| ... | ... | @@ -11,10 +11,10 @@ $var mod = 'error'; |
| 11 | 11 | |
| 12 | 12 | Your request could not be proccessed due to the following error(s): |
| 13 | 13 | |
| 14 | -<ul> | |
| 15 | -$for v in form.validators: | |
| 16 | - <b><li>$v.msg</li></b> | |
| 17 | -</ul> | |
| 14 | +<p><ul> | |
| 15 | +$for e in error_msgs: | |
| 16 | + <b><li>$e</li></b> | |
| 17 | +</ul></p> | |
| 18 | 18 | |
| 19 | 19 | <p><a href="/">Go back</a> and try again. </p> |
| 20 | 20 | <p>If you believe it is a bug, please report to <a | ... | ... |
src/web/templates/index.html
| 1 | -$def with (action, method, form) | |
| 2 | 1 | $var title: Home |
| 3 | 2 | $var mod = 'index'; |
| 4 | 3 | |
| 5 | 4 | <div class="graybox"> |
| 6 | - <h1>What is this?</h1> | |
| 7 | -<p>AppRecommender is a project in development that aims to provide solutions | |
| 8 | -for application recommendation at the GNU/Linux world. It was initially thought | |
| 9 | -as a Debian package recommender, but considering the multi-distro effort in | |
| 10 | -providing platform independent solutions, it should also follow this | |
| 11 | -principle.</p> | |
| 12 | -</div> | |
| 5 | +<h1>You might also like these packages...</h1> | |
| 13 | 6 | |
| 14 | -<div class="align-right"><img alt="AppRecommender logo" src="/static/images/AppRecommender-logo.jpg" width="320" /></div> | |
| 7 | +<p>Provide a list of packages or upload a popcon submission file and you'll get | |
| 8 | +a list of suggested packages automatically computed by AppRecommender. You can | |
| 9 | +customize the recommender setup or let it randomly choose a setup for you.</p> | |
| 15 | 10 | |
| 11 | +<p>Please fill in the form that follows the recommendation results. Your | |
| 12 | +feedback is very much appreciated!</p> | |
| 16 | 13 | |
| 14 | +<p>Enjoy it :)</p> | |
| 15 | +</div> | |
| 17 | 16 | |
| 17 | +<!--<div class="align-right"><img alt="AppRecommender logo" | |
| 18 | +src="/static/images/AppRecommender-logo.jpg" width="320" /></div>--> | |
| 18 | 19 | |
| 19 | -<form action="" name="weboptions"> | |
| 20 | +<form action="apprec" enctype="multipart/form-data" method="post" name="weboptions"> | |
| 20 | 21 | <p> |
| 22 | + <label>Upload file:<input type="file" id="pkgs_file" name="pkgs_file" size="35"/></label><br /> | |
| 23 | + <label>Packages list:<textarea rows="2" cols="40" name="pkgs_list" id="pkgs_list"></textarea><label><br /> | |
| 21 | 24 | <label>Profile size: <input type="text" name="profile_size" value="10"></label><br /> |
| 22 | - <label>Recommendations: <input type="text" name="recommendation_size" value="10"></label><br /> | |
| 23 | - <label>Weighting scheme:<br /><input type="radio" name="scheme" value="BM25">BM25</label><br /> | |
| 24 | - <label><input type="radio" name="scheme" value="trad"></label>Traditional<br /> | |
| 25 | - <label>Strategy: <br /><input type="radio" name="strategy" value="content"> Content-based</label><br /> | |
| 26 | - <label><input type="radio" name="strategy" value="collab"> Collaborative</label><br /> | |
| 27 | - <label><input type="radio" name="strategy" value="hybrid"> Hybrid</label><br /> | |
| 25 | + <label>Recommendation size: <input type="text" name="limit" value="10"></label><br /> | |
| 26 | + <label>Weighting scheme:<br /> | |
| 27 | + <input type="radio" name="weight" value="BM25" checked >BM25<br /> | |
| 28 | + <input type="radio" name="weight" value="trad">Traditional<br /> | |
| 29 | + </label> | |
| 30 | + <label>Strategy: <br /> | |
| 31 | + <input type="radio" name="strategy" value="content" checked> Content-based<br /> | |
| 32 | + <input type="radio" name="strategy" value="collab"> Collaborative<br /> | |
| 33 | + <input type="radio" name="strategy" value="hybrid"> Hybrid<br /> | |
| 34 | + </label> | |
| 28 | 35 | <!-- <label>Password:<input type="password" name="pass"></label> --> |
| 29 | - <label style="margin-bottom: 1em; padding-bottom: 1em; border-bottom: 3px silver groove;"><input type="hidden" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid"></label> | |
| 30 | - <label><input type="checkbox" name="ssh" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid"> Tag</label> | |
| 31 | - <label><input type="checkbox" name="iis" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid"> Description</label> | |
| 32 | - <br /><label><input type="checkbox" name="asp" class="DEPENDS ON strategy BEING collab OR strategy BEING hybrid"> Clustering</label><br /> | |
| 33 | - <label>Neighbours: <input type="text" name="neighbours" class="DEPENDS ON strategy BEING collab OR strategy BEING hybrid" value="50"></label><br /> | |
| 34 | - | |
| 35 | - <label>Personal profile: <br /><input type="checkbox" name="user_desktop" checked class="DEPENDS ON strategy BEING hybrid">Desktop</label> | |
| 36 | - <label><input type="checkbox" name="neighbours" class="DEPENDS ON strategy BEING hybrid">Admin</label> | |
| 37 | - <label><input type="checkbox" name="neighbours" class="DEPENDS ON strategy BEING hybrid">Devel</label> | |
| 38 | - <label><input type="checkbox" name="neighbours" class="DEPENDS ON strategy BEING hybrid">Science</label> | |
| 39 | - <label><input type="checkbox" name="neighbours" class="DEPENDS ON strategy BEING hybrid">Arts</label> | |
| 36 | + <!-- <label style="margin-bottom: 1em; padding-bottom: 1em; border-bottom: 3px silver groove;"><input type="hidden" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid"></label> --> | |
| 37 | + </p><p> | |
| 38 | + <label>Content attributes: | |
| 39 | + <input type="radio" name="content" value="tag" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid" checked> tag | |
| 40 | + <input type="radio" name="content" value="desc" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid"> description | |
| 41 | + <input type="radio" name="content" value="full" class="DEPENDS ON strategy BEING content OR strategy BEING hybrid"> both | |
| 42 | + </label><br /> | |
| 43 | + <label>Clustering | |
| 44 | + <input type="radio" name="cluster" value="True" class="DEPENDS ON strategy BEING collab OR strategy BEING hybrid" checked> Yes | |
| 45 | + <input type="radio" name="cluster" value="False" class="DEPENDS ON strategy BEING collab OR strategy BEING hybrid"> No | |
| 46 | + </label><br /> | |
| 47 | + <label>Neighbours: | |
| 48 | + <input type="text" name="neighbours" class="DEPENDS ON strategy BEING collab OR strategy BEING hybrid" value="50"> | |
| 49 | + </label><br /> | |
| 50 | + <label>Personal profile: | |
| 51 | + <input type="checkbox" name="profile_desktop" class="DEPENDS ON strategy BEING hybrid" checked>Desktop | |
| 52 | + <input type="checkbox" name="profile_admin" class="DEPENDS ON strategy BEING hybrid">Admin | |
| 53 | + <input type="checkbox" name="profile_devel" class="DEPENDS ON strategy BEING hybrid">Devel | |
| 54 | + <input type="checkbox" name="profile_science" class="DEPENDS ON strategy BEING hybrid">Science | |
| 55 | + <input type="checkbox" name="profile_arts" class="DEPENDS ON strategy BEING hybrid">Arts | |
| 56 | + </label> | |
| 40 | 57 | </p> |
| 41 | 58 | <input type="submit" /> |
| 42 | 59 | </form> |
| 43 | 60 | |
| 44 | -<!-- | |
| 45 | -<form action="$action" enctype="multipart/form-data" method="$method"> | |
| 46 | -$:form.render() | |
| 47 | -<br /> | |
| 48 | -<input type="submit" /> | |
| 49 | -</form> | |
| 50 | ---> | ... | ... |
src/web/templates/layout.html
| 1 | 1 | $def with (content) |
| 2 | -$ url_base = "http://localhost:8080/" | |
| 2 | +$ url_base = "http://localhost:8080" | |
| 3 | 3 | |
| 4 | 4 | <!DOCTYPE html> |
| 5 | 5 | <html lang="en"><head> |
| ... | ... | @@ -133,7 +133,7 @@ function hideMesg(){ |
| 133 | 133 | <div id="nav"> |
| 134 | 134 | <a href="$url_base">Home</a> |
| 135 | 135 | | |
| 136 | - <a href="http://www.ime.usp.br/~tassia/apprec.html">About</a> | |
| 136 | + <a href="$url_base/about">About</a> | |
| 137 | 137 | | |
| 138 | 138 | <a href="http://github.com/tassia/AppRecommender">Devel</a> |
| 139 | 139 | </div> | ... | ... |