diff --git a/.gcloudignore b/.gcloudignore new file mode 100644 index 0000000..e2f0b66 --- /dev/null +++ b/.gcloudignore @@ -0,0 +1,13 @@ +google_appengine +virtualenv_ +lib +.tox +env +.git +tmp +loveapp/config-example.py +YelpLove.egg-info +*.pyc +tests/ +testing/ +.pytest_cache \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1e57d95..76b1f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ tmp import/*.csv import/*.json venv +env .vscode \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e013afd..7d93486 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ +repos: - repo: https://github.com/pre-commit/pre-commit-hooks.git - sha: v0.7.1 + rev: v0.7.1 hooks: - id: autopep8-wrapper - id: check-added-large-files @@ -15,15 +16,20 @@ - id: double-quote-string-fixer - id: end-of-file-fixer - id: flake8 - language_version: python2.7 + language_version: python3.11 - id: fix-encoding-pragma - id: name-tests-test - id: pretty-format-json args: ['--autofix'] - id: requirements-txt-fixer - id: trailing-whitespace +- repo: https://github.com/asottile/reorder-python-imports + rev: v3.12.0 + hooks: + - id: reorder-python-imports + - repo: https://github.com/Yelp/detect-secrets - sha: 0.9.1 + rev: 0.9.1 hooks: - id: detect-secrets args: ['--baseline', '.secrets.baseline'] diff --git a/.travis.yml b/.travis.yml index 9c13f59..91492f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python python: - - "2.7" + - "3.11" # command to install dependencies install: "pip install tox" # command to run tests diff --git a/README.md b/README.md index ee13275..fb4d9a3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ To get an idea what Yelp Love looks like go and check out the [screenshots](/scr Yelp Love runs on [Google App Engine](https://appengine.google.com/). In order to install Yelp Love you will need a Google account and the -[Google App Engine SDK for Python 2.7](https://cloud.google.com/appengine/docs/standard/python/download). +[Google App Engine SDK for Python](https://cloud.google.com/appengine/docs/standard/python/download). ### Create a new project @@ -78,23 +78,23 @@ fields Yelp Love requires for an employee. The S3 bucket name must be configured in config.py. In order to access the S3 bucket you have to save AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY -using the [Secret](models/secret.py) model. Locally you can open up the -[interactive console](http://localhost:8000/console) and execute the following code: +using the [Secret](models/secret.py) model. Locally, you can temporarily add an endpoint inside loveapp/views/web.py and then navigate to it in your browser (e.g., http://localhost:8080/create_secrets): ```python -from models import Secret - -Secret(id='AWS_ACCESS_KEY_ID', value='change-me').put() -Secret(id='AWS_SECRET_ACCESS_KEY', value='change-me').put() +@web_app.route('/create_secrets') +def create_secrets(): + from loveapp.models import Secret + Secret(id='AWS_ACCESS_KEY_ID', value='change-me').put() + Secret(id='AWS_SECRET_ACCESS_KEY', value='change-me').put() + return "please delete me now" ``` -In production you can either use the [Datastore UI](https://console.cloud.google.com/datastore/entities/) -or the [Remote API](https://cloud.google.com/appengine/docs/python/tools/remoteapi). +In production you can use the [Datastore UI](https://console.cloud.google.com/datastore/entities/). To kick off the final import you have to run: ```python -from logic import employee +from loveapp.logic import employee employee.load_employees() ``` diff --git a/app.yaml b/app.yaml index 540cae7..4b281fc 100644 --- a/app.yaml +++ b/app.yaml @@ -1,24 +1,8 @@ service: default -runtime: python27 -api_version: 1 -threadsafe: true +runtime: python311 +app_engine_apis: true handlers: -- url: /api/.* - script: main.app - secure: always -- url: /keys/?.* - script: main.app - login: admin - secure: always -- url: /subscriptions/?.* - script: main.app - login: admin - secure: always -- url: /aliases/?.* - script: main.app - login: admin - secure: always - url: /robots.txt static_files: static/robots.txt upload: static/robots.txt @@ -26,31 +10,3 @@ handlers: - url: /static static_dir: static secure: always -- url: /_themes/(.*)/img/(.*) - static_files: themes/\1/static/img/\2 - upload: themes/(.*)/static/img/(.*) - secure: always - login: optional -- url: .* # Anything not explicitly listed above - script: main.app - login: required - secure: always - -builtins: -- remote_api: on - -libraries: -- name: ssl - version: latest - -skip_files: -- ^(.*/)?#.*#$ -- ^(.*/)?.*/RCS/.*$ -- ^(.*/)?.*\.py[co]$ -- ^(.*/)?.*~$ -- ^(.*/)?\..*$ -- ^YelpLove.egg-info(/.*)?$ -- ^config-example.py$ -- ^google_appengine(/.*)?$ -- ^tmp(/.*)?$ -- ^virtualenv_.*$ diff --git a/appengine_config.py b/appengine_config.py deleted file mode 100644 index 11ec9e2..0000000 --- a/appengine_config.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys - -# Load dependencies in lib -app_root_dir = os.path.dirname(__file__) -server_lib_dir = os.path.join(app_root_dir, 'lib') -if server_lib_dir not in sys.path: - sys.path.insert(0, server_lib_dir) diff --git a/import/employees.csv.example b/import/employees.csv.example deleted file mode 100644 index df29a0f..0000000 --- a/import/employees.csv.example +++ /dev/null @@ -1,3 +0,0 @@ -username,first_name,last_name,department,photo_url -john,John,Doe,,https://placehold.it/100x100 -janet,Janet,Doe,,https://placehold.it/100x100 \ No newline at end of file diff --git a/loveapp/__init__.py b/loveapp/__init__.py new file mode 100644 index 0000000..7637a02 --- /dev/null +++ b/loveapp/__init__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +import flask_themes2 +from flask import Blueprint +from flask import current_app +from flask import Flask +from flask_themes2 import Themes +from flask_themes2 import ThemeTemplateLoader +from google.appengine.api import wrap_wsgi_app + +import loveapp.config as config +from loveapp import views +from loveapp.util.auth import is_admin +from loveapp.util.company_values import linkify_company_values +from loveapp.util.converter import RegexConverter +from loveapp.util.csrf import generate_csrf_token + + +def create_app(theme_loaders=()): + if current_app: + return current_app + app = Flask(__name__.split('.')[0]) + app.wsgi_app = wrap_wsgi_app(app.wsgi_app) + + app.secret_key = config.SECRET_KEY + app.url_map.converters['regex'] = RegexConverter + app.jinja_env.globals['config'] = config + app.jinja_env.globals['csrf_token'] = generate_csrf_token + app.jinja_env.globals['is_admin'] = is_admin + app.jinja_env.filters['linkify_company_values'] = linkify_company_values + + app.register_blueprint(views.web.web_app) + app.register_blueprint(views.api.api_app) + app.register_blueprint(views.tasks.tasks_app) + + # flask_themes2 is storing themes_blueprint at the global level + # https://github.com/sysr-q/flask-themes2/blob/master/flask_themes2/__init__.py#L280C1-L281C58 + # which means on some parametrized test runs, we run into errors re-adding urls on the blueprint. + # Forcing the reset of themes_blueprint here seems to work + flask_themes2.themes_blueprint = Blueprint('_themes', __name__) + flask_themes2.themes_blueprint.jinja_loader = ThemeTemplateLoader(True) + Themes(app, app_identifier='yelplove', loaders=theme_loaders) + + # if debug property is present, let's use it + try: + app.debug = config.DEBUG + except AttributeError: + app.debug = False + + return app diff --git a/config-example.py b/loveapp/config-example.py similarity index 100% rename from config-example.py rename to loveapp/config-example.py diff --git a/loveapp/import/employees.csv b/loveapp/import/employees.csv new file mode 100644 index 0000000..06f2be8 --- /dev/null +++ b/loveapp/import/employees.csv @@ -0,0 +1,4 @@ +username,first_name,last_name,office,department,photo_url +duncan,Duncan,Cook,Awesomeness Office,Department Of Awesome,https://placehold.it/100x100 +hbomb,Harrison,Cook,,,https://placehold.it/100x100 +niffs,Jennifer,Cook,,,https://placehold.it/100x100 \ No newline at end of file diff --git a/loveapp/import/employees.csv.example b/loveapp/import/employees.csv.example new file mode 100644 index 0000000..78b1d4a --- /dev/null +++ b/loveapp/import/employees.csv.example @@ -0,0 +1,3 @@ +username,first_name,last_name,department,office,photo_url +john,John,Doe,,,https://placehold.it/100x100 +janet,Janet,Doe,,,https://placehold.it/100x100 \ No newline at end of file diff --git a/import/employees.json.example b/loveapp/import/employees.json.example similarity index 89% rename from import/employees.json.example rename to loveapp/import/employees.json.example index 4f24fd8..b10ee71 100644 --- a/import/employees.json.example +++ b/loveapp/import/employees.json.example @@ -4,6 +4,7 @@ "first_name": "John", "last_name": "Doe", "department": "", + "office": "", "photo_url": "https://placehold.it/100x100" }, { @@ -11,6 +12,7 @@ "first_name": "Janet", "last_name": "Doe", "department": "", + "office": "", "photo_url": "https://placehold.it/100x100" } ] diff --git a/logic/__init__.py b/loveapp/logic/__init__.py similarity index 92% rename from logic/__init__.py rename to loveapp/logic/__init__.py index 6672ddf..b0d0edf 100644 --- a/logic/__init__.py +++ b/loveapp/logic/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from datetime import timedelta -from itertools import izip_longest +from itertools import zip_longest import pytz from google.appengine.ext import ndb @@ -13,11 +13,11 @@ def chunk(iterable, chunk_size): """Collect data into fixed-length chunks or blocks (http://docs.python.org/2/library/itertools.html#recipes)""" args = [iter(iterable)] * chunk_size - return izip_longest(*args) + return zip_longest(*args) def to_the_future(dict): - for k, v in dict.iteritems(): + for k, v in dict.items(): if issubclass(v.__class__, ndb.Future): dict[k] = v.get_result() diff --git a/logic/alias.py b/loveapp/logic/alias.py similarity index 89% rename from logic/alias.py rename to loveapp/logic/alias.py index cce6ef2..1d066d2 100644 --- a/logic/alias.py +++ b/loveapp/logic/alias.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- - -from models import Alias -from models import Employee +from loveapp.models import Alias +from loveapp.models import Employee def get_alias(alias): diff --git a/logic/department.py b/loveapp/logic/department.py similarity index 84% rename from logic/department.py rename to loveapp/logic/department.py index 84b8afe..3271f8b 100644 --- a/logic/department.py +++ b/loveapp/logic/department.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- - -from models.employee import Employee +from loveapp.models.employee import Employee def get_all_departments(): diff --git a/logic/email.py b/loveapp/logic/email.py similarity index 90% rename from logic/email.py rename to loveapp/logic/email.py index dff6492..c78885c 100644 --- a/logic/email.py +++ b/loveapp/logic/email.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from google.appengine.api.mail import EmailMessage -from util.email import get_name_and_email -import config -import logic.secret +import loveapp.config as config +import loveapp.logic.secret +from loveapp.util.email import get_name_and_email if config.EMAIL_BACKEND == 'sendgrid': # a bit of a hack here so that we can avoid adding dependencies unless @@ -22,7 +22,7 @@ def send_appengine_email(sender, recipient, subject, body_html, body_text): def send_sendgrid_email(sender, recipient, subject, body_html, body_text): - key = logic.secret.get_secret('SENDGRID_API_KEY') + key = loveapp.logic.secret.get_secret('SENDGRID_API_KEY') sg = sendgrid.SendGridAPIClient(apikey=key) from_ = sendgrid.helpers.mail.Email(*get_name_and_email(sender)) diff --git a/logic/employee.py b/loveapp/logic/employee.py similarity index 93% rename from logic/employee.py rename to loveapp/logic/employee.py index 98b9f1b..4a499b7 100644 --- a/logic/employee.py +++ b/loveapp/logic/employee.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- import csv import json -import os.path import logging +import os.path from google.appengine.api import search from google.appengine.api.runtime import memory_usage from google.appengine.ext import ndb -import config +import loveapp.config as config from errors import NoSuchEmployee -from logic import chunk -from logic.secret import get_secret -from logic.toggle import set_toggle_state -from models import Employee -from models import Love -from models import LoveCount -from models.toggle import LOVE_SENDING_ENABLED -from logic.office import OfficeParser +from loveapp.logic import chunk +from loveapp.logic.office import OfficeParser +from loveapp.logic.secret import get_secret +from loveapp.logic.toggle import set_toggle_state +from loveapp.models import Employee +from loveapp.models import Love +from loveapp.models import LoveCount +from loveapp.models.toggle import LOVE_SENDING_ENABLED INDEX_NAME = 'employees' @@ -46,7 +46,7 @@ def _get_employee_info_from_csv(): def _clear_index(): - logging.info('Clearing index... {}MB'.format(memory_usage().current())) + logging.info('Clearing index... {}MB'.format(memory_usage().current)) index = search.Index(name=INDEX_NAME) last_id = None while True: @@ -70,7 +70,7 @@ def _clear_index(): last_id = doc_ids[-1] index.delete(doc_ids) - logging.info('Done clearing index. {}MB'.format(memory_usage().current())) + logging.info('Done clearing index. {}MB'.format(memory_usage().current)) def _generate_substrings(string): @@ -80,14 +80,14 @@ def _generate_substrings(string): Example: _concatenate_substrings('arothman') => 'a ar aro arot aroth arothm arothma' """ - return ' '.join([string[:i] for i in xrange(1, len(string))]) + return ' '.join([string[:i] for i in range(1, len(string))]) def _get_employee_info_from_s3(): from boto import connect_s3 from boto.s3.key import Key - logging.info('Reading employees file from S3... {}MB'.format(memory_usage().current())) + logging.info('Reading employees file from S3... {}MB'.format(memory_usage().current)) key = Key( connect_s3( aws_access_key_id=get_secret('AWS_ACCESS_KEY_ID'), @@ -96,12 +96,12 @@ def _get_employee_info_from_s3(): 'employees.json', ) employee_dicts = json.loads(key.get_contents_as_string()) - logging.info('Done reading employees file from S3. {}MB'.format(memory_usage().current())) + logging.info('Done reading employees file from S3. {}MB'.format(memory_usage().current)) return employee_dicts def _index_employees(employees): - logging.info('Indexing employees... {}MB'.format(memory_usage().current())) + logging.info('Indexing employees... {}MB'.format(memory_usage().current)) index = search.Index(name=INDEX_NAME) # According to appengine, put can handle a maximum of 200 documents, # and apparently batching is more efficient @@ -118,12 +118,12 @@ def _index_employees(employees): doc = search.Document(fields=[ # Full name is already unicode search.TextField(name='full_name', value=employee.full_name), - search.TextField(name='username', value=unicode(employee.username)), + search.TextField(name='username', value=employee.username), search.TextField(name='substrings', value=substrings), ]) documents.append(doc) index.put(documents) - logging.info('Done indexing employees. {}MB'.format(memory_usage().current())) + logging.info('Done indexing employees. {}MB'.format(memory_usage().current)) def _update_employees(employee_dicts): @@ -135,7 +135,7 @@ def _update_employees(employee_dicts): """ employee_dicts = list(employee_dicts) - logging.info('Updating employees... {}MB'.format(memory_usage().current())) + logging.info('Updating employees... {}MB'.format(memory_usage().current)) db_employee_dict = { employee.username: employee @@ -166,7 +166,7 @@ def _update_employees(employee_dicts): current_usernames.add(d['username']) if len(all_employees) % 200 == 0: - logging.info('Processed {} employees, {}MB'.format(len(all_employees), memory_usage().current())) + logging.info('Processed {} employees, {}MB'.format(len(all_employees), memory_usage().current)) ndb.put_multi(all_employees) # Figure out if there are any employees in the DB that aren't in the S3 @@ -181,7 +181,7 @@ def _update_employees(employee_dicts): terminated_employees.append(employee) ndb.put_multi(terminated_employees) - logging.info('Done updating employees. {}MB'.format(memory_usage().current())) + logging.info('Done updating employees. {}MB'.format(memory_usage().current)) def combine_employees(old_username, new_username): diff --git a/logic/event.py b/loveapp/logic/event.py similarity index 100% rename from logic/event.py rename to loveapp/logic/event.py diff --git a/logic/leaderboard.py b/loveapp/logic/leaderboard.py similarity index 75% rename from logic/leaderboard.py rename to loveapp/logic/leaderboard.py index 37cd4b8..bbdcfb6 100644 --- a/logic/leaderboard.py +++ b/loveapp/logic/leaderboard.py @@ -2,10 +2,10 @@ from datetime import datetime from datetime import timedelta -from logic import TIMESPAN_LAST_WEEK -from logic import to_the_future -from logic import utc_week_limits -import logic.love_count +import loveapp.logic.love_count +from loveapp.logic import TIMESPAN_LAST_WEEK +from loveapp.logic import to_the_future +from loveapp.logic import utc_week_limits def get_leaderboard_data(timespan, department, office=None): @@ -16,7 +16,7 @@ def get_leaderboard_data(timespan, department, office=None): utc_now -= timedelta(days=7) utc_week_start, _ = utc_week_limits(utc_now) - top_lovers, top_lovees = logic.love_count.top_lovers_and_lovees( + top_lovers, top_lovees = loveapp.logic.love_count.top_lovers_and_lovees( utc_week_start, dept=department, office=office, @@ -41,6 +41,6 @@ def get_leaderboard_data(timespan, department, office=None): ] # get results for the futures set up previously - map(to_the_future, top_lover_dicts) - map(to_the_future, top_loved_dicts) + list(map(to_the_future, top_lover_dicts)) + list(map(to_the_future, top_loved_dicts)) return (top_lover_dicts, top_loved_dicts) diff --git a/logic/love.py b/loveapp/logic/love.py similarity index 89% rename from logic/love.py rename to loveapp/logic/love.py index e57d2ad..b5db5a2 100644 --- a/logic/love.py +++ b/loveapp/logic/love.py @@ -1,20 +1,20 @@ # -*- coding: utf-8 -*- from datetime import datetime -from google.appengine.api import taskqueue -import config -import logic.alias -import logic.email -import logic.event +from google.appengine.api import taskqueue +import loveapp.config as config +import loveapp.logic.alias +import loveapp.logic.email +import loveapp.logic.event from errors import TaintedLove -from logic.toggle import get_toggle_state -from models import Employee -from models import Love -from models import LoveCount -from models.toggle import LOVE_SENDING_ENABLED -from util.company_values import get_hashtag_value_mapping -from util.render import render_template +from loveapp.logic.toggle import get_toggle_state +from loveapp.models import Employee +from loveapp.models import Love +from loveapp.models import LoveCount +from loveapp.models.toggle import LOVE_SENDING_ENABLED +from loveapp.util.company_values import get_hashtag_value_mapping +from loveapp.util.render import render_template def _love_query(start_dt, end_dt, include_secret): @@ -102,7 +102,7 @@ def send_love_email(l): # noqa recent_love_and_lovers=[(love, love.sender_key.get()) for love in recent_love[:3]] ) - logic.email.send_email(from_, to, subject, body_html, body_text) + loveapp.logic.email.send_email(from_, to, subject, body_html, body_text) def get_love(sender_username=None, recipient_username=None, limit=None): @@ -112,8 +112,8 @@ def get_love(sender_username=None, recipient_username=None, limit=None): :param recipient_username: If present, only return love sent to a particular user. :param limit: If present, only return this many items. """ - sender_username = logic.alias.name_for_alias(sender_username) - recipient_username = logic.alias.name_for_alias(recipient_username) + sender_username = loveapp.logic.alias.name_for_alias(sender_username) + recipient_username = loveapp.logic.alias.name_for_alias(recipient_username) if not (sender_username or recipient_username): raise TaintedLove('Not gonna give you all the love in the world. Sorry.') @@ -150,7 +150,7 @@ def send_loves(recipients, message, sender_username=None, secret=False): if sender_username is None: sender_username = Employee.get_current_employee().username - sender_username = logic.alias.name_for_alias(sender_username) + sender_username = loveapp.logic.alias.name_for_alias(sender_username) sender_key = Employee.query( Employee.username == sender_username, Employee.terminated == False, # noqa @@ -175,7 +175,7 @@ def send_loves(recipients, message, sender_username=None, secret=False): def validate_love_recipients(recipients): - unique_recipients = set([logic.alias.name_for_alias(name) for name in recipients]) + unique_recipients = set([loveapp.logic.alias.name_for_alias(name) for name in recipients]) if len(recipients) != len(unique_recipients): raise TaintedLove(u'Sorry, you are trying to send love to a user multiple times.') @@ -217,8 +217,8 @@ def _send_love(recipient_key, message, sender_key, secret): ) if not secret: - logic.event.add_event( - logic.event.LOVESENT, + loveapp.logic.event.add_event( + loveapp.logic.event.LOVESENT, {'love_id': new_love.key.id()}, ) @@ -228,7 +228,7 @@ def _get_company_values(new_love, message): hashtag_value_mapping = get_hashtag_value_mapping() matched_categories = set() - for hashtag, category in hashtag_value_mapping.iteritems(): + for hashtag, category in hashtag_value_mapping.items(): if hashtag in message.lower(): matched_categories.add(category) diff --git a/logic/love_count.py b/loveapp/logic/love_count.py similarity index 90% rename from logic/love_count.py rename to loveapp/logic/love_count.py index 52f2c9f..23c3d51 100644 --- a/logic/love_count.py +++ b/loveapp/logic/love_count.py @@ -5,12 +5,12 @@ from google.appengine.api.runtime import memory_usage from google.appengine.ext import ndb -from logic import utc_week_limits -from logic.toggle import set_toggle_state -from models import Employee -from models import Love -from models import LoveCount -from models.toggle import LOVE_SENDING_ENABLED +from loveapp.logic import utc_week_limits +from loveapp.logic.toggle import set_toggle_state +from loveapp.models import Employee +from loveapp.models import Love +from loveapp.models import LoveCount +from loveapp.models.toggle import LOVE_SENDING_ENABLED def top_lovers_and_lovees(utc_week_start, dept=None, office=None, limit=20): diff --git a/logic/love_link.py b/loveapp/logic/love_link.py similarity index 91% rename from logic/love_link.py rename to loveapp/logic/love_link.py index c7ff8f3..cdb5015 100644 --- a/logic/love_link.py +++ b/loveapp/logic/love_link.py @@ -4,13 +4,13 @@ import random import string -import logic.alias -from errors import NoSuchLoveLink -from models import LoveLink -from models import Employee - from google.appengine.ext import ndb +import loveapp.logic.alias +from errors import NoSuchLoveLink +from loveapp.models import Employee +from loveapp.models import LoveLink + def get_love_link(hash_key): loveLink = LoveLink.query(LoveLink.hash_key == hash_key).get() @@ -44,7 +44,7 @@ def add_recipient(hash_key, recipient): raise NoSuchLoveLink("Couldn't Love Link with id {}".format(hash_key)) # check that user exists, get_key_for_username throws an exception if not - recipient_username = logic.alias.name_for_alias(recipient) + recipient_username = loveapp.logic.alias.name_for_alias(recipient) Employee.get_key_for_username(recipient_username) loveLink.recipient_list += ', ' + recipient diff --git a/logic/notification_request.py b/loveapp/logic/notification_request.py similarity index 98% rename from logic/notification_request.py rename to loveapp/logic/notification_request.py index 6611304..4953ea6 100644 --- a/logic/notification_request.py +++ b/loveapp/logic/notification_request.py @@ -3,9 +3,10 @@ import hmac import json import logging + import urllib3 -import config +import loveapp.config as config CONTENT_TYPE_JSON = 'application/json' diff --git a/logic/notifier/__init__.py b/loveapp/logic/notifier/__init__.py similarity index 64% rename from logic/notifier/__init__.py rename to loveapp/logic/notifier/__init__.py index 7769020..274ed0f 100644 --- a/logic/notifier/__init__.py +++ b/loveapp/logic/notifier/__init__.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- -import logic.event - +import loveapp.logic.event from errors import UnknownEvent -from logic.notifier.lovesent_notifier import LovesentNotifier +from loveapp.logic.notifier.lovesent_notifier import LovesentNotifier EVENT_TO_NOTIFIER_MAPPING = { - logic.event.LOVESENT: LovesentNotifier, + loveapp.logic.event.LOVESENT: LovesentNotifier, } diff --git a/logic/notifier/lovesent_notifier.py b/loveapp/logic/notifier/lovesent_notifier.py similarity index 83% rename from logic/notifier/lovesent_notifier.py rename to loveapp/logic/notifier/lovesent_notifier.py index 56b8c8b..a5978b2 100644 --- a/logic/notifier/lovesent_notifier.py +++ b/loveapp/logic/notifier/lovesent_notifier.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- from google.appengine.ext import ndb -import logic.event - -from logic.notification_request import NotificationRequest -from models.love import Love -from models.subscription import Subscription +import loveapp.logic.event +from loveapp.logic.notification_request import NotificationRequest +from loveapp.models.love import Love +from loveapp.models.subscription import Subscription class LovesentNotifier(object): - event = logic.event.LOVESENT + event = loveapp.logic.event.LOVESENT def __init__(self, *args, **kwargs): self.love = ndb.Key(Love, kwargs.get('love_id')).get() diff --git a/logic/office.py b/loveapp/logic/office.py similarity index 98% rename from logic/office.py rename to loveapp/logic/office.py index 9347126..85a9e75 100644 --- a/logic/office.py +++ b/loveapp/logic/office.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- -import yaml from collections import Counter from collections import defaultdict +import yaml + REMOTE_OFFICE = 'Remote' OTHER_OFFICE = 'Other' def get_all_offices(): - from models import Employee + from loveapp.models import Employee """ Retrieve all the offices in the database diff --git a/logic/secret.py b/loveapp/logic/secret.py similarity index 85% rename from logic/secret.py rename to loveapp/logic/secret.py index 29c9fb9..b0bba49 100644 --- a/logic/secret.py +++ b/loveapp/logic/secret.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from errors import NoSuchSecret -from models import Secret +from loveapp.models import Secret def get_secret(id): diff --git a/logic/subscription.py b/loveapp/logic/subscription.py similarity index 74% rename from logic/subscription.py rename to loveapp/logic/subscription.py index 513abaf..d5e0cdd 100644 --- a/logic/subscription.py +++ b/loveapp/logic/subscription.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from models.subscription import Subscription +from loveapp.models.subscription import Subscription def delete_subscription(subscription_id): diff --git a/logic/toggle.py b/loveapp/logic/toggle.py similarity index 88% rename from logic/toggle.py rename to loveapp/logic/toggle.py index 45bb4a7..46fdc04 100644 --- a/logic/toggle.py +++ b/loveapp/logic/toggle.py @@ -3,9 +3,9 @@ from errors import InvalidToggleName from errors import InvalidToggleState -from models import Toggle -from models.toggle import TOGGLE_NAMES -from models.toggle import TOGGLE_STATES +from loveapp.models import Toggle +from loveapp.models.toggle import TOGGLE_NAMES +from loveapp.models.toggle import TOGGLE_STATES def _validate_and_maybe_create_toggle(name, state): diff --git a/models/__init__.py b/loveapp/models/__init__.py similarity index 99% rename from models/__init__.py rename to loveapp/models/__init__.py index e9f68d9..a3367de 100644 --- a/models/__init__.py +++ b/loveapp/models/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # flake8: noqa - from .access_key import AccessKey from .alias import Alias from .employee import Employee diff --git a/models/access_key.py b/loveapp/models/access_key.py similarity index 100% rename from models/access_key.py rename to loveapp/models/access_key.py diff --git a/models/alias.py b/loveapp/models/alias.py similarity index 86% rename from models/alias.py rename to loveapp/models/alias.py index d554199..da3e81f 100644 --- a/models/alias.py +++ b/loveapp/models/alias.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from google.appengine.ext import ndb -from models.employee import Employee +from loveapp.models.employee import Employee class Alias(ndb.Model): diff --git a/models/employee.py b/loveapp/models/employee.py similarity index 88% rename from models/employee.py rename to loveapp/models/employee.py index bb0ec7a..9ba8fc5 100644 --- a/models/employee.py +++ b/loveapp/models/employee.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- import base64 -import hashlib import functools +import hashlib -from google.appengine.ext import ndb from google.appengine.api import users +from google.appengine.ext import ndb -import config +import loveapp.config from errors import NoSuchEmployee -from util.pagination import Pagination +from loveapp.util.pagination import Pagination def memoized(func): @@ -52,7 +52,10 @@ def get_current_employee(cls): def create_from_dict(cls, d, persist=True): new_employee = cls() new_employee.username = d['username'] - new_employee.user = users.User('{user}@{domain}'.format(user=new_employee.username, domain=config.DOMAIN)) + new_employee.user = users.User( + '{user}@{domain}'.format(user=new_employee.username, domain=loveapp.config.DOMAIN), + _auth_domain=loveapp.config.DOMAIN + ) new_employee.update_from_dict(d) if persist is True: @@ -84,7 +87,7 @@ def update_from_dict(self, d): def get_gravatar(self): """Creates gravatar URL from email address.""" m = hashlib.md5() - m.update(self.user.email()) + m.update(self.user.email().encode()) encoded_hash = base64.b16encode(m.digest()).lower() return 'https://gravatar.com/avatar/{}?s=200'.format(encoded_hash) @@ -92,9 +95,9 @@ def get_photo_url(self): """Return an avatar photo URL (depending on Gravatar config). This still could be empty, in which case the theme needs to provide an alternate photo. """ - if config.GRAVATAR == 'always': + if loveapp.config.GRAVATAR == 'always': return self.get_gravatar() - elif config.GRAVATAR == 'backup' and not self.photo_url: + elif loveapp.config.GRAVATAR == 'backup' and not self.photo_url: return self.get_gravatar() else: return self.photo_url diff --git a/models/love.py b/loveapp/models/love.py similarity index 93% rename from models/love.py rename to loveapp/models/love.py index 4b10588..eafa84a 100644 --- a/models/love.py +++ b/loveapp/models/love.py @@ -3,7 +3,7 @@ from google.appengine.ext import ndb -from models import Employee +from loveapp.models import Employee class Love(ndb.Model): diff --git a/models/love_count.py b/loveapp/models/love_count.py similarity index 98% rename from models/love_count.py rename to loveapp/models/love_count.py index ee6ea1b..49811a7 100644 --- a/models/love_count.py +++ b/loveapp/models/love_count.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from google.appengine.ext import ndb -from logic import utc_week_limits +from loveapp.logic import utc_week_limits class LoveCount(ndb.Model): diff --git a/models/love_link.py b/loveapp/models/love_link.py similarity index 94% rename from models/love_link.py rename to loveapp/models/love_link.py index 5f558fe..072ac08 100644 --- a/models/love_link.py +++ b/loveapp/models/love_link.py @@ -3,7 +3,7 @@ from google.appengine.ext import ndb -import config +import loveapp.config as config class LoveLink(ndb.Model): diff --git a/models/secret.py b/loveapp/models/secret.py similarity index 100% rename from models/secret.py rename to loveapp/models/secret.py diff --git a/models/subscription.py b/loveapp/models/subscription.py similarity index 92% rename from models/subscription.py rename to loveapp/models/subscription.py index c277325..18e6973 100644 --- a/models/subscription.py +++ b/loveapp/models/subscription.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from google.appengine.ext import ndb -from logic.notification_request import CONTENT_TYPE_JSON -from models import Employee +from loveapp.logic.notification_request import CONTENT_TYPE_JSON +from loveapp.models import Employee class Subscription(ndb.Model): diff --git a/models/toggle.py b/loveapp/models/toggle.py similarity index 100% rename from models/toggle.py rename to loveapp/models/toggle.py diff --git a/static/robots.txt b/loveapp/static/robots.txt similarity index 100% rename from static/robots.txt rename to loveapp/static/robots.txt diff --git a/themes/default/info.json b/loveapp/themes/default/info.json similarity index 100% rename from themes/default/info.json rename to loveapp/themes/default/info.json diff --git a/themes/default/static/css/bootstrap.min.css b/loveapp/themes/default/static/css/bootstrap.min.css similarity index 100% rename from themes/default/static/css/bootstrap.min.css rename to loveapp/themes/default/static/css/bootstrap.min.css diff --git a/themes/default/static/css/style.css b/loveapp/themes/default/static/css/style.css similarity index 100% rename from themes/default/static/css/style.css rename to loveapp/themes/default/static/css/style.css diff --git a/themes/default/static/img/favicon.png b/loveapp/themes/default/static/img/favicon.png similarity index 100% rename from themes/default/static/img/favicon.png rename to loveapp/themes/default/static/img/favicon.png diff --git a/themes/default/static/img/glyphicons-halflings-white.png b/loveapp/themes/default/static/img/glyphicons-halflings-white.png similarity index 100% rename from themes/default/static/img/glyphicons-halflings-white.png rename to loveapp/themes/default/static/img/glyphicons-halflings-white.png diff --git a/themes/default/static/img/glyphicons-halflings.png b/loveapp/themes/default/static/img/glyphicons-halflings.png similarity index 100% rename from themes/default/static/img/glyphicons-halflings.png rename to loveapp/themes/default/static/img/glyphicons-halflings.png diff --git a/themes/default/static/img/logo.png b/loveapp/themes/default/static/img/logo.png similarity index 100% rename from themes/default/static/img/logo.png rename to loveapp/themes/default/static/img/logo.png diff --git a/themes/default/static/img/rocket.png b/loveapp/themes/default/static/img/rocket.png similarity index 100% rename from themes/default/static/img/rocket.png rename to loveapp/themes/default/static/img/rocket.png diff --git a/themes/default/static/img/star_header_bg.png b/loveapp/themes/default/static/img/star_header_bg.png similarity index 100% rename from themes/default/static/img/star_header_bg.png rename to loveapp/themes/default/static/img/star_header_bg.png diff --git a/themes/default/static/img/user_medium_square.png b/loveapp/themes/default/static/img/user_medium_square.png similarity index 100% rename from themes/default/static/img/user_medium_square.png rename to loveapp/themes/default/static/img/user_medium_square.png diff --git a/themes/default/static/js/jquery-ui-1.10.4.custom.min.js b/loveapp/themes/default/static/js/jquery-ui-1.10.4.custom.min.js similarity index 100% rename from themes/default/static/js/jquery-ui-1.10.4.custom.min.js rename to loveapp/themes/default/static/js/jquery-ui-1.10.4.custom.min.js diff --git a/themes/default/static/js/linkify.js b/loveapp/themes/default/static/js/linkify.js similarity index 100% rename from themes/default/static/js/linkify.js rename to loveapp/themes/default/static/js/linkify.js diff --git a/themes/default/static/js/main.js b/loveapp/themes/default/static/js/main.js similarity index 100% rename from themes/default/static/js/main.js rename to loveapp/themes/default/static/js/main.js diff --git a/themes/default/templates/alias_form.html b/loveapp/themes/default/templates/alias_form.html similarity index 100% rename from themes/default/templates/alias_form.html rename to loveapp/themes/default/templates/alias_form.html diff --git a/themes/default/templates/aliases.html b/loveapp/themes/default/templates/aliases.html similarity index 100% rename from themes/default/templates/aliases.html rename to loveapp/themes/default/templates/aliases.html diff --git a/themes/default/templates/email.html b/loveapp/themes/default/templates/email.html similarity index 99% rename from themes/default/templates/email.html rename to loveapp/themes/default/templates/email.html index 1c40d77..d931c57 100644 --- a/themes/default/templates/email.html +++ b/loveapp/themes/default/templates/email.html @@ -48,7 +48,7 @@
{{ love.message }}
+{{ love.message|linkify_company_values }}
{% if love.secret %}Shh... sent secretly!
{% endif %} diff --git a/themes/default/templates/employees.html b/loveapp/themes/default/templates/employees.html similarity index 100% rename from themes/default/templates/employees.html rename to loveapp/themes/default/templates/employees.html diff --git a/themes/default/templates/explore.html b/loveapp/themes/default/templates/explore.html similarity index 100% rename from themes/default/templates/explore.html rename to loveapp/themes/default/templates/explore.html diff --git a/themes/default/templates/home.html b/loveapp/themes/default/templates/home.html similarity index 100% rename from themes/default/templates/home.html rename to loveapp/themes/default/templates/home.html diff --git a/themes/default/templates/import.html b/loveapp/themes/default/templates/import.html similarity index 100% rename from themes/default/templates/import.html rename to loveapp/themes/default/templates/import.html diff --git a/themes/default/templates/import_employees_form.html b/loveapp/themes/default/templates/import_employees_form.html similarity index 100% rename from themes/default/templates/import_employees_form.html rename to loveapp/themes/default/templates/import_employees_form.html diff --git a/themes/default/templates/keys.html b/loveapp/themes/default/templates/keys.html similarity index 100% rename from themes/default/templates/keys.html rename to loveapp/themes/default/templates/keys.html diff --git a/themes/default/templates/layout.html b/loveapp/themes/default/templates/layout.html similarity index 100% rename from themes/default/templates/layout.html rename to loveapp/themes/default/templates/layout.html diff --git a/themes/default/templates/leaderboard.html b/loveapp/themes/default/templates/leaderboard.html similarity index 100% rename from themes/default/templates/leaderboard.html rename to loveapp/themes/default/templates/leaderboard.html diff --git a/themes/default/templates/love_form.html b/loveapp/themes/default/templates/love_form.html similarity index 100% rename from themes/default/templates/love_form.html rename to loveapp/themes/default/templates/love_form.html diff --git a/themes/default/templates/love_link.html b/loveapp/themes/default/templates/love_link.html similarity index 100% rename from themes/default/templates/love_link.html rename to loveapp/themes/default/templates/love_link.html diff --git a/themes/default/templates/me.html b/loveapp/themes/default/templates/me.html similarity index 100% rename from themes/default/templates/me.html rename to loveapp/themes/default/templates/me.html diff --git a/themes/default/templates/parts/avatar.html b/loveapp/themes/default/templates/parts/avatar.html similarity index 100% rename from themes/default/templates/parts/avatar.html rename to loveapp/themes/default/templates/parts/avatar.html diff --git a/themes/default/templates/parts/facetile.html b/loveapp/themes/default/templates/parts/facetile.html similarity index 100% rename from themes/default/templates/parts/facetile.html rename to loveapp/themes/default/templates/parts/facetile.html diff --git a/themes/default/templates/parts/flash.html b/loveapp/themes/default/templates/parts/flash.html similarity index 100% rename from themes/default/templates/parts/flash.html rename to loveapp/themes/default/templates/parts/flash.html diff --git a/themes/default/templates/parts/love_message.html b/loveapp/themes/default/templates/parts/love_message.html similarity index 100% rename from themes/default/templates/parts/love_message.html rename to loveapp/themes/default/templates/parts/love_message.html diff --git a/themes/default/templates/parts/photobox.html b/loveapp/themes/default/templates/parts/photobox.html similarity index 100% rename from themes/default/templates/parts/photobox.html rename to loveapp/themes/default/templates/parts/photobox.html diff --git a/themes/default/templates/sent.html b/loveapp/themes/default/templates/sent.html similarity index 100% rename from themes/default/templates/sent.html rename to loveapp/themes/default/templates/sent.html diff --git a/themes/default/templates/subscription_form.html b/loveapp/themes/default/templates/subscription_form.html similarity index 100% rename from themes/default/templates/subscription_form.html rename to loveapp/themes/default/templates/subscription_form.html diff --git a/themes/default/templates/subscriptions.html b/loveapp/themes/default/templates/subscriptions.html similarity index 100% rename from themes/default/templates/subscriptions.html rename to loveapp/themes/default/templates/subscriptions.html diff --git a/themes/default/templates/values.html b/loveapp/themes/default/templates/values.html similarity index 100% rename from themes/default/templates/values.html rename to loveapp/themes/default/templates/values.html diff --git a/util/__init__.py b/loveapp/util/__init__.py similarity index 98% rename from util/__init__.py rename to loveapp/util/__init__.py index 421f1d6..987db09 100644 --- a/util/__init__.py +++ b/loveapp/util/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- # flake8: noqa - from . import recipient diff --git a/util/auth.py b/loveapp/util/auth.py similarity index 100% rename from util/auth.py rename to loveapp/util/auth.py diff --git a/util/company_values.py b/loveapp/util/company_values.py similarity index 94% rename from util/company_values.py rename to loveapp/util/company_values.py index 85b3b23..3bf7b5e 100644 --- a/util/company_values.py +++ b/loveapp/util/company_values.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- import itertools -import markupsafe import re -import config +import markupsafe + +import loveapp.config as config def get_company_value(value_id): @@ -20,10 +21,10 @@ def get_company_value_link_pairs(): def supported_hashtags(): # Returns all supported hashtags - return map( + return list(map( lambda x: '#' + x, itertools.chain(*[value.hashtags for value in config.COMPANY_VALUES]) - ) + )) def get_hashtag_value_mapping(): @@ -37,7 +38,7 @@ def get_hashtag_value_mapping(): def linkify_company_values(love): # escape the input before we add our own safe links - escaped_love = unicode(markupsafe.escape(love)) + escaped_love = str(markupsafe.escape(love)) hashtag_value_mapping = get_hashtag_value_mapping() # find all the hashtags. diff --git a/util/converter.py b/loveapp/util/converter.py similarity index 100% rename from util/converter.py rename to loveapp/util/converter.py diff --git a/util/csrf.py b/loveapp/util/csrf.py similarity index 90% rename from util/csrf.py rename to loveapp/util/csrf.py index add91d3..8a7d5a1 100644 --- a/util/csrf.py +++ b/loveapp/util/csrf.py @@ -20,5 +20,5 @@ def check_csrf_protection(): def generate_csrf_token(): if '_csrf_token' not in session: - session['_csrf_token'] = binascii.hexlify(os.urandom(16)) + session['_csrf_token'] = str(binascii.hexlify(os.urandom(16))) return session['_csrf_token'] diff --git a/util/decorators.py b/loveapp/util/decorators.py similarity index 93% rename from util/decorators.py rename to loveapp/util/decorators.py index d4f2e7c..6c34686 100644 --- a/util/decorators.py +++ b/loveapp/util/decorators.py @@ -7,8 +7,8 @@ from flask.helpers import make_response from google.appengine.api import users -from models.access_key import AccessKey -from util.csrf import check_csrf_protection +from loveapp.models.access_key import AccessKey +from loveapp.util.csrf import check_csrf_protection def user_required(func): diff --git a/util/email.py b/loveapp/util/email.py similarity index 100% rename from util/email.py rename to loveapp/util/email.py diff --git a/util/pagination.py b/loveapp/util/pagination.py similarity index 100% rename from util/pagination.py rename to loveapp/util/pagination.py diff --git a/util/recipient.py b/loveapp/util/recipient.py similarity index 100% rename from util/recipient.py rename to loveapp/util/recipient.py diff --git a/util/render.py b/loveapp/util/render.py similarity index 93% rename from util/render.py rename to loveapp/util/render.py index 6211cc6..ec8cdc5 100644 --- a/util/render.py +++ b/loveapp/util/render.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- import json -from flask_themes2 import render_theme_template from flask.helpers import make_response +from flask_themes2 import render_theme_template -import config +import loveapp.config as config def get_current_theme(): diff --git a/views/__init__.py b/loveapp/views/__init__.py similarity index 98% rename from views/__init__.py rename to loveapp/views/__init__.py index 26142f2..fb96c8f 100644 --- a/views/__init__.py +++ b/loveapp/views/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # flake8: noqa - from . import api from . import tasks from . import web diff --git a/views/api.py b/loveapp/views/api.py similarity index 80% rename from views/api.py rename to loveapp/views/api.py index 14ac2b3..703740c 100644 --- a/views/api.py +++ b/loveapp/views/api.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- +from flask import Blueprint from flask import make_response from flask import request from errors import TaintedLove -from logic import TIMESPAN_THIS_WEEK -from logic.love import get_love -from logic.love import send_loves -from logic.love_link import create_love_link -from logic.leaderboard import get_leaderboard_data -from main import app -from models import Employee -from util.decorators import api_key_required -from util.recipient import sanitize_recipients -from util.render import make_json_response -from views import common +from loveapp.logic import TIMESPAN_THIS_WEEK +from loveapp.logic.leaderboard import get_leaderboard_data +from loveapp.logic.love import get_love +from loveapp.logic.love import send_loves +from loveapp.logic.love_link import create_love_link +from loveapp.models import Employee +from loveapp.util.decorators import api_key_required +from loveapp.util.recipient import sanitize_recipients +from loveapp.util.render import make_json_response +from loveapp.views import common LOVE_CREATED_STATUS_CODE = 201 # Created @@ -21,9 +21,12 @@ LOVE_BAD_PARAMS_STATUS_CODE = 422 # Unprocessable Entity LOVE_NOT_FOUND_STATUS_CODE = 404 # Not Found +api_app = Blueprint('api_app', __name__) # GET /api/love -@app.route('/api/love', methods=['GET']) + + +@api_app.route('/api/love', methods=['GET']) @api_key_required def api_get_love(): sender = request.args.get('sender') @@ -62,7 +65,7 @@ def api_get_love(): # POST /api/love -@app.route('/api/love', methods=['POST']) +@api_app.route('/api/love', methods=['POST']) @api_key_required def api_send_loves(): sender = request.form.get('sender') @@ -88,7 +91,7 @@ def api_send_loves(): # GET /api/leaderboard -@app.route('/api/leaderboard', methods=['GET']) +@api_app.route('/api/leaderboard', methods=['GET']) @api_key_required def api_get_leaderboard(): department = request.args.get('department', None) @@ -114,7 +117,7 @@ def api_get_leaderboard(): 'username': loved['employee'].username, 'department': loved['employee'].department, 'love_count': loved['num_received'], - 'photo_url': lover['employee'].photo_url, + 'photo_url': loved['employee'].photo_url, } for loved in top_loved_dicts ] @@ -122,7 +125,7 @@ def api_get_leaderboard(): return make_json_response(final_result) -@app.route('/api/autocomplete', methods=['GET']) +@api_app.route('/api/autocomplete', methods=['GET']) @api_key_required def autocomplete(): return common.autocomplete(request) diff --git a/views/common.py b/loveapp/views/common.py similarity index 79% rename from views/common.py rename to loveapp/views/common.py index a9eb8e4..c746f3a 100644 --- a/views/common.py +++ b/loveapp/views/common.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from logic.employee import employees_matching_prefix -from util.render import make_json_response +from loveapp.logic.employee import employees_matching_prefix +from loveapp.util.render import make_json_response def autocomplete(request): @@ -12,7 +12,7 @@ def autocomplete(request): users = [ { 'label': u'{} ({})'.format(full_name, username), - 'value': unicode(username), + 'value': username, 'avatar_url': photo_url, } for full_name, username, photo_url diff --git a/views/tasks.py b/loveapp/views/tasks.py similarity index 56% rename from views/tasks.py rename to loveapp/views/tasks.py index 90135ba..0af0329 100644 --- a/views/tasks.py +++ b/loveapp/views/tasks.py @@ -1,39 +1,42 @@ # -*- coding: utf-8 -*- +from flask import Blueprint from flask import request from flask import Response from google.appengine.api import taskqueue from google.appengine.ext import ndb -import logic.employee -import logic.notifier -import logic.love -import logic.love_count -import logic.love_link -from main import app -from models import Love +import loveapp.logic.employee +import loveapp.logic.love +import loveapp.logic.love_count +import loveapp.logic.love_link +import loveapp.logic.notifier +from loveapp.models import Love +tasks_app = Blueprint('tasks_app', __name__) # All tasks that are to be executed by cron need to use HTTP GET # see https://cloud.google.com/appengine/docs/python/config/cron -@app.route('/tasks/employees/load/s3', methods=['GET']) + + +@tasks_app.route('/tasks/employees/load/s3', methods=['GET']) def load_employees_from_s3(): - logic.employee.load_employees() + loveapp.logic.employee.load_employees() # we need to rebuild the love count index as the departments may have changed. taskqueue.add(url='/tasks/love_count/rebuild') return Response(status=200) # This task has a web UI to trigger it, so let's use POST -@app.route('/tasks/employees/load/csv', methods=['POST']) +@tasks_app.route('/tasks/employees/load/csv', methods=['POST']) def load_employees_from_csv(): - logic.employee.load_employees_from_csv() + loveapp.logic.employee.load_employees_from_csv() # we need to rebuild the love count index as the departments may have changed. taskqueue.add(url='/tasks/love_count/rebuild') return Response(status=200) # One-off tasks are much easier to trigger using GET -@app.route('/tasks/employees/combine', methods=['GET']) +@tasks_app.route('/tasks/employees/combine', methods=['GET']) def combine_employees(): old_username, new_username = request.args['old'], request.args['new'] if not old_username: @@ -41,38 +44,38 @@ def combine_employees(): elif not new_username: return Response(response='{} is not a valid username'.format(new_username), status=400) - logic.employee.combine_employees(old_username, new_username) + loveapp.logic.employee.combine_employees(old_username, new_username) return Response(status=200) -@app.route('/tasks/index/rebuild', methods=['GET']) +@tasks_app.route('/tasks/index/rebuild', methods=['GET']) def rebuild_index(): - logic.employee.rebuild_index() + loveapp.logic.employee.rebuild_index() return Response(status=200) -@app.route('/tasks/love/email', methods=['POST']) +@tasks_app.route('/tasks/love/email', methods=['POST']) def email_love(): love_id = int(request.form['id']) love = ndb.Key(Love, love_id).get() - logic.love.send_love_email(love) + loveapp.logic.love.send_love_email(love) return Response(status=200) -@app.route('/tasks/love_count/rebuild', methods=['GET']) +@tasks_app.route('/tasks/love_count/rebuild', methods=['GET']) def rebuild_love_count(): - logic.love_count.rebuild_love_count() + loveapp.logic.love_count.rebuild_love_count() return Response(status=200) -@app.route('/tasks/subscribers/notify', methods=['POST']) +@tasks_app.route('/tasks/subscribers/notify', methods=['POST']) def notify_subscribers(): - notifier = logic.notifier.notifier_for_event(request.json['event'])(**request.json['options']) + notifier = loveapp.logic.notifier.notifier_for_event(request.json['event'])(**request.json['options']) notifier.notify() return Response(status=200) -@app.route('/tasks/lovelinks/cleanup', methods=['GET']) +@tasks_app.route('/tasks/lovelinks/cleanup', methods=['GET']) def lovelinks_cleanup(): - logic.love_link.love_links_cleanup() + loveapp.logic.love_link.love_links_cleanup() return Response(status=200) diff --git a/views/web.py b/loveapp/views/web.py similarity index 67% rename from views/web.py rename to loveapp/views/web.py index 3aaf9da..4ad1990 100644 --- a/views/web.py +++ b/loveapp/views/web.py @@ -1,50 +1,50 @@ # -*- coding: utf-8 -*- import os.path - -import config - from datetime import datetime from flask import abort +from flask import Blueprint from flask import flash from flask import redirect from flask import request from flask import url_for +from google.appengine.api import taskqueue -import logic.alias -import logic.employee -import logic.event -import logic.love -import logic.love_link -import logic.love_count -import logic.subscription +import loveapp.config as config +import loveapp.logic.alias +import loveapp.logic.employee +import loveapp.logic.event +import loveapp.logic.love +import loveapp.logic.love_count +import loveapp.logic.love_link +import loveapp.logic.subscription from errors import NoSuchEmployee from errors import NoSuchLoveLink from errors import TaintedLove -from google.appengine.api import taskqueue -from logic import TIMESPAN_THIS_WEEK -from logic.love_link import create_love_link -from logic.leaderboard import get_leaderboard_data -from main import app -from models import AccessKey -from models import Alias -from models import Employee -from models import Subscription -from util.decorators import admin_required -from util.decorators import csrf_protect -from util.decorators import user_required -from util.recipient import sanitize_recipients -from util.render import render_template -from util.render import make_json_response -from util.company_values import get_company_value -from util.company_values import get_company_value_link_pairs -from util.company_values import values_matching_prefix -from views import common -from logic.office import get_all_offices -from logic.department import get_all_departments - - -@app.route('/', methods=['GET']) +from loveapp.logic import TIMESPAN_THIS_WEEK +from loveapp.logic.department import get_all_departments +from loveapp.logic.leaderboard import get_leaderboard_data +from loveapp.logic.love_link import create_love_link +from loveapp.logic.office import get_all_offices +from loveapp.models import Alias +from loveapp.models import Employee +from loveapp.models import Subscription +from loveapp.models.access_key import AccessKey +from loveapp.util.company_values import get_company_value +from loveapp.util.company_values import get_company_value_link_pairs +from loveapp.util.company_values import values_matching_prefix +from loveapp.util.decorators import admin_required +from loveapp.util.decorators import csrf_protect +from loveapp.util.decorators import user_required +from loveapp.util.recipient import sanitize_recipients +from loveapp.util.render import make_json_response +from loveapp.util.render import render_template +from loveapp.views import common + +web_app = Blueprint('web_app', __name__) + + +@web_app.route('/', methods=['GET']) @user_required def home(): link_id = request.args.get('link_id', None) @@ -61,13 +61,13 @@ def home(): ) -@app.route('/me', methods=['GET']) +@web_app.route('/me', methods=['GET']) @user_required def me(): current_employee = Employee.get_current_employee() - sent_love = logic.love.recent_sent_love(current_employee.key, limit=20) - received_love = logic.love.recent_received_love(current_employee.key, limit=20) + sent_love = loveapp.logic.love.recent_sent_love(current_employee.key, limit=20) + received_love = loveapp.logic.love.recent_received_love(current_employee.key, limit=20) return render_template( 'me.html', @@ -78,11 +78,11 @@ def me(): ) -@app.route('/hello test
' text = 'hello test' - @mock.patch('logic.email.EMAIL_BACKENDS') - @mock.patch('logic.email.config') + @mock.patch('loveapp.logic.email.EMAIL_BACKENDS') + @mock.patch('loveapp.logic.email.config') def test_send_email_appengine(self, mock_config, mock_backends): mock_config.EMAIL_BACKEND = 'appengine' mock_backends['appengine'] = mock.Mock() - logic.email.send_email(self.sender, self.recipient, self.subject, - self.html, self.text) + loveapp.logic.email.send_email(self.sender, self.recipient, self.subject, + self.html, self.text) mock_backends['appengine'].assert_called_once_with( self.sender, self.recipient, self.subject, self.html, self.text ) - @mock.patch('logic.email.EMAIL_BACKENDS') - @mock.patch('logic.email.config') + @mock.patch('loveapp.logic.email.EMAIL_BACKENDS') + @mock.patch('loveapp.logic.email.config') def test_send_email_sendgrid(self, mock_config, mock_backends): mock_config.EMAIL_BACKEND = 'sendgrid' mock_backends['sendgrid'] = mock.Mock() - logic.email.send_email(self.sender, self.recipient, self.subject, - self.html, self.text) + loveapp.logic.email.send_email(self.sender, self.recipient, self.subject, + self.html, self.text) mock_backends['sendgrid'].assert_called_once_with( self.sender, self.recipient, self.subject, self.html, self.text ) diff --git a/tests/logic/love_link_test.py b/tests/logic/love_link_test.py index d7fabfb..9d2ce58 100644 --- a/tests/logic/love_link_test.py +++ b/tests/logic/love_link_test.py @@ -1,53 +1,52 @@ # -*- coding: utf-8 -*- -import unittest import datetime +import unittest -import logic.love -import logic.love_link -from errors import NoSuchLoveLink -from testing.factories import create_love_link +import pytest +import loveapp.logic.love +import loveapp.logic.love_link +from errors import NoSuchLoveLink from testing.factories import create_employee +from testing.factories import create_love_link +@pytest.mark.usefixtures('gae_testbed') class LoveLinkTest(unittest.TestCase): - nosegae_taskqueue = True - nosegae_memcache = True - nosegae_datastore_v3 = True def setUp(self): self.link = create_love_link(hash_key='HeLLo', recipient_list='johndoe,janedoe', message='well hello there') self.princessbubblegum = create_employee(username='princessbubblegum') def test_get_love_link(self): - link = logic.love_link.get_love_link('HeLLo') + link = loveapp.logic.love_link.get_love_link('HeLLo') self.assertEqual(link.hash_key, 'HeLLo') self.assertEqual(link.recipient_list, 'johndoe,janedoe') self.assertEqual(link.message, 'well hello there') def test_create_love_link(self): - link = logic.love_link.create_love_link('jake', "it's adventure time!") + link = loveapp.logic.love_link.create_love_link('jake', "it's adventure time!") self.assertEqual(link.recipient_list, 'jake') self.assertEqual(link.message, "it's adventure time!") def test_add_recipient(self): - link = logic.love_link.create_love_link('finn', 'Mathematical!') + link = loveapp.logic.love_link.create_love_link('finn', 'Mathematical!') - logic.love_link.add_recipient(link.hash_key, 'princessbubblegum') - new_link = logic.love_link.get_love_link(link.hash_key) + loveapp.logic.love_link.add_recipient(link.hash_key, 'princessbubblegum') + new_link = loveapp.logic.love_link.get_love_link(link.hash_key) self.assertEqual(new_link.recipient_list, 'finn, princessbubblegum') def test_love_links_cleanup(self): - new_love = logic.love_link.create_love_link('jake', "I'm new love!") - old_love = logic.love_link.create_love_link('finn', "I'm old love :(") + new_love = loveapp.logic.love_link.create_love_link('jake', "I'm new love!") + old_love = loveapp.logic.love_link.create_love_link('finn', "I'm old love :(") old_love.timestamp = datetime.datetime.now() - datetime.timedelta(days=31) old_love.put() - logic.love_link.love_links_cleanup() - db_love = logic.love_link.get_love_link(new_love.hash_key) + loveapp.logic.love_link.love_links_cleanup() + db_love = loveapp.logic.love_link.get_love_link(new_love.hash_key) self.assertEqual(db_love.hash_key, new_love.hash_key) with self.assertRaises(NoSuchLoveLink): - logic.love_link.get_love_link(old_love.hash_key) + loveapp.logic.love_link.get_love_link(old_love.hash_key) diff --git a/tests/logic/love_test.py b/tests/logic/love_test.py index b20f81a..8b8aa7e 100644 --- a/tests/logic/love_test.py +++ b/tests/logic/love_test.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -import mock import unittest -from config import CompanyValue -import logic.love +import mock +import pytest + +import loveapp.logic.love from errors import TaintedLove +from loveapp.config import CompanyValue from testing.factories import create_alias_with_employee_username from testing.factories import create_employee -class SendLovesTest(unittest.TestCase): - nosegae_taskqueue = True - nosegae_memcache = True - nosegae_datastore_v3 = True +@pytest.mark.usefixtures('gae_testbed') +class TestSendLoves(unittest.TestCase): def setUp(self): self.alice = create_employee(username='alice') @@ -20,48 +20,50 @@ def setUp(self): self.carol = create_employee(username='carol') self.message = 'hallo' - def test_send_loves(self): - logic.love.send_loves( + @mock.patch('google.appengine.api.taskqueue.add', autospec=True) + def test_send_loves(self, mock_taskqueue_add): + loveapp.logic.love.send_loves( set(['bob', 'carol']), self.message, sender_username='alice', ) - loves_for_bob = logic.love.get_love(None, 'bob').get_result() + loves_for_bob = loveapp.logic.love.get_love(None, 'bob').get_result() self.assertEqual(len(loves_for_bob), 1) self.assertEqual(loves_for_bob[0].sender_key, self.alice.key) self.assertEqual(loves_for_bob[0].message, self.message) - loves_for_carol = logic.love.get_love(None, 'carol').get_result() + loves_for_carol = loveapp.logic.love.get_love(None, 'carol').get_result() self.assertEqual(len(loves_for_carol), 1) self.assertEqual(loves_for_carol[0].sender_key, self.alice.key) self.assertEqual(loves_for_carol[0].message, self.message) def test_invalid_sender(self): with self.assertRaises(TaintedLove): - logic.love.send_loves( + loveapp.logic.love.send_loves( set(['alice']), 'hallo', sender_username='wwu', ) - def test_sender_is_a_recipient(self): - logic.love.send_loves( + @mock.patch('google.appengine.api.taskqueue.add', autospec=True) + def test_sender_is_a_recipient(self, mock_taskqueue_add): + loveapp.logic.love.send_loves( set(['bob', 'alice']), self.message, sender_username='alice', ) - loves_for_bob = logic.love.get_love('alice', 'bob').get_result() + loves_for_bob = loveapp.logic.love.get_love('alice', 'bob').get_result() self.assertEqual(len(loves_for_bob), 1) self.assertEqual(loves_for_bob[0].message, self.message) - loves_for_alice = logic.love.get_love(None, 'alice').get_result() + loves_for_alice = loveapp.logic.love.get_love(None, 'alice').get_result() self.assertEqual(loves_for_alice, []) def test_sender_is_only_recipient(self): with self.assertRaises(TaintedLove): - logic.love.send_loves( + loveapp.logic.love.send_loves( set(['alice']), self.message, sender_username='alice', @@ -69,22 +71,23 @@ def test_sender_is_only_recipient(self): def test_invalid_recipient(self): with self.assertRaises(TaintedLove): - logic.love.send_loves( + loveapp.logic.love.send_loves( set(['bob', 'dean']), 'hallo', sender_username='alice', ) - loves_for_bob = logic.love.get_love('alice', 'bob').get_result() + loves_for_bob = loveapp.logic.love.get_love('alice', 'bob').get_result() self.assertEqual(loves_for_bob, []) - def test_send_loves_with_alias(self): + @mock.patch('google.appengine.api.taskqueue.add', autospec=True) + def test_send_loves_with_alias(self, mock_taskqueue_add): message = 'Loving your alias' create_alias_with_employee_username(name='bobby', username=self.bob.username) - logic.love.send_loves(['bobby'], message, sender_username=self.carol.username) + loveapp.logic.love.send_loves(['bobby'], message, sender_username=self.carol.username) - loves_for_bob = logic.love.get_love('carol', 'bob').get_result() + loves_for_bob = loveapp.logic.love.get_love('carol', 'bob').get_result() self.assertEqual(len(loves_for_bob), 1) self.assertEqual(loves_for_bob[0].sender_key, self.carol.key) self.assertEqual(loves_for_bob[0].message, message) @@ -93,20 +96,21 @@ def test_send_loves_with_alias_and_username_for_same_user(self): create_alias_with_employee_username(name='bobby', username=self.bob.username) with self.assertRaises(TaintedLove): - logic.love.send_loves(['bob', 'bobby'], 'hallo', sender_username='alice') + loveapp.logic.love.send_loves(['bob', 'bobby'], 'hallo', sender_username='alice') - loves_for_bob = logic.love.get_love('alice', 'bob').get_result() + loves_for_bob = loveapp.logic.love.get_love('alice', 'bob').get_result() self.assertEqual(loves_for_bob, []) - @mock.patch('util.company_values.config') - def test_send_love_with_value_hashtag(self, mock_config): + @mock.patch('loveapp.util.company_values.config') + @mock.patch('google.appengine.api.taskqueue.add', autospec=True) + def test_send_love_with_value_hashtag(self, mock_taskqueue_add, mock_config): mock_config.COMPANY_VALUES = [ CompanyValue('AWESOME', 'awesome', ['awesome']) ] message = 'Loving your alias #Awesome' create_alias_with_employee_username(name='bobby', username=self.bob.username) - logic.love.send_loves(['bobby'], message, sender_username=self.carol.username) + loveapp.logic.love.send_loves(['bobby'], message, sender_username=self.carol.username) - loves_for_bob = logic.love.get_love('carol', 'bob').get_result() + loves_for_bob = loveapp.logic.love.get_love('carol', 'bob').get_result() self.assertEqual(len(loves_for_bob), 1) self.assertEqual(loves_for_bob[0].company_values, ['AWESOME']) diff --git a/tests/logic/office_test.py b/tests/logic/office_test.py index 48bc77f..8845715 100644 --- a/tests/logic/office_test.py +++ b/tests/logic/office_test.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- import unittest + import mock -from logic.office import REMOTE_OFFICE -from logic.office import get_all_offices -from logic.office import OfficeParser +import pytest + +from loveapp.logic.office import get_all_offices +from loveapp.logic.office import OfficeParser +from loveapp.logic.office import REMOTE_OFFICE from testing.factories import create_employee OFFICES = { @@ -12,9 +15,7 @@ } -class OfficeTest(unittest.TestCase): - # enable the datastore stub - nosegae_datastore_v3 = True +class TestOffice(unittest.TestCase): def setUp(self): self.employee_dicts = [ @@ -27,11 +28,12 @@ def _create_employees(self): for office in OFFICES: create_employee(office=office, username='{}-{}'.format('username', office)) + @pytest.mark.usefixtures('gae_testbed') def test_get_all_offices(self): self._create_employees() - self.assertEqual(OFFICES, set(get_all_offices())) + assert OFFICES == set(get_all_offices()) - @mock.patch('logic.office.yaml.safe_load', return_value=OFFICES) + @mock.patch('loveapp.logic.office.yaml.safe_load', return_value=OFFICES) def test_employee_parser_no_team_match(self, mock_offices): office_parser = OfficeParser() self.assertEqual( @@ -54,7 +56,7 @@ def test_employee_parser_no_team_match(self, mock_offices): ) mock_offices.assert_called_once() - @mock.patch('logic.office.yaml.safe_load', return_value=OFFICES) + @mock.patch('loveapp.logic.office.yaml.safe_load', return_value=OFFICES) def test_employee_parser_with_team_match(self, mock_offices): office_parser = OfficeParser(self.employee_dicts) for employee in self.employee_dicts: diff --git a/tests/logic/secret_test.py b/tests/logic/secret_test.py index 23a8425..37ca965 100644 --- a/tests/logic/secret_test.py +++ b/tests/logic/secret_test.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- import unittest +import pytest + from errors import NoSuchSecret -from logic.secret import get_secret +from loveapp.logic.secret import get_secret from testing.factories import create_secret +@pytest.mark.usefixtures('gae_testbed') class SecretTest(unittest.TestCase): - nosegae_datastore_v3 = True def test_existing_secret(self): create_secret('FOO', value='bar') diff --git a/tests/logic/subscription_test.py b/tests/logic/subscription_test.py index 8460add..692433e 100644 --- a/tests/logic/subscription_test.py +++ b/tests/logic/subscription_test.py @@ -1,23 +1,19 @@ # -*- coding: utf-8 -*- import mock -import unittest -import logic.subscription -from models import Subscription +import loveapp.logic.subscription +from loveapp.models import Subscription from testing.factories import create_employee from testing.factories import create_subscription -class SubscriptionTest(unittest.TestCase): - nosegae_datastore_v3 = True +@mock.patch('loveapp.models.subscription.Employee', autospec=True) +def test_delete_subscription(mock_model_employee, gae_testbed): + mock_model_employee.get_current_employee.return_value = create_employee() - @mock.patch('models.subscription.Employee', autospec=True) - def test_delete_subscription(self, mock_model_employee): - mock_model_employee.get_current_employee.return_value = create_employee() + subscription = create_subscription() + assert Subscription.get_by_id(subscription.key.id()) is not None - subscription = create_subscription() - self.assertIsNotNone(Subscription.get_by_id(subscription.key.id())) + loveapp.logic.subscription.delete_subscription(subscription.key.id()) - logic.subscription.delete_subscription(subscription.key.id()) - - self.assertIsNone(Subscription.get_by_id(subscription.key.id())) + assert Subscription.get_by_id(subscription.key.id()) is None diff --git a/tests/models/access_key_test.py b/tests/models/access_key_test.py index bbc89ac..5ea6b70 100644 --- a/tests/models/access_key_test.py +++ b/tests/models/access_key_test.py @@ -1,16 +1,10 @@ # -*- coding: utf-8 -*- -import unittest +from loveapp.models.access_key import AccessKey -from models.access_key import AccessKey +def test_create(gae_testbed): + key = AccessKey.create('description') -class AccessKeyTest(unittest.TestCase): - # enable the datastore stub - nosegae_datastore_v3 = True - - def test_create(self): - key = AccessKey.create('description') - - self.assertIsNotNone(key) - self.assertEqual('description', key.description) - self.assertIsNotNone(key.access_key) + assert key is not None + assert 'description' == key.description + assert key.access_key is not None diff --git a/tests/models/employee_test.py b/tests/models/employee_test.py index bc6b096..0f4de0f 100644 --- a/tests/models/employee_test.py +++ b/tests/models/employee_test.py @@ -1,76 +1,73 @@ # -*- coding: utf-8 -*- import mock -import unittest - +import pytest from google.appengine.api import users from errors import NoSuchEmployee -from models.employee import Employee +from loveapp.models.employee import Employee from testing.factories import create_employee -class EmployeeTest(unittest.TestCase): - # enable the datastore stub - nosegae_datastore_v3 = True - - @mock.patch('models.employee.config') - def test_create_from_dict(self, mock_config): - mock_config.DOMAIN = 'foo.io' - - employee_dict = dict( - username='john.d', - first_name='John', - last_name='Doe', - department='Accounting', - office='USA CA SF New Montgomery', - photos=[], - ) - employee = Employee.create_from_dict(employee_dict) - - self.assertIsNotNone(employee) - self.assertIsNotNone(employee.user) - self.assertEqual('john.d@foo.io', employee.user.email()) - - @mock.patch('models.employee.users.get_current_user') - def test_get_current_employee(self, mock_get_current_user): - employee = create_employee(username='john.d') - mock_get_current_user.return_value = employee.user - current_employee = Employee.get_current_employee() - - self.assertIsNotNone(current_employee) - self.assertEqual('john.d', current_employee.username) - - @mock.patch('models.employee.users.get_current_user') - def test_get_current_employee_raises(self, mock_get_current_user): - mock_get_current_user.return_value = users.User('foo@bar.io') - - with self.assertRaises(NoSuchEmployee): - Employee.get_current_employee() - - def test_full_name(self): - employee = create_employee(first_name='Foo', last_name='Bar') - self.assertEqual('Foo Bar', employee.full_name) - - @mock.patch('models.employee.config') - def test_gravatar_backup(self, mock_config): - mock_config.GRAVATAR = 'backup' - employee = create_employee(photo_url='') - self.assertEqual(employee.get_gravatar(), employee.get_photo_url()) - employee = create_employee(photo_url='http://example.com/example.jpg') - self.assertEqual(employee.photo_url, employee.get_photo_url()) - - @mock.patch('models.employee.config') - def test_gravatar_always(self, mock_config): - mock_config.GRAVATAR = 'always' - employee = create_employee(photo_url='') - self.assertEqual(employee.get_gravatar(), employee.get_photo_url()) - employee = create_employee(photo_url='http://example.com/example.jpg') - self.assertEqual(employee.get_gravatar(), employee.get_photo_url()) - - @mock.patch('models.employee.config') - def test_gravatar_disabled(self, mock_config): - mock_config.GRAVATAR = 'disabled' - employee = create_employee(photo_url='') - self.assertEqual(employee.photo_url, employee.get_photo_url()) - employee = create_employee(photo_url='http://example.com/example.jpg') - self.assertEqual(employee.photo_url, employee.get_photo_url()) +def test_create_from_dict(mock_config, gae_testbed): + mock_config.DOMAIN = 'foo.io' + + employee_dict = dict( + username='john.d', + first_name='John', + last_name='Doe', + department='Accounting', + office='USA CA SF New Montgomery', + photos=[], + ) + employee = Employee.create_from_dict(employee_dict) + + assert employee is not None + assert employee.user is not None + assert 'john.d@foo.io' == employee.user.email() + + +@mock.patch('loveapp.models.employee.users.get_current_user') +def test_get_current_employee(mock_get_current_user, gae_testbed): + employee = create_employee(username='john.d') + mock_get_current_user.return_value = employee.user + current_employee = Employee.get_current_employee() + + assert current_employee is not None + assert 'john.d' == current_employee.username + + +@mock.patch('loveapp.models.employee.users.get_current_user') +def test_get_current_employee_raises(mock_get_current_user, gae_testbed): + mock_get_current_user.return_value = users.User('foo@bar.io') + + with pytest.raises(NoSuchEmployee): + Employee.get_current_employee() + + +def test_full_name(gae_testbed): + employee = create_employee(first_name='Foo', last_name='Bar') + assert 'Foo Bar' == employee.full_name + + +def test_gravatar_backup(mock_config, gae_testbed): + mock_config.GRAVATAR = 'backup' + employee = create_employee(photo_url='') + assert employee.get_gravatar() == employee.get_photo_url() + employee = create_employee(photo_url='http://example.com/example.jpg') + assert employee.photo_url == employee.get_photo_url() + + +def test_gravatar_always(mock_config, gae_testbed): + mock_config.GRAVATAR = 'always' + employee = create_employee(photo_url='') + assert employee.get_gravatar() == employee.get_photo_url() + employee = create_employee(photo_url='http://example.com/example.jpg') + assert employee.get_gravatar() == employee.get_photo_url() + + +def test_gravatar_disabled(mock_config, gae_testbed): + mock_config.GRAVATAR = 'disabled' + employee = create_employee(photo_url='') + assert employee.photo_url == employee.get_photo_url() + employee = create_employee(photo_url='http://example.com/example.jpg') + assert employee.photo_url == employee.get_photo_url() diff --git a/tests/models/love_link_test.py b/tests/models/love_link_test.py index 3144789..775ca48 100644 --- a/tests/models/love_link_test.py +++ b/tests/models/love_link_test.py @@ -1,17 +1,12 @@ # -*- coding: utf-8 -*- import mock -import unittest from testing.factories import create_love_link -class LoveLinkTest(unittest.TestCase): - # enable the datastore stub - nosegae_datastore_v3 = True +@mock.patch('loveapp.models.love_link.config') +def test_url(mock_config, gae_testbed): + mock_config.APP_BASE_URL = 'http://foo.io/' - @mock.patch('models.love_link.config') - def test_url(self, mock_config): - mock_config.APP_BASE_URL = 'http://foo.io/' - - link = create_love_link(hash_key='lOvEr') - self.assertEqual('http://foo.io/l/lOvEr', link.url) + link = create_love_link(hash_key='lOvEr') + assert 'http://foo.io/l/lOvEr' == link.url diff --git a/tests/util/company_values_test.py b/tests/util/company_values_test.py index 935d8e3..1f4260b 100644 --- a/tests/util/company_values_test.py +++ b/tests/util/company_values_test.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -import mock import unittest -from config import CompanyValue -import util.company_values +import mock + +import loveapp.util.company_values +from loveapp.config import CompanyValue class CompanyValuesUtilTest(unittest.TestCase): @@ -11,32 +12,32 @@ class CompanyValuesUtilTest(unittest.TestCase): COMPANY_VALUE_ONE = CompanyValue('FAKE_VALUE_ONE', 'Fake Value 1', ['fakevalue1']) COMPANY_VALUE_TWO = CompanyValue('FAKE_VALUE_TWO', 'Fake Value 2', ['fakevalue2', 'otherhashtag']) - @mock.patch('util.company_values.config') + @mock.patch('loveapp.util.company_values.config') def test_get_company_value(self, mock_config): mock_config.COMPANY_VALUES = [ self.COMPANY_VALUE_ONE, self.COMPANY_VALUE_TWO ] - company_value = util.company_values.get_company_value(self.COMPANY_VALUE_ONE.id) + company_value = loveapp.util.company_values.get_company_value(self.COMPANY_VALUE_ONE.id) self.assertEqual(company_value, self.COMPANY_VALUE_ONE) - probably_None = util.company_values.get_company_value('fake_value') + probably_None = loveapp.util.company_values.get_company_value('fake_value') self.assertEqual(None, probably_None) - @mock.patch('util.company_values.config') + @mock.patch('loveapp.util.company_values.config') def test_supported_hashtags(self, mock_config): mock_config.COMPANY_VALUES = [] - supported_hashtags = util.company_values.supported_hashtags() + supported_hashtags = loveapp.util.company_values.supported_hashtags() self.assertEqual(supported_hashtags, []) mock_config.COMPANY_VALUES = [self.COMPANY_VALUE_TWO] - supported_hashtags = util.company_values.supported_hashtags() + supported_hashtags = loveapp.util.company_values.supported_hashtags() self.assertEqual(supported_hashtags, ['#fakevalue2', '#otherhashtag']) - @mock.patch('util.company_values.config') + @mock.patch('loveapp.util.company_values.config') def test_get_hashtag_value_mapping(self, mock_config): mock_config.COMPANY_VALUES = [] - hashtag_mapping = util.company_values.get_hashtag_value_mapping() + hashtag_mapping = loveapp.util.company_values.get_hashtag_value_mapping() self.assertEqual({}, hashtag_mapping) mock_config.COMPANY_VALUES = [ @@ -49,14 +50,14 @@ def test_get_hashtag_value_mapping(self, mock_config): '#' + self.COMPANY_VALUE_TWO.hashtags[0]: self.COMPANY_VALUE_TWO.id, '#' + self.COMPANY_VALUE_TWO.hashtags[1]: self.COMPANY_VALUE_TWO.id, } - hashtag_mapping = util.company_values.get_hashtag_value_mapping() + hashtag_mapping = loveapp.util.company_values.get_hashtag_value_mapping() self.assertEqual(expected_mapping, hashtag_mapping) - @mock.patch('util.company_values.config') + @mock.patch('loveapp.util.company_values.config') def test_linkify_company_values(self, mock_config): mock_config.COMPANY_VALUES = [] love_text = u'who wants to #liveForever? ðŸ˜' - linkified_value = util.company_values.linkify_company_values(love_text) + linkified_value = loveapp.util.company_values.linkify_company_values(love_text) # should be the same, because there's no hashtags. self.assertEqual(love_text, linkified_value) @@ -64,11 +65,11 @@ def test_linkify_company_values(self, mock_config): CompanyValue('FREDDIE', 'Mercury', ('liveForever',)) ] love_text = 'who wants to #liveForever?' - linkified_value = util.company_values.linkify_company_values(love_text) + linkified_value = loveapp.util.company_values.linkify_company_values(love_text) # there should be a link in here now self.assertIn('href', linkified_value) - @mock.patch('util.company_values.config') + @mock.patch('loveapp.util.company_values.config') def test_values_matching_prefix(self, mock_config): mock_config.COMPANY_VALUES = [ CompanyValue('TEST', 'test', ['abseil', 'absolute', 'abrasion']) @@ -76,10 +77,10 @@ def test_values_matching_prefix(self, mock_config): self.assertEqual( set(['#abseil', '#absolute', '#abrasion']), - set(util.company_values.values_matching_prefix('#a')) + set(loveapp.util.company_values.values_matching_prefix('#a')) ) self.assertEqual( set(['#abseil', '#absolute']), - set(util.company_values.values_matching_prefix('#abs')) + set(loveapp.util.company_values.values_matching_prefix('#abs')) ) diff --git a/tests/util/email_test.py b/tests/util/email_test.py index f825f15..cf5a16e 100644 --- a/tests/util/email_test.py +++ b/tests/util/email_test.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import unittest -import util.email +import loveapp.util.email class GetNameAndEmailTest(unittest.TestCase): @@ -9,12 +9,12 @@ class GetNameAndEmailTest(unittest.TestCase): def test_bare_email(self): email_string = 'darwin@example.com' - email, name = util.email.get_name_and_email(email_string) + email, name = loveapp.util.email.get_name_and_email(email_string) self.assertEqual(email, email_string) self.assertIsNone(name) def test_name_and_email(self): email_string = 'Darwin Stoppelman