Commit 84b69a87 by Piotr Mitros

Merge; removed bad code

parents 804147c0 510f0286
...@@ -7,3 +7,4 @@ def dump_to_db(mongodb, events): ...@@ -7,3 +7,4 @@ def dump_to_db(mongodb, events):
## TODO: Error handling ## TODO: Error handling
collection = mongodb['event_log'] collection = mongodb['event_log']
collection.insert([e.event for e in events]) collection.insert([e.event for e in events])
...@@ -15,5 +15,5 @@ simplejson ...@@ -15,5 +15,5 @@ simplejson
South==0.7.6 South==0.7.6
django-celery==3.0.11 django-celery==3.0.11
celery-with-redis==3.0 celery-with-redis==3.0
-e git://github.com/MITx/django-pipeline.git@c5a4848d3d8fa90a7da4a4007f5653be40cccdd9#egg=django_pipeline-dev -e git://github.com/edx/django-pipeline.git@c5a4848d3d8fa90a7da4a4007f5653be40cccdd9#egg=django_pipeline-dev
-e git://github.com/MITx/django-staticfiles.git@6d2504e5c84a3003b4573e0ba0f11adf7583d372#egg=django_staticfiles-dev -e git://github.com/edx/django-staticfiles.git@6d2504e5c84a3003b4573e0ba0f11adf7583d372#egg=django_staticfiles-dev
''' Decorators for analytics modules. ''' Decorators for analytics modules.
@view defines a user-visible view @view defines a user-visible view
@query defines a machine-readable SOA @query defines a machine-readable SOA
...@@ -51,11 +51,11 @@ def event_handler(batch=True, per_user=False, per_resource=False, ...@@ -51,11 +51,11 @@ def event_handler(batch=True, per_user=False, per_resource=False,
def view(category = None, name = None, description = None, args = None): def view(category = None, name = None, description = None, args = None):
''' This decorator is appended to a view in an analytics module. A ''' This decorator is appended to a view in an analytics module. A
view will return HTML which will be shown to the user. view will return HTML which will be shown to the user.
category: Optional specification for type (global, per-user, category: Optional specification for type (global, per-user,
etc.). If not given, this will be extrapolated from the etc.). If not given, this will be extrapolated from the
argspec. This should typically be omitted. argspec. This should typically be omitted.
name: Optional specification for name shown to the user. This will name: Optional specification for name shown to the user. This will
default to function name. In most cases, this is recommended. default to function name. In most cases, this is recommended.
...@@ -64,7 +64,7 @@ def view(category = None, name = None, description = None, args = None): ...@@ -64,7 +64,7 @@ def view(category = None, name = None, description = None, args = None):
to the docstring. to the docstring.
args: Optional argspec for the function. This is generally better args: Optional argspec for the function. This is generally better
omitted. omitted.
''' '''
def view_factory(f): def view_factory(f):
registry.register_handler('view',category, name, description, f, args) registry.register_handler('view',category, name, description, f, args)
...@@ -74,20 +74,20 @@ def view(category = None, name = None, description = None, args = None): ...@@ -74,20 +74,20 @@ def view(category = None, name = None, description = None, args = None):
def query(category = None, name = None, description = None, args = None): def query(category = None, name = None, description = None, args = None):
''' This decorator is appended to a query in an analytics ''' This decorator is appended to a query in an analytics
module. A module will return output that can be used module. A module will return output that can be used
programmatically (typically JSON). programmatically (typically JSON).
category: Optional specification for type (global, per-user, category: Optional specification for type (global, per-user,
etc.). If not given, this will be extrapolated from the etc.). If not given, this will be extrapolated from the
argspec. This should typically be omitted. argspec. This should typically be omitted.
name: Optional specification for name exposed via SOA. This will name: Optional specification for name exposed via SOA. This will
default to function name. In most cases, this is recommended. default to function name. In most cases, this is recommended.
description: Optional description exposed via the SOA description: Optional description exposed via the SOA
discovery. If not given, this will default to the docstring. discovery. If not given, this will default to the docstring.
args: Optional argspec for the function. This is generally better args: Optional argspec for the function. This is generally better
omitted. omitted.
''' '''
def query_factory(f): def query_factory(f):
registry.register_handler('query',category, name, description, f, args) registry.register_handler('query',category, name, description, f, args)
...@@ -98,7 +98,7 @@ def query(category = None, name = None, description = None, args = None): ...@@ -98,7 +98,7 @@ def query(category = None, name = None, description = None, args = None):
def memoize_query(cache_time = 60*4, timeout = 60*15, ignores = ["<class 'pymongo.database.Database'>", "<class 'fs.osfs.OSFS'>"]): def memoize_query(cache_time = 60*4, timeout = 60*15, ignores = ["<class 'pymongo.database.Database'>", "<class 'fs.osfs.OSFS'>"]):
''' Call function only if we do not have the results for its execution already ''' Call function only if we do not have the results for its execution already
We ignore parameters of type pymongo.database.Database and fs.osfs.OSFS. These We ignore parameters of type pymongo.database.Database and fs.osfs.OSFS. These
will be different per call, but function identically. will be different per call, but function identically.
''' '''
def isuseful(a, ignores): def isuseful(a, ignores):
if str(type(a)) in ignores: if str(type(a)) in ignores:
...@@ -156,8 +156,8 @@ def memoize_query(cache_time = 60*4, timeout = 60*15, ignores = ["<class 'pymong ...@@ -156,8 +156,8 @@ def memoize_query(cache_time = 60*4, timeout = 60*15, ignores = ["<class 'pymong
def cron(period, params=None): def cron(period, params=None):
''' Run command periodically ''' Run command periodically
Unknown whether or how well this works. Unknown whether or how well this works.
''' '''
def factory(f): def factory(f):
@periodic_task(run_every=period, name=f.__name__) @periodic_task(run_every=period, name=f.__name__)
......
### HACK HACK HACK ### ### HACK HACK HACK ###
# #
# This is a horrible hack to make a render() function for modules. # This is a horrible hack to make a render() function for modules.
# #
# In the future, each module should run within its own space # In the future, each module should run within its own space
# (globals(), locals()), but for now, this kind of works. # (globals(), locals()), but for now, this kind of works.
# #
# This code: # This code:
# 1. Looks at the stack to figure out the calling module # 1. Looks at the stack to figure out the calling module
# 2. Matches that up to a module in INSTALLED_ANALYTICS_MODULE (based # 2. Matches that up to a module in INSTALLED_ANALYTICS_MODULE (based
# on longest matching path) # on longest matching path)
...@@ -13,12 +13,12 @@ ...@@ -13,12 +13,12 @@
# stored (this is part of setuptools) # stored (this is part of setuptools)
# 4. Renders the template with Mako # 4. Renders the template with Mako
# #
# I apologize about this code, but the alternative would be to # I apologize about this code, but the alternative would be to
# ship without this, in which case we'd accrue technical debt # ship without this, in which case we'd accrue technical debt
# with each new module written. # with each new module written.
# #
### HACK HACK HACK ### ### HACK HACK HACK ###
# #
# This file also has a static file finder for the modules. This is a # This file also has a static file finder for the modules. This is a
# bit less of a hack (although the implementation is still somewhat # bit less of a hack (although the implementation is still somewhat
# crude), but it sure doesn't belong in core.render. # crude), but it sure doesn't belong in core.render.
...@@ -36,6 +36,7 @@ from pkg_resources import resource_filename ...@@ -36,6 +36,7 @@ from pkg_resources import resource_filename
from mako.lookup import TemplateLookup from mako.lookup import TemplateLookup
from django.conf import settings from django.conf import settings
## Code borrowed from mitx/common/lib/tempdir ## Code borrowed from mitx/common/lib/tempdir
def mkdtemp_clean(suffix="", prefix="tmp", dir=None): def mkdtemp_clean(suffix="", prefix="tmp", dir=None):
"""Just like mkdtemp, but the directory will be deleted when the process ends.""" """Just like mkdtemp, but the directory will be deleted when the process ends."""
...@@ -43,6 +44,7 @@ def mkdtemp_clean(suffix="", prefix="tmp", dir=None): ...@@ -43,6 +44,7 @@ def mkdtemp_clean(suffix="", prefix="tmp", dir=None):
atexit.register(cleanup_tempdir, the_dir) atexit.register(cleanup_tempdir, the_dir)
return the_dir return the_dir
def cleanup_tempdir(the_dir): def cleanup_tempdir(the_dir):
"""Called on process exit to remove a temp directory.""" """Called on process exit to remove a temp directory."""
if os.path.exists(the_dir): if os.path.exists(the_dir):
...@@ -53,30 +55,41 @@ if module_directory is None: ...@@ -53,30 +55,41 @@ if module_directory is None:
module_directory = mkdtemp_clean() module_directory = mkdtemp_clean()
lookups = {} lookups = {}
def lookup(directory): def lookup(directory):
if directory in lookups: if directory in lookups:
return lookups[directory] return lookups[directory]
else: else:
l = TemplateLookup(directories = [directory], l = TemplateLookup(directories=[directory],
module_directory = module_directory, module_directory=module_directory,
output_encoding='utf-8', output_encoding='utf-8',
input_encoding='utf-8', input_encoding='utf-8',
encoding_errors='replace') encoding_errors='replace')
lookups[directory] = l lookups[directory] = l
return l return l
def render(templatefile, context, caller = None):
def render(templatefile, context, caller=None):
''' Render a template within a module.
1) Figure out the module this is being called from.
2) Use resource_filename from packagetools to figure out that module's templates directory
3) Use the mako template engine to render it.
Hacks:
1) We are not caching anything. This will cause performance issues for deployment.
2) The way we inspect the stack is quite messy. We should do this better.
'''
stack = traceback.extract_stack() stack = traceback.extract_stack()
if not caller: if not caller:
caller_path = os.path.abspath(stack[-2][0]) caller_path = os.path.abspath(stack[-2][0])
# For testing, use: sys.modules.keys() if sys.modules[module] and '__file__' in sys.modules[module].__dict__]# # For testing, use: sys.modules.keys() if sys.modules[module] and '__file__' in sys.modules[module].__dict__]#
analytics_modules = [sys.modules[module] for module in settings.INSTALLED_ANALYTICS_MODULES] analytics_modules = [sys.modules[module] for module in settings.INSTALLED_ANALYTICS_MODULES]
analytics_modules.sort(key = lambda x : len(os.path.commonprefix([x.__file__, os.path.abspath(caller_path)]))) analytics_modules.sort(key=lambda x: len(os.path.commonprefix([os.path.abspath(x.__file__), os.path.abspath(caller_path)])))
caller_module = analytics_modules[-1] caller_module = analytics_modules[-1]
caller_name = caller_module.__name__ caller_name = caller_module.__name__
template_directory = os.path.abspath(resource_filename(caller_name, "templates")) template_directory = os.path.abspath(resource_filename(caller_name, "templates"))
template = lookup(template_directory).get_template(templatefile) template = lookup(template_directory).get_template(templatefile)
return template.render_unicode(**context) return template.render_unicode(**context)
...@@ -86,11 +99,12 @@ from django.contrib.staticfiles.finders import BaseFinder ...@@ -86,11 +99,12 @@ from django.contrib.staticfiles.finders import BaseFinder
from django.contrib.staticfiles import utils from django.contrib.staticfiles import utils
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
class ModuleStorage(FileSystemStorage): class ModuleStorage(FileSystemStorage):
''' Storage which allows static files to live with prefixes in the ''' Storage which allows static files to live with prefixes in the
static directory which do not exist in the filesystem. A file can static directory which do not exist in the filesystem. A file can
exist as /modules/foo/static/bar.html in the source filesystem, exist as /modules/foo/static/bar.html in the source filesystem,
but as /djmodules/foo/bar.html in the static_files directory. but as /djmodules/foo/bar.html in the static_files directory.
''' '''
def path(self, name): def path(self, name):
''' Returns the absolute path to a file, stripping out ''' Returns the absolute path to a file, stripping out
...@@ -109,9 +123,10 @@ class ModuleStorage(FileSystemStorage): ...@@ -109,9 +123,10 @@ class ModuleStorage(FileSystemStorage):
return ["djmodules"], [] return ["djmodules"], []
elif path in ["djmodules", "djmodules/", "/djmodules", "/djmodules/"]: elif path in ["djmodules", "djmodules/", "/djmodules", "/djmodules/"]:
return [self.base_url.split('/')[1]], [] return [self.base_url.split('/')[1]], []
else: else:
return FileSystemStorage.listdir(self, path) return FileSystemStorage.listdir(self, path)
class ModuleFileFinder(BaseFinder): class ModuleFileFinder(BaseFinder):
''' Finds the static files for all installed analytics ''' Finds the static files for all installed analytics
modules. Does magic to put them in the right place in the URL modules. Does magic to put them in the right place in the URL
...@@ -122,7 +137,7 @@ class ModuleFileFinder(BaseFinder): ...@@ -122,7 +137,7 @@ class ModuleFileFinder(BaseFinder):
self.load_static() self.load_static()
def load_static(self): def load_static(self):
''' Walk all modules. Find paths. Create Django storages for them (Django's poorman's pyfs). ''' Walk all modules. Find paths. Create Django storages for them (Django's poorman's pyfs).
''' '''
self.module_paths = [(module.split('.')[-1], os.path.abspath(resource_filename(module, "static"))) for module in settings.INSTALLED_ANALYTICS_MODULES] self.module_paths = [(module.split('.')[-1], os.path.abspath(resource_filename(module, "static"))) for module in settings.INSTALLED_ANALYTICS_MODULES]
self.static_paths = [(module, path, ModuleStorage(path, os.path.join("djmodules", module))) for module, path in self.module_paths] self.static_paths = [(module, path, ModuleStorage(path, os.path.join("djmodules", module))) for module, path in self.module_paths]
...@@ -142,5 +157,3 @@ class ModuleFileFinder(BaseFinder): ...@@ -142,5 +157,3 @@ class ModuleFileFinder(BaseFinder):
for module, path, storage in self.static_paths: for module, path, storage in self.static_paths:
for path in utils.get_files(storage, ignore_patterns): for path in utils.get_files(storage, ignore_patterns):
yield path, storage yield path, storage
...@@ -149,19 +149,13 @@ def djt_event_property_check(cache, events): ...@@ -149,19 +149,13 @@ def djt_event_property_check(cache, events):
if "event_property_check" in evt: if "event_property_check" in evt:
cache.set("last_seen_user", evt.djt_agent, 30) cache.set("last_seen_user", evt.djt_agent, 30)
@query(name='djt_fake_user_count') @query()
def djt_fake_user_count_query(): def djt_fake_user_count():
return 2 return 2
@view(name='djt_fake_user_count') @view()
def djt_fake_user_count_view(query): def djt_fake_user_count(query):
''' Test of an abstraction used to call queries, abstracting away ''' Test of an abstraction used to call queries, abstracting away
the network, as well as optional parameters like fs, mongodb, etc. the network, as well as optional parameters like fs, db, etc.
''' '''
return "<html>Users: {uc}</html>".format(uc = query.djt_fake_user_count()) return "<html>Users: {uc}</html>".format(uc = query.djt_fake_user_count())
@event_handler()
def router(events):
for e in events:
if e.university == "Harvard":
track_event(e)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment