-
Notifications
You must be signed in to change notification settings - Fork 6
/
model.py
159 lines (131 loc) · 6.12 KB
/
model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""Datastore entity models"""
from collections import OrderedDict
import util
from google.appengine.ext import ndb
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
class User(ndb.Model):
first_name = ndb.TextProperty()
last_name = ndb.TextProperty()
username = ndb.StringProperty(indexed=False)
created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True, indexed=False)
@classmethod
def populate_by_id(cls, id, **kwargs): # pylint: disable=redefined-builtin, invalid-name
# ignore warnings due to argument named "id" for consistency with similar ndb methods
entity = cls.get_by_id(id) or cls(id=id)
entity.populate(**kwargs)
entity.put()
def get_description(self):
output = u'{}'.format(self.first_name)
if self.last_name:
output += u' {}'.format(self.last_name)
if self.username:
output += u' (@{})'.format(self.username)
return output
class Respondent(User):
username = ndb.StringProperty(indexed=True)
updated = ndb.DateTimeProperty(auto_now=True, indexed=True)
class Poll(ndb.Model):
admin_uid = ndb.StringProperty()
title = ndb.TextProperty()
title_short = ndb.StringProperty()
active = ndb.BooleanProperty(default=True)
multi = ndb.BooleanProperty(default=True, indexed=False)
options = ndb.PickleProperty(repeated=True)
created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True, indexed=False)
@classmethod
def new(cls, admin_uid, title):
title_short = util.uslice(title, 0, 512).lower()
return cls(admin_uid=admin_uid, title=title, title_short=title_short)
@staticmethod
@ndb.transactional
def toggle(poll_id, opt_id, uid, user_profile):
poll = Poll.get_by_id(poll_id)
if not poll:
return None, 'Sorry, this poll has been deleted'
if opt_id >= len(poll.options):
return None, 'Sorry, that\'s an invalid option'
status = poll.options[opt_id].toggle(uid, user_profile)
poll.put()
return poll, status
def get_friendly_id(self):
return util.uslice(self.title, 0, 512)
def generate_options_summary(self):
return u' / '.join([option.title for option in self.options])
def generate_respondents_summary(self):
all_uids_by_option = [option.people.keys() for option in self.options]
all_uids = util.flatten(all_uids_by_option)
num_respondents = len(set(all_uids))
if num_respondents == 0:
output = 'Nobody responded'
elif num_respondents == 1:
output = '1 person responded'
else:
output = '{} people responded'.format(num_respondents)
return output
def generate_poll_summary_with_link(self):
short_bold_title = util.make_html_bold(util.uslice(self.title, 0, 65))
respondents_summary = self.generate_respondents_summary()
link = '/view_{}'.format(self.key.id())
return u'{} {}.\n{}'.format(short_bold_title, respondents_summary, link)
def render_text(self):
header = [util.make_html_bold_first_line(self.title)]
body = [option.render_text() for option in self.options]
footer = [util.emoji_people_unicode() + ' ' + self.generate_respondents_summary()]
return u'\n\n'.join(header + body + footer)
def render_html(self):
from datetime import timedelta
user = User.get_by_id(int(self.admin_uid))
user_description = user.get_description() if user else 'unknown ({})'.format(self.admin_uid)
timestamp = (self.created + timedelta(hours=8)).strftime('%a, %d %b \'%y, %H:%M:%S')
details = u' <small>by {} on {}</small>'.format(user_description, timestamp)
text = self.render_text()
idx = text.find('\n')
text = (text[:idx] + details + text[idx:])
return '<p>' + text.replace('\n', '<br>\n') + '</p>'
def build_vote_buttons(self, admin=False):
poll_id = self.key.id()
buttons = []
for i, option in enumerate(self.options):
data = '{} {}'.format(poll_id, i)
button = InlineKeyboardButton(option.title, callback_data=data)
buttons.append([button])
if admin:
back_data = '{} back'.format(poll_id)
back_button = InlineKeyboardButton('Back', callback_data=back_data)
buttons.append([back_button])
return InlineKeyboardMarkup(buttons).to_dict()
def build_admin_buttons(self):
poll_id = self.key.id()
insert_key = self.get_friendly_id().encode('utf-8')
publish_button = InlineKeyboardButton('Publish poll', switch_inline_query=insert_key)
refresh_data = '{} refresh'.format(poll_id)
refresh_button = InlineKeyboardButton('Update results', callback_data=refresh_data)
vote_data = '{} vote'.format(poll_id)
vote_button = InlineKeyboardButton('Vote', callback_data=vote_data)
delete_data = '{} delete'.format(poll_id)
delete_button = InlineKeyboardButton('Delete', callback_data=delete_data)
buttons = [[publish_button], [refresh_button], [vote_button, delete_button]]
return InlineKeyboardMarkup(buttons).to_dict()
class Option(object):
def __init__(self, title, people=OrderedDict()):
self.title = title
self.people = people
def toggle(self, uid, user_profile):
uid = str(uid)
if self.people.get(uid):
self.people.pop(uid, None)
action = u'removed from'
else:
self.people[uid] = user_profile['first_name'], user_profile['last_name']
action = u'added to'
return u'Your name was {} {}!'.format(action, self.title)
def render_text(self):
title = util.make_html_bold(self.title)
if self.people:
title += u' ({}{})'.format(len(self.people), util.emoji_people_unicode())
name_list = util.strip_html_symbols(self.generate_name_list())
return title + '\n' + name_list
def generate_name_list(self):
return '\n'.join([first_name for first_name, _ in self.people.values()])