utils.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # -*- coding: utf-8 -*-
  2. """EventMan(ager) utils
  3. Miscellaneous utilities.
  4. Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>
  5. RaspiBO <info@raspibo.org>
  6. Licensed under the Apache License, Version 2.0 (the "License");
  7. you may not use this file except in compliance with the License.
  8. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. """
  15. import csv
  16. import copy
  17. import json
  18. import string
  19. import random
  20. import hashlib
  21. import datetime
  22. import io
  23. from bson.objectid import ObjectId
  24. def csvParse(csvStr, remap=None, merge=None):
  25. """Parse a CSV file, optionally renaming the columns and merging other information.
  26. :param csvStr: the CSV to parse, as a string
  27. :type csvStr: str
  28. :param remap: a dictionary used to rename the columns
  29. :type remap: dict
  30. :param merge: merge these information into each line
  31. :type merge: dict
  32. :returns: tuple with a dict of total and valid lines and the data
  33. :rtype: tuple
  34. """
  35. if isinstance(csvStr, bytes):
  36. csvStr = csvStr.decode('utf-8')
  37. fd = io.StringIO(csvStr)
  38. reader = csv.reader(fd)
  39. remap = remap or {}
  40. merge = merge or {}
  41. fields = 0
  42. reply = dict(total=0, valid=0)
  43. results = []
  44. try:
  45. headers = next(reader)
  46. fields = len(headers)
  47. except (StopIteration, csv.Error):
  48. return reply, {}
  49. for idx, header in enumerate(headers):
  50. if header in remap:
  51. headers[idx] = remap[header]
  52. else:
  53. headers[idx] = header.lower().replace(' ', '_').replace('.', '_')
  54. try:
  55. for row in reader:
  56. try:
  57. reply['total'] += 1
  58. if len(row) != fields:
  59. continue
  60. values = dict(zip(headers, row))
  61. values.update(merge)
  62. results.append(values)
  63. reply['valid'] += 1
  64. except csv.Error:
  65. continue
  66. except csv.Error:
  67. pass
  68. fd.close()
  69. return reply, results
  70. def hash_password(password, salt=None):
  71. """Hash a password.
  72. :param password: the cleartext password
  73. :type password: str
  74. :param salt: the optional salt (randomly generated, if None)
  75. :type salt: str
  76. :returns: the hashed password
  77. :rtype: str"""
  78. if salt is None:
  79. salt_pool = string.ascii_letters + string.digits
  80. salt = ''.join(random.choice(salt_pool) for x in range(32))
  81. pwd = '%s%s' % (salt, password)
  82. hash_ = hashlib.sha512(pwd.encode('utf-8'))
  83. return '$%s$%s' % (salt, hash_.hexdigest())
  84. has_eventbrite_sdk = False
  85. try:
  86. from eventbrite import Eventbrite
  87. has_eventbrite_sdk = True
  88. except ImportError:
  89. Eventbrite = object
  90. class CustomEventbrite(Eventbrite):
  91. """Custom methods to override official SDK limitations; code take from Yuval Hager; see:
  92. https://github.com/eventbrite/eventbrite-sdk-python/issues/18
  93. This class should be removed onces the official SDK supports pagination.
  94. """
  95. def custom_get_event_attendees(self, event_id, status=None, changed_since=None, page=1):
  96. data = {}
  97. if status:
  98. data['status'] = status
  99. if changed_since:
  100. data['changed_since'] = changed_since
  101. data['page'] = page
  102. return self.get("/events/{0}/attendees/".format(event_id), data=data)
  103. def get_all_event_attendees(self, event_id, status=None, changed_since=None):
  104. page = 1
  105. attendees = []
  106. while True:
  107. r = self.custom_get_event_attendees(event_id, status, changed_since, page=page)
  108. attendees.extend(r['attendees'])
  109. if r['pagination']['page_count'] <= page:
  110. break
  111. page += 1
  112. return attendees
  113. KEYS_REMAP = {
  114. ('capacity', 'number_of_tickets'),
  115. ('changed', 'updated_at', lambda x: x.replace('T', ' ').replace('Z', '')),
  116. ('created', 'created_at', lambda x: x.replace('T', ' ').replace('Z', '')),
  117. ('description', 'description', lambda x: x.get('text', ''))
  118. }
  119. def reworkObj(obj, kind='event'):
  120. """Rename and fix some key in the data from the Eventbrite API."""
  121. for remap in KEYS_REMAP:
  122. transf = lambda x: x
  123. if len(remap) == 2:
  124. old, new = remap
  125. else:
  126. old, new, transf = remap
  127. if old in obj:
  128. obj[new] = transf(obj[old])
  129. if old != new:
  130. del obj[old]
  131. if 'id' in obj:
  132. del obj['id']
  133. if kind == 'event':
  134. if 'name' in obj:
  135. obj['title'] = obj.get('name', {}).get('text') or ''
  136. del obj['name']
  137. if 'start' in obj:
  138. t = obj['start'].get('utc') or ''
  139. obj['begin_date'] = obj['begin_time'] = t.replace('T', ' ').replace('Z', '')
  140. if 'end' in obj:
  141. t = obj['end'].get('utc') or ''
  142. obj['end_date'] = obj['end_time'] = t.replace('T', ' ').replace('Z', '')
  143. else:
  144. profile = obj.get('profile') or {}
  145. complete_name = profile['name']
  146. obj['surname'] = profile.get('last_name') or ''
  147. obj['name'] = profile.get('first_name') or ''
  148. if not (obj['surname'] and obj['name']):
  149. obj['surname'] = complete_name
  150. for key in 'name', 'first_name', 'last_name':
  151. if key in profile:
  152. del profile[key]
  153. obj.update(profile)
  154. if 'profile' in obj:
  155. del obj['profile']
  156. obj['email'] = obj.get('email') or ''
  157. if 'job_title' in obj:
  158. obj['job title'] = obj['job_title']
  159. del obj['job_title']
  160. if 'costs' in obj:
  161. del obj['costs']
  162. return obj
  163. def expandBarcodes(attendees):
  164. """Generate an attendee for each barcode in the Eventbrite API data."""
  165. for attendee in attendees:
  166. if 'id' in attendee:
  167. del attendee['id']
  168. barcodes = attendee.get('barcodes') or []
  169. if not barcodes:
  170. yield attendee
  171. barcodes = [b.get('barcode') for b in barcodes if b.get('barcode')]
  172. if 'barcodes' in attendee:
  173. del attendee['barcodes']
  174. for code in barcodes:
  175. att_copy = copy.deepcopy(attendee)
  176. att_copy['order_nr'] = code
  177. yield att_copy
  178. def ebAPIFetch(oauthToken, eventID):
  179. """Fetch an event, complete with all attendees using Eventbrite API.
  180. :param oauthToken: Eventbrite API key
  181. :type oauthToken: str
  182. :param eventID: Eventbrite ID of the even to be fetched
  183. :type eventID: str
  184. :returns: information about the event and all its attendees
  185. :rtype: dict
  186. """
  187. if not has_eventbrite_sdk:
  188. raise Exception('unable to import eventbrite module')
  189. eb = CustomEventbrite(oauthToken)
  190. event = eb.get_event(eventID)
  191. eb_attendees = eb.get_all_event_attendees(eventID)
  192. event = reworkObj(event, kind='event')
  193. attendees = []
  194. for eb_attendee in eb_attendees:
  195. reworkObj(eb_attendee, kind='attendee')
  196. attendees.append(eb_attendee)
  197. event['eb_event_id'] = eventID
  198. attendees = list(expandBarcodes(attendees))
  199. info = {'event': event, 'attendees': attendees}
  200. return info
  201. class ImprovedEncoder(json.JSONEncoder):
  202. """Enhance the default JSON encoder to serialize datetime and ObjectId instances."""
  203. def default(self, o):
  204. if isinstance(o, bytes):
  205. try:
  206. return o.decode('utf-8')
  207. except:
  208. pass
  209. elif isinstance(o, (datetime.datetime, datetime.date,
  210. datetime.time, datetime.timedelta, ObjectId)):
  211. try:
  212. return str(o)
  213. except Exception:
  214. pass
  215. elif isinstance(o, set):
  216. return list(o)
  217. return json.JSONEncoder.default(self, o)
  218. # Inject our class as the default encoder.
  219. json._default_encoder = ImprovedEncoder()