0) {
+ ajax_options["data"] = JSON.stringify(jsondata)
+ }
var request = $.ajax(ajax_options);
diff --git a/gengine/templates/admin/index.html b/gengine/app/templates/admin/index.html
similarity index 65%
rename from gengine/templates/admin/index.html
rename to gengine/app/templates/admin/index.html
index 78ebc0b..181ef96 100644
--- a/gengine/templates/admin/index.html
+++ b/gengine/app/templates/admin/index.html
@@ -42,6 +42,12 @@ Welcome to the Gamification Engine Admin-Area
+ {%if settings_enable_authentication %}
+
+ {% endif %}
+
+
+
@@ -98,6 +104,11 @@
Welcome to the Gamification Engine Admin-Area
+
+
+
+
+
@@ -113,7 +124,52 @@
Welcome to the Gamification Engine Admin-Area
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -148,6 +204,28 @@ Welcome to the Gamification Engine Admin-Area
GET to "/achievement/{achievement_id}/level/{level}"
+
+ {%if settings_enable_authentication %}
+ Login
+
+ POST to "/auth/login"
+
+ {% endif %}
+
+ Register Device
+
+ POST to "/register_device/{user_id}"
+
+
+ Get Messages
+
+ GET to "/messages/{user_id}"
+
+
+ Set Messages Read
+
+ POST to "/read_messages/{user_id}"
+
@@ -159,4 +237,5 @@ Welcome to the Gamification Engine Admin-Area
{% block tail %}
{{ super() }}
-{% endblock %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/gengine/templates/admin/layout.html b/gengine/app/templates/admin/layout.html
similarity index 92%
rename from gengine/templates/admin/layout.html
rename to gengine/app/templates/admin/layout.html
index 12e3f9a..be1fd6c 100644
--- a/gengine/templates/admin/layout.html
+++ b/gengine/app/templates/admin/layout.html
@@ -61,6 +61,10 @@
{% endif %}
{% endfor %}
+
+
+ Authentication: {{ ('active' if settings_enable_authentication else 'inactive') | safe }}
+
{% endmacro %}
{% macro messages() %}
diff --git a/gengine/templates/admin_layout.html b/gengine/app/templates/admin_layout.html
similarity index 81%
rename from gengine/templates/admin_layout.html
rename to gengine/app/templates/admin_layout.html
index f9bdff9..f8aadaa 100644
--- a/gengine/templates/admin_layout.html
+++ b/gengine/app/templates/admin_layout.html
@@ -3,12 +3,15 @@
{% block head_tail %}
{{ super() }}
-
+
+
{% endblock %}
{% block tail %}
{{ super() }}
-
+
{% endblock %}
diff --git a/gengine/templates/admin_maintenance.html b/gengine/app/templates/admin_maintenance.html
similarity index 100%
rename from gengine/templates/admin_maintenance.html
rename to gengine/app/templates/admin_maintenance.html
diff --git a/gengine/templates/error.html b/gengine/app/templates/error.html
similarity index 100%
rename from gengine/templates/error.html
rename to gengine/app/templates/error.html
diff --git a/gengine/app/tests/__init__.py b/gengine/app/tests/__init__.py
new file mode 100644
index 0000000..fc80254
--- /dev/null
+++ b/gengine/app/tests/__init__.py
@@ -0,0 +1 @@
+pass
\ No newline at end of file
diff --git a/gengine/app/tests/base.py b/gengine/app/tests/base.py
new file mode 100644
index 0000000..32f0e2c
--- /dev/null
+++ b/gengine/app/tests/base.py
@@ -0,0 +1,59 @@
+import unittest
+import os
+from sqlalchemy.engine import create_engine
+from sqlalchemy.sql.schema import Table
+from sqlalchemy.orm.scoping import scoped_session
+from gengine.metadata import init_session, get_sessionmaker
+from gengine.app.tests import db
+
+class BaseDBTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ if cls is BaseDBTest:
+ raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
+ super(BaseDBTest, cls).setUpClass()
+
+ def setUp(self):
+ from gengine.app.cache import clear_all_caches
+ clear_all_caches()
+ self.db = db.db()
+ dsn = self.db.dsn()
+ self.engine = create_engine(
+ "postgresql://%(user)s@%(host)s:%(port)s/%(database)s" % {
+ "user" : dsn["user"],
+ "host": dsn["host"],
+ "port": dsn["port"],
+ "database": dsn["database"],
+ }
+ )
+ init_session(override_session=scoped_session(get_sessionmaker(bind=self.engine)), replace=True)
+ from gengine.metadata import Base
+ Base.metadata.bind = self.engine
+
+ Base.metadata.drop_all(self.engine)
+ self.engine.execute("DROP SCHEMA IF EXISTS public CASCADE")
+ self.engine.execute("CREATE SCHEMA IF NOT EXISTS public")
+
+ from alembic.config import Config
+ from alembic import command
+
+ alembic_cfg = Config(attributes={
+ 'engine': self.engine,
+ 'schema': 'public'
+ })
+ script_location = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
+ 'app/alembic'
+ )
+ alembic_cfg.set_main_option("script_location", script_location)
+
+ from gengine.app import model
+
+ tables = [t for name, t in model.__dict__.items() if isinstance(t, Table)]
+ Base.metadata.create_all(self.engine, tables=tables)
+
+ command.stamp(alembic_cfg, "head")
+
+ def tearDown(self):
+ self.db.stop()
diff --git a/gengine/app/tests/db.py b/gengine/app/tests/db.py
new file mode 100644
index 0000000..21eb8a1
--- /dev/null
+++ b/gengine/app/tests/db.py
@@ -0,0 +1,25 @@
+import os
+
+import logging
+log = logging.getLogger(__name__)
+
+try:
+ import testing.postgresql
+except ImportError as e:
+ log.info("testing.postgresql not installed")
+
+db = None
+
+def setupDB():
+ # Generate Postgresql class which shares the generated database
+ global db
+ db = testing.postgresql.PostgresqlFactory(
+ postgres=os.environ.get("TEST_POSTGRES",None),
+ initdb=os.environ.get("TEST_INITDB",None),
+ cache_initialized_db=True
+ )
+
+def unsetupDB():
+ # clear cached database at end of tests
+ global db
+ db.clear_cache()
diff --git a/gengine/app/tests/helpers.py b/gengine/app/tests/helpers.py
new file mode 100644
index 0000000..6ab5a51
--- /dev/null
+++ b/gengine/app/tests/helpers.py
@@ -0,0 +1,495 @@
+import random
+import datetime
+
+from gengine.app.model import User, Language, Achievement,Goal, Variable, Value, t_goals, GoalProperty, GoalGoalProperty, TranslationVariable, \
+ t_goals_goalproperties, t_users, GoalEvaluationCache, Reward, AchievementReward, AchievementUser
+from gengine.metadata import DBSession
+
+from gengine.app.model import UserDevice, t_user_device
+from sqlalchemy import and_, select
+
+import logging
+log = logging.getLogger(__name__)
+
+try:
+ import names
+except ImportError as e:
+ log.info("names not installed")
+
+default_gen_data = {
+ "timezone" : "Europe/Berlin",
+ "area" : {
+ "min_lat" : 51.65,
+ "max_lat" : 51.75,
+ "min_lng" : 8.70,
+ "max_lng" : 8.79
+ },
+ "country" : "DE",
+ "region" : "NRW",
+ "city" : "Paderborn",
+ "language" : "de",
+ "additional_public_data" : {
+ "first_name" : "Matthew",
+ "last_name" : "Hayden"
+ }
+}
+
+alt_gen_data = {
+ "timezone" : "US/Eastern",
+ "area" : {
+ "min_lat" : 40.680,
+ "max_lat" : 40.780,
+ "min_lng" : -73.89,
+ "max_lng" : -73.97
+ }
+}
+
+default_device_data = {
+ "device_os" : "iOS 5",
+ "app_version" : "1.1",
+ "push_id" : "5678",
+ "device_id" : "1234"
+}
+
+
+class Undefined():
+ pass
+
+undefined = Undefined()
+
+
+def randrange_float(f1,f2):
+ return random.random() * abs(f1 - f2) + min(f1,f2)
+
+
+def create_user(
+ user_id = undefined,
+ lat = undefined,
+ lng = undefined,
+ country = undefined,
+ region = undefined,
+ city = undefined,
+ timezone = undefined,
+ language = undefined,
+ friends = [],
+ groups = [],
+ additional_public_data = undefined,
+ gen_data = default_gen_data
+ ):
+ if additional_public_data is undefined:
+ additional_public_data = {
+ 'first_name' : 'Stefan',
+ 'last_name' : 'Rogers'
+ }
+
+ if user_id is undefined:
+ user_id = (DBSession.execute("SELECT max(id) as c FROM users").scalar() or 0) + 1
+ if lat is undefined:
+ lat = randrange_float(gen_data["area"]["min_lat"],gen_data["area"]["max_lat"])
+
+ if lng is undefined:
+ lng = randrange_float(gen_data["area"]["min_lng"], gen_data["area"]["max_lng"])
+
+ if country is undefined:
+ country = gen_data["country"]
+
+ if timezone is undefined:
+ timezone = gen_data["timezone"]
+
+ if region is undefined:
+ region = gen_data["region"]
+
+ if city is undefined:
+ city = gen_data["city"]
+
+ if language is undefined:
+ language = gen_data["language"]
+
+ User.set_infos(
+ user_id = user_id,
+ lat = lat,
+ lng = lng,
+ timezone = timezone,
+ country = country,
+ region = region,
+ city = city,
+ language = language,
+ groups = groups,
+ friends = friends,
+ additional_public_data = additional_public_data
+ )
+
+ return User.get_user(user_id)
+
+
+def update_user(
+ user_id = undefined,
+ lat = undefined,
+ lng = undefined,
+ country = undefined,
+ region = undefined,
+ city = undefined,
+ timezone = undefined,
+ language = undefined,
+ friends = [],
+ groups = [],
+ additional_public_data = undefined,
+ ):
+
+ User.set_infos(
+ user_id = user_id,
+ lat = lat,
+ lng = lng,
+ timezone = timezone,
+ country = country,
+ region = region,
+ city = city,
+ language = language,
+ groups = groups,
+ friends = friends,
+ additional_public_data = additional_public_data
+ )
+
+ return User.get_user(user_id)
+
+
+def delete_user(
+ user_id = undefined,
+ ):
+
+ User.delete_user(user_id)
+ users = DBSession.execute(select([t_users.c.id,])).fetchall()
+ return users
+
+
+def get_or_create_language(name):
+ lang = DBSession.query(Language).filter_by(name=name).first()
+ if not lang:
+ lang = Language()
+ lang.name = name
+ DBSession.add(lang)
+ DBSession.flush()
+ return lang
+
+
+def create_device(
+ user_id=undefined,
+ device_id=undefined,
+ device_os=undefined,
+ push_id=undefined,
+ app_version=undefined,
+ gen_data=default_device_data
+ ):
+
+ if push_id is undefined:
+ push_id = gen_data["push_id"]
+
+ if device_os is undefined:
+ device_os = gen_data["device_os"]
+
+ if app_version is undefined:
+ app_version = gen_data["app_version"]
+
+ if device_id is undefined:
+ device_id = gen_data["device_id"]
+
+ UserDevice.add_or_update_device(
+ device_id = device_id,
+ user_id = user_id,
+ device_os = device_os,
+ push_id = push_id,
+ app_version = app_version
+ )
+
+ device = DBSession.execute(t_user_device.select().where(and_(
+ t_user_device.c.device_id == device_id,
+ t_user_device.c.user_id == user_id
+ ))).fetchone()
+
+ return device
+
+
+def update_device(
+ user_id=undefined,
+ device_id=undefined,
+ device_os=undefined,
+ push_id=undefined,
+ app_version=undefined,
+ ):
+ UserDevice.add_or_update_device(
+ device_id=device_id,
+ user_id=user_id,
+ device_os=device_os,
+ push_id=push_id,
+ app_version=app_version
+ )
+
+ device = DBSession.execute(t_user_device.select().where(and_(
+ t_user_device.c.device_id == device_id,
+ t_user_device.c.user_id == user_id
+ ))).fetchone()
+
+ return device
+
+
+def create_achievement(
+ achievement_name = undefined,
+ achievement_valid_start = undefined,
+ achievement_valid_end = undefined,
+ achievement_lat = undefined,
+ achievement_lng = undefined,
+ achievement_max_distance = undefined,
+ achievement_evaluation = undefined,
+ achievement_relevance = undefined,
+ achievement_maxlevel = undefined,
+ achievement_view_permission = undefined
+ ):
+ achievement = Achievement()
+
+ if achievement_name is undefined:
+ achievement.name = "invite_users_achievement"
+ else:
+ achievement.name = achievement_name
+
+ if achievement_valid_start is undefined:
+ achievement.valid_start = "2016-12-16"
+ else:
+ achievement.valid_start = achievement_valid_start
+
+ if achievement_valid_end is undefined:
+ achievement.valid_end = datetime.date.today()
+ else:
+ achievement.valid_end = achievement_valid_end
+
+ if achievement_lat is undefined:
+ achievement.lat = 40.983
+ else:
+ achievement.lat = achievement_lat
+
+ if achievement_lng is undefined:
+ achievement.lng = 41.562
+ else:
+ achievement.lng = achievement_lng
+
+ if achievement_max_distance is undefined:
+ achievement.max_distance = 20000
+ else:
+ achievement.max_distance = achievement_max_distance
+
+ if achievement_evaluation is undefined:
+ achievement.evaluation = "immediately"
+ else:
+ achievement.evaluation = achievement_evaluation
+
+ if achievement_relevance is undefined:
+ achievement.relevance = "friends"
+ else:
+ achievement.relevance = achievement_relevance
+
+ if achievement_maxlevel is undefined:
+ achievement.maxlevel = 3
+ else:
+ achievement.maxlevel = achievement_maxlevel
+
+ if achievement_view_permission is undefined:
+ achievement.view_permission = "everyone"
+ else:
+ achievement.view_permission = achievement_view_permission
+
+ achievement.evaluation_timezone = "UTC"
+
+ DBSession.add(achievement)
+ DBSession.flush()
+
+ return achievement
+
+
+def create_goals(
+ achievement = undefined,
+ goal_condition = undefined,
+ goal_goal = undefined,
+ goal_operator = undefined,
+ goal_group_by_key = undefined,
+ goal_name = undefined
+ ):
+ goal = Goal()
+ if achievement["name"] is "invite_users_achievement":
+
+ if goal_condition is undefined:
+ goal.condition = """{"term": {"type": "literal", "variable": "invite_users"}}"""
+ else:
+ goal.condition = goal_condition
+
+ if goal_goal is undefined:
+ goal.goal = "5*level"
+ else:
+ goal.goal = goal_goal
+
+ if goal_operator is undefined:
+ goal.operator = "geq"
+ else:
+ goal.operator = goal_operator
+
+ if goal_group_by_key is undefined:
+ goal.group_by_key = False
+ else:
+ goal.group_by_key = goal_group_by_key
+
+ if goal_name is undefined:
+ goal.name = "goal_invite_users"
+ else:
+ goal.name = goal_name
+
+ goal.achievement_id = achievement.id
+ DBSession.add(goal)
+ DBSession.flush()
+
+ if achievement["name"] is "participate_achievement":
+
+ if goal_condition is undefined:
+ goal.condition = """{"term": {"key": ["5","7"], "type": "literal", "key_operator": "IN", "variable": "participate"}}"""
+ else:
+ goal.condition = goal_condition
+
+ if goal_goal is undefined:
+ goal.goal = "3*level"
+ else:
+ goal.goal = goal_goal
+
+ if goal_operator is undefined:
+ goal.operator = "geq"
+ else:
+ goal.operator = goal_operator
+
+ if goal_group_by_key is undefined:
+ goal.group_by_key = True
+ else:
+ goal.group_by_key = goal_group_by_key
+
+ if goal_name is undefined:
+ goal.name = "goal_participate"
+ else:
+ goal.name = goal_name
+
+ goal.achievement_id = achievement.id
+ DBSession.add(goal)
+ DBSession.flush()
+
+ return goal
+
+
+def create_goal_properties(goal_id):
+
+ goal_property = GoalProperty()
+ goal_property.name = "participate"
+ goal_property.is_variable = True
+ DBSession.add(goal_property)
+ DBSession.flush()
+
+ translation_variable = TranslationVariable()
+ translation_variable.name = "invite_users_goal_name"
+ DBSession.add(translation_variable)
+ DBSession.flush()
+
+ goals_goal_property = GoalGoalProperty()
+ goals_goal_property.goal_id = goal_id
+ goals_goal_property.property_id = goal_property.id
+ goals_goal_property.value = "7"
+ goals_goal_property.value_translation_id = translation_variable.id
+ goals_goal_property.from_level = 2
+ DBSession.add(goals_goal_property)
+ DBSession.flush()
+
+ goals_goal_property_result = DBSession.execute(t_goals_goalproperties.select().where(t_goals_goalproperties.c.goal_id == goal_id)).fetchone()
+
+ return goals_goal_property_result
+
+
+def create_achievement_rewards(achievement):
+ reward = Reward()
+ reward.name = "badge"
+ DBSession.add(reward)
+ DBSession.flush()
+
+ achievement_reward = AchievementReward()
+ achievement_reward.achievement_id = achievement.id
+ achievement_reward.reward_id = reward.id
+ achievement_reward.value = "https://www.gamification-software.com/img/trophy_{level1}.png"
+ achievement_reward.from_level = achievement.maxlevel
+ DBSession.add(achievement_reward)
+ DBSession.flush()
+
+ return achievement_reward
+
+
+def create_achievement_user(user, achievement, achievement_date, level):
+ achievement_user = AchievementUser()
+ achievement_user.user_id = user.id
+ achievement_user.achievement_id = achievement.id
+ achievement_user.achievement_date = achievement_date
+ achievement_user.level = level
+ DBSession.add(achievement_user)
+ DBSession.flush()
+
+ return achievement_user
+
+
+def create_goal_evaluation_cache(
+ goal_id ,
+ gec_achievement_date,
+ gec_user_id,
+ gec_achieved = undefined,
+ gec_value = undefined,
+ ):
+ goal_evaluation_cache = GoalEvaluationCache()
+
+ if gec_achieved is undefined:
+ goal_evaluation_cache.gec_achieved = True
+ else:
+ goal_evaluation_cache.gec_achieved = gec_achieved
+
+ if gec_value is undefined:
+ goal_evaluation_cache.gec_value = 20.0
+ else:
+ goal_evaluation_cache.gec_value = gec_value
+
+ goal_evaluation_cache.goal_id = goal_id
+ goal_evaluation_cache.achievement_date = gec_achievement_date
+ goal_evaluation_cache.user_id = gec_user_id
+ goal_evaluation_cache.achieved = gec_achieved
+ goal_evaluation_cache.value = gec_value
+ DBSession.add(goal_evaluation_cache)
+ DBSession.flush()
+
+ return goal_evaluation_cache
+
+
+def create_variable(
+ variable_name = undefined,
+ variable_group = undefined,
+ ):
+ variable = Variable()
+ variable.name = variable_name
+ variable.group = variable_group
+ DBSession.add(variable)
+ DBSession.flush()
+
+ return variable
+
+
+def create_value(
+ user_id=undefined,
+ variable_id=undefined,
+ var_value=undefined,
+ key="",
+ ):
+
+ value = Value()
+ value.user_id = user_id
+ value.variable_id = variable_id
+ value.value = var_value
+ value.key = key
+ DBSession.add(value)
+ DBSession.flush()
+
+ return value
diff --git a/gengine/app/tests/runner.py b/gengine/app/tests/runner.py
new file mode 100644
index 0000000..a25393c
--- /dev/null
+++ b/gengine/app/tests/runner.py
@@ -0,0 +1,55 @@
+from gengine.app.tests import db as db
+from gengine.metadata import init_declarative_base, init_session
+import unittest
+import os
+import pkgutil
+import logging
+import sys
+
+log = logging.getLogger(__name__)
+
+try:
+ import testing.redis
+except ImportError as e:
+ log.info("testing.redis not installed")
+
+init_session()
+init_declarative_base()
+
+__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))]
+
+def create_test_suite():
+ suite = unittest.TestSuite()
+ for imp, modname, _ in pkgutil.walk_packages(__path__):
+ mod = imp.find_module(modname).load_module(modname)
+ for test in unittest.defaultTestLoader.loadTestsFromModule(mod):
+ suite.addTests(test)
+ return suite
+
+if __name__=="__main__":
+ exit = 1
+ try:
+ redis = testing.redis.RedisServer()
+
+ from gengine.base.cache import setup_redis_cache
+ dsn = redis.dsn()
+ setup_redis_cache(dsn["host"], dsn["port"], dsn["db"])
+
+ from gengine.app.cache import init_caches
+ init_caches()
+
+ db.setupDB()
+ testSuite = create_test_suite()
+ text_runner = unittest.TextTestRunner(failfast=True).run(testSuite)
+ if text_runner.wasSuccessful():
+ exit = 0
+ finally:
+ try:
+ db.unsetupDB()
+ except:
+ log.exception()
+ try:
+ redis.stop()
+ except:
+ log.exception()
+ sys.exit(exit)
diff --git a/gengine/app/tests/test_achievement1.py b/gengine/app/tests/test_achievement1.py
new file mode 100644
index 0000000..4703a95
--- /dev/null
+++ b/gengine/app/tests/test_achievement1.py
@@ -0,0 +1,403 @@
+# -*- coding: utf-8 -*-
+import datetime
+import pytz
+
+from gengine.app.cache import clear_all_caches
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, create_achievement, create_variable, create_goals, create_achievement_rewards, create_achievement_user
+from gengine.metadata import DBSession
+from gengine.app.model import Achievement, User, AchievementUser, Value, AchievementReward, Reward, AchievementProperty, AchievementAchievementProperty, t_values
+from gengine.base.model import update_connection
+
+class TestAchievement(BaseDBTest):
+
+ # Includes get_achievement_by_location and get_achievement_by_date
+ def test_get_achievements_by_location_and_date(self):
+
+ user = create_user()
+ achievement1 = create_achievement(achievement_name="invite_users_achievement")
+ achievement2 = create_achievement(achievement_name="participate_achievement")
+ create_goals(achievement1)
+ create_goals(achievement2)
+ achievement_today = Achievement.get_achievements_by_user_for_today(user)
+ print("achievement_today")
+ print(achievement_today)
+
+ self.assertEqual(achievement_today[0]["name"], "invite_users_achievement")
+ self.assertEqual(len(achievement_today), 2)
+
+ def test_get_relevant_users_by_achievement_friends_and_user(self):
+
+ #Create First user
+ user1 = create_user()
+
+ # Create Second user
+ user2 = create_user(
+ lat = 85.59,
+ lng = 65.75,
+ country = "DE",
+ region = "Niedersachsen",
+ city = "Osnabrück",
+ timezone = "Europe/Berlin",
+ language = "de",
+ additional_public_data = {
+ "first_name" : "Michael",
+ "last_name" : "Clarke"
+ }
+ )
+
+ # Create Third user
+ user3 = create_user(
+ lat = 12.1,
+ lng = 12.2,
+ country = "RO",
+ region = "Transylvania",
+ city = "Cluj-Napoca",
+ timezone = "Europe/Bukarest",
+ language = "en",
+ additional_public_data = {
+ "first_name" : "Rudolf",
+ "last_name" : "Red Nose"
+ },
+ friends=[1, 2]
+ )
+
+ # Create Fourth user
+ user4 = create_user(
+ lat = 25.56,
+ lng = 15.89,
+ country = "AU",
+ region = "Sydney",
+ city = "New South Wales",
+ timezone = "Australia",
+ language = "en",
+ additional_public_data = {
+ "first_name" : "Steve",
+ "last_name" : "Waugh"
+ },
+ friends=[3]
+ )
+
+ achievement = create_achievement()
+ friendsOfuser1 = achievement.get_relevant_users_by_achievement_and_user(achievement, user1.id)
+ friendsOfuser3 = achievement.get_relevant_users_by_achievement_and_user(achievement, user3.id)
+ friendsOfuser4 = achievement.get_relevant_users_by_achievement_and_user(achievement, user4.id)
+
+ self.assertIn(1, friendsOfuser1)
+ self.assertIn(1, friendsOfuser3)
+ self.assertIn(2, friendsOfuser3)
+ self.assertIn(3, friendsOfuser4)
+
+ # For the relevance global
+ achievement1 = create_achievement(achievement_relevance = "global")
+
+ friendsOfuser1 = achievement.get_relevant_users_by_achievement_and_user(achievement1, user3.id)
+
+ self.assertIn(1, friendsOfuser1)
+ self.assertIn(2, friendsOfuser1)
+ self.assertIn(3, friendsOfuser1)
+ self.assertIn(4, friendsOfuser1)
+
+ def test_get_relevant_users_by_achievement_friends_and_user_reverse(self):
+
+ # Create First user
+ user1 = create_user()
+
+ # Create Second user
+ user2 = create_user(
+ lat=85.59,
+ lng=65.75,
+ country="DE",
+ region="Niedersachsen",
+ city="Osnabrück",
+ timezone="Europe/Berlin",
+ language="de",
+ additional_public_data={
+ "first_name": "Michael",
+ "last_name": "Clarke"
+ },
+ friends = [user1.id]
+ )
+
+ # Create Third user
+ user3 = create_user(
+ lat=12.1,
+ lng=12.2,
+ country="RO",
+ region="Transylvania",
+ city="Cluj-Napoca",
+ timezone="Europe/Bukarest",
+ language="en",
+ additional_public_data={
+ "first_name": "Rudolf",
+ "last_name": "Red Nose"
+ },
+ friends=[user1.id, user2.id]
+ )
+
+ # Create Fourth user
+ user4 = create_user(
+ lat=25.56,
+ lng=15.89,
+ country="AU",
+ region="Sydney",
+ city="New South Wales",
+ timezone="Australia",
+ language="en",
+ additional_public_data={
+ "first_name": "Steve",
+ "last_name": "Waugh"
+ },
+ friends=[user2.id, user3.id]
+ )
+
+ achievement = create_achievement()
+ usersForFriend1 = achievement.get_relevant_users_by_achievement_and_user_reverse(achievement, user1.id)
+ usersForFriend2 = achievement.get_relevant_users_by_achievement_and_user_reverse(achievement, user2.id)
+ usersForFriend3 = achievement.get_relevant_users_by_achievement_and_user_reverse(achievement, user3.id)
+ usersForFriend4 = achievement.get_relevant_users_by_achievement_and_user_reverse(achievement, user4.id)
+
+ self.assertIn(user2.id, usersForFriend1)
+ self.assertIn(user3.id, usersForFriend1)
+ self.assertIn(user3.id, usersForFriend2)
+ self.assertIn(user4.id, usersForFriend2)
+ self.assertIn(user4.id, usersForFriend3)
+ self.assertIn(user4.id, usersForFriend4)
+
+ def test_get_level(self):
+
+ user = create_user(timezone="Australia/Sydney", country="Australia", region="xyz", city="Sydney")
+ achievement = create_achievement(achievement_name="invite_users_achievement", achievement_evaluation="weekly")
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(evaluation_timezone=achievement.evaluation_timezone, evaluation_type="weekly")
+
+ create_achievement_user(user, achievement, achievement_date, level=2)
+
+ achievement.get_level(user.id, achievement["id"], achievement_date)
+ level = achievement.get_level_int(user.id, achievement.id, achievement_date)
+
+ achievement_date1 = Achievement.get_datetime_for_evaluation_type(evaluation_timezone=achievement.evaluation_timezone, evaluation_type="weekly", dt=achievement_date + datetime.timedelta(7))
+
+ achievement.get_level(user.id, achievement["id"], achievement_date1)
+ level1 = achievement.get_level_int(user.id, achievement.id, achievement_date1)
+
+ # Test for get_level as integer
+ print("level1:", level1)
+ self.assertEqual(level, 2)
+ self.assertEqual(level1, 0)
+
+ def test_get_rewards(self):
+
+ achievement = create_achievement(achievement_maxlevel=3)
+ create_achievement_rewards(achievement)
+ clear_all_caches()
+ rewardlist1 = Achievement.get_rewards(achievement.id, 1)
+ print("rewardlist1",rewardlist1)
+
+ rewardlist2 = Achievement.get_rewards(achievement.id, 5)
+ print("rewardlist2", rewardlist2)
+
+ rewardlist3 = Achievement.get_rewards(achievement.id, 3)
+ print("rewardlist3", rewardlist3)
+
+ self.assertEqual(rewardlist1, [])
+ self.assertEqual(rewardlist2, [])
+ self.assertNotEqual(rewardlist3, [])
+
+ def test_get_achievement_properties(self):
+
+ achievement = create_achievement(achievement_maxlevel=3)
+
+ achievementproperty = AchievementProperty()
+ achievementproperty.name = "xp"
+ DBSession.add(achievementproperty)
+ DBSession.flush()
+
+ achievements_achievementproperty = AchievementAchievementProperty()
+ achievements_achievementproperty.achievement_id = achievement.id
+ achievements_achievementproperty.property_id = achievementproperty.id
+ achievements_achievementproperty.value = "5"
+ achievements_achievementproperty.from_level = 2
+ DBSession.add(achievements_achievementproperty)
+ DBSession.flush()
+
+ clear_all_caches()
+
+ result1 = Achievement.get_achievement_properties(achievement.id, 4)
+ print(result1)
+
+ result2 = Achievement.get_achievement_properties(achievement.id, 1)
+ print(result2)
+
+ self.assertNotEqual(result1, [])
+ self.assertEqual(result2, [])
+
+ def test_evaluate_achievement_for_participate(self):
+
+ achievement = create_achievement(achievement_name="participate_achievement", achievement_relevance="own", achievement_maxlevel=4)
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement.evaluation)
+
+ current_level = 1
+ achievement_user = AchievementUser()
+ achievement_user.user_id = user.id
+ achievement_user.achievement_id = achievement.id
+ achievement_user.achievement_date = achievement_date
+ achievement_user.level = current_level
+ DBSession.add(achievement_user)
+ DBSession.flush()
+
+ variable = create_variable("participate", variable_group="day")
+ Value.increase_value(variable_name=variable.name, user=user, value=1, key="5")
+
+ create_goals(achievement,
+ goal_condition="""{"term": {"key": ["5","7"], "type": "literal", "key_operator": "IN", "variable": "participate"}}""",
+ goal_group_by_key=True,
+ goal_operator="geq",
+ goal_goal="1*level")
+
+ clear_all_caches()
+
+ level = Achievement.evaluate(user, achievement.id, achievement_date).get("level")
+
+ Value.increase_value(variable_name="participate", user=user, value=1, key="7")
+ level2 = Achievement.evaluate(user, achievement.id, achievement_date).get("level")
+
+ Value.increase_value(variable_name="participate", user=user, value=5, key="5")
+ level1 = Achievement.evaluate(user, achievement.id, achievement_date).get("level")
+
+ self.assertEqual(level, 1)
+ self.assertEqual(level2, 1)
+ self.assertEqual(level1, 4)
+
+ def test_evaluate_achievement_for_invite_users(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement", achievement_relevance="friends", achievement_maxlevel=10)
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement.evaluation)
+
+ create_achievement_user(user=user, achievement=achievement, achievement_date=achievement_date, level=1)
+
+ update_connection().execute(t_values.delete())
+ create_variable("invite_users", variable_group="day")
+ Value.increase_value(variable_name="invite_users", user=user, value=1, key=None)
+
+ create_goals(achievement,
+ goal_goal="1*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ level = Achievement.evaluate(user, achievement.id, achievement_date).get("level")
+ print("level: ", level)
+
+ Value.increase_value(variable_name="invite_users", user=user, value=8, key=None)
+ level1 = Achievement.evaluate(user, achievement.id, achievement_date).get("level")
+ print("level1 ", level1)
+
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ level2 = Achievement.evaluate(user, achievement.id, achievement_date).get("level")
+ print("level2: ", level2)
+
+ self.assertEqual(level, 1)
+ self.assertEqual(level1, 9)
+ self.assertEqual(level2, 10)
+
+ def test_get_reward_and_properties_for_achievement(self):
+
+ user = create_user()
+
+ achievement = create_achievement(achievement_name="invite_users_achievement", achievement_relevance="friends", achievement_maxlevel=3)
+
+ achievementproperty = AchievementProperty()
+ achievementproperty.name = "xp"
+ DBSession.add(achievementproperty)
+ DBSession.flush()
+
+ achievements_achievementproperty = AchievementAchievementProperty()
+ achievements_achievementproperty.achievement_id = achievement.id
+ achievements_achievementproperty.property_id = achievementproperty.id
+ achievements_achievementproperty.value = "5"
+ achievements_achievementproperty.from_level = None
+ DBSession.add(achievements_achievementproperty)
+ DBSession.flush()
+
+ create_achievement_rewards(achievement=achievement)
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement.evaluation)
+
+ create_achievement_user(user=user, achievement=achievement, achievement_date=achievement_date, level=1)
+
+ create_variable("invite_users", "none")
+ Value.increase_value(variable_name="invite_users", user=user, value=4, key="5")
+
+ create_goals(achievement = achievement,
+ goal_condition="""{"term": {"type": "literal", "variable": "invite_users"}}""",
+ goal_group_by_key=True,
+ goal_operator="geq",
+ goal_goal="1*level")
+
+ clear_all_caches()
+ result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("reward_achievement_result:",result)
+
+ self.assertEqual(len(result["new_levels"]["2"]["rewards"]), 0)
+ self.assertEqual(len(result["new_levels"]["3"]["rewards"]), 1)
+ self.assertEqual(len(result["new_levels"]["2"]["properties"]), 1)
+ self.assertEqual(len(result["new_levels"]["3"]["properties"]), 1)
+
+ def test_multiple_goals_of_same_achievement(self):
+
+ user = create_user()
+
+ achievement = create_achievement(achievement_name="participate_achievement", achievement_maxlevel=3)
+
+ create_achievement_rewards(achievement=achievement)
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement.evaluation)
+
+ create_goals(achievement=achievement,
+ goal_condition="""{"term": {"key": ["5","7"], "type": "literal", "key_operator": "IN", "variable": "participate_seminar"}}""",
+ goal_group_by_key=False,
+ goal_operator="geq",
+ goal_goal="2*level",
+ goal_name = "goal_participate_seminar")
+
+ create_goals(achievement=achievement,
+ goal_condition="""{"term": {"type": "literal", "variable": "participate_talk"}}""",
+ goal_group_by_key=False,
+ goal_operator="geq",
+ goal_goal="1*level",
+ goal_name="goal_participate_talk")
+
+ clear_all_caches()
+ create_achievement_user(user=user, achievement=achievement, achievement_date=achievement_date, level=1)
+
+ variable1 = create_variable("participate_seminar", variable_group=None)
+ variable2 = create_variable("participate_talk", variable_group=None)
+ Value.increase_value(variable1.name, user, "2", "5")
+ Value.increase_value(variable1.name, user, "3", "7")
+ Value.increase_value(variable2.name, user, "3", key=None)
+
+ result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("multiple_goals_of_same_achievement:",result)
+ Value.increase_value(variable1.name, user, "2", "7")
+ result1 = Achievement.evaluate(user, achievement.id, achievement_date)
+ print(result1)
+ Value.increase_value(variable2.name, user, "2", key=None)
+ result2 = Achievement.evaluate(user, achievement.id, achievement_date)
+ print(result2)
+
+ self.assertEqual(len(result["levels"]["3"]["rewards"]), 1)
+ self.assertEqual(result["levels"]["1"]["goals"]["1"]["goal_goal"], 2)
+ self.assertEqual(result["levels"]["3"]["goals"]["2"]["goal_goal"], 3)
+ self.assertEqual(result1["levels"]["2"]["goals"]["1"]["goal_goal"], 4)
+ self.assertEqual(result1["levels"]["3"]["goals"]["2"]["goal_goal"], 3)
+ self.assertEqual(result2["levels"]["2"]["goals"]["1"]["goal_goal"], 4)
+ self.assertEqual(result2["levels"]["3"]["goals"]["2"]["goal_goal"], 3)
+
diff --git a/gengine/app/tests/test_achievement_integration_tests.py b/gengine/app/tests/test_achievement_integration_tests.py
new file mode 100644
index 0000000..d03d0f4
--- /dev/null
+++ b/gengine/app/tests/test_achievement_integration_tests.py
@@ -0,0 +1,421 @@
+import datetime
+from gengine.app.cache import clear_all_caches
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, create_achievement, create_variable, create_goals, create_achievement_user
+from gengine.app.model import Achievement, Value
+
+
+class TestAchievementEvaluationType(BaseDBTest):
+
+ # Case1: Achieved in first and next week
+ def test_evaluate_achievement_for_weekly_evaluation_case1(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="weekly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+ next_weekdate = achievement_date + datetime.timedelta(10)
+ print(next_weekdate)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # User has achieved in first week and 2nd week
+ print("Weekly evaluation Case 1")
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print(achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="weekly", dt=next_weekdate)
+
+ Value.increase_value(variable_name="invite_users", user=user, value=16, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print(achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertNotEqual(next_weekdate, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('2', achievement_result["levels_achieved"])
+ self.assertIn('3', achievement_result["levels_achieved"])
+ self.assertIn('1', achievement_result1["new_levels"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case2: NOT Achieved in first week but in next week
+ def test_evaluate_achievement_for_weekly_evaluation_case2(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="weekly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ next_weekdate = achievement_date + datetime.timedelta(11)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # User has not achieved in first week but in 2nd week
+ print("Weekly evaluation Case 2")
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print(achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="weekly", dt=next_weekdate)
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertNotEqual(next_weekdate, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('1', achievement_result1["new_levels"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case3: NOT Achieved in first week but after some days in same week
+ def test_evaluate_achievement_for_weekly_evaluation_case3(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="weekly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # User has not achieved in first week and achieved after few days in a same week
+ print("Weekly evaluation Case 3")
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print(achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="weekly", dt=achievement_date+datetime.timedelta(3))
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertEqual(achievement_date, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case1: Achieved in first and next month
+ def test_evaluate_achievement_for_monthly_evaluation_case1(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="monthly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+ next_month = achievement_date + datetime.timedelta(35)
+ print(next_month)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # User has achieved in this month and next month
+ print("Monthly evaluation Case 1")
+
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("achievement result: ", achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="monthly", dt=next_month)
+
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertNotEqual(next_month, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('2', achievement_result["levels_achieved"])
+ self.assertIn('3', achievement_result["levels_achieved"])
+ self.assertIn('1', achievement_result1["new_levels"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case2: Not achieved in first but in next month
+ def test_evaluate_achievement_for_monthly_evaluation_case2(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="monthly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+ next_month = achievement_date + datetime.timedelta(31)
+ print(next_month)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # User has NOT achieved in this month but in the next month
+ print("Monthly evaluation Case 2")
+
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("achievement result: ", achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="monthly", dt=next_month+datetime.timedelta(days=10))
+
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertGreaterEqual(next_month, next_date) # next_month can be the 1st, 2nd, 3rd of 4th (February)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('1', achievement_result1["new_levels"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case3: Achieved in first month and after some days in a same month
+ def test_evaluate_achievement_for_monthly_evaluation_case3(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="monthly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # Not achieved in first month after some days in the same month
+ print("Monthly evaluation Case 3")
+
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("achievement result: ", achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="monthly", dt=achievement_date+datetime.timedelta(10))
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertEqual(achievement_date, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case1: Achieved in first year and next year
+ def test_evaluate_achievement_for_yearly_evaluation_case1(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="yearly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+ next_year = achievement_date + datetime.timedelta(425)
+ print(next_year)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # Goal achieved in both this month and next year
+ print("Yearly evaluation Case 1")
+
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("achievement result: ", achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="yearly", dt=next_year)
+
+ Value.increase_value(variable_name="invite_users", user=user, value=15, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print(achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertNotEqual(next_year, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('2', achievement_result["levels_achieved"])
+ self.assertIn('3', achievement_result["levels_achieved"])
+ self.assertIn('1', achievement_result1["new_levels"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case2: Not Achieved in first year but in next year
+ def test_evaluate_achievement_for_yearly_evaluation_case2(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="yearly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+ next_year = achievement_date + datetime.timedelta(534)
+ print(next_year)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # Not achieved in first year but in the second year
+ print("Yearly evaluation Case 2")
+
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("achievement result: ", achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="yearly", dt=next_year + datetime.timedelta(10))
+
+ Value.increase_value(variable_name="invite_users", user=user, value=15, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertNotEqual(next_year, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('1', achievement_result1["new_levels"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
+
+ # Case3: Achieved in this year and after some days in same year
+ def test_evaluate_achievement_for_yearly_evaluation_case3(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="yearly")
+
+ user = create_user()
+
+ achievement_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement["evaluation"])
+ print(achievement_date)
+ next_year = achievement_date + datetime.timedelta(501)
+ print(next_year)
+
+ create_achievement_user(user, achievement, achievement_date, level=1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal="3*level",
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+ clear_all_caches()
+
+ # Not achieved in first month after some days in the same year
+ print("Yearly evaluation Case 3")
+
+ Value.increase_value(variable_name="invite_users", user=user, value=5, key=None)
+ achievement_result = Achievement.evaluate(user, achievement.id, achievement_date)
+ print("achievement result: ", achievement_result)
+
+ next_date = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, evaluation_type="yearly", dt=achievement_date + datetime.timedelta(110))
+
+ Value.increase_value(variable_name="invite_users", user=user, value=10, key=None, at_datetime=next_date)
+ achievement_result1 = Achievement.evaluate(user, achievement.id, next_date)
+ print("achievement result1: ", achievement_result1)
+
+ self.assertEqual(achievement_result["achievement_date"], achievement_date)
+ self.assertEqual(achievement_result1["achievement_date"], next_date)
+ self.assertEqual(achievement_date, next_date)
+ self.assertIn('1', achievement_result["levels_achieved"])
+ self.assertIn('2', achievement_result1["new_levels"])
+ self.assertIn('3', achievement_result1["new_levels"])
\ No newline at end of file
diff --git a/gengine/app/tests/test_auth.py b/gengine/app/tests/test_auth.py
new file mode 100644
index 0000000..9d7436d
--- /dev/null
+++ b/gengine/app/tests/test_auth.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, update_user, delete_user, get_or_create_language
+from gengine.metadata import DBSession
+from gengine.app.model import AuthUser
+
+
+class TestUserCreation(BaseDBTest):
+
+ def test_user_creation(self):
+
+ lang = get_or_create_language("en")
+ user = create_user(
+ lat = 12.1,
+ lng = 12.2,
+ country = "RO",
+ region = "Transylvania",
+ city = "Cluj-Napoca",
+ timezone = "Europe/Bukarest",
+ language = "en",
+ additional_public_data = {
+ "first_name" : "Rudolf",
+ "last_name" : "Red Nose"
+ }
+ )
+
+ self.assertTrue(user.lat == 12.1)
+ self.assertTrue(user.lng == 12.2)
+ self.assertTrue(user.country == "RO")
+ self.assertTrue(user.region == "Transylvania")
+ self.assertTrue(user.city == "Cluj-Napoca")
+ self.assertTrue(user.timezone == "Europe/Bukarest")
+ self.assertTrue(user.language_id == lang.id)
+ self.assertTrue(user.additional_public_data["first_name"] == "Rudolf")
+ self.assertTrue(user.additional_public_data["last_name"] == "Red Nose")
+
+ def test_user_updation(self):
+
+ lang = get_or_create_language("en")
+ user = create_user()
+ user = update_user(
+ user_id = user.id,
+ lat = 14.2,
+ lng = 16.3,
+ country = "EN",
+ region = "Transylvania",
+ city = "Cluj-Napoca",
+ timezone = "Europe/Bukarest",
+ language = "en",
+ additional_public_data = {
+ "first_name" : "Rudolf",
+ "last_name" : "Red Nose"
+ }
+ )
+
+ # Correct cases
+ self.assertTrue(user.lat == 14.2)
+ self.assertTrue(user.lng == 16.3)
+ self.assertTrue(user.country == "EN")
+ self.assertTrue(user.region == "Transylvania")
+ self.assertTrue(user.city == "Cluj-Napoca")
+ self.assertTrue(user.timezone == "Europe/Bukarest")
+ self.assertTrue(user.language_id == lang.id)
+
+ def test_user_deletion(self):
+
+ user1 = create_user()
+
+ # Create Second user
+ user2 = create_user(
+ lat=85.59,
+ lng=65.75,
+ country="DE",
+ region="Niedersachsen",
+ city="Osnabrück",
+ timezone="Europe/Berlin",
+ language="de",
+ additional_public_data={
+ "first_name": "Michael",
+ "last_name": "Clarke"
+ },
+ friends=[1]
+ )
+
+ remaining_users = delete_user(
+ user_id = user1.id
+ )
+
+ # Correct cases
+ self.assertNotIn(user1.id, remaining_users)
+ self.assertEqual(user2.id, remaining_users[0].id)
+
+ def test_verify_password(self):
+ auth_user = AuthUser()
+ auth_user.password = "test12345"
+ auth_user.active = True
+ auth_user.email = "test@actidoo.com"
+ DBSession.add(auth_user)
+
+ iscorrect = auth_user.verify_password("test12345")
+
+ self.assertEqual(iscorrect, True)
+
+ def test_create_token(self):
+ user = create_user()
+ auth_user = AuthUser()
+ auth_user.user_id = user.id
+ auth_user.password = "test12345"
+ auth_user.active = True
+ auth_user.email = "test@actidoo.com"
+ DBSession.add(auth_user)
+
+ if auth_user.verify_password("test12345"):
+ token = auth_user.get_or_create_token()
+
+ self.assertNotEqual(token, None)
+
+
+
+
+
diff --git a/gengine/app/tests/test_device.py b/gengine/app/tests/test_device.py
new file mode 100644
index 0000000..81b3100
--- /dev/null
+++ b/gengine/app/tests/test_device.py
@@ -0,0 +1,67 @@
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, create_device, update_device
+
+
+class TestUserDevice(BaseDBTest):
+
+ def test_create_user_device(self):
+
+ user = create_user()
+
+ device = create_device(
+ device_id='3424',
+ user_id=user.id,
+ device_os='Android',
+ push_id='1234',
+ app_version='1.1'
+ )
+
+ self.assertTrue(device.device_id == '3424')
+ self.assertTrue(device.user_id == user.id)
+ self.assertTrue(device.device_os == 'Android')
+ self.assertTrue(device.push_id == '1234')
+ self.assertTrue(device.app_version == '1.1')
+
+ def test_update_user_device(self):
+
+ user = create_user()
+ create_device(user_id=user.id)
+
+ device = update_device(
+ user_id=user.id,
+ device_id='1256',
+ push_id='5126',
+ device_os='iOS',
+ app_version='1.2'
+ )
+
+ # Correct cases
+ self.assertTrue(device.device_id == '1256')
+ self.assertTrue(device.user_id == user.id)
+ self.assertTrue(device.push_id == '5126')
+ self.assertTrue(device.app_version == '1.2')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gengine/app/tests/test_eval_types_and_rewards.py b/gengine/app/tests/test_eval_types_and_rewards.py
new file mode 100644
index 0000000..2d338bc
--- /dev/null
+++ b/gengine/app/tests/test_eval_types_and_rewards.py
@@ -0,0 +1,101 @@
+import datetime
+
+from gengine.app.cache import clear_all_caches
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, create_achievement, create_variable, create_goals, create_achievement_user
+from gengine.app.model import Achievement, Value
+
+
+class TestEvaluationForMultipleUsersAndTimzone(BaseDBTest):
+
+ def test_friends_leaderboard(self):
+
+ user1 = create_user()
+
+ # Create Second user
+ user2 = create_user(
+ lat=85.59,
+ lng=65.75,
+ country="DE",
+ region="Roland",
+ city="New York",
+ timezone="US/Eastern",
+ language="en",
+ additional_public_data={
+ "first_name": "Michael",
+ "last_name": "Clarke"
+ }
+ )
+
+ # Create Third user
+ user3 = create_user(
+ lat=12.1,
+ lng=12.2,
+ country="RO",
+ region="Transylvania",
+ city="Cluj-Napoca",
+ timezone="Europe/Bucharest",
+ language="en",
+ additional_public_data={
+ "first_name": "Rudolf",
+ "last_name": "Red Nose"
+ },
+ friends=[1, 2]
+ )
+
+ # Create Fourth user
+ user4 = create_user(
+ lat=25.56,
+ lng=15.89,
+ country="AU",
+ region="Sydney",
+ city="New South Wales",
+ timezone="Australia/Sydney",
+ language="en",
+ additional_public_data={
+ "first_name": "Steve",
+ "last_name": "Waugh"
+ },
+ friends=[3]
+ )
+
+ achievement = create_achievement(achievement_name="invite_users_achievement",
+ achievement_relevance="friends",
+ achievement_maxlevel=3,
+ achievement_evaluation="weekly")
+
+ print(achievement.evaluation_timezone)
+ achievement_date1 = Achievement.get_datetime_for_evaluation_type(achievement.evaluation_timezone, achievement.evaluation)
+ print("Achievement date for first user:")
+ print(achievement_date1)
+
+ create_variable("invite_users", variable_group="day")
+
+ create_goals(achievement,
+ goal_goal=None,
+ goal_operator="geq",
+ goal_group_by_key=False
+ )
+
+ Value.increase_value(variable_name="invite_users", user=user1, value=12, key=None)
+ Value.increase_value(variable_name="invite_users", user=user2, value=2, key=None)
+ Value.increase_value(variable_name="invite_users", user=user3, value=11, key=None)
+ Value.increase_value(variable_name="invite_users", user=user4, value=6, key=None)
+
+ clear_all_caches()
+
+ print("test for multiple users")
+
+ # Evaluate achievement for friends of user 3
+ achievement1 = Achievement.evaluate(user3, achievement.id, achievement_date1)
+ print(achievement1["goals"][1]["leaderboard"])
+
+ # user 3 has to friends: user 1 and user 2
+ self.assertEqual(user1["id"], achievement1["goals"][1]["leaderboard"][0]["user"]["id"])
+ self.assertEqual(user3["id"], achievement1["goals"][1]["leaderboard"][1]["user"]["id"])
+ self.assertEqual(user2["id"], achievement1["goals"][1]["leaderboard"][2]["user"]["id"])
+
+ self.assertEqual(12.0, achievement1["goals"][1]["leaderboard"][0]["value"])
+ self.assertEqual(11.0, achievement1["goals"][1]["leaderboard"][1]["value"])
+ self.assertEqual(2.0, achievement1["goals"][1]["leaderboard"][2]["value"])
+
diff --git a/gengine/app/tests/test_goal.py b/gengine/app/tests/test_goal.py
new file mode 100644
index 0000000..155213b
--- /dev/null
+++ b/gengine/app/tests/test_goal.py
@@ -0,0 +1,190 @@
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, create_achievement, create_variable, create_goals, create_goal_properties, create_goal_evaluation_cache
+from gengine.app.model import Achievement, User, Goal, Value
+
+
+class TestEvaluateGoal(BaseDBTest):
+ def test_compute_progress(self):
+
+ user = create_user()
+ create_variable(variable_name="invite_users", variable_group="day")
+ Value.increase_value(variable_name="invite_users", user=user, value=6, key=None)
+ Value.increase_value(variable_name="invite_users", user=user, value=7, key=None)
+
+ create_variable(variable_name="participate", variable_group="day")
+ Value.increase_value(variable_name="participate", user=user, value=2, key="5")
+ Value.increase_value(variable_name="participate", user=user, value=3, key="7")
+ Value.increase_value(variable_name="participate", user=user, value=5, key="7")
+
+ achievement = create_achievement(achievement_name="invite_users_achievement")
+ goal = create_goals(achievement)
+
+ # goal is for invite_users, its group_by_key is false, progress is sum of all the values
+ achievement_date = Achievement.get_datetime_for_evaluation_type(User.get_user(user.id)["timezone"], achievement["evaluation"])
+ users_progress_goal = Goal.compute_progress(goal=goal, achievement=achievement, user=user, evaluation_date=achievement_date)
+ goal_evaluation = {e["user_id"]: e["value"] for e in users_progress_goal}
+ print(goal_evaluation)
+
+ self.assertLessEqual(goal_evaluation.get(user.id), 13)
+
+ # For goal1, since its group_by_key is True, it'll add the values of the same key
+ achievement1 = create_achievement(achievement_name="participate_achievement")
+ goal1 = create_goals(achievement1)
+ achievement_date1= Achievement.get_datetime_for_evaluation_type(User.get_user(user.id)["timezone"], achievement1["evaluation"])
+ users_progress_goal1 = Goal.compute_progress(goal=goal1, achievement=achievement1, user=user, evaluation_date=achievement_date1)
+ goal_evaluation1 = {e["user_id"]: e["value"] for e in users_progress_goal1}
+ print(goal_evaluation1)
+
+ self.assertLess(goal_evaluation1.get(user.id), 10)
+
+ # Check with group_by_key for goals participate = False
+ goal2 = create_goals(achievement1, goal_group_by_key=False)
+ users_progress_goal1 = Goal.compute_progress(goal=goal2, achievement=achievement1, user=user, evaluation_date=achievement_date1)
+ goal_evaluation2 = {e["user_id"]: e["value"] for e in users_progress_goal1}
+ print(goal_evaluation2)
+ self.assertLessEqual(goal_evaluation2.get(user.id), 10)
+
+ def test_evaluate_goal(self):
+
+ user = create_user()
+ create_variable(variable_name="invite_users", variable_group="day")
+ Value.increase_value(variable_name="invite_users", user=user, value=6, key=None)
+ Value.increase_value(variable_name="invite_users", user=user, value=7, key=None)
+
+ create_variable(variable_name="participate", variable_group="day")
+ Value.increase_value(variable_name="participate", user=user, value=6, key="5")
+ Value.increase_value(variable_name="participate", user=user, value=3, key="7")
+ Value.increase_value(variable_name="participate", user=user, value=5, key="7")
+
+ # Goal Participate with group_by = False
+ achievement = create_achievement(achievement_name="participate_achievement")
+ goal = create_goals(achievement, goal_group_by_key=False, goal_goal="3*level")
+ achievement_date = Achievement.get_datetime_for_evaluation_type(User.get_user(user.id)["timezone"], achievement["evaluation"])
+
+ evaluation_result = Goal.evaluate(goal, achievement, achievement_date, user, level=4, goal_eval_cache_before=False, execute_triggers=True)
+ print(evaluation_result)
+ # True cases
+ self.assertGreaterEqual(evaluation_result["value"], 12)
+ self.assertEqual(evaluation_result["achieved"], True)
+
+ # Goal Participate with group_by = True
+ goal2 = create_goals(achievement, goal_group_by_key=True, goal_goal="3*level")
+ evaluation_result2 = Goal.evaluate(goal2, achievement, achievement_date, user, level=4, goal_eval_cache_before=False, execute_triggers=True)
+ print(evaluation_result2)
+
+ self.assertLessEqual(evaluation_result2["value"], 12)
+ self.assertEqual(evaluation_result2["achieved"], False)
+
+ # Goal invite_users
+ achievement1 = create_achievement(achievement_name="invite_users_achievement")
+ goal1 = create_goals(achievement1, goal_goal="4*level")
+ achievement_date1 = Achievement.get_datetime_for_evaluation_type(User.get_user(user.id)["timezone"], achievement1["evaluation"])
+
+ evaluation_result1 = Goal.evaluate(goal1, achievement1, achievement_date1, user, level=2, goal_eval_cache_before=False, execute_triggers=True)
+ print(evaluation_result1)
+
+ self.assertGreaterEqual(evaluation_result1["value"], 8)
+ self.assertEqual(evaluation_result1["achieved"], True)
+
+ def test_get_goal_properties(self):
+
+ achievement = create_achievement()
+ goals = create_goals(achievement)
+
+ create_goal_properties(goals.id)
+ level = 4
+ result = Goal.get_goal_properties(goals.id, level)
+ print(result)
+
+ level1 = 1
+ result1 = Goal.get_goal_properties(goals.id, level1)
+ print(result1)
+
+ self.assertIsNot(result, [])
+ self.assertEquals(result1, [])
+
+ def test_get_leaderboard(self):
+
+ achievement = create_achievement(achievement_name="invite_users_achievement")
+ goals = create_goals(achievement)
+
+ # Create multiple users for a goal
+ user1 = create_user()
+ user2 = create_user(
+ lat=85.59,
+ lng=65.75,
+ country="USA",
+ region="Lethal crosside",
+ city="New York",
+ timezone="US/Eastern",
+ language="en",
+ additional_public_data={
+ "first_name": "Michael",
+ "last_name": "Clarke"
+ }
+ )
+
+ # Create Third user
+ user3 = create_user(
+ lat=12.1,
+ lng=12.2,
+ country="RO",
+ region="Transylvania",
+ city="Cluj-Napoca",
+ timezone="Europe/Bucharest",
+ language="en",
+ additional_public_data={
+ "first_name": "Rudolf",
+ "last_name": "Red Nose"
+ },
+ friends=[1, 2]
+ )
+
+ # Create Fourth user
+ user4 = create_user(
+ lat=25.56,
+ lng=15.89,
+ country="AU",
+ region="Sydney",
+ city="New South Wales",
+ timezone="Australia/Sydney",
+ language="en",
+ additional_public_data={
+ "first_name": "Steve",
+ "last_name": "Waugh"
+ },
+ friends=[3]
+ )
+
+ achievement_date_for_user1 = Achievement.get_datetime_for_evaluation_type(User.get_user(user1.id)["timezone"], achievement["evaluation"])
+ achievement_date_for_user2 = Achievement.get_datetime_for_evaluation_type(User.get_user(user2.id)["timezone"], achievement["evaluation"])
+ achievement_date_for_user3 = Achievement.get_datetime_for_evaluation_type(User.get_user(user3.id)["timezone"], achievement["evaluation"])
+ achievement_date_for_user4 = Achievement.get_datetime_for_evaluation_type(User.get_user(user4.id)["timezone"], achievement["evaluation"])
+ print(achievement_date_for_user4)
+
+ create_goal_evaluation_cache(goal_id=goals.id, gec_achievement_date=achievement_date_for_user1, gec_user_id=user1.id, gec_value=22.00, gec_achieved=True)
+ create_goal_evaluation_cache(goal_id=goals.id, gec_achievement_date=achievement_date_for_user2, gec_user_id=user2.id, gec_value=8.00, gec_achieved=True)
+ create_goal_evaluation_cache(goal_id=goals.id, gec_achievement_date=achievement_date_for_user3, gec_user_id=user3.id, gec_value=15.00, gec_achieved=True)
+
+ # Test for finding leaderboard in case where goal has been evaluated for all given users
+
+ # First get list of friends (user_ids) of given user
+ user_ids = Achievement.get_relevant_users_by_achievement_and_user(achievement, user3.id)
+
+ # Get leaderboard
+ positions = Goal.get_leaderboard(goals, achievement_date_for_user3, user_ids)
+ print(positions)
+ self.assertEqual(positions[0]["value"], 22.00)
+ self.assertEqual(positions[1]["value"], 15.00)
+ self.assertEqual(positions[2]["value"], 8.00)
+
+ # Test for Goal is not evaluated for few user_ids
+ create_variable(variable_name="invite_users", variable_group="day")
+ Value.increase_value(variable_name="invite_users", user=user4, value=6, key=None)
+ Value.increase_value(variable_name="invite_users", user=user4, value=9, key=None)
+
+ user_ids = Achievement.get_relevant_users_by_achievement_and_user(achievement, user4.id)
+ positions = Goal.get_leaderboard(goals, achievement_date_for_user4, user_ids)
+
+ print(positions)
+ self.assertEqual(positions[0]["value"], 15.00)
diff --git a/gengine/app/tests/test_value.py b/gengine/app/tests/test_value.py
new file mode 100644
index 0000000..c9932a4
--- /dev/null
+++ b/gengine/app/tests/test_value.py
@@ -0,0 +1,26 @@
+from gengine.app.tests.base import BaseDBTest
+from gengine.app.tests.helpers import create_user, create_variable,create_value
+from gengine.app.model import Value
+
+
+class TestValue(BaseDBTest):
+ def test_increase_value(self):
+ user = create_user()
+ variable = create_variable(variable_name="participate", variable_group="day")
+
+ value1 = Value.increase_value(variable.name, user, value=3, key="5")
+ value2 = Value.increase_value(variable.name, user, value=3, key="5")
+ value3 = Value.increase_value(variable.name, user, value=6, key="7")
+
+ # Correct cases
+ self.assertGreater(value2, value1)
+ self.assertEqual(value3, value2)
+
+ # Doesn't work when give variable_group = none i.e. current_datetime check which differes for two successive calls
+ # Increase value is being called only in evaluate_achievement function and not in evaluate_goal
+
+ def test_increase_value_null_key(self):
+ user = create_user()
+ variable = create_variable(variable_name="login", variable_group="day")
+ value1 = Value.increase_value(variable.name, user, value=1, key=None)
+ self.assertIs(value1, 1)
diff --git a/gengine/app/tests/test_variable.py b/gengine/app/tests/test_variable.py
new file mode 100644
index 0000000..e69de29
diff --git a/gengine/app/views.py b/gengine/app/views.py
new file mode 100644
index 0000000..8338baf
--- /dev/null
+++ b/gengine/app/views.py
@@ -0,0 +1,586 @@
+# -*- coding: utf-8 -*-
+import traceback
+
+import binascii
+from http.cookies import SimpleCookie
+
+import base64
+import copy
+import datetime
+
+import json
+
+import pytz
+from pyramid.request import Request
+from pyramid.response import Response
+from pyramid.settings import asbool
+from sqlalchemy.sql.expression import select, and_
+
+from gengine.app.permissions import perm_own_update_user_infos, perm_global_update_user_infos, perm_global_delete_user, perm_own_delete_user, \
+ perm_global_access_admin_ui, perm_global_register_device, perm_own_register_device, perm_global_read_messages, \
+ perm_own_read_messages
+from gengine.base.model import valid_timezone, exists_by_expr, update_connection
+from gengine.base.errors import APIError
+from pyramid.exceptions import NotFound
+from pyramid.renderers import render
+from pyramid.view import view_config
+from pyramid.wsgi import wsgiapp2
+from werkzeug import DebuggedApplication
+
+from gengine.app.admin import adminapp
+from gengine.app.formular import FormularEvaluationException
+from gengine.app.model import (
+ User,
+ Achievement,
+ Value,
+ Variable,
+ AuthUser, AuthToken, t_users, t_auth_users, t_auth_users_roles, t_auth_roles, t_auth_roles_permissions, UserDevice,
+ t_user_device, t_user_messages, UserMessage)
+from gengine.base.settings import get_settings
+from gengine.metadata import DBSession
+from gengine.wsgiutil import HTTPSProxied
+
+@view_config(route_name='add_or_update_user', renderer='string', request_method="POST")
+def add_or_update_user(request):
+ """add a user and set its metadata"""
+
+ user_id = int(request.matchdict["user_id"])
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ #ensure that the user exists and we have the permission to update it
+ may_update = request.has_perm(perm_global_update_user_infos) or request.has_perm(perm_own_update_user_infos) and request.user.id == user_id
+ if not may_update:
+ raise APIError(403, "forbidden", "You may not edit this user.")
+
+ #if not exists_by_expr(t_users,t_users.c.id==user_id):
+ # raise APIError(403, "forbidden", "The user does not exist. As the user authentication is enabled, you need to create the AuthUser first.")
+
+
+ lat=None
+ if len(request.POST.get("lat",""))>0:
+ lat = float(request.POST["lat"])
+
+ lon=None
+ if len(request.POST.get("lon",""))>0:
+ lon = float(request.POST["lon"])
+
+ friends=[]
+ if len(request.POST.get("friends",""))>0:
+ friends = [int(x) for x in request.POST["friends"].split(",")]
+
+ groups=[]
+ if len(request.POST.get("groups",""))>0:
+ groups = [int(x) for x in request.POST["groups"].split(",")]
+
+ timezone="UTC"
+ if len(request.POST.get("timezone",""))>0:
+ timezone = request.POST["timezone"]
+
+ if not valid_timezone(timezone):
+ timezone = 'UTC'
+
+ country=None
+ if len(request.POST.get("country",""))>0:
+ country = request.POST["country"]
+
+ region=None
+ if len(request.POST.get("region",""))>0:
+ region = request.POST["region"]
+
+ city=None
+ if len(request.POST.get("city",""))>0:
+ city = request.POST["city"]
+
+ language = None
+ if len(request.POST.get("language", "")) > 0:
+ language= request.POST["language"]
+
+ additional_public_data = {}
+ if len(request.POST.get("additional_public_data", "")) > 0:
+ try:
+ additional_public_data = json.loads(request.POST["additional_public_data"])
+ except:
+ additional_public_data = {}
+
+
+ User.set_infos(user_id=user_id,
+ lat=lat,
+ lng=lon,
+ timezone=timezone,
+ country=country,
+ region=region,
+ city=city,
+ language=language,
+ friends=friends,
+ groups=groups,
+ additional_public_data = additional_public_data)
+ return {"status": "OK", "user" : User.full_output(user_id)}
+
+@view_config(route_name='delete_user', renderer='string', request_method="DELETE")
+def delete_user(request):
+ """delete a user completely"""
+ user_id = int(request.matchdict["user_id"])
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ # ensure that the user exists and we have the permission to update it
+ may_delete = request.has_perm(perm_global_delete_user) or request.has_perm(perm_own_delete_user) and request.user.id == user_id
+ if not may_delete:
+ raise APIError(403, "forbidden", "You may not delete this user.")
+
+ User.delete_user(user_id)
+ return {"status": "OK"}
+
+def _get_progress(achievements_for_user, requesting_user):
+
+ achievements = Achievement.get_achievements_by_user_for_today(achievements_for_user)
+
+ def ea(achievement, achievement_date, execute_triggers):
+ try:
+ return Achievement.evaluate(achievements_for_user, achievement["id"], achievement_date, execute_triggers=execute_triggers)
+ except FormularEvaluationException as e:
+ return { "error": "Cannot evaluate formular: " + e.message, "id" : achievement["id"] }
+ except Exception as e:
+ tb = traceback.format_exc()
+ return { "error": tb, "id" : achievement["id"] }
+
+ check = lambda x : x!=None and not "error" in x and (x["hidden"]==False or x["level"]>0)
+
+ def may_view(achievement, requesting_user):
+ if not asbool(get_settings().get("enable_user_authentication", False)):
+ return True
+
+ if achievement["view_permission"] == "everyone":
+ return True
+ if achievement["view_permission"] == "own" and achievements_for_user["id"] == requesting_user["id"]:
+ return True
+ return False
+
+ evaluatelist = []
+ now = datetime.datetime.now(pytz.timezone(achievements_for_user["timezone"]))
+ for achievement in achievements:
+ if may_view(achievement, requesting_user):
+ achievement_dates = set()
+ d = max(achievement["created_at"], achievements_for_user["created_at"]).replace(tzinfo=pytz.utc)
+ dr = Achievement.get_datetime_for_evaluation_type(
+ achievement["evaluation_timezone"],
+ achievement["evaluation"],
+ dt=d
+ )
+
+ achievement_dates.add(dr)
+ if dr != None:
+ while d <= now:
+ if achievement["evaluation"] == "yearly":
+ d += datetime.timedelta(days=364)
+ elif achievement["evaluation"] == "monthly":
+ d += datetime.timedelta(days=28)
+ elif achievement["evaluation"] == "weekly":
+ d += datetime.timedelta(days=6)
+ elif achievement["evaluation"] == "daily":
+ d += datetime.timedelta(hours=23)
+ else:
+ break # should not happen
+
+ dr = Achievement.get_datetime_for_evaluation_type(
+ achievement["evaluation_timezone"],
+ achievement["evaluation"],
+ dt=d
+ )
+
+ if dr <= now:
+ achievement_dates.add(dr)
+
+ i=0
+ for achievement_date in reversed(sorted(achievement_dates)):
+ # We execute the goal triggers only for the newest and previous period, not for any periods longer ago
+ # (To not send messages for very old things....)
+ evaluatelist.append(ea(achievement, achievement_date, execute_triggers=(i == 0 or i == 1 or achievement_date == None)))
+ i += 1
+
+
+ ret = {
+ "achievements" : [
+ x for x in evaluatelist if check(x)
+ ],
+ "achievement_errors" : [
+ x for x in evaluatelist if x!=None and "error" in x
+ ]
+ }
+
+ return ret
+
+
+@view_config(route_name='get_progress', renderer='json', request_method="GET")
+def get_progress(request):
+ """get all relevant data concerning the user's progress"""
+ try:
+ user_id = int(request.matchdict["user_id"])
+ except:
+ raise APIError(400, "illegal_user_id", "no valid user_id given")
+
+ user = User.get_user(user_id)
+ if not user:
+ raise APIError(404, "user_not_found", "user not found")
+
+ output = _get_progress(achievements_for_user=user, requesting_user=request.user)
+ output = copy.deepcopy(output)
+
+ for i in range(len(output["achievements"])):
+ if "new_levels" in output["achievements"][i]:
+ del output["achievements"][i]["new_levels"]
+
+ return output
+
+@view_config(route_name='increase_value', renderer='json', request_method="POST")
+@view_config(route_name='increase_value_with_key', renderer='json', request_method="POST")
+def increase_value(request):
+ """increase a value for the user"""
+
+ user_id = int(request.matchdict["user_id"])
+ try:
+ value = float(request.POST["value"])
+ except:
+ try:
+ doc = request.json_body
+ value = doc["value"]
+ except:
+ raise APIError(400,"invalid_value","Invalid value provided")
+
+ key = request.matchdict["key"] if ("key" in request.matchdict and request.matchdict["key"] is not None) else ""
+ variable_name = request.matchdict["variable_name"]
+
+ user = User.get_user(user_id)
+ if not user:
+ raise APIError(404, "user_not_found", "user not found")
+
+ variable = Variable.get_variable_by_name(variable_name)
+ if not variable:
+ raise APIError(404, "variable_not_found", "variable not found")
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ if not Variable.may_increase(variable, request, user_id):
+ raise APIError(403, "forbidden", "You may not increase the variable for this user.")
+
+ Value.increase_value(variable_name, user, value, key)
+
+ output = _get_progress(achievements_for_user=user, requesting_user=request.user)
+ output = copy.deepcopy(output)
+ to_delete = list()
+ for i in range(len(output["achievements"])):
+ if len(output["achievements"][i]["new_levels"])>0:
+ if "levels" in output["achievements"][i]:
+ del output["achievements"][i]["levels"]
+ if "priority" in output["achievements"][i]:
+ del output["achievements"][i]["priority"]
+ if "goals" in output["achievements"][i]:
+ del output["achievements"][i]["goals"]
+ else:
+ to_delete.append(i)
+
+ for i in sorted(to_delete,reverse=True):
+ del output["achievements"][i]
+
+ return output
+
+@view_config(route_name="increase_multi_values", renderer="json", request_method="POST")
+def increase_multi_values(request):
+ try:
+ doc = request.json_body
+ except:
+ raise APIError(400, "invalid_json", "no valid json body")
+ ret = {}
+ for user_id, values in doc.items():
+ user = User.get_user(user_id)
+ if not user:
+ raise APIError(404, "user_not_found", "user %s not found" % (user_id,))
+
+ for variable_name, values_and_keys in values.items():
+ for value_and_key in values_and_keys:
+ variable = Variable.get_variable_by_name(variable_name)
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ if not Variable.may_increase(variable, request, user_id):
+ raise APIError(403, "forbidden", "You may not increase the variable %s for user %s." % (variable_name, user_id))
+
+ if not variable:
+ raise APIError(404, "variable_not_found", "variable %s not found" % (variable_name,))
+
+ if not 'value' in value_and_key:
+ raise APIError(400, "variable_not_found", "illegal value for %s" % (variable_name,))
+
+ value = value_and_key['value']
+ key = value_and_key.get('key','')
+
+ Value.increase_value(variable_name, user, value, key)
+
+ output = _get_progress(achievements_for_user=user, requesting_user=request.user)
+ output = copy.deepcopy(output)
+ to_delete = list()
+ for i in range(len(output["achievements"])):
+ if len(output["achievements"][i]["new_levels"])>0:
+ if "levels" in output["achievements"][i]:
+ del output["achievements"][i]["levels"]
+ if "priority" in output["achievements"][i]:
+ del output["achievements"][i]["priority"]
+ if "goals" in output["achievements"][i]:
+ del output["achievements"][i]["goals"]
+ else:
+ to_delete.append(i)
+
+ for i in sorted(to_delete, reverse=True):
+ del output["achievements"][i]
+
+ if len(output["achievements"])>0 :
+ ret[user_id]=output
+
+ return ret
+
+@view_config(route_name='get_achievement_level', renderer='json', request_method="GET")
+def get_achievement_level(request):
+ """get all information about an achievement for a specific level"""
+ try:
+ achievement_id = int(request.matchdict.get("achievement_id",None))
+ level = int(request.matchdict.get("level",None))
+ except:
+ raise APIError(400, "invalid_input", "invalid input")
+
+ achievement = Achievement.get_achievement(achievement_id)
+
+ if not achievement:
+ raise APIError(404, "achievement_not_found", "achievement not found")
+
+ level_output = Achievement.basic_output(achievement, [], True, level).get("levels").get(str(level), {"properties": {}, "rewards": {}})
+ if "goals" in level_output:
+ del level_output["goals"]
+ if "level" in level_output:
+ del level_output["level"]
+
+ return level_output
+
+
+@view_config(route_name='auth_login', renderer='json', request_method="POST")
+def auth_login(request):
+ try:
+ doc = request.json_body
+ except:
+ raise APIError(400, "invalid_json", "no valid json body")
+
+ user = request.user
+ email = doc.get("email")
+ password = doc.get("password")
+
+ if user:
+ #already logged in
+ token = user.get_or_create_token().token
+ else:
+ if not email or not password:
+ raise APIError(400, "login.email_and_password_required", "You need to send your email and password.")
+
+ user = DBSession.query(AuthUser).filter_by(email=email).first()
+
+ if not user or not user.verify_password(password):
+ raise APIError(401, "login.email_or_password_invalid", "Either the email address or the password is wrong.")
+
+ if not user.active:
+ raise APIError(400, "user_is_not_activated", "Your user is not activated.")
+
+ token = AuthToken.generate_token()
+ tokenObj = AuthToken(
+ user_id = user.id,
+ token = token
+ )
+
+ DBSession.add(tokenObj)
+
+ return {
+ "token" : token,
+ "user" : User.full_output(user.user_id),
+ }
+
+@view_config(route_name='register_device', renderer='json', request_method="POST")
+def register_device(request):
+ try:
+ doc = request.json_body
+ except:
+ raise APIError(400, "invalid_json", "no valid json body")
+
+ user_id = int(request.matchdict["user_id"])
+
+ device_id = doc.get("device_id")
+ push_id = doc.get("push_id")
+ device_os = doc.get("device_os")
+ app_version = doc.get("app_version")
+
+ if not device_id \
+ or not push_id \
+ or not user_id \
+ or not device_os \
+ or not app_version:
+ raise APIError(400, "register_device.required_fields",
+ "Required fields: device_id, push_id, device_os, app_version")
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ may_register = request.has_perm(perm_global_register_device) or request.has_perm(
+ perm_own_register_device) and str(request.user.id) == str(user_id)
+ if not may_register:
+ raise APIError(403, "forbidden", "You may not register devices for this user.")
+
+ if not exists_by_expr(t_users, t_users.c.id==user_id):
+ raise APIError(404, "register_device.user_not_found",
+ "There is no user with this id.")
+
+ UserDevice.add_or_update_device(user_id = user_id, device_id = device_id, push_id = push_id, device_os = device_os, app_version = app_version)
+
+ return {
+ "status" : "ok"
+ }
+
+@view_config(route_name='get_messages', renderer='json', request_method="GET")
+def get_messages(request):
+ try:
+ user_id = int(request.matchdict["user_id"])
+ except:
+ user_id = None
+
+ try:
+ offset = int(request.GET.get("offset",0))
+ except:
+ offset = 0
+
+ limit = 100
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ may_read_messages = request.has_perm(perm_global_read_messages) or request.has_perm(
+ perm_own_read_messages) and str(request.user.id) == str(user_id)
+ if not may_read_messages:
+ raise APIError(403, "forbidden", "You may not read the messages of this user.")
+
+ if not exists_by_expr(t_users, t_users.c.id == user_id):
+ raise APIError(404, "get_messages.user_not_found",
+ "There is no user with this id.")
+
+ q = t_user_messages.select().where(t_user_messages.c.user_id==user_id).order_by(t_user_messages.c.created_at.desc()).limit(limit).offset(offset)
+ rows = DBSession.execute(q).fetchall()
+
+ return {
+ "messages" : [{
+ "id" : message["id"],
+ "text" : UserMessage.get_text(message),
+ "is_read" : message["is_read"],
+ "created_at" : message["created_at"]
+ } for message in rows]
+ }
+
+
+@view_config(route_name='read_messages', renderer='json', request_method="POST")
+def set_messages_read(request):
+ try:
+ doc = request.json_body
+ except:
+ raise APIError(400, "invalid_json", "no valid json body")
+
+ user_id = int(request.matchdict["user_id"])
+
+ if asbool(get_settings().get("enable_user_authentication", False)):
+ may_read_messages = request.has_perm(perm_global_read_messages) or request.has_perm(
+ perm_own_read_messages) and str(request.user.id) == str(user_id)
+ if not may_read_messages:
+ raise APIError(403, "forbidden", "You may not read the messages of this user.")
+
+ if not exists_by_expr(t_users, t_users.c.id == user_id):
+ raise APIError(404, "set_messages_read.user_not_found", "There is no user with this id.")
+
+ message_id = doc.get("message_id")
+ q = select([t_user_messages.c.id,
+ t_user_messages.c.created_at], from_obj=t_user_messages).where(and_(t_user_messages.c.id==message_id,
+ t_user_messages.c.user_id==user_id))
+ msg = DBSession.execute(q).fetchone()
+ if not msg:
+ raise APIError(404, "set_messages_read.message_not_found", "There is no message with this id.")
+
+ uS = update_connection()
+ uS.execute(t_user_messages.update().values({
+ "is_read" : True
+ }).where(and_(
+ t_user_messages.c.user_id == user_id,
+ t_user_messages.c.created_at <= msg["created_at"]
+ )))
+
+ return {
+ "status" : "ok"
+ }
+
+@view_config(route_name='admin_app')
+@wsgiapp2
+def admin_tenant(environ, start_response):
+
+ def admin_app(environ, start_response):
+ #return HTTPSProxied(DebuggedApplication(adminapp.wsgi_app, True))(environ, start_response)
+ return HTTPSProxied(adminapp.wsgi_app)(environ, start_response)
+
+ def request_auth(environ, start_response):
+ resp = Response()
+ resp.status_code = 401
+ resp.www_authenticate = 'Basic realm="%s"' % ("Gamification Engine Admin",)
+ return resp(environ, start_response)
+
+ if not asbool(get_settings().get("enable_user_authentication", False)):
+ return admin_app(environ, start_response)
+
+ req = Request(environ)
+
+ def _get_basicauth_credentials(request):
+ authorization = request.headers.get("authorization","")
+ try:
+ authmeth, auth = authorization.split(' ', 1)
+ except ValueError: # not enough values to unpack
+ return None
+ if authmeth.lower() == 'basic':
+ try:
+ auth = base64.b64decode(auth.strip()).decode("UTF-8")
+ except binascii.Error: # can't decode
+ return None
+ try:
+ login, password = auth.split(':', 1)
+ except ValueError: # not enough values to unpack
+ return None
+ return {'login': login, 'password': password}
+ return None
+
+ user = None
+ cred = _get_basicauth_credentials(req)
+ token = req.cookies.get("token",None)
+ if token:
+ tokenObj = DBSession.query(AuthToken).filter(AuthToken.token == token).first()
+ user = None
+ if tokenObj and tokenObj.valid_until < datetime.datetime.utcnow():
+ tokenObj.extend()
+ if tokenObj:
+ user = tokenObj.user
+
+ if not user:
+ if cred:
+ user = DBSession.query(AuthUser).filter_by(email=cred["login"]).first()
+ if not user or not user.verify_password(cred["password"]):
+ return request_auth(environ, start_response)
+
+ if user:
+ j = t_auth_users.join(t_auth_users_roles).join(t_auth_roles).join(t_auth_roles_permissions)
+ q = select([t_auth_roles_permissions.c.name], from_obj=j).where(t_auth_users.c.user_id==user.user_id)
+ permissions = [r["name"] for r in DBSession.execute(q).fetchall()]
+ if not perm_global_access_admin_ui in permissions:
+ return request_auth(environ, start_response)
+ else:
+ token_s = user.get_or_create_token().token
+
+ def start_response_with_headers(status, headers, exc_info=None):
+
+ cookie = SimpleCookie()
+ cookie['X-Auth-Token'] = token_s
+ cookie['X-Auth-Token']['path'] = get_settings().get("urlprefix", "").rstrip("/") + "/"
+
+ headers.append(('Set-Cookie', cookie['X-Auth-Token'].OutputString()),)
+
+ return start_response(status, headers, exc_info)
+
+ return admin_app(environ, start_response_with_headers)
\ No newline at end of file
diff --git a/gengine/base/__init__.py b/gengine/base/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gengine/base/cache.py b/gengine/base/cache.py
new file mode 100644
index 0000000..a9e215a
--- /dev/null
+++ b/gengine/base/cache.py
@@ -0,0 +1,55 @@
+import warnings
+from dogpile.cache import make_region
+from pyramid_dogpile_cache import get_region
+
+force_redis = None
+
+def setup_redis_cache(host,port,db):
+ """ This is used to override all caching settings in the ini file. Needed for Testing. """
+ global force_redis
+ force_redis = {
+ 'host': host,
+ 'port': port,
+ 'db': db,
+ 'redis_expiration_time': 60 * 60 * 2, # 2 hours
+ 'distributed_lock': True
+ }
+
+
+def my_key_mangler(prefix):
+ def s(o):
+ if type(o) == dict:
+ return "_".join(["%s=%s" % (str(k), str(v)) for k, v in o.items()])
+ if type(o) == tuple:
+ return "_".join([str(v) for v in o])
+ if type(o) == list:
+ return "_".join([str(v) for v in o])
+ else:
+ return str(o)
+
+ def generate_key(key):
+ ret = ""
+ ret += prefix + s(key).replace(" ", "")
+ return ret
+
+ return generate_key
+
+
+def create_cache(name):
+ ch = None
+
+ if force_redis:
+ ch = make_region().configure(
+ 'dogpile.cache.redis',
+ arguments=force_redis
+ )
+ else:
+ try:
+ ch = get_region(name)
+ except:
+ ch = make_region().configure('dogpile.cache.memory')
+ warnings.warn("Warning: cache objects are in memory, are you creating docs?")
+
+ ch.key_mangler = my_key_mangler(name)
+
+ return ch
diff --git a/gengine/base/context.py b/gengine/base/context.py
new file mode 100644
index 0000000..4ed1047
--- /dev/null
+++ b/gengine/base/context.py
@@ -0,0 +1,13 @@
+import threading
+
+from gengine.base.util import DictObjectProxy
+
+_local = threading.local()
+
+def get_context():
+ if not hasattr(_local, "context"):
+ _local.context = DictObjectProxy()
+ return _local.context
+
+def reset_context():
+ _local.context = DictObjectProxy()
\ No newline at end of file
diff --git a/gengine/errors.py b/gengine/base/errors.py
similarity index 100%
rename from gengine/errors.py
rename to gengine/base/errors.py
diff --git a/gengine/base/model.py b/gengine/base/model.py
new file mode 100644
index 0000000..cfed947
--- /dev/null
+++ b/gengine/base/model.py
@@ -0,0 +1,117 @@
+import pytz
+from pytz.exceptions import UnknownTimeZoneError
+from sqlalchemy.inspection import inspect
+from sqlalchemy.orm.exc import DetachedInstanceError
+from sqlalchemy.sql.expression import select
+from sqlalchemy.sql.functions import func
+from sqlalchemy.util.compat import with_metaclass
+from zope.sqlalchemy.datamanager import mark_changed
+
+import gengine.metadata as meta
+
+class ABaseMeta(type):
+ def __init__(cls, name, bases, nmspc):
+ super(ABaseMeta, cls).__init__(name, bases, nmspc)
+
+ # monkey patch __unicode__
+ # this is required to give show the SQL error to the user in flask admin if constraints are violated
+ if hasattr(cls,"__unicode__"):
+ old_unicode = cls.__unicode__
+ def patched(self):
+ try:
+ return old_unicode(self)
+ except DetachedInstanceError:
+ return "(DetachedInstance)"
+ cls.__unicode__ = patched
+
+ def __getattr__(cls, item):
+ if item == "__table__":
+ return inspect(cls).local_table
+ raise AttributeError(item)
+
+
+class ABase(with_metaclass(ABaseMeta, object)):
+ """abstract base class which introduces a nice constructor for the model classes."""
+
+ def __init__(self, *args, **kw):
+ """ create a model object.
+
+ pass attributes by using named parameters, e.g. name="foo", value=123
+ """
+
+ for k, v in kw.items():
+ setattr(self, k, v)
+
+ def __str__(self):
+ if hasattr(self, "__unicode__"):
+ return self.__unicode__()
+
+ def __getitem__(self, key):
+ return getattr(self,key)
+
+ def __setitem__(self, key, item):
+ return setattr(self,key,item)
+
+
+def calc_distance(latlong1, latlong2):
+ """generates a sqlalchemy expression for distance query in km
+
+ :param latlong1: the location from which we look for rows, as tuple (lat,lng)
+
+ :param latlong2: the columns containing the latitude and longitude, as tuple (lat,lng)
+ """
+
+ # explain: http://geokoder.com/distances
+
+ # return func.sqrt(func.pow(69.1 * (latlong1[0] - latlong2[0]),2)
+ # + func.pow(53.0 * (latlong1[1] - latlong2[1]),2))
+
+ return func.sqrt(func.pow(111.2 * (latlong1[0] - latlong2[0]), 2)
+ + func.pow(111.2 * (latlong1[1] - latlong2[1]) * func.cos(latlong2[0]), 2))
+
+
+def coords(row):
+ return (row["lat"], row["lng"])
+
+
+def combine_updated_at(list_of_dates):
+ return max(list_of_dates)
+
+
+def get_insert_id_by_result(r):
+ return r.last_inserted_ids()[0]
+
+
+def get_insert_ids_by_result(r):
+ return r.last_inserted_ids()
+
+
+def exists_by_expr(t, expr):
+ # TODO: use exists instead of count
+ q = select([func.count("*").label("c")], from_obj=t).where(expr)
+ r = meta.DBSession.execute(q).fetchone()
+ if r.c > 0:
+ return True
+ else:
+ return False
+
+
+def datetime_trunc(field, timezone):
+ return "date_trunc('%(field)s', CAST(to_char(NOW() AT TIME ZONE %(timezone)s, 'YYYY-MM-DD HH24:MI:SS') AS TIMESTAMP)) AT TIME ZONE %(timezone)s" % {
+ "field": field,
+ "timezone": timezone
+ }
+
+
+def valid_timezone(timezone):
+ try:
+ pytz.timezone(timezone)
+ except UnknownTimeZoneError:
+ return False
+ return True
+
+
+def update_connection():
+ session = meta.DBSession() if callable(meta.DBSession) else meta.DBSession
+ mark_changed(session)
+ return session
diff --git a/gengine/base/monkeypatch_flaskadmin.py b/gengine/base/monkeypatch_flaskadmin.py
new file mode 100644
index 0000000..f50a147
--- /dev/null
+++ b/gengine/base/monkeypatch_flaskadmin.py
@@ -0,0 +1,6 @@
+def do_monkeypatch():
+ def get_url(self):
+ return self._view.get_url('%s.%s' % (self._view.endpoint, self._view._default_view))
+
+ import flask_admin.menu
+ flask_admin.menu.MenuView.get_url = get_url
\ No newline at end of file
diff --git a/gengine/base/settings.py b/gengine/base/settings.py
new file mode 100644
index 0000000..0e468a1
--- /dev/null
+++ b/gengine/base/settings.py
@@ -0,0 +1,9 @@
+_settings = None
+
+def set_settings(settings):
+ global _settings
+ _settings = settings
+
+def get_settings():
+ global _settings
+ return _settings
\ No newline at end of file
diff --git a/gengine/base/util.py b/gengine/base/util.py
new file mode 100644
index 0000000..ad092fb
--- /dev/null
+++ b/gengine/base/util.py
@@ -0,0 +1,30 @@
+class DictObjectProxy:
+
+ def __init__(self, obj={}):
+ super().__setattr__("obj",obj)
+
+ def __getattr__(self, name):
+ if not name in super().__getattribute__("obj"):
+ raise AttributeError
+ return super().__getattribute__("obj")[name]
+
+ def __setattr__(self, key, value):
+ super().__getattribute__("obj")[key] = value
+
+
+class Proxy(object):
+ def __init__(self):
+ self.target = None
+
+ def __getattr__(self, name):
+ return getattr(self.target, name)
+
+ def __setattr__(self, name, value):
+ if name == "target":
+ return object.__setattr__(self, name, value)
+ else:
+ setattr(self.target, name, value)
+
+ def __call__(self, *args, **kwargs):
+ return self.target(*args, **kwargs)
+
\ No newline at end of file
diff --git a/gengine/maintenance/__init__.py b/gengine/maintenance/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gengine/scripts/__init__.py b/gengine/maintenance/scripts/__init__.py
similarity index 100%
rename from gengine/scripts/__init__.py
rename to gengine/maintenance/scripts/__init__.py
diff --git a/gengine/scripts/generate_erd.py b/gengine/maintenance/scripts/generate_erd.py
similarity index 100%
rename from gengine/scripts/generate_erd.py
rename to gengine/maintenance/scripts/generate_erd.py
diff --git a/gengine/maintenance/scripts/generate_revision.py b/gengine/maintenance/scripts/generate_revision.py
new file mode 100644
index 0000000..ba5dca1
--- /dev/null
+++ b/gengine/maintenance/scripts/generate_revision.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+import sys
+
+import os
+import pyramid_dogpile_cache
+
+from pyramid.config import Configurator
+from pyramid.paster import (
+ get_appsettings,
+ setup_logging,
+)
+from pyramid.scripts.common import parse_vars
+from sqlalchemy import engine_from_config
+
+def usage(argv):
+ cmd = os.path.basename(argv[0])
+ print('usage: %s [var=value]\n'
+ '(example: "%s production.ini new_table_xy_created")' % (cmd, cmd))
+ sys.exit(1)
+
+
+def main(argv=sys.argv):
+ if len(argv) < 3:
+ usage(argv)
+ config_uri = argv[1]
+ message = argv[2]
+ options = parse_vars(argv[3:])
+ setup_logging(config_uri)
+ settings = get_appsettings(config_uri, options=options)
+
+ durl = os.environ.get("DATABASE_URL") # heroku
+ if durl:
+ settings['sqlalchemy.url'] = durl
+
+ murl = os.environ.get("MEMCACHED_URL")
+ if murl:
+ settings['urlcache_url'] = murl
+
+ revision(settings, message, options)
+
+
+def revision(settings, message, options):
+ engine = engine_from_config(settings, 'sqlalchemy.')
+
+ config = Configurator(settings=settings)
+ pyramid_dogpile_cache.includeme(config)
+
+ from gengine.metadata import (
+ init_session,
+ init_declarative_base,
+ init_db
+ )
+ init_session()
+ init_declarative_base()
+ init_db(engine)
+
+ from gengine.app.cache import init_caches
+ init_caches()
+
+ from gengine.metadata import (
+ Base,
+ )
+
+ if options.get("reset_db", False):
+ Base.metadata.drop_all(engine)
+ engine.execute("DROP SCHEMA IF EXISTS public CASCADE")
+
+ engine.execute("CREATE SCHEMA IF NOT EXISTS public")
+
+ from alembic.config import Config
+ from alembic import command
+
+ alembic_cfg = Config(attributes={
+ 'engine': engine,
+ 'schema': 'public'
+ })
+ script_location = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
+ 'app/alembic'
+ )
+ alembic_cfg.set_main_option("script_location", script_location)
+
+ command.revision(alembic_cfg,message,True)
+
+ engine.dispose()
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/gengine/maintenance/scripts/initializedb.py b/gengine/maintenance/scripts/initializedb.py
new file mode 100644
index 0000000..b79f944
--- /dev/null
+++ b/gengine/maintenance/scripts/initializedb.py
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+import sys
+
+import os
+import pyramid_dogpile_cache
+import transaction
+from pyramid.config import Configurator
+from pyramid.paster import (
+ get_appsettings,
+ setup_logging,
+ )
+from pyramid.scripts.common import parse_vars
+from sqlalchemy import engine_from_config
+from sqlalchemy.sql.schema import Table
+
+from gengine.app.cache import init_caches
+from gengine.app.permissions import perm_global_delete_user, perm_global_increase_value, perm_global_update_user_infos, \
+ perm_global_access_admin_ui, perm_global_read_messages, perm_global_register_device
+from gengine.base.model import exists_by_expr
+
+
+def usage(argv):
+ cmd = os.path.basename(argv[0])
+ print('usage: %s [var=value]\n'
+ '(example: "%s production.ini")' % (cmd, cmd))
+ sys.exit(1)
+
+
+def main(argv=sys.argv):
+ if len(argv) < 2:
+ usage(argv)
+ config_uri = argv[1]
+ options = parse_vars(argv[2:])
+ setup_logging(config_uri)
+ settings = get_appsettings(config_uri, options=options)
+
+ durl = os.environ.get("DATABASE_URL") #heroku
+ if durl:
+ settings['sqlalchemy.url']=durl
+
+ murl = os.environ.get("MEMCACHED_URL")
+ if murl:
+ settings['urlcache_url']=murl
+
+ initialize(settings,options)
+
+def initialize(settings,options):
+ engine = engine_from_config(settings, 'sqlalchemy.')
+
+ config = Configurator(settings=settings)
+ pyramid_dogpile_cache.includeme(config)
+
+ from gengine.metadata import (
+ init_session,
+ init_declarative_base,
+ init_db
+ )
+ init_caches()
+ init_session()
+ init_declarative_base()
+ init_db(engine)
+
+ from gengine.metadata import (
+ Base,
+ DBSession
+ )
+
+ if options.get("reset_db",False):
+ Base.metadata.drop_all(engine)
+ engine.execute("DROP SCHEMA IF EXISTS public CASCADE")
+
+ engine.execute("CREATE SCHEMA IF NOT EXISTS public")
+
+ from alembic.config import Config
+ from alembic import command
+
+ alembic_cfg = Config(attributes={
+ 'engine' : engine,
+ 'schema' : 'public'
+ })
+ script_location = os.path.join(
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
+ 'app/alembic'
+ )
+ alembic_cfg.set_main_option("script_location", script_location)
+
+ do_upgrade = options.get("upgrade",False)
+ if not do_upgrade:
+ #init
+ from gengine.app import model
+
+ tables = [t for name, t in model.__dict__.items() if isinstance(t, Table)]
+ Base.metadata.create_all(engine, tables=tables)
+
+ command.stamp(alembic_cfg, "head")
+
+ if options.get("populate_demo", False):
+ populate_demo(DBSession)
+ else:
+ #upgrade
+ command.upgrade(alembic_cfg,'head')
+
+ admin_user = options.get("admin_user", False)
+ admin_password = options.get("admin_password", False)
+
+ if admin_user and admin_password:
+ create_user(DBSession = DBSession, user=admin_user,password=admin_password)
+
+ engine.dispose()
+
+def create_user(DBSession, user, password):
+ from gengine.app.model import (
+ AuthUser,
+ User,
+ AuthRole,
+ AuthRolePermission
+ )
+ with transaction.manager:
+ existing = DBSession.query(AuthUser).filter_by(email=user).first()
+ if not existing:
+ try:
+ user1 = User(id=1, lat=10, lng=50, timezone="Europe/Berlin")
+ DBSession.add(user1)
+ DBSession.flush()
+
+ auth_user = AuthUser(user_id=user1.id, email=user, password=password, active=True)
+ DBSession.add(auth_user)
+
+ auth_role = AuthRole(name="Global Admin")
+ DBSession.add(auth_role)
+
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_access_admin_ui))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_delete_user))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_increase_value))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_update_user_infos))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_read_messages))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_register_device))
+
+ auth_user.roles.append(auth_role)
+ DBSession.add(auth_user)
+ except:
+ pass
+
+def populate_demo(DBSession):
+
+ from gengine.app.model import (
+ Achievement,
+ AchievementCategory,
+ Goal,
+ Variable,
+ User,
+ Language,
+ TranslationVariable,
+ Translation,
+ GoalProperty,
+ GoalGoalProperty,
+ Reward,
+ AchievementProperty,
+ AchievementAchievementProperty,
+ AchievementReward,
+ AuthUser,
+ AuthRole,
+ AuthRolePermission
+ )
+
+ def add_translation_variable(name):
+ t = TranslationVariable(name=name)
+ DBSession.add(t)
+ return t
+
+ def add_translation(variable, lang, text):
+ tr = Translation(translationvariable=variable, text=text, language=lang)
+ DBSession.add(tr)
+ return tr
+
+ with transaction.manager:
+ lang_de = Language(name="de")
+ lang_en = Language(name="en")
+ DBSession.add(lang_de)
+ DBSession.add(lang_en)
+
+ var_invited_users = Variable(name="invite_users")
+ DBSession.add(var_invited_users)
+
+ var_invited_users = Variable(name="participate",
+ group="none")
+ DBSession.add(var_invited_users)
+
+ goal_property_name = GoalProperty(name='name')
+ DBSession.add(goal_property_name)
+
+ achievementcategory_community = AchievementCategory(name="community")
+ DBSession.add(achievementcategory_community)
+
+ achievement_invite = Achievement(name='invite_users',
+ evaluation="immediately",
+ maxtimes=20,
+ achievementcategory=achievementcategory_community)
+ DBSession.add(achievement_invite)
+
+ transvar_invite = add_translation_variable(name="invite_users_goal_name")
+ add_translation(transvar_invite, lang_en, 'Invite ${5*level} Users')
+ add_translation(transvar_invite, lang_de, 'Lade ${5*level} Freunde ein')
+
+ achievement_invite_goal1 = Goal(name_translation=transvar_invite,
+ condition='{"term": {"type": "literal", "variable": "invite_users"}}',
+ goal="5*level",
+ operator="geq",
+ achievement=achievement_invite)
+ DBSession.add(achievement_invite_goal1)
+
+ DBSession.add(GoalGoalProperty(goal=achievement_invite_goal1, property=goal_property_name, value_translation=transvar_invite))
+
+ achievementcategory_sports = AchievementCategory(name="sports")
+ DBSession.add(achievementcategory_sports)
+
+ achievement_fittest = Achievement(name='fittest',
+ relevance="friends",
+ maxlevel=100,
+ achievementcategory=achievementcategory_sports)
+ DBSession.add(achievement_fittest)
+
+ transvar_fittest = add_translation_variable(name="fittest_goal_name")
+ add_translation(transvar_fittest, lang_en, 'Do the most sport activities among your friends')
+ add_translation(transvar_fittest, lang_de, 'Mache unter deinen Freunden am meisten Sportaktivitäten')
+
+ achievement_fittest_goal1 = Goal(name_translation=transvar_fittest,
+ condition='{"term": {"key": ["5","7","9"], "type": "literal", "key_operator": "IN", "variable": "participate"}}',
+ evaluation="weekly",
+ goal="5*level",
+ achievement=achievement_fittest
+ )
+
+ DBSession.add(achievement_fittest_goal1)
+ DBSession.add(GoalGoalProperty(goal=achievement_fittest_goal1, property=goal_property_name, value_translation=transvar_fittest))
+
+ property_name = AchievementProperty(name='name')
+ DBSession.add(property_name)
+
+ property_xp = AchievementProperty(name='xp')
+ DBSession.add(property_xp)
+
+ property_icon = AchievementProperty(name='icon')
+ DBSession.add(property_icon)
+
+ reward_badge = Reward(name='badge')
+ DBSession.add(reward_badge)
+
+ reward_image = Reward(name='backgroud_image')
+ DBSession.add(reward_image)
+
+ transvar_invite_name = add_translation_variable(name="invite_achievement_name")
+ add_translation(transvar_invite_name, lang_en, 'The Community!')
+ add_translation(transvar_invite_name, lang_de, 'Die Community!')
+
+ DBSession.add(AchievementAchievementProperty(achievement=achievement_invite, property=property_name, value_translation=transvar_invite_name))
+ DBSession.add(AchievementAchievementProperty(achievement=achievement_invite, property=property_xp, value='${100 * level}'))
+ DBSession.add(AchievementAchievementProperty(achievement=achievement_invite, property=property_icon, value="https://www.gamification-software.com/img/running.png"))
+
+ DBSession.add(AchievementReward(achievement=achievement_invite, reward=reward_badge, value="https://www.gamification-software.com/img/trophy.png", from_level=5))
+ DBSession.add(AchievementReward(achievement=achievement_invite, reward=reward_image, value="https://www.gamification-software.com/img/video-controller-336657_1920.jpg", from_level=5))
+
+ transvar_fittest_name = add_translation_variable(name="fittest_achievement_name")
+ add_translation(transvar_fittest_name, lang_en, 'The Fittest!')
+ add_translation(transvar_fittest_name, lang_de, 'Der Fitteste!')
+
+ DBSession.add(AchievementAchievementProperty(achievement=achievement_fittest, property=property_name, value_translation=transvar_fittest_name))
+ DBSession.add(AchievementAchievementProperty(achievement=achievement_fittest, property=property_xp, value='${50 + (200 * level)}'))
+ DBSession.add(AchievementAchievementProperty(achievement=achievement_fittest, property=property_icon, value="https://www.gamification-software.com/img/colorwheel.png"))
+
+ DBSession.add(AchievementReward(achievement=achievement_fittest, reward=reward_badge, value="https://www.gamification-software.com/img/easel.png", from_level=1))
+ DBSession.add(AchievementReward(achievement=achievement_fittest, reward=reward_image, value="https://www.gamification-software.com/img/game-characters-622654.jpg", from_level=1))
+
+
+ user1 = User(id=1,lat=10,lng=50,timezone="Europe/Berlin")
+ user2 = User(id=2,lat=10,lng=50,timezone="US/Eastern")
+ user3 = User(id=3,lat=10,lng=50)
+
+ user1.friends.append(user2)
+ user1.friends.append(user3)
+
+ user2.friends.append(user1)
+ user2.friends.append(user3)
+
+ user3.friends.append(user1)
+ user3.friends.append(user2)
+
+ DBSession.add(user1)
+ DBSession.add(user2)
+ DBSession.add(user3)
+ DBSession.flush()
+
+ try:
+ auth_user = AuthUser(user_id=user1.id,email="admin@gamification-software.com",password="test123",active=True)
+ DBSession.add(auth_user)
+
+ auth_role = AuthRole(name="Global Admin")
+ DBSession.add(auth_role)
+
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_access_admin_ui))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_delete_user))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_increase_value))
+ DBSession.add(AuthRolePermission(role=auth_role, name=perm_global_update_user_infos))
+
+ auth_user.roles.append(auth_role)
+ DBSession.add(auth_user)
+ except ImportError as e:
+ print("[auth] feature not installed - not importing auth demo data")
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/gengine/maintenance/scripts/push_messages.py b/gengine/maintenance/scripts/push_messages.py
new file mode 100644
index 0000000..620e858
--- /dev/null
+++ b/gengine/maintenance/scripts/push_messages.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+import sys
+import logging
+
+from zope.sqlalchemy.datamanager import mark_changed
+
+from gengine.metadata import MySession
+
+log = logging.getLogger(__name__)
+log.addHandler(logging.StreamHandler())
+
+import os
+import pyramid_dogpile_cache
+import transaction
+from gengine.app.cache import init_caches
+from pyramid.config import Configurator
+from pyramid.paster import (
+ get_appsettings,
+ setup_logging,
+)
+from pyramid.scripts.common import parse_vars
+from sqlalchemy import engine_from_config
+
+def usage(argv):
+ cmd = os.path.basename(argv[0])
+ print('usage: %s [var=value]\n'
+ '(example: "%s production.ini")' % (cmd, cmd))
+ sys.exit(1)
+
+
+def main(argv=sys.argv):
+ if len(argv) < 2:
+ usage(argv)
+ config_uri = argv[1]
+ options = parse_vars(argv[2:])
+ setup_logging(config_uri)
+ settings = get_appsettings(config_uri, options=options)
+
+ from gengine.base.settings import set_settings
+ set_settings(settings)
+
+ durl = os.environ.get("DATABASE_URL") # heroku
+ if durl:
+ settings['sqlalchemy.url'] = durl
+
+ murl = os.environ.get("MEMCACHED_URL")
+ if murl:
+ settings['urlcache_url'] = murl
+
+ engine = engine_from_config(settings, 'sqlalchemy.')
+
+ config = Configurator(settings=settings)
+ pyramid_dogpile_cache.includeme(config)
+
+ from gengine.metadata import (
+ init_session,
+ init_declarative_base,
+ init_db
+ )
+ init_session()
+ init_declarative_base()
+ init_db(engine)
+ init_caches()
+
+ from gengine.metadata import (
+ DBSession
+ )
+ sess = DBSession()
+ init_session(override_session=sess, replace=True)
+
+ import gengine.app.model as m
+ with transaction.manager:
+ mark_changed(sess, transaction.manager, True)
+
+ messages = sess.execute(m.t_user_messages.select().where(m.t_user_messages.c.has_been_pushed == False))
+ for msg in messages:
+ m.UserMessage.deliver(msg)
+ sess.flush()
+ sess.commit()
diff --git a/gengine/scripts/quickstart.py b/gengine/maintenance/scripts/quickstart.py
similarity index 100%
rename from gengine/scripts/quickstart.py
rename to gengine/maintenance/scripts/quickstart.py
diff --git a/gengine/metadata.py b/gengine/metadata.py
index 249de08..d0f0c87 100644
--- a/gengine/metadata.py
+++ b/gengine/metadata.py
@@ -1,37 +1,58 @@
from sqlalchemy.orm.session import Session, sessionmaker
import transaction
from sqlalchemy.orm.scoping import scoped_session
+from sqlalchemy.sql.schema import MetaData
from zope.sqlalchemy.datamanager import ZopeTransactionExtension
from sqlalchemy.ext.declarative.api import declarative_base
+from gengine.base.util import Proxy
+
+
class MySession(Session):
"""This allow us to use the flask-admin sqla extension, which uses DBSession.commit() rather than transaction.commit()"""
def commit(self,*args,**kw):
transaction.commit(*args,**kw)
-
+
def rollback(self,*args,**kw):
transaction.abort(*args,**kw)
-DBSession=None
+DBSession=Proxy()
+
+def get_sessionmaker(bind=None):
+ return sessionmaker(
+ extension=ZopeTransactionExtension(),
+ class_=MySession,
+ bind=bind
+ )
-def init_session(override_session=None):
+def init_session(override_session=None, replace=False):
global DBSession
+ if DBSession.target and not replace:
+ return
if override_session:
- DBSession = override_session
+ DBSession.target = override_session
else:
- DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension(), class_=MySession))
+ DBSession.target = scoped_session(get_sessionmaker())
Base=None
def init_declarative_base(override_base=None):
global Base
+ if Base:
+ return
if override_base:
- Base=override_base
+ Base = override_base
else:
- Base = declarative_base()
+ convention = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+ }
+ metadata = MetaData(naming_convention=convention)
+ Base = declarative_base(metadata = metadata)
def init_db(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
-
-
\ No newline at end of file
diff --git a/gengine/scripts/initializedb.py b/gengine/scripts/initializedb.py
deleted file mode 100644
index 5a44ef2..0000000
--- a/gengine/scripts/initializedb.py
+++ /dev/null
@@ -1,218 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import sys
-import transaction
-
-from sqlalchemy import engine_from_config
-
-from pyramid.paster import (
- get_appsettings,
- setup_logging,
- )
-
-from pyramid.scripts.common import parse_vars
-import pyramid_dogpile_cache
-from pyramid.config import Configurator
-
-def usage(argv):
- cmd = os.path.basename(argv[0])
- print('usage: %s [var=value]\n'
- '(example: "%s production.ini alembic.ini")' % (cmd, cmd))
- sys.exit(1)
-
-
-def main(argv=sys.argv):
- if len(argv) < 3:
- usage(argv)
- config_uri = argv[1]
- alembic_uri = argv[2]
- options = parse_vars(argv[3:])
- setup_logging(config_uri)
- settings = get_appsettings(config_uri, options=options)
-
- durl = os.environ.get("DATABASE_URL") #heroku
- if durl:
- settings['sqlalchemy.url']=durl
-
- murl = os.environ.get("MEMCACHED_URL")
- if murl:
- settings['urlcache_url']=murl
-
- engine = engine_from_config(settings, 'sqlalchemy.')
-
- config = Configurator(settings=settings)
- pyramid_dogpile_cache.includeme(config)
-
- from ..metadata import (
- init_session,
- init_declarative_base
- )
- init_session()
- init_declarative_base()
-
- from ..metadata import (
- Base,
- DBSession
- )
-
- from ..models import (
- Achievement,
- AchievementCategory,
- Goal,
- Variable,
- User,
- Language,
- TranslationVariable,
- Translation,
- GoalProperty,
- GoalGoalProperty,
- Reward,
- AchievementProperty,
- AchievementAchievementProperty,
- AchievementReward
- )
-
- DBSession.configure(bind=engine)
-
- if options.get("reset_db",False):
- Base.metadata.drop_all(engine)
-
- Base.metadata.create_all(engine)
-
-
- # then, load the Alembic configuration and generate the
- # version table, "stamping" it with the most recent rev:
- from alembic.config import Config
- from alembic import command
- alembic_cfg = Config(alembic_uri)
- command.stamp(alembic_cfg, "head")
-
-
-
- def add_translation_variable(name):
- t = TranslationVariable(name=name)
- DBSession.add(t)
- return t
-
- def add_translation(variable,lang,text):
- tr = Translation(translationvariable=variable,text=text,language=lang)
- DBSession.add(tr)
- return tr
-
- if options.get("populate_demo",False):
- with transaction.manager:
-
- lang_de = Language(name="de")
- lang_en = Language(name="en")
- DBSession.add(lang_de)
- DBSession.add(lang_en)
-
- var_invited_users = Variable(name="invite_users")
- DBSession.add(var_invited_users)
-
- var_invited_users = Variable(name="participate",
- group="none")
- DBSession.add(var_invited_users)
-
- goal_property_name = GoalProperty(name='name')
- DBSession.add(goal_property_name)
-
- achievementcategory_community = AchievementCategory(name="community")
- DBSession.add(achievementcategory_community)
-
- achievement_invite = Achievement(name='invite_users',
- evaluation="immediately",
- maxtimes=20,
- achievementcategory=achievementcategory_community)
- DBSession.add(achievement_invite)
-
- transvar_invite = add_translation_variable(name="invite_users_goal_name")
- add_translation(transvar_invite, lang_en, '"Invite "+`(5*p.level)`+" Users"')
- add_translation(transvar_invite, lang_de, '"Lade "+`(5*p.level)`+" Freunde ein"')
-
- achievement_invite_goal1 = Goal(name_translation=transvar_invite,
- condition='p.var=="invite_users"',
- goal="5*p.level",
- operator="geq",
- achievement=achievement_invite)
- DBSession.add(achievement_invite_goal1)
-
- DBSession.add(GoalGoalProperty(goal=achievement_invite_goal1, property=goal_property_name, value_translation=transvar_invite))
-
- achievementcategory_sports = AchievementCategory(name="sports")
- DBSession.add(achievementcategory_sports)
-
- achievement_fittest = Achievement(name='fittest',
- relevance="friends",
- maxlevel=100,
- achievementcategory=achievementcategory_sports)
- DBSession.add(achievement_fittest)
-
- transvar_fittest = add_translation_variable(name="fittest_goal_name")
- add_translation(transvar_fittest, lang_en, '"Do the most sport activities among your friends"')
- add_translation(transvar_fittest, lang_de, '"Mache unter deinen Freunden am meisten Sportaktivitäten"')
-
- achievement_fittest_goal1 = Goal(name_translation=transvar_fittest,
- condition='and_(p.var=="participate", p.key.in_(["5","7","9"]))',
- evaluation="weekly",
- goal="5*p.level",
- achievement=achievement_fittest
- )
-
- DBSession.add(achievement_fittest_goal1)
- DBSession.add(GoalGoalProperty(goal=achievement_fittest_goal1, property=goal_property_name, value_translation=transvar_fittest))
-
- property_name = AchievementProperty(name='name')
- DBSession.add(property_name)
-
- property_xp = AchievementProperty(name='xp')
- DBSession.add(property_xp)
-
- property_icon = AchievementProperty(name='icon')
- DBSession.add(property_icon)
-
- reward_badge = Reward(name='badge')
- DBSession.add(reward_badge)
-
- reward_image = Reward(name='backgroud_image')
- DBSession.add(reward_image)
-
- transvar_invite_name = add_translation_variable(name="invite_achievement_name")
- add_translation(transvar_invite_name, lang_en, '"The Community!"')
- add_translation(transvar_invite_name, lang_de, '"Die Community!"')
-
- DBSession.add(AchievementAchievementProperty(achievement=achievement_invite, property=property_name, value_translation=transvar_invite_name))
- DBSession.add(AchievementAchievementProperty(achievement=achievement_invite, property=property_xp, value='100 * p.level'))
- DBSession.add(AchievementAchievementProperty(achievement=achievement_invite, property=property_icon, value="'https://www.gamification-software.com/img/running.png'"))
-
- DBSession.add(AchievementReward(achievement=achievement_invite, reward=reward_badge, value="'https://www.gamification-software.com/img/trophy.png'", from_level=5))
- DBSession.add(AchievementReward(achievement=achievement_invite, reward=reward_image, value="'https://www.gamification-software.com/img/video-controller-336657_1920.jpg'", from_level=5))
-
- transvar_fittest_name = add_translation_variable(name="fittest_achievement_name")
- add_translation(transvar_fittest_name, lang_en, '"The Fittest!"')
- add_translation(transvar_fittest_name, lang_de, '"Der Fitteste!"')
-
- DBSession.add(AchievementAchievementProperty(achievement=achievement_fittest, property=property_name, value_translation=transvar_fittest_name))
- DBSession.add(AchievementAchievementProperty(achievement=achievement_fittest, property=property_xp, value='50 + (200 * p.level)'))
- DBSession.add(AchievementAchievementProperty(achievement=achievement_fittest, property=property_icon, value="'https://www.gamification-software.com/img/colorwheel.png'"))
-
- DBSession.add(AchievementReward(achievement=achievement_fittest, reward=reward_badge, value="'https://www.gamification-software.com/img/easel.png'", from_level=1))
- DBSession.add(AchievementReward(achievement=achievement_fittest, reward=reward_image, value="'https://www.gamification-software.com/img/game-characters-622654.jpg'", from_level=1))
-
-
- user1 = User(id=1,lat=10,lng=50,timezone="Europe/Berlin")
- user2 = User(id=2,lat=10,lng=50,timezone="US/Eastern")
- user3 = User(id=3,lat=10,lng=50)
-
- user1.friends.append(user2)
- user1.friends.append(user3)
-
- user2.friends.append(user1)
- user2.friends.append(user3)
-
- user3.friends.append(user1)
- user3.friends.append(user2)
-
- DBSession.add(user1)
- DBSession.add(user2)
- DBSession.add(user3)
\ No newline at end of file
diff --git a/gengine/urlcache.py b/gengine/urlcache.py
deleted file mode 100644
index a36371f..0000000
--- a/gengine/urlcache.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-from pymemcache.client import Client
-
-host = "localhost"
-port = 11211
-urlprefix = ""
-is_active = True
-urlcacheid = "gengine"
-
-def setup_urlcache(prefix, url, active, id):
- global urlprefix, host, port, is_active, urlcacheid
- urlprefix = prefix
- host, port = url.split(":")
- port = int(port)
- is_active = active
- urlcacheid = id
-
-def __build_key(key):
- return "::URL_CACHE::"+str(urlcacheid)+"::"+urlprefix+str(key)
-
-def get_or_set(key,generator):
- if is_active:
- client = Client((host,port))
- key = __build_key(key)
- result = client.get(key)
- if not result:
- result = generator()
- client.set(key, result)
- client.quit()
- return result
- else:
- return generator()
-
-def set_value(key,value):
- if is_active:
- client = Client((host,port))
- key = __build_key(key)
- client.set(key, value)
- client.quit()
-
-def invalidate(key):
- if is_active:
- key = __build_key(key)
- client = Client((host,port))
- client.delete(key)
- client.quit()
-
-def invalidate_all():
- if is_active:
- client = Client((host,port))
- client.flush_all()
- client.quit()
\ No newline at end of file
diff --git a/gengine/views.py b/gengine/views.py
deleted file mode 100644
index cdff0eb..0000000
--- a/gengine/views.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# -*- coding: utf-8 -*-
-from pyramid.view import view_config
-from .models import (
- User,
- Achievement,
- Value
- )
-
-from .urlcache import get_or_set
-from pyramid.renderers import render, JSON
-
-from .flaskadmin import flaskadminapp
-from pyramid.wsgi import wsgiapp2, wsgiapp
-from _collections import defaultdict
-from gengine.models import Variable, valid_timezone, Goal, AchievementReward, FormularEvaluationException
-from gengine.urlcache import set_value
-from pyramid.exceptions import NotFound, HTTPBadRequest
-from werkzeug import DebuggedApplication
-from gengine.wsgiutil import HTTPSProxied
-from .errors import APIError
-
-import traceback
-
-@view_config(route_name='add_or_update_user', renderer='string', request_method="POST")
-def add_or_update_user(request):
- """add a user and set its metadata"""
-
- user_id = int(request.matchdict["user_id"])
-
- lat=None
- if len(request.POST.get("lat",""))>0:
- lat = float(request.POST["lat"])
-
- lon=None
- if len(request.POST.get("lon",""))>0:
- lon = float(request.POST["lon"])
-
- friends=[]
- if len(request.POST.get("friends",""))>0:
- friends = [int(x) for x in request.POST["friends"].split(",")]
-
- groups=[]
- if len(request.POST.get("groups",""))>0:
- groups = [int(x) for x in request.POST["groups"].split(",")]
-
- timezone="UTC"
- if len(request.POST.get("timezone",""))>0:
- timezone = request.POST["timezone"]
-
- if not valid_timezone(timezone):
- timezone = 'UTC'
-
- country=None
- if len(request.POST.get("country",""))>0:
- country = request.POST["country"]
-
- region=None
- if len(request.POST.get("region",""))>0:
- region = request.POST["region"]
-
- city=None
- if len(request.POST.get("city",""))>0:
- city = request.POST["city"]
-
- User.set_infos(user_id=user_id,
- lat=lat,
- lng=lon,
- timezone=timezone,
- country=country,
- region=region,
- city=city,
- friends=friends,
- groups=groups)
-
- return {"status" : "OK"}
-
-@view_config(route_name='delete_user', renderer='string', request_method="DELETE")
-def delete_user(request):
- """delete a user completely"""
-
- user_id = int(request.matchdict["user_id"])
- User.delete_user(user_id)
- return {"status" : "OK"}
-
-def _get_progress(user,force_generation=False):
- def generate():
- achievements = Achievement.get_achievements_by_user_for_today(user)
-
- def ea(achievement):
- try:
- #print "evaluating "+`achievement["id"]`
- return Achievement.evaluate(user, achievement["id"])
- except FormularEvaluationException as e:
- return { "error": "Cannot evaluate formular: " + e.message, "id" : achievement["id"] }
- except Exception as e:
- tb = traceback.format_exc()
- return { "error": tb, "id" : achievement["id"] }
-
- check = lambda x : x!=None and not "error" in x and (x["hidden"]==False or x["level"]>0)
-
- evaluatelist = [ea(achievement) for achievement in achievements]
-
- ret = {
- "achievements" : {
- x["id"] : x for x in evaluatelist if check(x)
- },
- "achievement_errors" : {
- x["id"] : x for x in evaluatelist if x!=None and "error" in x
- }
- }
-
- return render("json",ret),ret
-
- key = "/progress/"+str(user.id)
-
- if not force_generation:
- #in this case, we do not return the decoded json object - the caller has to take of this if needed
- return get_or_set(key,lambda:generate()[0]), None
- else:
- ret_str, ret = generate()
- set_value(key,ret_str)
- return ret_str, ret
-
-@view_config(route_name='get_progress', renderer='json')
-def get_progress(request):
- """get all relevant data concerning the user's progress"""
- user_id = int(request.matchdict["user_id"])
-
- user = User.get_user(user_id)
- if not user:
- raise NotFound("user not found")
-
- return _get_progress(user, force_generation=False)[0]
-
-@view_config(route_name='increase_value', renderer='json', request_method="POST")
-@view_config(route_name='increase_value_with_key', renderer='json', request_method="POST")
-def increase_value(request):
- """increase a value for the user"""
-
- user_id = int(request.matchdict["user_id"])
- try:
- value = float(request.POST["value"])
- except:
- raise APIError(400,"invalid_value","Invalid value provided")
-
- key = request.matchdict["key"] if "key" in request.matchdict else ""
- variable_name = request.matchdict["variable_name"]
-
- user = User.get_user(user_id)
- if not user:
- raise APIError(404, "user_not_found", "user not found")
-
- variable = Variable.get_variable_by_name(variable_name)
- if not variable:
- raise APIError(404, "variable_not_found", "variable not found")
-
- Value.increase_value(variable_name, user, value, key)
-
- output = _get_progress(user,force_generation=True)[1]
-
- for aid in output["achievements"].keys():
- if len(output["achievements"][aid]["new_levels"])>0:
- del output["achievements"][aid]["levels"]
- del output["achievements"][aid]["priority"]
- del output["achievements"][aid]["goals"]
- else:
- del output["achievements"][aid]
- return output
-
-@view_config(route_name="increase_multi_values", renderer="json", request_method="POST")
-def increase_multi_values(request):
- try:
- doc = request.json_body
- except:
- raise APIError(400, "invalid_json", "no valid json body")
- ret = {}
- for user_id, values in doc.items():
- user = User.get_user(user_id)
- if not user:
- raise APIError(404, "user_not_found", "user %s not found" % (user_id,))
-
- for variable_name, values_and_keys in values.items():
- for value_and_key in values_and_keys:
- variable = Variable.get_variable_by_name(variable_name)
-
- if not variable:
- raise APIError(404, "variable_not_found", "variable %s not found" % (variable_name,))
-
- if not 'value' in value_and_key:
- raise APIError(400, "variable_not_found", "illegal value for %s" % (variable_name,))
-
- value = value_and_key['value']
- key = value_and_key.get('key','')
-
- Value.increase_value(variable_name, user, value, key)
-
- output = _get_progress(user,force_generation=True)[1]
-
- for aid in output["achievements"].keys():
- if len(output["achievements"][aid]["new_levels"])>0:
- del output["achievements"][aid]["levels"]
- del output["achievements"][aid]["priority"]
- del output["achievements"][aid]["goals"]
- else:
- del output["achievements"][aid]
-
- if len(output["achievements"])>0 :
- ret[user_id]=output
-
- return ret
-
-@view_config(route_name='get_achievement_level', renderer='string', request_method="GET")
-def get_achievement_level(request):
- """get all information about an achievement for a specific level"""
- try:
- achievement_id = int(request.matchdict.get("achievement_id",None))
- level = int(request.matchdict.get("level",None))
- except:
- raise APIError(400, "invalid_input", "invalid input")
-
- def generate():
- achievement = Achievement.get_achievement(achievement_id)
-
- if not achievement:
- raise APIError(404, "achievement_not_found", "achievement not found")
-
- level_output = Achievement.basic_output(achievement, [], True, level).get("levels").get(str(level), {"properties":{},"rewards":{}})
- if "goals" in level_output:
- del level_output["goals"]
- if "level" in level_output:
- del level_output["level"]
- return render("json",level_output)
-
- key = "/achievement/"+str(achievement_id)+"/level/"+str(level)
- request.response.content_type = 'application/json'
-
- return get_or_set(key,generate)
-
-@view_config(route_name='admin')
-@wsgiapp2
-def admin(environ, start_response):
- return HTTPSProxied(DebuggedApplication(flaskadminapp.wsgi_app, True))(environ,start_response)
- #return
\ No newline at end of file
diff --git a/gengine_quickstart_template/production.ini b/gengine_quickstart_template/production.ini
index aa13c3d..49fcc8b 100644
--- a/gengine_quickstart_template/production.ini
+++ b/gengine_quickstart_template/production.ini
@@ -39,6 +39,13 @@ dogpile_cache.achievements_by_user_for_today.arguments.filename = achievements_b
dogpile_cache.translations.backend = dogpile.cache.dbm
dogpile_cache.translations.arguments.filename = translations.dbm
+dogpile_cache.achievements_users_levels.backend = dogpile.cache.dbm
+dogpile_cache.achievements_users_levels.arguments.filename = achievements_users_levels.dbm
+
+dogpile_cache.goal_evaluation.backend = dogpile.cache.dbm
+dogpile_cache.goal_evaluation.arguments.filename = goal_evaluation.dbm
+
+dogpile_cache.goal_statements.backend = dogpile.cache.memory
# memcache
urlcache_url = 127.0.0.1:11211
@@ -47,6 +54,16 @@ urlcache_active = true
# callback url, will be used for time-related leaderboard evaluations (daily,monthly,yearly) (TBD)
notify_progress =
+enable_user_authentication = false
+fallback_language = en
+gcm.api_key=
+gcm.package=
+apns.dev.key=
+apns.dev.certificate=
+apns.prod.key=
+apns.prod.certificate=
+push_title=Gamification Engine
+
###
# wsgi server configuration
###
diff --git a/optional-requirements.txt b/optional-requirements.txt
new file mode 100644
index 0000000..39258ea
--- /dev/null
+++ b/optional-requirements.txt
@@ -0,0 +1,11 @@
+argon2==0.1.10
+names==0.3.0
+pbr==2.0.0
+pg8000==1.10.6
+python-gcm==0.4
+redis==2.10.5
+requests==2.13.0
+tapns3==3.0.0
+testing.common.database==2.0.0
+testing.postgresql==1.3.0
+testing.redis==1.1.1
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..6e6912e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,44 @@
+alembic==0.9.1
+appdirs==1.4.3
+Chameleon==3.1
+click==6.7
+dogpile.cache==0.6.2
+Flask==0.12
+Flask-Admin==1.5.0
+hupper==0.4.4
+itsdangerous==0.24
+Jinja2==2.9.5
+jsl==0.2.4
+jsonschema==2.6.0
+Mako==1.0.6
+MarkupSafe==1.0
+mock==2.0.0
+packaging==16.8
+PasteDeploy==1.5.2
+pbr==2.0.0
+psycopg2==2.7.1
+Pygments==2.2.0
+pymemcache==1.4.2
+pyparsing==2.2.0
+pyramid==1.8.3
+pyramid-chameleon==0.3
+pyramid-debugtoolbar==3.0.5
+pyramid-dogpile-cache==0.0.4
+pyramid-mako==1.0.2
+pyramid-tm==1.1.1
+python-editor==1.0.3
+pytz==2016.10
+raven==6.0.0
+repoze.lru==0.6
+six==1.10.0
+SQLAlchemy==1.1.7
+transaction==2.1.2
+translationstring==1.3
+venusian==1.0
+waitress==1.0.2
+WebOb==1.7.2
+Werkzeug==0.12.1
+WTForms==2.1
+zope.deprecation==4.2.0
+zope.interface==4.3.3
+zope.sqlalchemy==0.7.7
diff --git a/setup.py b/setup.py
index b007ddc..96bdbdc 100644
--- a/setup.py
+++ b/setup.py
@@ -27,8 +27,11 @@
'pymemcache',
'mock',
'alembic',
- 'raven'
- ]
+ 'raven',
+ 'jsl',
+ 'jsonschema',
+ 'pyparsing',
+]
version = ''
with open('gengine/__init__.py', 'r') as fd:
@@ -48,10 +51,11 @@
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"Topic :: Software Development :: Libraries",
- "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
"License :: OSI Approved :: MIT License"
],
- author='Marcel Sander, Jens Janiuk',
+ author='Marcel Sander, Jens Janiuk, Matthias Feldotto',
author_email='marcel@gamification-software.com',
license='MIT',
url='https://www.gamification-software.com',
@@ -61,12 +65,30 @@
zip_safe=False,
test_suite='gengine',
install_requires=requires,
+ extras_require={
+ "auth": [
+ 'argon2'
+ ],
+ "pushes": [
+ 'tapns3',
+ 'python-gcm',
+ ],
+ "testing": [
+ 'testing.postgresql',
+ 'testing.redis',
+ 'names'
+ ]
+ },
entry_points="""\
[paste.app_factory]
main = gengine:main
[console_scripts]
- initialize_gengine_db = gengine.scripts.initializedb:main
- gengine_quickstart = gengine.scripts.quickstart:main
- generate_gengine_erd = gengine.scripts.generate_erd:main
+ initialize_gengine_db = gengine.maintenance.scripts.initializedb:main
+ gengine_quickstart = gengine.maintenance.scripts.quickstart:main
+ generate_gengine_erd = gengine.maintenance.scripts.generate_erd:main
+ generate_gengine_revision = gengine.maintenance.scripts.generate_revision:main
+ gengine_push_messages = gengine.maintenance.scripts.push_messages:main
+ [redgalaxy.plugins]
+ gengine = gengine:redgalaxy
""",
)