Make routing reject non-numeric ids in URLs.
[zzz-spline-users.git] / splinext / users / model / __init__.py
1 # encoding: utf8
2 import colorsys
3 from math import sin, pi
4 import random
5
6 from sqlalchemy import Column, ForeignKey
7 from sqlalchemy.orm import relation
8 from sqlalchemy.types import Integer, Unicode
9
10 from spline.model.meta import TableBase
11
12 class AnonymousUser(object):
13 """Fake user object, for when the user isn't actually logged in.
14
15 Tests as false and tries to respond to method calls the expected way.
16 """
17
18 def __nonzero__(self):
19 return False
20 def __bool__(self):
21 return False
22
23 def can(self, action):
24 # XXX if viewing is ever a permission, this should probably change.
25 return False
26
27
28 class User(TableBase):
29 __tablename__ = 'users'
30 id = Column(Integer, primary_key=True)
31 name = Column(Unicode(length=20), nullable=False)
32 unique_identifier = Column(Unicode(length=32), nullable=False)
33
34 def __init__(self, *args, **kwargs):
35 # Generate a unique hash if one isn't provided (which it shouldn't be)
36 if 'unique_identifier' not in kwargs:
37 ident = u''.join(random.choice(u'0123456789abcdef')
38 for _ in range(32))
39 kwargs['unique_identifier'] = ident
40
41 super(User, self).__init__(*args, **kwargs)
42
43 def can(self, action):
44 # XXX this is probably not desired.
45 return True
46
47 @property
48 def unique_colors(self):
49 """Returns a list of (width, '#rrggbb') tuples that semi-uniquely
50 identify this user.
51 """
52 width_blob, colors_blob = self.unique_identifier[0:8], \
53 self.unique_identifier[8:32]
54
55 widths = []
56 for i in range(4):
57 width_hex = width_blob[i*2:i*2+2]
58 widths.append(int(width_hex, 16))
59 total_width = sum(widths)
60
61 ret = []
62 last_hue = None
63 for i in range(4):
64 raw_hue = int(colors_blob[i*6:i*6+2], 16) / 256.0
65 if last_hue:
66 # Make adjacent hues relatively close together, to avoid green
67 # + purple sorts of clashes.
68 # Minimum distance is 0.1; maximum is 0.35. Leaves half the
69 # spectrum available for any given color.
70 # Change 0.0–0.1 to -0.35–-0.1, 0.1–0.35
71 hue_offset = raw_hue * 0.5 - 0.25
72 if raw_hue < 0:
73 raw_hue -= 0.1
74 else:
75 raw_hue += 0.1
76
77 h = last_hue + raw_hue
78 else:
79 h = raw_hue
80 last_hue = h
81
82 l = int(colors_blob[i*6+2:i*6+4], 16) / 256.0
83 s = int(colors_blob[i*6+4:i*6+6], 16) / 256.0
84
85 # Secondary colors are extremely biased against when picking
86 # randomly from the hue spectrum.
87 # To alleviate this, try to bias hue towards secondary colors.
88 # This adjustment is based purely on experimentation; sin() works
89 # well because hue is periodic, * 6 means each period is 1/3 the
90 # hue spectrum, and the final / 24 is eyeballed
91 h += sin(h * pi * 6) / 24
92
93 # Cap lightness to 0.4 to 0.95, so it's not too close to white or
94 # black
95 l = l * 0.6 + 0.3
96
97 # Cap saturation to 0.5 to 1.0, so the color isn't too gray
98 s = s * 0.6 + 0.3
99
100 r, g, b = colorsys.hls_to_rgb(h, l, s)
101 color = "#{0:02x}{1:02x}{2:02x}".format(
102 int(r * 256),
103 int(g * 256),
104 int(b * 256),
105 )
106
107 ret.append((1.0 * widths[i] / total_width, color))
108
109 return ret
110
111
112 class OpenID(TableBase):
113 __tablename__ = 'openid'
114 openid = Column(Unicode(length=255), primary_key=True)
115 user_id = Column(Integer, ForeignKey('users.id'))
116 user = relation(User, lazy=False, backref='openids')