class ResourceMixin(object): # Keep track when records are created and updated. created_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow) updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow) def save(self): """ Save a model instance. """ db.session.add(self) db.session.commit() return self def delete(self): """ Delete a model instance. """ db.session.delete(self) return db.session.commit() def __str__(self): """ Create a human readable version of a class instance. """ obj_id = hex(id(self)) columns = self.__table__.c.keys() values = ', '.join("%s=%r" % (n, getattr(self, n)) for n in columns) return '<%s %s(%s)>' % (obj_id, self.__class__.__name__, values)
class Subscription(ResourceMixin, db.Model): __tablename__ = 'subscriptions' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id', onupdate='CASCADE', ondelete='CASCADE'), index=True, nullable=False) plan = db.Column(db.String(128))
class Category(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(30), unique=True) project_emails = db.Column(db.String(256)) posts = db.relationship('Post', back_populates='category') def delete(self): default_category = Category.query.get(1) posts = self.posts[:] for post in posts: post.category = default_category db.session.delete(self) db.session.commit()
class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) author = db.Column(db.String(30)) email = db.Column(db.String(254)) site = db.Column(db.String(255)) body = db.Column(db.Text) reviewed = db.Column(db.Boolean, default=False) timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) post_id = db.Column(db.Integer, db.ForeignKey('post.id')) post = db.relationship('Post', back_populates='comments')
class Vote(db.Model): __tablename__ = 'votes' id = db.Column(db.Integer, primary_key=True) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) post_id = db.Column(db.Integer, db.ForeignKey('posts.id')) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) def to_dict(self): return { 'id': self.id, 'body': self.body, } def from_dict(self, data, author=None, new_post=False): for field in ['body']: if field in data: setattr(self, field, data[field]) if new_post and author: self.author = author
class Admin(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True) password_hash = db.Column(db.String(128)) category_id = db.Column(db.Integer, db.ForeignKey('category.id')) category = db.relationship('Category', back_populates='users') def set_password(self, password): self.password_hash = generate_password_hash(password) def validate_password(self, password): return check_password_hash(self.password_hash, password) def can(self, role): return True if self.role >= role else False def generate_reset_token(self, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'reset': self.id})
class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) author_id = db.Column(db.Integer, db.ForeignKey('users.id')) votes = db.relationship('Vote', backref='post', lazy='dynamic') def to_dict(self): return { 'id': self.id, 'body': self.body, } def from_dict(self, data, author=None, new_post=False): for field in ['body']: if field in data: setattr(self, field, data[field]) if new_post and author: self.author = author
class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) author_id = db.Column(db.Integer, db.ForeignKey('users.id')) def to_json(self): json_post = { 'url': url_for('api.get_post', id=self.id), 'body': self.body, 'timestamp': self.timestamp, 'author_url': url_for('api.get_user', id=self.author_id), } return json_post @staticmethod def from_json(json_post): body = json_post.get('body') if body is None or body == '': raise ValidationError('post does not have a body') return Post(body=body)
class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(60), unique=True) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) category_id = db.Column(db.Integer, db.ForeignKey('category.id')) category = db.relationship('Category', back_populates='posts') comments = db.relationship('Comment', back_populates='post', cascade='all, delete-orphan') post_author = db.Column(db.String(60))
class User(UserMixin, ResourceMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) confirmed = db.Column(db.Boolean, default=False) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) name = db.Column(db.String(64)) location = db.Column(db.String(64)) about_me = db.Column(db.Text()) member_since = db.Column(db.DateTime(), default=datetime.utcnow) last_seen = db.Column(db.DateTime(), default=datetime.utcnow) posts = db.relationship('Post', backref='author', lazy='dynamic') # billing fields payment_id = db.Column(db.String(128), index=True) cancelled_subscription_on = db.Column(db.DateTime()) subscription = db.relationship('Subscription', backref='users', passive_deletes=True) invoices = db.relationship('Invoice', backref='users', passive_deletes=True) def __init__(self, **kwargs): """Assign default role upon registration. FLASK ADMIN Role assigned to ADMIN Emails """ super().__init__(**kwargs) if self.role is None: if self.email == current_app.config['DEMO_ADMIN']: self.role = Role.query.filter_by(name='Administrator').first() if self.role is None: self.role = Role.query.filter_by(default=True).first() @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password) def generate_confirmation_token(self, expiration=360): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'confirm': self.id}).decode('utf-8') def confirm(self, token): # note this doesn't commit the change, which is called in the views s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return False if data.get('confirm') != self.id: return False self.confirmed = True db.session.add(self) return True def generate_reset_token(self, expiration=360): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'reset': self.id}).decode('utf-8') @staticmethod def reset_password(token, new_password): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return False user = User.query.get(data.get('reset')) if user is None: return False user.password = new_password db.session.add(user) return True def generate_email_change_token(self, new_email, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({ 'change_email': self.id, 'new_email': new_email }).decode('utf-8') def change_email(self, token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return False if data.get('change_email') != self.id: return False new_email = data.get('new_email') if new_email is None: return False if self.query.filter_by(email=new_email).first() is not None: return False self.email = new_email db.session.add(self) return True def can(self, perm): return self.role is not None and self.role.has_permission(perm) def is_administrator(self): return self.can(Permission.ADMIN) def ping(self): self.last_seen = datetime.utcnow() db.session.add(self) db.session.commit() def generate_auth_token(self, expiration): """Generate token - for API route token based authentication """ s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'id': self.id}).decode('utf-8') @staticmethod def verify_auth_token(token): """Verify token - for API route token based authentication """ s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.query.get(data['id']) @staticmethod def parse_event_checkout_completed(parsed_info): """Parse checkout completed event and link customer to user """ user = User.query.get(parsed_info['user_id']) if not user: return None user.payment_id = parsed_info['stripe_customer_id'] user.cancelled_subscription_on = None user.save() def to_json(self): json_user = { 'url': url_for('api.get_user', id=self.id), 'username': self.username, 'member_since': self.member_since, 'last_seen': self.last_seen, 'posts_url': url_for('api.get_user_posts', id=self.id), 'post_count': self.posts.count(), } return json_user def __repr__(self): return '<User {}>'.format(self.username)
class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') votes = db.relationship('Vote', backref='author', lazy='dynamic') def __repr__(self): return '<User {}>'.format(self.username) @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password) def to_dict(self): return { 'id': self.id, 'username': self.username, 'email': self.email, } def from_dict(self, data, new_user=False): for field in ['username', 'email']: if field in data: setattr(self, field, data[field]) if new_user and 'password' in data: self.password = data['password'] def encode_auth_token(self, user_id): try: payload = { 'exp': dt.datetime.utcnow() + dt.timedelta(days=0, seconds=60 * 60 * 6), 'iat': dt.datetime.utcnow(), 'user_id': user_id } return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256') except Exception as e: return e @staticmethod def decode_auth_token(request): auth_header = request.headers.get('Authorization') if auth_header: auth_token = auth_header.split(" ")[1] else: auth_token = '' try: payload = jwt.decode(auth_token, current_app.config['SECRET_KEY']) return payload['user_id'] except jwt.ExpiredSignatureError: return 'Signature expired. Please log in again.' except jwt.InvalidTokenError: return 'Invalid token. Please log in again.'
class Invoice(ResourceMixin, db.Model): __tablename__ = 'invoices' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id', onupdate='CASCADE', ondelete='CASCADE'), index=True, nullable=False) plan = db.Column(db.String(128)) receipt_number = db.Column(db.String(128)) description = db.Column(db.String(128)) period_start_on = db.Column(db.Date) period_end_on = db.Column(db.Date) currency = db.Column(db.String(8)) tax = db.Column(db.Integer) tax_percent = db.Column(db.Float()) total = db.Column(db.Integer) # denormalizes the card details brand = db.Column(db.String(32)) last4 = db.Column(db.Integer) exp_date = db.Column(db.Date) @classmethod def billing_history(cls, user): """Get all invoices for a user """ invoices = cls.query.filter(cls.user_id == user.id) \ .order_by(cls.created_on.desc()).limit(12) return invoices @classmethod def parse_from_event(cls, payload): """ Parse and return the invoice information that will get saved locally. :return: dict """ data = payload['data']['object'] plan_info = data['lines']['data'][0]['plan'] period_start_on = datetime.utcfromtimestamp( data['lines']['data'][0]['period']['start']).date() period_end_on = datetime.utcfromtimestamp( data['lines']['data'][0]['period']['end']).date() invoice = { 'payment_id': data['customer'], 'plan': plan_info['name'], 'receipt_number': data['receipt_number'], 'description': plan_info['statement_descriptor'], 'period_start_on': period_start_on, 'period_end_on': period_end_on, 'currency': data['currency'], 'tax': data['tax'], 'tax_percent': data['tax_percent'], 'total': data['total'] } return invoice @classmethod def parse_event_save_invoice(cls, parsed_event): """Save invoice after parsing from event """ # import from User and avoid circular imports from .users import User # only save invoice if the user is valid id = parsed_event.get('payment_id') user = User.query.filter((User.payment_id == id)).first() if user: parsed_event['user_id'] = user.id # parsed_event['brand'] = user.credit_card.brand # parsed_event['last4'] = user.credit_card.last4 # parsed_event['exp_date'] = user.credit_card.exp_date del parsed_event['payment_id'] invoice = Invoice(**parsed_event) invoice.save() return user @classmethod def upcoming(cls, customer_id): """Upcoming invoice item """ payload = stripe.Invoice.upcoming(customer=customer_id) line_info = payload['lines']['data'][0] plan_info = line_info['plan'] next_billing_date = datetime.utcfromtimestamp( line_info['period']['start']) invoice = { 'plan': plan_info['id'], 'description': line_info['description'], 'next_bill_on': next_billing_date, 'amount_due': payload['amount_due'], 'interval': plan_info['interval'] } return invoice