소스 검색

Added a JSON based generic type.

Michael Hope 4 년 전
부모
커밋
edc008866a
5개의 변경된 파일133개의 추가작업 그리고 13개의 파일을 삭제
  1. 91 2
      niche.py
  2. 1 0
      schema.sql
  3. 12 0
      strings.py
  4. 20 11
      templates/user.html
  5. 9 0
      templates/user_edit.html

+ 91 - 2
niche.py 파일 보기

@@ -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')

+ 1 - 0
schema.sql 파일 보기

@@ -144,6 +144,7 @@ CREATE TABLE `1_users` (
144 144
   `bio` mediumtext NOT NULL,
145 145
   `preferences` text,
146 146
   `last_visit` int(14) unsigned NOT NULL default '0',
147
+  `contacts` text,
147 148
   PRIMARY KEY  (`userID`)
148 149
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
149 150
 

+ 12 - 0
strings.py 파일 보기

@@ -27,3 +27,15 @@ admin_needed_to_hide_a_link = 'Admin needed to hide a link'
27 27
 admin_needed_to_close_a_link = 'Admin needed to close a link'
28 28
 bad_username_or_password = "Bad username or password"
29 29
 only_the_user_can_checkout_their_links = "Only the user can checkout their links"
30
+field_realname = "Real name"
31
+field_email = "Email"
32
+field_gravatar_email = "Gravatar email"
33
+field_homepage = "Homepage"
34
+field_team = "Team"
35
+field_location = "Location"
36
+field_twitter = "Twitter"
37
+field_facebook = "Facebook"
38
+field_google_plus_ = "Google+"
39
+field_skype = "Skype"
40
+field_aim = "AIM"
41
+field_bio = "Bio"

+ 20 - 11
templates/user.html 파일 보기

@@ -1,18 +1,27 @@
1 1
 $def with (user)
2 2
 
3
-<h2>$user.username's profile</h2>
4
-
5
-<p>
6
-$if features.gravatar:
7
-  <img src="http://www.gravatar.com/avatar/$model.get_gravatar(user.email)"/>
8
-Name: $user.realname
9
-<p>User ID: $user.userID
3
+$ fields = user.contacts_json
4
+<h2>
5
+$if fields.gravatar_email:
6
+  <img src="http://www.gravatar.com/avatar/$model.get_gravatar(fields.gravatar_email)"/>
7
+$user.username (user #$user.userID)</h2>
8
+$if model.get_active():
9
+    <ul>
10
+    $for field in config.getlist('general', 'user_fields'):
11
+        $ value = fields.get(field)
12
+        $ value = value if value else user.get(field)
13
+        $if value:
14
+            <li>$model.field_text(field): $:linkify(value)
15
+    </ul>
10 16
 $if model.is_user_or_admin(user.userID):
17
+    <a href="user/$user.username/edit">Edit</a>
18
+    <span class="sep"> | </span>
11 19
     <a href="user/$user.username/password">Set password</a>
12
-<p>Homepage: <a href="$user.homepage">$user.homepage</a>
13
-<p>Bio:
14
-$:user.bio
15
-
20
+$else:
21
+    <a href="login">Log in</a> for details.
22
+<p><hr/>
23
+$:render_input(user.bio)
24
+<p><hr/>
16 25
 <p>
17 26
   <a href="user/$user.username/links">$len(user.links) links</a>
18 27
   <span class="sep"> | </span>

+ 9 - 0
templates/user_edit.html 파일 보기

@@ -0,0 +1,9 @@
1
+$def with (user, form)
2
+
3
+<h2>Editing $user.username</h2>
4
+
5
+<form name="main" method="post">
6
+  $:form.render()
7
+  <input type="submit" name="post" value="Update"/>
8
+</form>
9
+<p>Your bio is shown to all visitors.  Other details are only shown to logged in users.