|
|
@@ -11,6 +11,7 @@ import subprocess
|
|
11
|
11
|
import calendar
|
|
12
|
12
|
import gc
|
|
13
|
13
|
import collections
|
|
|
14
|
+import json
|
|
14
|
15
|
|
|
15
|
16
|
import web
|
|
16
|
17
|
import bleach
|
|
|
@@ -38,6 +39,7 @@ urls = (
|
|
38
|
39
|
r'/user/([^/]+)/comments', 'user_comments',
|
|
39
|
40
|
r'/user/([^/]+)/checkout', 'checkout',
|
|
40
|
41
|
r'/user/([^/]+)/password', 'password',
|
|
|
42
|
+ r'/user/([^/]+)/edit', 'user_edit',
|
|
41
|
43
|
r'/login', 'login',
|
|
42
|
44
|
r'/logout', 'logout',
|
|
43
|
45
|
r'/newuser', 'newuser',
|
|
|
@@ -67,6 +69,7 @@ DEFAULTS = [
|
|
67
|
69
|
'extra_tags': '',
|
|
68
|
70
|
'limit': 50,
|
|
69
|
71
|
'server_type': 'dev',
|
|
|
72
|
+ 'user_fields': 'realname email homepage gravatar_email team location twitter facebook google_plus_ skype aim',
|
|
70
|
73
|
}),
|
|
71
|
74
|
( 'groups', {
|
|
72
|
75
|
'admins': '',
|
|
|
@@ -144,6 +147,27 @@ fallbacks = {
|
|
144
|
147
|
'user': web.utils.Storage(username='anonymous'),
|
|
145
|
148
|
}
|
|
146
|
149
|
|
|
|
150
|
+class JSONMapper:
|
|
|
151
|
+ def __init__(self, around, name):
|
|
|
152
|
+ self._around = around
|
|
|
153
|
+ self._name = name
|
|
|
154
|
+
|
|
|
155
|
+ raw = getattr(around, name)
|
|
|
156
|
+ raw = json.loads(raw) if raw else {}
|
|
|
157
|
+ self._values = web.storage(raw)
|
|
|
158
|
+
|
|
|
159
|
+ def __getattr__(self, key):
|
|
|
160
|
+ return getattr(self._values, key, None)
|
|
|
161
|
+
|
|
|
162
|
+ def get(self, key):
|
|
|
163
|
+ return getattr(self._values, key, None)
|
|
|
164
|
+
|
|
|
165
|
+ def set(self, key, value):
|
|
|
166
|
+ if value != getattr(self, key):
|
|
|
167
|
+ self._values[key] = value
|
|
|
168
|
+ encoded = json.dumps(self._values)
|
|
|
169
|
+ db.update('1_users', where='userID = $id', contacts=encoded, vars={'id': self._around.userID})
|
|
|
170
|
+
|
|
147
|
171
|
class AutoMapper:
|
|
148
|
172
|
def __init__(self, type, around):
|
|
149
|
173
|
self._type = type
|
|
|
@@ -167,6 +191,12 @@ class AutoMapper:
|
|
167
|
191
|
|
|
168
|
192
|
raise web.notfound()
|
|
169
|
193
|
|
|
|
194
|
+ if name.endswith('_json'):
|
|
|
195
|
+ field = name[:-5]
|
|
|
196
|
+ mapper = JSONMapper(self, field)
|
|
|
197
|
+ setattr(self, name, mapper)
|
|
|
198
|
+ return mapper
|
|
|
199
|
+
|
|
170
|
200
|
if name.endswith('s'):
|
|
171
|
201
|
singular = name[:-1]
|
|
172
|
202
|
table = '1_%s' % name
|
|
|
@@ -178,6 +208,12 @@ class AutoMapper:
|
|
178
|
208
|
|
|
179
|
209
|
raise AttributeError(name)
|
|
180
|
210
|
|
|
|
211
|
+ def get(self, key):
|
|
|
212
|
+ return getattr(self, key, None)
|
|
|
213
|
+
|
|
|
214
|
+ def has(self, key):
|
|
|
215
|
+ return key in self._around
|
|
|
216
|
+
|
|
181
|
217
|
def ago(self):
|
|
182
|
218
|
return utils.ago(self.timestamp)
|
|
183
|
219
|
|
|
|
@@ -215,6 +251,9 @@ def first(type, column, id):
|
|
215
|
251
|
"""Get the first matching item in the table or raise not found."""
|
|
216
|
252
|
return first_or_none(type, column, id, strict=True)
|
|
217
|
253
|
|
|
|
254
|
+def linkify(text):
|
|
|
255
|
+ return bleach.clean(bleach.linkify(text, parse_email=True))
|
|
|
256
|
+
|
|
218
|
257
|
class Model:
|
|
219
|
258
|
"""Top level helpers. Exposed to scripts."""
|
|
220
|
259
|
def is_admin(self):
|
|
|
@@ -296,6 +335,10 @@ class Model:
|
|
296
|
335
|
def to_rss_date(self, timestamp):
|
|
297
|
336
|
return datetime.datetime.fromtimestamp(timestamp).strftime('%a, %d %b %Y %H:%M:%S +0000')
|
|
298
|
337
|
|
|
|
338
|
+ def field_text(self, name):
|
|
|
339
|
+ return get_string('field_%s' % name)
|
|
|
340
|
+
|
|
|
341
|
+
|
|
299
|
342
|
model = Model()
|
|
300
|
343
|
|
|
301
|
344
|
render_globals = {
|
|
|
@@ -303,6 +346,8 @@ render_globals = {
|
|
303
|
346
|
'config': config,
|
|
304
|
347
|
'features': features,
|
|
305
|
348
|
'version': get_version(),
|
|
|
349
|
+ 'linkify': linkify,
|
|
|
350
|
+ 'render_input': render_input,
|
|
306
|
351
|
}
|
|
307
|
352
|
|
|
308
|
353
|
render = web.template.render(
|
|
|
@@ -446,7 +491,6 @@ class links:
|
|
446
|
491
|
end = datetime.date(year, month + 1, day)
|
|
447
|
492
|
else:
|
|
448
|
493
|
end = start + datetime.timedelta(days=1)
|
|
449
|
|
- print start, end, span
|
|
450
|
494
|
|
|
451
|
495
|
tstart = time.mktime(start.timetuple())
|
|
452
|
496
|
tend = time.mktime(end.timetuple())
|
|
|
@@ -697,7 +741,7 @@ class password:
|
|
697
|
741
|
authenticate()
|
|
698
|
742
|
|
|
699
|
743
|
target = model.get_user_by_name(name)
|
|
700
|
|
- error(_("Permission denied"), not model.is_user_or_admin(target.userID), '/user/%s' % name)
|
|
|
744
|
+ need_user_or_admin(target.userID, _('Permission denied'))
|
|
701
|
745
|
|
|
702
|
746
|
def GET(self, name):
|
|
703
|
747
|
self.authenticate(name)
|
|
|
@@ -716,6 +760,51 @@ class password:
|
|
716
|
760
|
model.inform(_("Password changed"))
|
|
717
|
761
|
redirect('/user/%s' % name)
|
|
718
|
762
|
|
|
|
763
|
+class user_edit:
|
|
|
764
|
+ def make_form(self, user):
|
|
|
765
|
+ names = config.getlist('general', 'user_fields')
|
|
|
766
|
+ values = user.contacts_json
|
|
|
767
|
+
|
|
|
768
|
+ def get(name):
|
|
|
769
|
+ value = values.get(name)
|
|
|
770
|
+ return value if value else user.get(name)
|
|
|
771
|
+
|
|
|
772
|
+ fields = [web.form.Textbox(x, value=get(x), description=get_string('field_%s' % x)) for x in names]
|
|
|
773
|
+ fields.append(web.form.Textarea('bio', rows=10, cols=80, description=get_string('field_bio'), value=get('bio')))
|
|
|
774
|
+ return web.form.Form(*fields)
|
|
|
775
|
+
|
|
|
776
|
+ def get_target(self, name):
|
|
|
777
|
+ authenticate()
|
|
|
778
|
+ target = model.get_user_by_name(name)
|
|
|
779
|
+ need_user_or_admin(target.userID, _('Permission denied'))
|
|
|
780
|
+ return target
|
|
|
781
|
+
|
|
|
782
|
+ def GET(self, name):
|
|
|
783
|
+ target = self.get_target(name)
|
|
|
784
|
+ form = self.make_form(target)
|
|
|
785
|
+ return render.user_edit(target, form)
|
|
|
786
|
+
|
|
|
787
|
+ def POST(self, username):
|
|
|
788
|
+ target = self.get_target(username)
|
|
|
789
|
+ form = self.make_form(target)
|
|
|
790
|
+
|
|
|
791
|
+ if not form.validates():
|
|
|
792
|
+ return render.user_edit(target, form)
|
|
|
793
|
+
|
|
|
794
|
+ names = config.getlist('general', 'user_fields')
|
|
|
795
|
+ values = target.contacts_json
|
|
|
796
|
+
|
|
|
797
|
+ for name in names:
|
|
|
798
|
+ if target.has(name):
|
|
|
799
|
+ db.update('1_users', where='userID = $id', vars={'id': target.userID}, **{name: form[name].value})
|
|
|
800
|
+ else:
|
|
|
801
|
+ values.set(name, form[name].value)
|
|
|
802
|
+
|
|
|
803
|
+ bio = render_input(form.d.bio)
|
|
|
804
|
+ db.update('1_users', bio=bio, where='userID = $id', vars={'id': target.userID})
|
|
|
805
|
+ redirect('/user/%s' % username)
|
|
|
806
|
+
|
|
|
807
|
+
|
|
719
|
808
|
class rss:
|
|
720
|
809
|
def GET(self):
|
|
721
|
810
|
require_feature('rss')
|