Skip to content
This repository was archived by the owner on May 16, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions alembic/versions/11fb55be6e62_added_campaign_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Added campaign model

Revision ID: 11fb55be6e62
Revises: 155bdd6d893d
Create Date: 2013-12-23 15:37:54.560976

"""

# revision identifiers, used by Alembic.
revision = '11fb55be6e62'
down_revision = '155bdd6d893d'

from alembic import op
import sqlalchemy as sa


def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('send_newsletter', sa.Boolean(), nullable=False, server_default=sa.text(u"'t'")))

op.alter_column('user', 'send_newsletter', server_default=None)

# Create a new table for email_campaign
op.create_table('email_campaign',
sa.Column('id', sa.Integer, nullable=False, primary_key=True),
sa.Column('event_id', sa.Integer, nullable=False),
sa.Column('status', sa.Integer, nullable=False),
sa.Column('start_datetime', sa.DateTime, nullable=False),
sa.Column('end_datetime', sa.DateTime, nullable=True),
sa.Column('name', sa.Unicode(250), nullable=False, unique=True),
sa.Column('title', sa.Unicode(250), nullable=False),
sa.Column('created_at', sa.DateTime, nullable=False),
sa.Column('updated_at', sa.DateTime, nullable=False),
)
op.create_foreign_key("fk_email_campaign_event_id", "email_campaign", "event", ["event_id"], ["id"], ondelete="CASCADE")

# Create a new table for email_campaign_user
op.create_table('email_campaign_user',
sa.Column('id', sa.Integer, nullable=False, primary_key=True),
sa.Column('user_id', sa.Integer, nullable=False),
sa.Column('email_campaign_id', sa.Integer, nullable=False),
sa.Column('created_at', sa.DateTime, nullable=False),
sa.Column('updated_at', sa.DateTime, nullable=False),
)
op.create_foreign_key("fk_email_campaign_user_user_id", "email_campaign_user", "user", ["user_id"], ["id"], ondelete="CASCADE")
op.create_foreign_key("fk_email_campaign_user_email_campaign_id", "email_campaign_user", "email_campaign", ["email_campaign_id"], ["id"], ondelete="CASCADE")

### end Alembic commands ###


def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_column('user', 'send_newsletter')
op.drop_table('email_campaign_user')
op.drop_table('email_campaign')
### end Alembic commands ###
6 changes: 5 additions & 1 deletion hacknight/forms/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import wtforms
from baseframe.forms import Form, RichTextField

__all__ = ['ProfileForm']
__all__ = ['ProfileForm', 'NewsLetterForm']


class ProfileForm(Form):
type = wtforms.SelectField(u"Profile type", coerce=int, validators=[wtforms.validators.Required()])
description = RichTextField(u"Description/Bio",
content_css="/static/css/editor.css")


class NewsLetterForm(Form):
send_newsletter = wtforms.BooleanField("Receive NewsLetter", description="Do you like to receive notification about new hacknight?")
1 change: 1 addition & 0 deletions hacknight/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from hacknight.models.project import *
from hacknight.models.participant import *
from hacknight.models.sponsor import *
from hacknight.models.campaign import *
43 changes: 43 additions & 0 deletions hacknight/models/campaign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-

import datetime
from hacknight.models import db, BaseNameMixin, BaseMixin, Event, User


class EMAIL_CAMPAIGN_STATUS:
COMPLETED = 1
PROGRESS = 2


class EmailCampaign(BaseNameMixin, db.Model):
__tablename__ = "email_campaign"

event_id = db.Column(None, db.ForeignKey('event.id'), nullable=False)
event = db.relationship(Event)
start_datetime = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
end_datetime = db.Column(db.DateTime, nullable=True)
status = db.Column(db.Integer, default=EMAIL_CAMPAIGN_STATUS.PROGRESS, nullable=False)

@classmethod
def get(cls, event):
return cls.query.filter_by(event=event).first()

@classmethod
def sent_for(cls, event):
email_campaign = cls.get(event=event)
if email_campaign:
if email_campaign.status == EMAIL_CAMPAIGN_STATUS.COMPLETED:
return True
return False

def yet_to_send(self):
return set(User.subscribed_to_newsletter()) - set([user.user for user in self.users])


class EmailCampaignUser(BaseMixin, db.Model):
__tablename__ = "email_campaign_user"

user_id = db.Column(None, db.ForeignKey('user.id'), nullable=False)
user = db.relationship(User)
email_campaign_id = db.Column(None, db.ForeignKey('email_campaign.id'), nullable=False)
email_campaign = db.relationship(EmailCampaign, backref=db.backref('users', cascade='all, delete-orphan'))
4 changes: 4 additions & 0 deletions hacknight/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def url_for(self, action='view', _external=True):
return url_for('profile_view', profile=self.name, _external=_external)
elif action == 'new-event':
return url_for('event_new', profile=self.name, _external=_external)
elif action == 'settings':
return url_for('profile_settings', profile=self.name, _external=_external)
elif action == 'unsubscribe':
return url_for('profile_settings', action='unsubscribe', profile=self.name, _external=_external)


class Event(BaseScopedNameMixin, db.Model):
Expand Down
5 changes: 5 additions & 0 deletions hacknight/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class User(UserBase, db.Model):
phone_no = db.Column(db.Unicode(15), default=u'', nullable=True)
job_title = db.Column(db.Unicode(120), default=u'', nullable=True)
company = db.Column(db.Unicode(1200), default=u'', nullable=True)
send_newsletter = db.Column(db.Boolean, default=True, nullable=False)

@property
def profile_url(self):
Expand All @@ -28,6 +29,10 @@ def profiles(self):
return [self.profile] + Profile.query.filter(
Profile.userid.in_(self.organizations_owned_ids())).order_by('title').all()

@classmethod
def subscribed_to_newsletter(cls):
return cls.query.filter_by(send_newsletter=True).all()

def projects_in(self, event):
return [member.project for member in self.project_memberships if member.project.event == event]

Expand Down
7 changes: 7 additions & 0 deletions hacknight/templates/send_newsletter.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p>
You are receiving this mail because you registered for Hacknight newsletter.
<a href="{{user.profile.url_for('unsubscribe')}}">Unsubscribe</a><br/>
<h3> {{ event.title }} </h3>
{{ event.description|safe }}<br/>
<a href="{{ event.url_for('apply', _external=True) }}"> Join Hacknight </a>
</p>
26 changes: 23 additions & 3 deletions hacknight/views/profile.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-

from flask import render_template, g, abort, flash
from flask import render_template, g, abort, flash, request
from coaster.views import load_model
from baseframe.forms import render_redirect, render_form
from hacknight import app
from hacknight.models import db, Profile, User, Event
from hacknight.models import db, Profile, User, Event, PROFILE_TYPE
from hacknight.models.event import profile_types
from hacknight.forms.profile import ProfileForm
from hacknight.forms.profile import ProfileForm, NewsLetterForm
from hacknight.views.login import lastuser
from hacknight.models.participant import Participant

Expand Down Expand Up @@ -46,3 +46,23 @@ def profile_edit(profile):
return render_redirect(profile.url_for(), code=303)
return render_form(form=form, title=u"Edit profile", submit=u"Save",
cancel_url=profile.url_for(), ajax=True)


@app.route('/<profile>/settings', methods=['POST', 'GET'])
@lastuser.requires_login
@load_model(Profile, {'name': 'profile'}, 'profile')
def profile_settings(profile):
user = g.user
if not user.profile == profile:
return render_redirect(user.profile.url_for('settings'))
form = NewsLetterForm(obj=user)
action = request.args.get('action')
if action == "unsubscribe":
form.send_newsletter.data = False
if form.validate_on_submit():
form.populate_obj(user)
db.session.commit()
flash(u"Newsletter preference for '{fullname}' is saved".format(fullname=user.fullname), 'success')
return render_redirect(profile.url_for(), code=303)
return render_form(form=form, title=u"Settings", submit=u"Save",
cancel_url=profile.url_for(), ajax=False)
92 changes: 92 additions & 0 deletions send_newsletter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#! /usr/bin/env python

import datetime
import sys
import logging

from jinja2 import Environment, PackageLoader, TemplateNotFound
from html2text import html2text

from hacknight import app, init_for
from hacknight.models import EmailCampaign, EmailCampaignUser, EMAIL_CAMPAIGN_STATUS, Event, User, db
from hacknight.views.event import send_email


formatter = logging.Formatter(u'%(asctime)s - %(name)s - %(levelname)s - %(message)s')

logger = logging.getLogger('send_newsletter')
logger.setLevel(logging.INFO)

fh = logging.FileHandler('send_newsletter.log')
fh.setLevel(logging.INFO)
fh.setFormatter(formatter)
logger.addHandler(fh)


# Jinja2 template for newsletter
env = Environment(loader=PackageLoader('hacknight', 'templates'))


def get_template(name='send_newsletter.html'):
try:
template = env.get_template(name)
return template
except TemplateNotFound, e:
logger.error(e)
return None


def send_emails(event, email_campaign):
sender = User.query.filter_by(userid=event.profile.userid).first()
# We need request context to generate event url.
ctx = app.test_request_context('/')
ctx.push()
count = 0
for user in email_campaign.yet_to_send():
if user.email:
subject = u"New Hacknight {0}".format(event.title)
template = get_template()
html = template.render(user=user, event=event)
if html:
text = html2text(html)
send_email(sender=(sender.fullname, sender.email), to=user.email,
subject=subject, body=text, html=html)
email_campaign_user = EmailCampaignUser(user=user, email_campaign=email_campaign)
db.session.add(email_campaign_user)
db.session.commit()
count += 1
else:
logger.error(u"No HMTL found for {title}.".format(event.title))
break
logger.info(u"Email campaign completed for {0} users.".format(count))
ctx.pop()


def main():
try:
future_events = Event.upcoming_events()
for event in future_events:
email_campaign = EmailCampaign.get(event)
if not EmailCampaign.sent_for(event):
email_campaign = EmailCampaign.get(event)
if not email_campaign:
name = u'-'.join(["Newsletter campaign", event.title])
start_datetime = datetime.datetime.now()
email_campaign = EmailCampaign(name=name, title=name, start_datetime=start_datetime, event=event)
db.session.add(email_campaign)
db.session.commit()
send_emails(event, email_campaign)
email_campaign.end_datetime = datetime.datetime.now()
email_campaign.status = EMAIL_CAMPAIGN_STATUS.COMPLETED
db.session.commit()
except Exception, e:
logger.exception(e)


if __name__ == "__main__":
if len(sys.argv) >= 2:
init_for(sys.argv[1])
main()
else:
print("Missing parameter")
print("Syntax: python send_newsletter.py [development|production]")