From 2627f6c630648c3a0afd7a3a8350055cc8d3ce3c Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 2 Mar 2017 01:02:54 +0530 Subject: [PATCH 01/31] Fix Dogpile cache settings --- instance/settings-sample.py | 7 +++++-- instance/settings.py | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/instance/settings-sample.py b/instance/settings-sample.py index 8e91cc5b1..48568f34f 100644 --- a/instance/settings-sample.py +++ b/instance/settings-sample.py @@ -62,8 +62,11 @@ SUPPORT_EMAIL = 'person@example.com' #: Sitemap key SITEMAP_KEY = None -DOGPILE_CACHE_URLS = "http://127.0.0.1:6379" +# Dogpile cache backend +DOGPILE_CACHE_BACKEND = 'dogpile.cache.redis' +# Dogpile cache backend URL +DOGPILE_CACHE_URLS = '127.0.0.1:6379' +# Dogpile cache regions (important, do not remove!) DOGPILE_CACHE_REGIONS = [ ('hasjob_index', 3600) ] -DOGPILE_CACHE_BACKEND = 'dogpile.cache.redis' diff --git a/instance/settings.py b/instance/settings.py index 25d8d9f03..93739e199 100644 --- a/instance/settings.py +++ b/instance/settings.py @@ -95,3 +95,11 @@ LASTUSER_CLIENT_ID = '' #: LastUser client secret LASTUSER_CLIENT_SECRET = '' +# Dogpile cache backend +DOGPILE_CACHE_BACKEND = 'dogpile.cache.redis' +# Dogpile cache backend URL +DOGPILE_CACHE_URLS = '127.0.0.1:6379' +# Dogpile cache regions (important, do not remove!) +DOGPILE_CACHE_REGIONS = [ + ('hasjob_index', 3600) +] From 1748434b14ad18c5e1782882ea337123a4e5b107 Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 2 Mar 2017 01:37:25 +0530 Subject: [PATCH 02/31] provide cached option to index --- hasjob/views/index.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/hasjob/views/index.py b/hasjob/views/index.py index 7fd33beee..12ef3e2fa 100644 --- a/hasjob/views/index.py +++ b/hasjob/views/index.py @@ -90,7 +90,6 @@ def json_index(data): return jsonify(result) -@dogpile.region('hasjob_index') def fetch_jobposts(request_args, request_values, is_index, board, board_jobs, gkiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query=None): if basequery is None: basequery = JobPost.query @@ -321,10 +320,15 @@ def fetch_jobposts(request_args, request_values, is_index, board, board_jobs, gk pay_graph_data=pay_graph_data, paginated=index_is_paginated(), template_vars=template_vars) +@dogpile.region('hasjob_index') +def fetch_cached_jobposts(request_args, request_values, is_index, board, board_jobs, gkiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query=None): + return fetch_jobposts(request_args, request_values, is_index, board, board_jobs, gkiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query=None) + + @app.route('/', methods=['GET', 'POST'], subdomain='') @app.route('/', methods=['GET', 'POST']) @render_with({'text/html': 'index.html', 'application/json': json_index}, json=False) -def index(basequery=None, md5sum=None, tag=None, domain=None, location=None, title=None, showall=True, statuses=None, batched=True, ageless=False, template_vars={}): +def index(basequery=None, md5sum=None, tag=None, domain=None, location=None, title=None, showall=True, statuses=None, batched=True, ageless=False, cached=True, template_vars={}): now = datetime.utcnow() is_siteadmin = lastuser.has_permission('siteadmin') board = g.board @@ -360,7 +364,10 @@ def index(basequery=None, md5sum=None, tag=None, domain=None, location=None, tit flash(_(u"Search terms ignored because this didn’t parse: {query}").format(query=search_query), 'danger') search_query = None - data = fetch_jobposts(request.args, request.values, is_index, board, board_jobs, g.kiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query) + if cached: + data = fetch_cached_jobposts(request.args, request.values, is_index, board, board_jobs, g.kiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query) + else: + data = fetch_jobposts(request.args, request.values, is_index, board, board_jobs, g.kiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query) if data['data_filters']: # For logging @@ -444,7 +451,7 @@ def bookmarks(): @lastuser.requires_login def applied(): basequery = JobPost.query.join(JobApplication).filter(JobApplication.user == g.user) - return index(basequery=basequery, ageless=True, statuses=POSTSTATUS.ARCHIVED) + return index(basequery=basequery, ageless=True, statuses=POSTSTATUS.ARCHIVED, cached=False) @app.route('/type/', methods=['GET', 'POST'], subdomain='') From 5a359a4a1ce3cc26b0166bc36ed4b0c4503dc985 Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 2 Mar 2017 01:48:17 +0530 Subject: [PATCH 03/31] set cached=False for my, bookmarks and drafts --- hasjob/views/index.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hasjob/views/index.py b/hasjob/views/index.py index 12ef3e2fa..18c709763 100644 --- a/hasjob/views/index.py +++ b/hasjob/views/index.py @@ -427,7 +427,7 @@ def index(basequery=None, md5sum=None, tag=None, domain=None, location=None, tit @lastuser.requires_login def browse_drafts(): basequery = JobPost.query.filter_by(user=g.user) - return index(basequery=basequery, ageless=True, statuses=[POSTSTATUS.DRAFT, POSTSTATUS.PENDING]) + return index(basequery=basequery, ageless=True, statuses=[POSTSTATUS.DRAFT, POSTSTATUS.PENDING], cached=False) @app.route('/my', methods=['GET', 'POST'], subdomain='') @@ -435,7 +435,7 @@ def browse_drafts(): @lastuser.requires_login def my_posts(): basequery = JobPost.query.filter_by(user=g.user) - return index(basequery=basequery, ageless=True, statuses=POSTSTATUS.MY) + return index(basequery=basequery, ageless=True, statuses=POSTSTATUS.MY, cached=False) @app.route('/bookmarks', subdomain='') @@ -443,7 +443,7 @@ def my_posts(): @lastuser.requires_login def bookmarks(): basequery = JobPost.query.join(starred_job_table).filter(starred_job_table.c.user_id == g.user.id) - return index(basequery=basequery, ageless=True, statuses=POSTSTATUS.ARCHIVED) + return index(basequery=basequery, ageless=True, statuses=POSTSTATUS.ARCHIVED, cached=False) @app.route('/applied', subdomain='') From f246b89c22a23c0b09bb2b2ac80ac6304a2fe5fc Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 2 Mar 2017 12:37:59 +0530 Subject: [PATCH 04/31] dont cache /by/ --- hasjob/views/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/views/index.py b/hasjob/views/index.py index 18c709763..01a194052 100644 --- a/hasjob/views/index.py +++ b/hasjob/views/index.py @@ -498,7 +498,7 @@ def browse_by_email(md5sum): basequery = JobPost.query.filter_by(md5sum=md5sum) jobpost = basequery.first_or_404() jobpost_user = jobpost.user - return index(basequery=basequery, md5sum=md5sum, showall=True, template_vars={'jobpost_user': jobpost_user}) + return index(basequery=basequery, md5sum=md5sum, showall=True, cached=False, template_vars={'jobpost_user': jobpost_user}) @app.route('/in/', methods=['GET', 'POST'], subdomain='') From cc5192f428b503970553d48ff13fd0c3697371c6 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Sun, 5 Mar 2017 20:39:43 +0530 Subject: [PATCH 05/31] Don't ignore search query --- hasjob/views/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/views/index.py b/hasjob/views/index.py index 01a194052..ed6d7def1 100644 --- a/hasjob/views/index.py +++ b/hasjob/views/index.py @@ -322,7 +322,7 @@ def fetch_jobposts(request_args, request_values, is_index, board, board_jobs, gk @dogpile.region('hasjob_index') def fetch_cached_jobposts(request_args, request_values, is_index, board, board_jobs, gkiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query=None): - return fetch_jobposts(request_args, request_values, is_index, board, board_jobs, gkiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query=None) + return fetch_jobposts(request_args, request_values, is_index, board, board_jobs, gkiosk, basequery, md5sum, domain, location, title, showall, statuses, batched, ageless, template_vars, search_query) @app.route('/', methods=['GET', 'POST'], subdomain='') From 115c24ae83b6c3e3b6f5e90e3d7136c7fb9c6e6c Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Tue, 7 Mar 2017 19:04:05 +0530 Subject: [PATCH 06/31] Declare as text to avoid SQLAlchemy warning --- hasjob/models/campaign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/models/campaign.py b/hasjob/models/campaign.py index 817449741..699fcb111 100644 --- a/hasjob/models/campaign.py +++ b/hasjob/models/campaign.py @@ -284,7 +284,7 @@ def for_context(cls, position, board=None, user=None, anon_user=None, geonameids @classmethod def all(cls): - return cls.query.order_by('start_at desc, priority desc').all() + return cls.query.order_by(db.text('start_at desc, priority desc')).all() @classmethod def get(cls, name): From d43a4abda0682d703dc9150371236d1df46b18eb Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Tue, 7 Mar 2017 19:18:28 +0530 Subject: [PATCH 07/31] Use the comparison validator introduced in hasgeek/baseframe#139 (#388) --- hasjob/forms/campaign.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/hasjob/forms/campaign.py b/hasjob/forms/campaign.py index 1c4bc0baa..6370cf4e7 100644 --- a/hasjob/forms/campaign.py +++ b/hasjob/forms/campaign.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from flask import g -from baseframe import _, __ +from baseframe import __ import baseframe.forms as forms from wtforms.widgets import CheckboxInput, ListWidget from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField @@ -33,7 +33,8 @@ class CampaignForm(forms.Form): title = forms.StringField(__("Title"), description=__("A reference name for looking up this campaign again"), validators=[forms.validators.DataRequired(__("A title is required")), forms.validators.StripWhitespace()]) start_at = forms.DateTimeField(__("Start at"), timezone=lambda: g.user.timezone if g.user else None) - end_at = forms.DateTimeField(__("End at"), timezone=lambda: g.user.timezone if g.user else None) + end_at = forms.DateTimeField(__("End at"), timezone=lambda: g.user.timezone if g.user else None, + validators=[forms.validators.GreaterThan('start_at', __(u"The campaign can’t end before it starts"))]) public = forms.BooleanField(__("This campaign is live")) position = forms.RadioField(__("Display position"), choices=CAMPAIGN_POSITION.items(), coerce=int) priority = forms.IntegerField(__("Priority"), default=0, @@ -59,10 +60,6 @@ class CampaignForm(forms.Form): def validate_geonameids(self, field): field.data = [int(x) for x in field.data if x.isdigit()] - def validate_end_at(self, field): - if field.data <= self.start_at.data: - raise forms.ValidationError(_(u"The campaign can’t end before it starts")) - class CampaignActionForm(forms.Form): title = forms.StringField(__("Title"), description=__("Contents of the call to action button"), From bd8864768641084af97e907e5de863b55e92c704 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 8 Mar 2017 17:16:40 +0530 Subject: [PATCH 08/31] Let DateTimeField use default timezone. Requires hasgeek/baseframe#137 (#389) --- hasjob/forms/campaign.py | 5 ++--- hasjob/views/helper.py | 15 ++++----------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/hasjob/forms/campaign.py b/hasjob/forms/campaign.py index 6370cf4e7..4268e594b 100644 --- a/hasjob/forms/campaign.py +++ b/hasjob/forms/campaign.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from flask import g from baseframe import __ import baseframe.forms as forms from wtforms.widgets import CheckboxInput, ListWidget @@ -32,8 +31,8 @@ class CampaignContentForm(forms.Form): class CampaignForm(forms.Form): title = forms.StringField(__("Title"), description=__("A reference name for looking up this campaign again"), validators=[forms.validators.DataRequired(__("A title is required")), forms.validators.StripWhitespace()]) - start_at = forms.DateTimeField(__("Start at"), timezone=lambda: g.user.timezone if g.user else None) - end_at = forms.DateTimeField(__("End at"), timezone=lambda: g.user.timezone if g.user else None, + start_at = forms.DateTimeField(__("Start at")) + end_at = forms.DateTimeField(__("End at"), validators=[forms.validators.GreaterThan('start_at', __(u"The campaign can’t end before it starts"))]) public = forms.BooleanField(__("This campaign is live")) position = forms.RadioField(__("Display position"), choices=CAMPAIGN_POSITION.items(), coerce=int) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index 637ac6da2..218037512 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -7,7 +7,7 @@ import hashlib import bleach import requests -from pytz import utc, timezone +from pytz import utc from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError from geoip2.errors import AddressNotFoundError @@ -16,7 +16,7 @@ from flask.ext.lastuser import signal_user_looked_up from coaster.utils import uuid1mc from coaster.sqlalchemy import failsafe_add -from baseframe import _, cache +from baseframe import _, cache, get_timezone from baseframe.signals import form_validation_error, form_validation_success from .. import app, redis_store @@ -670,21 +670,14 @@ def jobpost_location_hierarchy(self): JobPost.location_hierarchy = property(jobpost_location_hierarchy) -def use_timezone(): - if g and g.user: - return g.user.tz - else: - return timezone(app.config['TIMEZONE']) - - @app.template_filter('shortdate') def shortdate(date): - return utc.localize(date).astimezone(use_timezone()).strftime('%b %e') + return utc.localize(date).astimezone(get_timezone()).strftime('%b %e') @app.template_filter('longdate') def longdate(date): - return utc.localize(date).astimezone(use_timezone()).strftime('%B %e, %Y') + return utc.localize(date).astimezone(get_timezone()).strftime('%B %e, %Y') @app.template_filter('cleanurl') From 745e96bee27c0c5a0a4d7f9cd089e975c82dc1eb Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 8 Mar 2017 22:52:37 +0530 Subject: [PATCH 09/31] Use filters introduced in hasgeek/baseframe#142 (#390) --- hasjob/forms/board.py | 26 ++++++++++++++------------ hasjob/forms/campaign.py | 19 +++++++++++-------- hasjob/forms/domain.py | 6 ++++-- hasjob/forms/jobpost.py | 35 ++++++++++++++++++----------------- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/hasjob/forms/board.py b/hasjob/forms/board.py index c60e9ef88..fde83088a 100644 --- a/hasjob/forms/board.py +++ b/hasjob/forms/board.py @@ -39,11 +39,11 @@ class BoardOptionsForm(forms.Form): description=__(u"Hasjob requires employers to reveal what they intend to pay, " u"but you can make it optional for jobs posted from this board. " u"Pay data is used to match candidates to jobs. We recommend you collect it")) - newjob_headline = forms.NullTextField(__(u"Headline"), + newjob_headline = forms.StringField(__(u"Headline"), description=__(u"Optional – The sample headline shown to employers when posting a job"), validators=[ - forms.validators.StripWhitespace(), - forms.validators.Length(min=0, max=100, message=__("%%(max)d characters maximum"))]) + forms.validators.Length(min=0, max=100, message=__("%%(max)d characters maximum"))], + filters=[forms.filters.strip(), forms.filters.none_if_empty()]) newjob_blurb = forms.TinyMce4Field(__(u"Posting instructions"), description=__(u"Optional – What should we tell employers when they post a job on your board? " u"Leave blank to use the default text"), @@ -109,15 +109,17 @@ class BoardForm(forms.Form): """ Edit board settings. """ - title = forms.StringField(__("Title"), validators=[ - forms.validators.DataRequired(__("The board needs a name")), - forms.validators.StripWhitespace(), - forms.validators.Length(min=1, max=80, message=__("%%(max)d characters maximum"))]) - caption = forms.NullTextField(__("Caption"), validators=[ - forms.validators.Optional(), - forms.validators.StripWhitespace(), - forms.validators.Length(min=0, max=80, message=__("%%(max)d characters maximum"))], - description=__("The title and caption appear at the top of the page. Keep them concise")) + title = forms.StringField(__("Title"), + validators=[ + forms.validators.DataRequired(__("The board needs a name")), + forms.validators.Length(min=1, max=80, message=__("%%(max)d characters maximum"))], + filters=[forms.filters.strip()]) + caption = forms.StringField(__("Caption"), + description=__("The title and caption appear at the top of the page. Keep them concise"), + validators=[ + forms.validators.Optional(), + forms.validators.Length(min=0, max=80, message=__("%%(max)d characters maximum"))], + filters=[forms.filters.strip(), forms.filters.none_if_empty()]) name = forms.AnnotatedTextField(__("URL Name"), prefix='https://', suffix=u'.hasjob.co', description=__(u"Optional — Will be autogenerated if blank"), validators=[ diff --git a/hasjob/forms/campaign.py b/hasjob/forms/campaign.py index 4268e594b..afb97d701 100644 --- a/hasjob/forms/campaign.py +++ b/hasjob/forms/campaign.py @@ -12,8 +12,8 @@ class CampaignContentForm(forms.Form): - subject = forms.NullTextField(__("Subject"), description=__("A subject title shown to viewers"), - validators=[forms.validators.Optional(), forms.validators.StripWhitespace()]) + subject = forms.StringField(__("Subject"), description=__("A subject title shown to viewers"), + validators=[forms.validators.Optional()], filters=[forms.filters.strip(), forms.filters.none_if_empty()]) blurb = forms.TinyMce4Field(__("Blurb"), description=__("Teaser to introduce the campaign and convince users to interact"), content_css=content_css, @@ -30,7 +30,8 @@ class CampaignContentForm(forms.Form): class CampaignForm(forms.Form): title = forms.StringField(__("Title"), description=__("A reference name for looking up this campaign again"), - validators=[forms.validators.DataRequired(__("A title is required")), forms.validators.StripWhitespace()]) + validators=[forms.validators.DataRequired(__("A title is required"))], + filters=[forms.filters.strip()]) start_at = forms.DateTimeField(__("Start at")) end_at = forms.DateTimeField(__("End at"), validators=[forms.validators.GreaterThan('start_at', __(u"The campaign can’t end before it starts"))]) @@ -62,12 +63,14 @@ def validate_geonameids(self, field): class CampaignActionForm(forms.Form): title = forms.StringField(__("Title"), description=__("Contents of the call to action button"), - validators=[forms.validators.DataRequired("You must provide some text"), forms.validators.StripWhitespace()]) - icon = forms.NullTextField(__("Icon"), validators=[forms.validators.Optional()], + validators=[forms.validators.DataRequired("You must provide some text")], + filters=[forms.filters.strip()]) + icon = forms.StringField(__("Icon"), validators=[forms.validators.Optional()], filters=[forms.filters.none_if_empty()], description=__("Optional Font-Awesome icon name")) public = forms.BooleanField(__("This action is live")) type = forms.RadioField(__("Type"), choices=CAMPAIGN_ACTION.items(), validators=[forms.validators.DataRequired(__("This is required"))]) - group = forms.NullTextField(__("RSVP group"), validators=[forms.validators.Optional()], + group = forms.StringField(__("RSVP group"), validators=[forms.validators.Optional()], + filters=[forms.filters.none_if_empty()], description=__("If you have multiple RSVP actions, add an optional group name")) category = forms.RadioField(__("Category"), validators=[forms.validators.DataRequired(__("This is required"))], widget=forms.InlineListWidget(class_='button-bar', class_prefix='btn btn-'), choices=[ @@ -84,10 +87,10 @@ class CampaignActionForm(forms.Form): validators=[forms.validators.Optional(), forms.validators.AllUrlsValid()]) link = forms.URLField(__("Link"), description=__(u"URL to redirect to, if type is “follow link”"), validators=[ - forms.validators.StripWhitespace(), optional_url, forms.validators.Length(min=0, max=250, message=__("%%(max)d characters maximum")), - forms.validators.ValidUrl()]) + forms.validators.ValidUrl()], + filters=[forms.filters.strip()]) form = forms.TextAreaField(__("Form JSON"), description=__("Form definition (for form type)"), validators=[forms.validators.Optional()]) seq = forms.IntegerField(__("Sequence #"), validators=[forms.validators.DataRequired(__("This is required"))], diff --git a/hasjob/forms/domain.py b/hasjob/forms/domain.py index 19f08b160..6b7d86957 100644 --- a/hasjob/forms/domain.py +++ b/hasjob/forms/domain.py @@ -10,12 +10,14 @@ class DomainForm(forms.Form): title = forms.StringField(__(u"Common name"), - validators=[forms.validators.DataRequired(), forms.validators.StripWhitespace(), + validators=[forms.validators.DataRequired(), forms.validators.Length(min=1, max=250, message=__("%(max)d characters maximum"))], + filters=[forms.filters.strip()], description=__("The name of your organization, excluding legal suffixes like Pvt Ltd")) - legal_title = forms.NullTextField(__("Legal name"), + legal_title = forms.StringField(__("Legal name"), validators=[forms.validators.Optional(), forms.validators.Length(min=1, max=250, message=__("%%(max)d characters maximum"))], + filters=[forms.filters.none_if_empty()], description=__(u"Optional — The full legal name of your organization")) # logo_url = forms.URLField(__("Logo URL"), # TODO: Use ImgeeField # validators=[forms.validators.Optional(), diff --git a/hasjob/forms/jobpost.py b/hasjob/forms/jobpost.py index beb2cb650..15ea00a88 100644 --- a/hasjob/forms/jobpost.py +++ b/hasjob/forms/jobpost.py @@ -46,16 +46,16 @@ class ListingForm(forms.Form): description=Markup(__("A single-line summary. This goes to the front page and across the network. " """A/B test it?""")), validators=[forms.validators.DataRequired(__("A headline is required")), - forms.validators.StripWhitespace(), forms.validators.Length(min=1, max=100, message=__("%%(max)d characters maximum")), - forms.validators.NoObfuscatedEmail(__(u"Do not include contact information in the post"))]) - job_headlineb = forms.NullTextField(__("Headline B"), + forms.validators.NoObfuscatedEmail(__(u"Do not include contact information in the post"))], + filters=[forms.filters.strip()]) + job_headlineb = forms.StringField(__("Headline B"), description=__(u"An alternate headline that will be shown to 50%% of users. " u"You’ll get a count of views per headline"), validators=[forms.validators.Optional(), - forms.validators.StripWhitespace(), forms.validators.Length(min=1, max=100, message=__("%%(max)d characters maximum")), - forms.validators.NoObfuscatedEmail(__(u"Do not include contact information in the post"))]) + forms.validators.NoObfuscatedEmail(__(u"Do not include contact information in the post"))], + filters=[forms.filters.strip(), forms.filters.none_if_empty()]) job_type = forms.RadioField(__("Type"), coerce=int, validators=[forms.validators.InputRequired(__("The job type must be specified"))]) job_category = forms.RadioField(__("Category"), coerce=int, @@ -63,8 +63,8 @@ class ListingForm(forms.Form): job_location = forms.StringField(__("Location"), description=__(u'“Bangalore”, “Chennai”, “Pune”, etc or “Anywhere” (without quotes)'), validators=[forms.validators.DataRequired(__(u"If this job doesn’t have a fixed location, use “Anywhere”")), - forms.validators.StripWhitespace(), - forms.validators.Length(min=3, max=80, message=__("%%(max)d characters maximum"))]) + forms.validators.Length(min=3, max=80, message=__("%%(max)d characters maximum"))], + filters=[forms.filters.strip()]) job_relocation_assist = forms.BooleanField(__("Relocation assistance available")) job_description = forms.TinyMce4Field(__("Description"), content_css=content_css, @@ -102,16 +102,17 @@ class ListingForm(forms.Form): u"yet. We do not accept posts from third parties such as recruitment consultants. Such posts " u"may be removed without notice"), validators=[forms.validators.DataRequired(__(u"This is required. Posting any name other than that of the actual organization is a violation of the ToS")), - forms.validators.StripWhitespace(), - forms.validators.Length(min=4, max=80, message=__("The name must be within %%(min)d to %%(max)d characters"))]) + forms.validators.Length(min=4, max=80, message=__("The name must be within %%(min)d to %%(max)d characters"))], + filters=[forms.filters.strip()]) company_logo = forms.FileField(__("Logo"), description=__(u"Optional — Your organization’s logo will appear at the top of your post."), ) # validators=[file_allowed(uploaded_logos, "That image type is not supported")]) company_logo_remove = forms.BooleanField(__("Remove existing logo")) company_url = forms.URLField(__("URL"), description=__(u"Your organization’s website"), - validators=[forms.validators.DataRequired(), forms.validators.StripWhitespace(), optional_url, - forms.validators.Length(max=255, message=__("%%(max)d characters maximum")), forms.validators.ValidUrl()]) + validators=[forms.validators.DataRequired(), optional_url, + forms.validators.Length(max=255, message=__("%%(max)d characters maximum")), forms.validators.ValidUrl()], + filters=[forms.filters.strip()]) hr_contact = forms.RadioField(__(u"Is it okay for recruiters and other " u"intermediaries to contact you about this post?"), coerce=getbool, description=__(u"We’ll display a notice to this effect on the post"), @@ -129,16 +130,16 @@ class ListingForm(forms.Form): u"Your email address will not be revealed to applicants until you respond")), validators=[ forms.validators.DataRequired(__("We need to confirm your email address before the job can be listed")), - forms.validators.StripWhitespace(), forms.validators.Length(min=5, max=80, message=__("%%(max)d characters maximum")), - forms.validators.ValidEmail(__("This does not appear to be a valid email address"))]) - twitter = forms.AnnotatedNullTextField(__("Twitter"), + forms.validators.ValidEmail(__("This does not appear to be a valid email address"))], + filters=[forms.filters.strip()]) + twitter = forms.AnnotatedTextField(__("Twitter"), description=__(u"Optional — your organization’s Twitter account. " u"We’ll tweet mentioning you so you get included on replies"), prefix='@', validators=[ forms.validators.Optional(), - forms.validators.StripWhitespace(), - forms.validators.Length(min=0, max=15, message=__(u"Twitter accounts can’t be over %%(max)d characters long"))]) + forms.validators.Length(min=0, max=15, message=__(u"Twitter accounts can’t be over %%(max)d characters long"))], + filters=[forms.filters.strip(), forms.filters.none_if_empty()]) collaborators = forms.UserSelectMultiField(__(u"Collaborators"), description=__(u"If someone is helping you evaluate candidates, type their names here. " u"They must have a HasGeek account. They will not receive email notifications " @@ -365,8 +366,8 @@ class ApplicationForm(forms.Form): description=__("Add new email addresses from your profile")) apply_phone = forms.StringField(__("Phone"), validators=[forms.validators.DataRequired(__("Specify a phone number")), - forms.validators.StripWhitespace(), forms.validators.Length(min=1, max=15, message=__("%%(max)d characters maximum"))], + filters=[forms.filters.strip()], description=__("A phone number the employer can reach you at")) apply_message = forms.TinyMce4Field(__("Job application"), content_css=content_css, From 0f694f84d68fd20d02a5f60cb716e4cbcab23c0d Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Tue, 14 Mar 2017 15:56:32 +0530 Subject: [PATCH 10/31] Don't show domain title from jobpost for withdrawn/rejected posts. --- hasjob/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/models/domain.py b/hasjob/models/domain.py index 172d4cd87..c249d4748 100644 --- a/hasjob/models/domain.py +++ b/hasjob/models/domain.py @@ -54,7 +54,7 @@ def use_title(self): return self.title if self.is_webmail: return self.name - post = self.jobposts.filter(JobPost.status.in_(POSTSTATUS.POSTPENDING)).order_by('datetime desc').first() + post = self.jobposts.filter(JobPost.status.in_(POSTSTATUS.ARCHIVED)).order_by('datetime desc').first() if post: return post.company_name return self.name From ff4ba1fdb2e0c00a4f96621b0b5e1afab5f5df9f Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 16 Mar 2017 02:10:18 +0530 Subject: [PATCH 11/31] disable dogpile caching temporarily --- hasjob/views/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/views/index.py b/hasjob/views/index.py index ed6d7def1..937e8e266 100644 --- a/hasjob/views/index.py +++ b/hasjob/views/index.py @@ -328,7 +328,7 @@ def fetch_cached_jobposts(request_args, request_values, is_index, board, board_j @app.route('/', methods=['GET', 'POST'], subdomain='') @app.route('/', methods=['GET', 'POST']) @render_with({'text/html': 'index.html', 'application/json': json_index}, json=False) -def index(basequery=None, md5sum=None, tag=None, domain=None, location=None, title=None, showall=True, statuses=None, batched=True, ageless=False, cached=True, template_vars={}): +def index(basequery=None, md5sum=None, tag=None, domain=None, location=None, title=None, showall=True, statuses=None, batched=True, ageless=False, cached=False, template_vars={}): now = datetime.utcnow() is_siteadmin = lastuser.has_permission('siteadmin') board = g.board From bf4625b19c623234a12c4bba4f1944cb4a0dde77 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Mon, 17 Apr 2017 15:03:16 +0530 Subject: [PATCH 12/31] Use DMY dates. Fixes #391 --- hasjob/views/helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index 218037512..36ca94833 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -672,12 +672,12 @@ def jobpost_location_hierarchy(self): @app.template_filter('shortdate') def shortdate(date): - return utc.localize(date).astimezone(get_timezone()).strftime('%b %e') + return utc.localize(date).astimezone(get_timezone()).strftime('%e %b') @app.template_filter('longdate') def longdate(date): - return utc.localize(date).astimezone(get_timezone()).strftime('%B %e, %Y') + return utc.localize(date).astimezone(get_timezone()).strftime('%e %B %Y') @app.template_filter('cleanurl') From 7335e8bf09728e4bb69458dbbeff4c6d733567a7 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 3 May 2017 15:37:20 +0530 Subject: [PATCH 13/31] Fix linter errors --- alembic/env.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/alembic/env.py b/alembic/env.py index bad6e54a2..d1e5a7e72 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,6 +1,5 @@ from __future__ import with_statement from alembic import context -from alembic.config import Config from sqlalchemy import engine_from_config, pool from logging.config import fileConfig from flask.ext.alembic import FlaskAlembicConfig @@ -30,6 +29,7 @@ # my_important_option = config.get_main_option("my_important_option") # ... etc. + def run_migrations_offline(): """Run migrations in 'offline' mode. @@ -48,6 +48,7 @@ def run_migrations_offline(): with context.begin_transaction(): context.run_migrations() + def run_migrations_online(): """Run migrations in 'online' mode. @@ -56,15 +57,15 @@ def run_migrations_online(): """ engine = engine_from_config( - config.get_section(config.config_ini_section), - prefix='sqlalchemy.', - poolclass=pool.NullPool) + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) connection = engine.connect() context.configure( - connection=connection, - target_metadata=target_metadata - ) + connection=connection, + target_metadata=target_metadata + ) try: with context.begin_transaction(): @@ -76,4 +77,3 @@ def run_migrations_online(): run_migrations_offline() else: run_migrations_online() - From 0974817fd05f5568acab8c3c95f7416fdd4a83ac Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 3 May 2017 15:37:28 +0530 Subject: [PATCH 14/31] Switch to new import syntax --- alembic/env.py | 2 +- hasjob/__init__.py | 12 ++++++------ hasjob/forms/jobpost.py | 2 +- hasjob/models/user.py | 2 +- hasjob/tagging.py | 2 +- hasjob/twitter.py | 2 +- hasjob/uploads.py | 2 +- hasjob/views/helper.py | 4 ++-- hasjob/views/listing.py | 2 +- hasjob/views/login.py | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/alembic/env.py b/alembic/env.py index d1e5a7e72..5d3dce3f5 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -2,7 +2,7 @@ from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig -from flask.ext.alembic import FlaskAlembicConfig +from flask_alembic import FlaskAlembicConfig # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/hasjob/__init__.py b/hasjob/__init__.py index 1f1068ea5..462c21bdd 100644 --- a/hasjob/__init__.py +++ b/hasjob/__init__.py @@ -3,12 +3,12 @@ import os.path import geoip2.database from flask import Flask -from flask.ext.assets import Bundle -from flask.ext.rq import RQ -from flask.ext.mail import Mail -from flask.ext.redis import Redis -from flask.ext.lastuser import Lastuser -from flask.ext.lastuser.sqlalchemy import UserManager +from flask_assets import Bundle +from flask_rq import RQ +from flask_mail import Mail +from flask_redis import Redis +from flask_lastuser import Lastuser +from flask_lastuser.sqlalchemy import UserManager from baseframe import baseframe, assets, Version import coaster.app from ._version import __version__ diff --git a/hasjob/forms/jobpost.py b/hasjob/forms/jobpost.py index 15ea00a88..cd6ffca89 100644 --- a/hasjob/forms/jobpost.py +++ b/hasjob/forms/jobpost.py @@ -9,7 +9,7 @@ import baseframe.forms as forms from baseframe.staticdata import webmail_domains from coaster.utils import getbool, get_email_domain -from flask.ext.lastuser import LastuserResourceException +from flask_lastuser import LastuserResourceException from ..models import User, JobType, JobApplication, EMPLOYER_RESPONSE, PAY_TYPE, CURRENCY, Domain from ..uploads import process_image, UploadNotAllowed diff --git a/hasjob/models/user.py b/hasjob/models/user.py index a584c28b1..8d203c59e 100644 --- a/hasjob/models/user.py +++ b/hasjob/models/user.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from flask import request -from flask.ext.lastuser.sqlalchemy import UserBase2 +from flask_lastuser.sqlalchemy import UserBase2 from sqlalchemy_utils.types import UUIDType from coaster.utils import unicode_http_header, uuid1mc from coaster.sqlalchemy import JsonDict diff --git a/hasjob/tagging.py b/hasjob/tagging.py index e6aa88f12..3dea3f47e 100644 --- a/hasjob/tagging.py +++ b/hasjob/tagging.py @@ -3,7 +3,7 @@ from collections import defaultdict from urlparse import urljoin import requests -from flask.ext.rq import job +from flask_rq import job from coaster.utils import text_blocks from coaster.nlp import extract_named_entities from . import app diff --git a/hasjob/twitter.py b/hasjob/twitter.py index 256441b11..923bc1f42 100644 --- a/hasjob/twitter.py +++ b/hasjob/twitter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from flask.ext.rq import job +from flask_rq import job from tweepy import OAuthHandler, API import bitlyapi import urllib2 diff --git a/hasjob/uploads.py b/hasjob/uploads.py index 6af1fe2e9..2e459adc7 100644 --- a/hasjob/uploads.py +++ b/hasjob/uploads.py @@ -5,7 +5,7 @@ from os.path import splitext from werkzeug import FileStorage -from flask.ext.uploads import UploadSet, configure_uploads, IMAGES, UploadNotAllowed +from flask_uploads import UploadSet, configure_uploads, IMAGES, UploadNotAllowed from hasjob import app diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index 36ca94833..a1aae739a 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -12,8 +12,8 @@ from sqlalchemy.exc import IntegrityError from geoip2.errors import AddressNotFoundError from flask import Markup, request, g, session -from flask.ext.rq import job -from flask.ext.lastuser import signal_user_looked_up +from flask_rq import job +from flask_lastuser import signal_user_looked_up from coaster.utils import uuid1mc from coaster.sqlalchemy import failsafe_add from baseframe import _, cache, get_timezone diff --git a/hasjob/views/listing.py b/hasjob/views/listing.py index b1637a694..37d22c0da 100644 --- a/hasjob/views/listing.py +++ b/hasjob/views/listing.py @@ -9,7 +9,7 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import StaleDataError from flask import abort, flash, g, redirect, render_template, request, url_for, session, Markup, jsonify -from flask.ext.mail import Message +from flask_mail import Message from baseframe import cache, dogpile from baseframe.forms import Form from coaster.utils import getbool, get_email_domain, md5sum, base_domain_matches diff --git a/hasjob/views/login.py b/hasjob/views/login.py index 1bdac43ed..c01292c5b 100644 --- a/hasjob/views/login.py +++ b/hasjob/views/login.py @@ -2,7 +2,7 @@ from sqlalchemy.exc import IntegrityError from flask import g, Response, redirect, flash -from flask.ext.lastuser import signal_user_session_refreshed +from flask_lastuser import signal_user_session_refreshed from coaster.views import get_next_url from .. import app, lastuser From 77133dda4786b0a976b4f05ef34d3000c8a880af Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 4 May 2017 13:43:42 +0530 Subject: [PATCH 15/31] fix #392 --- hasjob/__init__.py | 52 ++++++++++++++++++++++------------------------ manage.py | 5 ++--- requirements.txt | 2 -- rq.sh | 2 +- rqinit.py | 3 +-- runserver.py | 5 ++--- runtestserver.py | 3 +-- 7 files changed, 32 insertions(+), 40 deletions(-) diff --git a/hasjob/__init__.py b/hasjob/__init__.py index 462c21bdd..d356f4f3d 100644 --- a/hasjob/__init__.py +++ b/hasjob/__init__.py @@ -32,35 +32,33 @@ from . import models, views # NOQA from .models import db # NOQA - # Configure the app -def init_for(env): - coaster.app.init_app(app, env) - db.init_app(app) - db.app = app +coaster.app.init_app(app) +db.init_app(app) +db.app = app - app.geoip = None - if 'GEOIP_PATH' in app.config: - if not os.path.exists(app.config['GEOIP_PATH']): - app.logger.warn("GeoIP database missing at " + app.config['GEOIP_PATH']) - else: - app.geoip = geoip2.database.Reader(app.config['GEOIP_PATH']) +app.geoip = None +if 'GEOIP_PATH' in app.config: + if not os.path.exists(app.config['GEOIP_PATH']): + app.logger.warn("GeoIP database missing at " + app.config['GEOIP_PATH']) + else: + app.geoip = geoip2.database.Reader(app.config['GEOIP_PATH']) - RQ(app) +RQ(app) - baseframe.init_app(app, requires=['hasjob'], - ext_requires=['baseframe-bs3', - ('jquery.autosize', 'jquery.sparkline', 'jquery.liblink', 'jquery.wnumb', 'jquery.nouislider'), - 'baseframe-firasans', 'fontawesome>=4.3.0', 'bootstrap-multiselect', 'nprogress', 'ractive', - 'jquery.appear', 'hammer']) - # TinyMCE has to be loaded by itself, unminified, or it won't be able to find its assets - app.assets.register('js_tinymce', assets.require('!jquery.js', 'tinymce.js>=4.0.0', 'jquery.tinymce.js>=4.0.0')) - app.assets.register('css_editor', Bundle('css/editor.css', - filters=['cssrewrite', 'cssmin'], output='css/editor.packed.css')) +baseframe.init_app(app, requires=['hasjob'], + ext_requires=['baseframe-bs3', + ('jquery.autosize', 'jquery.sparkline', 'jquery.liblink', 'jquery.wnumb', 'jquery.nouislider'), + 'baseframe-firasans', 'fontawesome>=4.3.0', 'bootstrap-multiselect', 'nprogress', 'ractive', + 'jquery.appear', 'hammer']) +# TinyMCE has to be loaded by itself, unminified, or it won't be able to find its assets +app.assets.register('js_tinymce', assets.require('!jquery.js', 'tinymce.js>=4.0.0', 'jquery.tinymce.js>=4.0.0')) +app.assets.register('css_editor', Bundle('css/editor.css', + filters=['cssrewrite', 'cssmin'], output='css/editor.packed.css')) - from hasjob.uploads import configure as uploads_configure - uploads_configure() - mail.init_app(app) - redis_store.init_app(app) - lastuser.init_app(app) - lastuser.init_usermanager(UserManager(db, models.User)) +from hasjob.uploads import configure as uploads_configure +uploads_configure() +mail.init_app(app) +redis_store.init_app(app) +lastuser.init_app(app) +lastuser.init_usermanager(UserManager(db, models.User)) diff --git a/manage.py b/manage.py index 5bf4295f4..c743bf2ee 100755 --- a/manage.py +++ b/manage.py @@ -7,7 +7,7 @@ import hasjob.forms as forms import hasjob.views as views from hasjob.models import db -from hasjob import app, init_for +from hasjob import app from datetime import datetime, timedelta periodic = Manager(usage="Periodic tasks from cron (with recommended intervals)") @@ -16,7 +16,6 @@ @periodic.option('-e', '--env', default='dev', help="runtime env [default 'dev']") def sessions(env): """Sweep user sessions to close all inactive sessions (10m)""" - manager.init_for(env) es = models.EventSession # Close all sessions that have been inactive for >= 30 minutes es.query.filter(es.ended_at == None, # NOQA @@ -41,6 +40,6 @@ def impressions(env): if __name__ == '__main__': db.init_app(app) - manager = init_manager(app, db, init_for, hasjob=hasjob, models=models, forms=forms, views=views) + manager = init_manager(app, db, hasjob=hasjob, models=models, forms=forms, views=views) manager.add_command('periodic', periodic) manager.run() diff --git a/requirements.txt b/requirements.txt index 773f95e72..50b2b08bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,8 +26,6 @@ langid>=1.1.4dev tldextract unicodecsv geoip2 -Flask-Script==0.5.3 git+https://github.com/hasgeek/coaster git+https://github.com/hasgeek/flask-lastuser git+https://github.com/hasgeek/baseframe -git+https://github.com/jace/flask-alembic diff --git a/rq.sh b/rq.sh index 8b2c62bb0..fccfa2a3f 100755 --- a/rq.sh +++ b/rq.sh @@ -1,3 +1,3 @@ #!/bin/bash -rqworker -c rqdev hasjob +rqworker -c rqinit hasjob diff --git a/rqinit.py b/rqinit.py index edc15498a..00a6e60cd 100644 --- a/rqinit.py +++ b/rqinit.py @@ -1,8 +1,7 @@ from urlparse import urlparse -from hasjob import init_for, app +from hasjob import app -init_for('production') REDIS_URL = app.config.get('REDIS_URL', 'redis://localhost:6379/0') # REDIS_URL is not taken by setup_default_arguments function of rq/scripts/__init__.py diff --git a/runserver.py b/runserver.py index da2c656f3..e9e76128c 100755 --- a/runserver.py +++ b/runserver.py @@ -1,12 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from hasjob import app, init_for, models +from hasjob import app, models from hasjob.models import db if __name__ == '__main__': import sys - init_for('dev') # Seed with sample data with app.test_request_context(): if not models.JobType.query.notempty(): @@ -36,4 +35,4 @@ db.session.add(models.ReportCode(seq=30, name=u'anon', title=u'Organization is not clearly identified')) db.session.add(models.ReportCode(seq=40, name=u'unclear', title=u'Job position is not properly described')) db.session.commit() - app.run('0.0.0.0', debug=True) + app.run('0.0.0.0', debug=True, port=5000) diff --git a/runtestserver.py b/runtestserver.py index f7846fde6..fb94221c5 100644 --- a/runtestserver.py +++ b/runtestserver.py @@ -1,14 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from baseframe.staticdata import webmail_domains -from hasjob import app, init_for, models +from hasjob import app, models from hasjob.models import db if __name__ == '__main__': import sys webmail_domains = set(['gmail.com', 'hotmail.com']) - init_for('testing') # Seed with sample data with app.test_request_context(): if not models.JobType.query.notempty(): From e3d25d82dc8113af2184784c6c5b12c55cc191a9 Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 4 May 2017 13:53:36 +0530 Subject: [PATCH 16/31] rm init_for --- website.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/website.py b/website.py index ad60319b8..a1b308801 100755 --- a/website.py +++ b/website.py @@ -2,6 +2,4 @@ import os.path sys.path.insert(0, os.path.dirname(__file__)) -from hasjob import app as application, init_for - -init_for('prod') +from hasjob import app as application From 13b448e85d61e00a2bdee5a63ef33643c0212517 Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 4 May 2017 15:11:02 +0530 Subject: [PATCH 17/31] rm rqdev.py --- rqdev.py | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 rqdev.py diff --git a/rqdev.py b/rqdev.py deleted file mode 100644 index 5d9e969db..000000000 --- a/rqdev.py +++ /dev/null @@ -1,16 +0,0 @@ -from urlparse import urlparse - -from hasjob import init_for, app - -init_for('dev') - -REDIS_URL = app.config.get('REDIS_URL', 'redis://localhost:6379/0') - -# REDIS_URL is not taken by setup_default_arguments function of rq/scripts/__init__.py -# so, parse it into pieces and give it - -r = urlparse(REDIS_URL) -REDIS_HOST = r.hostname -REDIS_PORT = r.port -REDIS_PASSWORD = r.password -REDIS_DB = 0 From fa0af8650bcfe9f36bbe96f9b538206e5f099663 Mon Sep 17 00:00:00 2001 From: Shreyas Satish Date: Thu, 4 May 2017 19:19:28 +0530 Subject: [PATCH 18/31] flask-alembic -> flask-migrate --- hasjob/__init__.py | 3 +- {alembic => migrations}/README | 0 alembic.ini => migrations/alembic.ini | 8 +--- {alembic => migrations}/env.py | 44 +++++++++++-------- {alembic => migrations}/script.py.mako | 0 .../versions/1016f365bfa7_board_options.py | 0 .../12bc1e8538a_json_dict_server_default.py | 0 .../140e56666d9e_a_b_tests_for_headlines.py | 0 .../versions/15aede1ebe6f_domain_banned_at.py | 0 ...281a_discard_unused_org_and_team_models.py | 0 ...9ac_domain_has_organization_profile_now.py | 0 .../versions/17869f3e044c_event_sessions.py | 0 .../17a5476c8701_campaign_anon_models.py | 0 ...3e7055a_add_auto_to_auto_tagging_tables.py | 0 .../1b3f67ef6387_featured_board_flag.py | 0 .../versions/1bcc4e025b04_fullname.py | 0 .../versions/1ea37e8afd8_job_impressions.py | 0 .../versions/1fce9db567a5_nlp_tagging.py | 0 .../versions/1feef782ef45_jobpost_language.py | 0 .../23546c6d07e2_geotargeted_column.py | 0 .../241feed6748b_dismissed_campaign.py | 0 .../2420dd9c9949_employer_response_me.py | 0 .../260571e00aef_switch_to_userbase2.py | 0 .../275e9a964a9f_added_remote_working_flag.py | 0 .../28b492e71b0e_more_board_options.py | 0 .../versions/29fd6847a8e2_org_teams.py | 0 .../versions/2aab4c974a75_jobpost_search.py | 0 ...c2d1dc5_index_jobapplication_jobpost_id.py | 0 .../versions/2c594a115c39_rsvp_group.py | 0 .../2cd06f7444f8_private_types_and_ca.py | 0 .../2d966f2ff84f_campaign_user_required.py | 0 ...c05c34650b_index_job_views_by_job_first.py | 0 .../versions/30828b49527_start.py | 0 ...e0a8e6137_track_user_activity_in_boards.py | 0 .../33a61e082fb_campaign_targeting.py | 0 .../33de75e55858_adding_datetime_column.py | 0 .../348bd4e2226b_track_user_presence.py | 0 .../versions/34e1d4b2f636_payscale_info.py | 0 .../versions/375bb2e7a4cc_json_resume_dict.py | 0 .../3ac38e8fc78b_board_domains_and_lo.py | 0 .../3e27bf9706ee_reports_track_users_.py | 0 .../versions/3fc1aa06cbeb_jobtype_flags.py | 0 .../versions/41d839fcecb4_colnames.py | 0 .../versions/434344d0a2d0_location_tagging.py | 0 ..._job_applications_always_have_a_jobpost.py | 0 .../441fd49059e3_preserve_user_s_name.py | 0 .../versions/449914911f93_post_admins.py | 0 .../4616e70f18f4_index_fkey_to_user.py | 0 .../versions/465e724941d3_userblocked.py | 0 .../470c8feb73cc_jobapplication_repli.py | 0 .../versions/472ca8e93765_boards.py | 0 .../versions/476608367f85_dns_domains.py | 0 .../versions/48f145e9d55f_locations.py | 0 .../versions/499df876f3f2_apply_message.py | 0 ...2_campaign_action_sorting_sequence_and_.py | 0 ...449b_save_language_detection_confidence.py | 0 .../4bd08758f049_jobpost_twitter_account.py | 0 .../versions/4c8265da3caa_appwords.py | 0 .../versions/4d17424a3925_starred_jobs.py | 0 .../versions/4f639a4a5fc3_apphash.py | 0 .../504175f63661_boardjobpost_doesn_t.py | 0 ...5ec799_coin_toss_column_for_a_b_testing.py | 0 ...b17ff82_save_job_application_reply_date.py | 0 ...5ea2_auto_posting_to_board_by_type_and_.py | 0 ...d78c9ca_use_uuid_key_for_event_sessions.py | 0 ...8_user_opt_in_for_experimental_features.py | 0 .../5a725fd5ddd6_additional_columns.py | 0 .../5d4d936e9c_index_board_jobpost.py | 0 .../80a7e6c31df_index_jobpost_datetime.py | 0 .../versions/95eacca1a9d_jobapps.py | 0 .../c039166ecc6_board_auto_posting_tags.py | 0 .../versions/c20bd2c9b35_campaigns.py | 0 .../c2a626c5618_candidate_feedback.py | 0 .../versions/c55612fb52a_organization.py | 0 ...c5f5_one_campaign_action_saved_per_user.py | 0 .../d19802512a1_created_at_is_not_nullable.py | 0 .../d286e09aee1_created_updated_serv.py | 0 .../da1dfcda8b3_location_scoped_to_board.py | 0 .../versions/e49788bea4a_anon_job_view.py | 0 .../ec6faeb2eec_boards_de_linked_fro.py | 0 .../f66e99eb9f7_post_action_message.py | 0 requirements.txt | 1 + 82 files changed, 30 insertions(+), 26 deletions(-) rename {alembic => migrations}/README (100%) mode change 100644 => 100755 rename alembic.ini => migrations/alembic.ini (84%) rename {alembic => migrations}/env.py (55%) mode change 100644 => 100755 rename {alembic => migrations}/script.py.mako (100%) mode change 100644 => 100755 rename {alembic => migrations}/versions/1016f365bfa7_board_options.py (100%) rename {alembic => migrations}/versions/12bc1e8538a_json_dict_server_default.py (100%) rename {alembic => migrations}/versions/140e56666d9e_a_b_tests_for_headlines.py (100%) rename {alembic => migrations}/versions/15aede1ebe6f_domain_banned_at.py (100%) rename {alembic => migrations}/versions/1710bfac281a_discard_unused_org_and_team_models.py (100%) rename {alembic => migrations}/versions/1728cf57a9ac_domain_has_organization_profile_now.py (100%) rename {alembic => migrations}/versions/17869f3e044c_event_sessions.py (100%) rename {alembic => migrations}/versions/17a5476c8701_campaign_anon_models.py (100%) rename {alembic => migrations}/versions/17e2b3e7055a_add_auto_to_auto_tagging_tables.py (100%) rename {alembic => migrations}/versions/1b3f67ef6387_featured_board_flag.py (100%) rename {alembic => migrations}/versions/1bcc4e025b04_fullname.py (100%) rename {alembic => migrations}/versions/1ea37e8afd8_job_impressions.py (100%) rename {alembic => migrations}/versions/1fce9db567a5_nlp_tagging.py (100%) rename {alembic => migrations}/versions/1feef782ef45_jobpost_language.py (100%) rename {alembic => migrations}/versions/23546c6d07e2_geotargeted_column.py (100%) rename {alembic => migrations}/versions/241feed6748b_dismissed_campaign.py (100%) rename {alembic => migrations}/versions/2420dd9c9949_employer_response_me.py (100%) rename {alembic => migrations}/versions/260571e00aef_switch_to_userbase2.py (100%) rename {alembic => migrations}/versions/275e9a964a9f_added_remote_working_flag.py (100%) rename {alembic => migrations}/versions/28b492e71b0e_more_board_options.py (100%) rename {alembic => migrations}/versions/29fd6847a8e2_org_teams.py (100%) rename {alembic => migrations}/versions/2aab4c974a75_jobpost_search.py (100%) rename {alembic => migrations}/versions/2c1dec2d1dc5_index_jobapplication_jobpost_id.py (100%) rename {alembic => migrations}/versions/2c594a115c39_rsvp_group.py (100%) rename {alembic => migrations}/versions/2cd06f7444f8_private_types_and_ca.py (100%) rename {alembic => migrations}/versions/2d966f2ff84f_campaign_user_required.py (100%) rename {alembic => migrations}/versions/2fc05c34650b_index_job_views_by_job_first.py (100%) rename {alembic => migrations}/versions/30828b49527_start.py (100%) rename {alembic => migrations}/versions/326e0a8e6137_track_user_activity_in_boards.py (100%) rename {alembic => migrations}/versions/33a61e082fb_campaign_targeting.py (100%) rename {alembic => migrations}/versions/33de75e55858_adding_datetime_column.py (100%) rename {alembic => migrations}/versions/348bd4e2226b_track_user_presence.py (100%) rename {alembic => migrations}/versions/34e1d4b2f636_payscale_info.py (100%) rename {alembic => migrations}/versions/375bb2e7a4cc_json_resume_dict.py (100%) rename {alembic => migrations}/versions/3ac38e8fc78b_board_domains_and_lo.py (100%) rename {alembic => migrations}/versions/3e27bf9706ee_reports_track_users_.py (100%) rename {alembic => migrations}/versions/3fc1aa06cbeb_jobtype_flags.py (100%) rename {alembic => migrations}/versions/41d839fcecb4_colnames.py (100%) rename {alembic => migrations}/versions/434344d0a2d0_location_tagging.py (100%) rename {alembic => migrations}/versions/4365dd513103_job_applications_always_have_a_jobpost.py (100%) rename {alembic => migrations}/versions/441fd49059e3_preserve_user_s_name.py (100%) rename {alembic => migrations}/versions/449914911f93_post_admins.py (100%) rename {alembic => migrations}/versions/4616e70f18f4_index_fkey_to_user.py (100%) rename {alembic => migrations}/versions/465e724941d3_userblocked.py (100%) rename {alembic => migrations}/versions/470c8feb73cc_jobapplication_repli.py (100%) rename {alembic => migrations}/versions/472ca8e93765_boards.py (100%) rename {alembic => migrations}/versions/476608367f85_dns_domains.py (100%) rename {alembic => migrations}/versions/48f145e9d55f_locations.py (100%) rename {alembic => migrations}/versions/499df876f3f2_apply_message.py (100%) rename {alembic => migrations}/versions/49b48df19d82_campaign_action_sorting_sequence_and_.py (100%) rename {alembic => migrations}/versions/4b4924b0449b_save_language_detection_confidence.py (100%) rename {alembic => migrations}/versions/4bd08758f049_jobpost_twitter_account.py (100%) rename {alembic => migrations}/versions/4c8265da3caa_appwords.py (100%) rename {alembic => migrations}/versions/4d17424a3925_starred_jobs.py (100%) rename {alembic => migrations}/versions/4f639a4a5fc3_apphash.py (100%) rename {alembic => migrations}/versions/504175f63661_boardjobpost_doesn_t.py (100%) rename {alembic => migrations}/versions/525b125ec799_coin_toss_column_for_a_b_testing.py (100%) rename {alembic => migrations}/versions/5269eb17ff82_save_job_application_reply_date.py (100%) rename {alembic => migrations}/versions/576b32d25ea2_auto_posting_to_board_by_type_and_.py (100%) rename {alembic => migrations}/versions/57da2d78c9ca_use_uuid_key_for_event_sessions.py (100%) rename {alembic => migrations}/versions/593fed090308_user_opt_in_for_experimental_features.py (100%) rename {alembic => migrations}/versions/5a725fd5ddd6_additional_columns.py (100%) rename {alembic => migrations}/versions/5d4d936e9c_index_board_jobpost.py (100%) rename {alembic => migrations}/versions/80a7e6c31df_index_jobpost_datetime.py (100%) rename {alembic => migrations}/versions/95eacca1a9d_jobapps.py (100%) rename {alembic => migrations}/versions/c039166ecc6_board_auto_posting_tags.py (100%) rename {alembic => migrations}/versions/c20bd2c9b35_campaigns.py (100%) rename {alembic => migrations}/versions/c2a626c5618_candidate_feedback.py (100%) rename {alembic => migrations}/versions/c55612fb52a_organization.py (100%) rename {alembic => migrations}/versions/c5bdaccc5f5_one_campaign_action_saved_per_user.py (100%) rename {alembic => migrations}/versions/d19802512a1_created_at_is_not_nullable.py (100%) rename {alembic => migrations}/versions/d286e09aee1_created_updated_serv.py (100%) rename {alembic => migrations}/versions/da1dfcda8b3_location_scoped_to_board.py (100%) rename {alembic => migrations}/versions/e49788bea4a_anon_job_view.py (100%) rename {alembic => migrations}/versions/ec6faeb2eec_boards_de_linked_fro.py (100%) rename {alembic => migrations}/versions/f66e99eb9f7_post_action_message.py (100%) diff --git a/hasjob/__init__.py b/hasjob/__init__.py index d356f4f3d..8d293be43 100644 --- a/hasjob/__init__.py +++ b/hasjob/__init__.py @@ -3,6 +3,7 @@ import os.path import geoip2.database from flask import Flask +from flask_migrate import Migrate from flask_assets import Bundle from flask_rq import RQ from flask_mail import Mail @@ -36,7 +37,7 @@ coaster.app.init_app(app) db.init_app(app) db.app = app - +migrate = Migrate(app, db) app.geoip = None if 'GEOIP_PATH' in app.config: if not os.path.exists(app.config['GEOIP_PATH']): diff --git a/alembic/README b/migrations/README old mode 100644 new mode 100755 similarity index 100% rename from alembic/README rename to migrations/README diff --git a/alembic.ini b/migrations/alembic.ini similarity index 84% rename from alembic.ini rename to migrations/alembic.ini index 674dcc22d..f8ed4801f 100644 --- a/alembic.ini +++ b/migrations/alembic.ini @@ -1,13 +1,6 @@ # A generic, single database configuration. [alembic] -# path to migration scripts -script_location = alembic - -# flask sqlalchemy object name -# change this as appropriate -flask_sqlalchemy = db - # template used to generate migration files # file_template = %%(rev)s_%%(slug)s @@ -15,6 +8,7 @@ flask_sqlalchemy = db # the 'revision' command, regardless of autogenerate # revision_environment = false + # Logging configuration [loggers] keys = root,sqlalchemy,alembic diff --git a/alembic/env.py b/migrations/env.py old mode 100644 new mode 100755 similarity index 55% rename from alembic/env.py rename to migrations/env.py index 5d3dce3f5..459381606 --- a/alembic/env.py +++ b/migrations/env.py @@ -2,27 +2,25 @@ from alembic import context from sqlalchemy import engine_from_config, pool from logging.config import fileConfig -from flask_alembic import FlaskAlembicConfig +import logging # this is the Alembic Config object, which provides # access to the values within the .ini file in use. -config = FlaskAlembicConfig("alembic.ini") +config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') # add your model's MetaData object here # for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata from flask import current_app -with current_app.app_context(): - # set the database url - config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) - flask_app = __import__('%s' % (current_app.name), fromlist=[current_app.name]) - -db_obj_name = config.get_main_option("flask_sqlalchemy") -db_obj = getattr(flask_app, db_obj_name) -target_metadata = db_obj.metadata +config.set_main_option('sqlalchemy.url', + current_app.config.get('SQLALCHEMY_DATABASE_URI')) +target_metadata = current_app.extensions['migrate'].db.metadata # other values from the config, defined by the needs of env.py, # can be acquired: @@ -56,16 +54,26 @@ def run_migrations_online(): and associate a connection with the context. """ - engine = engine_from_config( - config.get_section(config.config_ini_section), - prefix='sqlalchemy.', - poolclass=pool.NullPool) + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.readthedocs.org/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + engine = engine_from_config(config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) connection = engine.connect() - context.configure( - connection=connection, - target_metadata=target_metadata - ) + context.configure(connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args) try: with context.begin_transaction(): diff --git a/alembic/script.py.mako b/migrations/script.py.mako old mode 100644 new mode 100755 similarity index 100% rename from alembic/script.py.mako rename to migrations/script.py.mako diff --git a/alembic/versions/1016f365bfa7_board_options.py b/migrations/versions/1016f365bfa7_board_options.py similarity index 100% rename from alembic/versions/1016f365bfa7_board_options.py rename to migrations/versions/1016f365bfa7_board_options.py diff --git a/alembic/versions/12bc1e8538a_json_dict_server_default.py b/migrations/versions/12bc1e8538a_json_dict_server_default.py similarity index 100% rename from alembic/versions/12bc1e8538a_json_dict_server_default.py rename to migrations/versions/12bc1e8538a_json_dict_server_default.py diff --git a/alembic/versions/140e56666d9e_a_b_tests_for_headlines.py b/migrations/versions/140e56666d9e_a_b_tests_for_headlines.py similarity index 100% rename from alembic/versions/140e56666d9e_a_b_tests_for_headlines.py rename to migrations/versions/140e56666d9e_a_b_tests_for_headlines.py diff --git a/alembic/versions/15aede1ebe6f_domain_banned_at.py b/migrations/versions/15aede1ebe6f_domain_banned_at.py similarity index 100% rename from alembic/versions/15aede1ebe6f_domain_banned_at.py rename to migrations/versions/15aede1ebe6f_domain_banned_at.py diff --git a/alembic/versions/1710bfac281a_discard_unused_org_and_team_models.py b/migrations/versions/1710bfac281a_discard_unused_org_and_team_models.py similarity index 100% rename from alembic/versions/1710bfac281a_discard_unused_org_and_team_models.py rename to migrations/versions/1710bfac281a_discard_unused_org_and_team_models.py diff --git a/alembic/versions/1728cf57a9ac_domain_has_organization_profile_now.py b/migrations/versions/1728cf57a9ac_domain_has_organization_profile_now.py similarity index 100% rename from alembic/versions/1728cf57a9ac_domain_has_organization_profile_now.py rename to migrations/versions/1728cf57a9ac_domain_has_organization_profile_now.py diff --git a/alembic/versions/17869f3e044c_event_sessions.py b/migrations/versions/17869f3e044c_event_sessions.py similarity index 100% rename from alembic/versions/17869f3e044c_event_sessions.py rename to migrations/versions/17869f3e044c_event_sessions.py diff --git a/alembic/versions/17a5476c8701_campaign_anon_models.py b/migrations/versions/17a5476c8701_campaign_anon_models.py similarity index 100% rename from alembic/versions/17a5476c8701_campaign_anon_models.py rename to migrations/versions/17a5476c8701_campaign_anon_models.py diff --git a/alembic/versions/17e2b3e7055a_add_auto_to_auto_tagging_tables.py b/migrations/versions/17e2b3e7055a_add_auto_to_auto_tagging_tables.py similarity index 100% rename from alembic/versions/17e2b3e7055a_add_auto_to_auto_tagging_tables.py rename to migrations/versions/17e2b3e7055a_add_auto_to_auto_tagging_tables.py diff --git a/alembic/versions/1b3f67ef6387_featured_board_flag.py b/migrations/versions/1b3f67ef6387_featured_board_flag.py similarity index 100% rename from alembic/versions/1b3f67ef6387_featured_board_flag.py rename to migrations/versions/1b3f67ef6387_featured_board_flag.py diff --git a/alembic/versions/1bcc4e025b04_fullname.py b/migrations/versions/1bcc4e025b04_fullname.py similarity index 100% rename from alembic/versions/1bcc4e025b04_fullname.py rename to migrations/versions/1bcc4e025b04_fullname.py diff --git a/alembic/versions/1ea37e8afd8_job_impressions.py b/migrations/versions/1ea37e8afd8_job_impressions.py similarity index 100% rename from alembic/versions/1ea37e8afd8_job_impressions.py rename to migrations/versions/1ea37e8afd8_job_impressions.py diff --git a/alembic/versions/1fce9db567a5_nlp_tagging.py b/migrations/versions/1fce9db567a5_nlp_tagging.py similarity index 100% rename from alembic/versions/1fce9db567a5_nlp_tagging.py rename to migrations/versions/1fce9db567a5_nlp_tagging.py diff --git a/alembic/versions/1feef782ef45_jobpost_language.py b/migrations/versions/1feef782ef45_jobpost_language.py similarity index 100% rename from alembic/versions/1feef782ef45_jobpost_language.py rename to migrations/versions/1feef782ef45_jobpost_language.py diff --git a/alembic/versions/23546c6d07e2_geotargeted_column.py b/migrations/versions/23546c6d07e2_geotargeted_column.py similarity index 100% rename from alembic/versions/23546c6d07e2_geotargeted_column.py rename to migrations/versions/23546c6d07e2_geotargeted_column.py diff --git a/alembic/versions/241feed6748b_dismissed_campaign.py b/migrations/versions/241feed6748b_dismissed_campaign.py similarity index 100% rename from alembic/versions/241feed6748b_dismissed_campaign.py rename to migrations/versions/241feed6748b_dismissed_campaign.py diff --git a/alembic/versions/2420dd9c9949_employer_response_me.py b/migrations/versions/2420dd9c9949_employer_response_me.py similarity index 100% rename from alembic/versions/2420dd9c9949_employer_response_me.py rename to migrations/versions/2420dd9c9949_employer_response_me.py diff --git a/alembic/versions/260571e00aef_switch_to_userbase2.py b/migrations/versions/260571e00aef_switch_to_userbase2.py similarity index 100% rename from alembic/versions/260571e00aef_switch_to_userbase2.py rename to migrations/versions/260571e00aef_switch_to_userbase2.py diff --git a/alembic/versions/275e9a964a9f_added_remote_working_flag.py b/migrations/versions/275e9a964a9f_added_remote_working_flag.py similarity index 100% rename from alembic/versions/275e9a964a9f_added_remote_working_flag.py rename to migrations/versions/275e9a964a9f_added_remote_working_flag.py diff --git a/alembic/versions/28b492e71b0e_more_board_options.py b/migrations/versions/28b492e71b0e_more_board_options.py similarity index 100% rename from alembic/versions/28b492e71b0e_more_board_options.py rename to migrations/versions/28b492e71b0e_more_board_options.py diff --git a/alembic/versions/29fd6847a8e2_org_teams.py b/migrations/versions/29fd6847a8e2_org_teams.py similarity index 100% rename from alembic/versions/29fd6847a8e2_org_teams.py rename to migrations/versions/29fd6847a8e2_org_teams.py diff --git a/alembic/versions/2aab4c974a75_jobpost_search.py b/migrations/versions/2aab4c974a75_jobpost_search.py similarity index 100% rename from alembic/versions/2aab4c974a75_jobpost_search.py rename to migrations/versions/2aab4c974a75_jobpost_search.py diff --git a/alembic/versions/2c1dec2d1dc5_index_jobapplication_jobpost_id.py b/migrations/versions/2c1dec2d1dc5_index_jobapplication_jobpost_id.py similarity index 100% rename from alembic/versions/2c1dec2d1dc5_index_jobapplication_jobpost_id.py rename to migrations/versions/2c1dec2d1dc5_index_jobapplication_jobpost_id.py diff --git a/alembic/versions/2c594a115c39_rsvp_group.py b/migrations/versions/2c594a115c39_rsvp_group.py similarity index 100% rename from alembic/versions/2c594a115c39_rsvp_group.py rename to migrations/versions/2c594a115c39_rsvp_group.py diff --git a/alembic/versions/2cd06f7444f8_private_types_and_ca.py b/migrations/versions/2cd06f7444f8_private_types_and_ca.py similarity index 100% rename from alembic/versions/2cd06f7444f8_private_types_and_ca.py rename to migrations/versions/2cd06f7444f8_private_types_and_ca.py diff --git a/alembic/versions/2d966f2ff84f_campaign_user_required.py b/migrations/versions/2d966f2ff84f_campaign_user_required.py similarity index 100% rename from alembic/versions/2d966f2ff84f_campaign_user_required.py rename to migrations/versions/2d966f2ff84f_campaign_user_required.py diff --git a/alembic/versions/2fc05c34650b_index_job_views_by_job_first.py b/migrations/versions/2fc05c34650b_index_job_views_by_job_first.py similarity index 100% rename from alembic/versions/2fc05c34650b_index_job_views_by_job_first.py rename to migrations/versions/2fc05c34650b_index_job_views_by_job_first.py diff --git a/alembic/versions/30828b49527_start.py b/migrations/versions/30828b49527_start.py similarity index 100% rename from alembic/versions/30828b49527_start.py rename to migrations/versions/30828b49527_start.py diff --git a/alembic/versions/326e0a8e6137_track_user_activity_in_boards.py b/migrations/versions/326e0a8e6137_track_user_activity_in_boards.py similarity index 100% rename from alembic/versions/326e0a8e6137_track_user_activity_in_boards.py rename to migrations/versions/326e0a8e6137_track_user_activity_in_boards.py diff --git a/alembic/versions/33a61e082fb_campaign_targeting.py b/migrations/versions/33a61e082fb_campaign_targeting.py similarity index 100% rename from alembic/versions/33a61e082fb_campaign_targeting.py rename to migrations/versions/33a61e082fb_campaign_targeting.py diff --git a/alembic/versions/33de75e55858_adding_datetime_column.py b/migrations/versions/33de75e55858_adding_datetime_column.py similarity index 100% rename from alembic/versions/33de75e55858_adding_datetime_column.py rename to migrations/versions/33de75e55858_adding_datetime_column.py diff --git a/alembic/versions/348bd4e2226b_track_user_presence.py b/migrations/versions/348bd4e2226b_track_user_presence.py similarity index 100% rename from alembic/versions/348bd4e2226b_track_user_presence.py rename to migrations/versions/348bd4e2226b_track_user_presence.py diff --git a/alembic/versions/34e1d4b2f636_payscale_info.py b/migrations/versions/34e1d4b2f636_payscale_info.py similarity index 100% rename from alembic/versions/34e1d4b2f636_payscale_info.py rename to migrations/versions/34e1d4b2f636_payscale_info.py diff --git a/alembic/versions/375bb2e7a4cc_json_resume_dict.py b/migrations/versions/375bb2e7a4cc_json_resume_dict.py similarity index 100% rename from alembic/versions/375bb2e7a4cc_json_resume_dict.py rename to migrations/versions/375bb2e7a4cc_json_resume_dict.py diff --git a/alembic/versions/3ac38e8fc78b_board_domains_and_lo.py b/migrations/versions/3ac38e8fc78b_board_domains_and_lo.py similarity index 100% rename from alembic/versions/3ac38e8fc78b_board_domains_and_lo.py rename to migrations/versions/3ac38e8fc78b_board_domains_and_lo.py diff --git a/alembic/versions/3e27bf9706ee_reports_track_users_.py b/migrations/versions/3e27bf9706ee_reports_track_users_.py similarity index 100% rename from alembic/versions/3e27bf9706ee_reports_track_users_.py rename to migrations/versions/3e27bf9706ee_reports_track_users_.py diff --git a/alembic/versions/3fc1aa06cbeb_jobtype_flags.py b/migrations/versions/3fc1aa06cbeb_jobtype_flags.py similarity index 100% rename from alembic/versions/3fc1aa06cbeb_jobtype_flags.py rename to migrations/versions/3fc1aa06cbeb_jobtype_flags.py diff --git a/alembic/versions/41d839fcecb4_colnames.py b/migrations/versions/41d839fcecb4_colnames.py similarity index 100% rename from alembic/versions/41d839fcecb4_colnames.py rename to migrations/versions/41d839fcecb4_colnames.py diff --git a/alembic/versions/434344d0a2d0_location_tagging.py b/migrations/versions/434344d0a2d0_location_tagging.py similarity index 100% rename from alembic/versions/434344d0a2d0_location_tagging.py rename to migrations/versions/434344d0a2d0_location_tagging.py diff --git a/alembic/versions/4365dd513103_job_applications_always_have_a_jobpost.py b/migrations/versions/4365dd513103_job_applications_always_have_a_jobpost.py similarity index 100% rename from alembic/versions/4365dd513103_job_applications_always_have_a_jobpost.py rename to migrations/versions/4365dd513103_job_applications_always_have_a_jobpost.py diff --git a/alembic/versions/441fd49059e3_preserve_user_s_name.py b/migrations/versions/441fd49059e3_preserve_user_s_name.py similarity index 100% rename from alembic/versions/441fd49059e3_preserve_user_s_name.py rename to migrations/versions/441fd49059e3_preserve_user_s_name.py diff --git a/alembic/versions/449914911f93_post_admins.py b/migrations/versions/449914911f93_post_admins.py similarity index 100% rename from alembic/versions/449914911f93_post_admins.py rename to migrations/versions/449914911f93_post_admins.py diff --git a/alembic/versions/4616e70f18f4_index_fkey_to_user.py b/migrations/versions/4616e70f18f4_index_fkey_to_user.py similarity index 100% rename from alembic/versions/4616e70f18f4_index_fkey_to_user.py rename to migrations/versions/4616e70f18f4_index_fkey_to_user.py diff --git a/alembic/versions/465e724941d3_userblocked.py b/migrations/versions/465e724941d3_userblocked.py similarity index 100% rename from alembic/versions/465e724941d3_userblocked.py rename to migrations/versions/465e724941d3_userblocked.py diff --git a/alembic/versions/470c8feb73cc_jobapplication_repli.py b/migrations/versions/470c8feb73cc_jobapplication_repli.py similarity index 100% rename from alembic/versions/470c8feb73cc_jobapplication_repli.py rename to migrations/versions/470c8feb73cc_jobapplication_repli.py diff --git a/alembic/versions/472ca8e93765_boards.py b/migrations/versions/472ca8e93765_boards.py similarity index 100% rename from alembic/versions/472ca8e93765_boards.py rename to migrations/versions/472ca8e93765_boards.py diff --git a/alembic/versions/476608367f85_dns_domains.py b/migrations/versions/476608367f85_dns_domains.py similarity index 100% rename from alembic/versions/476608367f85_dns_domains.py rename to migrations/versions/476608367f85_dns_domains.py diff --git a/alembic/versions/48f145e9d55f_locations.py b/migrations/versions/48f145e9d55f_locations.py similarity index 100% rename from alembic/versions/48f145e9d55f_locations.py rename to migrations/versions/48f145e9d55f_locations.py diff --git a/alembic/versions/499df876f3f2_apply_message.py b/migrations/versions/499df876f3f2_apply_message.py similarity index 100% rename from alembic/versions/499df876f3f2_apply_message.py rename to migrations/versions/499df876f3f2_apply_message.py diff --git a/alembic/versions/49b48df19d82_campaign_action_sorting_sequence_and_.py b/migrations/versions/49b48df19d82_campaign_action_sorting_sequence_and_.py similarity index 100% rename from alembic/versions/49b48df19d82_campaign_action_sorting_sequence_and_.py rename to migrations/versions/49b48df19d82_campaign_action_sorting_sequence_and_.py diff --git a/alembic/versions/4b4924b0449b_save_language_detection_confidence.py b/migrations/versions/4b4924b0449b_save_language_detection_confidence.py similarity index 100% rename from alembic/versions/4b4924b0449b_save_language_detection_confidence.py rename to migrations/versions/4b4924b0449b_save_language_detection_confidence.py diff --git a/alembic/versions/4bd08758f049_jobpost_twitter_account.py b/migrations/versions/4bd08758f049_jobpost_twitter_account.py similarity index 100% rename from alembic/versions/4bd08758f049_jobpost_twitter_account.py rename to migrations/versions/4bd08758f049_jobpost_twitter_account.py diff --git a/alembic/versions/4c8265da3caa_appwords.py b/migrations/versions/4c8265da3caa_appwords.py similarity index 100% rename from alembic/versions/4c8265da3caa_appwords.py rename to migrations/versions/4c8265da3caa_appwords.py diff --git a/alembic/versions/4d17424a3925_starred_jobs.py b/migrations/versions/4d17424a3925_starred_jobs.py similarity index 100% rename from alembic/versions/4d17424a3925_starred_jobs.py rename to migrations/versions/4d17424a3925_starred_jobs.py diff --git a/alembic/versions/4f639a4a5fc3_apphash.py b/migrations/versions/4f639a4a5fc3_apphash.py similarity index 100% rename from alembic/versions/4f639a4a5fc3_apphash.py rename to migrations/versions/4f639a4a5fc3_apphash.py diff --git a/alembic/versions/504175f63661_boardjobpost_doesn_t.py b/migrations/versions/504175f63661_boardjobpost_doesn_t.py similarity index 100% rename from alembic/versions/504175f63661_boardjobpost_doesn_t.py rename to migrations/versions/504175f63661_boardjobpost_doesn_t.py diff --git a/alembic/versions/525b125ec799_coin_toss_column_for_a_b_testing.py b/migrations/versions/525b125ec799_coin_toss_column_for_a_b_testing.py similarity index 100% rename from alembic/versions/525b125ec799_coin_toss_column_for_a_b_testing.py rename to migrations/versions/525b125ec799_coin_toss_column_for_a_b_testing.py diff --git a/alembic/versions/5269eb17ff82_save_job_application_reply_date.py b/migrations/versions/5269eb17ff82_save_job_application_reply_date.py similarity index 100% rename from alembic/versions/5269eb17ff82_save_job_application_reply_date.py rename to migrations/versions/5269eb17ff82_save_job_application_reply_date.py diff --git a/alembic/versions/576b32d25ea2_auto_posting_to_board_by_type_and_.py b/migrations/versions/576b32d25ea2_auto_posting_to_board_by_type_and_.py similarity index 100% rename from alembic/versions/576b32d25ea2_auto_posting_to_board_by_type_and_.py rename to migrations/versions/576b32d25ea2_auto_posting_to_board_by_type_and_.py diff --git a/alembic/versions/57da2d78c9ca_use_uuid_key_for_event_sessions.py b/migrations/versions/57da2d78c9ca_use_uuid_key_for_event_sessions.py similarity index 100% rename from alembic/versions/57da2d78c9ca_use_uuid_key_for_event_sessions.py rename to migrations/versions/57da2d78c9ca_use_uuid_key_for_event_sessions.py diff --git a/alembic/versions/593fed090308_user_opt_in_for_experimental_features.py b/migrations/versions/593fed090308_user_opt_in_for_experimental_features.py similarity index 100% rename from alembic/versions/593fed090308_user_opt_in_for_experimental_features.py rename to migrations/versions/593fed090308_user_opt_in_for_experimental_features.py diff --git a/alembic/versions/5a725fd5ddd6_additional_columns.py b/migrations/versions/5a725fd5ddd6_additional_columns.py similarity index 100% rename from alembic/versions/5a725fd5ddd6_additional_columns.py rename to migrations/versions/5a725fd5ddd6_additional_columns.py diff --git a/alembic/versions/5d4d936e9c_index_board_jobpost.py b/migrations/versions/5d4d936e9c_index_board_jobpost.py similarity index 100% rename from alembic/versions/5d4d936e9c_index_board_jobpost.py rename to migrations/versions/5d4d936e9c_index_board_jobpost.py diff --git a/alembic/versions/80a7e6c31df_index_jobpost_datetime.py b/migrations/versions/80a7e6c31df_index_jobpost_datetime.py similarity index 100% rename from alembic/versions/80a7e6c31df_index_jobpost_datetime.py rename to migrations/versions/80a7e6c31df_index_jobpost_datetime.py diff --git a/alembic/versions/95eacca1a9d_jobapps.py b/migrations/versions/95eacca1a9d_jobapps.py similarity index 100% rename from alembic/versions/95eacca1a9d_jobapps.py rename to migrations/versions/95eacca1a9d_jobapps.py diff --git a/alembic/versions/c039166ecc6_board_auto_posting_tags.py b/migrations/versions/c039166ecc6_board_auto_posting_tags.py similarity index 100% rename from alembic/versions/c039166ecc6_board_auto_posting_tags.py rename to migrations/versions/c039166ecc6_board_auto_posting_tags.py diff --git a/alembic/versions/c20bd2c9b35_campaigns.py b/migrations/versions/c20bd2c9b35_campaigns.py similarity index 100% rename from alembic/versions/c20bd2c9b35_campaigns.py rename to migrations/versions/c20bd2c9b35_campaigns.py diff --git a/alembic/versions/c2a626c5618_candidate_feedback.py b/migrations/versions/c2a626c5618_candidate_feedback.py similarity index 100% rename from alembic/versions/c2a626c5618_candidate_feedback.py rename to migrations/versions/c2a626c5618_candidate_feedback.py diff --git a/alembic/versions/c55612fb52a_organization.py b/migrations/versions/c55612fb52a_organization.py similarity index 100% rename from alembic/versions/c55612fb52a_organization.py rename to migrations/versions/c55612fb52a_organization.py diff --git a/alembic/versions/c5bdaccc5f5_one_campaign_action_saved_per_user.py b/migrations/versions/c5bdaccc5f5_one_campaign_action_saved_per_user.py similarity index 100% rename from alembic/versions/c5bdaccc5f5_one_campaign_action_saved_per_user.py rename to migrations/versions/c5bdaccc5f5_one_campaign_action_saved_per_user.py diff --git a/alembic/versions/d19802512a1_created_at_is_not_nullable.py b/migrations/versions/d19802512a1_created_at_is_not_nullable.py similarity index 100% rename from alembic/versions/d19802512a1_created_at_is_not_nullable.py rename to migrations/versions/d19802512a1_created_at_is_not_nullable.py diff --git a/alembic/versions/d286e09aee1_created_updated_serv.py b/migrations/versions/d286e09aee1_created_updated_serv.py similarity index 100% rename from alembic/versions/d286e09aee1_created_updated_serv.py rename to migrations/versions/d286e09aee1_created_updated_serv.py diff --git a/alembic/versions/da1dfcda8b3_location_scoped_to_board.py b/migrations/versions/da1dfcda8b3_location_scoped_to_board.py similarity index 100% rename from alembic/versions/da1dfcda8b3_location_scoped_to_board.py rename to migrations/versions/da1dfcda8b3_location_scoped_to_board.py diff --git a/alembic/versions/e49788bea4a_anon_job_view.py b/migrations/versions/e49788bea4a_anon_job_view.py similarity index 100% rename from alembic/versions/e49788bea4a_anon_job_view.py rename to migrations/versions/e49788bea4a_anon_job_view.py diff --git a/alembic/versions/ec6faeb2eec_boards_de_linked_fro.py b/migrations/versions/ec6faeb2eec_boards_de_linked_fro.py similarity index 100% rename from alembic/versions/ec6faeb2eec_boards_de_linked_fro.py rename to migrations/versions/ec6faeb2eec_boards_de_linked_fro.py diff --git a/alembic/versions/f66e99eb9f7_post_action_message.py b/migrations/versions/f66e99eb9f7_post_action_message.py similarity index 100% rename from alembic/versions/f66e99eb9f7_post_action_message.py rename to migrations/versions/f66e99eb9f7_post_action_message.py diff --git a/requirements.txt b/requirements.txt index 50b2b08bb..ba5e5bdf2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,3 +29,4 @@ geoip2 git+https://github.com/hasgeek/coaster git+https://github.com/hasgeek/flask-lastuser git+https://github.com/hasgeek/baseframe +Flask-Migrate From c6afe6350de84e98169da140d7397cee66c3aa3e Mon Sep 17 00:00:00 2001 From: Bibhas Date: Wed, 14 Jun 2017 15:42:26 +0530 Subject: [PATCH 19/31] added runtests.sh --- .travis.yml | 5 +++-- runtests.sh | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100755 runtests.sh diff --git a/.travis.yml b/.travis.yml index 330afab44..9b02f3efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,9 @@ install: - pip install -r test_requirements.txt - pip install -r requirements.txt - npm install casperjs -script: - - nosetests -v tests +script: ./runtests.sh +after_success: + - coveralls addons: postgresql: "9.4" services: diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 000000000..06e76521c --- /dev/null +++ b/runtests.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +export FLASK_ENV="TESTING" +coverage run `which nosetests -v tests` +coverage report From 93597553a475ac9fdc0b4dfe5a2cc1c00e445969 Mon Sep 17 00:00:00 2001 From: Bibhas Date: Wed, 14 Jun 2017 23:20:44 +0530 Subject: [PATCH 20/31] Update runtests.sh --- runtests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtests.sh b/runtests.sh index 06e76521c..2e9cb98b5 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,5 +1,5 @@ #!/bin/sh set -e export FLASK_ENV="TESTING" -coverage run `which nosetests -v tests` -coverage report +coverage run `which nosetests` "$@" +coverage report -m From dac00e419c2df8fbfe982b16413eaacb3b2febb2 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 15 Jun 2017 11:05:51 +0530 Subject: [PATCH 21/31] coaster.utils is now distinct --- hasjob/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/models/__init__.py b/hasjob/models/__init__.py index dea9d210e..6363ad9b3 100644 --- a/hasjob/models/__init__.py +++ b/hasjob/models/__init__.py @@ -2,7 +2,7 @@ # flake8: noqa from datetime import timedelta -from coaster import LabeledEnum +from coaster.utils import LabeledEnum from coaster.db import db from coaster.sqlalchemy import (BaseMixin, BaseNameMixin, TimestampMixin, BaseScopedIdMixin, BaseScopedNameMixin, CoordinatesMixin, make_timestamp_columns) From 2878b2645423d75a22f19eaf1b39de860b944d72 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 15 Jun 2017 11:08:20 +0530 Subject: [PATCH 22/31] coaster.utils is now distinct --- hasjob/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hasjob/utils.py b/hasjob/utils.py index 6ebd4710b..710a7d35f 100644 --- a/hasjob/utils.py +++ b/hasjob/utils.py @@ -1,6 +1,6 @@ import re from random import randint, choice -from coaster import simplify_text +from coaster.utils import simplify_text NO_NUM_RE = re.compile('[^0-9]+', re.UNICODE) From 1922df7b169187a008c3d4f1bf63cabdc1484fb7 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 15 Jun 2017 16:29:05 +0530 Subject: [PATCH 23/31] Update Flask-Redis --- hasjob/__init__.py | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hasjob/__init__.py b/hasjob/__init__.py index 8d293be43..952bfa3ca 100644 --- a/hasjob/__init__.py +++ b/hasjob/__init__.py @@ -7,7 +7,7 @@ from flask_assets import Bundle from flask_rq import RQ from flask_mail import Mail -from flask_redis import Redis +from flask_redis import FlaskRedis from flask_lastuser import Lastuser from flask_lastuser.sqlalchemy import UserManager from baseframe import baseframe, assets, Version @@ -21,7 +21,7 @@ app.static_folder = 'static' mail = Mail() lastuser = Lastuser() -redis_store = Redis() +redis_store = FlaskRedis() # Second, setup assets version = Version(__version__) diff --git a/requirements.txt b/requirements.txt index ba5e5bdf2..08ca9339c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ Flask-Assets Flask-Uploads Flask-RQ Flask-Testing -Flask-Redis +Flask-Redis>=0.3.0 Pillow sqlalchemy_utils SQLAlchemy>=1.0.0 From 78e54bcfd76f1e35a4da4fe134c094d29d2bdb7c Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 15 Jun 2017 16:29:20 +0530 Subject: [PATCH 24/31] Switch from uuid1mc to uuid4 As per https://github.com/hasgeek/coaster/issues/120 --- hasjob/models/user.py | 7 ++++--- hasjob/views/helper.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/hasjob/models/user.py b/hasjob/models/user.py index 8d203c59e..fefa87b52 100644 --- a/hasjob/models/user.py +++ b/hasjob/models/user.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from datetime import datetime, timedelta +from uuid import uuid4 from flask import request from flask_lastuser.sqlalchemy import UserBase2 from sqlalchemy_utils.types import UUIDType -from coaster.utils import unicode_http_header, uuid1mc +from coaster.utils import unicode_http_header from coaster.sqlalchemy import JsonDict from baseframe import _, cache from . import db, BaseMixin @@ -69,7 +70,7 @@ class EventSessionBase(object): @classmethod def new_from_request(cls, request): instance = cls() - instance.uuid = uuid1mc() # Don't wait for database commit to generate this + instance.uuid = uuid4() # Don't wait for database commit to generate this instance.created_at = datetime.utcnow() instance.referrer = unicode_http_header(request.referrer)[:2083] if request.referrer else None instance.utm_source = request.args.get('utm_source', u'')[:250] or None @@ -120,7 +121,7 @@ class EventSession(EventSessionBase, BaseMixin, db.Model): # See https://support.google.com/analytics/answer/2731565?hl=en for source of inspiration __tablename__ = 'event_session' # UUID for public lookup - uuid = db.Column(UUIDType(binary=False), nullable=True, default=uuid1mc, unique=True) + uuid = db.Column(UUIDType(binary=False), nullable=True, default=uuid4, unique=True) # Who is this user? If known user_id = db.Column(None, db.ForeignKey('user.id'), nullable=True, index=True) user = db.relationship(User) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index a1aae739a..a834a81d4 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -2,6 +2,7 @@ from os import path from datetime import datetime, timedelta +from uuid import uuid4 from urlparse import urljoin from urllib import quote, quote_plus import hashlib @@ -14,7 +15,6 @@ from flask import Markup, request, g, session from flask_rq import job from flask_lastuser import signal_user_looked_up -from coaster.utils import uuid1mc from coaster.sqlalchemy import failsafe_add from baseframe import _, cache, get_timezone from baseframe.signals import form_validation_error, form_validation_success @@ -92,7 +92,7 @@ def load_user_data(user): session.pop('au', None) else: if not session.get('au'): - session['au'] = u'test-' + unicode(uuid1mc()) + session['au'] = u'test-' + unicode(uuid4()) g.esession = EventSessionBase.new_from_request(request) g.event_data['anon_cookie_test'] = session['au'] # elif session['au'] == 'test': # Legacy test cookie, original request now lost @@ -116,7 +116,7 @@ def load_user_data(user): if not anon_user: # XXX: We got a fake value? This shouldn't happen g.event_data['anon_cookie_test'] = session['au'] - session['au'] = u'test-' + unicode(uuid1mc()) # Try again + session['au'] = u'test-' + unicode(uuid4()) # Try again g.esession = EventSessionBase.new_from_request(request) else: g.anon_user = anon_user From f44ed99fa0e70d62d89862fa74170dad9bd525ab Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Tue, 20 Jun 2017 10:47:58 +0530 Subject: [PATCH 25/31] Use SUUID for Campaign and CampaignAction This will need revisiting with hasgeek/coaster#123 though. --- hasjob/views/campaign.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hasjob/views/campaign.py b/hasjob/views/campaign.py index eaa6be0c2..db68b24da 100644 --- a/hasjob/views/campaign.py +++ b/hasjob/views/campaign.py @@ -6,7 +6,7 @@ from pytz import UTC import unicodecsv from flask import g, request, flash, url_for, redirect, render_template, Markup, abort -from coaster.utils import buid, make_name +from coaster.utils import suuid, make_name from coaster.views import load_model, load_models from baseframe.forms import render_form, render_delete_sqla, render_redirect from .. import app, lastuser @@ -44,7 +44,7 @@ def campaign_new(): if form.validate_on_submit(): campaign = Campaign(user=g.user) form.populate_obj(campaign) - campaign.name = buid() # Use a random name since it's also used in user action submit forms + campaign.name = suuid() # Use a random name since it's also used in user action submit forms db.session.add(campaign) db.session.commit() flash(u"Created a campaign", 'success') @@ -98,7 +98,7 @@ def campaign_action_new(campaign): action = CampaignAction(campaign=campaign) db.session.add(action) form.populate_obj(action) - action.make_name() + action.name = suuid() # Use a random name since it needs to be permanent db.session.commit() flash(u"Added campaign action ‘%s’" % action.title, 'interactive') return redirect(url_for('campaign_view', campaign=campaign.name), code=303) @@ -116,7 +116,6 @@ def campaign_action_edit(campaign, action): form = CampaignActionForm(obj=action) if form.validate_on_submit(): form.populate_obj(action) - action.make_name() db.session.commit() flash(u"Edited campaign action ‘%s’" % action.title, 'interactive') return redirect(url_for('campaign_view', campaign=campaign.name), code=303) From 066497ba47ea3282d29726df77aa4cde2404a978 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 28 Jun 2017 15:22:35 +0530 Subject: [PATCH 26/31] Remove obsolete init_for calls. --- manage.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/manage.py b/manage.py index c743bf2ee..57461658c 100755 --- a/manage.py +++ b/manage.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from coaster.manage import init_manager, manager, Manager +from coaster.manage import init_manager, Manager import hasjob import hasjob.models as models @@ -13,8 +13,8 @@ periodic = Manager(usage="Periodic tasks from cron (with recommended intervals)") -@periodic.option('-e', '--env', default='dev', help="runtime env [default 'dev']") -def sessions(env): +@periodic.command +def sessions(): """Sweep user sessions to close all inactive sessions (10m)""" es = models.EventSession # Close all sessions that have been inactive for >= 30 minutes @@ -24,17 +24,9 @@ def sessions(env): db.session.commit() -# Legacy call -@manager.option('-e', '--env', default='dev', help="runtime env [default 'dev']") -def sweep(env): - """Sweep user sessions to close all inactive sessions [deprecated]""" - sessions(env) - - -@periodic.option('-e', '--env', default='dev', help="runtime env [default 'dev']") -def impressions(env): +@periodic.command +def impressions(): """Recount impressions for jobposts in the dirty list (5m)""" - manager.init_for(env) views.helper.update_dirty_impression_counts() From eea50d1ec59e62d4337d8f34a41278dd6ef6307c Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Wed, 28 Jun 2017 16:06:34 +0530 Subject: [PATCH 27/31] Updated installation notes (added note on FLASK_ENV) --- README.md | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 337078a64..32276030b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Hasjob Code for Hasjob, HasGeek’s job board at https://hasjob.co/ -Copyright © 2010-2016 by HasGeek +Copyright © 2010-2017 by HasGeek Hasjob’s code is open source under the AGPL v3 license (see LICENSE.txt), but the name ‘Hasjob’ and the distinctive appearance of the job board are @@ -24,11 +24,20 @@ To have your contributions merged back into the master repository, you must agree to assign copyright to HasGeek and must assert that you have the right to make this assignment. ------ +## Installation -Hasjob can be used with Docker (recommended for quick start) or the harder way with a manual setup (recommended for getting involved). +Hasjob can be used with Docker (recommended for quick start) or the harder way with a manual setup (recommended for getting involved). The Docker approach is unmaintained at this time. Let us know if something doesn't work. -## With Docker +### Pick an environment + +Hasjob requires a `FLASK_ENV` environment variable set to one of the following values, depending on whether the deployment is in development or production: + +* `DEVELOPMENT` or `development` or `dev` (default) +* `PRODUCTION` or `production` or `prod` + +In a production environment, you must set `FLASK_ENV` globally for it to be available across processes. On Ubuntu/Debian systems, add it to `/etc/environment` and reboot. + +### With Docker #### Install and run with Docker @@ -58,11 +67,11 @@ Hasjob can be used with Docker (recommended for quick start) or the harder way w * You can edit the server name and Lastuser settings in `docker-compose.yml` -## Without Docker +### Without Docker Hasjob without Docker requires manual installation of all dependencies. -### Postgres and Redis +#### Postgres and Redis Hasjob requires Postgres >= 9.4 and Redis. To set up a Postgres DB: @@ -83,7 +92,7 @@ Edit `instance/development.py` to set the variable `SQLALCHEMY_DATABASE_URI` to Redis does not require special configuration, but must listen on localhost and port 6379 (default). -### Local URLs +#### Local URLs Hasjob makes use of subdomains to serve different sub-boards for jobs. To set it up: @@ -97,11 +106,11 @@ Hasjob makes use of subdomains to serve different sub-boards for jobs. To set it * Edit `instance/development.py` and change `SERVER_NAME` to `'hasjob.your-machine.local:5000'` -### Install dependencies +#### Install dependencies Hasjob runs on [Python](https://www.python.org) with the [Flask](http://flask.pocoo.org/) microframework. -#### Virutalenv + pip +##### Virutalenv + pip If you are going to use a computer on which you would work on multiple Python based projects, [Virtualenv](docs.python-guide.org/en/latest/dev/virtualenvs/) is strongly recommended to ensure Hasjob’s elaborate and sometimes version-specific requirements doesn't clash with anything else. @@ -127,20 +136,18 @@ Before you run the server in development mode, make sure you have Postgres serve $ python runserver.py -## Create root board +### Create root board Some functionality in Hasjob requires the presence of a sub-board named `www`. Create it by visiting `http://hasjob.your-machine.local:5000/board` (or the `/board` page on whatever hostname and port you used for your installation). The `www` board is a special-case to refer to the root website. -## Periodic jobs +### Periodic jobs Hasjob requires some tasks to be run in periodic background jobs. These can be called from cron. Use `crontab -e` as the user account running Hasjob and add: - */10 * * * * cd /path/to/hasjob; python manage.py periodic sessions -e dev - */5 * * * * cd /path/to/hasjob; python manage.py periodic impressions -e dev - -Switch from `dev` to `production` in a production environment. + */10 * * * * cd /path/to/hasjob; python manage.py periodic sessions + */5 * * * * cd /path/to/hasjob; python manage.py periodic impressions -## Testing +### Testing Tests are outdated at this time, but whatever tests exist are written in [CasperJS](http://casperjs.org/). @@ -150,7 +157,7 @@ Edit the top few lines of test file `tests/test_job_post.js` with the URL, usern Run the test with `casperjs test tests/test_job_post.js`. -## Other notes +### Other notes If you encounter a problem setting up, please look at existing issue reports on GitHub before filing a new issue. This code is the same version used in From 7d0602dd13a146e5d712c6326b45869998592140 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 29 Jun 2017 02:59:15 +0530 Subject: [PATCH 28/31] Don't hit Redis if there's no data --- hasjob/views/helper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hasjob/views/helper.py b/hasjob/views/helper.py index a834a81d4..4c552979f 100644 --- a/hasjob/views/helper.py +++ b/hasjob/views/helper.py @@ -536,11 +536,13 @@ def save_jobview(event_session_id, jobpost_id, bgroup, viewed_time): def mark_dirty_impression_counts(jobpost_ids): - redis_store.sadd('hasjob/dirty_impression_counts', *jobpost_ids) + if jobpost_ids: + redis_store.sadd('hasjob/dirty_impression_counts', *jobpost_ids) def remove_dirty_impression_counts(jobpost_ids): - redis_store.srem('hasjob/dirty_impression_counts', *jobpost_ids) + if jobpost_ids: + redis_store.srem('hasjob/dirty_impression_counts', *jobpost_ids) def list_dirty_impression_counts(): From 297bb1477b060bb23a70c0f0d8affa57b19eae29 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Thu, 13 Jul 2017 15:56:58 +0530 Subject: [PATCH 29/31] Use UuidMixin. Fixes #398. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inserts random UUIDs into old sessions that didn’t have a UUID (a bug) --- hasjob/models/user.py | 9 +--- migrations/script.py.mako | 1 + .../versions/a55bb6a85c83_use_uuidmixin.py | 54 +++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 migrations/versions/a55bb6a85c83_use_uuidmixin.py diff --git a/hasjob/models/user.py b/hasjob/models/user.py index fefa87b52..583a4f93e 100644 --- a/hasjob/models/user.py +++ b/hasjob/models/user.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- from datetime import datetime, timedelta -from uuid import uuid4 from flask import request from flask_lastuser.sqlalchemy import UserBase2 -from sqlalchemy_utils.types import UUIDType from coaster.utils import unicode_http_header -from coaster.sqlalchemy import JsonDict +from coaster.sqlalchemy import UuidMixin, JsonDict from baseframe import _, cache from . import db, BaseMixin @@ -70,7 +68,6 @@ class EventSessionBase(object): @classmethod def new_from_request(cls, request): instance = cls() - instance.uuid = uuid4() # Don't wait for database commit to generate this instance.created_at = datetime.utcnow() instance.referrer = unicode_http_header(request.referrer)[:2083] if request.referrer else None instance.utm_source = request.args.get('utm_source', u'')[:250] or None @@ -112,7 +109,7 @@ def load_from_cache(self, key, eventclass): self.events = [eventclass(**kwargs) for kwargs in result[key]] -class EventSession(EventSessionBase, BaseMixin, db.Model): +class EventSession(EventSessionBase, UuidMixin, BaseMixin, db.Model): """ A user's event session. Groups together user activity within a single time period. """ @@ -120,8 +117,6 @@ class EventSession(EventSessionBase, BaseMixin, db.Model): # See https://support.google.com/analytics/answer/2731565?hl=en for source of inspiration __tablename__ = 'event_session' - # UUID for public lookup - uuid = db.Column(UUIDType(binary=False), nullable=True, default=uuid4, unique=True) # Who is this user? If known user_id = db.Column(None, db.ForeignKey('user.id'), nullable=True, index=True) user = db.relationship(User) diff --git a/migrations/script.py.mako b/migrations/script.py.mako index 95702017e..ed970d8c9 100755 --- a/migrations/script.py.mako +++ b/migrations/script.py.mako @@ -14,6 +14,7 @@ from alembic import op import sqlalchemy as sa ${imports if imports else ""} + def upgrade(): ${upgrades if upgrades else "pass"} diff --git a/migrations/versions/a55bb6a85c83_use_uuidmixin.py b/migrations/versions/a55bb6a85c83_use_uuidmixin.py new file mode 100644 index 000000000..a9adc1bcb --- /dev/null +++ b/migrations/versions/a55bb6a85c83_use_uuidmixin.py @@ -0,0 +1,54 @@ +"""Use UuidMixin + +Revision ID: a55bb6a85c83 +Revises: 15aede1ebe6f +Create Date: 2017-07-13 14:23:03.655538 + +""" + +# revision identifiers, used by Alembic. +revision = 'a55bb6a85c83' +down_revision = '15aede1ebe6f' + +from uuid import uuid4 +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import table, column +from sqlalchemy_utils import UUIDType +from progressbar import ProgressBar +import progressbar.widgets + +event_session = table('event_session', + column('id', sa.Integer()), + column('uuid', UUIDType(binary=False)), + ) + + +def get_progressbar(label, maxval): + return ProgressBar(maxval=maxval, + widgets=[ + label, ': ', + progressbar.widgets.Percentage(), ' ', + progressbar.widgets.Bar(), ' ', + progressbar.widgets.ETA(), ' ' + ]) + + +def upgrade(): + conn = op.get_bind() + count = conn.scalar( + sa.select( + [sa.func.count('*')] + ).select_from(event_session).where(event_session.c.uuid == None)) # NOQA + progress = get_progressbar("UUIDs", count) + progress.start() + items = conn.execute(sa.select([event_session.c.id]).where(event_session.c.uuid == None)) # NOQA + for counter, item in enumerate(items): + conn.execute(sa.update(event_session).where(event_session.c.id == item.id).values(uuid=uuid4())) + progress.update(counter) + progress.finish() + op.alter_column('event_session', 'uuid', existing_type=UUIDType(binary=False), nullable=False) + + +def downgrade(): + op.alter_column('event_session', 'uuid', existing_type=UUIDType(binary=False), nullable=True) From 20a771ecf624bc91d9fd8a2c2a81e8200468cabe Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Fri, 14 Jul 2017 13:18:14 +0530 Subject: [PATCH 30/31] Add progressbar2 requirement --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 08ca9339c..f19437954 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,3 +30,4 @@ git+https://github.com/hasgeek/coaster git+https://github.com/hasgeek/flask-lastuser git+https://github.com/hasgeek/baseframe Flask-Migrate +progressbar2 From 478558a8e8d553f07c4021a3db8dba5bb5bf953a Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Fri, 14 Jul 2017 14:03:57 +0530 Subject: [PATCH 31/31] Fix for UUIDs in EventSessionBase --- hasjob/models/user.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hasjob/models/user.py b/hasjob/models/user.py index 583a4f93e..0ab482414 100644 --- a/hasjob/models/user.py +++ b/hasjob/models/user.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from datetime import datetime, timedelta +from uuid import uuid4 from flask import request from flask_lastuser.sqlalchemy import UserBase2 from coaster.utils import unicode_http_header @@ -68,6 +69,9 @@ class EventSessionBase(object): @classmethod def new_from_request(cls, request): instance = cls() + # We need to set a UUID despite the autogenerated default from UuidMixin, because + # EventSessionBase is used independently, isn't a db model, and doesn't have a uuid column. + instance.uuid = uuid4() instance.created_at = datetime.utcnow() instance.referrer = unicode_http_header(request.referrer)[:2083] if request.referrer else None instance.utm_source = request.args.get('utm_source', u'')[:250] or None