Guest User

Untitled

a guest
Mar 8th, 2016
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.59 KB | None | 0 0
  1. import uuid
  2. import wtforms_json
  3. from sqlalchemy import not_
  4. from sqlalchemy.dialects.postgresql import UUID
  5. from wtforms import Form
  6. from wtforms.fields import FormField, FieldList
  7. from wtforms.validators import Length
  8.  
  9. from flask import current_app as app
  10. from flask import request, json, jsonify, abort
  11. from flask.ext.sqlalchemy import SQLAlchemy
  12.  
  13.  
  14. db = SQLAlchemy(app)
  15. wtforms_json.init()
  16.  
  17.  
  18. class Model(db.Model):
  19. """Base SQLAlchemy Model for automatic serialization and
  20. deserialization of columns and nested relationships.
  21.  
  22. Usage::
  23.  
  24. >>> class User(Model):
  25. >>> id = db.Column(db.Integer(), primary_key=True)
  26. >>> email = db.Column(db.String(), index=True)
  27. >>> name = db.Column(db.String())
  28. >>> password = db.Column(db.String())
  29. >>> posts = db.relationship('Post', backref='user', lazy='dynamic')
  30. >>> ...
  31. >>> default_fields = ['email', 'name']
  32. >>> hidden_fields = ['password']
  33. >>> readonly_fields = ['email', 'password']
  34. >>>
  35. >>> class Post(Model):
  36. >>> id = db.Column(db.Integer(), primary_key=True)
  37. >>> user_id = db.Column(db.String(), db.ForeignKey('user.id'), nullable=False)
  38. >>> title = db.Column(db.String())
  39. >>> ...
  40. >>> default_fields = ['title']
  41. >>> readonly_fields = ['user_id']
  42. >>>
  43. >>> model = User(email='john@localhost')
  44. >>> db.session.add(model)
  45. >>> db.session.commit()
  46. >>>
  47. >>> # update name and create a new post
  48. >>> validated_input = {'name': 'John', 'posts': [{'title':'My First Post'}]}
  49. >>> model.set_columns(**validated_input)
  50. >>> db.session.commit()
  51. >>>
  52. >>> print(model.to_dict(show=['password', 'posts']))
  53. >>> {u'email': u'john@localhost', u'posts': [{u'id': 1, u'title': u'My First Post'}], u'name': u'John', u'id': 1}
  54. """
  55. __abstract__ = True
  56.  
  57. # Stores changes made to this model's attributes. Can be retrieved
  58. # with model.changes
  59. _changes = {}
  60.  
  61. def __init__(self, **kwargs):
  62. kwargs['_force'] = True
  63. self._set_columns(**kwargs)
  64.  
  65. def _set_columns(self, **kwargs):
  66. force = kwargs.get('_force')
  67.  
  68. readonly = []
  69. if hasattr(self, 'readonly_fields'):
  70. readonly = self.readonly_fields
  71. if hasattr(self, 'hidden_fields'):
  72. readonly += self.hidden_fields
  73.  
  74. readonly += [
  75. 'id',
  76. 'created',
  77. 'updated',
  78. 'modified',
  79. 'created_at',
  80. 'updated_at',
  81. 'modified_at',
  82. ]
  83.  
  84. changes = {}
  85.  
  86. columns = self.__table__.columns.keys()
  87. relationships = self.__mapper__.relationships.keys()
  88.  
  89. for key in columns:
  90. allowed = True if force or key not in readonly else False
  91. exists = True if key in kwargs else False
  92. if allowed and exists:
  93. val = getattr(self, key)
  94. if val != kwargs[key]:
  95. changes[key] = {'old': val, 'new': kwargs[key]}
  96. setattr(self, key, kwargs[key])
  97.  
  98. for rel in relationships:
  99. allowed = True if force or rel not in readonly else False
  100. exists = True if rel in kwargs else False
  101. if allowed and exists:
  102. is_list = self.__mapper__.relationships[rel].uselist
  103. if is_list:
  104. valid_ids = []
  105. query = getattr(self, rel)
  106. cls = self.__mapper__.relationships[rel].argument()
  107. for item in kwargs[rel]:
  108. if 'id' in item and query.filter_by(id=item['id']).limit(1).count() == 1:
  109. obj = cls.query.filter_by(id=item['id']).first()
  110. col_changes = obj.set_columns(**item)
  111. if col_changes:
  112. col_changes['id'] = str(item['id'])
  113. if rel in changes:
  114. changes[rel].append(col_changes)
  115. else:
  116. changes.update({rel: [col_changes]})
  117. valid_ids.append(str(item['id']))
  118. else:
  119. col = cls()
  120. col_changes = col.set_columns(**item)
  121. query.append(col)
  122. db.session.flush()
  123. if col_changes:
  124. col_changes['id'] = str(col.id)
  125. if rel in changes:
  126. changes[rel].append(col_changes)
  127. else:
  128. changes.update({rel: [col_changes]})
  129. valid_ids.append(str(col.id))
  130.  
  131. # delete related rows that were not in kwargs[rel]
  132. for item in query.filter(not_(cls.id.in_(valid_ids))).all():
  133. col_changes = {
  134. 'id': str(item.id),
  135. 'deleted': True,
  136. }
  137. if rel in changes:
  138. changes[rel].append(col_changes)
  139. else:
  140. changes.update({rel: [col_changes]})
  141. db.session.delete(item)
  142.  
  143. else:
  144. val = getattr(self, rel)
  145. if self.__mapper__.relationships[rel].query_class is not None:
  146. if val is not None:
  147. col_changes = val.set_columns(**kwargs[rel])
  148. if col_changes:
  149. changes.update({rel: col_changes})
  150. else:
  151. if val != kwargs[rel]:
  152. setattr(self, rel, kwargs[rel])
  153. changes[rel] = {'old': val, 'new': kwargs[rel]}
  154.  
  155. return changes
  156.  
  157. def set_columns(self, **kwargs):
  158. self._changes = self._set_columns(**kwargs)
  159. if 'modified' in self.__table__.columns:
  160. self.modified = datetime.utcnow()
  161. if 'updated' in self.__table__.columns:
  162. self.updated = datetime.utcnow()
  163. if 'modified_at' in self.__table__.columns:
  164. self.modified_at = datetime.utcnow()
  165. if 'updated_at' in self.__table__.columns:
  166. self.updated_at = datetime.utcnow()
  167. return self._changes
  168.  
  169. @property
  170. def changes(self):
  171. return self._changes
  172.  
  173. def reset_changes(self):
  174. self._changes = {}
  175.  
  176. def to_dict(self, show=None, hide=None, path=None, show_all=None):
  177. """ Return a dictionary representation of this model.
  178. """
  179.  
  180. if not show:
  181. show = []
  182. if not hide:
  183. hide = []
  184. hidden = []
  185. if hasattr(self, 'hidden_fields'):
  186. hidden = self.hidden_fields
  187. default = []
  188. if hasattr(self, 'default_fields'):
  189. default = self.default_fields
  190.  
  191. ret_data = {}
  192.  
  193. if not path:
  194. path = self.__tablename__.lower()
  195. def prepend_path(item):
  196. item = item.lower()
  197. if item.split('.', 1)[0] == path:
  198. return item
  199. if len(item) == 0:
  200. return item
  201. if item[0] != '.':
  202. item = '.%s' % item
  203. item = '%s%s' % (path, item)
  204. return item
  205. show[:] = [prepend_path(x) for x in show]
  206. hide[:] = [prepend_path(x) for x in hide]
  207.  
  208. columns = self.__table__.columns.keys()
  209. relationships = self.__mapper__.relationships.keys()
  210. properties = dir(self)
  211.  
  212. for key in columns:
  213. check = '%s.%s' % (path, key)
  214. if check in hide or key in hidden:
  215. continue
  216. if show_all or key is 'id' or check in show or key in default:
  217. ret_data[key] = getattr(self, key)
  218.  
  219. for key in relationships:
  220. check = '%s.%s' % (path, key)
  221. if check in hide or key in hidden:
  222. continue
  223. if show_all or check in show or key in default:
  224. hide.append(check)
  225. is_list = self.__mapper__.relationships[key].uselist
  226. if is_list:
  227. ret_data[key] = []
  228. for item in getattr(self, key):
  229. ret_data[key].append(item.to_dict(
  230. show=show,
  231. hide=hide,
  232. path=('%s.%s' % (path, key.lower())),
  233. show_all=show_all,
  234. ))
  235. else:
  236. if self.__mapper__.relationships[key].query_class is not None:
  237. ret_data[key] = getattr(self, key).to_dict(
  238. show=show,
  239. hide=hide,
  240. path=('%s.%s' % (path, key.lower())),
  241. show_all=show_all,
  242. )
  243. else:
  244. ret_data[key] = getattr(self, key)
  245.  
  246. for key in list(set(properties) - set(columns) - set(relationships)):
  247. if key.startswith('_'):
  248. continue
  249. check = '%s.%s' % (path, key)
  250. if check in hide or key in hidden:
  251. continue
  252. if show_all or check in show or key in default:
  253. val = getattr(self, key)
  254. try:
  255. ret_data[key] = json.loads(json.dumps(val))
  256. except:
  257. pass
  258.  
  259. return ret_data
  260.  
  261.  
  262. class User(Model):
  263. id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
  264. first_name = db.Column(db.String(120))
  265. last_name = db.Column(db.String(120))
  266. posts = db.relationship('Post', backref='user', lazy='dynamic')
  267.  
  268.  
  269. class Post(Model):
  270. id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
  271. user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('user.id'), nullable=False)
  272. title = db.Column(db.String(200))
  273. text = db.Column(db.String())
  274.  
  275.  
  276. class PostForm(Form):
  277. title = StringField(validators=[Length(max=200)])
  278. text = StringField()
  279.  
  280.  
  281. class UserForm(Form):
  282. first_name = StringField(validators=[Length(max=120)])
  283. last_name = StringField(validators=[Length(max=120)])
  284. posts = FieldList(FormField(PostForm))
  285.  
  286.  
  287. def requested_columns(request):
  288. show = request.args.get('show', None)
  289. if not show:
  290. return []
  291. return show.split(',')
  292.  
  293.  
  294. @app.route('/users/<string:user_id>', methods=['GET'])
  295. def read_user(user_id):
  296.  
  297. # get user from database
  298. user = User.query.filter_by(id=user_id).first()
  299. if user is None:
  300. abort(404)
  301.  
  302. # return user as json
  303. show = requested_columns(request)
  304. return jsonify(data=user.to_dict(show=show))
  305.  
  306.  
  307. @app.route('/users/<string:user_id>', methods=['PUT'])
  308. def update_user(user_id):
  309.  
  310. # get user from database
  311. user = User.query.filter_by(id=user_id).first()
  312. if user is None:
  313. abort(404)
  314.  
  315. input_data = request.get_json(force=True)
  316. if not isinstance(input_data, dict):
  317. return jsonify(error='Request data must be a JSON Object'), 400
  318.  
  319. # validate json user input using WTForms-JSON
  320. form = UserForm.from_json(input_data)
  321. if not form.validate():
  322. return jsonify(errors=form.errors), 400
  323.  
  324. # update user in database
  325. user.set_columns(**form.patch_data)
  326. db.session.commit()
  327.  
  328. # return user as json
  329. show = requested_columns(request)
  330. return jsonify(data=user.to_dict(show=show))
Add Comment
Please, Sign In to add comment