class Template(db.Model, BaseMixin): __tablename__ = 'templates' id = Column(db.Integer, primary_key=True) display_name = Column(db.String(STRING_LEN)) file_name = Column(db.String(STRING_LEN)) html = Column(db.Text()) url = Column(db.String(STRING_LEN)) active = Column(db.Integer) order = Column(db.Integer) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) @classmethod def get_all(cls): return [x for x in cls.query.order_by(cls.order.asc()).all()] @classmethod def get_all_choices(cls): return [(t.id, t.file_name, t.display_name) for t in cls.get_all()] @classmethod def get_html(cls, template_id): template = cls.query.filter_by(id=template_id).first() if template: path = os.path.abspath( os.path.join(current_app.config.get('PROJECT_ROOT'), 'static', 'templates', template.file_name + ".html")) with open(path) as myfile: html = myfile.read() return html else: return None
class Role(db.Model, RoleMixin, BaseMixin): __tablename__ = 'roles' id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255)) @classmethod def find_by_name(cls, name): return cls.query.filter_by(name=name).first()
class Blacklist(db.Model, BaseMixin): __tablename__ = 'blacklists' id = Column(db.Integer, primary_key=True) account_id = Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False) email = Column(db.String(STRING_LEN), index=True) detail = Column(db.String(STRING_LEN)) reason = Column(db.String(STRING_LEN)) manual = Column(db.Integer, default=0) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) @classmethod def check(cls, account_id, email): return cls.query.with_entities(cls.id).filter( cls.account_id == account_id, cls.email == email).count() or 0 @classmethod def insert(cls, account_id, email, reason, detail): if not Blacklist.check(account_id, email): if detail: detail = (detail[:240] + '..') if len(detail) > 240 else detail if reason: reason = (reason[:240] + '..') if len(reason) > 240 else reason Blacklist.create(account_id=account_id, email=email, reason=reason, detail=detail) return True else: return False @classmethod def save_file(cls, account_id, file): filename = secure_filename(file.filename) if current_app.debug: # Local Save account_path = os.path.join(current_app.config['IMPORT_FOLDER'], str(account_id)) make_dir(account_path) blacklist_path = os.path.join(account_path, 'blacklists') make_dir(blacklist_path) file_path = os.path.join(blacklist_path, filename) file.save(file_path) return file_path else: # S3 Save return upload_blacklist(account_id, filename, file)
class Misc(db.Model, BaseMixin): __tablename__ = 'misc' id = Column(db.Integer, primary_key=True) name = Column(db.String(STRING_LEN)) data = Column(JSON) count = Column(db.Integer) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) @classmethod def do_stuff(self): ''' Used for load testing ''' r = self.create(name='create_random', data={ 'dog': 'cat', 'black': 'white' }) r2 = Misc.find_by_id_anon(r.id) r2.update(data={}) r2.update(data={'yes': 'no'}) r2.save() r2.delete() r2 = Misc.find_by_id_anon(r.id) redis.incr('load_test')
class User(db.Model, UserMixin, BaseMixin): __tablename__ = 'users' id = Column(db.Integer, primary_key=True) account_id = Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False) account = relationship("Account", primaryjoin="Account.id==User.account_id", foreign_keys="User.account_id") email = Column(db.String(STRING_LEN), nullable=False, unique=True) first_name = Column(db.String(STRING_LEN), nullable=False) last_name = Column(db.String(STRING_LEN), nullable=False) accepted_terms = Column(db.SmallInteger, default=0) activation_key = Column(db.String(STRING_LEN)) password = Column(db.String(STRING_LEN), nullable=False) phone = Column(db.String(STRING_LEN)) image = Column(db.String(STRING_LEN)) confirmed_at = Column(db.DateTime(timezone=True)) login_count = Column(db.Integer) current_login_at = Column(db.DateTime(timezone=True)) last_login_at = Column(db.DateTime(timezone=True)) current_login_ip = Column(db.String(STRING_LEN)) last_login_ip = Column(db.String(STRING_LEN)) active = db.Column(db.Boolean(), default=True) roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) def __repr__(self): return '<User: %s>' % self.email @property def name(self): return ' '.join( [self.first_name.capitalize(), self.last_name.capitalize()]) @classmethod def get_by_id(cls, user_id): return cls.query.filter_by(id=user_id).first_or_404()
class Unsubscribe(db.Model, BaseMixin): __tablename__ = 'unsubscribes' id = Column(db.Integer, primary_key=True) account_id = Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False) email = Column(db.String(STRING_LEN), index=True) manual = Column(db.Integer, default=0) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) @classmethod def check(cls, account_id, email): return cls.query.with_entities(cls.id).filter( cls.account_id == account_id, cls.email == email).count() or 0 @classmethod def get_all_emails(cls): unsubscribes = cls.find_all().all() emails = [] for unsub in unsubscribes: emails.append(unsub.email) return emails @classmethod def generate_csv(cls): content = StringIO.StringIO() cw = csv.writer(content) unsubscribes = cls.find_all().all() for unsub in unsubscribes: created = arrow.get( unsub.created_at).to('US/Arizona').format('MM/DD/YY HH:mm:ss') cw.writerow([unsub.email, created]) ot = arrow.get(datetime.utcnow()) ot = ot.to('US/Arizona').format('MM/DD/YY HH:mm:ss') filename = "unsubscribes-{}.csv".format(ot) response = make_response(content.getvalue()) response.headers[ "Content-Disposition"] = "attachment; filename=%s" % filename response.headers["Content-type"] = "text/csv" return response
class List(db.Model, BaseMixin): __tablename__ = 'lists' id = Column(db.Integer, primary_key=True) account_id = Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False) name = Column(db.String(STRING_LEN), nullable=False) filename = Column(db.String(STRING_LEN)) import_data = deferred(db.Column(JSON)) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) account = relationship("Account", primaryjoin="Account.id==List.account_id", foreign_keys="List.account_id") campaigns = relationship("Campaign", primaryjoin="Campaign.list_id==List.id", foreign_keys="Campaign.list_id") def __repr__(self): return '<List: %s - %s>' % (self.id, self.name) @classmethod def save_file(cls, file, list_id, curr_acct_id): if current_app.debug: # Local Save filename = secure_filename(file.filename) account_path = os.path.join(current_app.config['IMPORT_FOLDER'], str(curr_acct_id)) make_dir(account_path) list_path = os.path.join(account_path, str(list_id)) make_dir(list_path) file_path = os.path.join(list_path, filename) file.save(file_path) return file_path else: # S3 Save filename = secure_filename(file.filename) return upload_list(curr_acct_id, list_id, filename, file) def total_send_count(self): return len(self.import_data) if self.import_data else 0 def get_import_data_keys(self): if self.import_data: keys = [] for k, v in self.import_data[0].iteritems(): if k != "hash_id": keys.append(k) keys.sort() return keys else: return [] def get_unique_col_values(self, col, tup=False): if self.import_data: vals = set([]) for row in self.import_data: if row.get(col): vals.add(row.get(col)) if tup: cover = [('', '')] for v in vals: cover.append((v, v)) return cover else: return vals else: return [] def random_data_sample(self, sample_size=15): if self.import_data: random_set = range(len(self.import_data)) sample_size = sample_size if len(self.import_data) >= 15 else len( self.import_data) randos = random.sample(random_set, sample_size) random_data = [] for r in randos: random_data.append(self.import_data[r]) return random_data else: return None
class Email(db.Model, BaseMixin): __tablename__ = 'emails' id = Column(db.Integer, primary_key=True) account_id = Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False) selector_col_val = Column(db.String(STRING_LEN)) name = Column(db.String(STRING_LEN), nullable=False) subject = Column(db.String(STRING_LEN), nullable=True) preheader = Column(db.String(STRING_LEN)) html = Column(db.Text()) text = Column(db.Text()) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) account = relationship("Account", primaryjoin="Account.id==Email.account_id", foreign_keys="Email.account_id") campaign_id = Column(db.Integer, db.ForeignKey('campaigns.id'), nullable=False) campaign = relationship("Campaign", primaryjoin="Campaign.id==Email.campaign_id", foreign_keys="Email.campaign_id") sends = relationship("Send", primaryjoin="Email.id==Send.email_id", foreign_keys="Send.email_id") def full_html(self): footer = self.account.footer_html html = self.html if footer or self.preheader: soup = BeautifulSoup(html) if soup.body: if footer: soup.body.append(BeautifulSoup(footer)) if self.preheader: preheader = "<span class='preheader'>%s</span>" % self.preheader soup.body.insert(0, preheader) html = unicode(soup.html) else: if footer: html = "%s<br>%s" % (html, footer) if self.preheader: preheader = "<span class='preheader'>%s</span>" % self.preheader html = preheader + html return urllib.unquote(html) def full_text(self): footer = self.account.footer_text text = self.text if footer: text = "%s\n\n%s" % (text, footer) return urllib.unquote(text) def render_html_attr(self, data, hash_id): data = Email.add_additional_data(data, hash_id) html = self.full_html() return pystache.render(html, data) def render_text_attr(self, data, hash_id): data = Email.add_additional_data(data, hash_id) text = self.full_text() return pystache.render(text, data) def check_keys(self, list_id): list_ = List.find_by_id_anon(list_id) keys = list_.random_data_sample(1)[0] renderer = pystache.Renderer(missing_tags='strict') try: renderer.render(self.html, keys) return True, None except KeyNotFoundError, e: return False, e.key
class Campaign(db.Model, BaseMixin): __tablename__ = 'campaigns' id = Column(db.Integer, primary_key=True) name = Column(db.String(STRING_LEN), nullable=False) account_id = Column(db.Integer, db.ForeignKey('accounts.id'), nullable=False) selector_col_name = Column(db.String(STRING_LEN)) from_email_dd = Column(db.String(STRING_LEN)) # dd = dynamic data from_name_dd = Column(db.String(STRING_LEN)) reply_to_dd = Column(db.String(STRING_LEN)) to_email_dd = Column(db.String(STRING_LEN)) to_name_dd = Column(db.String(STRING_LEN)) from_email_ov = Column(db.String(STRING_LEN)) # ov = overwrite from_name_ov = Column(db.String(STRING_LEN)) reply_to_ov = Column(db.String(STRING_LEN)) to_email_ov = Column(db.String(STRING_LEN)) to_name_ov = Column(db.String(STRING_LEN)) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) account = relationship("Account", primaryjoin="Account.id==Campaign.account_id", foreign_keys="Campaign.account_id") list_id = Column(db.Integer) list_ = relationship("List", primaryjoin="List.id==Campaign.list_id", foreign_keys="Campaign.list_id") emails = relationship("Email", primaryjoin="Campaign.id==Email.campaign_id", foreign_keys="Email.campaign_id") dispatcher = relationship( "Dispatcher", primaryjoin="Campaign.id==Dispatcher.campaign_id", foreign_keys="Dispatcher.campaign_id") def __repr__(self): return '<Campaign: %s - %s>' % (self.id, self.name) def check_email_keys(self): for email in self.emails: valid, bad_key = email.check_keys(self.list_id) if not valid: return False, bad_key return True, None def render_attr(self, attr, data): return pystache.render("{{ %s }}" % getattr(self, attr), data) def email_determiner(self, data): ''' Find which of the campaign's email's this data runs with returns first one found ''' if self.selector_col_name and len(self.selector_col_name) > 0: col_name_val = data.get(self.selector_col_name) if col_name_val and len(col_name_val) > 0: for email in self.emails: if email.selector_col_val: vals = json.loads(email.selector_col_val) for val in vals: if val and len(val) > 0 and col_name_val == val: return email else: return self.emails[0] if len(self.emails) else None def get_selector_import_data(self): selector_data = [] for data in self.list_.import_data: if self.email_determiner(data): selector_data.append(data) return selector_data def selector_send_count(self): key = 'selector_send_count_%s' % self.id val = get_cache(key) if val: return val else: count = 0 for data in self.list_.import_data: if self.email_determiner(data): count = count + 1 set_cache(key, count, 10) return count def determiner_duplicates(self): ''' Used so one person doesn't get multiple emails. Returns (True, val) if dups or (false, None) if its ok. ''' values = [] for email in self.emails: if email.selector_col_val: vals = json.loads(email.selector_col_val) for val in vals: if val and val in values: return True, val else: values.append(val) return False, None def selector_missing(self): if len(self.emails) > 1 and not self.selector_col_name: return True return False
class Account(db.Model, BaseMixin): __tablename__ = 'accounts' id = Column(db.Integer, primary_key=True) name = Column(db.String(STRING_LEN), nullable=False, unique=True) domain = Column(db.String(STRING_LEN), nullable=False) subaccount_name = Column(db.String(STRING_LEN)) api_key = Column(db.String(STRING_LEN)) dedicated_ip = Column(db.String(STRING_LEN)) active = Column(db.SmallInteger, default=1) default_from_email = Column(db.String(STRING_LEN)) contact_email = Column(db.String(STRING_LEN)) phone = Column(db.String(STRING_LEN)) address = Column(db.String(STRING_LEN)) city = Column(db.String(STRING_LEN)) state = Column(db.String(STRING_LEN)) zip_code = Column(db.String(STRING_LEN)) country = Column(db.Integer) auto_text = Column(db.Integer) footer_html = Column(db.Text()) footer_text = Column(db.Text()) created_at = Column(db.DateTime(timezone=True), default=db.func.now()) modified_at = Column(db.DateTime(timezone=True), default=db.func.now(), onupdate=db.func.now()) def ip_pool(self): return self.dedicated_ip or None @classmethod def find_by_name(cls, name): return cls.query.filter_by(name=name).first()